Spaces:
Sleeping
Sleeping
Initial probe deploy 2026-06-14 18:03:58
Browse files
README.md
CHANGED
|
@@ -3,28 +3,32 @@ title: AnimoFlow Rewriter Probe
|
|
| 3 |
emoji: 🔤
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: indigo
|
| 6 |
-
sdk:
|
| 7 |
-
|
|
|
|
| 8 |
pinned: false
|
| 9 |
short_description: Multilingual prompt rewriter probe (Qwen + RAFSL)
|
| 10 |
hardware: zero-a10g
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
# AnimoFlow Rewriter Probe
|
| 14 |
|
| 15 |
-
Phase-0 verification of the multilingual prompt-rewriter front-end planned for AnimoFlow's text-to-motion pipeline.
|
| 16 |
|
| 17 |
-
- **Model:**
|
| 18 |
-
- **Retriever:**
|
| 19 |
-
- **Corpus:** HumanML3D captions (
|
| 20 |
-
- **Hardware:**
|
| 21 |
|
| 22 |
-
##
|
| 23 |
|
| 24 |
-
-
|
| 25 |
-
- `
|
| 26 |
-
- `GET /ui` — Gradio sanity-check UI
|
| 27 |
|
| 28 |
## Purpose
|
| 29 |
|
| 30 |
-
This Space is a **measurement probe**, not a production service.
|
|
|
|
| 3 |
emoji: 🔤
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: indigo
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 5.6.0
|
| 8 |
+
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
short_description: Multilingual prompt rewriter probe (Qwen + RAFSL)
|
| 11 |
hardware: zero-a10g
|
| 12 |
+
suggested_hardware: zero-a10g
|
| 13 |
+
preload_from_hub:
|
| 14 |
+
- "Qwen/Qwen2.5-1.5B-Instruct"
|
| 15 |
+
- "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
| 16 |
---
|
| 17 |
|
| 18 |
# AnimoFlow Rewriter Probe
|
| 19 |
|
| 20 |
+
Phase-0 verification of the multilingual prompt-rewriter front-end planned for AnimoFlow's text-to-motion pipeline. Loads a small instruction-tuned LLM + a multilingual MiniLM retriever over the HumanML3D corpus and rewrites arbitrary user input into HumanML3D-style English captions.
|
| 21 |
|
| 22 |
+
- **Model:** `Qwen/Qwen2.5-1.5B-Instruct` by default (override via `MODEL_REPO` env / Space secret)
|
| 23 |
+
- **Retriever:** `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`
|
| 24 |
+
- **Corpus:** HumanML3D captions (52K unique)
|
| 25 |
+
- **Hardware:** ZeroGPU (A10G burst, PRO-account free tier)
|
| 26 |
|
| 27 |
+
## Usage
|
| 28 |
|
| 29 |
+
- **Gradio UI** — type a motion prompt in any language, press *Rewrite*.
|
| 30 |
+
- **API** — `gradio_client.Client("AnimoFlow/rewriter-probe").predict(prompt, api_name="/rewrite")` returns `{rewritten, examples, latency_s, ...}`.
|
|
|
|
| 31 |
|
| 32 |
## Purpose
|
| 33 |
|
| 34 |
+
This Space is a **measurement probe** for the AnimoFlow rewriter plan, not a production service. Plan: `vault/wiki/concepts/Prompt rewriter front-end plan` in the AnimoFlow vault.
|
app.py
CHANGED
|
@@ -1,20 +1,26 @@
|
|
| 1 |
-
"""HF Space probe:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from __future__ import annotations
|
| 3 |
|
| 4 |
-
import os
|
| 5 |
import threading
|
| 6 |
import time
|
| 7 |
-
from contextlib import asynccontextmanager
|
| 8 |
from pathlib import Path
|
| 9 |
|
| 10 |
import gradio as gr
|
| 11 |
-
import uvicorn
|
| 12 |
-
from fastapi import FastAPI, HTTPException
|
| 13 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
-
from pydantic import BaseModel, Field
|
| 15 |
|
| 16 |
try:
|
| 17 |
-
import spaces
|
| 18 |
_HAS_SPACES = True
|
| 19 |
except ImportError:
|
| 20 |
_HAS_SPACES = False
|
|
@@ -22,13 +28,12 @@ except ImportError:
|
|
| 22 |
from rewriter import Rewriter
|
| 23 |
|
| 24 |
DATA_DIR = Path(__file__).parent / "data"
|
| 25 |
-
PORT = int(os.environ.get("PORT", "7860"))
|
| 26 |
|
|
|
|
| 27 |
_REWRITER: Rewriter | None = None
|
| 28 |
_LOAD_LOCK = threading.Lock()
|
| 29 |
-
_LOAD_START_AT = time.time()
|
| 30 |
-
_FIRST_INFERENCE_AT: float | None = None
|
| 31 |
_LOAD_LATENCY: float | None = None
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
def get_rewriter() -> Rewriter:
|
|
@@ -42,70 +47,44 @@ def get_rewriter() -> Rewriter:
|
|
| 42 |
return _REWRITER
|
| 43 |
|
| 44 |
|
| 45 |
-
class RewriteRequest(BaseModel):
|
| 46 |
-
prompt: str = Field(..., min_length=1, max_length=2000)
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
class RewriteResponse(BaseModel):
|
| 50 |
-
rewritten: str
|
| 51 |
-
examples: list[str]
|
| 52 |
-
latency_s: float
|
| 53 |
-
input_tokens: int
|
| 54 |
-
output_tokens: int
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
@asynccontextmanager
|
| 58 |
-
async def lifespan(app: FastAPI):
|
| 59 |
-
# Cold-load: instantiate the rewriter at boot so the first request doesn't pay the load cost.
|
| 60 |
-
print(f"[lifespan] starting cold-load at t+{time.time() - _LOAD_START_AT:.1f}s", flush=True)
|
| 61 |
-
get_rewriter()
|
| 62 |
-
print(f"[lifespan] cold-load done at t+{time.time() - _LOAD_START_AT:.1f}s", flush=True)
|
| 63 |
-
yield
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
app = FastAPI(title="AnimoFlow Rewriter Probe", lifespan=lifespan)
|
| 67 |
-
app.add_middleware(
|
| 68 |
-
CORSMiddleware,
|
| 69 |
-
allow_origins=["*"],
|
| 70 |
-
allow_methods=["*"],
|
| 71 |
-
allow_headers=["*"],
|
| 72 |
-
)
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
@app.get("/healthz")
|
| 76 |
-
def healthz():
|
| 77 |
-
ready = _REWRITER is not None
|
| 78 |
-
return {
|
| 79 |
-
"ready": ready,
|
| 80 |
-
"uptime_s": time.time() - _LOAD_START_AT,
|
| 81 |
-
"load_latency_s": _LOAD_LATENCY,
|
| 82 |
-
"first_inference_at_uptime_s": (_FIRST_INFERENCE_AT - _LOAD_START_AT) if _FIRST_INFERENCE_AT else None,
|
| 83 |
-
"device": _REWRITER.device if _REWRITER else None,
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
|
| 87 |
def _do_rewrite(prompt: str) -> dict:
|
| 88 |
return get_rewriter().rewrite(prompt)
|
| 89 |
|
| 90 |
|
| 91 |
-
# On ZeroGPU, wrap
|
| 92 |
if _HAS_SPACES:
|
| 93 |
_do_rewrite = spaces.GPU(duration=30)(_do_rewrite)
|
| 94 |
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
| 98 |
global _FIRST_INFERENCE_AT
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
| 103 |
if _FIRST_INFERENCE_AT is None:
|
| 104 |
_FIRST_INFERENCE_AT = time.time()
|
| 105 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
|
| 108 |
-
# Gradio UI (mounted at /ui)
|
| 109 |
def gradio_rewrite(prompt: str):
|
| 110 |
if not prompt.strip():
|
| 111 |
return "(empty)", "(empty)", 0.0
|
|
@@ -114,11 +93,17 @@ def gradio_rewrite(prompt: str):
|
|
| 114 |
return out["rewritten"], examples_str, out["latency_s"]
|
| 115 |
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
with gr.Blocks(title="AnimoFlow Rewriter Probe") as demo:
|
| 118 |
-
gr.Markdown("# AnimoFlow Rewriter Probe —
|
| 119 |
gr.Markdown(
|
| 120 |
"Type a motion prompt in any language. The rewriter normalises it to a "
|
| 121 |
-
"HumanML3D-style English caption."
|
|
|
|
| 122 |
)
|
| 123 |
with gr.Row():
|
| 124 |
with gr.Column():
|
|
@@ -128,10 +113,20 @@ with gr.Blocks(title="AnimoFlow Rewriter Probe") as demo:
|
|
| 128 |
out_text = gr.Textbox(label="Rewritten (HumanML3D-style English)", lines=2)
|
| 129 |
out_examples = gr.Textbox(label="Retrieved exemplars", lines=4)
|
| 130 |
out_latency = gr.Number(label="Latency (s)", precision=3)
|
| 131 |
-
btn.click(gradio_rewrite, inputs=[inp], outputs=[out_text, out_examples, out_latency])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
if __name__ == "__main__":
|
| 137 |
-
|
|
|
|
| 1 |
+
"""HF Space probe: Gradio SDK + ZeroGPU.
|
| 2 |
+
|
| 3 |
+
Exposes a /rewrite Gradio API endpoint that returns:
|
| 4 |
+
rewritten: str
|
| 5 |
+
examples: list[str]
|
| 6 |
+
latency_s: float
|
| 7 |
+
input_tokens: int
|
| 8 |
+
output_tokens: int
|
| 9 |
+
cold_load_s: float
|
| 10 |
+
uptime_s: float
|
| 11 |
+
|
| 12 |
+
Plus a /healthz endpoint via gr.routes for the probe to poll.
|
| 13 |
+
"""
|
| 14 |
from __future__ import annotations
|
| 15 |
|
|
|
|
| 16 |
import threading
|
| 17 |
import time
|
|
|
|
| 18 |
from pathlib import Path
|
| 19 |
|
| 20 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
try:
|
| 23 |
+
import spaces
|
| 24 |
_HAS_SPACES = True
|
| 25 |
except ImportError:
|
| 26 |
_HAS_SPACES = False
|
|
|
|
| 28 |
from rewriter import Rewriter
|
| 29 |
|
| 30 |
DATA_DIR = Path(__file__).parent / "data"
|
|
|
|
| 31 |
|
| 32 |
+
_BOOT_AT = time.time()
|
| 33 |
_REWRITER: Rewriter | None = None
|
| 34 |
_LOAD_LOCK = threading.Lock()
|
|
|
|
|
|
|
| 35 |
_LOAD_LATENCY: float | None = None
|
| 36 |
+
_FIRST_INFERENCE_AT: float | None = None
|
| 37 |
|
| 38 |
|
| 39 |
def get_rewriter() -> Rewriter:
|
|
|
|
| 47 |
return _REWRITER
|
| 48 |
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
def _do_rewrite(prompt: str) -> dict:
|
| 51 |
return get_rewriter().rewrite(prompt)
|
| 52 |
|
| 53 |
|
| 54 |
+
# On ZeroGPU, wrap inference so HF allocates a GPU burst.
|
| 55 |
if _HAS_SPACES:
|
| 56 |
_do_rewrite = spaces.GPU(duration=30)(_do_rewrite)
|
| 57 |
|
| 58 |
|
| 59 |
+
def rewrite_api(prompt: str) -> dict:
|
| 60 |
+
"""Gradio API entry point exposed at /api/rewrite (and /rewrite for direct REST style)."""
|
| 61 |
global _FIRST_INFERENCE_AT
|
| 62 |
+
if not prompt or not prompt.strip():
|
| 63 |
+
return {"rewritten": "", "examples": [], "latency_s": 0.0, "input_tokens": 0, "output_tokens": 0,
|
| 64 |
+
"cold_load_s": _LOAD_LATENCY or 0.0, "uptime_s": time.time() - _BOOT_AT, "error": "empty prompt"}
|
| 65 |
+
t0 = time.time()
|
| 66 |
+
out = _do_rewrite(prompt)
|
| 67 |
if _FIRST_INFERENCE_AT is None:
|
| 68 |
_FIRST_INFERENCE_AT = time.time()
|
| 69 |
+
return {
|
| 70 |
+
**out,
|
| 71 |
+
"wall_latency_s": time.time() - t0,
|
| 72 |
+
"cold_load_s": _LOAD_LATENCY or 0.0,
|
| 73 |
+
"uptime_s": time.time() - _BOOT_AT,
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def healthz_api() -> dict:
|
| 78 |
+
return {
|
| 79 |
+
"ready": _REWRITER is not None,
|
| 80 |
+
"uptime_s": time.time() - _BOOT_AT,
|
| 81 |
+
"load_latency_s": _LOAD_LATENCY,
|
| 82 |
+
"first_inference_at_uptime_s": (_FIRST_INFERENCE_AT - _BOOT_AT) if _FIRST_INFERENCE_AT else None,
|
| 83 |
+
"device": _REWRITER.device if _REWRITER else None,
|
| 84 |
+
"model_id": _REWRITER.model_id if _REWRITER else None,
|
| 85 |
+
}
|
| 86 |
|
| 87 |
|
|
|
|
| 88 |
def gradio_rewrite(prompt: str):
|
| 89 |
if not prompt.strip():
|
| 90 |
return "(empty)", "(empty)", 0.0
|
|
|
|
| 93 |
return out["rewritten"], examples_str, out["latency_s"]
|
| 94 |
|
| 95 |
|
| 96 |
+
# Eager-load the rewriter at module import so the first Gradio call doesn't pay the load cost.
|
| 97 |
+
# We do this AFTER the spaces decorator is wired up so ZeroGPU is initialised.
|
| 98 |
+
get_rewriter()
|
| 99 |
+
|
| 100 |
+
|
| 101 |
with gr.Blocks(title="AnimoFlow Rewriter Probe") as demo:
|
| 102 |
+
gr.Markdown("# AnimoFlow Rewriter Probe — Qwen2.5-1.5B-Instruct + RAFSL on ZeroGPU")
|
| 103 |
gr.Markdown(
|
| 104 |
"Type a motion prompt in any language. The rewriter normalises it to a "
|
| 105 |
+
"HumanML3D-style English caption. Powered by Qwen2.5-1.5B-Instruct + a multilingual "
|
| 106 |
+
"MiniLM retriever over the 52K HumanML3D caption corpus."
|
| 107 |
)
|
| 108 |
with gr.Row():
|
| 109 |
with gr.Column():
|
|
|
|
| 113 |
out_text = gr.Textbox(label="Rewritten (HumanML3D-style English)", lines=2)
|
| 114 |
out_examples = gr.Textbox(label="Retrieved exemplars", lines=4)
|
| 115 |
out_latency = gr.Number(label="Latency (s)", precision=3)
|
| 116 |
+
btn.click(gradio_rewrite, inputs=[inp], outputs=[out_text, out_examples, out_latency], api_name="rewrite_ui")
|
| 117 |
+
|
| 118 |
+
# Pure API endpoint with JSON dict response — what the probe + clients should hit.
|
| 119 |
+
with gr.Row(visible=False):
|
| 120 |
+
api_in = gr.Textbox()
|
| 121 |
+
api_out = gr.JSON()
|
| 122 |
+
api_btn = gr.Button()
|
| 123 |
+
api_btn.click(rewrite_api, inputs=[api_in], outputs=[api_out], api_name="rewrite")
|
| 124 |
|
| 125 |
+
with gr.Row(visible=False):
|
| 126 |
+
hz_btn = gr.Button()
|
| 127 |
+
hz_out = gr.JSON()
|
| 128 |
+
hz_btn.click(healthz_api, inputs=[], outputs=[hz_out], api_name="healthz")
|
| 129 |
|
| 130 |
|
| 131 |
if __name__ == "__main__":
|
| 132 |
+
demo.queue().launch(server_name="0.0.0.0", server_port=7860)
|