changh95 commited on
Commit
75e7135
Β·
verified Β·
1 Parent(s): b59d704

Restore card metadata (license, pipeline_tag, base_model, tags) and upload tt-model authoring files

Browse files

README.md = the finished card (front matter: license other / nvidia-license / license_link, pipeline_tag robotics, base_model, descriptive tags + tt-model-catalog; quickstart paragraph simplified to the two bullets as in changh95/rf-detr-p150 and changh95/pi05-base-p150). Root files tt-model.yaml, SERVING.md, GPU_COMPARISON.md, LICENSE, LICENSE-NOTICE.md, requirements.lock and media/* from the staging dir; code/ and image/ untouched (as pushed by tt-model push).

.gitattributes CHANGED
@@ -48,3 +48,4 @@ image/blobs/sha256/e72991f8dede710d8265ab8a49bd37bddfd9b4958934fa6f69e7f15c3c904
48
  image/blobs/sha256/20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89 filter=lfs diff=lfs merge=lfs -text
49
  image/blobs/sha256/5a589f1aa835965af711da5a5217b53178226a35f12d46b268f651f27f50ab66 filter=lfs diff=lfs merge=lfs -text
50
  image/blobs/sha256/96d36b6a923d9a423d4d024e1d8b2865ba17630ff6c943b8b5399135262a392c filter=lfs diff=lfs merge=lfs -text
 
 
48
  image/blobs/sha256/20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89 filter=lfs diff=lfs merge=lfs -text
49
  image/blobs/sha256/5a589f1aa835965af711da5a5217b53178226a35f12d46b268f651f27f50ab66 filter=lfs diff=lfs merge=lfs -text
50
  image/blobs/sha256/96d36b6a923d9a423d4d024e1d8b2865ba17630ff6c943b8b5399135262a392c filter=lfs diff=lfs merge=lfs -text
51
+ media/demo_actions.png filter=lfs diff=lfs merge=lfs -text
GPU_COMPARISON.md ADDED
@@ -0,0 +1,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-14/15. Facts only: every number is copied from the file named in its
4
+ row. The two sides run **different code**: the GPU rows are NVIDIA's unmodified `Gr00tPolicy` (the official inference
5
+ path, eager PyTorch); the p150a rows are this package's tt-nn port (Stage-1: TTNN ops replayed from four Metal traces).
6
+ Read the caveats before comparing.
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 `5dc0517ee3b`), `Gr00tTT` with `TTPolicy(dtype_policy="mixed_dit", trace_layout="per_stage")`, served by `code/gr00t_p150/server/app.py` |
13
+ | Weights | `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e`, bf16 shards from the HF cache | same snapshot; converted once into the device layout (bf16 everywhere, bfp8_b for the 16 DiT blocks' matmul weights) |
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` (shipped); served numbers from this image: `SERVING.md` Β§"Container validation" |
20
+
21
+ ## Latency, batch 1 (ms)
22
+
23
+ ### RTX 5090, official eager bf16 + flash-attn 2 path (`ref-env-n15.md` Β§6)
24
+
25
+ | stage | median ms |
26
+ |---|---:|
27
+ | `Gr00tPolicy.get_action` end-to-end (preprocess + model + un-normalise, includes a `deepcopy` of the observation) | **58.0** |
28
+ | preprocessing only (`apply_transforms`: crop/resize, PIL, tokenizer, image processor β€” CPU) | 2.17 |
29
+ | `model.get_action` (autocast bf16) | 55.4 |
30
+ | backbone `EagleBackbone.forward` (ViT + mlp1 + 12 Γ— Qwen3 + lm_head) | 20.8 |
31
+ | β€” `extract_feature` (SigLIP ViT + mlp1) | 11.4 |
32
+ | β€” Qwen3 12 layers + lm_head (difference) | β‰ˆ 9.4 |
33
+ | action head `get_action` | 34.6 |
34
+ | β€” `process_backbone_output` (vlln + 4 VL self-attention blocks) | 1.68 |
35
+ | β€” 4 Γ— (action encoder + 16 DiT blocks + action decoder) (difference) | β‰ˆ 32.9 β‡’ β‰ˆ 8.2 per denoising step |
36
+
37
+ An fp32 (no autocast) model-only run measured 64–100 ms across runs and was not re-measured (not a target). 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, Stage-1 trace path (`e2e_stage1_n15.json`, `stages.<name>.value` / `.p90`)
42
+
43
+ | stage | median ms (p90) |
44
+ |---|---:|
45
+ | **e2e** = host encode + upload + 4 traces + readback + host decode | **43.36** (44.08) |
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, incl. synchronize) | 3.77 (4.33) |
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
+ Same run re-executed on the frozen snapshot worktree before packaging: e2e 43.01, traces 37.82 (vision 8.70 Β· llm 8.73 Β·
59
+ adapter 4.44 Β· denoise 15.99) β€” every trace median within 0.03 ms of the table above (`docs/publish/snapshot.md` Β§5.1).
60
+
61
+ Served over HTTP by this image (`tt-model serve` β†’ `scripts/bench_http.py`, 10 warm-ups + 50 timed demo requests, quiet
62
+ host): **device 42.43 ms (p90 43.18) Β· server-side 45.40 ms (p90 49.20)**, client wall 47.6 ms on the same host β€”
63
+ `SERVING.md` Β§4 has the three boots, incl. two under a heavily loaded host (device 44.3–44.5, server-side 53.6–58.1 ms
64
+ medians) and the cold / warm boot times. The served device figure is 0.7 ms above `bench_e2e`'s device row (41.68 ms).
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 Stage-1 path (`tests/tt/results/test_e2e_untraced_taps_vs_golden_n15_20260914-230740.json`) | gate applied to the p150a (multi-sample rule: twice the worst deviation of NVIDIA's bf16 path over 9 observations) |
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
+ Over the multi-sample set (9 observations, `golden-multisample.md` Β§4.1) NVIDIA's bf16 path itself drops to
81
+ `backbone_features` 0.99771, `vl_self_attention_out` 0.99573 and `action_pred_normalized` 0.999987; the p150a is held
82
+ to gates derived from those minima. All 58 rows of the p150a run pass; the final action chunk agrees with the fp32
83
+ reference to PCC β‰₯ 0.99995 on every gated group.
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 (Stage-1 traces) | ratio |
90
+ |---|---:|---:|---:|
91
+ | end-to-end: `Gr00tPolicy.get_action` vs port e2e (both: host preprocessing + model + un-normalisation, batch 1) | 58.0 | 43.36 | **1.34** |
92
+ | model only: `model.get_action` vs port device (upload + traces + readback) | 55.4 | 41.68 | **1.33** |
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's traces replay the same graph with no launch overhead, which is
99
+ where the 1.7Γ— on the head comes from, while the two backbones are within 20 %.
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 be faster than this
106
+ Stage-1 p150a path. The p150a's own next step (the persistent-megakernel denoise, Stage 2) is not in this image either.
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 reference above; neither is 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 3.8 ms input upload.
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
+
122
+ ```bash
123
+ # p150a, served (any host with the package pulled):
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
+ ```
LICENSE ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ NVIDIA License
2
+ 1. Definitions
3
+ β€œLicensor” means any person or entity that distributes its Work.
4
+ β€œWork” means (a) the original work of authorship made available under this license,
5
+ which may include software, documentation, or other files, and (b) any additions to or
6
+ derivative works thereof that are made available under this license.
7
+ The terms β€œreproduce,” β€œreproduction,” β€œderivative works,” and β€œdistribution” have the
8
+ meaning as provided under U.S. copyright law; provided, however, that for the purposes
9
+ of this license, derivative works shall not include works that remain separable from, or
10
+ merely link (or bind by name) to the interfaces of, the Work.
11
+ Works are β€œmade available” under this license by including in or with the Work either (a)
12
+ a copyright notice referencing the applicability of this license to the Work, or (b) a copy
13
+ of this license.
14
+ 2. License Grant
15
+ 2.1 Copyright Grant. Subject to the terms and conditions of this license, each
16
+ Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free,
17
+ copyright license to use, reproduce, prepare derivative works of, publicly display,
18
+ publicly perform, sublicense and distribute its Work and any resulting derivative
19
+ works in any form.
20
+ 3. Limitations
21
+ 3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so
22
+ under this license, (b) you include a complete copy of this license with your
23
+ distribution, and (c) you retain without modification any copyright, patent,
24
+ trademark, or attribution notices that are present in the Work.
25
+ 3.2 Derivative Works. You may specify that additional or different terms apply to
26
+ the use, reproduction, and distribution of your derivative works of the Work (β€œYour
27
+ Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3
28
+ applies to your derivative works, and (b) you identify the specific derivative works
29
+ that are subject to Your Terms. Notwithstanding Your Terms, this license (including
30
+ the redistribution requirements in Section 3.1) will continue to apply to the Work
31
+ itself.
32
+ 3.3 Use Limitation. The Work and any derivative works thereof only may be used
33
+ or intended for use non-commercially. Notwithstanding the foregoing, NVIDIA
34
+ Corporation and its affiliates may use the Work and any derivative works
35
+ commercially. As used herein, β€œnon-commercially” means for research or
36
+ evaluation purposes only.
37
+ 3.4 Patent Claims. If you bring or threaten to bring a patent claim against any
38
+ Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce
39
+ any patents that you allege are infringed by any Work, then your rights under this
40
+ license from such Licensor (including the grant in Section 2.1) will terminate
41
+ immediately.
42
+ 3.5 Trademarks. This license does not grant any rights to use any Licensor’s or its
43
+ affiliates’ names, logos, or trademarks, except as necessary to reproduce the
44
+ notices described in this license.
45
+ 3.6 Termination. If you violate any term of this license, then your rights under this
46
+ license (including the grant in Section 2.1) will terminate immediately.
47
+ 4. Disclaimer of Warranty.
48
+ THE WORK IS PROVIDED β€œAS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
49
+ EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF
50
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-
51
+ INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS
52
+ LICENSE.
53
+ 5. Limitation of Liability.
54
+ EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL
55
+ THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE
56
+ SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT,
57
+ INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR
58
+ RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT
59
+ NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR
60
+ DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER DAMAGES OR LOSSES),
61
+ EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
LICENSE-NOTICE.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licence notice β€” `changh95/GR00T-N1.5-3B-p150`
2
+
3
+ This repository packages NVIDIA's GR00T N1.5-3B policy for a Tenstorrent Blackhole p150a. It mixes material under
4
+ two licences; nothing here grants more than its source does.
5
+
6
+ | what | where in this repo | licence | source |
7
+ |---|---|---|---|
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`,
15
+ `license_link: https://huggingface.co/nvidia/GR00T-N1.5-3B/blob/main/LICENSE` (HF has no identifier for this licence;
16
+ it is *not* the "NVIDIA Open Model License" and *not* Apache-2.0).
17
+
18
+ The GR00T N1 report is [arXiv:2503.14734](https://arxiv.org/abs/2503.14734); the N1.5 checkpoint is described on its
19
+ [model card](https://huggingface.co/nvidia/GR00T-N1.5-3B).
README.md CHANGED
@@ -4,8 +4,21 @@ tags:
4
  - p150
5
  - tt-dit-server
6
  - tt-model-cache
7
- - tt-model-catalog
8
  - tt-model-container
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ---
10
 
11
  # gr00t-n1.5-3b-p150
@@ -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
- `pull --with-weights` downloads the Docker image and the [`nvidia/GR00T-N1.5-3B`](https://huggingface.co/nvidia/GR00T-N1.5-3B) weights at `869830fc749c35f34771aa5209f923ac57e4564e` (into your HF cache; they are not in the image). `serve` starts the model's own HTTP server on port 20000 (or the next free port, if that one is busy); the first start compiles kernels for your device, which takes several minutes, and the server is ready when it logs `Application startup complete`.
 
28
 
29
  ### Run with tt-cli
30
 
 
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
 
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` (3 bf16 safetensors shards, 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
 
SERVING.md ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Serving GR00T N1.5-3B (`nvidia/GR00T-N1.5-3B`) on Blackhole with tt-model-manager
2
+
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-1**
7
+ path β€” every op a TTNN op, captured once into four Metal traces (`vision`, `llm`, `adapter`, `denoise`) and replayed per
8
+ request; the persistent-megakernel denoise (Stage 2) is not part of this release.
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 `5dc0517ee3bec090b7ead025001a343c2f63aa7b` (parent `668c290`; frozen 2026-09-14T23:36 KST), minus `__pycache__`, `.omc` tool state and 2,001 of the 2,004 per-test result JSONs (the three N1.5 e2e results the card cites and the `d0/d1/d2/mk_k1` summaries stay) |
14
+ | weights | `nvidia/GR00T-N1.5-3B` @ `869830fc749c35f34771aa5209f923ac57e4564e`: `model-0000{1,2,3}-of-00003.safetensors` (5,448,327,040 B), `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.device.open_gr00t_device(trace_region_size=64 MiB, l1_small_size=32768, device_id=0)`; asserts the p150a's 11Γ—10 compute grid and 8 DRAM banks; `TTPolicy(dtype_policy="mixed_dit", trace_layout="per_stage")` |
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`),
20
+ `$TREE=/home/deepgadget/experiments/gr00t/tt-metal` (the built working clone, Python 3.10 venv),
21
+ `$ROOT=/home/deepgadget/experiments/tt-models` (tt-model-manager tooling).
22
+
23
+ ## 1. Layout
24
+
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, megakernel WIP),
35
+ reference/ (fp32 torch reference), tests/, benchmarks/
36
+ code/models/{common,tt_dit,demos}/ the 16 tt-metal files the port imports from the tree (source.code in tt-model.yaml)
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
40
+ requirements.lock the image venv (Python 3.12) as `uv pip freeze` -- written by `tt-model package`
41
+ tt_kernel_manifest.json, image/ written by `tt-model package`; uploaded by `tt-model push`
42
+ ```
43
+
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 16 tree files). The server package `gr00t_p150` is shared with `changh95/GR00T-N1.6-3B-p150`; this
48
+ repo ships only the N1.5 assets (`demo/n15`, `assets/tokenizer/n15`).
49
+
50
+ ## 2. Run on the HOST for validation (no Docker)
51
+
52
+ The tree venv (`$TREE/python_env`, Python 3.10, torch 2.11.0+cpu, ttnn editable) has everything except
53
+ `fastapi`/`uvicorn`. Do not install into the tree venv; put the HTTP stack in a side directory and prepend it to
54
+ `PYTHONPATH` (`$PUB/http-side` already holds fastapi 0.141.1 / uvicorn 0.53.0 / pydantic 2.13.5 / starlette 1.6.0):
55
+
56
+ ```bash
57
+ export PATH=$HOME/.local/bin:$PATH
58
+ mkdir -p $PUB/http-side && uv pip install --python $TREE/python_env/bin/python --target $PUB/http-side fastapi uvicorn "pydantic>=2"
59
+
60
+ cd $PUB/GR00T-N1.5-3B-p150
61
+ export TT_METAL_HOME=$TREE ARCH_NAME=blackhole
62
+ export PYTHONPATH=$PWD/code:$TREE:$TREE/ttnn:$PUB/http-side # code/ FIRST so models.experimental.gr00t is the snapshot
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 (~6.4 GB for N1.5)
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')"
71
+
72
+ # serve + smoke + stop -- ONE device-lock hold (the box has one p150a shared with other sessions):
73
+ DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh bash -c '
74
+ '$TREE'/python_env/bin/python -m uvicorn --host 127.0.0.1 --port 20010 --lifespan on gr00t_p150.server.app:app & UV=$!
75
+ python3 code/gr00t_p150/server/smoke_test.py --url http://127.0.0.1:20010 --wait 1800 --out /tmp/gr00t-n15-smoke.json; RC=$?
76
+ kill -TERM $UV; wait $UV; exit $RC'
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)` β†’ `Loading pipeline:
81
+ Gr00tTT.from_pretrained(n15, …)` β†’ `Model built in N s (weights N s, 778 tensors, 3176 MB on device, cache path
82
+ warm|cold)` β†’ `Warming up: capture per_stage traces on the n15 demo observation, then N traced predict(s)` β†’
83
+ `Warmup k/N: … ms (encode, device, decode)` β†’ `Warm-up fidelity vs golden fp32: actions PCC … ` β†’ `Warmup complete
84
+ (capture s, first ms, steady ms; boot s) -- per_stage traces ['vision', 'llm', 'adapter', 'denoise']` β†’ uvicorn
85
+ `Application startup complete`. Any failure raises and uvicorn exits non-zero (no CPU fallback); the boot also **fails**
86
+ if the warm-up actions' PCC vs the shipped golden is below `GR00T_WARMUP_PCC_MIN` (0.999). SIGTERM / Ctrl-C:
87
+ `Releasing traces and device tensors` β†’ `Closing device`.
88
+
89
+ Measured host boots (warm `~/.cache/gr00t-tt` and warm `TT_METAL_CACHE`; `$PUB/logs/server_n15_*.log`): first boot of a
90
+ process 14.6 s to READY (model built 9.3 s incl. 9.2 s reading the `.tensorbin` tier, warm + capture 2.5 s, 5 traced
91
+ warm-ups 44.4–44.8 ms); an immediately following boot 4.5 s (0.6 s model build with the tiers in the page cache); on the
92
+ frozen snapshot worktree 5.2 s (`server_n15_snapshot_20260914-235713.log`). Kernels are rebuilt once for a new
93
+ `TT_METAL_HOME` path (~42 s for the 355 kernels of the four traces, `docs/publish/snapshot.md` Β§2.3).
94
+
95
+ Expected smoke line (host, snapshot worktree, `$PUB/logs/smoke_n15_snapshot_20260914-235713.log`):
96
+
97
+ ```
98
+ PASS GR00T-N1.5-3B-p150 n15: PCC(actions)=0.999982 max|d|=0.0289 PCC(action_pred_valid)=0.999976 repeat_maxdiff=0.0e+00
99
+ seed_path_ok device_ms=42.95/42.16 total_ms=47.22/44.83 wall_ms=50
100
+ per_group[left_arm=0.99995/0.0214 right_arm=0.99997/0.0172 left_hand=0.94335/0.0179 right_hand=0.99999/0.0289]
101
+ ```
102
+
103
+ The per-group PCC / max|d| equal the port's own device test (`code/models/experimental/gr00t/tests/tt/results/
104
+ test_e2e_predict_actions_vs_golden_n15_20260914-230743.json`) to the printed precision; the traced policy is
105
+ deterministic, so every run reproduces them bit-exactly.
106
+
107
+ Offline overrides: `GR00T_WEIGHTS_DIR=<dir with the snapshot files>` (shimmed into a private hub cache so the port's
108
+ `snapshot_dir()` resolves to it), `GR00T_TOKENIZER_DIR=<dir>`.
109
+
110
+ Offline checks that need no device:
111
+
112
+ ```bash
113
+ cd $PUB/GR00T-N1.5-3B-p150
114
+ # manifest + launcher preview (from the repo dir -- `root: code` is CWD-relative); must print VALID
115
+ $ROOT/.venv/bin/python -c "from tt_kernel.container_manifest import load_container_manifest; m = load_container_manifest('tt-model.yaml', check_sources=True); p = m.resolve_profile(); print('VALID', m.name, m.kind, p.hardware, p.mesh_device, m.weights_ref)"
116
+ # Python 3.12 resolution of runtime.packages (what the image does) -- must keep numpy<2 and torch 2.11.0+cpu
117
+ uv venv --python 3.12 $PUB/depcheck-n15 -q
118
+ 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" \
119
+ safetensors huggingface_hub "transformers==5.12.1" "opencv-python-headless==4.8.1.78" "torchvision==0.26.0" pytest \
120
+ --extra-index-url https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match
121
+ # -> 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 (logs/uv-dryrun-n15.log)
122
+ ```
123
+
124
+ ## 3. Package, serve, push (Docker)
125
+
126
+ Rootless Docker on this box needs `source $ROOT/bin/docker-env.sh` first (PATH + `DOCKER_HOST`; the bare `docker`
127
+ on PATH is podman). **Run every `tt-model` command from this directory**: `source.tt_metal` and
128
+ `extra_code[].root: code` resolve against the process CWD. `--out` points outside any git checkout because `stage()`
129
+ deletes `<out>/gr00t-n1.5-3b-p150` before rebuilding. The manifest `name` is the slug `gr00t-n1.5-3b-p150`, so that is
130
+ the build directory, the cache directory (`~/.cache/tt-model/gr00t-n1.5-3b-p150/`) and the generated card's title.
131
+
132
+ ```bash
133
+ source $ROOT/bin/docker-env.sh
134
+ cd $PUB/GR00T-N1.5-3B-p150
135
+
136
+ $ROOT/.venv/bin/tt-model package --container tt-model.yaml --out $PUB/build # tt-metal C++ build (ccache) + venv + verify.sh
137
+ # device: serve -> smoke -> bench -> stop inside ONE lock hold (`serve` returns after READY and leaves the container
138
+ # running on the card; a second with-device.sh caller would probe a busy card and reset it)
139
+ DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh bash -c '
140
+ source '$ROOT'/bin/docker-env.sh
141
+ '$ROOT'/.venv/bin/tt-model serve '$PUB'/build/gr00t-n1.5-3b-p150/tt_kernel_manifest.json 2>&1 | tee '$PUB'/logs/serve-n15.log || exit 1
142
+ PORT=$(grep -o "127.0.0.1:[0-9]*" '$PUB'/logs/serve-n15.log | head -1 | cut -d: -f2)
143
+ python3 code/gr00t_p150/server/smoke_test.py --url http://127.0.0.1:$PORT --out '$PUB'/logs/smoke-n15.json; RC=$?
144
+ python3 code/scripts/bench_http.py --url http://127.0.0.1:$PORT --reps 50 --warmup 10 --out '$PUB'/logs/bench-http-n15.json
145
+ '$ROOT'/.venv/bin/tt-model logs changh95/GR00T-N1.5-3B-p150 > '$PUB'/logs/container-n15.log 2>&1
146
+ '$ROOT'/.venv/bin/tt-model stop changh95/GR00T-N1.5-3B-p150; exit $RC'
147
+ $ROOT/.venv/bin/tt-model push $PUB/build/gr00t-n1.5-3b-p150 --publish
148
+ ```
149
+
150
+ `serve` pre-downloads the pinned files (a cache hit on this host), then `docker run --user 0:0 --device
151
+ /dev/tenstorrent --ipc host` with `/dev/hugepages-1G`, `~/.cache/huggingface` at `/hf` (rw, `HF_HOME=/hf`) and
152
+ `~/.cache/tt-model/gr00t-n1.5-3b-p150/{cache,weights,tensors}` at `/cache` (`TT_METAL_CACHE`, JIT kernels),
153
+ `/weight-cache` (`TT_DIT_CACHE_DIR`; the port's weight tiers go to `/weight-cache/gr00t-tt`, ~6.4 GB) and
154
+ `/tensor-cache`; it exports exactly `HF_MODEL=nvidia/GR00T-N1.5-3B`, `MESH_DEVICE=P150`, `TT_MESH_SHAPE=1x1` plus
155
+ `serve.env` from `tt-model.yaml`, publishes the first free port from 20000 and waits ≀ 1800 s for `Application startup
156
+ complete`. tt-cli users: `tt serve changh95/GR00T-N1.5-3B-p150` / `tt model stop changh95/GR00T-N1.5-3B-p150`
157
+ (after the push).
158
+
159
+ `push` makes `code/` and `image/` on the Hub exactly the staged trees and replaces `README.md` with the generated card
160
+ (everything worth keeping lives in `card.description` / `card.quickstart` of `tt-model.yaml`); `media/`, `SERVING.md`,
161
+ `GPU_COMPARISON.md`, `LICENSE`, `LICENSE-NOTICE.md`, `tt-model.yaml` and `.gitattributes` at the repo root survive; the
162
+ `license` / `pipeline_tag` / `base_model` front matter is restored afterwards with `huggingface_hub.metadata_update`.
163
+
164
+ ## 4. Container validation (the image built from this manifest)
165
+
166
+ Image `tt-model/gr00t-n1.5-3b-p150:443501bd3b7a` (`sha256:443501bd3b7afce579bf743eeedd64809886653c29a9d7125614e8451cd61a7b`,
167
+ 3.2 GB as 46 OCI blobs), built 2026-09-14 15:17–15:30 UTC from the manifest above (`tt-model package`: tt-metal C++ build
168
+ with a warm ccache β‰ˆ 7 min, Python 3.12 venv, all 17 `verify:` lines + the launcher's own passed inside the image;
169
+ 13 min 17 s in total). Validated on the p150a on 2026-09-15 00:56–01:02 KST: three `tt-model serve` boots of this exact
170
+ image, each followed by `smoke_test.py`, a 50-request `bench_http.py` and `tt-model stop`, all inside one device-lock hold
171
+ per run (`publish/logs/validate-n15.run2.log` = boots 1–2, `validate-n15.run3.log` = boot 3; per-boot files
172
+ `{serve,smoke,bench-http,container,info}-n15.{2,3}.*`; boot 1's per-file artefacts were overwritten by boot 3 and survive
173
+ only inside `validate-n15.run2.log`).
174
+
175
+ | 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` |
176
+ |---|---|---|---|---|---|---|---|
177
+ | 1 (**cold**) | `weights/` and `cache/` empty before; 6.4 GB + 411 MB after | two concurrent tt-metal image builds + CPU tests (load avg 16–110) | **1 min 43 s** (103.6 s wall) | 100.9 s: device open β‰ˆ 4 s β†’ `Model built in 8.9 s (… cache path cold)` (checkpoint β†’ host plan β†’ `.tensorbin`) β†’ trace capture **87.3 s** incl. the JIT of the four traces' kernels (`riscv-tt-elf-g++` lines in the container log) β†’ 3 warm-ups 50.9 / 45.8 / 101.5 ms | **PASS** β€” PCC(actions) 0.999982, max\|d\| 0.0289; PCC(`action_pred_valid`) 0.999976; device 46.96 / 42.21 ms, total 54.42 / 48.21 ms | 44.27 (55.35) / 53.59 (91.12) ms; encode median 3.2, p90 41 ms; client wall 58.1 (94.5) | clean SIGTERM shutdown 4.9 s |
178
+ | 2 (warm, immediately after) | reused | same loaded host | 20.2 s (21.3 s wall) | 15.1 s: `Model built in 0.6 s (… cache path warm)`, capture 6.6 s, warm-ups 47.3 / 119.4 / 65.3 ms (encode spikes = host contention) | **PASS** β€” same fidelity; device 50.88 / 42.59 ms, total 67.44 / 45.45 ms | 44.51 (55.76) / 58.14 (96.86) ms; encode median 5.8, p90 38 ms; client wall 64.4 (104.2) | clean 3.1 s |
179
+ | 3 (warm, quiet host) | reused | load avg β‰ˆ 9, no other device or build work | **9.7 s** (10.0 s wall) | 7.6 s: model 0.6 s, capture 3.2 s, warm-ups 44.5 β†’ 44.3 ms | **PASS** β€” same fidelity; device 42.99 / 42.19 ms, total 47.31 / 44.95 ms, client wall 50 ms | **42.43 (43.18) / 45.40 (49.20) ms**, min 41.66 / 44.46; decode 1.06, encode 1.63, decode_actions 0.14; client wall 47.6 (51.8) | clean 2.0 s |
180
+
181
+ Fidelity is identical in every boot and identical to the host runs of Β§2 β€” the served `actions` JSON of the container is
182
+ byte-for-byte the host run's (`smoke-n15.3.json` vs `smoke_n15_snapshot_20260914-235713.json`) β€” and equal to the
183
+ port's device test `tests/tt/results/test_e2e_predict_actions_vs_golden_n15_20260914-230743.json` to the printed precision:
184
+ `left_arm` 0.99995 / 0.0214, `right_arm` 0.99997 / 0.0172, `left_hand` 0.94335 / 0.0179 (abs-gated), `right_hand`
185
+ 0.99999 / 0.0289. The boot-3 benchmark is the card's "served" row; the device median 42.4 ms is 0.7 ms above the port's
186
+ own `bench_e2e` device figure (41.68 ms: upload + traces + readback) and the loaded-host runs show that the extra
187
+ latency and the p90 tails sit in the host-side stages (`encode`), not on the device.
188
+
189
+ `GET /info` inside the container reports `weights.snapshot_dir = /hf/hub/models--nvidia--GR00T-N1.5-3B/snapshots/869830fc…`
190
+ (the mounted HF cache), `source.tt_metal = {commit 668c290…, ttnn_dist_version 0.65.2.dev10011}`,
191
+ `source.port_snapshot_commit = 5dc0517ee3b…`, `device_facts` grid 11Γ—10 / DRAM 8Γ—1 (`info-n15.3.json`).
192
+
193
+ **Device-sharing note for this host.** The first validation attempt (00:32–00:40 KST) ran while another session's
194
+ container was still holding the card after that session's lock hold had ended; `bin/with-device.sh`'s probe hung and
195
+ reset the card twice, then refused to run. Since then every device entry goes through
196
+ `publish/scripts/run_validation_n15_when_free.sh`, which waits until `docker ps` shows no `tt-model-*` container and the
197
+ lock is free. Use the manifest path with `tt-model stop` / `logs` for an un-pushed package (`tt-model stop
198
+ changh95/GR00T-N1.5-3B-p150` only works after `pull`).
199
+
200
+ ## 5. Request / response contract
201
+
202
+ | route | returns |
203
+ |---|---|
204
+ | `GET /health` | `{"status": "ok" \| "starting", "model": "GR00T-N1.5-3B-p150", "device": "blackhole:0"}` β€” 200 always; `ok` only after the warm-up |
205
+ | `GET /info` | model / version / task / hardware / `stage` (`stage1-ttnn-traces` + megakernel note) / `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` |
206
+ | `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 |
207
+ | `GET /demo` | `{"request": <ready-to-post demo body>, "expected": <fp32 reference actions + gates>}` |
208
+ | `POST /predict` | one observation β†’ one 16-step action chunk (below) |
209
+
210
+ `POST /predict` request (JSON; unknown fields β†’ 422):
211
+
212
+ | field | type | meaning |
213
+ |---|---|---|
214
+ | `images` | `{"ego_view": <b64 PNG/JPEG>}` (or a one-element list per camera) | exactly the GR1 camera; any resolution with sides in [64, 4096] px; the server applies the checkpoint's eval chain (x/255 β†’ 0.95 centre crop β†’ bilinear antialiased 224Γ—224 β†’ Eagle2.5 normalisation), bit-exact vs the reference preprocessing |
215
+ | `state` | `{"left_arm": [[7]], "right_arm": [[7]], "left_hand": [[6]], "right_hand": [[6]]}` | raw physical joint state, one time step (`(D,)` or `(1, D)`), finite floats; sin/cos-encoded and padded to 64 dims on the host |
216
+ | `instruction` | `str` (aliases `language`, `prompt`) | task text, 1–2000 chars, ≀ 102 BPE tokens after the port's prompt build (`repr([instruction])` inside the Eagle2 chat prompt); longer β†’ 400, never truncated |
217
+ | `embodiment` | `"gr1"`, optional | must equal the server's; anything else β†’ 400 |
218
+ | `seed` | int β‰₯ 0, optional | CPU-generator seed of the `[1, 16, 32]` flow-matching noise; default 0 (the deployed policy's) |
219
+ | `noise` | `[[32 floats] Γ— 16]`, optional | explicit initial noise; mutually exclusive with `seed` |
220
+ | `state_dtype` | `"float64"` (default) \| `"float32"` | dtype of the state before normalisation (the GR1 dataset stores float64) |
221
+ | `return_normalized` | bool, default false | also return the model-space chunk `action_pred_normalized` (`[16][32]`) and `action_pred_valid_hd` (`[16, 26]`) |
222
+
223
+ Response (200):
224
+
225
+ ```json
226
+ {"actions": {"left_arm": [[7 floats] x 16], "right_arm": [...], "left_hand": [...], "right_hand": [...]},
227
+ "action_horizon": 16, "action_keys": ["left_arm", "right_arm", "left_hand", "right_hand"], "action_dims": {"left_arm": 7, "right_arm": 7, "left_hand": 6, "right_hand": 6},
228
+ "normalized": false, "embodiment": "gr1", "embodiment_id": 24, "version": "n15", "model": "GR00T-N1.5-3B-p150", "layout": "gr1",
229
+ "images": {"ego_view": {"frames": 1, "received_hw": [256, 256]}}, "seq_len": 296, "prompt_tokens": 14, "state_dtype": "float64",
230
+ "noise_source": "seed" | "client", "seed": 0 | null,
231
+ "timing_ms": {"decode": 1.5, "encode": 2.6, "device": 43.0, "decode_actions": 0.1, "total": 47.2}}
232
+ ```
233
+
234
+ `actions` are physical joint targets (radians) β€” `(y + 1) / 2 Β· (max βˆ’ min) + min` with the checkpoint's
235
+ `experiment_cfg/metadata.json` GR1 statistics β€” exactly what `Gr00tPolicy.get_action` returns. `timing_ms`: `decode` =
236
+ base64 + image decode + validation; `encode` = the port's host preprocessing; `device` = `predict_normalized` (static-shape
237
+ asserts, input writes, 4 Γ— `execute_trace`, one blocking readback); `decode_actions` = un-normalisation; `total` = the
238
+ whole handler. Errors: **400** structural problems (`{"detail": {"errors": [...], "inputs": {...}}}`), undecodable image,
239
+ bad state / instruction / noise, other embodiment; **503** while starting; **500** `Type: text` on a device failure.
240
+ Handlers are synchronous and serialised on one lock; batch 1.
241
+
242
+ ## 6. Environment (read in the lifespan, never at import)
243
+
244
+ | variable | container value (`serve.env`) | meaning |
245
+ |---|---|---|
246
+ | `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`) |
247
+ | `GR00T_VERSION` | `n15` | the server code is shared by both GR00T packages and never guesses |
248
+ | `GR00T_EMBODIMENT` / `GR00T_LAYOUT` | `gr1` / `gr1` | the only device-validated layout of this release |
249
+ | `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 |
250
+ | `GR00T_TRACE_REGION_SIZE` / `GR00T_L1_SMALL_SIZE` | default 67108864 / 32768 | the port's validated `open_gr00t_device` values |
251
+ | `GR00T_TOKENIZER_DIR_N15` | `/opt/tt-metal/gr00t_p150/assets/tokenizer/n15` | vendored Qwen2 tokenizer (not in the weights repo); `GR00T_TOKENIZER_DIR` overrides |
252
+ | `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`) |
253
+ | `GR00T_PROJECT_ROOT` / `GR00T_GOLDEN_ROOT` | `/nonexistent` | the port's dev-box defaults; nothing on the serve path needs them |
254
+ | `GR00T_WARMUP_RUNS` / `GR00T_WARMUP_PCC_MIN` | `3` / default 0.999 | traced predicts before READY; boot fails below the PCC floor vs the shipped golden |
255
+ | `GR00T_DEFAULT_SEED`, `GR00T_TORCH_THREADS`, `GR00T_WEIGHTS_DIR` | unset | request default seed (0), torch intra-op threads, offline snapshot dir |
256
+ | `TT_MESH_SHAPE`, `TT_DEVICE_ID`, `TT_METAL_VISIBLE_DEVICES` | `1x1` (launcher), `0`, `0` | one chip; any other mesh shape is refused |
257
+
258
+ ## 7. Caveats
259
+
260
+ - **Stage-1 path only.** TTNN ops in Metal traces; the persistent-megakernel denoise (K1 streaming kernel: 464 GB/s bf16 /
261
+ 414 GB/s bfp8, `code/models/experimental/gr00t/tests/tt/results/mk_k1_summary.md`) is not integrated into `Gr00tTT` and not served.
262
+ - **Shape-locked traces.** One embodiment (`gr1`), one camera, one frame, ≀ 102 text tokens (LLM S padded to 384), batch 1.
263
+ Other embodiments / cameras / longer instructions β†’ 400 before anything reaches the device. Concurrent requests queue on one lock.
264
+ - **Deterministic noise.** Actions are a deterministic function of (observation, seed | noise); the reference noise of the
265
+ demo is not seed-reproducible, so the smoke test posts it as `noise`. Same observation + same noise β†’ bit-identical actions.
266
+ - **Base checkpoint.** Outputs are the GR1 sim data-config actions of the released N1.5 checkpoint; a real robot needs NVIDIA's post-training.
267
+ - **First container boot is cold**: the port converts the 5.4 GB checkpoint into its `.pt` plan tier and the `.tensorbin`
268
+ device tier under `/weight-cache/gr00t-tt` and JITs the kernels of the four traces into `/cache`; both persist under
269
+ `~/.cache/tt-model/gr00t-n1.5-3b-p150/`, so later boots are seconds (measured values in Β§4).
270
+ - **Python 3.12 image vs 3.10 tree venv.** Host runs are an approximation; the image is authoritative (its `verify.sh`
271
+ imports the app and the port with no device; the served smoke in Β§4 is the proof).
272
+ - **Weights licence.** `nvidia/GR00T-N1.5-3B` is under the NVIDIA License (non-commercial: research or evaluation use);
273
+ the server reports it under `/info -> license`. See `LICENSE-NOTICE.md`.
274
+ - `tt-model curl` and the ready card's `/v1/models` hint are OpenAI-shaped and are not this API; use the routes above.
media/demo_actions.png ADDED

Git LFS Details

  • SHA256: b84e80a26ce9ad40756ec49bbd8c16bef3202210358337ee89e3a39ee3703699
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
media/demo_ego_view.png ADDED
media/demo_observation.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "state": {
3
+ "left_arm": [
4
+ [
5
+ 0.024056308373237207,
6
+ 0.033822765064097184,
7
+ -0.016396038448321357,
8
+ -2.0726931601418075,
9
+ 0.012247859931799656,
10
+ -0.03578162003720947,
11
+ -0.012941182543890062
12
+ ]
13
+ ],
14
+ "right_arm": [
15
+ [
16
+ 0.11583298923348632,
17
+ -0.10512690694722195,
18
+ 0.2879116792491345,
19
+ -1.9319273126265488,
20
+ 0.3167383566755674,
21
+ -0.1142246377868063,
22
+ 1.0186058587273703
23
+ ]
24
+ ],
25
+ "left_hand": [
26
+ [
27
+ 6.398235076672701e-05,
28
+ -9.923445381200851e-05,
29
+ -0.0002996759294460034,
30
+ -0.00018601688776459478,
31
+ -0.003114431240670283,
32
+ 1.7421393062719688
33
+ ]
34
+ ],
35
+ "right_hand": [
36
+ [
37
+ 0.009224068251638124,
38
+ 0.011558206453856013,
39
+ 0.014177547256735298,
40
+ 0.011238545611423637,
41
+ 0.1418649869353279,
42
+ 1.741832493216234
43
+ ]
44
+ ]
45
+ },
46
+ "instruction": "pick the pear from the counter and place it in the plate",
47
+ "embodiment": "gr1",
48
+ "state_dtype": "float64",
49
+ "_note": "GR1 sim robot_sim.PickNPlace demo step (the port's canonical golden observation); add images.ego_view = base64 of media/demo_ego_view.png and POST to /predict"
50
+ }
tt-model.yaml ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # tt-model-manager container manifest (schema 5.1) for NVIDIA Isaac GR00T N1.5-3B (Stage-1 TTNN path) on Blackhole.
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
6
+ # `source /home/deepgadget/experiments/tt-models/bin/docker-env.sh` first (bare `docker` is podman).
7
+ #
8
+ # tt-model package --container tt-model.yaml --out /home/deepgadget/experiments/gr00t/publish/build
9
+ # DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh bash -c '
10
+ # tt-model serve /home/deepgadget/experiments/gr00t/publish/build/gr00t-n1.5-3b-p150/tt_kernel_manifest.json && \
11
+ # python3 code/gr00t_p150/server/smoke_test.py --url http://127.0.0.1:20000; tt-model stop changh95/GR00T-N1.5-3B-p150'
12
+ # tt-model push /home/deepgadget/experiments/gr00t/publish/build/gr00t-n1.5-3b-p150 --publish
13
+ schema: "5.1"
14
+
15
+ repo: changh95/GR00T-N1.5-3B-p150
16
+ name: gr00t-n1.5-3b-p150 # slug ([a-z0-9][a-z0-9._-]*): cache dir, container name, generated H1
17
+
18
+ # A POINTER, pinned. Weights are never baked into the image: `tt-model serve` pre-downloads exactly these files
19
+ # at this sha into the host HF cache (mounted at /hf, HF_HOME=/hf) and the server resolves the same snapshot with
20
+ # huggingface_hub.snapshot_download(revision=TT_WEIGHTS_REVISION, local_files_only=True) and the port's own
21
+ # common.configs.snapshot_dir() (both must agree, the server asserts it).
22
+ weights:
23
+ repo: nvidia/GR00T-N1.5-3B
24
+ revision: 869830fc749c35f34771aa5209f923ac57e4564e
25
+ allow_patterns:
26
+ - "model-*.safetensors" # 3 bf16 shards, 5,448,327,040 bytes
27
+ - model.safetensors.index.json # required by the port's LazyCheckpoint (common/checkpoint.py)
28
+ - config.json
29
+ - experiment_cfg/metadata.json # GR1 normalisation statistics (common/normalization.py)
30
+ - LICENSE # NVIDIA License -- keep it next to the weights it covers
31
+
32
+ kind: tt-dit-server
33
+ arch: blackhole
34
+
35
+ source:
36
+ # Clean checkout of tt-metal main 668c2907575 (v0.79.0-dev20260914-1): a local clone of the validated tree with its
37
+ # three submodules populated and `git status --porcelain` empty, so the image is built from exactly the commit the
38
+ # card links to (the working clone has untracked dirs and would be recorded as a dirty tree).
39
+ tt_metal: /home/deepgadget/experiments/gr00t/publish/tt-metal-668c2907575
40
+ # tt-metal-relative files the port imports from the tree OTHER than itself, with their import closure
41
+ # (the image's tt-metal copy excludes models/, so everything under models/ must be listed here or in extra_code).
42
+ code:
43
+ - models/common/utility_functions.py # models.tt_dit.utils.matmul imports is_blackhole (needs pytest!)
44
+ - models/tt_dit/utils/matmul.py # tt/layers.py: the `minimal` matmul rows (lazy)
45
+ - models/tt_dit/utils/agmm_rules.py # lazy from matmul.py
46
+ - models/tt_dit/utils/mmrs_rules.py # lazy from matmul.py
47
+ - models/tt_dit/utils/tensor.py # layers/linear.py, layers/module.py, lora.py, parallel/manager.py
48
+ - models/tt_dit/utils/progress.py # layers/module.py
49
+ - models/tt_dit/utils/walltime.py # utils/progress.py
50
+ - models/tt_dit/utils/substate.py # layers/module.py
51
+ - models/tt_dit/layers/linear.py # encoders/qwen3vl/vision_qwen3vl.py (N1.7 tower; lazy, unused by N1.5)
52
+ - models/tt_dit/layers/module.py
53
+ - models/tt_dit/layers/normalization.py
54
+ - models/tt_dit/layers/lora.py # imported at the bottom of layers/linear.py
55
+ - models/tt_dit/parallel/config.py
56
+ - models/tt_dit/parallel/manager.py
57
+ - models/tt_dit/encoders/qwen3vl/vision_qwen3vl.py # tt/qwen3vl_vision.py (lazy; not on the N1.5 serve path)
58
+ - models/demos/deepseek_v3_b1/unified_kernel_descriptor.py # tt/megakernel/descriptors.py (lazy; Stage-2 work, not served)
59
+ # This repo's own code (lands at /opt/tt-metal/<path>; PYTHONPATH=/opt/tt-metal).
60
+ extra_code:
61
+ - root: code
62
+ paths:
63
+ - models # code/models/experimental/gr00t: the frozen snapshot of the port (tt-metal ref gr00t-p150-snapshot, 5dc0517ee3b)
64
+ - gr00t_p150 # the policy server, its smoke test, the demo observation and the vendored Qwen2 tokenizer files
65
+ - scripts # download_weights.sh, bench_http.py
66
+ ubuntu: "22.04"
67
+ python: "3.12"
68
+
69
+ runtime:
70
+ app: gr00t_p150.server.app:app
71
+ mesh_shape_env: TT_MESH_SHAPE
72
+ # On top of the kind defaults (fastapi, uvicorn, pydantic>=2, pillow), the auto-pinned torch==2.11.0+cpu (the tree's
73
+ # requirements-dev.txt pin) and ttnn's own deps (numpy<2, loguru, ...). Every third-party module on the serve path,
74
+ # pinned where the tree pins it. Resolution on Python 3.12 proven with a uv dry-run (SERVING.md).
75
+ packages:
76
+ - "numpy>=1.24.4,<2"
77
+ - safetensors # common/checkpoint.py
78
+ - huggingface_hub # server: snapshot_download(local_files_only=True) guard
79
+ - transformers==5.12.1 # common/prompts.get_tokenizer (Qwen2 tokenizer, lazy); tree pin
80
+ - opencv-python-headless==4.8.1.78 # common/preprocessing.py imports cv2 at module level; tree pin (cp312 wheel exists)
81
+ - torchvision==0.26.0 # preprocessing.n15_crop_resize (center_crop / resize antialias); tree pin for torch 2.11.0
82
+ - pytest # models/common/utility_functions.py imports pytest at module level
83
+
84
+ serve:
85
+ port: 20000
86
+ hardware: p150
87
+ mesh_device: P150
88
+ env:
89
+ TT_WEIGHTS_REVISION: "869830fc749c35f34771aa5209f923ac57e4564e" # == weights.revision == configs.HF_SNAPSHOT_SHAS["n15"]
90
+ TT_METAL_VISIBLE_DEVICES: "0"
91
+ TT_DEVICE_ID: "0"
92
+ GR00T_VERSION: "n15"
93
+ GR00T_EMBODIMENT: "gr1" # the only embodiment with a device-validated layout in this release
94
+ GR00T_LAYOUT: "gr1" # static layout: 1 image x 256 tokens, L_max 102 text tokens, LLM sequence padded to 384
95
+ GR00T_POLICY: "mixed_dit" # TTPolicy.dtype_policy: DiT block matmul weights bfp8_b, everything else bf16 (validated default)
96
+ GR00T_TRACE_LAYOUT: "per_stage" # four Metal traces vision / llm / adapter / denoise (the benchmarked layout)
97
+ GR00T_TOKENIZER_DIR_N15: "/opt/tt-metal/gr00t_p150/assets/tokenizer/n15"
98
+ GR00T_PROJECT_ROOT: "/nonexistent" # the port's dev-box default for project-relative paths; nothing on the serve path needs it
99
+ GR00T_GOLDEN_ROOT: "/nonexistent" # tests only (defensive)
100
+ GR00T_TT_CACHE: "/weight-cache/gr00t-tt" # host plan (.pt) + device-layout (.tensorbin) weight tiers -> ~/.cache/tt-model/gr00t-n1.5-3b-p150/weights
101
+ GR00T_WARMUP_RUNS: "3" # traced predict() calls after trace capture, before READY
102
+
103
+ # Build-time assertions, run INSIDE the finished image as uid 1000, no device, no weights, no HF_MODEL.
104
+ verify:
105
+ - "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'"
106
+ - "import models.experimental.gr00t.tt.model as m; assert m.Gr00tTT"
107
+ - "from models.experimental.gr00t.common import preprocessing, normalization, prompts, checkpoint, weights, configs; assert configs.HF_SNAPSHOT_SHAS['n15'] == '869830fc749c35f34771aa5209f923ac57e4564e'"
108
+ - "import pytest, models.common.utility_functions; import models.tt_dit.utils.matmul as mm; assert mm.get_matmul_config"
109
+ - "import models.demos.deepseek_v3_b1.unified_kernel_descriptor as u; assert u.UnifiedKernelDescriptor"
110
+ - "import models.tt_dit.encoders.qwen3vl.vision_qwen3vl"
111
+ - "import models.experimental.gr00t.tt.layers, models.experimental.gr00t.tt.qwen3vl_vision, models.experimental.gr00t.tt.megakernel.descriptors"
112
+ - "import safetensors, huggingface_hub, cv2, numpy, PIL; assert int(numpy.__version__.split('.')[0]) < 2, numpy.__version__"
113
+ - "import transformers; assert transformers.__version__ == '5.12.1', transformers.__version__"
114
+ - "import torchvision; assert torchvision.__version__.startswith('0.26.0'), torchvision.__version__"
115
+ - "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())"
116
+ - "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"
117
+ - "from pathlib import Path; assert Path('/opt/tt-metal/models/experimental/gr00t/common/golden_tap_map.json').is_file()"
118
+ - "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()"
119
+ - "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('*'))"
120
+ - "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'))"
121
+ - "from gr00t_p150.server.app import parse_mesh_shape as p; assert p('1x1') == p('(1, 1)') == p('1,1') == (1, 1)"
122
+
123
+ card:
124
+ description: >
125
+ NVIDIA Isaac GR00T N1.5 (3B vision-language-action policy: SigLIP ViT (224Β², 256 tokens/image) β†’ linear projector β†’
126
+ 12-layer Qwen3-1.7B backbone β†’ 4-block VL self-attention β†’ 16-block cross/self AdaLN DiT, 4 Euler flow-matching
127
+ steps) running on one Tenstorrent Blackhole p150a via tt-nn: one camera frame + proprioceptive state + a task
128
+ instruction in, a 16-step chunk of GR1 arm/hand joint targets out. Stage-1 release: every op is a TTNN op replayed
129
+ from four Metal traces (DiT block matmul weights bfp8, everything else bf16); the persistent-megakernel denoise is in
130
+ progress and not in this image.
131
+
132
+ Weights: [nvidia/GR00T-N1.5-3B](https://huggingface.co/nvidia/GR00T-N1.5-3B) (NVIDIA License, non-commercial) Β·
133
+ Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) (GR00T N1 report; the N1.5 changes are described in the
134
+ [N1.5 model card](https://huggingface.co/nvidia/GR00T-N1.5-3B)) Β·
135
+ Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (tag `n1.5-release`) Β·
136
+ Port: `code/models/experimental/gr00t` in this repo (tt-metal-tree layout, frozen snapshot `5dc0517ee3b`) + the
137
+ policy server `code/gr00t_p150`
138
+ quickstart: |
139
+ ### Run with tt-cli
140
+
141
+ ```bash
142
+ tt serve changh95/GR00T-N1.5-3B-p150
143
+ python3 - <<'EOF'
144
+ import base64, json
145
+ body = json.load(open("media/demo_observation.json")) # GR1 PickNPlace demo step: raw joint state + instruction
146
+ body.pop("_note", None)
147
+ body["images"] = {"ego_view": base64.b64encode(open("media/demo_ego_view.png", "rb").read()).decode()}
148
+ json.dump(body, open("req.json", "w"))
149
+ EOF
150
+ curl -s localhost:20000/predict -H 'Content-Type: application/json' -d @req.json
151
+ tt model stop changh95/GR00T-N1.5-3B-p150
152
+ ```
153
+
154
+ - `POST /predict`: `images` (`{"ego_view": <base64 PNG/JPEG>}` β€” the one GR1 camera; any size with sides in 64–4096 px, the server applies the checkpoint's own eval chain: 0.95 centre crop β†’ bilinear 224Γ—224 β†’ Eagle2.5 normalisation), `state` (`{group: [[floats]]}`: `left_arm` 7, `right_arm` 7, `left_hand` 6, `right_hand` 6 β€” raw joint values, one time step), `instruction` (task text, ≀ 102 BPE tokens); optional `embodiment` (`gr1`, the only layout in this image), `seed` (initial flow-matching noise; default 0 = the deployed policy's seed β†’ deterministic) or `noise` (an explicit `[16][32]` initial noise), `state_dtype` (`float64` default, as the GR1 dataset), `return_normalized` (also return the model-space chunk).
155
+ - `GET /health`, `GET /info` (the full input/output contract, weights/tree provenance, warm-up latency and fidelity), `GET /demo` (the shipped demo request and its fp32 reference actions).
156
+
157
+ ### Response
158
+
159
+ ```json
160
+ {"actions": {"left_arm": [[0.0069, 0.2107, 0.0483, -1.8136, -0.1264, -0.0548, 0.0147], "... 16 steps"],
161
+ "right_arm": [[-0.0177, -0.1731, 0.3702, -1.7429, 0.4968, -0.0567, 0.9849], "..."],
162
+ "left_hand": [[0.0081, 0.0155, 0.0033, 0.0064, 0.0169, 0.0438], "..."],
163
+ "right_hand": [[-1.4880, -1.4915, -1.4781, -1.4810, -2.9609, 2.9904], "..."]},
164
+ "action_horizon": 16, "action_keys": ["left_arm", "right_arm", "left_hand", "right_hand"], "action_dims": {"left_arm": 7, "right_arm": 7, "left_hand": 6, "right_hand": 6},
165
+ "normalized": false, "embodiment": "gr1", "embodiment_id": 24, "version": "n15", "model": "GR00T-N1.5-3B-p150", "layout": "gr1",
166
+ "images": {"ego_view": {"frames": 1, "received_hw": [256, 256]}}, "seq_len": 296, "prompt_tokens": 14, "state_dtype": "float64",
167
+ "noise_source": "client", "seed": null,
168
+ "timing_ms": {"decode": 1.5, "encode": 2.6, "device": 43.0, "decode_actions": 0.1, "total": 47.2}}
169
+ ```
170
+
171
+ - `actions` are **physical, un-normalised** joint targets (radians) in the GR1 arms-and-hands action space of the checkpoint's `experiment_cfg/metadata.json` statistics, 16 steps Γ— [7, 7, 6, 6] dims β€” what `Gr00tPolicy.get_action` returns for this checkpoint; the values above are abridged from the shipped demo request (`GET /demo`) with its golden initial noise. `seq_len` is the LLM prompt length the request produced (296 for the demo; padded to 384 on device). `timing_ms.device` covers input upload, the four trace replays and the action readback; `total` is the whole handler.
172
+
173
+ ### Demo
174
+
175
+ | Input (`media/demo_ego_view.png`, GR1 sim `robot_sim.PickNPlace` traj 0 / step 100, 256Γ—256) | Instruction | p150a actions vs the fp32 reference (`media/demo_actions.png`) |
176
+ |:---:|:---|:---:|
177
+ | ![](media/demo_ego_view.png) | `pick the pear from the counter and place it in the plate` | ![](media/demo_actions.png) |
178
+
179
+ ### Accuracy and speed
180
+
181
+ | Metric | Value |
182
+ |---|---:|
183
+ | Action chunk vs the fp32 reference (`Gr00tPolicy`, same observation, same initial noise) | PCC `left_arm` 0.99995 Β· `right_arm` 0.99997 Β· `right_hand` 0.99999 (gates 0.9999; max\|d\| 0.021 / 0.017 / 0.029 rad) Β· `left_hand` max\|d\| 0.018 rad ≀ 0.046 (PCC 0.943 reported only: the golden chunk is near-constant there, spread 0.1 rad β€” NVIDIA's own bf16 GPU path scores 0.952) Β· normalised chunk `action_pred_normalized` 0.99998 (max\|d\| 0.014 ≀ 0.027) |
184
+ | Intermediate taps vs the fp32 reference (untraced path from pixels, 57 gated taps + determinism row) | **58/58 rows pass**; tightest: `vit_block_first` 0.99994 (gate 0.9999), `backbone_features` 0.99586 (0.995), `vl_self_attention_out` 0.99119 (0.991), `vit_post_ln` 0.99506 (0.993) |
185
+ | Traced == untraced, determinism, stale state | traced `action_pred` / `backbone_features` / hoisted K,V bit-equal to the untraced run; 5 trace replays bit-identical; mutated inputs (`n_text` > L_max, other shapes) refused before any device write; A→B→A→B alternating observations on one captured model pass |
186
+ | Inference on p150a (port benchmark, warm, batch 1, one 224Β² image, LLM S 296β†’384, median of 50; `benchmarks/results/e2e_stage1_n15.json`) | **43.4 ms end-to-end** (p90 44.1) = host encode 1.5 + upload 3.8 + 4 traces 37.8 (vision 8.7 Β· LLM 8.7 Β· adapter 4.5 Β· denoise 16.0) + readback 0.1 + decode 0.2 |
187
+ | Served over HTTP by this image (`tt-model serve`, warm, batch 1, the demo request, 10 warm-ups + 50 timed; `scripts/bench_http.py`) | **42.4 ms device Β· 45.4 ms server-side** (medians; p90 43.2 / 49.2; min 41.7 / 44.5) = image decode 1.1 + host encode 1.6 + device 42.4 (upload, 4 trace replays, readback) + un-normalise 0.1; 47.6 ms client wall on the same host (p90 51.8). Under a heavily loaded host (two concurrent tt-metal builds, load average 16–110) the same loop read 44.3 / 44.5 ms device and 53.6 / 58.1 ms server-side medians with p90s of 55 / 91–97 ms β€” the host-side stages, not the device, absorb the contention. The served actions are bit-identical to the port's device test and to the host run |
188
+ | Same policy on an RTX 5090 (same host, NVIDIA's unmodified `Gr00tPolicy`, eager PyTorch bf16 autocast + flash-attn 2, batch 1) | `get_action` e2e **58.0 ms** (preprocessing 2.2, model 55.4 = backbone 20.8 + action head 34.6) β†’ the p150a is 1.34Γ— faster end-to-end (43.4 vs 58.0) and 1.33Γ— on the model (41.7 device vs 55.4); NVIDIA's N1.5 README quotes 47.9 ms on an H100 (backbone 23.2 + head 4 Γ— 6.2) |
189
+
190
+ ### Caveats
191
+
192
+ - **Stage-1 path.** Vision / LLM / adapter / denoise are TTNN ops captured in four Metal traces; DiT block matmul weights are bfp8_b, everything else bf16 (`mixed_dit`). The persistent-megakernel denoise (Stage 2) is at rung K1 β€” its weight-streaming kernel measures 464 GB/s bf16 / 414 GB/s bfp8 (90.6 % of DRAM peak) on the full DiT block set β€” and is **not** in this image.
193
+ - **One embodiment, one layout.** The image is built for GR1 arms-and-hands (`gr1`, embodiment slot 24, one `ego_view` camera, ≀ 102 text tokens β†’ LLM sequence padded to 384); other embodiments, cameras or longer instructions are refused with 400, nothing is truncated. Batch 1; requests are serialised on one lock.
194
+ - **Noise is seeded** (seed 0, as the deployed policy); pass `seed` or `noise` for another draw. The reference noise of the shipped demo is not seed-reproducible, so the smoke test sends it as `noise`. Outputs are the base checkpoint's actions for the GR1 sim data config β€” a real robot needs NVIDIA's post-training.
195
+ - Not an OpenAI-compatible API; `GET /v1/models` is a stub so the tt-model ready card does not 404.
196
+ - Validated on tt-metal main `668c2907575` (`v0.79.0-dev20260914-1`), single p150a only, with this exact image (`tt-model serve` β†’ smoke test β†’ 50-request benchmark β†’ `tt-model stop`, three boots). **First boot is cold**: the checkpoint is converted into the port's device layout under `~/.cache/tt-model/gr00t-n1.5-3b-p150/weights` (6.4 GB, 9 s) and the kernels of the four traces are JIT-compiled into `…/cache` (411 MB, ~87 s incl. trace capture) β€” `tt-model serve` reported READY after 1 min 43 s; the next boots reused both caches and were ready in 20 s (loaded host) and 9.7 s (quiet host). Every boot re-checks the warm-up actions against the shipped fp32 reference (PCC 0.999982 here) and refuses to serve below 0.999.
197
+ - GPU comparison: the RTX 5090 row is NVIDIA's unmodified `Gr00tPolicy` path (`Isaac-GR00T` `n1.5-release`, torch 2.7.1+cu128, transformers 4.51.3, flash-attn 2.8.3), best median of 3 Γ— (20 warm-ups + 20 timed calls), GPU idle, no `torch.compile` / TensorRT; the p150a rows are the Stage-1 trace path incl. upload and readback. p150a power was not measured, so no efficiency comparison is made. Full table: [`GPU_COMPARISON.md`](GPU_COMPARISON.md).
198
+
199
+ ### Licensing
200
+
201
+ - Weights: [nvidia/GR00T-N1.5-3B](https://huggingface.co/nvidia/GR00T-N1.5-3B), **NVIDIA License** β€” use limited to research or evaluation (non-commercial), redistribution only under the same licence (copy in [`LICENSE`](LICENSE)). Not redistributed here; `tt-model serve` fetches the pinned snapshot into your HF cache.
202
+ - Tokenizer files under `code/gr00t_p150/assets/tokenizer/n15/` (Qwen2 BPE + Eagle2.5 special tokens) are copied from [NVIDIA/Isaac-GR00T `n1.5-release`](https://github.com/NVIDIA/Isaac-GR00T/tree/n1.5-release) (Apache-2.0).
203
+ - Port and serving code (`code/models/experimental/gr00t`, `code/gr00t_p150`, `code/scripts`): Apache-2.0 (SPDX headers), Β© 2026 Tenstorrent; as a derivative work of the weights it is distributed here under the same non-commercial use limitation (NVIDIA License Β§3.2). tt-metal / tt-nn: Apache-2.0.
204
+ - Demo frame and state (`media/`): one step of the GR1 `robot_sim.PickNPlace` demo data shipped with Isaac-GR00T.