README.md CHANGED
@@ -4,22 +4,15 @@ emoji: 💬
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: "6.5.1"
8
- python_version: "3.11.13"
9
  app_file: app.py
10
  pinned: false
11
- hf_oauth: true
12
- hf_oauth_scopes:
13
- - inference-api
14
  license: apache-2.0
15
  short_description: "a compact Vision-Language Model"
16
- startup_duration_timeout: 1h
17
- preload_from_hub:
18
- - tencent/Penguin-VL-8B
19
  ---
20
 
21
- This Space runs Penguin-VL-8B and preloads model artifacts from `tencent/Penguin-VL-8B`.
22
 
23
- On dedicated GPU hardware, the app also loads the model into GPU memory during startup so users can interact without waiting for the first request to initialize the model.
24
-
25
- If you move this Space back to ZeroGPU later, restore the `@spaces.GPU(...)` path and set `PRELOAD_MODEL_ON_STARTUP=0` in the Space variables.
 
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: "6.11.0"
8
+ python_version: "3.12.12"
9
  app_file: app.py
10
  pinned: false
 
 
 
11
  license: apache-2.0
12
  short_description: "a compact Vision-Language Model"
 
 
 
13
  ---
14
 
15
+ This Space runs Penguin-VL-8B on ZeroGPU.
16
 
17
+ The model is loaded into CPU memory at startup. ZeroGPU transparently migrates
18
+ it to GPU when a request is processed via `@spaces.GPU`.
 
app.py CHANGED
@@ -1,9 +1,10 @@
 
1
  import os
2
  import warnings
3
 
4
  from inference.interface import PenguinVLQwen3GradioInterface
5
  from inference.server import PenguinVLQwen3DirectClient
6
- from inference.server.direct_client import ensure_flash_attn_installed, preload_model
7
 
8
  warnings.filterwarnings(
9
  "ignore",
@@ -14,12 +15,7 @@ warnings.filterwarnings(
14
 
15
  def main():
16
  model_path = os.getenv("MODEL_PATH", "tencent/Penguin-VL-8B")
17
- ensure_flash_attn_installed()
18
- if os.getenv("PRELOAD_MODEL_ON_STARTUP", "1") == "1":
19
- try:
20
- preload_model(model_path)
21
- except Exception as exc:
22
- print(f"Startup model preload skipped: {exc}")
23
 
24
  model_client = PenguinVLQwen3DirectClient(
25
  model_path=model_path,
 
1
+ import spaces # noqa: F401 — Must precede torch for ZeroGPU monkey-patching
2
  import os
3
  import warnings
4
 
5
  from inference.interface import PenguinVLQwen3GradioInterface
6
  from inference.server import PenguinVLQwen3DirectClient
7
+ from inference.server.direct_client import preload_model
8
 
9
  warnings.filterwarnings(
10
  "ignore",
 
15
 
16
  def main():
17
  model_path = os.getenv("MODEL_PATH", "tencent/Penguin-VL-8B")
18
+ preload_model(model_path)
 
 
 
 
 
19
 
20
  model_client = PenguinVLQwen3DirectClient(
21
  model_path=model_path,
inference/interface/gradio_interface.py CHANGED
@@ -1,4 +1,3 @@
1
- import os
2
  import os.path as osp
3
 
4
  import gradio as gr
@@ -10,233 +9,156 @@ Developed by [Penguin-VL](https://github.com/tencent-ailab/Penguin-VL) team at T
10
 
11
  Note: speed on ZeroGPU does not reflect real model speed and may be influenced by the shared environment. For stable and fast Gradio Space deployment and running, please visit [the local UI instructions](https://github.com/tencent-ailab/Penguin-VL?tab=readme-ov-file#-gradio-demo-local-ui). For usage examples and expected results, please refer to [here](https://github.com/tencent-ailab/Penguin-VL/blob/master/inference/notebooks/01_penguinvl_inference_recipes.public.ipynb).
12
 
13
- Please login with your Hugging Face account first. We provide some example images and videos for easier trials.
14
  """
15
 
 
 
16
 
17
- class PenguinVLQwen3GradioInterface(object):
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  def __init__(self, model_client, example_dir=None, default_system_prompt="You are a helpful assistant developed by Tencent AI Lab PenguinVL team.", **server_kwargs):
20
  self.model_client = model_client
21
  self.server_kwargs = server_kwargs
22
  self.default_system_prompt = (default_system_prompt or "").strip()
23
 
24
- self.image_formats = ("png", "jpg", "jpeg")
25
- self.video_formats = ("mp4", "mov")
26
- image_examples, video_examples = [], []
27
- if example_dir is not None:
28
- example_files = [
29
- osp.join(example_dir, f) for f in os.listdir(example_dir)
30
- ]
31
- for example_file in example_files:
32
- if example_file.endswith(self.image_formats):
33
- image_examples.append([example_file])
34
- elif example_file.endswith(self.video_formats):
35
- video_examples.append([example_file])
36
-
37
- with gr.Blocks() as self.interface:
38
- gr.Markdown(HEADER)
39
- with gr.Row():
40
- chatbot_kwargs = {"elem_id": "chatbot", "height": 710}
41
- try:
42
- chatbot = gr.Chatbot(type="messages", **chatbot_kwargs)
43
- except TypeError:
44
- # Gradio 6 uses OpenAI-style messages by default and removed the `type` arg.
45
- chatbot = gr.Chatbot(**chatbot_kwargs)
46
-
47
- with gr.Column():
48
- with gr.Tab(label="Input"):
49
-
50
- with gr.Row():
51
- input_video = gr.Video(sources=["upload"], label="Upload Video")
52
- input_image = gr.Image(sources=["upload"], type="filepath", label="Upload Image")
53
-
54
- if len(image_examples):
55
- gr.Examples(image_examples, inputs=[input_image], label="Example Images")
56
- if len(video_examples):
57
- gr.Examples(video_examples, inputs=[input_video], label="Example Videos")
58
-
59
- input_text = gr.Textbox(label="Input Text", placeholder="Type your message here and press enter to submit")
60
-
61
- submit_button = gr.Button("Generate")
62
-
63
- with gr.Tab(label="Configure"):
64
- with gr.Accordion("Prompt Config", open=True):
65
- system_prompt = gr.Textbox(
66
- value=self.default_system_prompt,
67
- label="System Prompt",
68
- lines=4,
69
- placeholder="Optional: system instruction prepended to each request",
70
- )
71
-
72
- with gr.Accordion("Generation Config", open=True):
73
- do_sample = gr.Checkbox(value=True, label="Do Sample")
74
- temperature = gr.Slider(minimum=0.0, maximum=1.0, value=0.1, label="Temperature")
75
- top_p = gr.Slider(minimum=0.0, maximum=1.0, value=0.9, label="Top P")
76
- max_new_tokens = gr.Slider(minimum=0, maximum=4096, value=1536, step=1, label="Max New Tokens")
77
-
78
- with gr.Accordion("Video Config", open=True):
79
- fps = gr.Slider(minimum=0.0, maximum=10.0, value=1, label="FPS")
80
- max_frames = gr.Slider(minimum=0, maximum=256, value=180, step=1, label="Max Frames")
81
-
82
- input_video.change(self._on_video_upload, [chatbot, input_video], [chatbot, input_video])
83
- input_image.change(self._on_image_upload, [chatbot, input_image], [chatbot, input_image])
84
- input_text.submit(
85
- self._predict,
86
- [
87
- chatbot, input_text, system_prompt, do_sample, temperature, top_p, max_new_tokens,
88
- fps, max_frames,
89
- ],
90
- [chatbot, input_text],
91
- )
92
- submit_button.click(
93
- self._predict,
94
- [
95
- chatbot, input_text, system_prompt, do_sample, temperature, top_p, max_new_tokens,
96
- fps, max_frames,
97
- ],
98
- [chatbot, input_text],
99
- )
100
-
101
- def _on_video_upload(self, messages, video):
102
- messages = messages or []
103
- if video is not None:
104
- # messages.append({"role": "user", "content": gr.Video(video)})
105
- messages.append({"role": "user", "content": {"path": video}})
106
- return messages, None
107
-
108
- def _on_image_upload(self, messages, image):
109
- messages = messages or []
110
- if image is not None:
111
- # messages.append({"role": "user", "content": gr.Image(image)})
112
- messages.append({"role": "user", "content": {"path": image}})
113
- return messages, None
114
-
115
- def _on_text_submit(self, messages, text):
116
- messages = messages or []
117
- messages.append({"role": "user", "content": text})
118
- return messages, ""
119
-
120
- def _extract_media_path(self, content):
121
- if isinstance(content, dict):
122
- if content.get("type") == "text" and isinstance(content.get("text"), str):
123
- raise ValueError(f"Text content is not media: {content}")
124
- media_path = content.get("path")
125
- if media_path:
126
- return media_path
127
- for value in content.values():
128
- try:
129
- return self._extract_media_path(value)
130
- except ValueError:
131
- continue
132
-
133
- if isinstance(content, (list, tuple)) and len(content) > 0:
134
- for item in content:
135
- try:
136
- return self._extract_media_path(item)
137
- except ValueError:
138
- continue
139
-
140
- raise ValueError(f"Unsupported media content: {content}")
141
-
142
- def _extract_text_content(self, content):
143
  if isinstance(content, str):
144
- return content
145
 
146
  if isinstance(content, dict):
147
- if content.get("type") == "text" and isinstance(content.get("text"), str):
148
- return content["text"]
 
 
 
 
 
149
  text = content.get("text")
150
  if isinstance(text, str):
151
- return text
152
 
153
- if isinstance(content, (list, tuple)) and len(content) > 0:
154
- text_parts = []
155
  for item in content:
156
- try:
157
- text_parts.append(self._extract_text_content(item))
158
- except ValueError:
159
- continue
160
- if text_parts:
161
- return "\n".join(part for part in text_parts if part)
162
 
163
- raise ValueError(f"Unsupported text content: {content}")
164
 
165
- def _normalize_user_content(self, content, fps, max_frames):
166
- if isinstance(content, str):
167
- return [{"type": "text", "text": content}]
168
-
169
- if isinstance(content, (list, tuple)):
170
- normalized_items = []
171
- for item in content:
172
- normalized_items.extend(self._normalize_user_content(item, fps, max_frames))
173
- return normalized_items
174
-
175
- if isinstance(content, dict):
176
- try:
177
- text = self._extract_text_content(content)
178
- except ValueError:
179
- text = None
180
- else:
181
- return [{"type": "text", "text": text}]
182
 
183
- media_path = self._extract_media_path(content)
184
- media_ext = osp.splitext(media_path)[1].lower().lstrip(".")
185
- if media_ext in self.video_formats:
186
- return [{"type": "video", "video": {"video_path": media_path, "fps": fps, "max_frames": max_frames}}]
187
- if media_ext in self.image_formats:
188
- return [{"type": "image", "image": {"image_path": media_path}}]
189
- raise ValueError(f"Unsupported media type: {media_path}")
190
-
191
- raise ValueError(f"Unsupported user content: {content}")
192
-
193
- def _predict(self, messages, input_text, system_prompt, do_sample, temperature, top_p, max_new_tokens,
194
- fps, max_frames):
195
- messages = list(messages or [])
196
- input_text = input_text or ""
197
- if input_text and len(input_text) > 0:
198
- messages.append({"role": "user", "content": input_text})
199
- new_messages = []
200
  active_system_prompt = (system_prompt or self.default_system_prompt).strip()
201
  if active_system_prompt:
202
- new_messages.append({
203
  "role": "system",
204
  "content": [{"type": "text", "text": active_system_prompt}],
205
  })
206
 
207
- contents = []
208
- for message in messages:
209
- if message["role"] == "assistant":
210
- if len(contents):
211
- new_messages.append({"role": "user", "content": contents})
212
- contents = []
213
- new_messages.append(message)
214
- elif message["role"] == "user":
215
- contents.extend(self._normalize_user_content(message["content"], fps, max_frames))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
- if len(contents):
218
- new_messages.append({"role": "user", "content": contents})
219
 
220
- if len(new_messages) == 0 or new_messages[-1]["role"] != "user":
221
- return messages
 
222
 
223
  generation_config = {
224
  "do_sample": do_sample,
225
  "temperature": temperature,
226
  "top_p": top_p,
227
- "max_new_tokens": max_new_tokens
228
  }
229
 
230
- response = self.model_client.submit({"conversation": new_messages, "generation_config": generation_config})
231
  if isinstance(response, str):
232
- messages.append({"role": "assistant", "content": response})
233
- yield messages, ""
234
  return
235
 
236
- messages.append({"role": "assistant", "content": ""})
237
  for token in response:
238
- messages[-1]['content'] += token
239
- yield messages, ""
240
 
241
  def launch(self):
242
- self.interface.launch(**self.server_kwargs)
 
 
1
  import os.path as osp
2
 
3
  import gradio as gr
 
9
 
10
  Note: speed on ZeroGPU does not reflect real model speed and may be influenced by the shared environment. For stable and fast Gradio Space deployment and running, please visit [the local UI instructions](https://github.com/tencent-ailab/Penguin-VL?tab=readme-ov-file#-gradio-demo-local-ui). For usage examples and expected results, please refer to [here](https://github.com/tencent-ailab/Penguin-VL/blob/master/inference/notebooks/01_penguinvl_inference_recipes.public.ipynb).
11
 
12
+ Please login with your Hugging Face account first.
13
  """
14
 
15
+ IMAGE_FORMATS = ("png", "jpg", "jpeg")
16
+ VIDEO_FORMATS = ("mp4", "mov")
17
 
18
+ # (filename, prompt) pairs sourced from the official inference notebook.
19
+ EXAMPLE_PAIRS = [
20
+ ("leetcode.png", "please think this problem step by step and give the python code solution"),
21
+ ("newspaper.png", "please output the text in the image"),
22
+ ("horse_poet.png", "Write a short poem inspired by this image. Capture the relationship between the man and the horse, as well as the traditional, historical atmosphere of the painting."),
23
+ ("2b_table_result.png", "please output the table content in markdown format."),
24
+ ("chart_understanding.png", "Look at the 'Nonmetropolitan' line. In what approximate year does it reach its absolute lowest point on the chart, and what is the approximate percent change at that time?"),
25
+ ("video-example.mp4", "please describe the video in details"),
26
+ ("polar_bear.mp4", "Describe what happens in this video."),
27
+ ]
28
+
29
+
30
+ class PenguinVLQwen3GradioInterface:
31
 
32
  def __init__(self, model_client, example_dir=None, default_system_prompt="You are a helpful assistant developed by Tencent AI Lab PenguinVL team.", **server_kwargs):
33
  self.model_client = model_client
34
  self.server_kwargs = server_kwargs
35
  self.default_system_prompt = (default_system_prompt or "").strip()
36
 
37
+ examples = []
38
+ if example_dir:
39
+ for filename, prompt in EXAMPLE_PAIRS:
40
+ path = osp.join(example_dir, filename)
41
+ if osp.isfile(path):
42
+ examples.append([{"text": prompt, "files": [path]}])
43
+
44
+ self.interface = gr.ChatInterface(
45
+ fn=self._predict,
46
+ multimodal=True,
47
+ description=HEADER,
48
+ additional_inputs=[
49
+ gr.Textbox(label="System Prompt", value=self.default_system_prompt, lines=4),
50
+ gr.Checkbox(label="Do Sample", value=True),
51
+ gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, value=0.1),
52
+ gr.Slider(label="Top P", minimum=0.0, maximum=1.0, value=0.9),
53
+ gr.Slider(label="Max New Tokens", minimum=0, maximum=4096, value=1536, step=1),
54
+ gr.Slider(label="FPS (video)", minimum=0.0, maximum=10.0, value=1),
55
+ gr.Slider(label="Max Frames (video)", minimum=0, maximum=256, value=180, step=1),
56
+ ],
57
+ additional_inputs_accordion=gr.Accordion(label="Settings", open=False),
58
+ examples=examples or None,
59
+ )
60
+
61
+ def _classify_file(self, path):
62
+ ext = osp.splitext(path)[1].lower().lstrip(".")
63
+ if ext in VIDEO_FORMATS:
64
+ return "video"
65
+ if ext in IMAGE_FORMATS:
66
+ return "image"
67
+ return None
68
+
69
+ def _normalize_content(self, content, fps, max_frames):
70
+ """Convert a single history content entry to model conversation format."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  if isinstance(content, str):
72
+ return [{"type": "text", "text": content}]
73
 
74
  if isinstance(content, dict):
75
+ path = content.get("path") or content.get("url", "")
76
+ if path:
77
+ media_type = self._classify_file(path)
78
+ if media_type == "video":
79
+ return [{"type": "video", "video": {"video_path": path, "fps": fps, "max_frames": max_frames}}]
80
+ if media_type == "image":
81
+ return [{"type": "image", "image": {"image_path": path}}]
82
  text = content.get("text")
83
  if isinstance(text, str):
84
+ return [{"type": "text", "text": text}]
85
 
86
+ if isinstance(content, list):
87
+ result = []
88
  for item in content:
89
+ result.extend(self._normalize_content(item, fps, max_frames))
90
+ return result
 
 
 
 
91
 
92
+ return []
93
 
94
+ def _build_conversation(self, message, history, system_prompt, fps, max_frames):
95
+ conversation = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  active_system_prompt = (system_prompt or self.default_system_prompt).strip()
98
  if active_system_prompt:
99
+ conversation.append({
100
  "role": "system",
101
  "content": [{"type": "text", "text": active_system_prompt}],
102
  })
103
 
104
+ # History — merge consecutive user messages into one turn
105
+ for entry in history:
106
+ role = entry["role"]
107
+ if role == "assistant":
108
+ conversation.append({"role": "assistant", "content": entry["content"]})
109
+ elif role == "user":
110
+ normalized = self._normalize_content(entry["content"], fps, max_frames)
111
+ if not normalized:
112
+ continue
113
+ if conversation and conversation[-1]["role"] == "user":
114
+ conversation[-1]["content"].extend(normalized)
115
+ else:
116
+ conversation.append({"role": "user", "content": normalized})
117
+
118
+ # Current message
119
+ current_content = []
120
+ for f in message.get("files") or []:
121
+ path = f if isinstance(f, str) else f.get("path", "")
122
+ media_type = self._classify_file(path)
123
+ if media_type == "video":
124
+ current_content.append({"type": "video", "video": {"video_path": path, "fps": fps, "max_frames": max_frames}})
125
+ elif media_type == "image":
126
+ current_content.append({"type": "image", "image": {"image_path": path}})
127
+ text = (message.get("text") or "").strip()
128
+ if text:
129
+ current_content.append({"type": "text", "text": text})
130
+
131
+ if current_content:
132
+ if conversation and conversation[-1]["role"] == "user":
133
+ conversation[-1]["content"].extend(current_content)
134
+ else:
135
+ conversation.append({"role": "user", "content": current_content})
136
+
137
+ return conversation
138
 
139
+ def _predict(self, message, history, system_prompt, do_sample, temperature, top_p, max_new_tokens, fps, max_frames):
140
+ conversation = self._build_conversation(message, history, system_prompt, fps, max_frames)
141
 
142
+ if not conversation or conversation[-1]["role"] != "user":
143
+ yield ""
144
+ return
145
 
146
  generation_config = {
147
  "do_sample": do_sample,
148
  "temperature": temperature,
149
  "top_p": top_p,
150
+ "max_new_tokens": max_new_tokens,
151
  }
152
 
153
+ response = self.model_client.submit({"conversation": conversation, "generation_config": generation_config})
154
  if isinstance(response, str):
155
+ yield response
 
156
  return
157
 
158
+ text = ""
159
  for token in response:
160
+ text += token
161
+ yield text
162
 
163
  def launch(self):
164
+ self.interface.launch(ssr_mode=False, **self.server_kwargs)
inference/server/direct_client.py CHANGED
@@ -1,10 +1,7 @@
1
- import importlib
2
- import importlib.util
3
  import os
4
- import subprocess
5
- import sys
6
  from threading import Lock, Thread
7
 
 
8
  import torch
9
  from transformers import AutoModelForCausalLM, AutoProcessor, TextIteratorStreamer
10
 
@@ -13,42 +10,16 @@ _MODEL = None
13
  _PROCESSOR = None
14
  _MODEL_PATH = None
15
  _MODEL_LOCK = Lock()
16
- _FLASH_ATTN_LOCK = Lock()
17
- _FLASH_ATTN_PACKAGE = "flash_attn"
18
- _FLASH_ATTN_REQUIREMENT = os.getenv("FLASH_ATTN_REQUIREMENT", "flash-attn==2.8.3")
19
 
20
 
21
  def _get_attn_implementation():
22
- return os.getenv("ATTN_IMPLEMENTATION", "sdpa")
23
 
24
 
25
  def _get_model_revision():
26
  return os.getenv("MODEL_REVISION")
27
 
28
 
29
- def ensure_flash_attn_installed():
30
- if importlib.util.find_spec(_FLASH_ATTN_PACKAGE) is not None:
31
- return
32
-
33
- with _FLASH_ATTN_LOCK:
34
- if importlib.util.find_spec(_FLASH_ATTN_PACKAGE) is not None:
35
- return
36
-
37
- install_cmd = [
38
- sys.executable,
39
- "-m",
40
- "pip",
41
- "install",
42
- _FLASH_ATTN_REQUIREMENT,
43
- "--no-build-isolation",
44
- ]
45
- print(f"Installing {_FLASH_ATTN_REQUIREMENT} with --no-build-isolation...")
46
- subprocess.check_call(install_cmd, env=os.environ.copy())
47
- importlib.invalidate_caches()
48
- if importlib.util.find_spec(_FLASH_ATTN_PACKAGE) is None:
49
- raise RuntimeError(f"Failed to import {_FLASH_ATTN_PACKAGE} after installation.")
50
-
51
-
52
  def _ensure_model_loaded(model_path):
53
  global _MODEL, _PROCESSOR, _MODEL_PATH
54
 
@@ -59,7 +30,6 @@ def _ensure_model_loaded(model_path):
59
  if _MODEL is not None and _PROCESSOR is not None and _MODEL_PATH == model_path:
60
  return _MODEL, _PROCESSOR
61
 
62
- ensure_flash_attn_installed()
63
  attn_implementation = _get_attn_implementation()
64
  revision = _get_model_revision()
65
 
@@ -88,6 +58,7 @@ def preload_model(model_path):
88
  return _ensure_model_loaded(model_path)
89
 
90
 
 
91
  def _run_generation_stream(payload):
92
  model_path = payload["model_path"]
93
  model, processor = _ensure_model_loaded(model_path)
 
 
 
1
  import os
 
 
2
  from threading import Lock, Thread
3
 
4
+ import spaces
5
  import torch
6
  from transformers import AutoModelForCausalLM, AutoProcessor, TextIteratorStreamer
7
 
 
10
  _PROCESSOR = None
11
  _MODEL_PATH = None
12
  _MODEL_LOCK = Lock()
 
 
 
13
 
14
 
15
  def _get_attn_implementation():
16
+ return os.getenv("ATTN_IMPLEMENTATION", "flash_attention_2")
17
 
18
 
19
  def _get_model_revision():
20
  return os.getenv("MODEL_REVISION")
21
 
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  def _ensure_model_loaded(model_path):
24
  global _MODEL, _PROCESSOR, _MODEL_PATH
25
 
 
30
  if _MODEL is not None and _PROCESSOR is not None and _MODEL_PATH == model_path:
31
  return _MODEL, _PROCESSOR
32
 
 
33
  attn_implementation = _get_attn_implementation()
34
  revision = _get_model_revision()
35
 
 
58
  return _ensure_model_loaded(model_path)
59
 
60
 
61
+ @spaces.GPU(duration=120)
62
  def _run_generation_stream(payload):
63
  model_path = payload["model_path"]
64
  model, processor = _ensure_model_loaded(model_path)
pre-requirements.txt DELETED
@@ -1,5 +0,0 @@
1
- # Build helpers recommended by the FlashAttention installation guide.
2
- packaging
3
- psutil
4
- ninja
5
- wheel
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,9 +1,5 @@
1
  --extra-index-url https://download.pytorch.org/whl/cu124
2
 
3
- # Base runtime for Transformers inference and the Gradio demo.
4
- # Training, notebook, and vLLM-specific extras were removed from this file.
5
- # The previous full list is preserved in requirements.original.txt.
6
-
7
  # Core model runtime
8
  torch==2.5.1
9
  torchvision==0.20.1
@@ -13,7 +9,7 @@ accelerate==1.10.1
13
  huggingface_hub==0.34.4
14
  sentencepiece==0.1.99
15
  timm==1.0.3
16
- numpy==1.24.4
17
  Pillow
18
  einops==0.6.1
19
  einops-exts==0.0.4
@@ -22,16 +18,12 @@ einops-exts==0.0.4
22
  decord==0.6.0
23
  imageio==2.34.0
24
  imageio-ffmpeg==0.4.9
25
- opencv-python-headless==4.6.0.66
26
  ffmpeg-python
27
  requests
28
 
29
  # UI
30
  gradio>=5.44.1,<7
31
 
32
- # FlashAttention is installed separately with:
33
- # pip install flash-attn==2.8.3 --no-build-isolation
34
- # This cannot be expressed in a standard Gradio Space requirements install step.
35
-
36
- # Optional extras
37
- # vllm==0.11.0
 
1
  --extra-index-url https://download.pytorch.org/whl/cu124
2
 
 
 
 
 
3
  # Core model runtime
4
  torch==2.5.1
5
  torchvision==0.20.1
 
9
  huggingface_hub==0.34.4
10
  sentencepiece==0.1.99
11
  timm==1.0.3
12
+ numpy>=1.26.0
13
  Pillow
14
  einops==0.6.1
15
  einops-exts==0.0.4
 
18
  decord==0.6.0
19
  imageio==2.34.0
20
  imageio-ffmpeg==0.4.9
21
+ opencv-python-headless
22
  ffmpeg-python
23
  requests
24
 
25
  # UI
26
  gradio>=5.44.1,<7
27
 
28
+ # FlashAttention pre-built wheel from GitHub (no nvcc required)
29
+ https://github.com/Dao-AILab/flash-attention/releases/download/v2.8.3/flash_attn-2.8.3+cu12torch2.5cxx11abiFALSE-cp312-cp312-linux_x86_64.whl