AnimoFlow commited on
Commit
af1040f
·
verified ·
1 Parent(s): 160b869

Initial probe deploy 2026-06-14 18:03:58

Browse files
Files changed (2) hide show
  1. README.md +16 -12
  2. app.py +62 -67
README.md CHANGED
@@ -3,28 +3,32 @@ title: AnimoFlow Rewriter Probe
3
  emoji: 🔤
4
  colorFrom: blue
5
  colorTo: indigo
6
- sdk: docker
7
- app_port: 7860
 
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. This Space loads Gemma-3-1B-it + a multilingual MiniLM retriever over the HumanML3D corpus, and rewrites arbitrary user input into HumanML3D-style English captions.
16
 
17
- - **Model:** [`google/gemma-3-1b-it`](https://huggingface.co/google/gemma-3-1b-it) (revision `dcc83ea841ab`)
18
- - **Retriever:** [`sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`](https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)
19
- - **Corpus:** HumanML3D captions (29K unique)
20
- - **Hardware:** CPU basic (free tier)
21
 
22
- ## Endpoints
23
 
24
- - `POST /rewrite``{"prompt": "..."}` `{"rewritten": "...", "latency_s": ..., ...}`
25
- - `GET /healthz` readiness + cold-load metrics
26
- - `GET /ui` — Gradio sanity-check UI
27
 
28
  ## Purpose
29
 
30
- This Space is a **measurement probe**, not a production service. It exists to verify the architecture decisions in the production rewriter plan before they land in `animoflow-web/nodes/prompt_rewriter/`.
 
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: minimal /rewrite + healthcheck endpoints + Gradio UI."""
 
 
 
 
 
 
 
 
 
 
 
 
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 # HF ZeroGPU decorator
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 the inference call so HF allocates a GPU burst for it.
92
  if _HAS_SPACES:
93
  _do_rewrite = spaces.GPU(duration=30)(_do_rewrite)
94
 
95
 
96
- @app.post("/rewrite", response_model=RewriteResponse)
97
- def rewrite(req: RewriteRequest):
98
  global _FIRST_INFERENCE_AT
99
- try:
100
- out = _do_rewrite(req.prompt)
101
- except Exception as e:
102
- raise HTTPException(status_code=500, detail=f"rewriter failed: {type(e).__name__}: {e}")
 
103
  if _FIRST_INFERENCE_AT is None:
104
  _FIRST_INFERENCE_AT = time.time()
105
- return RewriteResponse(**out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 — Gemma-3-1B-it + RAFSL")
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
- app = gr.mount_gradio_app(app, demo, path="/ui")
 
 
 
134
 
135
 
136
  if __name__ == "__main__":
137
- uvicorn.run(app, host="0.0.0.0", port=PORT)
 
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)