linoyts's picture
linoyts HF Staff
Gemma text-encoder microservice
7cf6314 verified
Raw
History Blame Contribute Delete
4.42 kB
"""
OmniForcing text-encoder microservice (Gemma + LTX-2 AV connectors).
Runs the AVGemmaTextEncoder in isolation and returns the conditioning tensors
(video_context, audio_context, attention_mask) as a safetensors file, so the
main generation Space can fetch them over the Gradio API and never has to load
Gemma itself. Only needs ~30GB on disk (Gemma ~24GB + the 6GB LTX slim file),
so it fits the free ephemeral disk with no persistent storage.
"""
from __future__ import annotations
import os
import sys
import threading
import traceback
from pathlib import Path
import gradio as gr
import spaces
import torch
from huggingface_hub import hf_hub_download, snapshot_download
from safetensors.torch import save_file
SLIM_REPO = os.environ.get("SLIM_REPO", "linoyts/ltx2-vae-connectors-slim")
SLIM_FILE = "ltx2_slim.safetensors"
GEMMA_REPO = os.environ.get("GEMMA_REPO", "google/gemma-3-12b-it-qat-q4_0-unquantized")
GEMMA_REPO_FALLBACK = "unsloth/gemma-3-12b-it"
HF_TOKEN = os.environ.get("HF_TOKEN")
DTYPE = torch.bfloat16
HERE = Path(__file__).resolve().parent
PKG = HERE / "ltx2" / "packages"
for _p in ("ltx-distillation", "ltx-causal", "ltx-core", "ltx-pipelines"):
_s = PKG / _p / "src"
if _s.exists():
sys.path.insert(0, str(_s))
from ltx_distillation.models.text_encoder_wrapper import create_text_encoder_wrapper # noqa: E402
STATE: dict = {}
LOAD = {"stage": "starting", "error": None, "ready": False}
def _set(s):
LOAD["stage"] = s
print(f"[load] {s}", flush=True)
def _load():
try:
_set("downloading LTX slim (connectors)")
slim = hf_hub_download(SLIM_REPO, SLIM_FILE, token=HF_TOKEN)
_set("downloading Gemma")
repo = GEMMA_REPO
try:
gemma_dir = snapshot_download(
repo, token=HF_TOKEN,
allow_patterns=["*.safetensors", "*.json", "*.model", "tokenizer*"],
)
except Exception as exc:
print(f"[load] {repo} failed ({exc}); fallback {GEMMA_REPO_FALLBACK}", flush=True)
repo = GEMMA_REPO_FALLBACK
gemma_dir = snapshot_download(
repo, token=HF_TOKEN,
allow_patterns=["*.safetensors", "*.json", "*.model", "tokenizer*"],
)
_set("building text encoder (CPU)")
te = create_text_encoder_wrapper(
checkpoint_path=slim, gemma_path=gemma_dir, device=torch.device("cpu"), dtype=DTYPE,
).eval()
STATE["te"] = te
STATE["gemma_repo"] = repo
LOAD["ready"] = True
_set("ready")
except Exception as exc:
LOAD["error"] = f"{type(exc).__name__}: {exc}"
_set(f"ERROR: {LOAD['error']}")
traceback.print_exc()
threading.Thread(target=_load, daemon=True).start()
_OUT = Path("/tmp/enc"); _OUT.mkdir(exist_ok=True)
@spaces.GPU(duration=120)
def encode(prompt: str):
if not prompt or not prompt.strip():
raise gr.Error("Empty prompt.")
if LOAD["error"]:
raise gr.Error(f"Encoder failed to load: {LOAD['error']}")
if not LOAD["ready"]:
raise gr.Error(f"Still loading ({LOAD['stage']}).")
te = STATE["te"].to("cuda")
with torch.no_grad():
cond = te(text_prompts=[prompt.strip()])
STATE["te"] = te.to("cpu")
torch.cuda.empty_cache()
out = {
"video_context": cond["video_context"].to("cpu").contiguous(),
"audio_context": cond["audio_context"].to("cpu").contiguous(),
"attention_mask": cond["attention_mask"].to("cpu").contiguous(),
}
path = _OUT / "cond.safetensors"
save_file(out, str(path), metadata={"format": "pt"})
return str(path)
with gr.Blocks(title="OmniForcing Gemma Encoder") as demo:
gr.Markdown(
"# OmniForcing — Text Encoder microservice\n"
"Encodes a prompt into `{video_context, audio_context, attention_mask}` "
"and returns them as a safetensors file. Consumed by the main generation Space."
)
inp = gr.Textbox(label="Prompt", lines=2)
btn = gr.Button("Encode", variant="primary")
out = gr.File(label="conditioning (.safetensors)")
status = gr.Textbox(label="Status", value="loading…", interactive=False)
btn.click(encode, inputs=inp, outputs=out, api_name="encode")
demo.load(lambda: LOAD["stage"], outputs=status)
if __name__ == "__main__":
demo.queue(max_size=16).launch(server_name="0.0.0.0", server_port=7860)