jomasego commited on
Commit
e889541
·
verified ·
1 Parent(s): ad0ebea

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,3 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
1
  # PowerPoint Template Customizer
2
 
3
  Generate styled presentations from a user-provided `.pptx` design template, guided by LLM-produced structured slide JSON.
@@ -33,10 +44,15 @@ python app.py
33
 
34
  ## Backend Options
35
  Set `MODEL_BACKEND` environment variable:
36
- - `transformers` (default): uses `unsloth/gemma-4-12b-it` through Hugging Face Transformers.
37
- - `llama.cpp`: uses local GGUF via `LLAMA_CPP_MODEL_PATH`.
38
  - `vllm`: uses OpenAI-compatible endpoint (`VLLM_BASE_URL`, `VLLM_MODEL_NAME`).
39
 
 
 
 
 
 
40
  ## Quick Local Backend Smoke Test (`llama.cpp`)
41
  ```bash
42
  pip install llama-cpp-python huggingface_hub openai
@@ -50,6 +66,13 @@ LLAMA_CPP_MODEL_PATH=/absolute/path/to/model.gguf \
50
  python scripts/local_backend_smoke.py
51
  ```
52
 
 
 
 
 
 
 
 
53
  ## Tests
54
  ```bash
55
  pytest -q
 
1
+ ---
2
+ title: PowerPoint Template Customizer
3
+ emoji: "📊"
4
+ colorFrom: blue
5
+ colorTo: gray
6
+ sdk: gradio
7
+ sdk_version: 5.34.2
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
  # PowerPoint Template Customizer
13
 
14
  Generate styled presentations from a user-provided `.pptx` design template, guided by LLM-produced structured slide JSON.
 
44
 
45
  ## Backend Options
46
  Set `MODEL_BACKEND` environment variable:
47
+ - `llama.cpp` (default): uses GGUF and auto-resolves from `unsloth/gemma-4-12b-it-GGUF` when `LLAMA_CPP_MODEL_PATH` is not set.
48
+ - `transformers`: optional fallback that uses `unsloth/gemma-4-12b-it` through Hugging Face Transformers.
49
  - `vllm`: uses OpenAI-compatible endpoint (`VLLM_BASE_URL`, `VLLM_MODEL_NAME`).
50
 
51
+ GGUF-related environment variables:
52
+ - `GGUF_MODEL_REPO` (default: `unsloth/gemma-4-12b-it-GGUF`)
53
+ - `GGUF_CACHE_DIR` (default: `models`)
54
+ - `LLAMA_CPP_MODEL_PATH` (optional explicit path override)
55
+
56
  ## Quick Local Backend Smoke Test (`llama.cpp`)
57
  ```bash
58
  pip install llama-cpp-python huggingface_hub openai
 
66
  python scripts/local_backend_smoke.py
67
  ```
68
 
69
+ Or rely on auto-download from Hugging Face:
70
+ ```bash
71
+ MODEL_BACKEND=llama.cpp \
72
+ GGUF_MODEL_REPO=unsloth/gemma-4-12b-it-GGUF \
73
+ python scripts/local_backend_smoke.py
74
+ ```
75
+
76
  ## Tests
77
  ```bash
78
  pytest -q
requirements.txt CHANGED
@@ -3,4 +3,7 @@ transformers
3
  torch
4
  accelerate
5
  python-pptx
6
- spaces
 
 
 
 
3
  torch
4
  accelerate
5
  python-pptx
6
+ spaces
7
+ llama-cpp-python
8
+ huggingface_hub
9
+ openai
scripts/local_backend_smoke.py CHANGED
@@ -19,9 +19,15 @@ def main() -> int:
19
  os.environ.setdefault("MODEL_BACKEND", backend)
20
 
21
  if backend == "llama.cpp" and not os.getenv("LLAMA_CPP_MODEL_PATH"):
22
- candidate = repo_root / "models" / "Qwen2.5-0.5B-Instruct-Q4_K_M.gguf"
23
- if candidate.exists():
24
- os.environ["LLAMA_CPP_MODEL_PATH"] = str(candidate)
 
 
 
 
 
 
25
 
26
  output = customize_presentation(
27
  str(template),
 
19
  os.environ.setdefault("MODEL_BACKEND", backend)
20
 
21
  if backend == "llama.cpp" and not os.getenv("LLAMA_CPP_MODEL_PATH"):
22
+ candidates = [
23
+ repo_root / "models" / "gemma-4-12b-it-Q4_K_M.gguf",
24
+ repo_root / "models" / "Gemma-4-12b-it-Q4_K_M.gguf",
25
+ repo_root / "models" / "Qwen2.5-0.5B-Instruct-Q4_K_M.gguf",
26
+ ]
27
+ for candidate in candidates:
28
+ if candidate.exists():
29
+ os.environ["LLAMA_CPP_MODEL_PATH"] = str(candidate)
30
+ break
31
 
32
  output = customize_presentation(
33
  str(template),
src/llm/gemma_client.py CHANGED
@@ -1,8 +1,8 @@
1
  from __future__ import annotations
2
 
3
- import json
4
  import os
5
  from dataclasses import dataclass
 
6
 
7
  import spaces
8
  import torch
@@ -22,10 +22,11 @@ SYSTEM_PROMPT = (
22
 
23
  @dataclass
24
  class GemmaClientConfig:
25
- model_id: str = "unsloth/gemma-4-12b-it"
 
26
  temperature: float = 0.4
27
  max_new_tokens: int = 900
28
- backend: str = "transformers" # transformers | llama.cpp | vllm
29
 
30
 
31
  class GemmaClient:
@@ -38,7 +39,7 @@ class GemmaClient:
38
  if self._model is not None and self._tokenizer is not None:
39
  return
40
 
41
- model_id = self.config.model_id
42
  self._tokenizer = AutoTokenizer.from_pretrained(model_id)
43
 
44
  kwargs = {
@@ -98,9 +99,7 @@ class GemmaClient:
98
  "llama.cpp backend requested but llama-cpp-python is not installed."
99
  ) from exc
100
 
101
- model_path = os.getenv("LLAMA_CPP_MODEL_PATH")
102
- if not model_path:
103
- raise RuntimeError("Set LLAMA_CPP_MODEL_PATH to a local GGUF file for llama.cpp backend.")
104
 
105
  llm = Llama(
106
  model_path=model_path,
@@ -118,6 +117,52 @@ class GemmaClient:
118
  )
119
  return completion["choices"][0]["message"]["content"].strip()
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def _generate_vllm(self, user_prompt: str) -> str:
122
  try:
123
  from openai import OpenAI
@@ -159,7 +204,7 @@ _DEFAULT_CLIENT: GemmaClient | None = None
159
  def load_model(backend: str | None = None) -> GemmaClient:
160
  global _DEFAULT_CLIENT
161
  if _DEFAULT_CLIENT is None or (backend and _DEFAULT_CLIENT.config.backend != backend):
162
- cfg = GemmaClientConfig(backend=backend or os.getenv("MODEL_BACKEND", "transformers"))
163
  _DEFAULT_CLIENT = GemmaClient(cfg)
164
  return _DEFAULT_CLIENT
165
 
 
1
  from __future__ import annotations
2
 
 
3
  import os
4
  from dataclasses import dataclass
5
+ from pathlib import Path
6
 
7
  import spaces
8
  import torch
 
22
 
23
  @dataclass
24
  class GemmaClientConfig:
25
+ model_id: str = "unsloth/gemma-4-12b-it-GGUF"
26
+ transformers_model_id: str = "unsloth/gemma-4-12b-it"
27
  temperature: float = 0.4
28
  max_new_tokens: int = 900
29
+ backend: str = "llama.cpp" # llama.cpp | transformers | vllm
30
 
31
 
32
  class GemmaClient:
 
39
  if self._model is not None and self._tokenizer is not None:
40
  return
41
 
42
+ model_id = self.config.transformers_model_id
43
  self._tokenizer = AutoTokenizer.from_pretrained(model_id)
44
 
45
  kwargs = {
 
99
  "llama.cpp backend requested but llama-cpp-python is not installed."
100
  ) from exc
101
 
102
+ model_path = self._resolve_llama_cpp_model_path()
 
 
103
 
104
  llm = Llama(
105
  model_path=model_path,
 
117
  )
118
  return completion["choices"][0]["message"]["content"].strip()
119
 
120
+ def _resolve_llama_cpp_model_path(self) -> str:
121
+ env_model_path = os.getenv("LLAMA_CPP_MODEL_PATH")
122
+ if env_model_path:
123
+ return env_model_path
124
+
125
+ preferred = [
126
+ "Q4_K_M.gguf",
127
+ "q4_k_m.gguf",
128
+ "Q4_K_S.gguf",
129
+ "q4_k_s.gguf",
130
+ "Q5_K_M.gguf",
131
+ "q5_k_m.gguf",
132
+ ]
133
+
134
+ try:
135
+ from huggingface_hub import HfApi, hf_hub_download
136
+ except Exception as exc:
137
+ raise RuntimeError(
138
+ "llama.cpp backend requires either LLAMA_CPP_MODEL_PATH or huggingface_hub to auto-download GGUF."
139
+ ) from exc
140
+
141
+ repo_id = os.getenv("GGUF_MODEL_REPO", self.config.model_id)
142
+ files = HfApi(token=os.getenv("HF_TOKEN")).list_repo_files(repo_id=repo_id)
143
+ gguf_files = [f for f in files if f.lower().endswith(".gguf")]
144
+ if not gguf_files:
145
+ raise RuntimeError(f"No GGUF files found in repo: {repo_id}")
146
+
147
+ selected = None
148
+ for suffix in preferred:
149
+ selected = next((f for f in gguf_files if f.endswith(suffix)), None)
150
+ if selected:
151
+ break
152
+ if selected is None:
153
+ selected = gguf_files[0]
154
+
155
+ local_dir = Path(os.getenv("GGUF_CACHE_DIR", "models"))
156
+ local_dir.mkdir(parents=True, exist_ok=True)
157
+ downloaded = hf_hub_download(
158
+ repo_id=repo_id,
159
+ filename=selected,
160
+ local_dir=str(local_dir),
161
+ token=os.getenv("HF_TOKEN"),
162
+ )
163
+ os.environ["LLAMA_CPP_MODEL_PATH"] = downloaded
164
+ return downloaded
165
+
166
  def _generate_vllm(self, user_prompt: str) -> str:
167
  try:
168
  from openai import OpenAI
 
204
  def load_model(backend: str | None = None) -> GemmaClient:
205
  global _DEFAULT_CLIENT
206
  if _DEFAULT_CLIENT is None or (backend and _DEFAULT_CLIENT.config.backend != backend):
207
+ cfg = GemmaClientConfig(backend=backend or os.getenv("MODEL_BACKEND", "llama.cpp"))
208
  _DEFAULT_CLIENT = GemmaClient(cfg)
209
  return _DEFAULT_CLIENT
210
 
src/services/customization_service.py CHANGED
@@ -31,7 +31,7 @@ def customize_presentation(template_input, user_input: str) -> str:
31
  template_path = _resolve_template_path(template_input)
32
 
33
  style_profile = parse_template_style(template_path)
34
- backend = os.getenv("MODEL_BACKEND", "transformers")
35
  slides_json = generate_slides_json(user_input.strip(), backend=backend)
36
 
37
  output_path = compile_presentation(
 
31
  template_path = _resolve_template_path(template_input)
32
 
33
  style_profile = parse_template_style(template_path)
34
+ backend = os.getenv("MODEL_BACKEND", "llama.cpp")
35
  slides_json = generate_slides_json(user_input.strip(), backend=backend)
36
 
37
  output_path = compile_presentation(