Stage-2 release: DiT denoise as one persistent megakernel (snapshot 009de892, image dfb9c3889ab1)
Browse filesReplaces the Stage-1 (TTNN-op denoise) package with the megakernel release: image sha256:dfb9c3889ab17cc1d8187085b27e854d0311fb974d729efe88db9bfdce51869f (tt-model/gr00t-n1.5-3b-p150:dfb9c3889ab1), port snapshot 009de892c895 on tt-metal 668c290757, code/ digest d87906471090789d. `tt-model push --publish` uploaded code/, image/, tt_kernel_manifest.json, requirements.lock and the generated card and pruned the code/ and image/ files this bundle no longer ships; this commit restores the reviewed staging card (front matter: license other / nvidia-license, pipeline_tag robotics, base_model nvidia/GR00T-N1.5-3B, 13 tags; body = the tt-model.yaml card with the simplified quickstart bullets) and the authored files tt-model.yaml, SERVING.md, GPU_COMPARISON.md, LICENSE, LICENSE-NOTICE.md, requirements.lock and media/*. Card text per docs/publish/review-mk.md.
- GPU_COMPARISON.md +80 -60
- LICENSE-NOTICE.md +1 -1
- README.md +17 -4
- SERVING.md +132 -84
- media/demo_actions.png +2 -2
- tt-model.yaml +54 -26
|
@@ -1,22 +1,23 @@
|
|
| 1 |
# GR00T-N1.5-3B-p150 — Blackhole p150a vs RTX 5090 (same host, same weights, same observation)
|
| 2 |
|
| 3 |
-
Dates: GPU rows 2026-09-14, p150a rows 2026-09-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
| 7 |
|
| 8 |
## What was run
|
| 9 |
|
| 10 |
| | RTX 5090 | Blackhole p150a |
|
| 11 |
|---|---|---|
|
| 12 |
-
| Code | NVIDIA [Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) tag `n1.5-release` (`4af2b622`), `Gr00tPolicy.get_action`, `FourierGr1ArmsOnlyDataConfig`, unmodified | `code/models/experimental/gr00t` (frozen snapshot `
|
| 13 |
-
| Weights | `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e`, bf16 shards from the HF cache | same snapshot; converted once into the device layout (bf16 everywhere
|
| 14 |
-
| Precision | official path: `torch.autocast(bfloat16)` + flash-attention 2 (`flash_attention_2` is hard-coded in the vendored Eagle2.5 config) | bf16 activations; HiFi2 matmul fidelity in the backbone/adapter, bfp8_b DiT weights; fp32 host pre/post-processing |
|
| 15 |
| Observation | the port's canonical golden sample: one 256×256 `ego_view` frame (GR1 sim `robot_sim.PickNPlace` traj 0 / step 100), GR1 state (7/7/6/6), instruction "pick the pear from the counter and place it in the plate", batch 1, fixed initial noise | identical inputs (`code/gr00t_p150/demo/n15/`); LLM sequence 296 tokens padded to the static 384 |
|
| 16 |
-
| GPU / host | NVIDIA GeForce RTX 5090 (sm_120), driver 580.126.18; settled at P1, 2985 MHz SM / 13,801 MHz mem, 190–250 W during the loop | one p150a (11×10 compute grid, 8 DRAM banks), same host, `/dev/tenstorrent/0` |
|
| 17 |
| Software | Python 3.10.20, torch 2.7.1+cu128, torchvision 0.22.1+cu128, transformers 4.51.3, flash-attn 2.8.3 (official prebuilt wheel; its kernels verified on sm_120 against SDPA to 1 bf16 ulp), cuDNN 9.7.1 | tt-metal main `668c2907575` (`v0.79.0-dev20260914-1`), torch 2.11.0+cpu; image: Python 3.12 |
|
| 18 |
-
| Loop | `time_n15.py`: 3 rounds × (20 warm-ups + 20 timed calls), `torch.cuda.synchronize()` around every call, GPU otherwise idle; **best median of the 3 rounds** (the round medians agree within 0.4 ms) | `benchmarks/bench_e2e.py`: 10 warm-ups + 50 timed calls, host wall clock (`perf_counter`), trace replays enqueued non-blocking with one synchronize per call; a second 50-call pass with a synchronize after every trace gives the per-trace split; **median** (p90 in brackets) |
|
| 19 |
-
| Source files | `docs/research/ref-env-n15.md` §6 (project research notes; timing JSON `golden/n15/timing.json`) — not shipped in this repo | `code/models/experimental/gr00t/benchmarks/results/e2e_stage1_n15.json` (
|
| 20 |
|
| 21 |
## Latency, batch 1 (ms)
|
| 22 |
|
|
@@ -38,84 +39,102 @@ An fp32 (no autocast) model-only run measured 64–100 ms across runs and was no
|
|
| 38 |
`torch.compile` / TensorRT run was made for N1.5 (see caveats). NVIDIA's own N1.5 README quotes, for a single **H100**:
|
| 39 |
VLM backbone 23.18 ms, action head 4 × 6.18 = 24.7 ms, full model 47.88 ms.
|
| 40 |
|
| 41 |
-
### Blackhole p150a
|
| 42 |
|
| 43 |
-
| stage |
|
| 44 |
-
|---|---:|
|
| 45 |
-
| **e2e** = host encode + upload + 4 traces + readback + host decode | **
|
| 46 |
-
| device = upload + traces + readback | 41.68 (42.25) |
|
| 47 |
-
| 4 trace replays, one synchronize | 37.83 (37.87) |
|
| 48 |
-
| — trace `vision` (SigLIP tower + projector) | 8.71 |
|
| 49 |
-
| — trace `llm` (Qwen3-1.7B[:12]) | 8.75 |
|
| 50 |
-
| — trace `adapter` (VL-LN + 4 VL-SA blocks + K/V hoist for the DiT cross-attention + state encoder) | 4.47 |
|
| 51 |
-
| — trace `denoise` (4 Euler steps × (action encoder + 16 DiT blocks + decoder)) | 16.01 |
|
| 52 |
-
| upload (host inputs → device buffers
|
| 53 |
-
| readback of `action_pred` (D2H) | 0.09 |
|
| 54 |
-
| host encode (`Observation` → `ModelInputs`: image chain, prompt + tokenizer, sin/cos state) | 1.52 (1.82) |
|
| 55 |
-
| host decode (un-normalise) | 0.17 |
|
| 56 |
-
| e2e of the split pass (synchronize after every trace) | 43.92 (44.68) |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
`
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
## Fidelity vs the fp32 reference (same observation, same initial noise)
|
| 67 |
|
| 68 |
The fp32 reference is NVIDIA's own `Gr00tPolicy` run on CPU in fp32 (math SDPA). Both bf16 paths are measured
|
| 69 |
against it; PCC in float64 over the flattened tensor.
|
| 70 |
|
| 71 |
-
| tensor | NVIDIA bf16 + FA2 on the RTX 5090 (`ref-env-n15.md` §5.1) | p150a
|
| 72 |
-
|---|---:|---:|---:|
|
| 73 |
-
| `backbone_features` (post final RMSNorm) | 0.999006 | 0.995863 | 0.995 |
|
| 74 |
-
| `vl_self_attention_out` (= DiT `encoder_hidden_states`) | 0.997315 | 0.991189 | 0.991 |
|
| 75 |
-
| `vit_post_layernorm_out` | 0.999000 | 0.995057 | 0.993 |
|
| 76 |
-
| `action_pred_normalized` (1×16×32) | 0.999991 | 0.999980 (max\|d\| 0.0143) | 0.9999 (+ abs 0.027) |
|
| 77 |
-
| `unnormalized_action.left_arm` / `right_arm` / `right_hand` | 0.99998 / 0.99998 / 0.99999 | 0.999947 / 0.999972 / 0.999994 (max\|d\| 0.021 / 0.017 / 0.029 rad) | 0.9999 (+ abs 0.050 / 0.038 / 0.058) |
|
| 78 |
-
| `unnormalized_action.left_hand` (range 0.099 rad in this sample — PCC not meaningful) | 0.9525 | 0.943352 (max\|d\| 0.018 rad) | abs 0.046 only |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
## Comparison with matching definitions
|
| 86 |
|
| 87 |
Ratio = RTX 5090 ms / p150a ms (> 1 means the p150a is faster).
|
| 88 |
|
| 89 |
-
| row | RTX 5090 (official eager bf16 + FA2) | p150a
|
| 90 |
-
|---|---:|---:|---:|
|
| 91 |
-
| end-to-end: `Gr00tPolicy.get_action` vs port e2e (both: host preprocessing + model + un-normalisation, batch 1) | 58.0 |
|
| 92 |
-
| model only: `model.get_action` vs port device (upload + traces + readback) | 55.4 |
|
| 93 |
-
| backbone: `EagleBackbone.forward` vs traces `vision` + `llm` | 20.8 | 17.46 | 1.19 |
|
| 94 |
-
| action head: `ActionHead.get_action` (VL-SA + 4 denoising steps) vs traces `adapter` + `denoise` | 34.6 | 20.48 | 1.69 |
|
| 95 |
-
| host preprocessing: `apply_transforms` vs port encode | 2.17 | 1.52 | 1.43 |
|
| 96 |
|
| 97 |
Reading: at batch 1 the eager GPU path is kernel-launch bound (the action head is 60 % of its time at 1/3 of the FLOPs,
|
| 98 |
-
≈ 16 blocks × ~15 kernels per denoising step); the p150a
|
| 99 |
-
|
|
|
|
|
|
|
| 100 |
|
| 101 |
## Caveats — read before quoting a ratio
|
| 102 |
|
| 103 |
1. **Eager GPU only.** No `torch.compile` or TensorRT run was made for N1.5. For N1.6 on the same GPU,
|
| 104 |
`torch.compile(DiT.forward, mode="max-autotune")` cut the official e2e from 63.1 ms to 33.1 ms (`ref-env-n16.md` §5)
|
| 105 |
-
and NVIDIA's N1.6 README lists 37 ms compiled on an RTX 5090; a compiled N1.5 would very likely
|
| 106 |
-
|
| 107 |
2. **Different code, similar precision class.** GPU: bf16 autocast + FA2 (NVIDIA's shipped path). p150a: bf16 with
|
| 108 |
-
bfp8_b DiT weights and HiFi2 matmul fidelity. Both are compared against the same fp32
|
|
|
|
| 109 |
3. **Stage boundaries differ slightly.** The GPU "backbone" includes `lm_head`; the p150a `adapter` trace holds the VL
|
| 110 |
self-attention (GPU: inside the action head) plus the K/V hoist and state encoder. Only the e2e and model rows are
|
| 111 |
like-for-like.
|
| 112 |
4. **Loops differ** (3 × 20 timed calls best-median vs 50 timed calls median; `cuda.synchronize` per call vs one
|
| 113 |
host-side synchronize per call). Both exclude HTTP/JSON framing. The GPU e2e includes a `deepcopy` of the observation
|
| 114 |
-
(NVIDIA's code); the p150a e2e includes the
|
| 115 |
5. **Power / efficiency: not compared.** GPU draw during the loop was 190–250 W (idle not recorded in this run); p150a
|
| 116 |
power was not measured.
|
| 117 |
6. **Batch 1, one sample.** Timings are for the canonical observation; the port's traces are shape-locked, so any
|
| 118 |
GR1 request with ≤ 102 text tokens takes the same device time (the sequence is padded to 384).
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
## Reproduce
|
| 121 |
|
|
@@ -124,7 +143,8 @@ where the 1.7× on the head comes from, while the two backbones are within 20 %.
|
|
| 124 |
tt serve changh95/GR00T-N1.5-3B-p150
|
| 125 |
python3 code/scripts/bench_http.py --url http://127.0.0.1:20000 --reps 50 --warmup 10 # server-side timing_ms + client wall
|
| 126 |
# p150a, the port benchmark (needs a built tt-metal 668c2907575 tree, the pinned weights and the project's golden observation):
|
| 127 |
-
python code/models/experimental/gr00t/benchmarks/bench_e2e.py --version n15 --out <dir>
|
|
|
|
| 128 |
# RTX 5090: Isaac-GR00T n1.5-release + the environment of ref-env-n15.md §1.2; the timing script (tt_golden/time_n15.py)
|
| 129 |
# lives in the project's research tree, not in this repo.
|
| 130 |
```
|
|
|
|
| 1 |
# GR00T-N1.5-3B-p150 — Blackhole p150a vs RTX 5090 (same host, same weights, same observation)
|
| 2 |
|
| 3 |
+
Dates: GPU rows 2026-09-14, p150a rows 2026-09-18 (megakernel release; the Stage-1 rows of the 2026-09-14 release are kept
|
| 4 |
+
as the reference). Facts only: every number is copied from the file named in its row. The two sides run **different
|
| 5 |
+
code**: the GPU rows are NVIDIA's unmodified `Gr00tPolicy` (the official inference path, eager PyTorch); the p150a rows
|
| 6 |
+
are this package's tt-nn port (Stage 2: vision / LLM / adapter as TTNN ops replayed from three Metal traces, the DiT
|
| 7 |
+
denoise as one persistent megakernel `generic_op` in the fourth). Read the caveats before comparing.
|
| 8 |
|
| 9 |
## What was run
|
| 10 |
|
| 11 |
| | RTX 5090 | Blackhole p150a |
|
| 12 |
|---|---|---|
|
| 13 |
+
| Code | NVIDIA [Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) tag `n1.5-release` (`4af2b622`), `Gr00tPolicy.get_action`, `FourierGr1ArmsOnlyDataConfig`, unmodified | `code/models/experimental/gr00t` (frozen snapshot `009de892c895`), `Gr00tTT` with `TTPolicy(dtype_policy="mixed_dit", dit_backend="megakernel", mk_arena_dtype="bfp8_b", trace_layout="per_stage")`, device opened by `open_model_device(num_command_queues=2)`, served by `code/gr00t_p150/server/app.py`; Stage-1 reference rows: the same tree with `dit_backend="ttnn"` |
|
| 14 |
+
| Weights | `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e`, bf16 shards from the HF cache | same snapshot; converted once into the device layout (bf16 everywhere; the 16 DiT blocks' matmul weights bfp8_b, packed into the megakernel's DRAM arena — 496 MB streamed per denoising step) |
|
| 15 |
+
| Precision | official path: `torch.autocast(bfloat16)` + flash-attention 2 (`flash_attention_2` is hard-coded in the vendored Eagle2.5 config) | bf16 activations; HiFi2 matmul fidelity in the backbone / adapter and inside the megakernel's blocks (bf16 / HiFi4 tail ops), bfp8_b DiT weights; fp32 host pre/post-processing |
|
| 16 |
| Observation | the port's canonical golden sample: one 256×256 `ego_view` frame (GR1 sim `robot_sim.PickNPlace` traj 0 / step 100), GR1 state (7/7/6/6), instruction "pick the pear from the counter and place it in the plate", batch 1, fixed initial noise | identical inputs (`code/gr00t_p150/demo/n15/`); LLM sequence 296 tokens padded to the static 384 |
|
| 17 |
+
| GPU / host | NVIDIA GeForce RTX 5090 (sm_120), driver 580.126.18; settled at P1, 2985 MHz SM / 13,801 MHz mem, 190–250 W during the loop | one p150a (11×10 compute grid, 8 DRAM banks; the megakernel uses 110 cores), same host, `/dev/tenstorrent/0` |
|
| 18 |
| Software | Python 3.10.20, torch 2.7.1+cu128, torchvision 0.22.1+cu128, transformers 4.51.3, flash-attn 2.8.3 (official prebuilt wheel; its kernels verified on sm_120 against SDPA to 1 bf16 ulp), cuDNN 9.7.1 | tt-metal main `668c2907575` (`v0.79.0-dev20260914-1`), torch 2.11.0+cpu; image: Python 3.12 |
|
| 19 |
+
| Loop | `time_n15.py`: 3 rounds × (20 warm-ups + 20 timed calls), `torch.cuda.synchronize()` around every call, GPU otherwise idle; **best median of the 3 rounds** (the round medians agree within 0.4 ms) | `benchmarks/bench_e2e.py`: 10 warm-ups + 50 timed calls, host wall clock (`perf_counter`), input writes on CQ 1, trace replays enqueued non-blocking with one synchronize per call; a second 50-call pass with a synchronize after every trace gives the per-trace split; **median** (p90 in brackets) |
|
| 20 |
+
| Source files | `docs/research/ref-env-n15.md` §6 (project research notes; timing JSON `golden/n15/timing.json`) — not shipped in this repo | `code/models/experimental/gr00t/benchmarks/results/bench_e2e_n15_20260918-050550.json` (megakernel, the frozen snapshot), `bench_e2e_n15_20260918-032243.json` (same tree, `--dit-backend ttnn`), `e2e_stage1_n15.json` (the 2026-09-14 Stage-1 release) — all shipped; served numbers from this image: `SERVING.md` §4 |
|
| 21 |
|
| 22 |
## Latency, batch 1 (ms)
|
| 23 |
|
|
|
|
| 39 |
`torch.compile` / TensorRT run was made for N1.5 (see caveats). NVIDIA's own N1.5 README quotes, for a single **H100**:
|
| 40 |
VLM backbone 23.18 ms, action head 4 × 6.18 = 24.7 ms, full model 47.88 ms.
|
| 41 |
|
| 42 |
+
### Blackhole p150a (`stages.<name>.value` / `.p90` of the named JSONs)
|
| 43 |
|
| 44 |
+
| stage | megakernel denoise (this release; `bench_e2e_n15_20260918-050550.json`) | same tree, Stage-1 TTNN denoise (`bench_e2e_n15_20260918-032243.json`) | Stage-1 release of 2026-09-14 (`e2e_stage1_n15.json`) |
|
| 45 |
+
|---|---:|---:|---:|
|
| 46 |
+
| **e2e** = host encode + upload + 4 traces + readback + host decode | **33.59** (33.91) | 40.38 (40.76) | 43.36 (44.08) |
|
| 47 |
+
| device = upload + traces + readback | 32.41 (32.61) | 39.14 (39.39) | 41.68 (42.25) |
|
| 48 |
+
| 4 trace replays, one synchronize | 31.12 (31.15) | 37.87 (37.91) | 37.83 (37.87) |
|
| 49 |
+
| — trace `vision` (SigLIP tower + projector) | 8.73 | 8.73 | 8.71 |
|
| 50 |
+
| — trace `llm` (Qwen3-1.7B[:12]) | 8.78 | 8.76 | 8.75 |
|
| 51 |
+
| — trace `adapter` (VL-LN + 4 VL-SA blocks + K/V hoist for the DiT cross-attention + state encoder) | 4.86 (intermediates in DRAM) | 4.48 | 4.47 |
|
| 52 |
+
| — trace `denoise` (4 Euler steps × (action encoder + 16 DiT blocks + decoder)) | **8.93** = 2.23 per step (one `generic_op`) | 16.02 | 16.01 |
|
| 53 |
+
| upload (host inputs → device buffers; CQ 1 in the 09-18 rows) | 1.25 (1.44) | 1.24 (1.41) | 3.77 (4.33) |
|
| 54 |
+
| readback of `action_pred` (D2H) | 0.05 | 0.07 | 0.09 |
|
| 55 |
+
| host encode (`Observation` → `ModelInputs`: image chain, prompt + tokenizer, sin/cos state) | 0.99 (1.22) | 1.05 (1.21) | 1.52 (1.82) |
|
| 56 |
+
| host decode (un-normalise) | 0.13 | 0.16 | 0.17 |
|
| 57 |
+
| e2e of the split pass (synchronize after every trace) | 34.16 (34.89) | 40.64 (40.96) | 43.92 (44.68) |
|
| 58 |
+
|
| 59 |
+
The megakernel column is the frozen snapshot's own run (`commit 009de892c895`); the same benchmark on the live tree at the
|
| 60 |
+
K4b checkpoint gave e2e 33.69 / device 32.48 / denoise 8.92 ms (`bench_e2e_n15_20260918-043024.json`, shipped) — every
|
| 61 |
+
device-side row within 0.1 ms. Against the same-tree TTNN denoise the megakernel saves **6.8 ms end-to-end (−16.8 %)**, all
|
| 62 |
+
of it in the denoise trace (16.02 → 8.93 ms, **−44 %**; per step 4.00 → 2.23 ms) minus the adapter's DRAM placement
|
| 63 |
+
(+0.38 ms). The megakernel alone, traced and replayed 20 times, runs the four steps in 8.86 ms
|
| 64 |
+
(`bench_mk_step_n15_20260918-035610.json`: 2.216 ms per step; the bfp8 streaming floor at the measured 414 GB/s would be
|
| 65 |
+
1.20 ms per step, so the kernel is 1.85× off its bandwidth floor — the block is compute- and gather-bound, not stream-bound).
|
| 66 |
+
The upload / host-encode differences between the 09-18 and 09-14 columns come from the port's 2-CQ input path and
|
| 67 |
+
device-side embedding gather added between the releases, not from the megakernel.
|
| 68 |
+
|
| 69 |
+
Served over HTTP by this image (`tt-model serve` → `scripts/bench_http.py`, 10 warm-ups + 50 timed demo requests, warm
|
| 70 |
+
boot): **device 32.5 ms (p90 32.82) · server-side 34.6 ms (p90 35.39)**,
|
| 71 |
+
client wall 36.3 ms on the same host — `SERVING.md` §4 has both boots and the cold / warm boot times. The Stage-1
|
| 72 |
+
image of 2026-09-14 served 42.43 ms device / 45.40 ms server-side on the same host.
|
| 73 |
|
| 74 |
## Fidelity vs the fp32 reference (same observation, same initial noise)
|
| 75 |
|
| 76 |
The fp32 reference is NVIDIA's own `Gr00tPolicy` run on CPU in fp32 (math SDPA). Both bf16 paths are measured
|
| 77 |
against it; PCC in float64 over the flattened tensor.
|
| 78 |
|
| 79 |
+
| tensor | NVIDIA bf16 + FA2 on the RTX 5090 (`ref-env-n15.md` §5.1) | p150a, megakernel denoise (`tests/tt/results/test_e2e_untraced_taps_vs_golden_n15_20260918-044858.json`, frozen snapshot) | p150a, Stage-1 release of 2026-09-14 (`test_e2e_untraced_taps_vs_golden_n15_20260914-230740.json` of the port project's `build-n15.md` §6 — JSON not shipped in this repo) | gate applied to the p150a (multi-sample rule: twice the worst deviation of NVIDIA's bf16 path over 9 observations) |
|
| 80 |
+
|---|---:|---:|---:|---:|
|
| 81 |
+
| `backbone_features` (post final RMSNorm) | 0.999006 | 0.995863 | 0.995863 | 0.995 |
|
| 82 |
+
| `vl_self_attention_out` (= DiT `encoder_hidden_states`) | 0.997315 | 0.991189 | 0.991189 | 0.991 |
|
| 83 |
+
| `vit_post_layernorm_out` | 0.999000 | 0.995057 | 0.995057 | 0.993 |
|
| 84 |
+
| `action_pred_normalized` (1×16×32) | 0.999991 | 0.999980 (max\|d\| 0.0137) | 0.999980 (max\|d\| 0.0143) | 0.9999 (+ abs 0.027) |
|
| 85 |
+
| `unnormalized_action.left_arm` / `right_arm` / `right_hand` | 0.99998 / 0.99998 / 0.99999 | 0.999944 / 0.999971 / 0.999993 (max\|d\| 0.023 / 0.017 / 0.029 rad) | 0.999947 / 0.999972 / 0.999994 (max\|d\| 0.021 / 0.017 / 0.029 rad) | 0.9999 (+ abs 0.050 / 0.038 / 0.058) |
|
| 86 |
+
| `unnormalized_action.left_hand` (range 0.099 rad in this sample — PCC not meaningful) | 0.9525 | 0.928345 (max\|d\| 0.030 rad) | 0.943352 (max\|d\| 0.018 rad) | abs 0.046 only |
|
| 87 |
+
|
| 88 |
+
The backbone and adapter rows are bit-identical between the two p150a columns (the megakernel does not touch them); the
|
| 89 |
+
action rows move by ≤ 3e-6 PCC. Megakernel vs the TTNN denoise on the same device and observation: `action_pred_normalized`
|
| 90 |
+
PCC 0.999997, per-step velocities ≥ 0.99998 (`test_mk_e2e_backends_agree_canonical_n15_20260918-042408.json`). Over the
|
| 91 |
+
multi-sample set (9 observations, `golden-multisample.md` §4.1) NVIDIA's bf16 path itself drops to `backbone_features`
|
| 92 |
+
0.99771, `vl_self_attention_out` 0.99573 and `action_pred_normalized` 0.999987; the p150a is held to gates derived from
|
| 93 |
+
those minima. All 30 rows of the megakernel run pass (29 gated rows incl. the `left_hand` abs row + the two-runs-bit-equal
|
| 94 |
+
row; the Stage-1 path had 58 rows because its per-step DiT intermediates are observable and gated — inside one
|
| 95 |
+
`generic_op` they are not). On the 9 multi-sample observations the megakernel's gate outcomes equal the TTNN path's on 8;
|
| 96 |
+
the exception is `right_arm` on `t3_s300_seed2` at 0.999869 vs 0.9999 (`SERVING.md` §7).
|
| 97 |
|
| 98 |
## Comparison with matching definitions
|
| 99 |
|
| 100 |
Ratio = RTX 5090 ms / p150a ms (> 1 means the p150a is faster).
|
| 101 |
|
| 102 |
+
| row | RTX 5090 (official eager bf16 + FA2) | p150a, megakernel denoise | ratio | p150a, Stage-1 release (2026-09-14) | ratio |
|
| 103 |
+
|---|---:|---:|---:|---:|---:|
|
| 104 |
+
| end-to-end: `Gr00tPolicy.get_action` vs port e2e (both: host preprocessing + model + un-normalisation, batch 1) | 58.0 | 33.59 | **1.73** | 43.36 | 1.34 |
|
| 105 |
+
| model only: `model.get_action` vs port device (upload + traces + readback) | 55.4 | 32.41 | **1.71** | 41.68 | 1.33 |
|
| 106 |
+
| backbone: `EagleBackbone.forward` vs traces `vision` + `llm` | 20.8 | 17.51 | 1.19 | 17.46 | 1.19 |
|
| 107 |
+
| action head: `ActionHead.get_action` (VL-SA + 4 denoising steps) vs traces `adapter` + `denoise` | 34.6 | 13.79 | **2.51** | 20.48 | 1.69 |
|
| 108 |
+
| host preprocessing: `apply_transforms` vs port encode | 2.17 | 0.99 | 2.19 | 1.52 | 1.43 |
|
| 109 |
|
| 110 |
Reading: at batch 1 the eager GPU path is kernel-launch bound (the action head is 60 % of its time at 1/3 of the FLOPs,
|
| 111 |
+
≈ 16 blocks × ~15 kernels per denoising step); the p150a replays the backbone graph with no launch overhead and runs the
|
| 112 |
+
whole head as one program whose weights stream from DRAM once per step, which is where the 2.5× on the head comes from,
|
| 113 |
+
while the two backbones are within 20 %. The backbone (vision + LLM, 17.5 ms) is now the largest block of the p150a's
|
| 114 |
+
33.6 ms; the denoise is 27 %.
|
| 115 |
|
| 116 |
## Caveats — read before quoting a ratio
|
| 117 |
|
| 118 |
1. **Eager GPU only.** No `torch.compile` or TensorRT run was made for N1.5. For N1.6 on the same GPU,
|
| 119 |
`torch.compile(DiT.forward, mode="max-autotune")` cut the official e2e from 63.1 ms to 33.1 ms (`ref-env-n16.md` §5)
|
| 120 |
+
and NVIDIA's N1.6 README lists 37 ms compiled on an RTX 5090; a compiled N1.5 would very likely land near this
|
| 121 |
+
p150a figure rather than 1.7× behind it.
|
| 122 |
2. **Different code, similar precision class.** GPU: bf16 autocast + FA2 (NVIDIA's shipped path). p150a: bf16 with
|
| 123 |
+
bfp8_b DiT weights (streamed by the megakernel) and HiFi2 matmul fidelity. Both are compared against the same fp32
|
| 124 |
+
reference above; neither is fp32.
|
| 125 |
3. **Stage boundaries differ slightly.** The GPU "backbone" includes `lm_head`; the p150a `adapter` trace holds the VL
|
| 126 |
self-attention (GPU: inside the action head) plus the K/V hoist and state encoder. Only the e2e and model rows are
|
| 127 |
like-for-like.
|
| 128 |
4. **Loops differ** (3 × 20 timed calls best-median vs 50 timed calls median; `cuda.synchronize` per call vs one
|
| 129 |
host-side synchronize per call). Both exclude HTTP/JSON framing. The GPU e2e includes a `deepcopy` of the observation
|
| 130 |
+
(NVIDIA's code); the p150a e2e includes the 1.2 ms input upload.
|
| 131 |
5. **Power / efficiency: not compared.** GPU draw during the loop was 190–250 W (idle not recorded in this run); p150a
|
| 132 |
power was not measured.
|
| 133 |
6. **Batch 1, one sample.** Timings are for the canonical observation; the port's traces are shape-locked, so any
|
| 134 |
GR1 request with ≤ 102 text tokens takes the same device time (the sequence is padded to 384).
|
| 135 |
+
7. **Megakernel disclosures.** The p150a rows of this release run the denoise with 64 KiB less allocatable L1 per core
|
| 136 |
+
and the adapter's intermediates in DRAM, expose no DiT intermediates for gating, and miss one multi-sample action gate
|
| 137 |
+
the Stage-1 path passes (`SERVING.md` §7). `GR00T_DIT_BACKEND=ttnn` reproduces the Stage-1 rows on the same image.
|
| 138 |
|
| 139 |
## Reproduce
|
| 140 |
|
|
|
|
| 143 |
tt serve changh95/GR00T-N1.5-3B-p150
|
| 144 |
python3 code/scripts/bench_http.py --url http://127.0.0.1:20000 --reps 50 --warmup 10 # server-side timing_ms + client wall
|
| 145 |
# p150a, the port benchmark (needs a built tt-metal 668c2907575 tree, the pinned weights and the project's golden observation):
|
| 146 |
+
python code/models/experimental/gr00t/benchmarks/bench_e2e.py --version n15 --out <dir> # megakernel (default)
|
| 147 |
+
python code/models/experimental/gr00t/benchmarks/bench_e2e.py --version n15 --dit-backend ttnn --out <dir> # Stage-1 reference
|
| 148 |
# RTX 5090: Isaac-GR00T n1.5-release + the environment of ref-env-n15.md §1.2; the timing script (tt_golden/time_n15.py)
|
| 149 |
# lives in the project's research tree, not in this repo.
|
| 150 |
```
|
|
@@ -8,7 +8,7 @@ two licences; nothing here grants more than its source does.
|
|
| 8 |
| Model weights `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e` | **not included** — `tt-model serve` / `code/scripts/download_weights.sh` fetch the pinned snapshot into your HF cache | **NVIDIA License** (copy in [`LICENSE`](LICENSE); original: [`LICENSE` in the weights repo](https://huggingface.co/nvidia/GR00T-N1.5-3B/blob/main/LICENSE)) — use limited to research or evaluation (§3.3), redistribution only under the same licence with a copy included (§3.1), derivative works carry the same use limitation (§3.2) | NVIDIA |
|
| 9 |
| Qwen2 tokenizer files (`vocab.json`, `merges.txt`, `tokenizer_config.json`, `special_tokens_map.json`, `added_tokens.json`) | `code/gr00t_p150/assets/tokenizer/n15/` | Apache-2.0 (copied from `gr00t/model/backbone/eagle2_hg_model/` of [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) at tag `n1.5-release`, an Apache-2.0 repository) | NVIDIA / Alibaba Qwen |
|
| 10 |
| Demo frame and joint state (`media/demo_ego_view.png`, `media/demo_observation.json`, `code/gr00t_p150/demo/n15/`) | `media/`, `code/gr00t_p150/demo/n15/` | one step of the GR1 `robot_sim.PickNPlace` demo data shipped with Isaac-GR00T (`demo_data/`, Apache-2.0 repository); the reference actions in `expected.json` were computed from those inputs with the weights above | NVIDIA |
|
| 11 |
-
| The tt-nn port (`code/models/experimental/gr00t/`), the policy server (`code/gr00t_p150/`), the scripts (`code/scripts/`) and the tt-metal files copied into `code/models/{common,tt_dit,demos}/` | `code/` | **Apache-2.0** (SPDX headers; © 2026 Tenstorrent). Because the port's device-layout weights and the served outputs are derivative works of the NVIDIA weights, running this package is bound by the NVIDIA License's non-commercial use limitation (§3.2/§3.3) | Tenstorrent |
|
| 12 |
| The container image (`image/`) | `image/` | tt-metal / tt-nn (Apache-2.0), Ubuntu 22.04 base and the Python packages listed in `requirements.lock` under their own licences; **contains no weights** | Tenstorrent, upstream projects |
|
| 13 |
|
| 14 |
Model card front matter therefore declares `license: other`, `license_name: nvidia-license`,
|
|
|
|
| 8 |
| Model weights `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e` | **not included** — `tt-model serve` / `code/scripts/download_weights.sh` fetch the pinned snapshot into your HF cache | **NVIDIA License** (copy in [`LICENSE`](LICENSE); original: [`LICENSE` in the weights repo](https://huggingface.co/nvidia/GR00T-N1.5-3B/blob/main/LICENSE)) — use limited to research or evaluation (§3.3), redistribution only under the same licence with a copy included (§3.1), derivative works carry the same use limitation (§3.2) | NVIDIA |
|
| 9 |
| Qwen2 tokenizer files (`vocab.json`, `merges.txt`, `tokenizer_config.json`, `special_tokens_map.json`, `added_tokens.json`) | `code/gr00t_p150/assets/tokenizer/n15/` | Apache-2.0 (copied from `gr00t/model/backbone/eagle2_hg_model/` of [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) at tag `n1.5-release`, an Apache-2.0 repository) | NVIDIA / Alibaba Qwen |
|
| 10 |
| Demo frame and joint state (`media/demo_ego_view.png`, `media/demo_observation.json`, `code/gr00t_p150/demo/n15/`) | `media/`, `code/gr00t_p150/demo/n15/` | one step of the GR1 `robot_sim.PickNPlace` demo data shipped with Isaac-GR00T (`demo_data/`, Apache-2.0 repository); the reference actions in `expected.json` were computed from those inputs with the weights above | NVIDIA |
|
| 11 |
+
| The tt-nn port (`code/models/experimental/gr00t/`), the policy server (`code/gr00t_p150/`), the scripts (`code/scripts/`) and the tt-metal files copied into `code/models/{common,tt_dit,demos}/` (incl. the four `deepseek_v3_b1/unified_kernels/*.hpp` device headers the megakernel includes) | `code/` | **Apache-2.0** (SPDX headers; © 2026 Tenstorrent). Because the port's device-layout weights and the served outputs are derivative works of the NVIDIA weights, running this package is bound by the NVIDIA License's non-commercial use limitation (§3.2/§3.3) | Tenstorrent |
|
| 12 |
| The container image (`image/`) | `image/` | tt-metal / tt-nn (Apache-2.0), Ubuntu 22.04 base and the Python packages listed in `requirements.lock` under their own licences; **contains no weights** | Tenstorrent, upstream projects |
|
| 13 |
|
| 14 |
Model card front matter therefore declares `license: other`, `license_name: nvidia-license`,
|
|
@@ -4,11 +4,24 @@ tags:
|
|
| 4 |
- p150
|
| 5 |
- tt-dit-server
|
| 6 |
- tt-model-cache
|
| 7 |
-
- tt-model-catalog
|
| 8 |
- tt-model-container
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
-
#
|
| 12 |
|
| 13 |
NVIDIA Isaac GR00T N1.5 (3B vision-language-action policy) running on one Tenstorrent Blackhole p150a via tt-nn: one camera frame, proprioceptive state and a task instruction in, a 16-step chunk of GR1 arm and hand joint targets out. Stage-2 release: vision tower, LLM and VL adapter as TTNN ops replayed from Metal traces (bf16); the whole DiT denoise (4 steps × 16 blocks) as **one persistent megakernel** on 110 Tensix cores, streaming its bfp8 weights from a DRAM arena once per step. `GR00T_DIT_BACKEND=ttnn` selects the Stage-1 TTNN denoise.
|
| 14 |
Weights: [nvidia/GR00T-N1.5-3B](https://huggingface.co/nvidia/GR00T-N1.5-3B) · Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) · Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (`n1.5-release`) · Port: `code/models/experimental/gr00t` and the server `code/gr00t_p150` in this repo
|
|
@@ -24,7 +37,8 @@ tt-model pull changh95/GR00T-N1.5-3B-p150 --with-weights
|
|
| 24 |
tt-model serve changh95/GR00T-N1.5-3B-p150
|
| 25 |
```
|
| 26 |
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
### Run with tt-cli
|
| 30 |
|
|
@@ -100,4 +114,3 @@ The exact sources the image was built from — `code/` in this repo is byte-iden
|
|
| 100 |
| tt-metal | [`668c290757550588d0ce46b180c344a462a2aaf5`](https://github.com/tenstorrent/tt-metal/commit/668c290757550588d0ce46b180c344a462a2aaf5) |
|
| 101 |
| `code/` digest | `d87906471090789d` (sha256, first 16 hex digits) |
|
| 102 |
| built | 2026-09-17T20:23:50+00:00 by tt-model 0.1.0 |
|
| 103 |
-
|
|
|
|
| 4 |
- p150
|
| 5 |
- tt-dit-server
|
| 6 |
- tt-model-cache
|
|
|
|
| 7 |
- tt-model-container
|
| 8 |
+
- tenstorrent
|
| 9 |
+
- ttnn
|
| 10 |
+
- tt-metal
|
| 11 |
+
- tt-nn
|
| 12 |
+
- robotics
|
| 13 |
+
- vla
|
| 14 |
+
- gr00t
|
| 15 |
+
- tt-model-catalog
|
| 16 |
+
pipeline_tag: robotics
|
| 17 |
+
license: other
|
| 18 |
+
license_name: nvidia-license
|
| 19 |
+
license_link: https://huggingface.co/nvidia/GR00T-N1.5-3B/blob/main/LICENSE
|
| 20 |
+
base_model:
|
| 21 |
+
- nvidia/GR00T-N1.5-3B
|
| 22 |
---
|
| 23 |
|
| 24 |
+
# GR00T-N1.5-3B-p150
|
| 25 |
|
| 26 |
NVIDIA Isaac GR00T N1.5 (3B vision-language-action policy) running on one Tenstorrent Blackhole p150a via tt-nn: one camera frame, proprioceptive state and a task instruction in, a 16-step chunk of GR1 arm and hand joint targets out. Stage-2 release: vision tower, LLM and VL adapter as TTNN ops replayed from Metal traces (bf16); the whole DiT denoise (4 steps × 16 blocks) as **one persistent megakernel** on 110 Tensix cores, streaming its bfp8 weights from a DRAM arena once per step. `GR00T_DIT_BACKEND=ttnn` selects the Stage-1 TTNN denoise.
|
| 27 |
Weights: [nvidia/GR00T-N1.5-3B](https://huggingface.co/nvidia/GR00T-N1.5-3B) · Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) · Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (`n1.5-release`) · Port: `code/models/experimental/gr00t` and the server `code/gr00t_p150` in this repo
|
|
|
|
| 37 |
tt-model serve changh95/GR00T-N1.5-3B-p150
|
| 38 |
```
|
| 39 |
|
| 40 |
+
- Weights [`nvidia/GR00T-N1.5-3B`](https://huggingface.co/nvidia/GR00T-N1.5-3B) at `869830fc749c` (5.45 GB) go to your HF cache; the image does not contain them.
|
| 41 |
+
- Serves on port 20000 (or the next free port); ready when the log says `Application startup complete`.
|
| 42 |
|
| 43 |
### Run with tt-cli
|
| 44 |
|
|
|
|
| 114 |
| tt-metal | [`668c290757550588d0ce46b180c344a462a2aaf5`](https://github.com/tenstorrent/tt-metal/commit/668c290757550588d0ce46b180c344a462a2aaf5) |
|
| 115 |
| `code/` digest | `d87906471090789d` (sha256, first 16 hex digits) |
|
| 116 |
| built | 2026-09-17T20:23:50+00:00 by tt-model 0.1.0 |
|
|
|
|
@@ -3,17 +3,20 @@
|
|
| 3 |
This repo is a **tt-model container package source**: `tt-model.yaml` + the code under `code/`. `tt-model package
|
| 4 |
--container` builds the OCI image (tt-metal `668c2907575` built from source + the port + the HTTP stack),
|
| 5 |
`tt-model serve` runs it against the card, `tt-model push` publishes it to this HF repo. The server is
|
| 6 |
-
`code/gr00t_p150/server/app.py` (FastAPI, `kind: tt-dit-server`); what it runs on the device is the port's **Stage-
|
| 7 |
-
path
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
| | |
|
| 11 |
|---|---|
|
| 12 |
| tt-metal tree the image is built from | `/home/deepgadget/experiments/gr00t/publish/tt-metal-668c2907575` — a clean local clone of tt-metal `main` @ `668c290757550588d0ce46b180c344a462a2aaf5` (`v0.79.0-dev20260914-1`), submodules populated, `git status` empty (the working clone `/home/deepgadget/experiments/gr00t/tt-metal` at the same commit has untracked dirs and would be recorded as dirty); torch pin 2.11.0 |
|
| 13 |
-
| port code shipped | `code/models/experimental/gr00t/` = tt-metal ref `gr00t-p150-snapshot`, commit `
|
| 14 |
| weights | `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e`: `model-0000{1,2,3}-of-00003.safetensors` (5,448,327,040 B of bf16 tensors; 5,448,460,016 B on disk in 3 files), `model.safetensors.index.json`, `config.json`, `experiment_cfg/metadata.json`, `LICENSE` — a pinned pointer, never in the image |
|
| 15 |
-
| app | `gr00t_p150.server.app:app` (uvicorn `--lifespan on`; the lifespan does weights → tokenizer → device → model → trace capture → warm-up → READY) |
|
| 16 |
-
| device recipe | `models.experimental.gr00t.tt.
|
| 17 |
| serving shape | embodiment `gr1` (slot 24), layout `gr1`: 1 camera (`ego_view`) × 1 frame × 256 image tokens, ≤ 102 text tokens, LLM sequence padded to 384, state 7/7/6/6 → 16 × [7, 7, 6, 6] actions, batch 1 |
|
| 18 |
|
| 19 |
Below, `$PUB=/home/deepgadget/experiments/gr00t/publish` (this repo is `$PUB/GR00T-N1.5-3B-p150`),
|
|
@@ -25,15 +28,17 @@ Below, `$PUB=/home/deepgadget/experiments/gr00t/publish` (this repo is `$PUB/GR0
|
|
| 25 |
```
|
| 26 |
tt-model.yaml authoring manifest (schema 5.1) -- the whole build/serve interface, incl. the model card text
|
| 27 |
SERVING.md this file
|
| 28 |
-
GPU_COMPARISON.md RTX 5090 vs p150a, matching definitions and caveats
|
| 29 |
LICENSE the NVIDIA License (weights); LICENSE-NOTICE.md says what is under which licence
|
| 30 |
media/demo_ego_view.png the demo frame (256x256, GR1 sim robot_sim.PickNPlace traj 0 / step 100)
|
| 31 |
media/demo_observation.json the matching raw joint state + instruction (request body minus the image)
|
| 32 |
-
media/demo_actions.png served actions vs the fp32 reference for that request
|
| 33 |
code/models/experimental/gr00t/ the tt-nn port (tt-metal tree layout): common/ (host pipeline: preprocessing, prompts,
|
| 34 |
-
normalisation, checkpoint, weight plan), tt/ (device model, layers, traces
|
|
|
|
| 35 |
reference/ (fp32 torch reference), tests/, benchmarks/
|
| 36 |
-
code/models/{common,tt_dit,demos}/ the
|
|
|
|
| 37 |
code/gr00t_p150/ the policy server: server/{app,schemas,smoke_test}.py, demo/n15/ (canonical demo
|
| 38 |
observation + golden actions + noise), assets/tokenizer/n15/ (vendored Qwen2 tokenizer)
|
| 39 |
code/scripts/ download_weights.sh, bench_http.py
|
|
@@ -44,8 +49,20 @@ tt_kernel_manifest.json, image/ written by `tt-model package`; uploaded by `tt
|
|
| 44 |
The port keeps its tt-metal-tree absolute imports (`models.experimental.gr00t.*`, `models.tt_dit.*`,
|
| 45 |
`models.common.*`): in the image the code lands at `/opt/tt-metal/<path>` with `PYTHONPATH=/opt/tt-metal`, and the
|
| 46 |
image's tt-metal copy excludes `models/`, so everything under `models/` comes from this repo's `code/` (the port) and
|
| 47 |
-
`source.code` (the
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
## 2. Run on the HOST for validation (no Docker)
|
| 51 |
|
|
@@ -63,8 +80,9 @@ export PYTHONPATH=$PWD/code:$TREE:$TREE/ttnn:$PUB/http-side # code/ FIRST s
|
|
| 63 |
export TT_METAL_CACHE=$HOME/.cache/tt-metal-cache-gr00t # warm JIT kernels of the port's device sessions
|
| 64 |
export HF_MODEL=nvidia/GR00T-N1.5-3B TT_WEIGHTS_REVISION=869830fc749c35f34771aa5209f923ac57e4564e
|
| 65 |
export GR00T_VERSION=n15 TT_MESH_SHAPE=1x1 TT_DEVICE_ID=0 TT_METAL_VISIBLE_DEVICES=0
|
|
|
|
| 66 |
export GR00T_TOKENIZER_DIR_N15=$PWD/code/gr00t_p150/assets/tokenizer/n15 # optional: the server picks the vendored dir itself
|
| 67 |
-
export GR00T_TT_CACHE=$HOME/.cache/gr00t-tt # host .pt plan tier + .tensorbin device tier (~
|
| 68 |
|
| 69 |
# import check, no device (what the image's verify.sh does):
|
| 70 |
$TREE/python_env/bin/python -c "import gr00t_p150.server.app as a, sys; assert a.app and 'ttnn' not in sys.modules; print('ok')"
|
|
@@ -77,35 +95,40 @@ DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh
|
|
| 77 |
```
|
| 78 |
|
| 79 |
Boot log landmarks (they drive `tt-model serve`'s checklist): `Loading weights: nvidia/GR00T-N1.5-3B @ 869830fc…` →
|
| 80 |
-
`Tokenizer files: …` → `Opening device 0 (trace_region_size=67108864, l1_small_size=32768
|
| 81 |
-
Gr00tTT.from_pretrained(n15, …)` → `Model built in
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
`
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
`
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
```
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
| 101 |
```
|
| 102 |
|
| 103 |
-
The per-group PCC / max|d| equal the port's own device test
|
| 104 |
-
|
| 105 |
-
deterministic, so every run reproduces them bit-exactly.
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
`per_stage` actions equal the device test to the printed precision (§4).
|
| 109 |
|
| 110 |
Offline overrides: `GR00T_WEIGHTS_DIR=<dir with the snapshot files>` (shimmed into a private hub cache so the port's
|
| 111 |
`snapshot_dir()` resolves to it), `GR00T_TOKENIZER_DIR=<dir>`.
|
|
@@ -121,7 +144,7 @@ uv venv --python 3.12 $PUB/depcheck-n15 -q
|
|
| 121 |
uv pip install --python $PUB/depcheck-n15/bin/python --dry-run torch==2.11.0 fastapi uvicorn "pydantic>=2" pillow "numpy>=1.24.4,<2" \
|
| 122 |
safetensors huggingface_hub "transformers==5.12.1" "opencv-python-headless==4.8.1.78" "torchvision==0.26.0" pytest \
|
| 123 |
--extra-index-url https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match
|
| 124 |
-
# -> torch 2.11.0+cpu, torchvision 0.26.0+cpu, numpy 1.26.4, transformers 5.12.1, opencv-python-headless 4.8.1.78 (cp312 wheel), pytest 9.1.1
|
| 125 |
```
|
| 126 |
|
| 127 |
## 3. Package, serve, push (Docker)
|
|
@@ -152,8 +175,8 @@ $ROOT/.venv/bin/tt-model push $PUB/build/gr00t-n1.5-3b-p150 --publish
|
|
| 152 |
|
| 153 |
`serve` pre-downloads the pinned files (a cache hit on this host), then `docker run --user 0:0 --device
|
| 154 |
/dev/tenstorrent --ipc host` with `/dev/hugepages-1G`, `~/.cache/huggingface` at `/hf` (rw, `HF_HOME=/hf`) and
|
| 155 |
-
`~/.cache/tt-model/gr00t-n1.5-3b-p150/{cache,weights,tensors}` at `/cache` (`TT_METAL_CACHE`, JIT kernels
|
| 156 |
-
`/weight-cache` (`TT_DIT_CACHE_DIR`; the port's weight tiers go to `/weight-cache/gr00t-tt`, ~6.4 GB) and
|
| 157 |
`/tensor-cache`; it exports exactly `HF_MODEL=nvidia/GR00T-N1.5-3B`, `MESH_DEVICE=P150`, `TT_MESH_SHAPE=1x1` plus
|
| 158 |
`serve.env` from `tt-model.yaml`, publishes the first free port from 20000 and waits ≤ 1800 s for `Application startup
|
| 159 |
complete`. tt-cli users: `tt serve changh95/GR00T-N1.5-3B-p150` / `tt model stop changh95/GR00T-N1.5-3B-p150`
|
|
@@ -166,38 +189,41 @@ complete`. tt-cli users: `tt serve changh95/GR00T-N1.5-3B-p150` / `tt model stop
|
|
| 166 |
|
| 167 |
## 4. Container validation (the image built from this manifest)
|
| 168 |
|
| 169 |
-
Image `tt-model/gr00t-n1.5-3b-p150:
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
`{serve,smoke,bench-http
|
| 176 |
-
|
|
|
|
| 177 |
|
| 178 |
| boot | caches (`~/.cache/tt-model/gr00t-n1.5-3b-p150/`) | host load | `tt-model serve` → READY | container boot (lifespan) | smoke | `bench_http.py` (10 warm-up + 50): device / server-side median (p90) | `tt-model stop` |
|
| 179 |
|---|---|---|---|---|---|---|---|
|
| 180 |
-
| 1 (**cold**) | `weights/` and `cache/` empty before;
|
| 181 |
-
| 2 (warm
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
`
|
| 188 |
-
0.
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
`GET /info` inside the container reports `
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
`
|
| 200 |
-
|
|
|
|
|
|
|
| 201 |
changh95/GR00T-N1.5-3B-p150` only works after `pull`).
|
| 202 |
|
| 203 |
## 5. Request / response contract
|
|
@@ -205,7 +231,7 @@ changh95/GR00T-N1.5-3B-p150` only works after `pull`).
|
|
| 205 |
| route | returns |
|
| 206 |
|---|---|
|
| 207 |
| `GET /health` | `{"status": "ok" \| "starting", "model": "GR00T-N1.5-3B-p150", "device": "blackhole:0"}` — 200 always; `ok` only after the warm-up |
|
| 208 |
-
| `GET /info` | model / version / task / hardware / `stage` (`stage1-ttnn-traces`
|
| 209 |
| `GET /v1/models` | OpenAI-shaped stub `{"object": "list", "data": [{"id": "nvidia/GR00T-N1.5-3B", "object": "model", "owned_by": "changh95"}]}` so the tt-model ready card / `tt-model curl` do not 404; this is not a chat API |
|
| 210 |
| `GET /demo` | `{"request": <ready-to-post demo body>, "expected": <fp32 reference actions + gates>}` |
|
| 211 |
| `POST /predict` | one observation → one 16-step action chunk (below) |
|
|
@@ -231,45 +257,67 @@ Response (200):
|
|
| 231 |
"normalized": false, "embodiment": "gr1", "embodiment_id": 24, "version": "n15", "model": "GR00T-N1.5-3B-p150", "layout": "gr1",
|
| 232 |
"images": {"ego_view": {"frames": 1, "received_hw": [256, 256]}}, "seq_len": 296, "prompt_tokens": 14, "state_dtype": "float64",
|
| 233 |
"noise_source": "seed" | "client", "seed": 0 | null,
|
| 234 |
-
"timing_ms": {"decode": 1.
|
| 235 |
```
|
| 236 |
|
| 237 |
`actions` are physical joint targets (radians) — `(y + 1) / 2 · (max − min) + min` with the checkpoint's
|
| 238 |
`experiment_cfg/metadata.json` GR1 statistics — exactly what `Gr00tPolicy.get_action` returns. `timing_ms`: `decode` =
|
| 239 |
base64 + image decode + validation; `encode` = the port's host preprocessing; `device` = `predict_normalized` (static-shape
|
| 240 |
-
asserts, input writes, 4 × `execute_trace`
|
| 241 |
-
whole handler. Errors: **400** structural problems (`{"detail":
|
| 242 |
-
bad state / instruction / noise, other embodiment; **503** while
|
| 243 |
-
Handlers are synchronous and serialised on one lock; batch 1.
|
| 244 |
|
| 245 |
## 6. Environment (read in the lifespan, never at import)
|
| 246 |
|
| 247 |
| variable | container value (`serve.env`) | meaning |
|
| 248 |
|---|---|---|
|
| 249 |
| `HF_MODEL` / `TT_WEIGHTS_REVISION` | `nvidia/GR00T-N1.5-3B` (launcher) / `869830fc…` | must be the version's pinned repo and sha (both are asserted against the port's own `configs.HF_REPOS` / `HF_SNAPSHOT_SHAS`) |
|
| 250 |
-
| `GR00T_VERSION` | `n15` | the server code is shared by
|
| 251 |
| `GR00T_EMBODIMENT` / `GR00T_LAYOUT` | `gr1` / `gr1` | the only device-validated layout of this release |
|
| 252 |
| `GR00T_POLICY` / `GR00T_TRACE_LAYOUT` | `mixed_dit` / `per_stage` | `TTPolicy` knobs; `bf16` / `mixed` and `two` exist in the port but are not the benchmarked defaults |
|
| 253 |
-
| `
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
| `GR00T_TOKENIZER_DIR_N15` | `/opt/tt-metal/gr00t_p150/assets/tokenizer/n15` | vendored Qwen2 tokenizer (not in the weights repo); `GR00T_TOKENIZER_DIR` overrides |
|
| 255 |
| `GR00T_TT_CACHE` | `/weight-cache/gr00t-tt` | host `.pt` plan tier + `.tensorbin` device tier (persisted under `~/.cache/tt-model/gr00t-n1.5-3b-p150/weights`) |
|
| 256 |
| `GR00T_PROJECT_ROOT` / `GR00T_GOLDEN_ROOT` | `/nonexistent` | the port's dev-box defaults; nothing on the serve path needs them |
|
| 257 |
-
| `GR00T_WARMUP_RUNS` / `GR00T_WARMUP_PCC_MIN` | `
|
| 258 |
| `GR00T_DEFAULT_SEED`, `GR00T_TORCH_THREADS`, `GR00T_WEIGHTS_DIR` | unset | request default seed (0), torch intra-op threads, offline snapshot dir |
|
| 259 |
| `TT_MESH_SHAPE`, `TT_DEVICE_ID`, `TT_METAL_VISIBLE_DEVICES` | `1x1` (launcher), `0`, `0` | one chip; any other mesh shape is refused |
|
| 260 |
|
| 261 |
## 7. Caveats
|
| 262 |
|
| 263 |
-
- **Stage-
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
- **Shape-locked traces.** One embodiment (`gr1`), one camera, one frame, ≤ 102 text tokens (LLM S padded to 384), batch 1.
|
| 266 |
Other embodiments / cameras / longer instructions → 400 before anything reaches the device. Concurrent requests queue on one lock.
|
| 267 |
- **Deterministic noise.** Actions are a deterministic function of (observation, seed | noise); the reference noise of the
|
| 268 |
-
demo is not seed-reproducible, so the smoke test posts it as `noise`. Same observation + same noise → bit-identical actions
|
|
|
|
| 269 |
- **Base checkpoint.** Outputs are the GR1 sim data-config actions of the released N1.5 checkpoint; a real robot needs NVIDIA's post-training.
|
| 270 |
- **First container boot is cold**: the port converts the 5.4 GB checkpoint into its `.pt` plan tier and the `.tensorbin`
|
| 271 |
-
device tier under `/weight-cache/gr00t-tt` and JITs
|
| 272 |
-
`~/.cache/tt-model/gr00t-n1.5-3b-p150/`, so later boots are seconds (measured values in §4).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
- **Python 3.12 image vs 3.10 tree venv.** Host runs are an approximation; the image is authoritative (its `verify.sh`
|
| 274 |
imports the app and the port with no device; the served smoke in §4 is the proof).
|
| 275 |
- **Weights licence.** `nvidia/GR00T-N1.5-3B` is under the NVIDIA License (non-commercial: research or evaluation use);
|
|
|
|
| 3 |
This repo is a **tt-model container package source**: `tt-model.yaml` + the code under `code/`. `tt-model package
|
| 4 |
--container` builds the OCI image (tt-metal `668c2907575` built from source + the port + the HTTP stack),
|
| 5 |
`tt-model serve` runs it against the card, `tt-model push` publishes it to this HF repo. The server is
|
| 6 |
+
`code/gr00t_p150/server/app.py` (FastAPI, `kind: tt-dit-server`); what it runs on the device is the port's **Stage-2**
|
| 7 |
+
path: the vision tower, the LLM and the VL adapter as TTNN ops captured once into three Metal traces (`vision`, `llm`,
|
| 8 |
+
`adapter`) and, in the fourth trace (`denoise`), the whole DiT flow-matching head — action encoder, 16 DiT blocks,
|
| 9 |
+
decoder, 4 Euler steps — as **one persistent megakernel `generic_op`** (`TTPolicy.dit_backend="megakernel"`, bfp8_b
|
| 10 |
+
weight arena in DRAM; the port's default since 2026-09-18). `GR00T_DIT_BACKEND=ttnn` serves the Stage-1 TTNN denoise
|
| 11 |
+
(the 2026-09-14 release path) from the same image; both are reported by `GET /info -> stage`.
|
| 12 |
|
| 13 |
| | |
|
| 14 |
|---|---|
|
| 15 |
| tt-metal tree the image is built from | `/home/deepgadget/experiments/gr00t/publish/tt-metal-668c2907575` — a clean local clone of tt-metal `main` @ `668c290757550588d0ce46b180c344a462a2aaf5` (`v0.79.0-dev20260914-1`), submodules populated, `git status` empty (the working clone `/home/deepgadget/experiments/gr00t/tt-metal` at the same commit has untracked dirs and would be recorded as dirty); torch pin 2.11.0 |
|
| 16 |
+
| port code shipped | `code/models/experimental/gr00t/` = tt-metal ref `gr00t-p150-snapshot-mk`, commit `009de892c895917ad6abe9a6c36518f00316ed4b` (parent `668c290`; frozen 2026-09-18T04:45 KST; megakernel tree `tt/megakernel/**` = the K4b-final checkpoint, sha256 listing `tests/tt/results/mk_tree_20260918-041439.sha256`, digest `7df61c35e9dc7857`), minus `__pycache__`, `.omc` tool state, the port's `generated/` runtime output and all but the 50 N1.5 result JSONs this repo cites (`tests/tt/results/`: the 13 `*_summary.md` + the K4b-checkpoint chain of 2026-09-18 04:08–04:37 KST + the 4 snapshot-validation JSONs; `benchmarks/results/`: 8 JSONs, §1) |
|
| 17 |
| weights | `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e`: `model-0000{1,2,3}-of-00003.safetensors` (5,448,327,040 B of bf16 tensors; 5,448,460,016 B on disk in 3 files), `model.safetensors.index.json`, `config.json`, `experiment_cfg/metadata.json`, `LICENSE` — a pinned pointer, never in the image |
|
| 18 |
+
| app | `gr00t_p150.server.app:app` (uvicorn `--lifespan on`; the lifespan does weights → tokenizer → policy → device → model → trace capture → warm-up → READY) |
|
| 19 |
+
| device recipe | `models.experimental.gr00t.tt.model.open_model_device(policy, num_command_queues=2, trace_region_size=64 MiB, l1_small_size=32768, device_id=0)`: under the megakernel policy the worker L1 is opened 64 KiB smaller (`worker_l1_size` 1,461,248 − 65,536 = **1,395,712 B**; the kernel-config ring of the megakernel binaries, 136,192 B) and the VL adapter's intermediates are placed in DRAM; asserts the p150a's 11×10 compute grid and 8 DRAM banks; `TTPolicy(dtype_policy="mixed_dit", dit_backend="megakernel", mk_arena_dtype="bfp8_b", trace_layout="per_stage")` |
|
| 20 |
| serving shape | embodiment `gr1` (slot 24), layout `gr1`: 1 camera (`ego_view`) × 1 frame × 256 image tokens, ≤ 102 text tokens, LLM sequence padded to 384, state 7/7/6/6 → 16 × [7, 7, 6, 6] actions, batch 1 |
|
| 21 |
|
| 22 |
Below, `$PUB=/home/deepgadget/experiments/gr00t/publish` (this repo is `$PUB/GR00T-N1.5-3B-p150`),
|
|
|
|
| 28 |
```
|
| 29 |
tt-model.yaml authoring manifest (schema 5.1) -- the whole build/serve interface, incl. the model card text
|
| 30 |
SERVING.md this file
|
| 31 |
+
GPU_COMPARISON.md RTX 5090 vs p150a (megakernel and Stage-1 rows), matching definitions and caveats
|
| 32 |
LICENSE the NVIDIA License (weights); LICENSE-NOTICE.md says what is under which licence
|
| 33 |
media/demo_ego_view.png the demo frame (256x256, GR1 sim robot_sim.PickNPlace traj 0 / step 100)
|
| 34 |
media/demo_observation.json the matching raw joint state + instruction (request body minus the image)
|
| 35 |
+
media/demo_actions.png served actions vs the fp32 reference for that request (rendered from this image's smoke response)
|
| 36 |
code/models/experimental/gr00t/ the tt-nn port (tt-metal tree layout): common/ (host pipeline: preprocessing, prompts,
|
| 37 |
+
normalisation, checkpoint, weight plan), tt/ (device model, layers, traces; tt/megakernel/:
|
| 38 |
+
arena, core map, descriptors, dit_program + kernels/dit_kernel.cpp and ops/*.hpp),
|
| 39 |
reference/ (fp32 torch reference), tests/, benchmarks/
|
| 40 |
+
code/models/{common,tt_dit,demos}/ the 20 tt-metal files the port needs from the tree (source.code in tt-model.yaml): 16 Python
|
| 41 |
+
modules + the 4 deepseek_v3_b1/unified_kernels/*.hpp headers the megakernel's kernel source includes
|
| 42 |
code/gr00t_p150/ the policy server: server/{app,schemas,smoke_test}.py, demo/n15/ (canonical demo
|
| 43 |
observation + golden actions + noise), assets/tokenizer/n15/ (vendored Qwen2 tokenizer)
|
| 44 |
code/scripts/ download_weights.sh, bench_http.py
|
|
|
|
| 49 |
The port keeps its tt-metal-tree absolute imports (`models.experimental.gr00t.*`, `models.tt_dit.*`,
|
| 50 |
`models.common.*`): in the image the code lands at `/opt/tt-metal/<path>` with `PYTHONPATH=/opt/tt-metal`, and the
|
| 51 |
image's tt-metal copy excludes `models/`, so everything under `models/` comes from this repo's `code/` (the port) and
|
| 52 |
+
`source.code` (the 20 tree files). The megakernel's kernel source is JIT-compiled at boot from
|
| 53 |
+
`/opt/tt-metal/models/experimental/gr00t/tt/megakernel/kernels/dit_kernel.cpp`; it includes four headers of the
|
| 54 |
+
tree's `models/demos/deepseek_v3_b1/unified_kernels/` (`kernel_op_api`, `kernel_utils`, `dataflow_utils`, `mcast`),
|
| 55 |
+
which is why those are in `source.code` (their own includes live under `tt_metal/`, which the image ships). The server
|
| 56 |
+
package `gr00t_p150` is shared with `changh95/GR00T-N1.6-3B-p150` and `changh95/GR00T-N1.7-3B-p150`; this repo ships
|
| 57 |
+
only the N1.5 assets (`demo/n15`, `assets/tokenizer/n15`).
|
| 58 |
+
|
| 59 |
+
Result JSONs shipped under `code/models/experimental/gr00t/benchmarks/results/` (the card's numbers ship with the code):
|
| 60 |
+
`bench_e2e_n15_20260918-050550.json` (the snapshot's own megakernel benchmark, `commit 009de892…`, the card's port row),
|
| 61 |
+
`bench_e2e_n15_20260918-043024.json` (the same benchmark on the live tree at the K4b checkpoint), `bench_e2e_n15_20260918-032243.json`
|
| 62 |
+
(the same tree with `--dit-backend ttnn`: the Stage-1 reference the −16.8 % is measured against), `bench_mk_step_n15_20260918-035610.json`
|
| 63 |
+
(the megakernel alone, 20 traced replays), `bench_load_n15_20260918-042919.json` (start-up cost with / without the ttnn DiT
|
| 64 |
+
weights), and the 2026-09-14 Stage-1 documents `e2e_stage1_n15.json`, `denoise_stage1_n15.json`, `stage_ops_n15.json`
|
| 65 |
+
(the previous release's card).
|
| 66 |
|
| 67 |
## 2. Run on the HOST for validation (no Docker)
|
| 68 |
|
|
|
|
| 80 |
export TT_METAL_CACHE=$HOME/.cache/tt-metal-cache-gr00t # warm JIT kernels of the port's device sessions
|
| 81 |
export HF_MODEL=nvidia/GR00T-N1.5-3B TT_WEIGHTS_REVISION=869830fc749c35f34771aa5209f923ac57e4564e
|
| 82 |
export GR00T_VERSION=n15 TT_MESH_SHAPE=1x1 TT_DEVICE_ID=0 TT_METAL_VISIBLE_DEVICES=0
|
| 83 |
+
export GR00T_DIT_BACKEND=megakernel GR00T_MK_ARENA_DTYPE=bfp8_b GR00T_NUM_CQS=2 # = the image's serve.env (= the port's defaults)
|
| 84 |
export GR00T_TOKENIZER_DIR_N15=$PWD/code/gr00t_p150/assets/tokenizer/n15 # optional: the server picks the vendored dir itself
|
| 85 |
+
export GR00T_TT_CACHE=$HOME/.cache/gr00t-tt # host .pt plan tier + .tensorbin device tier (~2.5 GB under the megakernel for N1.5; 6.4 GB on the Stage-1 path)
|
| 86 |
|
| 87 |
# import check, no device (what the image's verify.sh does):
|
| 88 |
$TREE/python_env/bin/python -c "import gr00t_p150.server.app as a, sys; assert a.app and 'ttnn' not in sys.modules; print('ok')"
|
|
|
|
| 95 |
```
|
| 96 |
|
| 97 |
Boot log landmarks (they drive `tt-model serve`'s checklist): `Loading weights: nvidia/GR00T-N1.5-3B @ 869830fc…` →
|
| 98 |
+
`Tokenizer files: …` → `Opening device 0 (trace_region_size=67108864, l1_small_size=32768, num_command_queues=2,
|
| 99 |
+
dit_backend=megakernel, mk_arena_dtype=bfp8_b)` → `Loading pipeline: Gr00tTT.from_pretrained(n15, …)` → `Model built in
|
| 100 |
+
N s (weights N s, 499 tensors, 2652 MB on device, cache path warm|cold; dit_backend megakernel, arena bfp8_b, adapter_mem
|
| 101 |
+
DRAM, head {mk_arena_plan_pack_s, mk_upload_s, …}; ttnn DiT weights skipped: 279 tensors / 524 MB)` → `Warming up: capture
|
| 102 |
+
per_stage traces on the n15 demo observation, then N traced predict(s)` → `Warmup k/N: … ms (encode, device, decode)` →
|
| 103 |
+
`Warm-up fidelity vs golden fp32: actions PCC …` → `Warmup complete (capture s, first ms, steady ms; boot s) -- per_stage
|
| 104 |
+
traces ['vision', 'llm', 'adapter', 'denoise']` → uvicorn `Application startup complete`. Any failure raises and uvicorn
|
| 105 |
+
exits non-zero (no CPU fallback); the boot also **fails** if the warm-up actions' PCC vs the shipped golden is below
|
| 106 |
+
`GR00T_WARMUP_PCC_MIN` (0.999). SIGTERM / Ctrl-C: `Releasing traces and device tensors` → `Closing device`.
|
| 107 |
+
|
| 108 |
+
Under the megakernel the ttnn DiT / precompute / encoder tensors are not uploaded (499 device tensors, 2,652 MB, instead
|
| 109 |
+
of the Stage-1 path's 778 / 3,176 MB; `bench_load_n15_20260918-042919.json`); the bfp8_b weight arena the kernel streams
|
| 110 |
+
from (16 blocks, 496 MB per step) is packed on the host and uploaded once at model build (`mk_arena_plan_pack_s` ≈ 0.9 s,
|
| 111 |
+
`mk_upload_s` ≈ 1.0 s in the log line).
|
| 112 |
+
|
| 113 |
+
Measured host boot of the megakernel path with a **fresh, empty `TT_METAL_CACHE`** (every kernel JIT-compiled, incl. the
|
| 114 |
+
megakernel; warm `~/.cache/gr00t-tt` weight tiers; `GR00T_WARMUP_RUNS=5`; `$PUB/logs/server_n15_20260918-043232.log`,
|
| 115 |
+
`tests/tt/results/mk_default_summary.md` §7): `Model built in 2.5 s` (weights 0.4 s), `warm_and_capture` 31.4 s, boot to
|
| 116 |
+
READY **37.6 s**, 5 traced warm-ups 33.8 → 33.8 ms. Kernels are rebuilt once for a new `TT_METAL_HOME` path.
|
| 117 |
+
|
| 118 |
+
Expected smoke line (host, megakernel default, same log stamp; `$PUB/logs/smoke_n15_20260918-043232.log`):
|
| 119 |
|
| 120 |
```
|
| 121 |
+
info: … stage=stage2-megakernel dit_backend=megakernel arena=bfp8_b cqs=2
|
| 122 |
+
PASS GR00T-N1.5-3B-p150 n15: PCC(actions)=0.999980 max|d|=0.0296 PCC(action_pred_valid)=0.999976 repeat_maxdiff=0.0e+00
|
| 123 |
+
seed_path_ok device_ms=33.03/32.64 total_ms=37.02/35.0 wall_ms=40
|
| 124 |
+
per_group[left_arm=0.99994/0.0229 right_arm=0.99997/0.0172 left_hand=0.92834/0.0296 right_hand=0.99999/0.0289]
|
| 125 |
```
|
| 126 |
|
| 127 |
+
The per-group PCC / max|d| equal the port's own device test on the frozen snapshot
|
| 128 |
+
(`code/models/experimental/gr00t/tests/tt/results/test_e2e_predict_actions_vs_golden_n15_20260918-044904.json`, `commit
|
| 129 |
+
009de892…`) to the printed precision; the traced policy is deterministic, so every run reproduces them bit-exactly. The
|
| 130 |
+
Stage-1 host smoke of the 2026-09-14 release (`smoke_n15_snapshot_20260914-235713.log`: PCC(actions) 0.999982, max|d|
|
| 131 |
+
0.0289, device 42.95 / 42.16 ms) is the reference the megakernel figures are compared with in `GPU_COMPARISON.md`.
|
|
|
|
| 132 |
|
| 133 |
Offline overrides: `GR00T_WEIGHTS_DIR=<dir with the snapshot files>` (shimmed into a private hub cache so the port's
|
| 134 |
`snapshot_dir()` resolves to it), `GR00T_TOKENIZER_DIR=<dir>`.
|
|
|
|
| 144 |
uv pip install --python $PUB/depcheck-n15/bin/python --dry-run torch==2.11.0 fastapi uvicorn "pydantic>=2" pillow "numpy>=1.24.4,<2" \
|
| 145 |
safetensors huggingface_hub "transformers==5.12.1" "opencv-python-headless==4.8.1.78" "torchvision==0.26.0" pytest \
|
| 146 |
--extra-index-url https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match
|
| 147 |
+
# -> torch 2.11.0+cpu, torchvision 0.26.0+cpu, numpy 1.26.4, transformers 5.12.1, opencv-python-headless 4.8.1.78 (cp312 wheel), pytest 9.1.1
|
| 148 |
```
|
| 149 |
|
| 150 |
## 3. Package, serve, push (Docker)
|
|
|
|
| 175 |
|
| 176 |
`serve` pre-downloads the pinned files (a cache hit on this host), then `docker run --user 0:0 --device
|
| 177 |
/dev/tenstorrent --ipc host` with `/dev/hugepages-1G`, `~/.cache/huggingface` at `/hf` (rw, `HF_HOME=/hf`) and
|
| 178 |
+
`~/.cache/tt-model/gr00t-n1.5-3b-p150/{cache,weights,tensors}` at `/cache` (`TT_METAL_CACHE`, JIT kernels incl. the
|
| 179 |
+
megakernel), `/weight-cache` (`TT_DIT_CACHE_DIR`; the port's weight tiers go to `/weight-cache/gr00t-tt`, ~2.5 GB under the megakernel, 6.4 GB on the Stage-1 path) and
|
| 180 |
`/tensor-cache`; it exports exactly `HF_MODEL=nvidia/GR00T-N1.5-3B`, `MESH_DEVICE=P150`, `TT_MESH_SHAPE=1x1` plus
|
| 181 |
`serve.env` from `tt-model.yaml`, publishes the first free port from 20000 and waits ≤ 1800 s for `Application startup
|
| 182 |
complete`. tt-cli users: `tt serve changh95/GR00T-N1.5-3B-p150` / `tt model stop changh95/GR00T-N1.5-3B-p150`
|
|
|
|
| 189 |
|
| 190 |
## 4. Container validation (the image built from this manifest)
|
| 191 |
|
| 192 |
+
Image `tt-model/gr00t-n1.5-3b-p150:dfb9c3889ab1` (`sha256:dfb9c3889ab17cc1d8187085b27e854d0311fb974d729efe88db9bfdce51869f`, 3.2 GB (3,218,078,767 B in 46 content-addressed blobs)), built 2026-09-18 05:23–05:28 KST (`built.created_at` 2026-09-17T20:23:50+00:00) from the manifest above
|
| 193 |
+
(`tt-model package`: tt-metal C++ build with a warm ccache, Python 3.12 venv, all 25 `verify:` lines + the launcher's own passed
|
| 194 |
+
inside the image; 4 min 22 s with the two sibling GR00T builds sharing the host in total; `publish/logs/pkg-mk-n15.log`). Validated on the p150a on 2026-09-18 05:32–05:35 KST: two `tt-model serve`
|
| 195 |
+
boots of this exact image inside **one** device-lock hold (`publish/scripts/validate_image_mk_n15.sh` via
|
| 196 |
+
`run_validation_mk_n15_when_free.sh`, which enters the lock only when no `tt-model-*` container and no other process holds the
|
| 197 |
+
card), each followed by `smoke_test.py`, a 50-request `bench_http.py` and `tt-model stop` by manifest path; per-boot files
|
| 198 |
+
`publish/logs/{serve,container,info,smoke,bench-http}-mk-n15-c{1,2}-20260918-053217.*`, driver log `validate-mk-n15-20260918-053217.log`.
|
| 199 |
+
Before boot 1 the package's `~/.cache/tt-model/gr00t-n1.5-3b-p150/{cache,weights}` of the Stage-1 release were moved aside, so
|
| 200 |
+
boot 1 is the true cold boot of a fresh install (checkpoint → weight tiers, JIT of every kernel incl. the megakernel).
|
| 201 |
|
| 202 |
| boot | caches (`~/.cache/tt-model/gr00t-n1.5-3b-p150/`) | host load | `tt-model serve` → READY | container boot (lifespan) | smoke | `bench_http.py` (10 warm-up + 50): device / server-side median (p90) | `tt-model stop` |
|
| 203 |
|---|---|---|---|---|---|---|---|
|
| 204 |
+
| 1 (**cold**) | `weights/` and `cache/` empty before; 343 MB `cache/` + 2.5 GB `weights/` after | load avg 7.8 at start, 31 at the end (two sibling image builds on the host) | **2 min 14 s (134.7 s wall: container start 31.6 s + lifespan)** | 97.7 s: `Model built in 29.9 s (weights 12.8 s, arena plan + pack 14.3 s; cache path cold)` → trace capture incl. the JIT of every kernel and the megakernel 55.7 s → 5 warm-ups 34.2 → 33.6 ms | **PASS** — PCC(actions) 0.999980, max\|d\| 0.0296; PCC(`action_pred_valid`) 0.999976; device 33.17 / 33.10 ms, total 36.90 / 35.56 ms | 32.59 (32.83) / 34.59 (35.05) ms; client wall 36.6 (37.2) | clean, 2.0 s |
|
| 205 |
+
| 2 (warm) | reused | load avg 31 (same builds) | **11.2 s** | 9.0 s: `Model built in 2.6 s (… cache path warm)`, capture 2.9 s, warm-ups 33.6 → 33.4 ms | **PASS** — same fidelity; device 33.51 / 33.14 ms, total 37.21 / 35.52 ms | **32.52 (32.82) / 34.62 (35.39) ms**, min 32.29 / 34.09; decode 1.02, encode 0.90, decode_actions 0.10; client wall 36.3 (37.3) | clean, 2.0 s |
|
| 206 |
+
|
| 207 |
+
Fidelity is identical in both boots and identical to the host run of §2 — the served `actions` JSON of the container is
|
| 208 |
+
byte-for-byte the host run's (`smoke-mk-n15-c2-20260918-053217.json` vs `smoke_n15_20260918-043232.json`) — and equal to the
|
| 209 |
+
port's device test on the frozen snapshot `tests/tt/results/test_e2e_predict_actions_vs_golden_n15_20260918-044904.json` to
|
| 210 |
+
the printed precision: `left_arm` 0.99994 / 0.0229, `right_arm` 0.99997 / 0.0172, `left_hand` 0.92834 / 0.0296
|
| 211 |
+
(abs-gated), `right_hand` 0.99999 / 0.0289. The boot-2 benchmark is the card's "served" row; its device median is
|
| 212 |
+
0.1 ms above the port's own `bench_e2e` device figure on the snapshot (32.41 ms: upload + traces + readback,
|
| 213 |
+
`bench_e2e_n15_20260918-050550.json`). The Stage-1 image of 2026-09-14 served 42.4 ms device / 45.4 ms server-side on
|
| 214 |
+
the same host (`build-n15.md` §5); the megakernel image serves 9.9 ms less device time per request.
|
| 215 |
+
|
| 216 |
+
`GET /info` inside the container reports `stage = {served: stage2-megakernel, dit_backend: megakernel, mk_arena_dtype:
|
| 217 |
+
bfp8_b, num_command_queues: 2, cq1_uploads: true, worker_l1_size: 1395712, adapter_mem: DRAM, weights: {499 tensors,
|
| 218 |
+
2652.2 MB, skipped_ttnn_dit: {279, 523.9 MB}}, head_timing_s}`, `weights.snapshot_dir = /hf/hub/models--nvidia--GR00T-N1.5-3B/snapshots/869830fc…`
|
| 219 |
+
(the mounted HF cache), `source.tt_metal = {commit 668c290…}`, `source.port_snapshot_commit`, `device_facts` grid 11×10 /
|
| 220 |
+
DRAM 8×1 (`info-mk-n15-c2-20260918-053217.json`).
|
| 221 |
+
|
| 222 |
+
**Device-sharing note for this host.** The card is shared with other sessions; every device entry goes through
|
| 223 |
+
`bin/with-device.sh` (flock + a ttnn probe) and, for containers, through `publish/scripts/run_validation_mk_n15_when_free.sh`,
|
| 224 |
+
which waits until `docker ps` shows no `tt-model-*` container, `fuser /dev/tenstorrent/0` is empty and the lock is free
|
| 225 |
+
(a container left running after its lock hold ends would otherwise be reset by the next caller's probe — `build-n15.md`
|
| 226 |
+
§5.0). Use the manifest path with `tt-model stop` / `logs` for an un-pushed package (`tt-model stop
|
| 227 |
changh95/GR00T-N1.5-3B-p150` only works after `pull`).
|
| 228 |
|
| 229 |
## 5. Request / response contract
|
|
|
|
| 231 |
| route | returns |
|
| 232 |
|---|---|
|
| 233 |
| `GET /health` | `{"status": "ok" \| "starting", "model": "GR00T-N1.5-3B-p150", "device": "blackhole:0"}` — 200 always; `ok` only after the warm-up |
|
| 234 |
+
| `GET /info` | model / version / task / hardware / `stage` (`served`: `stage2-megakernel` \| `stage1-ttnn-traces`, `dit_backend`, `mk_arena_dtype`, `num_command_queues`, `cq1_uploads`, `worker_l1_size`, `adapter_mem`, `weights` incl. the skipped ttnn DiT set, `head_timing_s`, a note) / `policy` (the resolved `TTPolicy`) / `weights` (repo, revision, snapshot dir actually loaded) / `source` (port snapshot commit, server version, tt-metal commit, ttnn dist version) / `inputs` (the full observation contract) / `outputs` / `noise.default_seed` / `limits` / `device_facts` (11×10 grid, 8 DRAM banks) / `warmup_latency_ms` / `warmup_fidelity_vs_golden` / `license` |
|
| 235 |
| `GET /v1/models` | OpenAI-shaped stub `{"object": "list", "data": [{"id": "nvidia/GR00T-N1.5-3B", "object": "model", "owned_by": "changh95"}]}` so the tt-model ready card / `tt-model curl` do not 404; this is not a chat API |
|
| 236 |
| `GET /demo` | `{"request": <ready-to-post demo body>, "expected": <fp32 reference actions + gates>}` |
|
| 237 |
| `POST /predict` | one observation → one 16-step action chunk (below) |
|
|
|
|
| 257 |
"normalized": false, "embodiment": "gr1", "embodiment_id": 24, "version": "n15", "model": "GR00T-N1.5-3B-p150", "layout": "gr1",
|
| 258 |
"images": {"ego_view": {"frames": 1, "received_hw": [256, 256]}}, "seq_len": 296, "prompt_tokens": 14, "state_dtype": "float64",
|
| 259 |
"noise_source": "seed" | "client", "seed": 0 | null,
|
| 260 |
+
"timing_ms": {"decode": 1.47, "encode": 2.02, "device": 33.51, "decode_actions": 0.15, "total": 37.21}}
|
| 261 |
```
|
| 262 |
|
| 263 |
`actions` are physical joint targets (radians) — `(y + 1) / 2 · (max − min) + min` with the checkpoint's
|
| 264 |
`experiment_cfg/metadata.json` GR1 statistics — exactly what `Gr00tPolicy.get_action` returns. `timing_ms`: `decode` =
|
| 265 |
base64 + image decode + validation; `encode` = the port's host preprocessing; `device` = `predict_normalized` (static-shape
|
| 266 |
+
asserts, input writes on CQ 1, 4 × `execute_trace` — the fourth is the megakernel —, one blocking readback);
|
| 267 |
+
`decode_actions` = un-normalisation; `total` = the whole handler. Errors: **400** structural problems (`{"detail":
|
| 268 |
+
{"errors": [...], "inputs": {...}}}`), undecodable image, bad state / instruction / noise, other embodiment; **503** while
|
| 269 |
+
starting; **500** `Type: text` on a device failure. Handlers are synchronous and serialised on one lock; batch 1.
|
| 270 |
|
| 271 |
## 6. Environment (read in the lifespan, never at import)
|
| 272 |
|
| 273 |
| variable | container value (`serve.env`) | meaning |
|
| 274 |
|---|---|---|
|
| 275 |
| `HF_MODEL` / `TT_WEIGHTS_REVISION` | `nvidia/GR00T-N1.5-3B` (launcher) / `869830fc…` | must be the version's pinned repo and sha (both are asserted against the port's own `configs.HF_REPOS` / `HF_SNAPSHOT_SHAS`) |
|
| 276 |
+
| `GR00T_VERSION` | `n15` | the server code is shared by the three GR00T packages and never guesses |
|
| 277 |
| `GR00T_EMBODIMENT` / `GR00T_LAYOUT` | `gr1` / `gr1` | the only device-validated layout of this release |
|
| 278 |
| `GR00T_POLICY` / `GR00T_TRACE_LAYOUT` | `mixed_dit` / `per_stage` | `TTPolicy` knobs; `bf16` / `mixed` and `two` exist in the port but are not the benchmarked defaults |
|
| 279 |
+
| `GR00T_DIT_BACKEND` | `megakernel` (= the port's default, pinned) | `TTPolicy.dit_backend`: `megakernel` = the denoise trace is one persistent `generic_op` (Stage 2); `ttnn` = the Stage-1 sequence of TTNN ops (for A/B; the device is then opened with the firmware `worker_l1_size` and the adapter's intermediates in L1). Reported in `/info -> stage` |
|
| 280 |
+
| `GR00T_MK_ARENA_DTYPE` | `bfp8_b` (= what `auto` resolves to for n15) | dtype of the megakernel's DRAM weight arena: `auto` / `bf16` / `bfp8_b`; the bf16 arena is 2.66 ms per step instead of 2.22 (`tests/tt/results/mk_k4b_summary.md` §0) |
|
| 281 |
+
| `GR00T_NUM_CQS` | `2` | command queues of `open_model_device`: `2` = per-call input writes on CQ 1 overlap the trace replays (the benchmarked path; bit-equal to `1` per `tests/tt/results/test_mk_2cq_bit_equality_n15_20260918-041502.json`) |
|
| 282 |
+
| `GR00T_TRACE_REGION_SIZE` / `GR00T_L1_SMALL_SIZE` | default 67108864 / 32768 | the port's validated `open_model_device` values |
|
| 283 |
+
| `GR00T_MK_L1_CUT_KIB` (port) | unset (= 64) | the worker-L1 cut the megakernel's kernel-config ring needs; read by the port's `open_model_device`, not by the server. Changing it invalidates the validated configuration |
|
| 284 |
| `GR00T_TOKENIZER_DIR_N15` | `/opt/tt-metal/gr00t_p150/assets/tokenizer/n15` | vendored Qwen2 tokenizer (not in the weights repo); `GR00T_TOKENIZER_DIR` overrides |
|
| 285 |
| `GR00T_TT_CACHE` | `/weight-cache/gr00t-tt` | host `.pt` plan tier + `.tensorbin` device tier (persisted under `~/.cache/tt-model/gr00t-n1.5-3b-p150/weights`) |
|
| 286 |
| `GR00T_PROJECT_ROOT` / `GR00T_GOLDEN_ROOT` | `/nonexistent` | the port's dev-box defaults; nothing on the serve path needs them |
|
| 287 |
+
| `GR00T_WARMUP_RUNS` / `GR00T_WARMUP_PCC_MIN` | `5` / `0.999` | traced predicts before READY; boot fails below the PCC floor vs the shipped golden |
|
| 288 |
| `GR00T_DEFAULT_SEED`, `GR00T_TORCH_THREADS`, `GR00T_WEIGHTS_DIR` | unset | request default seed (0), torch intra-op threads, offline snapshot dir |
|
| 289 |
| `TT_MESH_SHAPE`, `TT_DEVICE_ID`, `TT_METAL_VISIBLE_DEVICES` | `1x1` (launcher), `0`, `0` | one chip; any other mesh shape is refused |
|
| 290 |
|
| 291 |
## 7. Caveats
|
| 292 |
|
| 293 |
+
- **Stage-2 path.** The denoise is one persistent megakernel `generic_op` (K4b-final kernel: bfp8 weights streamed from a
|
| 294 |
+
DRAM arena once per step, 110 Tensix cores, dual-NoC hub multicast, distributed LayerNorm, per-head flash SDPA split over
|
| 295 |
+
three cores for the long-key cross blocks; `tests/tt/results/mk_k4b_summary.md`, `mk_k5_summary.md`, `mk_default_summary.md`).
|
| 296 |
+
Consequences: (1) the device is opened with a 64 KiB smaller worker L1 (`worker_l1_size` 1,395,712 B) so the kernel-config
|
| 297 |
+
ring holds the ≈ 131.7 KB compute-core program (4.5 KB of headroom under the 136,192 B ring: any kernel growth must be
|
| 298 |
+
re-checked), and the VL adapter's intermediates are placed in DRAM (+0.38 ms on the `adapter` trace: 4.86 vs 4.48 ms);
|
| 299 |
+
(2) the DiT residual-stream intermediates (`dit_block{i}_out`, `dit_out[k]`) are not observable from the served model —
|
| 300 |
+
the untraced tap set is the backbone / adapter taps + `action_decoder_out[k]`, `pred_velocity[k]` and the action rows
|
| 301 |
+
(30 rows instead of the Stage-1 path's 58); (3) the e2e gain over the Stage-1 denoise on the same tree is −16.8 %
|
| 302 |
+
(40.38 → 33.59 ms; denoise trace 16.02 → 8.93 ms, −44 %), not the plan's −30 %.
|
| 303 |
+
- **Fidelity vs the Stage-1 path.** Gate outcomes are equal on 8 of the 9 N1.5 observations (the demo and 7 of the 8 multi-sample
|
| 304 |
+
goldens); the megakernel's only extra miss is `unnormalized_actions.right_arm` on `t3_s300_seed2` at PCC 0.999869 vs
|
| 305 |
+
the 0.9999 gate (max|d| 0.029 rad within its 0.038 tolerance; recorded as a known marginal row with floor 0.9998 in
|
| 306 |
+
`tests/tt/test_mk_e2e.py`; `tests/tt/results/test_mk_e2e_backends_agree_t3_s300_seed2_n15_20260918-042413.json`). On the
|
| 307 |
+
demo observation megakernel vs TTNN `action_pred_normalized` PCC is 0.999997 (`…_canonical_n15_20260918-042408.json`).
|
| 308 |
- **Shape-locked traces.** One embodiment (`gr1`), one camera, one frame, ≤ 102 text tokens (LLM S padded to 384), batch 1.
|
| 309 |
Other embodiments / cameras / longer instructions → 400 before anything reaches the device. Concurrent requests queue on one lock.
|
| 310 |
- **Deterministic noise.** Actions are a deterministic function of (observation, seed | noise); the reference noise of the
|
| 311 |
+
demo is not seed-reproducible, so the smoke test posts it as `noise`. Same observation + same noise → bit-identical actions;
|
| 312 |
+
traced == untraced and 2 CQs == 1 CQ bit for bit.
|
| 313 |
- **Base checkpoint.** Outputs are the GR1 sim data-config actions of the released N1.5 checkpoint; a real robot needs NVIDIA's post-training.
|
| 314 |
- **First container boot is cold**: the port converts the 5.4 GB checkpoint into its `.pt` plan tier and the `.tensorbin`
|
| 315 |
+
device tier under `/weight-cache/gr00t-tt` and JITs every kernel of the four traces — incl. the megakernel — into `/cache`;
|
| 316 |
+
both persist under `~/.cache/tt-model/gr00t-n1.5-3b-p150/`, so later boots are seconds (measured values in §4). A cache
|
| 317 |
+
directory left by the Stage-1 image is reused (its kernels stay, the megakernel is added).
|
| 318 |
+
- **One megakernel model per process.** Building a second megakernel `Gr00tTT` after releasing the first on the same open
|
| 319 |
+
device hung the card on N1.6 during development (`mk_default_summary.md` §9); the server builds exactly one model per
|
| 320 |
+
process and never rebuilds it.
|
| 321 |
- **Python 3.12 image vs 3.10 tree venv.** Host runs are an approximation; the image is authoritative (its `verify.sh`
|
| 322 |
imports the app and the port with no device; the served smoke in §4 is the proof).
|
| 323 |
- **Weights licence.** `nvidia/GR00T-N1.5-3B` is under the NVIDIA License (non-commercial: research or evaluation use);
|
|
Git LFS Details
|
|
Git LFS Details
|
|
@@ -1,5 +1,8 @@
|
|
| 1 |
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
-
# tt-model-manager container manifest (schema 5.1) for NVIDIA Isaac GR00T N1.5-3B
|
|
|
|
|
|
|
|
|
|
| 3 |
#
|
| 4 |
# Run EVERY tt-model command from this directory: `source.tt_metal` and `extra_code[].root: code`
|
| 5 |
# resolve against the process CWD, not against this file. Rootless Docker on this box needs
|
|
@@ -38,7 +41,7 @@ source:
|
|
| 38 |
# three submodules populated and `git status --porcelain` empty, so the image is built from exactly the commit the
|
| 39 |
# card links to (the working clone has untracked dirs and would be recorded as a dirty tree).
|
| 40 |
tt_metal: /home/deepgadget/experiments/gr00t/publish/tt-metal-668c2907575
|
| 41 |
-
# tt-metal-relative files the port
|
| 42 |
# (the image's tt-metal copy excludes models/, so everything under models/ must be listed here or in extra_code).
|
| 43 |
code:
|
| 44 |
- models/common/utility_functions.py # models.tt_dit.utils.matmul imports is_blackhole (needs pytest!)
|
|
@@ -56,13 +59,22 @@ source:
|
|
| 56 |
- models/tt_dit/parallel/config.py
|
| 57 |
- models/tt_dit/parallel/manager.py
|
| 58 |
- models/tt_dit/encoders/qwen3vl/vision_qwen3vl.py # tt/qwen3vl_vision.py (lazy; not on the N1.5 serve path)
|
| 59 |
-
- models/demos/deepseek_v3_b1/unified_kernel_descriptor.py # tt/megakernel/descriptors.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
# This repo's own code (lands at /opt/tt-metal/<path>; PYTHONPATH=/opt/tt-metal).
|
| 61 |
extra_code:
|
| 62 |
- root: code
|
| 63 |
paths:
|
| 64 |
-
- models # code/models/experimental/gr00t: the frozen snapshot of the port (tt-metal ref gr00t-p150-snapshot,
|
| 65 |
-
- gr00t_p150 # the policy server, its smoke test, the demo observation and the vendored Qwen2 tokenizer files
|
| 66 |
- scripts # download_weights.sh, bench_http.py
|
| 67 |
ubuntu: "22.04"
|
| 68 |
python: "3.12"
|
|
@@ -95,35 +107,47 @@ serve:
|
|
| 95 |
GR00T_LAYOUT: "gr1" # static layout: 1 image x 256 tokens, L_max 102 text tokens, LLM sequence padded to 384
|
| 96 |
GR00T_POLICY: "mixed_dit" # TTPolicy.dtype_policy: DiT block matmul weights bfp8_b, everything else bf16 (validated default)
|
| 97 |
GR00T_TRACE_LAYOUT: "per_stage" # four Metal traces vision / llm / adapter / denoise (the benchmarked layout)
|
|
|
|
|
|
|
|
|
|
| 98 |
GR00T_TOKENIZER_DIR_N15: "/opt/tt-metal/gr00t_p150/assets/tokenizer/n15"
|
| 99 |
GR00T_PROJECT_ROOT: "/nonexistent" # the port's dev-box default for project-relative paths; nothing on the serve path needs it
|
| 100 |
GR00T_GOLDEN_ROOT: "/nonexistent" # tests only (defensive)
|
| 101 |
GR00T_TT_CACHE: "/weight-cache/gr00t-tt" # host plan (.pt) + device-layout (.tensorbin) weight tiers -> ~/.cache/tt-model/gr00t-n1.5-3b-p150/weights
|
| 102 |
-
GR00T_WARMUP_RUNS: "
|
|
|
|
| 103 |
|
| 104 |
# Build-time assertions, run INSIDE the finished image as uid 1000, no device, no weights, no HF_MODEL.
|
| 105 |
verify:
|
| 106 |
- "import gr00t_p150.server.app as a; assert a.app; import sys; assert 'ttnn' not in sys.modules, 'server import must be side-effect free'"
|
| 107 |
- "import models.experimental.gr00t.tt.model as m; assert m.Gr00tTT"
|
| 108 |
- "from models.experimental.gr00t.common import preprocessing, normalization, prompts, checkpoint, weights, configs; assert configs.HF_SNAPSHOT_SHAS['n15'] == '869830fc749c35f34771aa5209f923ac57e4564e'"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
- "import pytest, models.common.utility_functions; import models.tt_dit.utils.matmul as mm; assert mm.get_matmul_config"
|
| 110 |
- "import models.demos.deepseek_v3_b1.unified_kernel_descriptor as u; assert u.UnifiedKernelDescriptor"
|
| 111 |
- "import models.tt_dit.encoders.qwen3vl.vision_qwen3vl"
|
| 112 |
-
- "import models.experimental.gr00t.tt.layers, models.experimental.gr00t.tt.qwen3vl_vision, models.experimental.gr00t.tt.
|
|
|
|
| 113 |
- "import safetensors, huggingface_hub, cv2, numpy, PIL; assert int(numpy.__version__.split('.')[0]) < 2, numpy.__version__"
|
| 114 |
- "import transformers; assert transformers.__version__ == '5.12.1', transformers.__version__"
|
| 115 |
- "import torchvision; assert torchvision.__version__.startswith('0.26.0'), torchvision.__version__"
|
| 116 |
- "from pathlib import Path; d = Path('/opt/tt-metal/gr00t_p150/assets/tokenizer/n15'); assert all((d / f).is_file() for f in ('vocab.json', 'merges.txt', 'tokenizer_config.json', 'special_tokens_map.json', 'added_tokens.json')), sorted(p.name for p in d.iterdir())"
|
| 117 |
- "import os; os.environ['GR00T_TOKENIZER_DIR_N15'] = '/opt/tt-metal/gr00t_p150/assets/tokenizer/n15'; from models.experimental.gr00t.common.prompts import get_tokenizer; t = get_tokenizer('n15'); assert t.convert_tokens_to_ids('<IMG_CONTEXT>') == 151669"
|
| 118 |
-
- "from pathlib import Path; assert Path('/opt/tt-metal/models/experimental/gr00t/common/golden_tap_map.json').is_file()"
|
| 119 |
-
- "from pathlib import Path; assert Path('/opt/tt-metal/gr00t_p150/server/smoke_test.py').is_file() and Path('/opt/tt-metal/scripts/bench_http.py').is_file()"
|
| 120 |
- "from pathlib import Path; d = Path('/opt/tt-metal/gr00t_p150/demo/n15'); assert all((d / f).is_file() for f in ('observation.json', 'noise.json', 'expected.json', 'frames/ego_view_t0.png')), sorted(str(p) for p in d.rglob('*'))"
|
| 121 |
-
- "import ttnn; assert all(hasattr(ttnn, a) for a in ('begin_trace_capture', 'end_trace_capture', 'execute_trace', 'release_trace', 'copy_host_to_device_tensor', 'MinimalMatmulConfig'))"
|
| 122 |
- "from gr00t_p150.server.app import parse_mesh_shape as p; assert p('1x1') == p('(1, 1)') == p('1,1') == (1, 1)"
|
| 123 |
|
| 124 |
card:
|
| 125 |
description: |
|
| 126 |
-
NVIDIA Isaac GR00T N1.5 (3B vision-language-action policy) running on one Tenstorrent Blackhole p150a via tt-nn: one camera frame, proprioceptive state and a task instruction in, a 16-step chunk of GR1 arm and hand joint targets out. Stage-
|
| 127 |
Weights: [nvidia/GR00T-N1.5-3B](https://huggingface.co/nvidia/GR00T-N1.5-3B) · Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) · Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (`n1.5-release`) · Port: `code/models/experimental/gr00t` and the server `code/gr00t_p150` in this repo
|
| 128 |
quickstart: |
|
| 129 |
### Run with tt-cli
|
|
@@ -147,14 +171,15 @@ card:
|
|
| 147 |
### Response
|
| 148 |
|
| 149 |
```json
|
| 150 |
-
{"actions": {"left_arm": [[0.
|
| 151 |
-
"right_arm": [[-0.
|
| 152 |
-
"left_hand": [[0.0081, 0.
|
| 153 |
-
"right_hand": [[-1.
|
| 154 |
-
"action_horizon": 16, "embodiment": "gr1", "seq_len": 296,
|
| 155 |
-
"timing_ms": {"decode": 1.
|
| 156 |
```
|
| 157 |
|
|
|
|
| 158 |
- `actions` are physical, un-normalised joint targets (radians), 16 steps × [7, 7, 6, 6] dims, as `Gr00tPolicy.get_action` returns them; `timing_ms.device` covers upload, the trace replays and the readback.
|
| 159 |
|
| 160 |
### Demo
|
|
@@ -167,20 +192,23 @@ card:
|
|
| 167 |
|
| 168 |
| Metric | Value |
|
| 169 |
|---|---:|
|
| 170 |
-
| Action chunk vs the fp32 reference (same observation, same initial noise) | PCC `left_arm` 0.
|
| 171 |
-
| Intermediate taps vs the fp32 reference (
|
| 172 |
-
|
|
| 173 |
-
|
|
| 174 |
-
|
|
| 175 |
-
| Same
|
|
|
|
|
|
|
| 176 |
|
| 177 |
### Caveats
|
| 178 |
|
| 179 |
-
- Stage-
|
|
|
|
| 180 |
- One embodiment and layout: GR1 arms and hands, one `ego_view` camera, instruction ≤ 102 tokens; anything else is refused with 400. Batch 1.
|
| 181 |
-
- Noise is seeded by default (seed 0) so the output is reproducible; NVIDIA's `Gr00tPolicy` draws fresh noise per call. Outputs are the base checkpoint's actions for the GR1 sim data config.
|
| 182 |
- Not an OpenAI-compatible API; `GET /v1/models` is a stub.
|
| 183 |
-
- First boot is cold (weight conversion + kernel JIT, READY after
|
| 184 |
|
| 185 |
### Licensing
|
| 186 |
|
|
|
|
| 1 |
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# tt-model-manager container manifest (schema 5.1) for NVIDIA Isaac GR00T N1.5-3B on Blackhole -- Stage-2 release:
|
| 3 |
+
# the DiT denoise runs as ONE persistent megakernel generic_op (TTPolicy.dit_backend="megakernel", bfp8_b weight
|
| 4 |
+
# arena, the port's default since 2026-09-18); vision / llm / adapter stay TTNN ops in Metal traces. The Stage-1
|
| 5 |
+
# TTNN denoise is selectable with GR00T_DIT_BACKEND=ttnn.
|
| 6 |
#
|
| 7 |
# Run EVERY tt-model command from this directory: `source.tt_metal` and `extra_code[].root: code`
|
| 8 |
# resolve against the process CWD, not against this file. Rootless Docker on this box needs
|
|
|
|
| 41 |
# three submodules populated and `git status --porcelain` empty, so the image is built from exactly the commit the
|
| 42 |
# card links to (the working clone has untracked dirs and would be recorded as a dirty tree).
|
| 43 |
tt_metal: /home/deepgadget/experiments/gr00t/publish/tt-metal-668c2907575
|
| 44 |
+
# tt-metal-relative files the port needs from the tree OTHER than itself, with their import closure
|
| 45 |
# (the image's tt-metal copy excludes models/, so everything under models/ must be listed here or in extra_code).
|
| 46 |
code:
|
| 47 |
- models/common/utility_functions.py # models.tt_dit.utils.matmul imports is_blackhole (needs pytest!)
|
|
|
|
| 59 |
- models/tt_dit/parallel/config.py
|
| 60 |
- models/tt_dit/parallel/manager.py
|
| 61 |
- models/tt_dit/encoders/qwen3vl/vision_qwen3vl.py # tt/qwen3vl_vision.py (lazy; not on the N1.5 serve path)
|
| 62 |
+
- models/demos/deepseek_v3_b1/unified_kernel_descriptor.py # tt/megakernel/descriptors.py: the ProgramDescriptor builder of the megakernel (served)
|
| 63 |
+
# Device-side headers the megakernel's kernel source includes (JIT-compiled at boot from
|
| 64 |
+
# /opt/tt-metal/models/experimental/gr00t/tt/megakernel/kernels/dit_kernel.cpp -> ../../../../../demos/deepseek_v3_b1/unified_kernels/*.hpp
|
| 65 |
+
# and, from ops/allgather.hpp, "models/demos/deepseek_v3_b1/unified_kernels/mcast.hpp" through -I$TT_METAL_HOME).
|
| 66 |
+
# Their own includes (tt_metal/tools/profiler/kernel_profiler.hpp, api/compute/blank.h, api/dataflow/dataflow_api.h,
|
| 67 |
+
# api/compute/experimental/deepseek_compute_kernel_hw_startup.h) live under tt_metal/, which the image ships.
|
| 68 |
+
- models/demos/deepseek_v3_b1/unified_kernels/kernel_op_api.hpp
|
| 69 |
+
- models/demos/deepseek_v3_b1/unified_kernels/kernel_utils.hpp
|
| 70 |
+
- models/demos/deepseek_v3_b1/unified_kernels/dataflow_utils.hpp
|
| 71 |
+
- models/demos/deepseek_v3_b1/unified_kernels/mcast.hpp
|
| 72 |
# This repo's own code (lands at /opt/tt-metal/<path>; PYTHONPATH=/opt/tt-metal).
|
| 73 |
extra_code:
|
| 74 |
- root: code
|
| 75 |
paths:
|
| 76 |
+
- models # code/models/experimental/gr00t: the frozen megakernel-release snapshot of the port (tt-metal ref gr00t-p150-snapshot-mk, 009de892c895; K4b-final kernel tree 7df61c35e9dc7857)
|
| 77 |
+
- gr00t_p150 # the policy server (megakernel default, open_model_device), its smoke test, the demo observation and the vendored Qwen2 tokenizer files
|
| 78 |
- scripts # download_weights.sh, bench_http.py
|
| 79 |
ubuntu: "22.04"
|
| 80 |
python: "3.12"
|
|
|
|
| 107 |
GR00T_LAYOUT: "gr1" # static layout: 1 image x 256 tokens, L_max 102 text tokens, LLM sequence padded to 384
|
| 108 |
GR00T_POLICY: "mixed_dit" # TTPolicy.dtype_policy: DiT block matmul weights bfp8_b, everything else bf16 (validated default)
|
| 109 |
GR00T_TRACE_LAYOUT: "per_stage" # four Metal traces vision / llm / adapter / denoise (the benchmarked layout)
|
| 110 |
+
GR00T_DIT_BACKEND: "megakernel" # TTPolicy.dit_backend: the denoise trace = one persistent megakernel generic_op (the port's default; pinned). "ttnn" = the Stage-1 TTNN op sequence
|
| 111 |
+
GR00T_MK_ARENA_DTYPE: "bfp8_b" # the megakernel's DRAM weight arena (what "auto" resolves to for n15: MK_ARENA_DTYPE_DEFAULT); "bf16" selectable
|
| 112 |
+
GR00T_NUM_CQS: "2" # command queues of tt.model.open_model_device: per-call input writes on CQ 1 overlap the traces (bit-equal to 1 CQ, tests/tt/test_mk_2cq.py)
|
| 113 |
GR00T_TOKENIZER_DIR_N15: "/opt/tt-metal/gr00t_p150/assets/tokenizer/n15"
|
| 114 |
GR00T_PROJECT_ROOT: "/nonexistent" # the port's dev-box default for project-relative paths; nothing on the serve path needs it
|
| 115 |
GR00T_GOLDEN_ROOT: "/nonexistent" # tests only (defensive)
|
| 116 |
GR00T_TT_CACHE: "/weight-cache/gr00t-tt" # host plan (.pt) + device-layout (.tensorbin) weight tiers -> ~/.cache/tt-model/gr00t-n1.5-3b-p150/weights
|
| 117 |
+
GR00T_WARMUP_RUNS: "5" # traced predict() calls after trace capture, before READY (the megakernel-default smoke configuration)
|
| 118 |
+
GR00T_WARMUP_PCC_MIN: "0.999" # boot FAILS when the warm-up actions' PCC vs the shipped fp32 golden is below this
|
| 119 |
|
| 120 |
# Build-time assertions, run INSIDE the finished image as uid 1000, no device, no weights, no HF_MODEL.
|
| 121 |
verify:
|
| 122 |
- "import gr00t_p150.server.app as a; assert a.app; import sys; assert 'ttnn' not in sys.modules, 'server import must be side-effect free'"
|
| 123 |
- "import models.experimental.gr00t.tt.model as m; assert m.Gr00tTT"
|
| 124 |
- "from models.experimental.gr00t.common import preprocessing, normalization, prompts, checkpoint, weights, configs; assert configs.HF_SNAPSHOT_SHAS['n15'] == '869830fc749c35f34771aa5209f923ac57e4564e'"
|
| 125 |
+
- "from models.experimental.gr00t.tt.policy import TTPolicy, DEFAULT_DIT_BACKEND, MK_ARENA_DTYPE_DEFAULT; p = TTPolicy(); assert p.dit_backend == 'megakernel' == DEFAULT_DIT_BACKEND and p.mk_arena_dtype == 'auto' and MK_ARENA_DTYPE_DEFAULT['n15'] == 'bfp8_b', (p.dit_backend, p.mk_arena_dtype)"
|
| 126 |
+
- "import os; os.environ['GR00T_DIT_BACKEND'] = 'ttnn'; from models.experimental.gr00t.tt.policy import TTPolicy; assert TTPolicy().with_env_overrides().dit_backend == 'ttnn' and TTPolicy().dit_backend == 'megakernel'"
|
| 127 |
+
- "from models.experimental.gr00t.tt.model import open_model_device, device_worker_l1_size, MK_L1_CUT_BYTES_DEFAULT, ADAPTER_MEM_BY_BACKEND; assert MK_L1_CUT_BYTES_DEFAULT == 65536 and ADAPTER_MEM_BY_BACKEND['megakernel'] == 'DRAM'"
|
| 128 |
+
- "import models.experimental.gr00t.tt.megakernel.dit_program, models.experimental.gr00t.tt.megakernel.arena, models.experimental.gr00t.tt.megakernel.core_map; from models.experimental.gr00t.tt.megakernel.descriptors import KERNEL_SOURCE, L1_USABLE_BYTES; from pathlib import Path; assert Path('/opt/tt-metal', KERNEL_SOURCE).is_file(), KERNEL_SOURCE"
|
| 129 |
+
- "from pathlib import Path; k = Path('/opt/tt-metal/models/experimental/gr00t/tt/megakernel/kernels'); assert all((k / 'ops' / f).is_file() for f in ('allgather.hpp', 'block_matmul.hpp', 'dist_layernorm.hpp', 'euler_head.hpp', 'head_sdpa.hpp', 'row_layernorm.hpp', 'weight_stream.hpp')), sorted(p.name for p in (k / 'ops').iterdir())"
|
| 130 |
+
- "from pathlib import Path; d = Path('/opt/tt-metal/models/demos/deepseek_v3_b1/unified_kernels'); assert all((d / f).is_file() for f in ('kernel_op_api.hpp', 'kernel_utils.hpp', 'dataflow_utils.hpp', 'mcast.hpp')), sorted(p.name for p in d.iterdir()) if d.is_dir() else 'missing dir'"
|
| 131 |
+
- "from pathlib import Path; assert Path('/opt/tt-metal/tt_metal/tools/profiler/kernel_profiler.hpp').is_file() and Path('/opt/tt-metal/tt_metal/hw/inc/api/compute/experimental/deepseek_compute_kernel_hw_startup.h').is_file() and Path('/opt/tt-metal/tt_metal/hw/inc/api/compute/blank.h').is_file()"
|
| 132 |
- "import pytest, models.common.utility_functions; import models.tt_dit.utils.matmul as mm; assert mm.get_matmul_config"
|
| 133 |
- "import models.demos.deepseek_v3_b1.unified_kernel_descriptor as u; assert u.UnifiedKernelDescriptor"
|
| 134 |
- "import models.tt_dit.encoders.qwen3vl.vision_qwen3vl"
|
| 135 |
+
- "import models.experimental.gr00t.tt.layers, models.experimental.gr00t.tt.qwen3vl_vision, models.experimental.gr00t.tt.action_head"
|
| 136 |
+
- "from gr00t_p150.server.app import STAGE_SERVED, DEFAULT_NUM_CQS, DIT_BACKENDS; assert STAGE_SERVED['megakernel'] == 'stage2-megakernel' and STAGE_SERVED['ttnn'] == 'stage1-ttnn-traces' and DEFAULT_NUM_CQS == 2 and set(DIT_BACKENDS) == {'ttnn', 'megakernel'}"
|
| 137 |
- "import safetensors, huggingface_hub, cv2, numpy, PIL; assert int(numpy.__version__.split('.')[0]) < 2, numpy.__version__"
|
| 138 |
- "import transformers; assert transformers.__version__ == '5.12.1', transformers.__version__"
|
| 139 |
- "import torchvision; assert torchvision.__version__.startswith('0.26.0'), torchvision.__version__"
|
| 140 |
- "from pathlib import Path; d = Path('/opt/tt-metal/gr00t_p150/assets/tokenizer/n15'); assert all((d / f).is_file() for f in ('vocab.json', 'merges.txt', 'tokenizer_config.json', 'special_tokens_map.json', 'added_tokens.json')), sorted(p.name for p in d.iterdir())"
|
| 141 |
- "import os; os.environ['GR00T_TOKENIZER_DIR_N15'] = '/opt/tt-metal/gr00t_p150/assets/tokenizer/n15'; from models.experimental.gr00t.common.prompts import get_tokenizer; t = get_tokenizer('n15'); assert t.convert_tokens_to_ids('<IMG_CONTEXT>') == 151669"
|
| 142 |
+
- "from pathlib import Path; assert Path('/opt/tt-metal/models/experimental/gr00t/common/golden_tap_map.json').is_file() and Path('/opt/tt-metal/models/experimental/gr00t/tests/tt/gates/gates_multi.json').is_file()"
|
| 143 |
+
- "from pathlib import Path; assert Path('/opt/tt-metal/gr00t_p150/server/smoke_test.py').is_file() and Path('/opt/tt-metal/scripts/bench_http.py').is_file() and Path('/opt/tt-metal/scripts/download_weights.sh').is_file()"
|
| 144 |
- "from pathlib import Path; d = Path('/opt/tt-metal/gr00t_p150/demo/n15'); assert all((d / f).is_file() for f in ('observation.json', 'noise.json', 'expected.json', 'frames/ego_view_t0.png')), sorted(str(p) for p in d.rglob('*'))"
|
| 145 |
+
- "import ttnn; assert all(hasattr(ttnn, a) for a in ('begin_trace_capture', 'end_trace_capture', 'execute_trace', 'release_trace', 'copy_host_to_device_tensor', 'MinimalMatmulConfig', 'generic_op', 'ProgramDescriptor', 'ComputeConfigDescriptor', 'CBDescriptor', 'SemaphoreDescriptor', 'UnpackToDestMode'))"
|
| 146 |
- "from gr00t_p150.server.app import parse_mesh_shape as p; assert p('1x1') == p('(1, 1)') == p('1,1') == (1, 1)"
|
| 147 |
|
| 148 |
card:
|
| 149 |
description: |
|
| 150 |
+
NVIDIA Isaac GR00T N1.5 (3B vision-language-action policy) running on one Tenstorrent Blackhole p150a via tt-nn: one camera frame, proprioceptive state and a task instruction in, a 16-step chunk of GR1 arm and hand joint targets out. Stage-2 release: vision tower, LLM and VL adapter as TTNN ops replayed from Metal traces (bf16); the whole DiT denoise (4 steps × 16 blocks) as **one persistent megakernel** on 110 Tensix cores, streaming its bfp8 weights from a DRAM arena once per step. `GR00T_DIT_BACKEND=ttnn` selects the Stage-1 TTNN denoise.
|
| 151 |
Weights: [nvidia/GR00T-N1.5-3B](https://huggingface.co/nvidia/GR00T-N1.5-3B) · Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) · Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (`n1.5-release`) · Port: `code/models/experimental/gr00t` and the server `code/gr00t_p150` in this repo
|
| 152 |
quickstart: |
|
| 153 |
### Run with tt-cli
|
|
|
|
| 171 |
### Response
|
| 172 |
|
| 173 |
```json
|
| 174 |
+
{"actions": {"left_arm": [[0.0032, 0.2093, 0.0567, -1.8143, -0.1239, -0.0563, 0.0177], "... 16 steps"],
|
| 175 |
+
"right_arm": [[-0.0157, -0.1731, 0.3713, -1.7457, 0.4968, -0.0574, 0.9908], "..."],
|
| 176 |
+
"left_hand": [[0.0081, 0.0121, 0.0042, 0.0064, 0.0169, 0.0380], "..."],
|
| 177 |
+
"right_hand": [[-1.4850, -1.4915, -1.4734, -1.4743, -2.9550, 2.9947], "..."]},
|
| 178 |
+
"action_horizon": 16, "embodiment": "gr1", "seq_len": 296, "noise_source": "client", "seed": null,
|
| 179 |
+
"timing_ms": {"decode": 1.47, "encode": 2.02, "device": 33.51, "decode_actions": 0.15, "total": 37.21}}
|
| 180 |
```
|
| 181 |
|
| 182 |
+
- The example is the demo request with the reference noise, as `GET /demo` returns it (`noise_source: "client"`, `seed: null`): the smoke response of this image's second (warm) boot, `timing_ms` included.
|
| 183 |
- `actions` are physical, un-normalised joint targets (radians), 16 steps × [7, 7, 6, 6] dims, as `Gr00tPolicy.get_action` returns them; `timing_ms.device` covers upload, the trace replays and the readback.
|
| 184 |
|
| 185 |
### Demo
|
|
|
|
| 192 |
|
| 193 |
| Metric | Value |
|
| 194 |
|---|---:|
|
| 195 |
+
| Action chunk vs the fp32 reference (same observation, same initial noise) | PCC `left_arm` 0.99994 · `right_arm` 0.99997 · `right_hand` 0.99999 · `left_hand` max\|d\| 0.030 rad (near-constant group) · normalised chunk 0.99998 |
|
| 196 |
+
| Intermediate taps vs the fp32 reference (the 28 taps the megakernel path exposes, from pixels; DiT intermediates not observable inside the megakernel) | 30/30 rows pass (29 gated); tightest margin `vit_block_first` 0.99994 vs 0.9999 |
|
| 197 |
+
| Megakernel vs the Stage-1 TTNN denoise, same device | normalised chunk PCC 0.999997 |
|
| 198 |
+
| Traced vs untraced, determinism | bit-identical; 5 trace replays bit-identical; 2 CQs == 1 CQ |
|
| 199 |
+
| Inference on p150a (warm, batch 1, median of 50) | **33.6 ms end-to-end** = host encode 1.0 + upload 1.2 + traces 31.1 (vision 8.7 · LLM 8.8 · adapter 4.9 · denoise 8.9 = 2.2 per step) + readback and decode 0.2 |
|
| 200 |
+
| Same tree with the Stage-1 TTNN denoise (`GR00T_DIT_BACKEND=ttnn`) | 40.4 ms end-to-end (denoise 16.0) → megakernel −16.8 % end-to-end, −44 % denoise |
|
| 201 |
+
| Served over HTTP by this image (warm, batch 1) | 32.5 ms device · 34.6 ms server-side incl. PNG decode |
|
| 202 |
+
| Same policy on an RTX 5090 (same host, NVIDIA's unmodified `Gr00tPolicy`, eager PyTorch bf16 + flash-attn 2) | 58.0 ms end-to-end (model 55.4) → p150a 1.73× faster |
|
| 203 |
|
| 204 |
### Caveats
|
| 205 |
|
| 206 |
+
- Stage-2 path: one persistent megakernel runs the denoise (bfp8 weights streamed from a DRAM arena once per step, 110 cores, distributed LayerNorm, per-head flash SDPA); the rest is TTNN ops in three bf16 Metal traces. Its binaries (≈ 131.7 KB) fit 4.5 KB under the 136,192 B kernel-config ring, so the device runs with 64 KiB less L1 per core and the VL adapter's intermediates in DRAM (+0.4 ms); the DiT intermediates are not observable from the served model. `GR00T_DIT_BACKEND=ttnn` serves the Stage-1 denoise from the same image.
|
| 207 |
+
- Fidelity vs the Stage-1 path: gate outcomes equal on 8 of the 9 observations (the demo and 7 of the 8 multi-sample goldens); the one miss is `right_arm` on `t3_s300_seed2`, PCC 0.99987 vs the 0.9999 gate (the TTNN path passes it; the normalised chunk passes on both). The 16.8 % end-to-end gain is below the plan's 30 %.
|
| 208 |
- One embodiment and layout: GR1 arms and hands, one `ego_view` camera, instruction ≤ 102 tokens; anything else is refused with 400. Batch 1.
|
| 209 |
+
- Noise is seeded by default (seed 0, the seed the fp32 reference was dumped with) so the output is reproducible; NVIDIA's `Gr00tPolicy` draws fresh noise per call. Outputs are the base checkpoint's actions for the GR1 sim data config.
|
| 210 |
- Not an OpenAI-compatible API; `GET /v1/models` is a stub.
|
| 211 |
+
- First boot is cold (weight conversion + kernel JIT incl. the megakernel, READY after 2 min 14 s); later boots take about 11 s. Validated on tt-metal `668c2907575`, single p150a only. Details: [`SERVING.md`](SERVING.md), [`GPU_COMPARISON.md`](GPU_COMPARISON.md).
|
| 212 |
|
| 213 |
### Licensing
|
| 214 |
|