File size: 6,604 Bytes
5f43c7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# Deploying Her · हेर to a Hugging Face ZeroGPU Space

A one-shot runbook to stand up **Her** on a private or public Hugging Face **ZeroGPU**
Space. Written so another operator (human or agent) can do it end-to-end without
prior context. The whole thing is automated by `scripts/deploy.py`; the rest of this
doc explains what it does and how to verify + troubleshoot.

---

## 0. What gets deployed

Her runs in **Gradio Server mode** (`gradio.Server`) because **ZeroGPU only supports
the Gradio SDK** and its GPU quota needs the HF iframe auth headers forwarded:

- Deterministic engine endpoints (`/api/health|sessions|upload|analyze|project|clear|consent`)
  are plain FastAPI routes the React UI calls with `fetch`.
- GPU narration (`overview/advice/chat/project_chat/project_narrative`) are Gradio API
  endpoints (`@app.api`) the browser calls via `@gradio/client` (forwards auth).
- The built React SPA (`ui/dist`) is served from `/`.
- Uploaded sessions persist on an **HF storage bucket** mounted at `/data`, **namespaced
  per browser** (`/data/<sha256(token)>/…`), auto-deleted after 24h / on Clear / on exit.
- A shared **binary registry** lives at `/data/_registry/` (outside all user namespaces)
  and is enriched over time (local bundled DB → Nemotron → public registries).

---

## 1. Prerequisites

- A **Hugging Face PRO** account (required for **private** ZeroGPU Spaces and ZeroGPU
  quota). Org Team/Enterprise plan if deploying under an org.
- A **write** token: https://huggingface.co/settings/tokens → run `hf auth login`
  (or `export HF_TOKEN=hf_…`).
- **Python 3.10+** with `huggingface_hub` (use the deploy venv: `python3.10 -m venv
  .venv-deploy && . .venv-deploy/bin/activate && pip install "huggingface_hub>=1.0"`).
- **Node 18/20 + npm** (to build the UI once).

---

## 2. Build the UI (required — the Space does NOT run npm)

```bash
cd ui && npm install && npm run build && cd ..
# produces ui/dist (git-ignored, but shipped by deploy.py)
```

Optional — refresh the local binary registry (top CLI tools from Homebrew + npm + PyPI):

```bash
python3 scripts/build_binaries_db.py        # writes narrator/knowledge/binaries.bundled.json
```

---

## 3. Deploy (one command)

```bash
# PRIVATE test space (update an existing one — bucket already mounted):
python scripts/deploy.py --space <owner>/her

# PUBLIC hackathon space from scratch (creates space + bucket, mounts it, makes it public):
python scripts/deploy.py --space <org>/her --public --create
```

`deploy.py` is idempotent and does all of:
1. `create_repo(space_sdk="gradio", private=…, exist_ok=True)` + enforce visibility.
2. `create_bucket(<owner>/<name>-data)` *(only with `--create`/`--factory`)*.
3. Set the four Space **variables**:
   - `SPACE_MODEL_REPO=nvidia/Nemotron-Mini-4B-Instruct`
   - `HER_DATA_DIR=/data`
   - `HER_EXTRA_ROOT=/data`
   - `HER_LEARNED_PATH=/data/_registry/binaries.learned.json`
4. Attach the bucket volume at `/data` *(only with `--create`/`--factory`)*.
5. `upload_folder` (ships `ui/dist` + the bundled DB; excludes trace content, venvs,
   node_modules, `.git`, `*.gguf`).
6. `request_space_hardware("zero-a10g")` (ZeroGPU).
7. `restart_space(factory_reboot=True)` *(with `--create`/`--factory`)* — **required the
   first time a bucket is attached**, otherwise `/data` is ephemeral container disk.

> **Why `--create` matters:** a plain restart does NOT mount a newly-attached bucket.
> The factory reboot does. For later code-only updates, drop `--create` (faster).

---

## 4. Verify

```python
# in the deploy venv; uses your HF token for the (possibly private) Space
import httpx, time
from huggingface_hub import get_token
H = {"Authorization": f"Bearer {get_token()}"}
BASE = "https://<owner>-her.hf.space"           # owner/name -> owner-name.hf.space
c = httpx.Client(headers=H, timeout=180, follow_redirects=True)

print(c.get(BASE + "/api/health").json())        # -> {"ok":true,"llama":true,"gpu":true}
# upload a .jsonl, then:
#   GET /api/analyze?path=<returned path>  with header X-Her-Client: <any token>
#   -> engine JSON (turns/tools/cost/binaries)
# GPU narration (forwards auth):
from gradio_client import Client
gc = Client("<owner>/her", token=get_token())
print(gc.predict("<uploaded path>", "<client token>", api_name="/overview"))
```

Checklist:
- `health.llama == true` → the model loaded (watch build logs if not, see below).
- Upload → `/api/sessions` (with `X-Her-Client`) shows your sessions grouped into projects.
- A different `X-Her-Client` sees nothing (per-user isolation).
- `gradio_client.predict(..., api_name="/overview")` returns grounded prose.

---

## 5. Troubleshooting

- **`health.llama == false` (model didn't load).** Read logs:
  `api.fetch_space_logs("<owner>/her")`. The default model
  `nvidia/Nemotron-Mini-4B-Instruct` is a standard arch (loads natively). **Do not** use
  the Mamba-hybrid `Nemotron-Nano-9B-v2` — its remote code needs `mamba-ssm`/`causal-conv1d`
  CUDA kernels that don't build on ZeroGPU. Swap models by setting the `SPACE_MODEL_REPO`
  variable (no redeploy needed; it restarts).
- **Uploads vanish on restart / bucket empty.** The bucket wasn't mounted — re-run with
  `--factory` (or `restart_space(factory_reboot=True)`). Confirm with
  `api.list_bucket_tree("<owner>/her-data")` after an upload.
- **ZeroGPU not requestable via API.** Set it in **Space → Settings → Hardware → ZeroGPU**.
- **Blank UI / 503.** `ui/dist` wasn't built/shipped — run `npm run build` then redeploy.
- **GPU calls 401/fail from the browser.** They must go through `@gradio/client` (not raw
  `fetch`) so the iframe auth forwards — this is already how the React app calls them.

---

## 6. Pinned versions & key facts

- `sdk: gradio`, `sdk_version: 6.16.0` (Server mode), `python_version: "3.10.13"`,
  `app_file: app.py`, hardware `zero-a10g`.
- `requirements.txt`: `gradio==6.16.0`, `spaces`, `python-multipart`, `torch`,
  `transformers>=4.48.3,<5`, `accelerate`, `sentencepiece`, `einops`, `huggingface_hub`.
- Model: `nvidia/Nemotron-Mini-4B-Instruct` (swap via `SPACE_MODEL_REPO`).
- `@gradio/client` (JS) pinned to match (`^2.2.1`).

---

## 7. Before a PUBLIC launch

- **Privacy disclosure:** show the "we never store your sessions; only anonymous tool
  names are kept" copy in the first-run disclaimer (`ui/src/components/DisclaimerModal.jsx`).
- **ZeroGPU quota:** public visitors draw on the *owner's* ZeroGPU quota (then pre-paid
  credits). Consider a soft cap if traffic is high.
- Per-user isolation + 24h auto-clear are already on and public-safe.