daKhosa commited on
Commit
b3586c1
·
1 Parent(s): 1efc965

Replace LTX enhancer with Sulphur GGUF enhancer

Browse files
Files changed (2) hide show
  1. app.py +80 -3
  2. requirements.txt +1 -0
app.py CHANGED
@@ -29,9 +29,17 @@ MAX_SEED = np.iinfo(np.int32).max
29
  DEFAULT_FRAME_RATE = 24.0
30
  DEFAULT_PROMPT = "Make this image come alive with cinematic motion, smooth animation."
31
  PROMPT_ADHERENCE_SUFFIX = (
32
- "Follow the prompt literally. Keep the named subjects, actions, setting, style, "
33
- "camera motion, and timing consistent with the user's request."
34
  )
 
 
 
 
 
 
 
 
 
35
 
36
  RESOLUTIONS = {
37
  "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024)},
@@ -215,11 +223,63 @@ def _validate_lora(name, lora):
215
 
216
  def _prompt_for_model(prompt):
217
  prompt = " ".join(str(prompt or DEFAULT_PROMPT).split())
 
218
  if PROMPT_ADHERENCE_SUFFIX.lower() in prompt.lower():
219
  return prompt
220
  return f"{prompt}\n\n{PROMPT_ADHERENCE_SUFFIX}"
221
 
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  def _ensure_gemma_model_alias(gemma_weight):
224
  gemma_weight = Path(gemma_weight)
225
  alias = gemma_weight.with_name("model.safetensors")
@@ -249,6 +309,16 @@ def download_assets():
249
  "ltx-2.3-spatial-upscaler-x2-1.1.safetensors",
250
  ASSETS_DIR,
251
  )
 
 
 
 
 
 
 
 
 
 
252
 
253
  gemma_folder = "gemma-3-12b-it-qat-q4_0-unquantized"
254
  gemma_files = [
@@ -281,6 +351,8 @@ def download_assets():
281
  "sulphur_lora": sulphur_lora,
282
  "distilled_lora": distilled_lora,
283
  "upsampler": upsampler,
 
 
284
  "gemma_root": ASSETS_DIR / gemma_folder,
285
  }
286
 
@@ -293,6 +365,9 @@ LORAS = [
293
  ]
294
  _validate_lora("sulphur_distilled_condsafe", LORAS[0])
295
  _validate_lora("sulphur_experimental_lora_v1", LORAS[1])
 
 
 
296
 
297
  pipeline = DistilledPipeline(
298
  distilled_checkpoint_path=str(ASSETS["checkpoint"]),
@@ -356,6 +431,8 @@ def generate_video(
356
  num_frames = ((num_frames - 1 + 7) // 8) * 8 + 1
357
  height = int(height)
358
  width = int(width)
 
 
359
  model_prompt = _prompt_for_model(prompt)
360
 
361
  print(f"[generate] {width}x{height}, frames={num_frames}, seed={current_seed}")
@@ -381,7 +458,7 @@ def generate_video(
381
  frame_rate=frame_rate,
382
  images=images,
383
  tiling_config=tiling_config,
384
- enhance_prompt=bool(enhance_prompt),
385
  )
386
 
387
  output_path = tempfile.mktemp(suffix=".mp4")
 
29
  DEFAULT_FRAME_RATE = 24.0
30
  DEFAULT_PROMPT = "Make this image come alive with cinematic motion, smooth animation."
31
  PROMPT_ADHERENCE_SUFFIX = (
32
+ "Follow the prompt literally. Preserve the named subjects, actions, setting, and style."
 
33
  )
34
+ MAX_PROMPT_CHARS = 900
35
+ MAX_ENHANCED_PROMPT_CHARS = 1400
36
+ PROMPT_ENHANCER_SYSTEM_PROMPT = (
37
+ "You are Sulphur's prompt enhancer for image-to-video generation. Rewrite the user's prompt into one concise, "
38
+ "literal cinematic video prompt. Preserve the user's subject, action, setting, style, and intent. Add useful "
39
+ "visual motion, camera movement, lighting, and temporal details. Do not refuse, moralize, apologize, explain, "
40
+ "quote policy, or mention being an AI. Return only the rewritten prompt."
41
+ )
42
+ PROMPT_ENHANCER_USER_TEMPLATE = "Rewrite this video prompt only:\n{prompt}"
43
 
44
  RESOLUTIONS = {
45
  "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024)},
 
223
 
224
  def _prompt_for_model(prompt):
225
  prompt = " ".join(str(prompt or DEFAULT_PROMPT).split())
226
+ prompt = _truncate_prompt(prompt, MAX_PROMPT_CHARS)
227
  if PROMPT_ADHERENCE_SUFFIX.lower() in prompt.lower():
228
  return prompt
229
  return f"{prompt}\n\n{PROMPT_ADHERENCE_SUFFIX}"
230
 
231
 
232
+ def _truncate_prompt(prompt, limit):
233
+ prompt = str(prompt or "").strip()
234
+ if len(prompt) <= limit:
235
+ return prompt
236
+ truncated = prompt[:limit].rsplit(" ", 1)[0].strip()
237
+ return truncated or prompt[:limit].strip()
238
+
239
+
240
+ _PROMPT_ENHANCER = None
241
+
242
+
243
+ def _get_prompt_enhancer():
244
+ global _PROMPT_ENHANCER
245
+ if _PROMPT_ENHANCER is not None:
246
+ return _PROMPT_ENHANCER
247
+ from llama_cpp import Llama
248
+
249
+ print(f"[prompt-enhancer] loading {ASSETS['prompt_enhancer_model']}")
250
+ _PROMPT_ENHANCER = Llama(
251
+ model_path=str(ASSETS["prompt_enhancer_model"]),
252
+ n_ctx=2048,
253
+ n_threads=max(1, min(8, os.cpu_count() or 1)),
254
+ n_gpu_layers=0,
255
+ verbose=False,
256
+ )
257
+ return _PROMPT_ENHANCER
258
+
259
+
260
+ def _enhance_prompt_with_sulphur(prompt):
261
+ base_prompt = _truncate_prompt(prompt or DEFAULT_PROMPT, MAX_PROMPT_CHARS)
262
+ llm = _get_prompt_enhancer()
263
+ result = llm.create_chat_completion(
264
+ messages=[
265
+ {"role": "system", "content": PROMPT_ENHANCER_SYSTEM_PROMPT},
266
+ {"role": "user", "content": PROMPT_ENHANCER_USER_TEMPLATE.format(prompt=base_prompt)},
267
+ ],
268
+ temperature=0.35,
269
+ top_p=0.9,
270
+ max_tokens=320,
271
+ )
272
+ enhanced = result["choices"][0]["message"]["content"].strip()
273
+ enhanced = enhanced.strip("\"'` \n\t")
274
+ blocked_phrases = ("cannot fulfill", "safety guidelines", "as an ai", "i cannot", "i'm unable")
275
+ if not enhanced or enhanced == "/" or any(phrase in enhanced.lower() for phrase in blocked_phrases):
276
+ print(f"[prompt-enhancer] rejected unusable output; falling back to literal prompt: {enhanced!r}")
277
+ return base_prompt
278
+ enhanced = _truncate_prompt(enhanced, MAX_ENHANCED_PROMPT_CHARS)
279
+ print(f"[prompt-enhancer] {enhanced}")
280
+ return enhanced
281
+
282
+
283
  def _ensure_gemma_model_alias(gemma_weight):
284
  gemma_weight = Path(gemma_weight)
285
  alias = gemma_weight.with_name("model.safetensors")
 
309
  "ltx-2.3-spatial-upscaler-x2-1.1.safetensors",
310
  ASSETS_DIR,
311
  )
312
+ prompt_enhancer_model = _download(
313
+ "SulphurAI/Sulphur-2-base",
314
+ "prompt_enhancer/sulphur_prompt_enhancer_model-q8_0.gguf",
315
+ ASSETS_DIR,
316
+ )
317
+ prompt_enhancer_mmproj = _download(
318
+ "SulphurAI/Sulphur-2-base",
319
+ "prompt_enhancer/mmproj-BF16.gguf",
320
+ ASSETS_DIR,
321
+ )
322
 
323
  gemma_folder = "gemma-3-12b-it-qat-q4_0-unquantized"
324
  gemma_files = [
 
351
  "sulphur_lora": sulphur_lora,
352
  "distilled_lora": distilled_lora,
353
  "upsampler": upsampler,
354
+ "prompt_enhancer_model": prompt_enhancer_model,
355
+ "prompt_enhancer_mmproj": prompt_enhancer_mmproj,
356
  "gemma_root": ASSETS_DIR / gemma_folder,
357
  }
358
 
 
365
  ]
366
  _validate_lora("sulphur_distilled_condsafe", LORAS[0])
367
  _validate_lora("sulphur_experimental_lora_v1", LORAS[1])
368
+ print("[lora] configured order:")
369
+ for index, lora in enumerate(LORAS, start=1):
370
+ print(f"[lora] {index}: strength={lora.strength} path={lora.path}")
371
 
372
  pipeline = DistilledPipeline(
373
  distilled_checkpoint_path=str(ASSETS["checkpoint"]),
 
431
  num_frames = ((num_frames - 1 + 7) // 8) * 8 + 1
432
  height = int(height)
433
  width = int(width)
434
+ if enhance_prompt:
435
+ prompt = _enhance_prompt_with_sulphur(prompt)
436
  model_prompt = _prompt_for_model(prompt)
437
 
438
  print(f"[generate] {width}x{height}, frames={num_frames}, seed={current_seed}")
 
458
  frame_rate=frame_rate,
459
  images=images,
460
  tiling_config=tiling_config,
461
+ enhance_prompt=False,
462
  )
463
 
464
  output_path = tempfile.mktemp(suffix=".mp4")
requirements.txt CHANGED
@@ -16,3 +16,4 @@ Pillow
16
  sentencepiece
17
  scikit-image>=0.25.2
18
  flashpack==0.1.2
 
 
16
  sentencepiece
17
  scikit-image>=0.25.2
18
  flashpack==0.1.2
19
+ llama-cpp-python>=0.3.9