Add files using upload-large-folder tool
Browse files- README.md +24 -36
- code/models/experimental/gr00t/tests/tt/test_bfp8_emulation_cpu.py +617 -0
- code/models/experimental/gr00t/tests/tt/test_mk_e2e.py +591 -0
- code/models/experimental/gr00t/tests/tt/test_mk_head_sdpa.py +754 -0
- code/models/experimental/gr00t/tests/tt/test_mk_layernorm.py +784 -0
- code/models/experimental/gr00t/tests/tt/test_tt_action_head.py +34 -44
- code/models/experimental/gr00t/tests/tt/test_tt_backbone.py +9 -4
- code/models/experimental/gr00t/tests/tt/test_tt_determinism.py +36 -0
- code/models/experimental/gr00t/tests/tt/test_tt_dtype_policy.py +904 -0
- code/models/experimental/gr00t/tests/tt/test_tt_model_e2e.py +54 -7
- code/models/experimental/gr00t/tests/tt/test_tt_perf.py +269 -25
- code/models/experimental/gr00t/tests/tt/test_tt_stale_state.py +36 -0
- image/blobs/sha256/0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60 +1 -0
- image/blobs/sha256/0c65a290603e6797836c24eaf4dbad448eb486ddd37d5b22ec615fd70cb133f2 +1 -0
- image/blobs/sha256/1c590b200182d3f28659786c6b802d5c9fb63bef9a1aa09e5efdde2591a12c20 +1 -0
- image/blobs/sha256/252a9530fcab61e4872b019330842abd14e288837bb494eabf7d961c2c623112 +1 -0
- image/blobs/sha256/3c4361cee2aeb8ae93f4ef84a1a82ff11ad20abeea0aeb0c97e9bf3be6859591 +1 -0
- image/blobs/sha256/3f7cf805baca40c6075bef88421935d03e2e28cbdb906610cd51b7b761523c12 +1 -0
- image/blobs/sha256/5e8401978135bdfadc3dd329a54851b7fa7a06c24bb959b536e75966fea6b278 +1 -0
- image/blobs/sha256/7f8ab8adfddcbb515e20c539f42c507b2bd9f9ad8e27e87719e5487da6125f89 +1 -0
- image/blobs/sha256/8dbbd756d63b80f87f9c009aeb284a337a9457d21e97317066c14cdd8f21349f +1 -0
- image/blobs/sha256/8eedd596896beb613d02b359b843aadeb06cb5ec8effbdbdf697632d1f238328 +1 -0
- image/blobs/sha256/909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418 +0 -0
- image/blobs/sha256/91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4 +0 -0
- image/blobs/sha256/97a4a5f03aa47006f66965fb4a4f06010d695fc04d7683459bfdd729381c399d +1 -0
- image/blobs/sha256/be72b093968ed7fd244f0b1dff9dc05fd87d0031245c3a710ad2a3c0e55a772f +1 -0
- image/blobs/sha256/c601ad8dea9cf1cb5c826fd622d446767719a4bdcc005207d9883b0bae3ec2f2 +1 -0
- image/blobs/sha256/e219f70f6edabc2003d4bee846c0f04bec4f41d668285011fda98a2ae63bdec7 +1 -0
- image/blobs/sha256/e3c4bbf0e1a172c093407d72bb7f93bfe42b5da41e10fbb57164701db74b06af +1 -0
- image/blobs/sha256/f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5 +0 -0
- image/blobs/sha256/fb55b0f1dfab1bc8d7a83da0cfb21ce42baa0f8f3b85eaffab714c5fc4119cdc +1 -0
- image/index.json +1 -1
- image/manifest.json +1 -1
- image/repositories +1 -1
- requirements.lock +6 -6
- tt_kernel_manifest.json +17 -8
README.md
CHANGED
|
@@ -5,25 +5,11 @@ tags:
|
|
| 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.6-3B/blob/main/LICENSE
|
| 20 |
-
base_model:
|
| 21 |
-
- nvidia/GR00T-N1.6-3B
|
| 22 |
---
|
| 23 |
|
| 24 |
-
#
|
| 25 |
|
| 26 |
-
NVIDIA Isaac GR00T N1.6 (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, hand and waist actions out. Stage-
|
| 27 |
Weights: [nvidia/GR00T-N1.6-3B](https://huggingface.co/nvidia/GR00T-N1.6-3B) · Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) · Project page: [gr00t-n1_6](https://research.nvidia.com/labs/gear/gr00t-n1_6/) · Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (`n1.6.1-release`) · Port: `code/models/experimental/gr00t` and the server `code/gr00t_p150` in this repo
|
| 28 |
|
| 29 |
Runs on **p150** (mesh `P150`).
|
|
@@ -37,8 +23,7 @@ tt-model pull changh95/GR00T-N1.6-3B-p150 --with-weights
|
|
| 37 |
tt-model serve changh95/GR00T-N1.6-3B-p150
|
| 38 |
```
|
| 39 |
|
| 40 |
-
-
|
| 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 |
|
|
@@ -55,18 +40,18 @@ tt model stop changh95/GR00T-N1.6-3B-p150
|
|
| 55 |
```
|
| 56 |
|
| 57 |
- `POST /predict`: `images` (`{"ego_view_bg_crop_pad_res256_freq20": <base64 PNG/JPEG>}`), `state` (`{group: [floats]}`: `left_arm` 7, `right_arm` 7, `left_hand` 6, `right_hand` 6, `waist` 3, raw joint values), `instruction` (≤ 24 tokens); optional `seed` (default 42) or `noise` (`[50, 128]`), `return_normalized`.
|
| 58 |
-
- `GET /health`, `GET /info`, `GET /demo` (the shipped demo request and its golden actions).
|
| 59 |
|
| 60 |
### Response
|
| 61 |
|
| 62 |
```json
|
| 63 |
-
{"actions": {"left_arm": [[-0.0289, 0.1453, 0.0224, -2.1383, -0.0165, -0.
|
| 64 |
-
"right_arm": [[-0.0024, -0.
|
| 65 |
-
"left_hand": [[0.
|
| 66 |
-
"right_hand": [[-0.
|
| 67 |
-
"waist": [[
|
| 68 |
"action_horizon": 16, "embodiment": "gr1", "seq_len": 116, "seed": 42,
|
| 69 |
-
"timing_ms": {"decode": 0.
|
| 70 |
```
|
| 71 |
|
| 72 |
- `actions` are physical, un-normalised joint targets, 16 steps × [7, 7, 6, 6, 3] dims, as `Gr00tPolicy.get_action` returns them for the GR1 data config (arm and hand groups are relative outputs composed onto the request's `state`, `waist` is absolute); `timing_ms.device` covers upload, the trace replays and the readback.
|
|
@@ -81,21 +66,23 @@ tt model stop changh95/GR00T-N1.6-3B-p150
|
|
| 81 |
|
| 82 |
| Metric | Value |
|
| 83 |
|---|---:|
|
| 84 |
-
| Action chunk vs the fp32 reference (same observation, same initial noise) | PCC `left_arm` 0.99995 · `right_arm` 0.
|
| 85 |
-
| Intermediate taps vs the fp32 reference (
|
| 86 |
-
|
|
| 87 |
-
|
|
| 88 |
-
|
|
| 89 |
-
|
|
|
|
|
| 90 |
|
| 91 |
### Caveats
|
| 92 |
|
| 93 |
-
- Stage-
|
| 94 |
-
- Fidelity margin: the served chunk matches the fp32 reference at PCC 0.
|
|
|
|
| 95 |
- One embodiment and layout: GR1 (embodiment slot 20), one `ego_view_bg_crop_pad_res256_freq20` camera, instruction ≤ 24 tokens; anything else is refused with 400. Batch 1.
|
| 96 |
- Noise is seeded (seed 42, as the deployed policy); pass `seed` or `noise` for another draw. Outputs are the base checkpoint's actions for the GR1 sim data config.
|
| 97 |
- Not an OpenAI-compatible API; `GET /v1/models` is a stub.
|
| 98 |
-
- First boot is cold (weight conversion + kernel JIT,
|
| 99 |
|
| 100 |
### Licensing
|
| 101 |
|
|
@@ -110,5 +97,6 @@ The exact sources the image was built from — `code/` in this repo is byte-iden
|
|
| 110 |
| component | built from |
|
| 111 |
| --- | --- |
|
| 112 |
| tt-metal | [`668c290757550588d0ce46b180c344a462a2aaf5`](https://github.com/tenstorrent/tt-metal/commit/668c290757550588d0ce46b180c344a462a2aaf5) |
|
| 113 |
-
| `code/` digest | `
|
| 114 |
-
| built | 2026-09-
|
|
|
|
|
|
| 5 |
- tt-dit-server
|
| 6 |
- tt-model-cache
|
| 7 |
- tt-model-container
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
+
# gr00t-n1.6-3b-p150
|
| 11 |
|
| 12 |
+
NVIDIA Isaac GR00T N1.6 (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, hand and waist actions out. Stage-2 release: the whole DiT denoise (32 blocks × 4 flow-matching steps) runs as one persistent megakernel streaming bfp8 weights from a DRAM arena; vision, LLM and adapter are TTNN ops (bf16) replayed from Metal traces; `GR00T_DIT_BACKEND=ttnn` selects the Stage-1 denoise.
|
| 13 |
Weights: [nvidia/GR00T-N1.6-3B](https://huggingface.co/nvidia/GR00T-N1.6-3B) · Paper: [arXiv:2503.14734](https://arxiv.org/abs/2503.14734) · Project page: [gr00t-n1_6](https://research.nvidia.com/labs/gear/gr00t-n1_6/) · Upstream code: [NVIDIA/Isaac-GR00T](https://github.com/NVIDIA/Isaac-GR00T) (`n1.6.1-release`) · Port: `code/models/experimental/gr00t` and the server `code/gr00t_p150` in this repo
|
| 14 |
|
| 15 |
Runs on **p150** (mesh `P150`).
|
|
|
|
| 23 |
tt-model serve changh95/GR00T-N1.6-3B-p150
|
| 24 |
```
|
| 25 |
|
| 26 |
+
`pull --with-weights` downloads the Docker image and the [`nvidia/GR00T-N1.6-3B`](https://huggingface.co/nvidia/GR00T-N1.6-3B) weights at `d0814e7ecb19202e7c8468b46098b0b7ef3a6d61` (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`.
|
|
|
|
| 27 |
|
| 28 |
### Run with tt-cli
|
| 29 |
|
|
|
|
| 40 |
```
|
| 41 |
|
| 42 |
- `POST /predict`: `images` (`{"ego_view_bg_crop_pad_res256_freq20": <base64 PNG/JPEG>}`), `state` (`{group: [floats]}`: `left_arm` 7, `right_arm` 7, `left_hand` 6, `right_hand` 6, `waist` 3, raw joint values), `instruction` (≤ 24 tokens); optional `seed` (default 42) or `noise` (`[50, 128]`), `return_normalized`.
|
| 43 |
+
- `GET /health`, `GET /info` (`stage` names the denoise backend), `GET /demo` (the shipped demo request and its golden actions).
|
| 44 |
|
| 45 |
### Response
|
| 46 |
|
| 47 |
```json
|
| 48 |
+
{"actions": {"left_arm": [[-0.0289, 0.1453, 0.0224, -2.1383, -0.0165, -0.0225, -0.0428], "... 16 steps"],
|
| 49 |
+
"right_arm": [[-0.0024, -0.1512, 0.0269, -2.1461, -0.0091, 0.0124, 0.0044], "..."],
|
| 50 |
+
"left_hand": [[0.0135, -0.0092, -0.0047, 0.0070, 0.0455, 0.0281], "..."],
|
| 51 |
+
"right_hand": [[-0.2253, -0.2421, -0.2345, -0.2311, -0.2338, 0.3385], "..."],
|
| 52 |
+
"waist": [[0.0001, -0.0060, 0.0008], "..."]},
|
| 53 |
"action_horizon": 16, "embodiment": "gr1", "seq_len": 116, "seed": 42,
|
| 54 |
+
"timing_ms": {"decode": 0.9, "encode": 1.4, "device": 39.6, "decode_actions": 0.2, "total": 42.1}}
|
| 55 |
```
|
| 56 |
|
| 57 |
- `actions` are physical, un-normalised joint targets, 16 steps × [7, 7, 6, 6, 3] dims, as `Gr00tPolicy.get_action` returns them for the GR1 data config (arm and hand groups are relative outputs composed onto the request's `state`, `waist` is absolute); `timing_ms.device` covers upload, the trace replays and the readback.
|
|
|
|
| 66 |
|
| 67 |
| Metric | Value |
|
| 68 |
|---|---:|
|
| 69 |
+
| Action chunk vs the fp32 reference (same observation, same initial noise) | PCC `left_arm` 0.99995 · `right_arm` 0.99990 · `right_hand` 0.99308 · `left_hand` / `waist` max\|d\| 0.059 / 0.013 (near-constant groups) · all 29 action dims 0.99923 (normalised valid slice 0.99922) |
|
| 70 |
+
| Intermediate taps vs the fp32 reference (the 23 taps the megakernel path exposes, from pixels; DiT block internals are not observable inside the kernel) | 26 of 28 gated rows pass (29 of 31 rows incl. the determinism row and the two xfail info rows); 2 marginal misses on the DiT state-token row that no action reads (`action_decoder_out[k=1,2]` 0.999594 / 0.999598 vs gates 0.9997 / 0.99978), caused by bf16 error in the SigLIP2 tower; the Stage-1 card's `dit_out[k=1,2]` misses are now inside the kernel |
|
| 71 |
+
| Megakernel vs the Stage-1 TTNN denoise, same device | `action_pred_normalized` PCC ≥ 0.999994 on every N1.6 reference sample |
|
| 72 |
+
| Traced vs untraced, determinism | bit-identical (13 of 13 rows); 5 trace replays bit-identical; 2 CQs == 1 CQ |
|
| 73 |
+
| Inference on p150a (warm, batch 1, median of 50) | **40.7 ms end-to-end** (p90 40.8) = host encode 1.4 + upload 1.4 + traces 37.8 (vision 10.8 · LLM 9.6 · adapter 2.2 · denoise 15.3 = 3.84 ms per step) + readback and decode 0.1 |
|
| 74 |
+
| Served over HTTP by this image (warm, batch 1, 50 requests) | 39.6 ms device · 42.1 ms server-side incl. PNG decode (p90 42.5) |
|
| 75 |
+
| Same policy on an RTX 5090 (same host, NVIDIA's unmodified `Gr00tPolicy`, eager PyTorch bf16 + flash-attn 2) | 63.1 ms end-to-end → p150a 1.55× faster; with `torch.compile` on the DiT the GPU reaches 33.1 ms → GPU 1.23× faster than this Stage-2 path |
|
| 76 |
|
| 77 |
### Caveats
|
| 78 |
|
| 79 |
+
- Stage-2 path: the `denoise` trace is one persistent `generic_op` on all 110 Tensix cores (8 readers stream the 32-block weight set once per step from a 0.98 GB bfp8 DRAM arena to 96 compute cores; distributed LayerNorm, per-head flash SDPA, Euler update and decoder inside the kernel). Against the ttnn denoise on the same tree (e2e 56.2 ms, denoise 30.4) it saves 27.6 % / 49.6 %, short of the planned 30 % e2e; `GR00T_DIT_BACKEND=ttnn` selects that path.
|
| 80 |
+
- Fidelity margin: the served chunk matches the fp32 reference at PCC 0.99923 (physical actions; 0.99922 on the normalised valid slice, where NVIDIA's own bf16 GPU run scores 0.99956), 2.3e-4 above the 0.999 boot self-check.
|
| 81 |
+
- Costs of the megakernel: 64 KiB less allocatable L1 per core (the kernel-config ring is 136,192 B, the compute-core binaries take ≈ 131.7 KB of it), the VL adapter's intermediates in DRAM (+0.2 ms adapter trace), and no DiT-internal taps (10 head taps + the backbone taps remain gated).
|
| 82 |
- One embodiment and layout: GR1 (embodiment slot 20), one `ego_view_bg_crop_pad_res256_freq20` camera, instruction ≤ 24 tokens; anything else is refused with 400. Batch 1.
|
| 83 |
- Noise is seeded (seed 42, as the deployed policy); pass `seed` or `noise` for another draw. Outputs are the base checkpoint's actions for the GR1 sim data config.
|
| 84 |
- Not an OpenAI-compatible API; `GET /v1/models` is a stub.
|
| 85 |
+
- First boot is cold (weight conversion + kernel JIT incl. the megakernel, 58 s); later boots take 14 s. Validated on tt-metal `668c2907575`, single p150a only. Details: [`SERVING.md`](SERVING.md), [`GPU_COMPARISON.md`](GPU_COMPARISON.md).
|
| 86 |
|
| 87 |
### Licensing
|
| 88 |
|
|
|
|
| 97 |
| component | built from |
|
| 98 |
| --- | --- |
|
| 99 |
| tt-metal | [`668c290757550588d0ce46b180c344a462a2aaf5`](https://github.com/tenstorrent/tt-metal/commit/668c290757550588d0ce46b180c344a462a2aaf5) |
|
| 100 |
+
| `code/` digest | `8e6bd13a23654f91` (sha256, first 16 hex digits) |
|
| 101 |
+
| built | 2026-09-17T20:40:28+00:00 by tt-model 0.1.0 |
|
| 102 |
+
|
code/models/experimental/gr00t/tests/tt/test_bfp8_emulation_cpu.py
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""CPU emulation of ttnn's ``bfloat8_b`` / ``bfloat4_b`` weight rounding on the fp32 torch reference (WP-Q;
|
| 5 |
+
IMPLEMENTATION_PLAN.md §1.2 "After WP-Q admission", §7.2; golden-tap-map-and-thresholds.md §6 "emulation method").
|
| 6 |
+
|
| 7 |
+
No device, no ``ttnn`` import. Two parts:
|
| 8 |
+
|
| 9 |
+
* :func:`bfp_round` -- an exact torch re-implementation of tt-metal's host conversion
|
| 10 |
+
``tt_metal/impl/data_format/blockfloat_common.cpp`` (``get_max_exp`` over the 16 consecutive elements of a tile
|
| 11 |
+
face row -> shared 8-bit exponent; ``convert_u32_to_bfp<Bfp8_b|Bfp4_b, truncate=false>``: hidden bit made
|
| 12 |
+
explicit, mantissa shifted right by the exponent difference, round-to-nearest-even to 7 (bfp8) / 3 (bfp4) bits,
|
| 13 |
+
saturated at ``2^bits - 1``, zero / denormal input -> +0). In a ``[K, N]`` TILE tensor a face row is 16
|
| 14 |
+
consecutive elements along the last dim starting at column 0 (tile padding is zero and does not move the shared
|
| 15 |
+
exponent), so the emulation blocks the **last** dim; ``tests/tt/test_tt_dtype_policy.py`` checks it on the device
|
| 16 |
+
against ``ttnn.from_torch(x, bfloat8_b).to_torch()`` (1-mantissa-step tolerance, exactness recorded).
|
| 17 |
+
* The class emulation: a :class:`RoundingCheckpoint` (a ``LazyCheckpoint`` whose ``get`` rounds the HF weights of
|
| 18 |
+
one dtype class exactly the way the device plan lays them out -- head padding, fused projections, meta-RoPE row
|
| 19 |
+
permutation of the LLM q/k, tile-interleaved gate/up: :func:`class_round_specs` derives the key set and the block
|
| 20 |
+
orientation from ``common.weights.enumerate_plan``) feeds ``reference.model.Gr00tReference`` with the canonical
|
| 21 |
+
golden inputs; every golden tap the reference produces is gated by ``tests.tt.harness.check_taps`` (multi-sample
|
| 22 |
+
gates, valid slices, per-dim action PCC) and the class -> min PCC / worst margin table is written through the
|
| 23 |
+
``results`` fixture. Activations, softmax, GELU, LayerNorm and accumulation stay fp32 (the device's bf16
|
| 24 |
+
activations and HiFi2 operand truncation are **not** emulated -- the device sweep of ``test_tt_dtype_policy.py``
|
| 25 |
+
is the authority; a class that fails here is strong evidence against admission).
|
| 26 |
+
|
| 27 |
+
Run (CPU, ~1 min per (version, class))::
|
| 28 |
+
|
| 29 |
+
python -m pytest -q -rA -p no:cacheprovider models/experimental/gr00t/tests/tt/test_bfp8_emulation_cpu.py \\
|
| 30 |
+
--gr00t-version n15
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
from __future__ import annotations
|
| 34 |
+
|
| 35 |
+
import re
|
| 36 |
+
import time
|
| 37 |
+
from dataclasses import dataclass
|
| 38 |
+
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
| 39 |
+
|
| 40 |
+
import pytest
|
| 41 |
+
import torch
|
| 42 |
+
import torch.nn.functional as F
|
| 43 |
+
|
| 44 |
+
from models.experimental.gr00t.common import weights as W
|
| 45 |
+
from models.experimental.gr00t.common.checkpoint import LazyCheckpoint
|
| 46 |
+
from models.experimental.gr00t.common.configs import GR00T_VERSIONS, get_config
|
| 47 |
+
from models.experimental.gr00t.common.golden import GoldenSet
|
| 48 |
+
from models.experimental.gr00t.common.pcc import pcc
|
| 49 |
+
from models.experimental.gr00t.tests.tt import harness
|
| 50 |
+
from models.experimental.gr00t.tests.tt.conftest import skip_if_missing
|
| 51 |
+
from models.experimental.gr00t.tt.policy import TTPolicy, plan_options
|
| 52 |
+
|
| 53 |
+
torch.set_grad_enabled(False)
|
| 54 |
+
|
| 55 |
+
BLOCK = 16 # elements sharing one exponent (a tile face row; blockfloat_common.cpp get_max_exp asserts size 16)
|
| 56 |
+
MANTISSA_BITS: Dict[str, int] = {"bfp8_b": 7, "bfp4_b": 3, "bfp2_b": 1}
|
| 57 |
+
#: Sweep classes -> plan categories (plan §1.2 rows 3-5 + the DiT row; "projector" = N1.6 mlp1 + N1.7 mergers).
|
| 58 |
+
CLASS_CATEGORIES: Dict[str, Tuple[str, ...]] = {
|
| 59 |
+
"vision": ("vision",),
|
| 60 |
+
"projector": ("projector", "vision_merger"),
|
| 61 |
+
"llm": ("llm",),
|
| 62 |
+
"vl_self_attention": ("vl_self_attention",),
|
| 63 |
+
"dit": ("dit",),
|
| 64 |
+
}
|
| 65 |
+
#: Classes the plan expects to admit (§1.2 "After WP-Q admission"): the emulation must pass their gates.
|
| 66 |
+
EXPECTED_ADMIT: Tuple[str, ...] = ("vision", "projector", "llm", "vl_self_attention", "dit")
|
| 67 |
+
#: Emulation cases: name -> (classes, mantissa bits, expected to pass). ``all_bfp8`` = every class at once (the
|
| 68 |
+
#: plan's end state); ``dit_ff_bfp4`` = the plan §1.2 row-1 experiment (bfp4 on ff1/ff2 only; mb2 §B.8 FF-out
|
| 69 |
+
#: 0.9986 and golden-tap-map §6 say "probably not").
|
| 70 |
+
CASES: Dict[str, Tuple[Tuple[str, ...], int, bool]] = {
|
| 71 |
+
"vision": (("vision",), 7, True),
|
| 72 |
+
"projector": (("projector",), 7, True),
|
| 73 |
+
"llm": (("llm",), 7, True),
|
| 74 |
+
"vl_self_attention": (("vl_self_attention",), 7, True),
|
| 75 |
+
"dit": (("dit",), 7, True),
|
| 76 |
+
"all_bfp8": (EXPECTED_ADMIT, 7, True),
|
| 77 |
+
"dit_ff_bfp4": (("dit_ff",), 3, False),
|
| 78 |
+
}
|
| 79 |
+
_FF_KEY = re.compile(r"\.ff\.net\.(0\.proj|2)\.weight$")
|
| 80 |
+
_PERHEAD_OUT = re.compile(r"^per-head pad (\d+)->(\d+) \(out\)")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 84 |
+
# 1. The rounding primitive (blockfloat_common.cpp)
|
| 85 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 86 |
+
def _blocked(x: torch.Tensor, dim: int) -> Tuple[torch.Tensor, Tuple[int, ...], int, int]:
|
| 87 |
+
"""``x`` -> fp32 ``[-1, BLOCK]`` view along ``dim`` (zero padded to a multiple of BLOCK); returns the view, the
|
| 88 |
+
moved shape, the moved dim and the pad so :func:`_unblocked` can restore it."""
|
| 89 |
+
if not isinstance(x, torch.Tensor):
|
| 90 |
+
raise TypeError(f"expected a torch.Tensor, got {type(x).__name__}")
|
| 91 |
+
if x.ndim == 0:
|
| 92 |
+
raise ValueError("bfp rounding needs at least one dim")
|
| 93 |
+
d = dim % x.ndim
|
| 94 |
+
xm = x.detach().to(torch.float32).movedim(d, -1)
|
| 95 |
+
n = xm.shape[-1]
|
| 96 |
+
pad = (-n) % BLOCK
|
| 97 |
+
if pad:
|
| 98 |
+
xm = F.pad(xm, (0, pad))
|
| 99 |
+
return xm.contiguous().reshape(-1, BLOCK), tuple(xm.shape), d, pad
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _unblocked(blocks: torch.Tensor, moved_shape: Tuple[int, ...], d: int, pad: int) -> torch.Tensor:
|
| 103 |
+
out = blocks.reshape(moved_shape)
|
| 104 |
+
if pad:
|
| 105 |
+
out = out[..., : moved_shape[-1] - pad]
|
| 106 |
+
return out.movedim(-1, d).contiguous()
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _decompose(blocks: torch.Tensor, mantissa_bits: int) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 110 |
+
"""Per element of a ``[-1, BLOCK]`` fp32 view: the rounded ``mantissa_bits``-bit mantissa (hidden bit explicit),
|
| 111 |
+
the block's shared exponent (biased, as stored) and the sign -- ``convert_u32_to_bfp<_, false>`` step by step."""
|
| 112 |
+
if mantissa_bits not in MANTISSA_BITS.values():
|
| 113 |
+
raise ValueError(f"mantissa_bits must be one of {sorted(MANTISSA_BITS.values())}, got {mantissa_bits}")
|
| 114 |
+
bits = blocks.view(torch.int32)
|
| 115 |
+
exp = (bits >> 23) & 0xFF
|
| 116 |
+
sign = (bits >> 31) & 1
|
| 117 |
+
mant = bits & 0x7FFFFF
|
| 118 |
+
zero_or_denormal = exp == 0
|
| 119 |
+
shared = exp.amax(dim=-1, keepdim=True) # get_max_exp over the 16 elements (is_exp_a = false for *_b)
|
| 120 |
+
mant_full = mant | (1 << 23) # hidden one
|
| 121 |
+
shift = (shared - exp).clamp(min=0, max=31)
|
| 122 |
+
m = mant_full >> shift # shift further down by the exponent difference
|
| 123 |
+
shift_bits = 24 - mantissa_bits # MANTISSA_BFP_SHIFT
|
| 124 |
+
round_mask = (1 << shift_bits) - 1
|
| 125 |
+
tie = 1 << (shift_bits - 1)
|
| 126 |
+
round_value = m & round_mask
|
| 127 |
+
m = m >> shift_bits
|
| 128 |
+
guard = m & 1
|
| 129 |
+
round_up = (round_value > tie) | ((round_value == tie) & (guard == 1))
|
| 130 |
+
m = m + round_up.to(torch.int32)
|
| 131 |
+
m = torch.clamp(m, max=(1 << mantissa_bits) - 1) # saturate (no carry into the exponent)
|
| 132 |
+
m = torch.where(zero_or_denormal, torch.zeros_like(m), m)
|
| 133 |
+
return m, shared.expand_as(m), sign
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _compose(m: torch.Tensor, shared: torch.Tensor, sign: torch.Tensor, mantissa_bits: int) -> torch.Tensor:
|
| 137 |
+
"""``convert_bfp_to_u32``: ``m x 2^(shared - 127 - (mantissa_bits - 1))`` with the sign (``+0`` for m == 0)."""
|
| 138 |
+
scale = torch.pow(2.0, (shared - 127 - (mantissa_bits - 1)).to(torch.float64))
|
| 139 |
+
val = (m.to(torch.float64) * scale).to(torch.float32)
|
| 140 |
+
neg = (sign == 1) & (m != 0)
|
| 141 |
+
return torch.where(neg, -val, val)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def bfp_round(t: torch.Tensor, mantissa_bits: int = 7, *, dim: int = -1) -> torch.Tensor:
|
| 145 |
+
"""Round ``t`` the way ``ttnn.from_torch(t, dtype=bfloat8_b | bfloat4_b, layout=TILE)`` stores it: blocks of 16
|
| 146 |
+
consecutive elements along ``dim`` (the last dim of the device tensor) share the maximum exponent, mantissas are
|
| 147 |
+
round-to-nearest-even to ``mantissa_bits`` (7 = bfp8_b, 3 = bfp4_b). Returns fp32 in ``t``'s shape."""
|
| 148 |
+
blocks, moved_shape, d, pad = _blocked(t, dim)
|
| 149 |
+
m, shared, sign = _decompose(blocks, mantissa_bits)
|
| 150 |
+
return _unblocked(_compose(m, shared, sign, mantissa_bits), moved_shape, d, pad)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def bfp8_round(t: torch.Tensor, *, dim: int = -1) -> torch.Tensor:
|
| 154 |
+
"""ttnn ``bfloat8_b`` rounding (7-bit mantissa, 16-element shared exponent) along ``dim`` (default: last)."""
|
| 155 |
+
return bfp_round(t, 7, dim=dim)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def bfp4_round(t: torch.Tensor, *, dim: int = -1) -> torch.Tensor:
|
| 159 |
+
"""ttnn ``bfloat4_b`` rounding (3-bit mantissa, 16-element shared exponent) along ``dim`` (default: last)."""
|
| 160 |
+
return bfp_round(t, 3, dim=dim)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def mantissa_step(t: torch.Tensor, mantissa_bits: int = 7, *, dim: int = -1) -> torch.Tensor:
|
| 164 |
+
"""One mantissa step of every element's block: ``2^(shared_exp - 127 - (mantissa_bits - 1))`` (fp32, ``t``'s shape);
|
| 165 |
+
the tolerance of the device cross-check."""
|
| 166 |
+
blocks, moved_shape, d, pad = _blocked(t, dim)
|
| 167 |
+
_, shared, _ = _decompose(blocks, mantissa_bits)
|
| 168 |
+
step = torch.pow(2.0, (shared - 127 - (mantissa_bits - 1)).to(torch.float64)).to(torch.float32)
|
| 169 |
+
return _unblocked(step, moved_shape, d, pad)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def round_linear_weight(
|
| 173 |
+
w_out_in: torch.Tensor,
|
| 174 |
+
mantissa_bits: int = 7,
|
| 175 |
+
*,
|
| 176 |
+
head_dim: Optional[int] = None,
|
| 177 |
+
row_perm: Optional[torch.Tensor] = None,
|
| 178 |
+
) -> torch.Tensor:
|
| 179 |
+
"""Round an HF ``nn.Linear`` weight ``[out, in]`` exactly as its device plan tensor ``[in, out]`` (WL §4.1 "T")
|
| 180 |
+
is stored: the 16-element blocks run along **out** (dim 0).
|
| 181 |
+
|
| 182 |
+
* ``head_dim``: the plan zero-pads every head of a Q/K/V projection on the out axis (``pad_heads_out``: SigLIP
|
| 183 |
+
72 -> 96, DiT 48 -> 64) **before** transposing, so head ``h`` occupies its own padded column range and the
|
| 184 |
+
last block of a 72-wide head holds 8 real values + 8 zeros -- blocking ``[nh, head_dim, in]`` along dim 1 with
|
| 185 |
+
zero padding reproduces that exactly (zeros never move the shared exponent).
|
| 186 |
+
* ``row_perm``: the plan permutes the out rows of the LLM q/k projections into the Meta RoPE layout
|
| 187 |
+
(``reverse_permute``) before transposing; rounding on the permuted rows and un-permuting reproduces the device
|
| 188 |
+
blocks.
|
| 189 |
+
"""
|
| 190 |
+
if w_out_in.ndim != 2:
|
| 191 |
+
raise ValueError(f"expected a 2-D [out, in] weight, got {tuple(w_out_in.shape)}")
|
| 192 |
+
w = w_out_in.to(torch.float32)
|
| 193 |
+
if row_perm is not None:
|
| 194 |
+
if head_dim is not None:
|
| 195 |
+
raise ValueError("row_perm and head_dim are exclusive")
|
| 196 |
+
if row_perm.shape != (w.shape[0],):
|
| 197 |
+
raise ValueError(f"row_perm {tuple(row_perm.shape)} does not index out={w.shape[0]}")
|
| 198 |
+
rounded_p = bfp_round(w[row_perm], mantissa_bits, dim=0)
|
| 199 |
+
out = torch.empty_like(w)
|
| 200 |
+
out[row_perm] = rounded_p
|
| 201 |
+
return out
|
| 202 |
+
if head_dim is not None:
|
| 203 |
+
n_out, n_in = w.shape
|
| 204 |
+
if n_out % head_dim:
|
| 205 |
+
raise ValueError(f"out={n_out} is not a multiple of head_dim={head_dim}")
|
| 206 |
+
per_head = bfp_round(w.reshape(n_out // head_dim, head_dim, n_in), mantissa_bits, dim=1)
|
| 207 |
+
return per_head.reshape(n_out, n_in)
|
| 208 |
+
return bfp_round(w, mantissa_bits, dim=0)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def rope_row_permutation(n_out: int, n_heads: int) -> torch.Tensor:
|
| 212 |
+
"""``perm`` with ``reverse_permute(w, n_heads) == w[perm]`` (rows of a ``[out, in]`` q/k projection)."""
|
| 213 |
+
idx = torch.arange(n_out, dtype=torch.float64).reshape(n_out, 1)
|
| 214 |
+
return W.reverse_permute(idx, n_heads).reshape(n_out).to(torch.long)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 218 |
+
# 2. Class -> HF keys with their device block orientation
|
| 219 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 220 |
+
@dataclass(frozen=True)
|
| 221 |
+
class RoundSpec:
|
| 222 |
+
"""How one HF weight key is rounded: mantissa bits, per-head out blocking, meta-RoPE row permutation."""
|
| 223 |
+
|
| 224 |
+
key: str
|
| 225 |
+
plan_name: str
|
| 226 |
+
category: str
|
| 227 |
+
mantissa_bits: int
|
| 228 |
+
head_dim: Optional[int] = None
|
| 229 |
+
rope_heads: Optional[int] = None # row_perm = rope_row_permutation(out, rope_heads)
|
| 230 |
+
|
| 231 |
+
def apply(self, w: torch.Tensor) -> torch.Tensor:
|
| 232 |
+
perm = None if self.rope_heads is None else rope_row_permutation(w.shape[0], self.rope_heads)
|
| 233 |
+
return round_linear_weight(w, self.mantissa_bits, head_dim=self.head_dim, row_perm=perm)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def class_round_specs(version: str, classes: Tuple[str, ...], mantissa_bits: int = 7) -> Dict[str, RoundSpec]:
|
| 237 |
+
"""``HF key -> RoundSpec`` for every bfp8 candidate of the plan categories behind ``classes`` (the canonical
|
| 238 |
+
embodiment's ``mixed`` plan under the default ``TTPolicy`` transform options, exactly what ``TTWeights`` uploads).
|
| 239 |
+
|
| 240 |
+
``"dit_ff"`` is the FF-only subset of ``dit`` (``ff.net.0.proj`` / ``ff.net.2``) for the bfp4 experiment.
|
| 241 |
+
Raises on a class without candidates in this version only when the class is not a known no-op (N1.5 projector
|
| 242 |
+
``fc1`` is ``big=False`` by plan §1.2; N1.6 has no VL-SA blocks).
|
| 243 |
+
"""
|
| 244 |
+
if version not in GR00T_VERSIONS:
|
| 245 |
+
raise KeyError(f"unknown version {version!r}")
|
| 246 |
+
cfg = get_config(version)
|
| 247 |
+
cats: List[str] = []
|
| 248 |
+
ff_only = False
|
| 249 |
+
for c in classes:
|
| 250 |
+
if c == "dit_ff":
|
| 251 |
+
cats.append("dit")
|
| 252 |
+
ff_only = True
|
| 253 |
+
elif c in CLASS_CATEGORIES:
|
| 254 |
+
cats.extend(CLASS_CATEGORIES[c])
|
| 255 |
+
else:
|
| 256 |
+
raise KeyError(f"unknown class {c!r}; expected one of {sorted(CLASS_CATEGORIES)} or 'dit_ff'")
|
| 257 |
+
meta = W.enumerate_plan(version, None, "mixed", plan_options(TTPolicy(), version))
|
| 258 |
+
specs: Dict[str, RoundSpec] = {}
|
| 259 |
+
for name, entry in sorted(meta.items()):
|
| 260 |
+
if entry.category not in cats or not entry.bfp8_candidate:
|
| 261 |
+
continue
|
| 262 |
+
m = _PERHEAD_OUT.match(entry.transform)
|
| 263 |
+
head_dim = int(m.group(1)) if m else None
|
| 264 |
+
meta_rope = "meta rope rows" in entry.transform
|
| 265 |
+
for key in entry.source_keys:
|
| 266 |
+
if ff_only and not _FF_KEY.search(key):
|
| 267 |
+
continue
|
| 268 |
+
if key in specs:
|
| 269 |
+
raise KeyError(f"{key} feeds two plan tensors ({specs[key].plan_name}, {name})")
|
| 270 |
+
rope_heads: Optional[int] = None
|
| 271 |
+
if meta_rope:
|
| 272 |
+
if key.endswith("q_proj.weight"):
|
| 273 |
+
rope_heads = int(cfg.llm.n_heads)
|
| 274 |
+
elif key.endswith("k_proj.weight"):
|
| 275 |
+
rope_heads = int(cfg.llm.n_kv_heads)
|
| 276 |
+
elif not key.endswith("v_proj.weight"):
|
| 277 |
+
raise KeyError(f"{name}: unexpected meta-rope source key {key}")
|
| 278 |
+
specs[key] = RoundSpec(key, name, entry.category, mantissa_bits, head_dim, rope_heads)
|
| 279 |
+
return specs
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class RoundingCheckpoint(LazyCheckpoint):
|
| 283 |
+
"""``LazyCheckpoint`` whose ``get`` returns the weights of ``specs`` rounded (golden-tap-map §6 method, applied
|
| 284 |
+
through the reference's own weight reads so no module is re-implemented). ``hits`` counts the reads per key so
|
| 285 |
+
the caller can assert that every candidate of the class was actually consumed."""
|
| 286 |
+
|
| 287 |
+
def __init__(self, version: str, specs: Mapping[str, RoundSpec]):
|
| 288 |
+
super().__init__(version)
|
| 289 |
+
self.specs: Dict[str, RoundSpec] = dict(specs)
|
| 290 |
+
self.hits: Dict[str, int] = {}
|
| 291 |
+
self.round_s = 0.0
|
| 292 |
+
missing = [k for k in self.specs if k not in self]
|
| 293 |
+
if missing:
|
| 294 |
+
raise KeyError(f"{version}: {len(missing)} spec keys are not in the checkpoint, e.g. {missing[:3]}")
|
| 295 |
+
|
| 296 |
+
def get(self, key: str, dtype: Optional[torch.dtype] = None) -> torch.Tensor:
|
| 297 |
+
t = super().get(key, dtype)
|
| 298 |
+
spec = self.specs.get(key)
|
| 299 |
+
if spec is None:
|
| 300 |
+
return t
|
| 301 |
+
t0 = time.perf_counter()
|
| 302 |
+
r = spec.apply(t.to(torch.float32))
|
| 303 |
+
self.round_s += time.perf_counter() - t0
|
| 304 |
+
self.hits[key] = self.hits.get(key, 0) + 1
|
| 305 |
+
return r.to(t.dtype)
|
| 306 |
+
|
| 307 |
+
def slot(self, key: str, e: int, dtype: Optional[torch.dtype] = None) -> torch.Tensor:
|
| 308 |
+
if key in self.specs:
|
| 309 |
+
raise NotImplementedError(f"{key}: slot reads of a rounded key are not part of the emulation")
|
| 310 |
+
return super().slot(key, e, dtype)
|
| 311 |
+
|
| 312 |
+
def rows(self, key: str, lo: int, hi: int, dtype: Optional[torch.dtype] = None) -> torch.Tensor:
|
| 313 |
+
if key in self.specs:
|
| 314 |
+
raise NotImplementedError(f"{key}: row reads of a rounded key are not part of the emulation")
|
| 315 |
+
return super().rows(key, lo, hi, dtype)
|
| 316 |
+
|
| 317 |
+
def unread(self) -> List[str]:
|
| 318 |
+
return sorted(k for k in self.specs if k not in self.hits)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 322 |
+
# 3. Reference forward -> golden-keyed candidates
|
| 323 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 324 |
+
def reference_candidates(
|
| 325 |
+
out: Any, gs: GoldenSet, ref: Any, obs: Any, exclude: Tuple[str, ...]
|
| 326 |
+
) -> Dict[str, torch.Tensor]:
|
| 327 |
+
"""Every golden tap instance of ``gs`` the reference produced (``reference.model.reference_tap_tensor``), keyed by
|
| 328 |
+
``GoldenSet.threshold_key`` in the golden's shape, plus ``unnormalized_actions.<group>`` through ``ref.decode``;
|
| 329 |
+
taps in ``exclude`` (host inputs / reference-only) are skipped."""
|
| 330 |
+
from models.experimental.gr00t.reference.model import reconcile_shape, reference_tap_tensor
|
| 331 |
+
|
| 332 |
+
cand: Dict[str, torch.Tensor] = {}
|
| 333 |
+
for tap, kw in gs.available("fp32"):
|
| 334 |
+
if tap in exclude or tap == "unnormalized_actions":
|
| 335 |
+
continue
|
| 336 |
+
t = reference_tap_tensor(out, gs, tap, kw)
|
| 337 |
+
if t is None:
|
| 338 |
+
continue
|
| 339 |
+
cand[gs.threshold_key(tap, **kw)] = reconcile_shape(t.detach().to(torch.float32), gs.load(tap, **kw))
|
| 340 |
+
for group, t in ref.decode(out.action_pred_normalized, obs).items():
|
| 341 |
+
cand[f"unnormalized_actions.{group}"] = t.detach().to(torch.float32)
|
| 342 |
+
return cand
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
def run_reference(
|
| 346 |
+
version: str, gs: GoldenSet, obs: Any, mi: Any, ck: Optional[LazyCheckpoint]
|
| 347 |
+
) -> Tuple[Dict[str, torch.Tensor], Dict[str, float]]:
|
| 348 |
+
"""Build the reference on ``ck`` (None = unrounded fp32), run it on the canonical inputs with the golden noise
|
| 349 |
+
and return the golden-keyed candidates and the timings."""
|
| 350 |
+
from models.experimental.gr00t.reference.model import Gr00tReference
|
| 351 |
+
from models.experimental.gr00t.tt.model import HOST_OR_UNPRODUCED_TAPS
|
| 352 |
+
|
| 353 |
+
t0 = time.perf_counter()
|
| 354 |
+
ref = Gr00tReference(version, checkpoint=ck)
|
| 355 |
+
t1 = time.perf_counter()
|
| 356 |
+
out = ref.forward_from_inputs(mi, gs.load("initial_noise"), return_taps=True)
|
| 357 |
+
t2 = time.perf_counter()
|
| 358 |
+
cand = reference_candidates(out, gs, ref, obs, HOST_OR_UNPRODUCED_TAPS)
|
| 359 |
+
timing = {"build_s": t1 - t0, "forward_s": t2 - t1, **{k + "_s": v for k, v in out.timing.items()}}
|
| 360 |
+
return cand, timing
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
_BASELINE: Dict[str, Tuple[Dict[str, torch.Tensor], harness.Report]] = {}
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def baseline(version: str, gs: GoldenSet, obs: Any, mi: Any) -> Tuple[Dict[str, torch.Tensor], harness.Report]:
|
| 367 |
+
"""The unrounded fp32 reference's candidates and report (computed once per version, cached in the module)."""
|
| 368 |
+
if version not in _BASELINE:
|
| 369 |
+
cand, _ = run_reference(version, gs, obs, mi, None)
|
| 370 |
+
report = harness.check_taps(version, cand, gs, multi=True)
|
| 371 |
+
_BASELINE[version] = (cand, report)
|
| 372 |
+
return _BASELINE[version]
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def pcc_table(report: harness.Report) -> Dict[str, float]:
|
| 376 |
+
return {r["key"]: float(r["pcc"]) for r in report.rows if r.get("pcc") is not None}
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 380 |
+
# 4. Unit tests of the primitive (no checkpoint needed)
|
| 381 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 382 |
+
def test_cpu_bfp_round_hand_values() -> None:
|
| 383 |
+
"""Hand-checked blocks: the shared exponent is the block max; small values lose mantissa bits; RNE ties."""
|
| 384 |
+
# one block: max 1.0 (exp 127); 7-bit mantissa -> resolution 2^-6 = 0.015625 for every element of the block
|
| 385 |
+
x = torch.zeros(16)
|
| 386 |
+
x[0] = 1.0
|
| 387 |
+
x[1] = 0.5 + 0.015625 / 2 # 0.5078125: exactly between 0.5 and 0.515625 -> tie -> even (0.5 = mantissa 32)
|
| 388 |
+
x[2] = 0.5 + 0.015625 * 1.5 # -> rounds up to 0.53125 (mantissa 34, tie -> even)
|
| 389 |
+
x[3] = 0.001 # < one step -> 0
|
| 390 |
+
x[4] = -1.0
|
| 391 |
+
x[5] = 1.0 + 0.015625 * 0.75 # rounds up to 1.015625
|
| 392 |
+
r = bfp8_round(x)
|
| 393 |
+
assert r[0] == 1.0 and r[4] == -1.0
|
| 394 |
+
assert r[1] == 0.5, r[1]
|
| 395 |
+
assert r[2] == 0.53125, r[2]
|
| 396 |
+
assert r[3] == 0.0 and torch.signbit(r[3]).item() is False
|
| 397 |
+
assert r[5] == 1.015625, r[5]
|
| 398 |
+
# saturation: the max element with an all-ones mantissa that would round up stays at 127/64
|
| 399 |
+
y = torch.zeros(16)
|
| 400 |
+
y[0] = 1.0 + 63 / 64 + 0.9 / 64
|
| 401 |
+
assert bfp8_round(y)[0] == 1.0 + 63 / 64
|
| 402 |
+
# bfp4: 3-bit mantissa -> resolution 2^-2 of the block max
|
| 403 |
+
z = torch.zeros(16)
|
| 404 |
+
z[0], z[1], z[2] = 1.0, 0.6, 0.1
|
| 405 |
+
r4 = bfp4_round(z)
|
| 406 |
+
assert r4[0] == 1.0 and r4[1] == 0.5 and r4[2] == 0.0, r4[:3]
|
| 407 |
+
# 1 mantissa step
|
| 408 |
+
assert torch.equal(mantissa_step(x)[:16], torch.full((16,), 2.0**-6))
|
| 409 |
+
assert torch.equal(mantissa_step(z, 3)[:16], torch.full((16,), 2.0**-2))
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def test_cpu_bfp_round_properties() -> None:
|
| 413 |
+
"""Idempotence, block independence (dim / padding), the golden-tap-map §6 self-test magnitude (N(0,3) rel-L2
|
| 414 |
+
6.4e-3 for bfp8, 0.10 for bfp4; bf16 1.7e-3) and exactness on values already on the bfp8 grid."""
|
| 415 |
+
g = torch.Generator().manual_seed(0)
|
| 416 |
+
x = torch.randn(64, 200, generator=g) * 3.0
|
| 417 |
+
r = bfp8_round(x)
|
| 418 |
+
assert torch.equal(bfp8_round(r), r), "not idempotent"
|
| 419 |
+
rel = float((r - x).norm() / x.norm())
|
| 420 |
+
assert 5e-3 < rel < 8e-3, rel # §6: 6.4e-3
|
| 421 |
+
rel4 = float((bfp4_round(x) - x).norm() / x.norm())
|
| 422 |
+
assert 0.08 < rel4 < 0.13, rel4 # §6: 0.10
|
| 423 |
+
rel_bf16 = float((x.bfloat16().float() - x).norm() / x.norm())
|
| 424 |
+
assert 1.4e-3 < rel_bf16 < 2.0e-3, rel_bf16
|
| 425 |
+
# error never exceeds half a mantissa step of the block, except the saturated block maxima (mantissa 127
|
| 426 |
+
# cannot carry into the exponent: convert_u32_to_bfp clamps) which may sit one full step low
|
| 427 |
+
err = (r - x).abs()
|
| 428 |
+
step = mantissa_step(x)
|
| 429 |
+
assert bool((err <= step + 1e-12).all())
|
| 430 |
+
assert float((err > step / 2 + 1e-12).float().mean()) < 0.01
|
| 431 |
+
# blocks are along the chosen dim only: rounding the transpose along dim 0 equals rounding along dim 1
|
| 432 |
+
assert torch.equal(bfp8_round(x.t().contiguous(), dim=0), bfp8_round(x).t())
|
| 433 |
+
# ragged last block (200 = 12*16 + 8) equals explicit zero padding to 208
|
| 434 |
+
xp = F.pad(x, (0, 8))
|
| 435 |
+
assert torch.equal(bfp8_round(xp)[:, :200], r)
|
| 436 |
+
# a 4-D tensor blocks its last dim
|
| 437 |
+
x4 = torch.randn(1, 32, 100, 64, generator=g)
|
| 438 |
+
assert torch.equal(bfp8_round(x4), bfp8_round(x4.reshape(-1, 64)).reshape(x4.shape))
|
| 439 |
+
# values with <= 7 significant bits and a common exponent are exact (all elements in [1, 2) on the 1/64 grid)
|
| 440 |
+
y = (torch.randint(64, 128, (16, 16), generator=g).to(torch.float32)) / 64.0
|
| 441 |
+
assert torch.equal(bfp8_round(y), y)
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def test_cpu_round_linear_weight_layouts() -> None:
|
| 445 |
+
"""The head-padded / row-permuted paths equal rounding of the plan's own transformed tensor along its last dim."""
|
| 446 |
+
g = torch.Generator().manual_seed(1)
|
| 447 |
+
nh, hd, hdp, n_in = 4, 72, 96, 40
|
| 448 |
+
w = torch.randn(nh * hd, n_in, generator=g)
|
| 449 |
+
dev = W.to_in_out(W.pad_heads_out(w, nh, hd, hdp)) # [in, nh*hdp]: what TTWeights uploads
|
| 450 |
+
dev_r = bfp8_round(dev) # device blocks along the last dim
|
| 451 |
+
back = dev_r.t().reshape(nh, hdp, n_in)[:, :hd].reshape(nh * hd, n_in)
|
| 452 |
+
assert torch.equal(round_linear_weight(w, head_dim=hd), back)
|
| 453 |
+
assert not torch.equal(round_linear_weight(w), back), "72-wide heads straddle 16-blocks: plain blocking differs"
|
| 454 |
+
# meta-RoPE rows (LLM q/k): plan = to_in_out(reverse_permute(w)) -> blocks along out of the permuted rows
|
| 455 |
+
wq = torch.randn(8 * 128, 64, generator=g)
|
| 456 |
+
perm = rope_row_permutation(wq.shape[0], 8)
|
| 457 |
+
assert torch.equal(wq[perm], W.reverse_permute(wq, 8))
|
| 458 |
+
dev_q = bfp8_round(W.to_in_out(W.reverse_permute(wq, 8)))
|
| 459 |
+
back_q = torch.empty_like(wq)
|
| 460 |
+
back_q[perm] = dev_q.t()
|
| 461 |
+
assert torch.equal(round_linear_weight(wq, row_perm=perm), back_q)
|
| 462 |
+
# tile-interleaved gate/up: each 32-column tile belongs to one matrix -> plain per-matrix blocking is exact
|
| 463 |
+
wg, wu = torch.randn(96, 48, generator=g), torch.randn(96, 48, generator=g)
|
| 464 |
+
fused = bfp8_round(W.fuse_gate_up(wg, wu, "tile_interleaved"))
|
| 465 |
+
plain = bfp8_round(W.fuse_gate_up(round_linear_weight(wg), round_linear_weight(wu), "tile_interleaved"))
|
| 466 |
+
assert torch.equal(fused, plain)
|
| 467 |
+
# fused projections concatenate along out: per-matrix blocking is exact when out % 16 == 0
|
| 468 |
+
fq = bfp8_round(W.fuse_projections([wg, wu])[0])
|
| 469 |
+
assert torch.equal(fq, W.fuse_projections([round_linear_weight(wg), round_linear_weight(wu)])[0])
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
@pytest.mark.parametrize("version", list(GR00T_VERSIONS))
|
| 473 |
+
def test_cpu_class_round_specs_cover_candidates(version: str) -> None:
|
| 474 |
+
"""Every bfp8 candidate of every class maps to HF keys with the right orientation; no key is claimed twice; the
|
| 475 |
+
known no-ops (N1.5 projector, N1.6 VL-SA) are empty; the meta-RoPE keys carry the head counts."""
|
| 476 |
+
cfg = get_config(version)
|
| 477 |
+
meta = W.enumerate_plan(version, None, "mixed", plan_options(TTPolicy(), version))
|
| 478 |
+
all_specs: Dict[str, RoundSpec] = {}
|
| 479 |
+
for cls in CLASS_CATEGORIES:
|
| 480 |
+
specs = class_round_specs(version, (cls,))
|
| 481 |
+
cands = [e for e in meta.values() if e.category in CLASS_CATEGORIES[cls] and e.bfp8_candidate]
|
| 482 |
+
assert {s.plan_name for s in specs.values()} == {e.name for e in cands}, cls
|
| 483 |
+
assert not (set(specs) & set(all_specs)), cls
|
| 484 |
+
all_specs.update(specs)
|
| 485 |
+
if cls == "projector" and version == "n15":
|
| 486 |
+
assert specs == {}, "N1.5 projector.fc1 is big=False (plan §1.2 row 2)"
|
| 487 |
+
if cls == "vl_self_attention" and version == "n16":
|
| 488 |
+
assert specs == {}, "N1.6 has no VL-SA blocks"
|
| 489 |
+
union = class_round_specs(version, EXPECTED_ADMIT)
|
| 490 |
+
assert set(union) == set(all_specs)
|
| 491 |
+
for spec in union.values():
|
| 492 |
+
if spec.rope_heads is not None:
|
| 493 |
+
assert spec.category == "llm" and spec.rope_heads in (cfg.llm.n_heads, cfg.llm.n_kv_heads)
|
| 494 |
+
if spec.head_dim is not None:
|
| 495 |
+
assert spec.category in ("vision", "dit")
|
| 496 |
+
assert spec.head_dim == (cfg.vision.head_dim if spec.category == "vision" else cfg.dit.head_dim)
|
| 497 |
+
if cfg.vision.family in ("siglip_v1", "siglip2_naflex"):
|
| 498 |
+
assert any(s.head_dim == 72 for s in union.values() if s.category == "vision")
|
| 499 |
+
ff = class_round_specs(version, ("dit_ff",), 3)
|
| 500 |
+
assert ff and all(_FF_KEY.search(k) and s.mantissa_bits == 3 for k, s in ff.items())
|
| 501 |
+
assert len(ff) == 2 * cfg.dit.n_blocks
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 505 |
+
# 5. The emulation on the goldens (checkpoint + golden required; skipped otherwise)
|
| 506 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 507 |
+
@pytest.fixture(scope="module")
|
| 508 |
+
def canonical(version: str) -> Tuple[GoldenSet, Any, Any]:
|
| 509 |
+
skip_if_missing(version)
|
| 510 |
+
from models.experimental.gr00t.common.preprocessing import encode
|
| 511 |
+
from models.experimental.gr00t.reference.model import load_golden_sample
|
| 512 |
+
|
| 513 |
+
gs, obs = load_golden_sample(version)
|
| 514 |
+
return gs, obs, encode(version, obs)
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
def test_cpu_emulation_baseline_is_fp32_exact(
|
| 518 |
+
version: str, canonical: Tuple[GoldenSet, Any, Any], results: Any
|
| 519 |
+
) -> None:
|
| 520 |
+
"""The unrounded reference passes every gate (foundation-f4: fp32-exact) -- the anchor of the class deltas."""
|
| 521 |
+
gs, obs, mi = canonical
|
| 522 |
+
cand, report = baseline(version, gs, obs, mi)
|
| 523 |
+
results.add_report(report)
|
| 524 |
+
results.set_meta(case="baseline_fp32", n_rows=len(report.rows), min_pcc=report.min_pcc())
|
| 525 |
+
print(report.summary(failures_only=True) if not report.ok else f"[{version}] baseline {len(report.rows)} rows ok")
|
| 526 |
+
report.assert_ok()
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
@pytest.mark.parametrize("case", list(CASES))
|
| 530 |
+
def test_cpu_bfp_emulation_class(version: str, case: str, canonical: Tuple[GoldenSet, Any, Any], results: Any) -> None:
|
| 531 |
+
"""One dtype class (or the union / the bfp4 FF experiment) rounded on the fp32 reference; every golden tap gated
|
| 532 |
+
with the multi-sample gates; the table row (min PCC, worst margin, failures, deltas vs the fp32 baseline) goes to
|
| 533 |
+
the results JSON. A class the plan expects to admit must pass; the bfp4 experiment is recorded and xfails."""
|
| 534 |
+
gs, obs, mi = canonical
|
| 535 |
+
classes, mbits, expect_pass = CASES[case]
|
| 536 |
+
specs = class_round_specs(version, classes, mbits)
|
| 537 |
+
if not specs:
|
| 538 |
+
results.set_meta(case=case, classes=list(classes), n_keys=0, note="no bfp8 candidates in this version")
|
| 539 |
+
pytest.skip(f"{version}: class {case} has no bfp8 candidates (plan §1.2)")
|
| 540 |
+
base_cand, base_report = baseline(version, gs, obs, mi)
|
| 541 |
+
ck = RoundingCheckpoint(version, specs)
|
| 542 |
+
cand, timing = run_reference(version, gs, obs, mi, ck)
|
| 543 |
+
unread = ck.unread()
|
| 544 |
+
assert not unread, f"{version}/{case}: {len(unread)} candidate keys were never read by the reference: {unread[:5]}"
|
| 545 |
+
assert set(cand) == set(base_cand), sorted(set(cand) ^ set(base_cand))
|
| 546 |
+
report = harness.check_taps(version, cand, gs, results=results, multi=True)
|
| 547 |
+
base_pcc = pcc_table(base_report)
|
| 548 |
+
this_pcc = pcc_table(report)
|
| 549 |
+
deltas = {k: this_pcc[k] - base_pcc[k] for k in this_pcc if k in base_pcc}
|
| 550 |
+
worst_key, worst_margin = report.worst_margin()
|
| 551 |
+
min_key, min_pcc = report.min_pcc()
|
| 552 |
+
# per-tap agreement with the *baseline* candidates (how much the class moved the tensors)
|
| 553 |
+
vs_base = {k: pcc(cand[k], base_cand[k]) for k in ("backbone_features", "action_pred_normalized") if k in cand}
|
| 554 |
+
results.set_meta(
|
| 555 |
+
case=case,
|
| 556 |
+
classes=list(classes),
|
| 557 |
+
mantissa_bits=mbits,
|
| 558 |
+
n_keys=len(specs),
|
| 559 |
+
plan_tensors=sorted({s.plan_name for s in specs.values()}),
|
| 560 |
+
n_rows=len(report.rows),
|
| 561 |
+
n_fail=len(report.failures()),
|
| 562 |
+
min_pcc={"key": min_key, "pcc": min_pcc},
|
| 563 |
+
worst_margin={"key": worst_key, "margin": worst_margin},
|
| 564 |
+
failing_keys=[r["key"] for r in report.failures()],
|
| 565 |
+
pcc_delta_vs_fp32=deltas,
|
| 566 |
+
pcc_vs_fp32_candidates=vs_base,
|
| 567 |
+
round_s=ck.round_s,
|
| 568 |
+
)
|
| 569 |
+
results.set_timing(**timing, round_s=ck.round_s)
|
| 570 |
+
print(
|
| 571 |
+
f"\n[{version}] {case}: {len(specs)} keys rounded ({mbits}-bit) in {ck.round_s:.1f} s; forward "
|
| 572 |
+
f"{timing['forward_s']:.1f} s; rows {len(report.rows)} fail {len(report.failures())}; "
|
| 573 |
+
f"min PCC {min_pcc:.6f} ({min_key}); worst margin {worst_margin:+.6f} ({worst_key})"
|
| 574 |
+
)
|
| 575 |
+
for key in (
|
| 576 |
+
"vit_block_last",
|
| 577 |
+
"projector_out",
|
| 578 |
+
"backbone_features",
|
| 579 |
+
"vlln_out",
|
| 580 |
+
"vl_self_attention_out",
|
| 581 |
+
"dit_block_last_out[k=3]",
|
| 582 |
+
"dit_out[k=3]",
|
| 583 |
+
"pred_velocity[k=3]",
|
| 584 |
+
"action_pred_normalized",
|
| 585 |
+
"action_pred_valid",
|
| 586 |
+
):
|
| 587 |
+
if key in this_pcc:
|
| 588 |
+
print(f" {key:28s} {this_pcc[key]:.6f} (delta {deltas[key]:+.2e}, gate {report.row(key)['gate_pcc']})")
|
| 589 |
+
if report.failures():
|
| 590 |
+
print(report.summary(failures_only=True))
|
| 591 |
+
if expect_pass:
|
| 592 |
+
report.assert_ok()
|
| 593 |
+
elif not report.ok:
|
| 594 |
+
pytest.xfail(
|
| 595 |
+
f"{version}/{case}: experiment fails {len(report.failures())} gate(s): "
|
| 596 |
+
+ ", ".join(f"{r['key']} {r['pcc']:.6f} < {r['gate_pcc']}" for r in report.failures()[:6])
|
| 597 |
+
)
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
__all__ = [
|
| 601 |
+
"BLOCK",
|
| 602 |
+
"CASES",
|
| 603 |
+
"CLASS_CATEGORIES",
|
| 604 |
+
"EXPECTED_ADMIT",
|
| 605 |
+
"MANTISSA_BITS",
|
| 606 |
+
"RoundSpec",
|
| 607 |
+
"RoundingCheckpoint",
|
| 608 |
+
"bfp4_round",
|
| 609 |
+
"bfp8_round",
|
| 610 |
+
"bfp_round",
|
| 611 |
+
"class_round_specs",
|
| 612 |
+
"mantissa_step",
|
| 613 |
+
"reference_candidates",
|
| 614 |
+
"rope_row_permutation",
|
| 615 |
+
"round_linear_weight",
|
| 616 |
+
"run_reference",
|
| 617 |
+
]
|
code/models/experimental/gr00t/tests/tt/test_mk_e2e.py
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""WP-K5 device test: ``Gr00tTT`` with ``TTPolicy(dit_backend="megakernel")`` against the Stage-1 ttnn backend on the
|
| 5 |
+
same device, the same weights tier and the same golden sample(s) (IMPLEMENTATION_PLAN.md §5.7 K5 row, §7.4, §7.6;
|
| 6 |
+
``tests/tt/results/mk_k4_summary.md`` §4.2.2 for the kernel-level oracle protocol this extends to the e2e level).
|
| 7 |
+
|
| 8 |
+
Per version the module builds the two models **sequentially** (one device, opened with the megakernel's reduced
|
| 9 |
+
``worker_l1_size`` so both backends run under the same configuration; the ttnn model is built with the DRAM adapter
|
| 10 |
+
intermediates that configuration needs, ``adapter_mem="DRAM"``, values unchanged), runs every selected sample
|
| 11 |
+
through ``run_untraced(return_taps=True)`` and the traced path (warm -> capture -> N replays) and releases the model;
|
| 12 |
+
the tests then compare the recorded host results:
|
| 13 |
+
|
| 14 |
+
* ``test_mk_e2e_backends_agree`` -- ``action_pred_normalized`` (untraced and traced) and every per-step
|
| 15 |
+
``pred_velocity[k]`` of the megakernel vs the ttnn backend: PCC >= 0.9999 (the K4 oracle gate, plan §5.7; the
|
| 16 |
+
``action_decoder_out[k]`` rows -- 64 rows including the state / future tokens nothing downstream reads -- are
|
| 17 |
+
recorded as information) and max |d| recorded; the megakernel's taps pass every ``gates_multi.json`` gate the ttnn
|
| 18 |
+
backend passes on that sample (``harness.check_taps``, the decoded ``unnormalized_actions.<group>`` included) and
|
| 19 |
+
it is at least as close to the fp32 golden on ``action_pred_normalized`` (K4's closeness rule, slack 2e-5). On a
|
| 20 |
+
sample where the ttnn backend itself misses the ``action_pred_normalized`` golden gate (the Stage-1 divergent
|
| 21 |
+
multi samples, STATUS.md) the two bf16 pipelines are not each other's oracle: the oracle rows are informational
|
| 22 |
+
and the closeness rule decides. :data:`MK_KNOWN_MARGINAL` lists the rows the megakernel misses while the ttnn
|
| 23 |
+
backend passes (``mk_k5_summary.md`` §4.1 / §7 item 4) with a PCC floor: such a run is an **xfail**, never a pass;
|
| 24 |
+
* ``test_mk_e2e_trace_protocol`` -- megakernel traced == untraced bit-equal (raw device buffer and the kernel's
|
| 25 |
+
per-step ``velocity_out`` taps; K4 review NB 13), 10 traced replays bit-identical (plan D12: megakernel 10x),
|
| 26 |
+
``backbone_features`` / ``K_0`` / ``V_0`` bit-equal traced vs untraced, the ttnn backend's 5 replays likewise;
|
| 27 |
+
* ``test_mk_e2e_denoise_trace_ms`` -- informational: the split-pass trace times of both backends (median of a few
|
| 28 |
+
synchronised replays under alloc tracking -- **not** the latency source; ``benchmarks/bench_e2e.py --dit-backend``
|
| 29 |
+
is) and the ratio of the denoise trace.
|
| 30 |
+
|
| 31 |
+
Run (one pytest process per version, through the device lock; alloc tracking is asserted, so set it in the shell)::
|
| 32 |
+
|
| 33 |
+
TT_METAL_TRACE_ALLOC_TRACKING=1 DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh \\
|
| 34 |
+
python -m pytest -q -rA --timeout=3600 -p no:cacheprovider \\
|
| 35 |
+
models/experimental/gr00t/tests/tt/test_mk_e2e.py --gr00t-version n16 [--sample multi] \\
|
| 36 |
+
[GR00T_MK_ARENA_DTYPE=bf16|bfp8_b in the environment selects the megakernel arena; default: the policy table]
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
from __future__ import annotations
|
| 40 |
+
|
| 41 |
+
import os
|
| 42 |
+
import statistics
|
| 43 |
+
import time
|
| 44 |
+
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Set, Tuple
|
| 45 |
+
|
| 46 |
+
import pytest
|
| 47 |
+
import torch
|
| 48 |
+
|
| 49 |
+
from models.experimental.gr00t.common.golden import GoldenSet
|
| 50 |
+
from models.experimental.gr00t.common.pcc import max_abs, pcc
|
| 51 |
+
from models.experimental.gr00t.tests.tt import harness
|
| 52 |
+
from models.experimental.gr00t.tests.tt.conftest import (
|
| 53 |
+
DEFAULT_L1_SMALL_SIZE,
|
| 54 |
+
DEFAULT_TRACE_REGION_SIZE,
|
| 55 |
+
DEFAULT_TT_METAL_CACHE,
|
| 56 |
+
OPT_SAMPLE,
|
| 57 |
+
SAMPLE_SETS,
|
| 58 |
+
samples_for,
|
| 59 |
+
skip_if_missing,
|
| 60 |
+
)
|
| 61 |
+
from models.experimental.gr00t.tests.tt.test_tt_model_e2e import (
|
| 62 |
+
decoded_groups,
|
| 63 |
+
golden_keyed,
|
| 64 |
+
required_gated_keys,
|
| 65 |
+
split_info,
|
| 66 |
+
)
|
| 67 |
+
from models.experimental.gr00t.tt import model as M
|
| 68 |
+
from models.experimental.gr00t.tt import tracer as T
|
| 69 |
+
from models.experimental.gr00t.tt.layout import StaticShapeError
|
| 70 |
+
from models.experimental.gr00t.tt.policy import TTPolicy
|
| 71 |
+
|
| 72 |
+
torch.set_grad_enabled(False)
|
| 73 |
+
|
| 74 |
+
BACKENDS: Tuple[str, ...] = ("ttnn", "megakernel")
|
| 75 |
+
GATE_ORACLE = 0.9999 # plan §5.7 K4/K5: PCC vs the Stage-1 device path
|
| 76 |
+
GOLDEN_SLACK = 2e-5 # mk_k4_summary.md §4.3: PCC(mk, golden) >= PCC(S1, golden) - slack
|
| 77 |
+
#: rows the megakernel misses while the ttnn backend passes them (WP-K5 device facts, mk_k5_summary.md §4.1 / §7 item 4;
|
| 78 |
+
#: both arenas, marginal gates): (version, sample) -> {key: (field, PCC floor)}. ``field`` names the quantity of the
|
| 79 |
+
#: ``harness.check_taps`` row that actually fails its gate -- ``"pcc"`` (the full-tensor PCC) or
|
| 80 |
+
#: ``"per_dim.min_gated_pcc"`` (the worst gated dimension of a per-dimension gate) -- and the run is an **xfail**
|
| 81 |
+
#: only while that quantity stays above the floor (WP-K5 review correction 2: the N1.7 row fails on dim 9 while its
|
| 82 |
+
#: full-tensor PCC 0.99983 never failed, so a floor on ``pcc`` would let dim 9 sink to 0.99 unnoticed)
|
| 83 |
+
MK_KNOWN_MARGINAL: Dict[Tuple[str, str], Dict[str, Tuple[str, float]]] = {
|
| 84 |
+
# 0.999872 (bf16) / 0.999875 (bfp8) vs gate 0.9999 (ttnn 0.999915); max|d| 0.034 / 0.029 within the 0.038 tolerance
|
| 85 |
+
("n15", "t3_s300_seed2"): {"unnormalized_actions.right_arm": ("pcc", 0.9998)},
|
| 86 |
+
# per-dim gate 0.996 on dim 9: 0.99589 (bf16) / 0.99585 (bfp8) vs ttnn 0.99690; full-tensor PCC 0.99983 passes
|
| 87 |
+
("n17", "t1_s120"): {"pred_velocity[k=2]": ("per_dim.min_gated_pcc", 0.995)},
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def marginal_value(row: Mapping[str, Any], field: str) -> Optional[float]:
|
| 92 |
+
"""The quantity of a ``check_taps`` row that :data:`MK_KNOWN_MARGINAL` puts a floor on: ``"pcc"`` or the dotted
|
| 93 |
+
``"per_dim.min_gated_pcc"`` (``None`` when the row lacks it -- then the row is *not* a known marginal)."""
|
| 94 |
+
cur: Any = row
|
| 95 |
+
for part in field.split("."):
|
| 96 |
+
if not isinstance(cur, Mapping) or part not in cur:
|
| 97 |
+
return None
|
| 98 |
+
cur = cur[part]
|
| 99 |
+
return None if cur is None else float(cur)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def known_marginal_regressions(
|
| 103 |
+
version: str, sample: str, regressions: Sequence[str], mk_rows: Mapping[str, Mapping[str, Any]]
|
| 104 |
+
) -> Tuple[List[str], Dict[str, Dict[str, Any]]]:
|
| 105 |
+
"""Split ``regressions`` (gates the ttnn backend passes and the megakernel fails) into the :data:`MK_KNOWN_MARGINAL`
|
| 106 |
+
rows that are above their floor (-> xfail) and the rest (-> fail); returns ``(known, facts)`` where ``facts``
|
| 107 |
+
records field / value / floor per known row."""
|
| 108 |
+
marginal = MK_KNOWN_MARGINAL.get((version, sample), {})
|
| 109 |
+
known: List[str] = []
|
| 110 |
+
facts: Dict[str, Dict[str, Any]] = {}
|
| 111 |
+
for key in regressions:
|
| 112 |
+
if key not in marginal:
|
| 113 |
+
continue
|
| 114 |
+
field, floor = marginal[key]
|
| 115 |
+
value = marginal_value(mk_rows.get(key, {}), field)
|
| 116 |
+
if value is not None and value >= floor:
|
| 117 |
+
known.append(key)
|
| 118 |
+
facts[key] = {"field": field, "value": value, "floor": floor}
|
| 119 |
+
return sorted(known), facts
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
#: oracle-gated per-step taps (the action rows); action_decoder_out[k] (all 64 rows) is informational
|
| 123 |
+
ORACLE_STEP_TAPS: Tuple[str, ...] = ("pred_velocity",)
|
| 124 |
+
#: a sample whose ttnn-backend ``action_pred_normalized`` is below this PCC vs its golden is not a reference for either
|
| 125 |
+
#: backend (the N1.7 extras: `droid_rtc` 0.849 for both); its rows are recorded and the test is skipped
|
| 126 |
+
CLOSENESS_MIN_REFERENCE = 0.99
|
| 127 |
+
N_REPLAYS: Dict[str, int] = {"ttnn": 5, "megakernel": 10} # plan §7.4: 5 traced; megakernel 10x
|
| 128 |
+
N_SPLIT_REPS = 5
|
| 129 |
+
ACTION_KEYS: Tuple[str, ...] = ("action_pred_normalized", "action_pred_valid")
|
| 130 |
+
#: the keys both backends produce (``tt.action_head.head_tap_names("megakernel", ...)``)
|
| 131 |
+
COMMON_STEP_TAPS: Tuple[str, ...] = ("action_decoder_out", "pred_velocity")
|
| 132 |
+
|
| 133 |
+
_RESULTS: Dict[str, Dict[str, Dict[str, Dict[str, Any]]]] = {} # version -> backend -> sample -> record
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 137 |
+
# CPU tier: the known-marginal resolver (WP-K5 review correction 2)
|
| 138 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 139 |
+
def test_cpu_known_marginal_floor_on_failing_quantity() -> None:
|
| 140 |
+
"""The N1.7 floor sits on ``per_dim.min_gated_pcc`` (the quantity that fails), the N1.5 floor on ``pcc``; a row
|
| 141 |
+
below its floor, a row without the field, or an unlisted key never counts as a known marginal."""
|
| 142 |
+
n17_row = {
|
| 143 |
+
"key": "pred_velocity[k=2]",
|
| 144 |
+
"pcc": 0.999826,
|
| 145 |
+
"gate_pcc": 0.9997,
|
| 146 |
+
"ok": False,
|
| 147 |
+
"per_dim": {"gate_pcc": 0.996, "min_gated_pcc": 0.995846, "failed_dims": [9], "ok": False},
|
| 148 |
+
}
|
| 149 |
+
known, facts = known_marginal_regressions("n17", "t1_s120", ["pred_velocity[k=2]"], {n17_row["key"]: n17_row})
|
| 150 |
+
assert known == ["pred_velocity[k=2]"] and facts["pred_velocity[k=2]"]["field"] == "per_dim.min_gated_pcc"
|
| 151 |
+
assert facts["pred_velocity[k=2]"]["value"] == 0.995846 and facts["pred_velocity[k=2]"]["floor"] == 0.995
|
| 152 |
+
sunk = dict(n17_row, per_dim=dict(n17_row["per_dim"], min_gated_pcc=0.99)) # full-tensor pcc unchanged
|
| 153 |
+
assert known_marginal_regressions("n17", "t1_s120", ["pred_velocity[k=2]"], {sunk["key"]: sunk})[0] == []
|
| 154 |
+
no_field = {k: v for k, v in n17_row.items() if k != "per_dim"}
|
| 155 |
+
assert (
|
| 156 |
+
known_marginal_regressions("n17", "t1_s120", ["pred_velocity[k=2]"], {"pred_velocity[k=2]": no_field})[0] == []
|
| 157 |
+
)
|
| 158 |
+
n15_row = {"key": "unnormalized_actions.right_arm", "pcc": 0.999875, "gate_pcc": 0.9999, "ok": False}
|
| 159 |
+
known, facts = known_marginal_regressions(
|
| 160 |
+
"n15", "t3_s300_seed2", ["unnormalized_actions.right_arm", "action_pred_normalized"], {n15_row["key"]: n15_row}
|
| 161 |
+
)
|
| 162 |
+
assert known == ["unnormalized_actions.right_arm"] and facts[known[0]]["field"] == "pcc"
|
| 163 |
+
assert (
|
| 164 |
+
known_marginal_regressions("n15", "canonical", ["unnormalized_actions.right_arm"], {n15_row["key"]: n15_row})[0]
|
| 165 |
+
== []
|
| 166 |
+
)
|
| 167 |
+
for (v, smp), rows in MK_KNOWN_MARGINAL.items():
|
| 168 |
+
for key, (field, floor) in rows.items():
|
| 169 |
+
assert field in ("pcc", "per_dim.min_gated_pcc") and 0.99 <= floor < 1.0, (v, smp, key)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 173 |
+
# fixtures
|
| 174 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 175 |
+
@pytest.fixture(scope="module")
|
| 176 |
+
def mk_policy(policy_name: str, trace_layout: str) -> TTPolicy:
|
| 177 |
+
"""The megakernel policy of this run (dtype policy / trace layout from the options, arena from the environment)."""
|
| 178 |
+
pol = TTPolicy(dtype_policy=policy_name, trace_layout=trace_layout, dit_backend="megakernel").with_env_overrides()
|
| 179 |
+
if pol.dit_backend != "megakernel":
|
| 180 |
+
raise pytest.UsageError("test_mk_e2e always builds the megakernel backend; unset GR00T_DIT_BACKEND")
|
| 181 |
+
return pol
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
@pytest.fixture(scope="module")
|
| 185 |
+
def mk_device(request: pytest.FixtureRequest, mk_policy: TTPolicy) -> Iterator[Any]:
|
| 186 |
+
"""One p150a opened with the K4 ``worker_l1_size`` (both backends run under it; module docstring)."""
|
| 187 |
+
from models.experimental.gr00t.tt.device import close_gr00t_device
|
| 188 |
+
|
| 189 |
+
os.environ.setdefault("TT_METAL_CACHE", str(DEFAULT_TT_METAL_CACHE))
|
| 190 |
+
device = M.open_model_device(
|
| 191 |
+
mk_policy, trace_region_size=DEFAULT_TRACE_REGION_SIZE, l1_small_size=DEFAULT_L1_SMALL_SIZE
|
| 192 |
+
)
|
| 193 |
+
facts = harness.device_facts(device)
|
| 194 |
+
facts["worker_l1_size"] = M.device_worker_l1_size(device)
|
| 195 |
+
request.config._gr00t_device_facts = facts # type: ignore[attr-defined]
|
| 196 |
+
try:
|
| 197 |
+
yield device
|
| 198 |
+
finally:
|
| 199 |
+
_RESULTS.clear()
|
| 200 |
+
close_gr00t_device(device)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def _selected_samples(request: pytest.FixtureRequest, version: str) -> Tuple[str, ...]:
|
| 204 |
+
opt = str(request.config.getoption(OPT_SAMPLE))
|
| 205 |
+
names = samples_for(version, opt) if opt in SAMPLE_SETS else (opt,)
|
| 206 |
+
return tuple(names)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
@pytest.fixture(scope="module")
|
| 210 |
+
def backend_results(
|
| 211 |
+
request: pytest.FixtureRequest, version: str, mk_device: Any, mk_policy: TTPolicy
|
| 212 |
+
) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
| 213 |
+
"""``backend -> sample -> record`` of every selected sample, both backends built once per version (module doc)."""
|
| 214 |
+
if version not in _RESULTS:
|
| 215 |
+
if not T.alloc_tracking_requested():
|
| 216 |
+
pytest.fail(
|
| 217 |
+
f"{T.ALLOC_TRACKING_ENV}=1 must be set in the shell before pytest starts (plan §4 / static-shape §10 "
|
| 218 |
+
"item 9): this test asserts traced == untraced"
|
| 219 |
+
)
|
| 220 |
+
samples = _selected_samples(request, version)
|
| 221 |
+
for s in samples:
|
| 222 |
+
skip_if_missing(version, s)
|
| 223 |
+
# the Stage-1 reference must be the ttnn backend explicitly: the TTPolicy default is the megakernel since
|
| 224 |
+
# 2026-09-18 (a defaulted policy here would compare the megakernel with itself)
|
| 225 |
+
ttnn_policy = TTPolicy(
|
| 226 |
+
dtype_policy=mk_policy.dtype_policy, trace_layout=mk_policy.trace_layout, dit_backend="ttnn"
|
| 227 |
+
)
|
| 228 |
+
_RESULTS[version] = {
|
| 229 |
+
"ttnn": _collect(version, samples, ttnn_policy, mk_device),
|
| 230 |
+
"megakernel": _collect(version, samples, mk_policy, mk_device),
|
| 231 |
+
}
|
| 232 |
+
if not (
|
| 233 |
+
_RESULTS[version]["ttnn"]["_model"]["recipe"]["dit_backend"] == "ttnn"
|
| 234 |
+
and _RESULTS[version]["megakernel"]["_model"]["recipe"]["dit_backend"] == "megakernel"
|
| 235 |
+
):
|
| 236 |
+
raise AssertionError("the two collections must be the ttnn and the megakernel backend")
|
| 237 |
+
return _RESULTS[version]
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 241 |
+
# collection (one model per backend, every selected sample, then release)
|
| 242 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 243 |
+
def _collect(version: str, samples: Tuple[str, ...], policy: TTPolicy, device: Any) -> Dict[str, Dict[str, Any]]:
|
| 244 |
+
from models.experimental.gr00t.reference.model import load_golden_sample
|
| 245 |
+
|
| 246 |
+
backend = policy.dit_backend
|
| 247 |
+
t0 = time.perf_counter()
|
| 248 |
+
# both models run on the megakernel's device configuration (reduced worker L1): the ttnn model therefore also
|
| 249 |
+
# needs the DRAM adapter intermediates (tt.model.ADAPTER_MEM_BY_BACKEND; values identical, placement only)
|
| 250 |
+
model = M.Gr00tTT.from_pretrained(version, policy=policy, device=device, adapter_mem="DRAM")
|
| 251 |
+
build_s = time.perf_counter() - t0
|
| 252 |
+
print(
|
| 253 |
+
f"\n[test_mk_e2e] {version}/{backend}: from_pretrained {build_s:.1f} s (weights {model.timing['weights_load_s']:.1f} s"
|
| 254 |
+
f"{'; arena ' + str(model.recipe.mk_arena_dtype) if backend == 'megakernel' else ''}; adapter_mem "
|
| 255 |
+
f"{model.adapter_mem}); head timing {({k: round(v, 2) for k, v in model.head.timing.items()})}"
|
| 256 |
+
)
|
| 257 |
+
out: Dict[str, Dict[str, Any]] = {}
|
| 258 |
+
try:
|
| 259 |
+
describe = model.describe()
|
| 260 |
+
for sample in samples:
|
| 261 |
+
gs, obs = load_golden_sample(version, sample)
|
| 262 |
+
try:
|
| 263 |
+
mi = model.encode(obs) # raises for another embodiment (the N1.7 `g1_synthetic` extra)
|
| 264 |
+
except (ValueError, StaticShapeError) as exc:
|
| 265 |
+
out[sample] = {"sample": sample, "backend": backend, "skip": f"{type(exc).__name__}: {exc}"}
|
| 266 |
+
print(
|
| 267 |
+
f"[test_mk_e2e] {version}/{backend}/{sample}: not this model's embodiment / layout -> skipped ({exc})"
|
| 268 |
+
)
|
| 269 |
+
continue
|
| 270 |
+
if mi.shape_key != model.dlayout.shape_key:
|
| 271 |
+
out[sample] = {"sample": sample, "backend": backend, "skip": f"shape key {mi.shape_key} != model's"}
|
| 272 |
+
continue
|
| 273 |
+
noise = gs.load("initial_noise")
|
| 274 |
+
rec: Dict[str, Any] = {"sample": sample, "backend": backend, "build_s": build_s, "n_text": int(mi.n_text)}
|
| 275 |
+
t0 = time.perf_counter()
|
| 276 |
+
pred, taps = model.run_untraced(mi, noise, return_taps=True, raw=True)
|
| 277 |
+
rec["untraced_s"] = time.perf_counter() - t0
|
| 278 |
+
rec["untraced_device_s"] = model.timing["last_untraced_s"]
|
| 279 |
+
rec["pred"] = pred
|
| 280 |
+
rec["raw"] = {k[len(M.RAW_PREFIX) :]: v for k, v in taps.items() if k.startswith(M.RAW_PREFIX)}
|
| 281 |
+
host_taps = {k: v for k, v in taps.items() if not k.startswith(M.RAW_PREFIX)}
|
| 282 |
+
expected = set(M.expected_tap_keys(model.cfg, model.dlayout, backend))
|
| 283 |
+
missing = sorted(expected - set(host_taps))
|
| 284 |
+
if missing:
|
| 285 |
+
raise AssertionError(f"{version}/{backend}: run_untraced lacks {missing}")
|
| 286 |
+
cand = golden_keyed(host_taps, gs)
|
| 287 |
+
cand.update(decoded_groups(model, pred, obs))
|
| 288 |
+
cand, info = split_info(version, cand)
|
| 289 |
+
rec["cand"] = cand
|
| 290 |
+
rec["required"] = required_gated_keys(version, gs, model.dlayout, backend) | {
|
| 291 |
+
k for k in cand if k.startswith(M.DECODE_TAP)
|
| 292 |
+
}
|
| 293 |
+
rec["head_taps"] = {k: v for k, v in host_taps.items() if k.split("[")[0] in COMMON_STEP_TAPS + ACTION_KEYS}
|
| 294 |
+
# traced path: capture on the first sample, replay every sample
|
| 295 |
+
if not model.captured:
|
| 296 |
+
t0 = time.perf_counter()
|
| 297 |
+
model.warm_and_capture(mi, noise)
|
| 298 |
+
rec["warm_and_capture_s"] = time.perf_counter() - t0
|
| 299 |
+
preds: List[torch.Tensor] = []
|
| 300 |
+
states: List[Dict[str, torch.Tensor]] = []
|
| 301 |
+
replay_s: List[float] = []
|
| 302 |
+
for _ in range(N_REPLAYS[backend]):
|
| 303 |
+
t0 = time.perf_counter()
|
| 304 |
+
preds.append(model.predict_normalized(mi, noise))
|
| 305 |
+
replay_s.append(time.perf_counter() - t0)
|
| 306 |
+
states.append(model.traced_state())
|
| 307 |
+
rec["traced_preds"], rec["traced_states"], rec["replay_s"] = preds, states, replay_s
|
| 308 |
+
if backend == "megakernel":
|
| 309 |
+
# the kernel's per-step decoder taps of the last traced replay vs the untraced run (K4 review NB 13);
|
| 310 |
+
# the untraced taps are golden-shaped ([1, M, A_max] per step), so compare on that slice
|
| 311 |
+
vel = model.head.velocity_taps_host() # [n_steps, 64, A_pad]
|
| 312 |
+
m, a_max = int(model.dlayout.m_logical), int(model.cfg.encoders.max_action_dim)
|
| 313 |
+
rec["traced_velocity"] = vel[:, :m, :a_max].contiguous()
|
| 314 |
+
rec["untraced_velocity"] = torch.stack(
|
| 315 |
+
[host_taps[f"action_decoder_out[k={k}]"][0] for k in range(vel.shape[0])]
|
| 316 |
+
)
|
| 317 |
+
if rec["traced_velocity"].shape != rec["untraced_velocity"].shape:
|
| 318 |
+
raise AssertionError(
|
| 319 |
+
f"velocity shapes {tuple(rec['traced_velocity'].shape)} vs {tuple(rec['untraced_velocity'].shape)}"
|
| 320 |
+
)
|
| 321 |
+
# split pass: one synchronised replay per trace (informational under alloc tracking)
|
| 322 |
+
tr = model.tracer
|
| 323 |
+
per_stage: Dict[str, List[float]] = {s: [] for s in tr.stage_names}
|
| 324 |
+
for _ in range(N_SPLIT_REPS):
|
| 325 |
+
tr.upload(mi, noise)
|
| 326 |
+
tr.synchronize()
|
| 327 |
+
for st in tr.stage_names:
|
| 328 |
+
t0 = time.perf_counter()
|
| 329 |
+
tr.replay(st)
|
| 330 |
+
tr.synchronize()
|
| 331 |
+
per_stage[st].append((time.perf_counter() - t0) * 1e3)
|
| 332 |
+
rec["split_ms"] = {s: statistics.median(v) for s, v in per_stage.items()}
|
| 333 |
+
rec["split_raw_equal"] = bool(torch.equal(tr.read_action_pred(), states[0]["action_pred"]))
|
| 334 |
+
out[sample] = rec
|
| 335 |
+
out["_model"] = {
|
| 336 |
+
"describe": describe,
|
| 337 |
+
"timing": dict(model.timing),
|
| 338 |
+
"head_timing": dict(model.head.timing),
|
| 339 |
+
"recipe": model.recipe.describe(),
|
| 340 |
+
"executor": model.executor.describe(),
|
| 341 |
+
}
|
| 342 |
+
finally:
|
| 343 |
+
model.release()
|
| 344 |
+
return out
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 348 |
+
# helpers
|
| 349 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 350 |
+
def _row(results: Any, tap: str, key: str, ok: bool, rule: str = "exact", **extra: Any) -> Dict[str, Any]:
|
| 351 |
+
r = {"tap": tap, "key": key, "rule": rule, "ok": bool(ok), **extra}
|
| 352 |
+
results.add_row(r)
|
| 353 |
+
return r
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
def _pcc_row(
|
| 357 |
+
results: Any, key: str, got: torch.Tensor, ref: torch.Tensor, gate: Optional[float], **extra: Any
|
| 358 |
+
) -> Dict[str, Any]:
|
| 359 |
+
"""``gate=None`` records the row as information (``ok`` = finite only)."""
|
| 360 |
+
p = pcc(got.float(), ref.float())
|
| 361 |
+
finite = bool(torch.isfinite(got).all())
|
| 362 |
+
r = {
|
| 363 |
+
"tap": key.split("[")[0],
|
| 364 |
+
"key": f"{key}-mk-vs-ttnn",
|
| 365 |
+
"rule": "pcc" if gate is not None else "info",
|
| 366 |
+
"pcc": p,
|
| 367 |
+
"gate_pcc": gate,
|
| 368 |
+
"max_abs": max_abs(got.float(), ref.float()),
|
| 369 |
+
"finite": finite,
|
| 370 |
+
"ok": bool(finite and (gate is None or p >= gate)),
|
| 371 |
+
**extra,
|
| 372 |
+
}
|
| 373 |
+
results.add_row(r)
|
| 374 |
+
return r
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
def _meta(results: Any, version: str, sample: str, res: Dict[str, Dict[str, Dict[str, Any]]]) -> None:
|
| 378 |
+
mk, s1 = res["megakernel"], res["ttnn"]
|
| 379 |
+
for rec in (mk.get(sample), s1.get(sample)):
|
| 380 |
+
if rec is None or "skip" in rec:
|
| 381 |
+
pytest.skip(f"{version}/{sample}: {rec['skip'] if rec else 'not collected'}")
|
| 382 |
+
results.set_meta(
|
| 383 |
+
sample=sample,
|
| 384 |
+
mk_recipe=mk["_model"]["recipe"],
|
| 385 |
+
ttnn_recipe=s1["_model"]["recipe"],
|
| 386 |
+
mk_head=mk["_model"]["describe"].get("head"),
|
| 387 |
+
mk_head_timing=mk["_model"]["head_timing"],
|
| 388 |
+
build_s={"ttnn": s1[sample]["build_s"], "megakernel": mk[sample]["build_s"]},
|
| 389 |
+
alloc_tracking_env=os.environ.get(T.ALLOC_TRACKING_ENV),
|
| 390 |
+
n_text=mk[sample]["n_text"],
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def _failures(
|
| 395 |
+
version: str, gs: GoldenSet, rec: Dict[str, Any], results: Optional[Any]
|
| 396 |
+
) -> Tuple[harness.Report, Set[str]]:
|
| 397 |
+
report = harness.check_taps(version, rec["cand"], gs, results=results)
|
| 398 |
+
return report, {r["key"] for r in report.failures()}
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 402 |
+
# tests
|
| 403 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 404 |
+
def test_mk_e2e_backends_agree(
|
| 405 |
+
version: str,
|
| 406 |
+
sample: str,
|
| 407 |
+
golden: Tuple[GoldenSet, Any, Any],
|
| 408 |
+
backend_results: Dict[str, Dict[str, Dict[str, Any]]],
|
| 409 |
+
results: harness.ResultsWriter,
|
| 410 |
+
) -> None:
|
| 411 |
+
"""Megakernel vs ttnn backend on the same sample: action_pred / per-step velocities >= 0.9999, golden gates."""
|
| 412 |
+
gs, _, _ = golden
|
| 413 |
+
mk, s1 = backend_results["megakernel"][sample], backend_results["ttnn"][sample]
|
| 414 |
+
_meta(results, version, sample, backend_results)
|
| 415 |
+
# golden gates first: the megakernel must pass every gate the ttnn backend passes (+ K4's closeness rule); a
|
| 416 |
+
# sample where the ttnn backend misses its own action_pred gate is a Stage-1 divergence -> no oracle there
|
| 417 |
+
report_mk, fail_mk = _failures(version, gs, mk, results)
|
| 418 |
+
report_s1, fail_s1 = _failures(version, gs, s1, None)
|
| 419 |
+
s1_is_oracle = "action_pred_normalized" not in fail_s1
|
| 420 |
+
oracle = GATE_ORACLE if s1_is_oracle else None
|
| 421 |
+
rows: Dict[str, Dict[str, Any]] = {}
|
| 422 |
+
rows["action_pred_normalized"] = _pcc_row(results, "action_pred_normalized", mk["pred"], s1["pred"], oracle)
|
| 423 |
+
rows["action_pred_normalized[traced]"] = _pcc_row(
|
| 424 |
+
results, "action_pred_normalized[traced]", mk["traced_preds"][0], s1["traced_preds"][0], oracle
|
| 425 |
+
)
|
| 426 |
+
for key, t in sorted(mk["head_taps"].items()):
|
| 427 |
+
if key in ACTION_KEYS:
|
| 428 |
+
continue
|
| 429 |
+
gate = oracle if key.split("[")[0] in ORACLE_STEP_TAPS else None
|
| 430 |
+
rows[key] = _pcc_row(results, key, t, s1["head_taps"][key], gate)
|
| 431 |
+
common = set(mk["cand"]) & set(s1["cand"])
|
| 432 |
+
regressions = sorted((fail_mk & common) - fail_s1)
|
| 433 |
+
mk_rows = {r["key"]: r for r in report_mk.rows}
|
| 434 |
+
known, known_facts = known_marginal_regressions(version, sample, regressions, mk_rows)
|
| 435 |
+
gold = gs.load("action_pred_normalized").float()
|
| 436 |
+
close_mk, close_s1 = pcc(mk["pred"], gold), pcc(s1["pred"], gold)
|
| 437 |
+
reference_ok = close_s1 >= CLOSENESS_MIN_REFERENCE
|
| 438 |
+
_row(
|
| 439 |
+
results,
|
| 440 |
+
"action_pred_normalized",
|
| 441 |
+
"mk-closeness-to-golden",
|
| 442 |
+
close_mk >= close_s1 - GOLDEN_SLACK,
|
| 443 |
+
rule="pcc",
|
| 444 |
+
pcc=close_mk,
|
| 445 |
+
s1_pcc_vs_golden=close_s1,
|
| 446 |
+
gate_pcc=close_s1 - GOLDEN_SLACK,
|
| 447 |
+
max_abs=max_abs(mk["pred"], gold),
|
| 448 |
+
s1_max_abs_vs_golden=max_abs(s1["pred"], gold),
|
| 449 |
+
)
|
| 450 |
+
_row(
|
| 451 |
+
results,
|
| 452 |
+
"gates",
|
| 453 |
+
"mk-passes-every-gate-ttnn-passes",
|
| 454 |
+
not regressions,
|
| 455 |
+
rule="set",
|
| 456 |
+
n_common=len(common),
|
| 457 |
+
n_required_mk=len(mk["required"]),
|
| 458 |
+
fail_mk=sorted(fail_mk),
|
| 459 |
+
fail_ttnn=sorted(fail_s1),
|
| 460 |
+
regressions=regressions,
|
| 461 |
+
known_marginal=known,
|
| 462 |
+
known_marginal_facts=known_facts,
|
| 463 |
+
fixed_by_mk=sorted((fail_s1 & common) - fail_mk),
|
| 464 |
+
ttnn_only_fail=sorted(fail_s1 - common),
|
| 465 |
+
ttnn_is_oracle=s1_is_oracle,
|
| 466 |
+
)
|
| 467 |
+
print(f"[{version}/{sample}] mk gates: {report_mk.summary()}")
|
| 468 |
+
print(f"[{version}/{sample}] ttnn gates: {report_s1.summary()}")
|
| 469 |
+
print(
|
| 470 |
+
f"[{version}/{sample}] mk vs ttnn: action_pred {rows['action_pred_normalized']['pcc']:.7f} "
|
| 471 |
+
f"(max|d| {rows['action_pred_normalized']['max_abs']:.3g}); mk/ttnn vs golden {close_mk:.6f}/{close_s1:.6f}; "
|
| 472 |
+
f"regressions {regressions}; fixed {sorted((fail_s1 & common) - fail_mk)}"
|
| 473 |
+
)
|
| 474 |
+
assert set(mk["cand"]) == mk["required"], (
|
| 475 |
+
sorted(mk["required"] - set(mk["cand"])),
|
| 476 |
+
sorted(set(mk["cand"]) - mk["required"]),
|
| 477 |
+
)
|
| 478 |
+
if not reference_ok:
|
| 479 |
+
pytest.skip(
|
| 480 |
+
f"{version}/{sample}: the ttnn backend is at PCC {close_s1:.4f} vs the golden (megakernel {close_mk:.4f}); "
|
| 481 |
+
f"neither pipeline is a reference for this sample (rows recorded)"
|
| 482 |
+
)
|
| 483 |
+
bad = {k: r["pcc"] for k, r in rows.items() if not r["ok"]}
|
| 484 |
+
assert not bad, f"megakernel vs ttnn below {GATE_ORACLE}: {bad}"
|
| 485 |
+
assert (
|
| 486 |
+
close_mk >= close_s1 - GOLDEN_SLACK
|
| 487 |
+
), f"mk vs golden {close_mk:.6f} < ttnn vs golden {close_s1:.6f} - {GOLDEN_SLACK}"
|
| 488 |
+
unknown = [k for k in regressions if k not in known]
|
| 489 |
+
assert not unknown, f"gates the ttnn backend passes and the megakernel fails: {unknown} (known marginal: {known})"
|
| 490 |
+
if known:
|
| 491 |
+
pytest.xfail(
|
| 492 |
+
f"{version}/{sample}: only MK_KNOWN_MARGINAL rows regress (above their floors): "
|
| 493 |
+
+ ", ".join(
|
| 494 |
+
f"{k} {known_facts[k]['field']} {known_facts[k]['value']:.6f} >= floor {known_facts[k]['floor']} "
|
| 495 |
+
f"(row pcc {mk_rows[k]['pcc']:.6f} vs gate {mk_rows[k].get('gate_pcc')})"
|
| 496 |
+
for k in known
|
| 497 |
+
)
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def test_mk_e2e_trace_protocol(
|
| 502 |
+
version: str,
|
| 503 |
+
sample: str,
|
| 504 |
+
golden: Tuple[GoldenSet, Any, Any],
|
| 505 |
+
backend_results: Dict[str, Dict[str, Dict[str, Any]]],
|
| 506 |
+
results: harness.ResultsWriter,
|
| 507 |
+
) -> None:
|
| 508 |
+
"""Traced == untraced (raw buffers), N replays bit-identical, for both backends (megakernel 10x)."""
|
| 509 |
+
_meta(results, version, sample, backend_results)
|
| 510 |
+
ok_all = True
|
| 511 |
+
for backend in BACKENDS:
|
| 512 |
+
rec = backend_results[backend][sample]
|
| 513 |
+
st0 = rec["traced_states"][0]
|
| 514 |
+
eq_pred = bool(
|
| 515 |
+
torch.equal(st0["action_pred"], rec["raw"]["action_pred"])
|
| 516 |
+
and torch.equal(rec["traced_preds"][0], rec["pred"])
|
| 517 |
+
)
|
| 518 |
+
eq_bb = bool(torch.equal(st0["backbone_features"], rec["raw"]["backbone_features"]))
|
| 519 |
+
eq_kv = bool(torch.equal(st0["K_0"], rec["raw"]["K_0"]) and torch.equal(st0["V_0"], rec["raw"]["V_0"]))
|
| 520 |
+
eq_replays = all(torch.equal(st0["action_pred"], s["action_pred"]) for s in rec["traced_states"][1:])
|
| 521 |
+
eq_host = all(torch.equal(p, rec["traced_preds"][0]) for p in rec["traced_preds"][1:])
|
| 522 |
+
_row(
|
| 523 |
+
results,
|
| 524 |
+
"action_pred",
|
| 525 |
+
f"{backend}-traced_equals_untraced",
|
| 526 |
+
eq_pred,
|
| 527 |
+
pcc=pcc(rec["traced_preds"][0], rec["pred"]),
|
| 528 |
+
max_abs=max_abs(rec["traced_preds"][0], rec["pred"]),
|
| 529 |
+
)
|
| 530 |
+
_row(results, "backbone_features", f"{backend}-traced_equals_untraced", eq_bb)
|
| 531 |
+
_row(results, "K_0/V_0", f"{backend}-traced_equals_untraced", eq_kv)
|
| 532 |
+
_row(
|
| 533 |
+
results,
|
| 534 |
+
"action_pred",
|
| 535 |
+
f"{backend}-{N_REPLAYS[backend]}_replays_bit_identical",
|
| 536 |
+
eq_replays and eq_host,
|
| 537 |
+
n_replays=N_REPLAYS[backend],
|
| 538 |
+
replay_ms=[round(s * 1e3, 3) for s in rec["replay_s"]],
|
| 539 |
+
)
|
| 540 |
+
_row(results, "action_pred", f"{backend}-split_replayer_equals_predict", rec["split_raw_equal"])
|
| 541 |
+
if "traced_velocity" in rec:
|
| 542 |
+
eq_vel = bool(torch.equal(rec["traced_velocity"], rec["untraced_velocity"]))
|
| 543 |
+
_row(results, "velocity_out", f"{backend}-traced_equals_untraced", eq_vel)
|
| 544 |
+
ok_all = ok_all and eq_vel
|
| 545 |
+
print(
|
| 546 |
+
f"[{version}/{sample}] {backend}: traced==untraced {eq_pred}, {N_REPLAYS[backend]} replays identical "
|
| 547 |
+
f"{eq_replays and eq_host}, replay+D2H ms {[round(s * 1e3, 2) for s in rec['replay_s']]}"
|
| 548 |
+
)
|
| 549 |
+
ok_all = ok_all and eq_pred and eq_bb and eq_kv and eq_replays and eq_host and rec["split_raw_equal"]
|
| 550 |
+
assert ok_all, "see the rows: a backend's traced path is not bit-equal to its untraced path / not deterministic"
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
def test_mk_e2e_denoise_trace_ms(
|
| 554 |
+
version: str,
|
| 555 |
+
sample: str,
|
| 556 |
+
golden: Tuple[GoldenSet, Any, Any],
|
| 557 |
+
backend_results: Dict[str, Dict[str, Dict[str, Any]]],
|
| 558 |
+
results: harness.ResultsWriter,
|
| 559 |
+
) -> None:
|
| 560 |
+
"""Informational split-pass trace times of both backends under alloc tracking (bench_e2e is the latency source)."""
|
| 561 |
+
_meta(results, version, sample, backend_results)
|
| 562 |
+
mk, s1 = backend_results["megakernel"][sample], backend_results["ttnn"][sample]
|
| 563 |
+
stages = list(mk["split_ms"])
|
| 564 |
+
denoise = stages[-1]
|
| 565 |
+
ratio = mk["split_ms"][denoise] / s1["split_ms"][denoise]
|
| 566 |
+
for st in stages:
|
| 567 |
+
_row(
|
| 568 |
+
results,
|
| 569 |
+
"trace",
|
| 570 |
+
f"split_ms/{st}",
|
| 571 |
+
True,
|
| 572 |
+
rule="timing",
|
| 573 |
+
ttnn_ms=s1["split_ms"][st],
|
| 574 |
+
megakernel_ms=mk["split_ms"][st],
|
| 575 |
+
ratio_mk_over_ttnn=mk["split_ms"][st] / s1["split_ms"][st],
|
| 576 |
+
note="median of synchronised replays under TT_METAL_TRACE_ALLOC_TRACKING=1 (informational)",
|
| 577 |
+
)
|
| 578 |
+
results.set_timing(
|
| 579 |
+
mk_denoise_split_s=mk["split_ms"][denoise] / 1e3,
|
| 580 |
+
ttnn_denoise_split_s=s1["split_ms"][denoise] / 1e3,
|
| 581 |
+
mk_untraced_device_s=mk["untraced_device_s"],
|
| 582 |
+
ttnn_untraced_device_s=s1["untraced_device_s"],
|
| 583 |
+
mk_replay_median_s=sorted(mk["replay_s"])[len(mk["replay_s"]) // 2],
|
| 584 |
+
ttnn_replay_median_s=sorted(s1["replay_s"])[len(s1["replay_s"]) // 2],
|
| 585 |
+
)
|
| 586 |
+
results.set_meta(denoise_trace=denoise, denoise_ratio_mk_over_ttnn=ratio)
|
| 587 |
+
print(
|
| 588 |
+
f"[{version}/{sample}] split ms ttnn {({k: round(v, 2) for k, v in s1['split_ms'].items()})} | "
|
| 589 |
+
f"megakernel {({k: round(v, 2) for k, v in mk['split_ms'].items()})} | denoise ratio {ratio:.3f}"
|
| 590 |
+
)
|
| 591 |
+
assert ratio > 0 and all(v > 0 for v in mk["split_ms"].values())
|
code/models/experimental/gr00t/tests/tt/test_mk_head_sdpa.py
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""WP-K3 tests of the megakernel's ``HeadSDPA`` (one head per core, flash-style over 64-key chunks) and the head
|
| 5 |
+
exchange, run through the standalone harness kernel ``kernels/test/sdpa_harness_kernel.cpp`` (IMPLEMENTATION_PLAN.md
|
| 6 |
+
§5.4 P3/P4 + cross-block paragraph, §5.7 K3 row, R13; mb2 §B.1 mask / scale semantics).
|
| 7 |
+
|
| 8 |
+
* ``test_*_cpu`` -- the chunk arithmetic and DRAM tile-id maps the kernel uses, the test's own torch references
|
| 9 |
+
(masked == pad-key-excluded) and an fp32 emulation of the chunked running-softmax (half chunks included) -- no device;
|
| 10 |
+
* ``test_head_sdpa`` -- 32 heads on the 32 head cores, ``M = 64`` query rows with the version's logical rows
|
| 11 |
+
(49 / 51 / 41), random Q/K/V with padded rows / keys, Sk in {64, 96, 320, 384, 448, 544}: **self** mode (Sk 64:
|
| 12 |
+
K_h / V_h delivered by the BRISC of cores 32+h / 64+h into core h, plan §5.4 P3) and **cross** mode (K^T / V / mask
|
| 13 |
+
64-key chunks fetched from the DRAM hoist tensors into the 2-deep rings, pre-transposed K^T hoist and natural K
|
| 14 |
+
with the on-TRISC transpose), with / without the ``-1e9`` pad-key mask, explicit ``48**-0.5`` scale and the folded
|
| 15 |
+
variant (Q pre-scaled in bf16 = ``arena.fold_attn_scale``). Gate: PCC >= 0.9995 vs torch fp32 on the same bf16
|
| 16 |
+
inputs (valid rows; the masked case against the pad-key-excluded reference) **and** vs
|
| 17 |
+
``ttnn.transformer.scaled_dot_product_attention``; no NaN anywhere (all 64 rows x 32 heads); second launch
|
| 18 |
+
bit-identical;
|
| 19 |
+
* ``test_head_sdpa_timing`` -- per-head µs per Sk (mb3 difference method over in-kernel iterations), K/V/mask fetch
|
| 20 |
+
GB/s of the cross mode.
|
| 21 |
+
|
| 22 |
+
Run (one pytest process per file, through the device lock)::
|
| 23 |
+
|
| 24 |
+
DEVICE_LOCK_TIMEOUT=14400 bin/with-device.sh python -m pytest -q -rA --timeout=1800 -p no:cacheprovider \\
|
| 25 |
+
models/experimental/gr00t/tests/tt/test_mk_head_sdpa.py --gr00t-version n16
|
| 26 |
+
|
| 27 |
+
``ttnn`` is imported lazily (device tests only); the CPU tests import nothing from ``ttnn``.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
from __future__ import annotations
|
| 31 |
+
|
| 32 |
+
import itertools
|
| 33 |
+
import math
|
| 34 |
+
import struct
|
| 35 |
+
import time
|
| 36 |
+
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple
|
| 37 |
+
|
| 38 |
+
import pytest
|
| 39 |
+
import torch
|
| 40 |
+
|
| 41 |
+
from models.experimental.gr00t.common import pcc as pcc_mod
|
| 42 |
+
from models.experimental.gr00t.tt.megakernel import arena as A
|
| 43 |
+
from models.experimental.gr00t.tt.megakernel import dit_program as P
|
| 44 |
+
from models.experimental.gr00t.tt.megakernel.core_map import N_HEADS, CoreMap
|
| 45 |
+
|
| 46 |
+
torch.set_grad_enabled(False)
|
| 47 |
+
|
| 48 |
+
KERNEL_SOURCE = "models/experimental/gr00t/tt/megakernel/kernels/test/sdpa_harness_kernel.cpp"
|
| 49 |
+
GATE_PCC = 0.9995 # WP-K3 acceptance (plan §5.7 K3 row)
|
| 50 |
+
SCALE = 48**-0.5 # cfg.dit.attention_scale (head_dim 48 padded to 64: the explicit scale is mandatory, mb2 §B.1)
|
| 51 |
+
MASK_NEG = -1e9 # static-shape-plan §4.3: a large finite negative, never -inf
|
| 52 |
+
TILE = 32
|
| 53 |
+
M_ROWS = 64
|
| 54 |
+
HEAD_DIM = 64
|
| 55 |
+
#: logical query rows per version (LayoutConfig.dit_m_logical: 49 / 51 / 41; asserted against ArenaLayout on CPU)
|
| 56 |
+
M_LOGICAL: Mapping[str, int] = {"n15": 49, "n16": 51, "n17": 41}
|
| 57 |
+
SK_LIST: Tuple[int, ...] = (64, 96, 320, 384, 448, 544)
|
| 58 |
+
#: valid keys per Sk (the mask excludes the rest): Sk 64 uses the version's logical rows (self mask), the cross values
|
| 59 |
+
#: are real layouts (n16 image 81/96, n15 all 296/320 and 297/384, n17 alt image 528/544) or a synthetic pad (441/448)
|
| 60 |
+
N_VALID: Mapping[int, int] = {96: 81, 320: 296, 384: 297, 448: 441, 544: 528}
|
| 61 |
+
#: ttnn SDPA chunk pairs per Sk (q_chunk 64; (64, 64) verified for 64 / 96 / 320 / 512 in mb2 §B.1, (64, 128) for
|
| 62 |
+
#: 384 / 448 in tt/layers.py SDPA_CHUNKS; (64, 320) / (64, 448) are the forbidden pairs)
|
| 63 |
+
TTNN_CHUNKS: Mapping[int, Tuple[int, int]] = {
|
| 64 |
+
64: (64, 64),
|
| 65 |
+
96: (64, 64),
|
| 66 |
+
320: (64, 64),
|
| 67 |
+
384: (64, 128),
|
| 68 |
+
448: (64, 128),
|
| 69 |
+
544: (64, 64),
|
| 70 |
+
}
|
| 71 |
+
MODES: Tuple[str, ...] = ("self", "cross")
|
| 72 |
+
K_LAYOUTS: Tuple[str, ...] = ("transposed", "natural")
|
| 73 |
+
SEED = 1234
|
| 74 |
+
CB_IDS: Mapping[str, int] = {
|
| 75 |
+
"cb_in": 0,
|
| 76 |
+
"cb_o": 1,
|
| 77 |
+
"cb_k": 2,
|
| 78 |
+
"cb_v": 3,
|
| 79 |
+
"cb_kt": 4,
|
| 80 |
+
"cb_mask": 5,
|
| 81 |
+
"cb_zero": 6,
|
| 82 |
+
"cb_ones": 7,
|
| 83 |
+
"cb_s": 8,
|
| 84 |
+
"cb_oacc": 9,
|
| 85 |
+
"cb_m0": 10,
|
| 86 |
+
"cb_m1": 11,
|
| 87 |
+
"cb_l0": 12,
|
| 88 |
+
"cb_l1": 13,
|
| 89 |
+
"cb_alpha": 14,
|
| 90 |
+
}
|
| 91 |
+
SEM_IDS: Mapping[str, int] = {"sem_kv": 0, "sem_credit": 1}
|
| 92 |
+
TILE_BF16 = TILE * TILE * 2
|
| 93 |
+
TILE_FP32 = TILE * TILE * 4
|
| 94 |
+
KV_SLOTS = 2 # 2-deep K / V / mask chunk rings (plan §5.5)
|
| 95 |
+
O_SLOTS = 2 # output slots of the tensor-backed cb_o (iteration i writes slot i % O_SLOTS)
|
| 96 |
+
#: (name, bytes, page bytes, dtype) of the plain CBs of the harness kernel (head_sdpa.hpp CB contract)
|
| 97 |
+
PLAIN_CBS: Tuple[Tuple[str, int, int, str], ...] = (
|
| 98 |
+
("cb_k", KV_SLOTS * 4 * TILE_BF16, TILE_BF16, "bf16"),
|
| 99 |
+
("cb_v", KV_SLOTS * 4 * TILE_BF16, TILE_BF16, "bf16"),
|
| 100 |
+
("cb_kt", 4 * TILE_BF16, TILE_BF16, "bf16"),
|
| 101 |
+
("cb_mask", KV_SLOTS * 4 * TILE_BF16, TILE_BF16, "bf16"),
|
| 102 |
+
("cb_zero", TILE_BF16, TILE_BF16, "bf16"),
|
| 103 |
+
("cb_ones", TILE_BF16, TILE_BF16, "bf16"),
|
| 104 |
+
("cb_s", 4 * TILE_FP32, TILE_FP32, "fp32"),
|
| 105 |
+
("cb_oacc", 4 * TILE_FP32, TILE_FP32, "fp32"),
|
| 106 |
+
("cb_m0", 2 * TILE_FP32, TILE_FP32, "fp32"),
|
| 107 |
+
("cb_m1", 2 * TILE_FP32, TILE_FP32, "fp32"),
|
| 108 |
+
("cb_l0", 2 * TILE_FP32, TILE_FP32, "fp32"),
|
| 109 |
+
("cb_l1", 2 * TILE_FP32, TILE_FP32, "fp32"),
|
| 110 |
+
("cb_alpha", 2 * TILE_FP32, TILE_FP32, "fp32"),
|
| 111 |
+
)
|
| 112 |
+
#: common runtime args of the harness kernel (sdpa_harness_kernel.cpp enum RT), then compute_xy[96]
|
| 113 |
+
RT_NAMES: Tuple[str, ...] = (
|
| 114 |
+
"mode",
|
| 115 |
+
"n_iters",
|
| 116 |
+
"sk_tiles",
|
| 117 |
+
"use_mask",
|
| 118 |
+
"scale_bits",
|
| 119 |
+
"k_natural",
|
| 120 |
+
"addr_k",
|
| 121 |
+
"addr_v",
|
| 122 |
+
"addr_mask",
|
| 123 |
+
"kv_slots",
|
| 124 |
+
"o_slots",
|
| 125 |
+
"debug_stage",
|
| 126 |
+
"debug_chunk",
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 131 |
+
# CPU-side mirrors of the kernel contract
|
| 132 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 133 |
+
def n_valid_keys(sk: int, m_logical: int) -> int:
|
| 134 |
+
"""Valid keys of the masked case: the version's logical rows for the self mask (Sk 64), :data:`N_VALID` else."""
|
| 135 |
+
if sk == M_ROWS:
|
| 136 |
+
return m_logical
|
| 137 |
+
if sk not in N_VALID:
|
| 138 |
+
raise KeyError(f"no valid-key count for Sk {sk}; expected one of {sorted(N_VALID)} or 64")
|
| 139 |
+
return N_VALID[sk]
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def chunk_plan(sk: int) -> List[int]:
|
| 143 |
+
"""Key tiles per 64-key chunk (``HeadSDPAParams::chunk_kt``): ``[2, 2, ..., 1?]``; Sk must be a multiple of 32."""
|
| 144 |
+
if sk <= 0 or sk % TILE:
|
| 145 |
+
raise ValueError(f"Sk must be a positive multiple of {TILE}, got {sk}")
|
| 146 |
+
kt = sk // TILE
|
| 147 |
+
out = []
|
| 148 |
+
while kt > 0:
|
| 149 |
+
out.append(min(2, kt))
|
| 150 |
+
kt -= out[-1]
|
| 151 |
+
return out
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def fp32_bits(x: float) -> int:
|
| 155 |
+
"""The fp32 bit pattern of ``x`` as the kernel's ``scale_bits`` runtime arg."""
|
| 156 |
+
return struct.unpack("<I", struct.pack("<f", float(x)))[0]
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def tile_id_kv(h: int, key_tile: int, d_tile: int, lt: int) -> int:
|
| 160 |
+
"""Tile id of ``(h, key_tile, d_tile)`` in a TILE_LAYOUT ``[1, H, L_pad, 64]`` tensor (``HoistChunkFetcher``)."""
|
| 161 |
+
return (h * lt + key_tile) * 2 + d_tile
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def tile_id_kT(h: int, d_tile: int, key_tile: int, lt: int) -> int:
|
| 165 |
+
"""Tile id of ``(h, d_tile, key_tile)`` in a TILE_LAYOUT ``[1, H, 64, L_pad]`` tensor (pre-transposed hoist)."""
|
| 166 |
+
return (h * 2 + d_tile) * lt + key_tile
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def tile_id_mask(r: int, key_tile: int, lt: int) -> int:
|
| 170 |
+
"""Tile id of ``(r, key_tile)`` in a TILE_LAYOUT ``[1, 1, 64, L_pad]`` mask."""
|
| 171 |
+
return r * lt + key_tile
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def tiles_row_major(t: torch.Tensor) -> torch.Tensor:
|
| 175 |
+
"""``[..., R, C]`` -> ``[n_tiles, 32, 32]`` in the TILE_LAYOUT page order (row-major over the flattened tile grid)."""
|
| 176 |
+
r, c = t.shape[-2], t.shape[-1]
|
| 177 |
+
if r % TILE or c % TILE:
|
| 178 |
+
raise ValueError(f"not tile aligned: {tuple(t.shape)}")
|
| 179 |
+
flat = t.reshape(-1, r, c)
|
| 180 |
+
tiles = flat.reshape(flat.shape[0], r // TILE, TILE, c // TILE, TILE).permute(0, 1, 3, 2, 4)
|
| 181 |
+
return tiles.reshape(-1, TILE, TILE)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 185 |
+
# inputs and references
|
| 186 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 187 |
+
def make_inputs(sk: int, m_logical: int, seed: int) -> Dict[str, torch.Tensor]:
|
| 188 |
+
"""Random bf16 ``q [32, 64, 64]``, ``k`` / ``v [32, Sk, 64]`` (pad rows / keys are random too, so a leak shows) and
|
| 189 |
+
the additive ``mask [64, Sk]`` (0 on the ``n_valid_keys`` valid keys, ``-1e9`` elsewhere)."""
|
| 190 |
+
g = torch.Generator().manual_seed(seed)
|
| 191 |
+
q = torch.randn(N_HEADS, M_ROWS, HEAD_DIM, generator=g).to(torch.bfloat16)
|
| 192 |
+
k = torch.randn(N_HEADS, sk, HEAD_DIM, generator=g).to(torch.bfloat16)
|
| 193 |
+
v = torch.randn(N_HEADS, sk, HEAD_DIM, generator=g).to(torch.bfloat16)
|
| 194 |
+
nv = n_valid_keys(sk, m_logical)
|
| 195 |
+
mask = torch.zeros(M_ROWS, sk, dtype=torch.bfloat16)
|
| 196 |
+
mask[:, nv:] = MASK_NEG
|
| 197 |
+
return {"q": q, "k": k, "v": v, "mask": mask}
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def fold_scale(q: torch.Tensor, scale: float) -> torch.Tensor:
|
| 201 |
+
"""The folded variant: Q already carries the softmax scale (bf16-rounded like ``arena.fold_attn_scale`` applied to
|
| 202 |
+
Wq / bq)."""
|
| 203 |
+
return (q.float() * scale).to(torch.bfloat16)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def torch_reference(
|
| 207 |
+
q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, mask: Optional[torch.Tensor], scale: float
|
| 208 |
+
) -> torch.Tensor:
|
| 209 |
+
"""fp32 ``softmax(scale * q k^T + mask) v`` on the bf16 inputs; ``[H, 64, 64]`` fp32."""
|
| 210 |
+
s = torch.matmul(q.float(), k.float().transpose(-1, -2)) * scale
|
| 211 |
+
if mask is not None:
|
| 212 |
+
s = s + mask.float()
|
| 213 |
+
p = torch.softmax(s, dim=-1)
|
| 214 |
+
return torch.matmul(p, v.float())
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def flash_emulation(
|
| 218 |
+
q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, mask: Optional[torch.Tensor], scale: float
|
| 219 |
+
) -> torch.Tensor:
|
| 220 |
+
"""fp32 emulation of the kernel's chunked loop (head_sdpa.hpp steps 2-6 + finalise) with the same chunk plan."""
|
| 221 |
+
qf, kf, vf = q.float(), k.float(), v.float()
|
| 222 |
+
sk = kf.shape[1]
|
| 223 |
+
o = torch.zeros(qf.shape[0], M_ROWS, HEAD_DIM)
|
| 224 |
+
m = torch.full((qf.shape[0], M_ROWS, 1), -float("inf"))
|
| 225 |
+
l = torch.zeros(qf.shape[0], M_ROWS, 1)
|
| 226 |
+
key = 0
|
| 227 |
+
for kt in chunk_plan(sk):
|
| 228 |
+
keys = slice(key, key + kt * TILE)
|
| 229 |
+
s = torch.matmul(qf, kf[:, keys].transpose(-1, -2)) * scale
|
| 230 |
+
if mask is not None:
|
| 231 |
+
s = s + mask[:, keys].float()
|
| 232 |
+
m_new = torch.maximum(m, s.amax(dim=-1, keepdim=True))
|
| 233 |
+
p = torch.exp(s - m_new)
|
| 234 |
+
alpha = torch.exp(m - m_new)
|
| 235 |
+
l = alpha * l + p.sum(dim=-1, keepdim=True)
|
| 236 |
+
o = alpha * o + torch.matmul(p, vf[:, keys])
|
| 237 |
+
m = m_new
|
| 238 |
+
key += kt * TILE
|
| 239 |
+
return o / l
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 243 |
+
# CPU tests
|
| 244 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 245 |
+
def test_m_logical_matches_arena_cpu(version: str) -> None:
|
| 246 |
+
"""The logical rows of the test (49 / 51 / 41) are the ArenaLayout's ``m_logical`` for the version."""
|
| 247 |
+
L = A.ArenaLayout.plan(version, tile_order="row_major")
|
| 248 |
+
assert L.m_logical == M_LOGICAL[version], (version, L.m_logical)
|
| 249 |
+
assert L.m_pad == M_ROWS and L.head_dim_pad == HEAD_DIM and L.n_heads == N_HEADS
|
| 250 |
+
assert math.isclose(L.cfg.dit.attention_scale, SCALE, rel_tol=1e-9)
|
| 251 |
+
for ks, lp in L.l_pad.items():
|
| 252 |
+
assert lp % TILE == 0 and (lp in SK_LIST), (ks, lp) # every real key-set length is in the tested Sk list
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def test_chunk_plan_and_tile_ids_cpu() -> None:
|
| 256 |
+
"""Chunk plan of every Sk (half chunk for 96 / 544) and the DRAM tile-id maps of ``HoistChunkFetcher``."""
|
| 257 |
+
assert chunk_plan(64) == [2] and chunk_plan(96) == [2, 1] and chunk_plan(320) == [2] * 5
|
| 258 |
+
assert chunk_plan(384) == [2] * 6 and chunk_plan(448) == [2] * 7 and chunk_plan(544) == [2] * 8 + [1]
|
| 259 |
+
with pytest.raises(ValueError):
|
| 260 |
+
chunk_plan(80)
|
| 261 |
+
h, sk = 5, 96
|
| 262 |
+
lt = sk // TILE
|
| 263 |
+
kv = torch.arange(N_HEADS * sk * HEAD_DIM, dtype=torch.float32).reshape(1, N_HEADS, sk, HEAD_DIM)
|
| 264 |
+
tiles = tiles_row_major(kv)
|
| 265 |
+
for key_t in range(lt):
|
| 266 |
+
for d in range(2):
|
| 267 |
+
ref = kv[0, h, key_t * TILE : (key_t + 1) * TILE, d * TILE : (d + 1) * TILE]
|
| 268 |
+
assert torch.equal(tiles[tile_id_kv(h, key_t, d, lt)], ref)
|
| 269 |
+
kT = kv.transpose(-1, -2).contiguous() # [1, H, 64, Sk]
|
| 270 |
+
tiles_t = tiles_row_major(kT)
|
| 271 |
+
for d in range(2):
|
| 272 |
+
for key_t in range(lt):
|
| 273 |
+
ref = kT[0, h, d * TILE : (d + 1) * TILE, key_t * TILE : (key_t + 1) * TILE]
|
| 274 |
+
assert torch.equal(tiles_t[tile_id_kT(h, d, key_t, lt)], ref)
|
| 275 |
+
mask = torch.arange(M_ROWS * sk, dtype=torch.float32).reshape(1, 1, M_ROWS, sk)
|
| 276 |
+
tiles_m = tiles_row_major(mask)
|
| 277 |
+
for r in range(2):
|
| 278 |
+
for key_t in range(lt):
|
| 279 |
+
assert torch.equal(
|
| 280 |
+
tiles_m[tile_id_mask(r, key_t, lt)],
|
| 281 |
+
mask[0, 0, r * TILE : (r + 1) * TILE, key_t * TILE : (key_t + 1) * TILE],
|
| 282 |
+
)
|
| 283 |
+
assert fp32_bits(1.0) == 0x3F800000 and fp32_bits(SCALE) == 0x3E13CD3A
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
@pytest.mark.parametrize("sk", SK_LIST)
|
| 287 |
+
def test_references_cpu(sk: int) -> None:
|
| 288 |
+
"""The masked torch reference equals the pad-key-excluded one; the chunked fp32 emulation reproduces the full
|
| 289 |
+
softmax for every Sk (incl. the half-chunk tails) with and without the mask."""
|
| 290 |
+
m_logical = M_LOGICAL["n16"]
|
| 291 |
+
x = make_inputs(sk, m_logical, SEED + sk)
|
| 292 |
+
nv = n_valid_keys(sk, m_logical)
|
| 293 |
+
masked = torch_reference(x["q"], x["k"], x["v"], x["mask"], SCALE)
|
| 294 |
+
excluded = torch_reference(x["q"], x["k"][:, :nv], x["v"][:, :nv], None, SCALE)
|
| 295 |
+
assert torch.allclose(masked, excluded, atol=1e-5, rtol=1e-5)
|
| 296 |
+
assert pcc_mod.pcc(masked, excluded) > 0.999999
|
| 297 |
+
for mask in (x["mask"], None):
|
| 298 |
+
ref = torch_reference(x["q"], x["k"], x["v"], mask, SCALE)
|
| 299 |
+
emu = flash_emulation(x["q"], x["k"], x["v"], mask, SCALE)
|
| 300 |
+
assert torch.isfinite(emu).all()
|
| 301 |
+
assert torch.allclose(emu, ref, atol=2e-5, rtol=1e-4), (sk, mask is None)
|
| 302 |
+
# the unmasked reference is a different function of the inputs (pad keys leak): the test can tell them apart
|
| 303 |
+
leak = torch_reference(x["q"], x["k"], x["v"], None, SCALE)
|
| 304 |
+
assert pcc_mod.pcc(leak, excluded) < 0.9995
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 308 |
+
# the harness launcher (device)
|
| 309 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 310 |
+
class HeadSDPAHarness:
|
| 311 |
+
"""Launches ``sdpa_harness_kernel.cpp`` over the 96 compute cores of the megakernel core map.
|
| 312 |
+
|
| 313 |
+
Tensors: ``qkv_in`` ``[96*64, 64]`` bf16 HEIGHT_SHARDED (shard of compute core ``c`` = Q_c for ``c < 32``, and in
|
| 314 |
+
self mode K_{c-32} / V_{c-64} for the sender cores), the DRAM hoists ``k_hoist`` (``[1,32,64,Sk]`` K^T or
|
| 315 |
+
``[1,32,Sk,64]`` K), ``v_hoist`` ``[1,32,Sk,64]``, ``mask`` ``[1,1,64,Sk]``, and the output ``o_out``
|
| 316 |
+
``[96*64, 64*O_SLOTS]`` bf16 HEIGHT_SHARDED (tensor-backed ``cb_o``).
|
| 317 |
+
"""
|
| 318 |
+
|
| 319 |
+
def __init__(self, device: Any) -> None:
|
| 320 |
+
import ttnn # lazy
|
| 321 |
+
|
| 322 |
+
self._ttnn = ttnn
|
| 323 |
+
self.device = device
|
| 324 |
+
self.core_map = CoreMap.from_device(device)
|
| 325 |
+
self._crs = self.core_map.to_core_range_sets()
|
| 326 |
+
self._shard_cores = self._device_shard_order()
|
| 327 |
+
self._xy = [self._pack_xy(c) for c in self.core_map.compute]
|
| 328 |
+
self._per_core = self.core_map.per_core_args()
|
| 329 |
+
self.launches = 0
|
| 330 |
+
self.compile_s: Optional[float] = None
|
| 331 |
+
|
| 332 |
+
# ------------------------------------------------------------------ helpers
|
| 333 |
+
def _device_shard_order(self) -> List[int]:
|
| 334 |
+
ttnn = self._ttnn
|
| 335 |
+
order = P.shard_order(self.core_map)
|
| 336 |
+
cores = ttnn.corerange_to_cores(self._crs["compute"], None, True)
|
| 337 |
+
got = [self.core_map.compute_idx_of((int(c.x), int(c.y))) for c in cores]
|
| 338 |
+
if got != order:
|
| 339 |
+
raise RuntimeError("ttnn shard order of the compute CoreRangeSet differs from CoreMap row-major order")
|
| 340 |
+
return order
|
| 341 |
+
|
| 342 |
+
def _pack_xy(self, core: Tuple[int, int]) -> int:
|
| 343 |
+
v = self.device.worker_core_from_logical_core(self._ttnn.CoreCoord(core[0], core[1]))
|
| 344 |
+
if not (0 <= v.x < 256 and 0 <= v.y < 256):
|
| 345 |
+
raise ValueError(f"NoC coordinate {v} does not fit a byte")
|
| 346 |
+
return int(v.x) | (int(v.y) << 8)
|
| 347 |
+
|
| 348 |
+
def _sharded(self, t: torch.Tensor, cols: int) -> Any:
|
| 349 |
+
ttnn = self._ttnn
|
| 350 |
+
if t.shape != (self.core_map.n_compute * M_ROWS, cols):
|
| 351 |
+
raise ValueError(
|
| 352 |
+
f"sharded tensor must be [{self.core_map.n_compute * M_ROWS}, {cols}], got {tuple(t.shape)}"
|
| 353 |
+
)
|
| 354 |
+
spec = ttnn.ShardSpec(self._crs["compute"], [M_ROWS, cols], ttnn.ShardOrientation.ROW_MAJOR)
|
| 355 |
+
mem = ttnn.MemoryConfig(ttnn.TensorMemoryLayout.HEIGHT_SHARDED, ttnn.BufferType.L1, spec)
|
| 356 |
+
return ttnn.from_torch(t, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=self.device, memory_config=mem)
|
| 357 |
+
|
| 358 |
+
def _dram(self, t: torch.Tensor) -> Any:
|
| 359 |
+
ttnn = self._ttnn
|
| 360 |
+
dev = ttnn.from_torch(
|
| 361 |
+
t, dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=self.device, memory_config=ttnn.DRAM_MEMORY_CONFIG
|
| 362 |
+
)
|
| 363 |
+
page = int(dev.buffer_page_size())
|
| 364 |
+
if page != TILE_BF16:
|
| 365 |
+
raise RuntimeError(f"DRAM tile tensor page {page} B != {TILE_BF16}: the kernel reads 2 KB tile pages")
|
| 366 |
+
return dev
|
| 367 |
+
|
| 368 |
+
def head_output(self, out: torch.Tensor, h: int, slot: int) -> torch.Tensor:
|
| 369 |
+
"""O_h ``[64, 64]`` from the ``o_out`` readback: the ``[64, 128]`` shard of head core ``h`` holds its 8 tile
|
| 370 |
+
pages row-major over a 2 x 4 tile grid, cb_o slot ``s`` = pages ``4s..4s+3`` = shard rows ``32s..32s+31``, and
|
| 371 |
+
page ``4s + p`` is the head's output tile ``(r, c) = (p // 2, p % 2)``."""
|
| 372 |
+
i = self._shard_cores.index(h)
|
| 373 |
+
shard = out[i * M_ROWS : (i + 1) * M_ROWS]
|
| 374 |
+
o = torch.empty(M_ROWS, HEAD_DIM, dtype=out.dtype)
|
| 375 |
+
for pg in range(4):
|
| 376 |
+
r, c = divmod(pg, 2)
|
| 377 |
+
o[r * TILE : (r + 1) * TILE, c * TILE : (c + 1) * TILE] = shard[
|
| 378 |
+
slot * TILE : (slot + 1) * TILE, pg * TILE : (pg + 1) * TILE
|
| 379 |
+
]
|
| 380 |
+
return o
|
| 381 |
+
|
| 382 |
+
# ------------------------------------------------------------------ program
|
| 383 |
+
def _program(self, io: Mapping[str, Any], rt_values: Mapping[str, int]) -> Any:
|
| 384 |
+
ttnn = self._ttnn
|
| 385 |
+
from models.demos.deepseek_v3_b1.unified_kernel_descriptor import (
|
| 386 |
+
PerCoreRuntimeArgsDescriptor,
|
| 387 |
+
UnifiedKernelDescriptor,
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
crs = self._crs["compute"]
|
| 391 |
+
dt = {"bf16": ttnn.bfloat16, "fp32": ttnn.float32}
|
| 392 |
+
tile_desc = ttnn.TileDescriptor(ttnn.Tile([TILE, TILE]))
|
| 393 |
+
cbs = [
|
| 394 |
+
ttnn.cb_descriptor_from_sharded_tensor(CB_IDS["cb_in"], io["qkv_in"]),
|
| 395 |
+
ttnn.cb_descriptor_from_sharded_tensor(CB_IDS["cb_o"], io["o_out"]),
|
| 396 |
+
]
|
| 397 |
+
for name, total, page, fmt in PLAIN_CBS:
|
| 398 |
+
f = ttnn.CBFormatDescriptor(buffer_index=CB_IDS[name], data_format=dt[fmt], page_size=page, tile=tile_desc)
|
| 399 |
+
cbs.append(ttnn.CBDescriptor(total_size=total, core_ranges=crs, format_descriptors=[f]))
|
| 400 |
+
sems = [ttnn.SemaphoreDescriptor(id=i, core_ranges=crs, initial_value=0) for i in SEM_IDS.values()]
|
| 401 |
+
named = [(k, v) for k, v in CB_IDS.items()] + [(k, v) for k, v in SEM_IDS.items()]
|
| 402 |
+
named += [("n_heads", N_HEADS), ("n_compute", self.core_map.n_compute)]
|
| 403 |
+
common = [int(rt_values[k]) for k in RT_NAMES] + list(self._xy)
|
| 404 |
+
for w in common:
|
| 405 |
+
if not 0 <= w < 2**32:
|
| 406 |
+
raise ValueError(f"runtime arg {w} does not fit uint32")
|
| 407 |
+
per_core = [
|
| 408 |
+
(ttnn.CoreCoord(x, y), list(self._per_core[(x, y)]))
|
| 409 |
+
for (x, y) in sorted(self.core_map.compute, key=lambda c: (c[1], c[0]))
|
| 410 |
+
]
|
| 411 |
+
uk = UnifiedKernelDescriptor(
|
| 412 |
+
kernel_source=KERNEL_SOURCE,
|
| 413 |
+
core_ranges=crs,
|
| 414 |
+
ncrisc_named_compile_time_args=named,
|
| 415 |
+
brisc_named_compile_time_args=named,
|
| 416 |
+
trisc_named_compile_time_args=named,
|
| 417 |
+
ncrisc_common_runtime_args=common,
|
| 418 |
+
brisc_common_runtime_args=common,
|
| 419 |
+
trisc_common_runtime_args=common,
|
| 420 |
+
trisc_compute_config=ttnn.ComputeConfigDescriptor(
|
| 421 |
+
math_fidelity=ttnn.MathFidelity.HiFi2, fp32_dest_acc_en=True, math_approx_mode=False
|
| 422 |
+
),
|
| 423 |
+
per_core_runtime_args_descriptor=PerCoreRuntimeArgsDescriptor(
|
| 424 |
+
ncrisc_args=per_core, brisc_args=per_core, trisc_args=per_core
|
| 425 |
+
),
|
| 426 |
+
)
|
| 427 |
+
result = uk.get_kernel_descriptors()
|
| 428 |
+
if len(result.groups) != 1:
|
| 429 |
+
raise RuntimeError(f"harness kernel compiled into {len(result.groups)} groups, expected 1")
|
| 430 |
+
return ttnn.ProgramDescriptor(kernels=result.kernels, semaphores=sems, cbs=cbs)
|
| 431 |
+
|
| 432 |
+
# ------------------------------------------------------------------ run
|
| 433 |
+
def run(
|
| 434 |
+
self,
|
| 435 |
+
q: torch.Tensor,
|
| 436 |
+
k: torch.Tensor,
|
| 437 |
+
v: torch.Tensor,
|
| 438 |
+
mask: Optional[torch.Tensor],
|
| 439 |
+
*,
|
| 440 |
+
mode: str,
|
| 441 |
+
scale: Optional[float],
|
| 442 |
+
k_layout: str = "transposed",
|
| 443 |
+
n_iters: int = 1,
|
| 444 |
+
debug: Tuple[int, int] = (0, 0),
|
| 445 |
+
) -> Tuple[torch.Tensor, float]:
|
| 446 |
+
"""One launch; returns ``(O [32, 64, 64] bf16 of the last iteration, launch + synchronize wall seconds)``.
|
| 447 |
+
|
| 448 |
+
``scale=None`` = folded (the kernel skips the SFPU scale); ``k_layout`` is the cross-mode hoist layout
|
| 449 |
+
(``"transposed"`` = K^T ``[1,32,64,Sk]``, ``"natural"`` = K ``[1,32,Sk,64]`` + on-TRISC transpose); self mode
|
| 450 |
+
always delivers natural K_h. ``debug=(stage, chunk)`` (bring-up only) makes the TRISC dump the intermediate
|
| 451 |
+
of ``stage`` (head_sdpa.hpp ``debug_stage``) at chunk ``chunk`` into the output slot instead of O.
|
| 452 |
+
"""
|
| 453 |
+
ttnn = self._ttnn
|
| 454 |
+
if mode not in MODES:
|
| 455 |
+
raise ValueError(f"mode must be in {MODES}, got {mode!r}")
|
| 456 |
+
if k_layout not in K_LAYOUTS:
|
| 457 |
+
raise ValueError(f"k_layout must be in {K_LAYOUTS}, got {k_layout!r}")
|
| 458 |
+
if q.shape != (N_HEADS, M_ROWS, HEAD_DIM) or q.dtype != torch.bfloat16:
|
| 459 |
+
raise ValueError(f"q must be bf16 [{N_HEADS}, 64, 64], got {tuple(q.shape)} {q.dtype}")
|
| 460 |
+
sk = int(k.shape[1])
|
| 461 |
+
if k.shape != (N_HEADS, sk, HEAD_DIM) or v.shape != (N_HEADS, sk, HEAD_DIM) or sk % TILE or sk < TILE:
|
| 462 |
+
raise ValueError(
|
| 463 |
+
f"k / v must be [{N_HEADS}, Sk (multiple of 32), 64], got {tuple(k.shape)} {tuple(v.shape)}"
|
| 464 |
+
)
|
| 465 |
+
if k.dtype != torch.bfloat16 or v.dtype != torch.bfloat16:
|
| 466 |
+
raise ValueError("k / v must be bf16")
|
| 467 |
+
if mask is not None and (mask.shape != (M_ROWS, sk) or mask.dtype != torch.bfloat16):
|
| 468 |
+
raise ValueError(f"mask must be bf16 [64, {sk}] or None, got {tuple(mask.shape)}")
|
| 469 |
+
if mode == "self" and sk != M_ROWS:
|
| 470 |
+
raise ValueError(f"self mode exchanges one 64-key chunk; Sk {sk} needs cross mode")
|
| 471 |
+
if n_iters < 1:
|
| 472 |
+
raise ValueError("n_iters >= 1")
|
| 473 |
+
n_c = self.core_map.n_compute
|
| 474 |
+
k_natural = 1 if (mode == "self" or k_layout == "natural") else 0
|
| 475 |
+
|
| 476 |
+
qkv = torch.zeros(n_c * M_ROWS, HEAD_DIM, dtype=torch.bfloat16)
|
| 477 |
+
for i, c in enumerate(self._shard_cores):
|
| 478 |
+
rows = slice(i * M_ROWS, (i + 1) * M_ROWS)
|
| 479 |
+
if c < N_HEADS:
|
| 480 |
+
qkv[rows] = q[c]
|
| 481 |
+
elif mode == "self" and c < 2 * N_HEADS:
|
| 482 |
+
qkv[rows] = k[c - N_HEADS]
|
| 483 |
+
elif mode == "self":
|
| 484 |
+
qkv[rows] = v[c - 2 * N_HEADS]
|
| 485 |
+
io: Dict[str, Any] = {"qkv_in": self._sharded(qkv, HEAD_DIM)}
|
| 486 |
+
k_host = k.reshape(1, N_HEADS, sk, HEAD_DIM)
|
| 487 |
+
if not k_natural:
|
| 488 |
+
k_host = k_host.transpose(-1, -2).contiguous()
|
| 489 |
+
io["k_hoist"] = self._dram(k_host)
|
| 490 |
+
io["v_hoist"] = self._dram(v.reshape(1, N_HEADS, sk, HEAD_DIM))
|
| 491 |
+
io["mask"] = self._dram(
|
| 492 |
+
mask.reshape(1, 1, M_ROWS, sk) if mask is not None else torch.zeros(1, 1, TILE, TILE, dtype=torch.bfloat16)
|
| 493 |
+
)
|
| 494 |
+
io["o_out"] = self._sharded(
|
| 495 |
+
torch.zeros(n_c * M_ROWS, HEAD_DIM * O_SLOTS, dtype=torch.bfloat16), HEAD_DIM * O_SLOTS
|
| 496 |
+
)
|
| 497 |
+
rt = {
|
| 498 |
+
"mode": MODES.index(mode),
|
| 499 |
+
"n_iters": n_iters,
|
| 500 |
+
"sk_tiles": sk // TILE,
|
| 501 |
+
"use_mask": 0 if mask is None else 1,
|
| 502 |
+
"scale_bits": 0 if scale is None else fp32_bits(scale),
|
| 503 |
+
"k_natural": k_natural,
|
| 504 |
+
"addr_k": int(io["k_hoist"].buffer_address()),
|
| 505 |
+
"addr_v": int(io["v_hoist"].buffer_address()),
|
| 506 |
+
"addr_mask": int(io["mask"].buffer_address()),
|
| 507 |
+
"kv_slots": KV_SLOTS,
|
| 508 |
+
"o_slots": O_SLOTS,
|
| 509 |
+
"debug_stage": int(debug[0]),
|
| 510 |
+
"debug_chunk": int(debug[1]),
|
| 511 |
+
}
|
| 512 |
+
if scale is not None and rt["scale_bits"] == 0:
|
| 513 |
+
raise ValueError("an explicit scale of 0.0 is indistinguishable from the folded variant")
|
| 514 |
+
program = self._program(io, rt)
|
| 515 |
+
tensors = [io["qkv_in"], io["k_hoist"], io["v_hoist"], io["mask"], io["o_out"]]
|
| 516 |
+
t0 = time.perf_counter()
|
| 517 |
+
ttnn.generic_op(tensors, program)
|
| 518 |
+
ttnn.synchronize_device(self.device)
|
| 519 |
+
dt = time.perf_counter() - t0
|
| 520 |
+
if self.compile_s is None:
|
| 521 |
+
self.compile_s = dt
|
| 522 |
+
self.launches += 1
|
| 523 |
+
out = ttnn.to_torch(io["o_out"])
|
| 524 |
+
slot = (n_iters - 1) % O_SLOTS
|
| 525 |
+
o = torch.stack([self.head_output(out, h, slot) for h in range(N_HEADS)])
|
| 526 |
+
for t in io.values():
|
| 527 |
+
ttnn.deallocate(t)
|
| 528 |
+
return o, dt
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
def ttnn_reference(
|
| 532 |
+
device: Any,
|
| 533 |
+
q: torch.Tensor,
|
| 534 |
+
k: torch.Tensor,
|
| 535 |
+
v: torch.Tensor,
|
| 536 |
+
mask: Optional[torch.Tensor],
|
| 537 |
+
scale: float,
|
| 538 |
+
chunks: Tuple[int, int],
|
| 539 |
+
) -> torch.Tensor:
|
| 540 |
+
"""``ttnn.transformer.scaled_dot_product_attention`` (HiFi2, fp32 acc, exp_approx_mode False, explicit scale) on
|
| 541 |
+
``[1, 32, 64, 64] x [1, 32, Sk, 64]`` with the additive ``[1, 1, 64, Sk]`` mask; returns ``[32, 64, 64]`` fp32."""
|
| 542 |
+
import ttnn
|
| 543 |
+
|
| 544 |
+
kw = dict(dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=device, memory_config=ttnn.DRAM_MEMORY_CONFIG)
|
| 545 |
+
tq = ttnn.from_torch(q.reshape(1, N_HEADS, M_ROWS, HEAD_DIM), **kw)
|
| 546 |
+
tk = ttnn.from_torch(k.reshape(1, N_HEADS, -1, HEAD_DIM), **kw)
|
| 547 |
+
tv = ttnn.from_torch(v.reshape(1, N_HEADS, -1, HEAD_DIM), **kw)
|
| 548 |
+
tm = ttnn.from_torch(mask.reshape(1, 1, M_ROWS, -1), **kw) if mask is not None else None
|
| 549 |
+
grid = device.compute_with_storage_grid_size()
|
| 550 |
+
pc = ttnn.SDPAProgramConfig(
|
| 551 |
+
compute_with_storage_grid_size=ttnn.CoreCoord(grid.x, grid.y),
|
| 552 |
+
q_chunk_size=chunks[0],
|
| 553 |
+
k_chunk_size=chunks[1],
|
| 554 |
+
exp_approx_mode=False,
|
| 555 |
+
)
|
| 556 |
+
ckc = ttnn.WormholeComputeKernelConfig(
|
| 557 |
+
math_fidelity=ttnn.MathFidelity.HiFi2, math_approx_mode=False, fp32_dest_acc_en=True, packer_l1_acc=False
|
| 558 |
+
)
|
| 559 |
+
out = ttnn.transformer.scaled_dot_product_attention(
|
| 560 |
+
tq, tk, tv, attn_mask=tm, is_causal=False, scale=float(scale), program_config=pc, compute_kernel_config=ckc
|
| 561 |
+
)
|
| 562 |
+
res = ttnn.to_torch(out).float().reshape(N_HEADS, M_ROWS, HEAD_DIM)
|
| 563 |
+
for t in (tq, tk, tv, tm, out):
|
| 564 |
+
if t is not None:
|
| 565 |
+
ttnn.deallocate(t)
|
| 566 |
+
return res
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
class _Session:
|
| 570 |
+
"""One harness per pytest session; ttnn references cached per (version, sk, mask, folded)."""
|
| 571 |
+
|
| 572 |
+
harness: Optional[HeadSDPAHarness] = None
|
| 573 |
+
refs: Dict[Tuple[Any, ...], torch.Tensor] = {}
|
| 574 |
+
|
| 575 |
+
@classmethod
|
| 576 |
+
def get(cls, device: Any) -> HeadSDPAHarness:
|
| 577 |
+
if cls.harness is None:
|
| 578 |
+
cls.harness = HeadSDPAHarness(device)
|
| 579 |
+
return cls.harness
|
| 580 |
+
|
| 581 |
+
@classmethod
|
| 582 |
+
def ttnn_ref(
|
| 583 |
+
cls,
|
| 584 |
+
device: Any,
|
| 585 |
+
key: Tuple[Any, ...],
|
| 586 |
+
q: torch.Tensor,
|
| 587 |
+
k: torch.Tensor,
|
| 588 |
+
v: torch.Tensor,
|
| 589 |
+
mask: Optional[torch.Tensor],
|
| 590 |
+
scale: float,
|
| 591 |
+
chunks: Tuple[int, int],
|
| 592 |
+
) -> torch.Tensor:
|
| 593 |
+
if key not in cls.refs:
|
| 594 |
+
cls.refs[key] = ttnn_reference(device, q, k, v, mask, scale, chunks)
|
| 595 |
+
return cls.refs[key]
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 599 |
+
# device tests
|
| 600 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 601 |
+
def _cases() -> List[Tuple[str, int, bool, bool, str]]:
|
| 602 |
+
"""(mode, Sk, use_mask, folded, k_layout): self x Sk 64 x mask x scale, cross x every Sk x mask x scale x layout."""
|
| 603 |
+
cases: List[Tuple[str, int, bool, bool, str]] = []
|
| 604 |
+
for use_mask, folded in itertools.product((True, False), (False, True)):
|
| 605 |
+
cases.append(("self", M_ROWS, use_mask, folded, "natural"))
|
| 606 |
+
for sk, use_mask, folded, layout in itertools.product(SK_LIST, (True, False), (False, True), K_LAYOUTS):
|
| 607 |
+
cases.append(("cross", sk, use_mask, folded, layout))
|
| 608 |
+
return cases
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
CASES = _cases()
|
| 612 |
+
CASE_IDS = [
|
| 613 |
+
f"{m}-sk{sk}-{'mask' if um else 'nomask'}-{'folded' if f else 'explicit'}-{kl}" for m, sk, um, f, kl in CASES
|
| 614 |
+
]
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
@pytest.mark.parametrize("mode,sk,use_mask,folded,k_layout", CASES, ids=CASE_IDS)
|
| 618 |
+
def test_head_sdpa(
|
| 619 |
+
gr00t_device: Any, results: Any, version: str, mode: str, sk: int, use_mask: bool, folded: bool, k_layout: str
|
| 620 |
+
) -> None:
|
| 621 |
+
"""32 heads through the harness kernel vs torch fp32 (valid rows; masked -> pad-key-excluded reference) and vs
|
| 622 |
+
ttnn SDPA: PCC >= 0.9995, no NaN over all 64 rows, second launch bit-identical."""
|
| 623 |
+
hz = _Session.get(gr00t_device)
|
| 624 |
+
m_logical = M_LOGICAL[version]
|
| 625 |
+
x = make_inputs(sk, m_logical, SEED + sk)
|
| 626 |
+
q = fold_scale(x["q"], SCALE) if folded else x["q"]
|
| 627 |
+
ref_scale = 1.0 if folded else SCALE
|
| 628 |
+
mask = x["mask"] if use_mask else None
|
| 629 |
+
nv = n_valid_keys(sk, m_logical)
|
| 630 |
+
# references: the masked case must equal the reference over the valid keys only (pad keys excluded)
|
| 631 |
+
if use_mask:
|
| 632 |
+
ref = torch_reference(q, x["k"][:, :nv], x["v"][:, :nv], None, ref_scale)
|
| 633 |
+
else:
|
| 634 |
+
ref = torch_reference(q, x["k"], x["v"], None, ref_scale)
|
| 635 |
+
ref_key = (version, sk, use_mask, folded)
|
| 636 |
+
ttnn_err: Optional[str] = None
|
| 637 |
+
try:
|
| 638 |
+
ref_ttnn: Optional[torch.Tensor] = _Session.ttnn_ref(
|
| 639 |
+
gr00t_device, ref_key, q, x["k"], x["v"], mask, ref_scale, TTNN_CHUNKS[sk]
|
| 640 |
+
)
|
| 641 |
+
except Exception as exc: # noqa: BLE001 - recorded in the row, fails the gate
|
| 642 |
+
ref_ttnn, ttnn_err = None, f"{type(exc).__name__}: {exc}"[:400]
|
| 643 |
+
|
| 644 |
+
got, dt = hz.run(
|
| 645 |
+
x["q"] if not folded else q, x["k"], x["v"], mask, mode=mode, scale=None if folded else SCALE, k_layout=k_layout
|
| 646 |
+
)
|
| 647 |
+
got2, _ = hz.run(
|
| 648 |
+
x["q"] if not folded else q, x["k"], x["v"], mask, mode=mode, scale=None if folded else SCALE, k_layout=k_layout
|
| 649 |
+
)
|
| 650 |
+
gotf = got.float()
|
| 651 |
+
finite = bool(torch.isfinite(gotf).all())
|
| 652 |
+
valid = slice(0, m_logical)
|
| 653 |
+
m_torch = pcc_mod.metrics(gotf[:, valid], ref[:, valid])
|
| 654 |
+
pcc_all_rows = pcc_mod.pcc(gotf, ref) if finite else float("nan")
|
| 655 |
+
pcc_ttnn = pcc_mod.pcc(gotf[:, valid], ref_ttnn[:, valid]) if (finite and ref_ttnn is not None) else float("nan")
|
| 656 |
+
pcc_ttnn_vs_torch = pcc_mod.pcc(ref_ttnn[:, valid], ref[:, valid]) if ref_ttnn is not None else float("nan")
|
| 657 |
+
deterministic = bool(torch.equal(got, got2))
|
| 658 |
+
ok = bool(finite and m_torch["pcc"] >= GATE_PCC and pcc_ttnn >= GATE_PCC and deterministic)
|
| 659 |
+
row = {
|
| 660 |
+
"tap": "mk_k3_head_sdpa",
|
| 661 |
+
"key": f"{mode}-sk{sk}-{'mask' if use_mask else 'nomask'}-{'folded' if folded else 'explicit'}-{k_layout}",
|
| 662 |
+
"rule": "pcc",
|
| 663 |
+
"pcc": m_torch["pcc"],
|
| 664 |
+
"gate_pcc": GATE_PCC,
|
| 665 |
+
"pcc_ttnn": pcc_ttnn,
|
| 666 |
+
"pcc_ttnn_vs_torch": pcc_ttnn_vs_torch,
|
| 667 |
+
"pcc_all_rows": pcc_all_rows,
|
| 668 |
+
"max_abs": m_torch["max_abs"],
|
| 669 |
+
"rel_l2": m_torch["rel_l2"],
|
| 670 |
+
"finite": finite,
|
| 671 |
+
"deterministic": deterministic,
|
| 672 |
+
"mode": mode,
|
| 673 |
+
"sk": sk,
|
| 674 |
+
"n_chunks": len(chunk_plan(sk)),
|
| 675 |
+
"n_valid_keys": nv if use_mask else sk,
|
| 676 |
+
"m_logical": m_logical,
|
| 677 |
+
"use_mask": use_mask,
|
| 678 |
+
"folded": folded,
|
| 679 |
+
"k_layout": k_layout,
|
| 680 |
+
"ttnn_chunks": list(TTNN_CHUNKS[sk]),
|
| 681 |
+
"ttnn_error": ttnn_err,
|
| 682 |
+
"wall_s": dt,
|
| 683 |
+
"ok": ok,
|
| 684 |
+
}
|
| 685 |
+
results.add_row(row)
|
| 686 |
+
results.set_meta(compile_s=hz.compile_s, launches=hz.launches, core_map_hub=list(hz.core_map.hub))
|
| 687 |
+
assert finite, f"non-finite output ({(~torch.isfinite(gotf)).sum().item()} elements)"
|
| 688 |
+
assert deterministic, "second launch differs from the first"
|
| 689 |
+
assert (
|
| 690 |
+
m_torch["pcc"] >= GATE_PCC
|
| 691 |
+
), f"PCC vs torch {m_torch['pcc']:.6f} < {GATE_PCC} (max|d| {m_torch['max_abs']:.4f})"
|
| 692 |
+
assert ttnn_err is None, f"ttnn SDPA reference failed: {ttnn_err}"
|
| 693 |
+
assert pcc_ttnn >= GATE_PCC, f"PCC vs ttnn SDPA {pcc_ttnn:.6f} < {GATE_PCC} (ttnn vs torch {pcc_ttnn_vs_torch:.6f})"
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
def _timing_cases() -> List[Tuple[str, int, str]]:
|
| 697 |
+
cases = [("self", M_ROWS, "natural")]
|
| 698 |
+
cases += [("cross", sk, "transposed") for sk in SK_LIST]
|
| 699 |
+
cases += [("cross", sk, "natural") for sk in (M_ROWS, 544)]
|
| 700 |
+
return cases
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
TIMING_CASES = _timing_cases()
|
| 704 |
+
#: in-kernel iteration counts of the difference method: the per-head cost is a few us, so the window must be >= 1 ms
|
| 705 |
+
#: (200 iterations) to sit well above the ~300 us generic_op launch overhead and its jitter (R21, mb3 F5)
|
| 706 |
+
N_SMALL, N_LARGE, REPEATS = 20, 220, 3
|
| 707 |
+
|
| 708 |
+
|
| 709 |
+
@pytest.mark.parametrize("mode,sk,k_layout", TIMING_CASES, ids=[f"{m}-sk{sk}-{kl}" for m, sk, kl in TIMING_CASES])
|
| 710 |
+
def test_head_sdpa_timing(gr00t_device: Any, results: Any, version: str, mode: str, sk: int, k_layout: str) -> None:
|
| 711 |
+
"""Per-head µs (all 32 heads run in parallel, so the per-iteration time of the launch is the per-head time) with
|
| 712 |
+
the mask and the explicit scale, mb3 difference method: ``(t(220) - t(20)) / 200`` in-kernel iterations (every
|
| 713 |
+
iteration re-delivers K/V/mask like a DiT block would), min of 3."""
|
| 714 |
+
hz = _Session.get(gr00t_device)
|
| 715 |
+
m_logical = M_LOGICAL[version]
|
| 716 |
+
x = make_inputs(sk, m_logical, SEED + sk)
|
| 717 |
+
ref = torch_reference(
|
| 718 |
+
x["q"], x["k"][:, : n_valid_keys(sk, m_logical)], x["v"][:, : n_valid_keys(sk, m_logical)], None, SCALE
|
| 719 |
+
)
|
| 720 |
+
last: Dict[str, torch.Tensor] = {}
|
| 721 |
+
|
| 722 |
+
def launch(n: int) -> float:
|
| 723 |
+
got, dt = hz.run(x["q"], x["k"], x["v"], x["mask"], mode=mode, scale=SCALE, k_layout=k_layout, n_iters=n)
|
| 724 |
+
last["got"] = got
|
| 725 |
+
return dt
|
| 726 |
+
|
| 727 |
+
kv_bytes = 2 * N_HEADS * sk * HEAD_DIM * 2 # K + V of all heads (cross: DRAM hoist; self: NoC head exchange)
|
| 728 |
+
mask_bytes = N_HEADS * M_ROWS * sk * 2 # every head fetches its own copy of the mask from DRAM
|
| 729 |
+
bytes_per_iter = kv_bytes + mask_bytes
|
| 730 |
+
rate = P.measure_rate(launch, N_SMALL, N_LARGE, bytes_per_iter, repeats=REPEATS)
|
| 731 |
+
pcc_last = pcc_mod.pcc(last["got"].float()[:, :m_logical], ref[:, :m_logical])
|
| 732 |
+
us = rate["per_iter_s"] * 1e6
|
| 733 |
+
row = {
|
| 734 |
+
"tap": "mk_k3_timing",
|
| 735 |
+
"key": f"{mode}-sk{sk}-{k_layout}",
|
| 736 |
+
"rule": "info",
|
| 737 |
+
"mode": mode,
|
| 738 |
+
"sk": sk,
|
| 739 |
+
"k_layout": k_layout,
|
| 740 |
+
"n_chunks": len(chunk_plan(sk)),
|
| 741 |
+
"us_per_head": us,
|
| 742 |
+
"us_per_chunk": us / len(chunk_plan(sk)),
|
| 743 |
+
"fetch_gbps": rate["gbps"],
|
| 744 |
+
"bytes_per_iter": bytes_per_iter,
|
| 745 |
+
"t_small_s": rate["t_small_s"],
|
| 746 |
+
"t_large_s": rate["t_large_s"],
|
| 747 |
+
"n_small": N_SMALL,
|
| 748 |
+
"n_large": N_LARGE,
|
| 749 |
+
"pcc_last_iter": pcc_last,
|
| 750 |
+
"ok": bool(math.isfinite(us) and us > 0 and pcc_last >= GATE_PCC),
|
| 751 |
+
}
|
| 752 |
+
results.add_row(row)
|
| 753 |
+
results.set_timing(**{f"us_{mode}_sk{sk}_{k_layout}": us / 1e6})
|
| 754 |
+
assert row["ok"], f"timing run: {us:.2f} us/iter, PCC of the last iteration {pcc_last:.6f}"
|
code/models/experimental/gr00t/tests/tt/test_mk_layernorm.py
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""WP-K2 device tests of the megakernel's ``RowLayerNorm`` (plain LN / dynamic-affine AdaLN / residual-on-receive)
|
| 5 |
+
through its own single-core harness kernel (IMPLEMENTATION_PLAN.md §5.7 K2; kernels/test/ln_harness_kernel.cpp).
|
| 6 |
+
|
| 7 |
+
* ``test_helpers_cpu`` -- the host helpers (bf16 ulp metric, ``Single`` tile order pack/unpack, references);
|
| 8 |
+
* ``test_ln_plain_golden`` -- ``LN_eps(x)`` on the golden ``sa_embs`` of steps 0 and 3 (bf16-rounded, zero-padded
|
| 9 |
+
to 64 rows), eps 1e-5 (blocks) and 1e-6 (output head): PCC >= 0.9999 and max|d| <= 2 bf16 ulps vs torch fp32;
|
| 10 |
+
* ``test_adaln_golden`` -- ``LN(x) * (1 + s) + b`` with the in-kernel tilised ROW_MAJOR AdaLN rows, same gates, and
|
| 11 |
+
the **swapped (s, b) rows must fail** the PCC gate;
|
| 12 |
+
* ``test_residual_adaln`` -- ``x <- x + g`` (g in the P12 ``Single`` gather order, in place on the resident x) then
|
| 13 |
+
AdaLN: ``x_after`` bit-exact vs ``bf16(x + g)``, ``h`` gated as above;
|
| 14 |
+
* ``test_stress_rows`` -- synthetic rows with large |mean| / std, an outlier channel and a near-constant row;
|
| 15 |
+
* ``test_ln_replicated_96`` -- the same LN on the 96 compute cores of the CoreMap (every core bit-identical);
|
| 16 |
+
* ``test_ln_timing`` -- microseconds per LN (plain / AdaLN / residual + AdaLN) by the in-kernel iteration difference
|
| 17 |
+
method, plus the ``mk_ln*`` Tracy zones when ``GR00T_MK_PROFILE=1``;
|
| 18 |
+
* ``test_ff_branch_vs_s1`` -- ``x + ff2(GELU(ff1(LN(x))))`` = harness LN -> K1 ``run_single_op`` ff1 / ff2 ->
|
| 19 |
+
harness residual, vs the Stage-1 ttnn path (``tt.layers``) on device: PCC >= 0.9999.
|
| 20 |
+
|
| 21 |
+
Run (one pytest process per file, through the device lock)::
|
| 22 |
+
|
| 23 |
+
DEVICE_LOCK_TIMEOUT=14400 bin/with-device.sh python -m pytest -q -rA --timeout=1800 -p no:cacheprovider \\
|
| 24 |
+
models/experimental/gr00t/tests/tt/test_mk_layernorm.py --gr00t-version n16
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import csv
|
| 30 |
+
import math
|
| 31 |
+
import os
|
| 32 |
+
import struct
|
| 33 |
+
import time
|
| 34 |
+
from collections import defaultdict
|
| 35 |
+
from dataclasses import dataclass
|
| 36 |
+
from pathlib import Path
|
| 37 |
+
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
| 38 |
+
|
| 39 |
+
import pytest
|
| 40 |
+
import torch
|
| 41 |
+
|
| 42 |
+
from models.experimental.gr00t.common import pcc as pcc_mod
|
| 43 |
+
from models.experimental.gr00t.common.configs import get_config
|
| 44 |
+
from models.experimental.gr00t.common.golden import GoldenSet, golden_root
|
| 45 |
+
from models.experimental.gr00t.tests.tt.conftest import have_golden
|
| 46 |
+
|
| 47 |
+
if os.environ.get("GR00T_MK_PROFILE") == "1": # before the device fixture imports ttnn
|
| 48 |
+
os.environ.setdefault("TT_METAL_DEVICE_PROFILER", "1")
|
| 49 |
+
|
| 50 |
+
torch.set_grad_enabled(False)
|
| 51 |
+
|
| 52 |
+
KERNEL = "models/experimental/gr00t/tt/megakernel/kernels/test/ln_harness_kernel.cpp"
|
| 53 |
+
TT_METAL_HOME = Path(os.environ.get("TT_METAL_HOME", str(Path(__file__).resolve().parents[5])))
|
| 54 |
+
PROFILE_CSV = TT_METAL_HOME / "generated" / "profiler" / ".logs" / "profile_log_device.csv"
|
| 55 |
+
M_ROWS = 64
|
| 56 |
+
WIDTH = 1536
|
| 57 |
+
TILE = 32
|
| 58 |
+
N_KT = WIDTH // TILE
|
| 59 |
+
N_TILES = 2 * N_KT
|
| 60 |
+
EPS_BLOCK = 1e-5
|
| 61 |
+
EPS_HEAD = 1e-6
|
| 62 |
+
GATE_PCC = 0.9999
|
| 63 |
+
GATE_ULPS = 2.0
|
| 64 |
+
SEED = 11
|
| 65 |
+
STEPS = (0, 3)
|
| 66 |
+
#: CB ids of the harness (kernel named CTAs ln_cb_*)
|
| 67 |
+
CB: Dict[str, int] = {
|
| 68 |
+
"x": 0,
|
| 69 |
+
"g": 1,
|
| 70 |
+
"out": 2,
|
| 71 |
+
"const": 3,
|
| 72 |
+
"stat": 4,
|
| 73 |
+
"stat32": 5,
|
| 74 |
+
"y32": 6,
|
| 75 |
+
"adaln_raw": 7,
|
| 76 |
+
"adaln": 8,
|
| 77 |
+
"xc": 9,
|
| 78 |
+
}
|
| 79 |
+
G_ORDERS: Dict[str, int] = {"row_major": 0, "single": 2} # row_layernorm.hpp LNGOrder
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 83 |
+
# host helpers
|
| 84 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 85 |
+
def f32_bits(v: float) -> int:
|
| 86 |
+
"""fp32 bit pattern of ``v`` as an unsigned 32-bit int (kernel scalar arguments)."""
|
| 87 |
+
return struct.unpack("<I", struct.pack("<f", float(v)))[0]
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def bf16_ulp(t: torch.Tensor) -> torch.Tensor:
|
| 91 |
+
"""bf16 spacing at the magnitude of each element (``2^(floor(log2|t|) - 7)``; zero -> the smallest normal ulp)."""
|
| 92 |
+
a = t.detach().abs().double()
|
| 93 |
+
e = torch.floor(torch.log2(torch.clamp(a, min=2.0**-126)))
|
| 94 |
+
return torch.pow(2.0, e - 7)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def ulp_metrics(got: torch.Tensor, ref: torch.Tensor) -> Dict[str, float]:
|
| 98 |
+
"""``max|d|`` in bf16 ulps: elementwise at ``max(|ref_i|, rms(ref))`` (the acceptance metric: an element far
|
| 99 |
+
below the row's scale is judged at the ulp of the scale) and the plain per-element version for information."""
|
| 100 |
+
g, r = got.double().reshape(-1), ref.double().reshape(-1)
|
| 101 |
+
if g.numel() != r.numel():
|
| 102 |
+
raise ValueError(f"numel mismatch {g.numel()} vs {r.numel()}")
|
| 103 |
+
d = (g - r).abs()
|
| 104 |
+
rms = float(torch.sqrt(torch.mean(r * r)))
|
| 105 |
+
floor = torch.full_like(r, rms)
|
| 106 |
+
ulp_scale = bf16_ulp(torch.maximum(r.abs(), floor))
|
| 107 |
+
return {
|
| 108 |
+
"max_abs": float(d.max()),
|
| 109 |
+
"max_ulps": float((d / ulp_scale).max()),
|
| 110 |
+
"mean_ulps": float((d / ulp_scale).mean()),
|
| 111 |
+
"max_ulps_elementwise": float((d / bf16_ulp(r)).max()),
|
| 112 |
+
"rms_ref": rms,
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def bf16_round_ties_away(t: torch.Tensor) -> torch.Tensor:
|
| 117 |
+
"""fp32 -> bf16 with ties rounded away from zero (the Tensix packer's fp32 -> bf16 conversion, measured in
|
| 118 |
+
test_residual_adaln: torch rounds ties to even), returned as fp32 values."""
|
| 119 |
+
b = t.float().contiguous().view(torch.int32).to(torch.int64) & 0xFFFFFFFF
|
| 120 |
+
upper = b >> 16
|
| 121 |
+
lower = b & 0xFFFF
|
| 122 |
+
upper = torch.where(lower >= 0x8000, upper + 1, upper)
|
| 123 |
+
return ((upper & 0xFFFF) << 16).to(torch.int32).view(torch.float32)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def ref_layernorm(
|
| 127 |
+
x: torch.Tensor, eps: float, one_plus_scale: Optional[torch.Tensor] = None, shift: Optional[torch.Tensor] = None
|
| 128 |
+
) -> torch.Tensor:
|
| 129 |
+
"""fp32 ``layer_norm(x) [* (1 + s) + b]`` of a bf16-representable ``x`` (reference.action_head.AdaLayerNorm),
|
| 130 |
+
rounded to bf16 (the device output dtype)."""
|
| 131 |
+
if x.shape != (M_ROWS, WIDTH):
|
| 132 |
+
raise ValueError(f"x must be [64, 1536], got {tuple(x.shape)}")
|
| 133 |
+
if (one_plus_scale is None) != (shift is None):
|
| 134 |
+
raise ValueError("one_plus_scale and shift must be given together")
|
| 135 |
+
y = torch.nn.functional.layer_norm(x.float(), (WIDTH,), eps=float(eps))
|
| 136 |
+
if one_plus_scale is not None:
|
| 137 |
+
y = y * one_plus_scale.float().reshape(1, WIDTH) + shift.float().reshape(1, WIDTH)
|
| 138 |
+
return y.to(torch.bfloat16).float()
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def to_single_order(g: torch.Tensor) -> torch.Tensor:
|
| 142 |
+
"""[64, 1536] -> the [64, 1536] TILE-layout *shard image* whose page ``2k + r`` holds tile ``(r, k)`` (the P12
|
| 143 |
+
gather placement, dit_program.py ``Single`` order)."""
|
| 144 |
+
if g.shape != (M_ROWS, WIDTH):
|
| 145 |
+
raise ValueError(f"g must be [64, 1536], got {tuple(g.shape)}")
|
| 146 |
+
out = torch.empty_like(g)
|
| 147 |
+
for k in range(N_KT):
|
| 148 |
+
for r in range(2):
|
| 149 |
+
p = 2 * k + r
|
| 150 |
+
R, C = divmod(p, N_KT)
|
| 151 |
+
out[R * TILE : (R + 1) * TILE, C * TILE : (C + 1) * TILE] = g[
|
| 152 |
+
r * TILE : (r + 1) * TILE, k * TILE : (k + 1) * TILE
|
| 153 |
+
]
|
| 154 |
+
return out
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def from_single_order(shard: torch.Tensor) -> torch.Tensor:
|
| 158 |
+
"""Inverse of :func:`to_single_order`."""
|
| 159 |
+
if shard.shape != (M_ROWS, WIDTH):
|
| 160 |
+
raise ValueError(f"shard must be [64, 1536], got {tuple(shard.shape)}")
|
| 161 |
+
out = torch.empty_like(shard)
|
| 162 |
+
for k in range(N_KT):
|
| 163 |
+
for r in range(2):
|
| 164 |
+
p = 2 * k + r
|
| 165 |
+
R, C = divmod(p, N_KT)
|
| 166 |
+
out[r * TILE : (r + 1) * TILE, k * TILE : (k + 1) * TILE] = shard[
|
| 167 |
+
R * TILE : (R + 1) * TILE, C * TILE : (C + 1) * TILE
|
| 168 |
+
]
|
| 169 |
+
return out
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def golden_sa_embs(version: str, step: int) -> torch.Tensor:
|
| 173 |
+
"""``sa_embs[k=step]`` of the canonical golden sample, bf16-rounded and zero-padded to 64 rows (fp32 values)."""
|
| 174 |
+
gs = GoldenSet(version)
|
| 175 |
+
t = gs.load("sa_embs", "fp32", step=step)
|
| 176 |
+
if t.dim() != 3 or t.shape[0] != 1 or t.shape[2] != WIDTH or t.shape[1] > M_ROWS:
|
| 177 |
+
raise ValueError(f"{version}: sa_embs shape {tuple(t.shape)} unexpected")
|
| 178 |
+
x = torch.zeros(M_ROWS, WIDTH)
|
| 179 |
+
x[: t.shape[1]] = t[0]
|
| 180 |
+
return x.to(torch.bfloat16).float()
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def random_adaln(seed: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 184 |
+
"""``(1 + s, b)`` bf16 vectors of the magnitude of the checkpoint AdaLN outputs (s, b ~ N(0, 0.3^2))."""
|
| 185 |
+
gen = torch.Generator().manual_seed(seed)
|
| 186 |
+
s = 0.3 * torch.randn(WIDTH, generator=gen)
|
| 187 |
+
b = 0.3 * torch.randn(WIDTH, generator=gen)
|
| 188 |
+
return (1.0 + s).to(torch.bfloat16).float(), b.to(torch.bfloat16).float()
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def stress_rows() -> torch.Tensor:
|
| 192 |
+
"""Synthetic [64, 1536]: N(0,1) rows plus a large-|mean| row, an outlier-channel row (mb2 §B.3 case), a
|
| 193 |
+
near-constant row, a large-scale row and a zero row -- bf16-rounded."""
|
| 194 |
+
gen = torch.Generator().manual_seed(3)
|
| 195 |
+
x = torch.randn(M_ROWS, WIDTH, generator=gen)
|
| 196 |
+
x[1] = 100.0 + torch.randn(WIDTH, generator=gen) # |mean| / std = 100
|
| 197 |
+
x[2, 1279] = 1.53e4 # massive activation channel (mb2 §B.3 magnitudes, moved inside the 1536 width)
|
| 198 |
+
x[2, 1500] = -7.8e3
|
| 199 |
+
x[3] = 5.0 + 1e-2 * torch.randn(WIDTH, generator=gen) # near-constant
|
| 200 |
+
x[4] = 300.0 * torch.randn(WIDTH, generator=gen) # large scale (sa_embs absmax ~ 400-1000)
|
| 201 |
+
x[5] = 0.0 # zero row (a padded query row): LN -> 0
|
| 202 |
+
x[6] = 1e-3 * torch.randn(WIDTH, generator=gen) # tiny scale
|
| 203 |
+
return x.to(torch.bfloat16).float()
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 207 |
+
# the launcher
|
| 208 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 209 |
+
@dataclass
|
| 210 |
+
class LNResult:
|
| 211 |
+
h: torch.Tensor # [n_cores*64, 1536] bf16 -> fp32 (per-core LN outputs stacked)
|
| 212 |
+
x_after: torch.Tensor # the resident x after the launch (== x + g with a residual)
|
| 213 |
+
wall_s: float
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class LNHarness:
|
| 217 |
+
"""Launch ``ln_harness_kernel.cpp`` on ``cores`` (each core runs the same LN on its own replica of x).
|
| 218 |
+
|
| 219 |
+
Every call builds the program afresh (tensor-backed CB addresses change between allocations) and frees its
|
| 220 |
+
device tensors before returning; the kernel binary is JIT-cached per (CTAs) by tt-metal.
|
| 221 |
+
"""
|
| 222 |
+
|
| 223 |
+
def __init__(self, device: Any, cores: Sequence[Tuple[int, int]]):
|
| 224 |
+
import ttnn # lazy
|
| 225 |
+
|
| 226 |
+
self.ttnn = ttnn
|
| 227 |
+
self.device = device
|
| 228 |
+
self.cores = [(int(x), int(y)) for x, y in cores]
|
| 229 |
+
if not self.cores or len(set(self.cores)) != len(self.cores):
|
| 230 |
+
raise ValueError("cores must be a non-empty list of distinct logical cores")
|
| 231 |
+
self.crs = ttnn.CoreRangeSet(
|
| 232 |
+
[
|
| 233 |
+
ttnn.CoreRange(ttnn.CoreCoord(x, y), ttnn.CoreCoord(x, y))
|
| 234 |
+
for x, y in sorted(self.cores, key=lambda c: (c[1], c[0]))
|
| 235 |
+
]
|
| 236 |
+
)
|
| 237 |
+
self.shard_cores = sorted(self.cores, key=lambda c: (c[1], c[0])) # HEIGHT shard i <-> this core
|
| 238 |
+
self.launches = 0
|
| 239 |
+
|
| 240 |
+
# ------------------------------------------------------------------ tensors
|
| 241 |
+
def _sharded(self, t: torch.Tensor) -> Any:
|
| 242 |
+
ttnn = self.ttnn
|
| 243 |
+
spec = ttnn.ShardSpec(self.crs, [M_ROWS, WIDTH], ttnn.ShardOrientation.ROW_MAJOR)
|
| 244 |
+
mem = ttnn.MemoryConfig(ttnn.TensorMemoryLayout.HEIGHT_SHARDED, ttnn.BufferType.L1, spec)
|
| 245 |
+
return ttnn.from_torch(
|
| 246 |
+
t.to(torch.bfloat16), dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, device=self.device, memory_config=mem
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
def _cb(self, name: str, total: int, page: int, fmt: Any) -> Any:
|
| 250 |
+
ttnn = self.ttnn
|
| 251 |
+
f = ttnn.CBFormatDescriptor(
|
| 252 |
+
buffer_index=CB[name], data_format=fmt, page_size=page, tile=ttnn.TileDescriptor(ttnn.Tile([TILE, TILE]))
|
| 253 |
+
)
|
| 254 |
+
return ttnn.CBDescriptor(total_size=total, core_ranges=self.crs, format_descriptors=[f])
|
| 255 |
+
|
| 256 |
+
# ------------------------------------------------------------------ launch
|
| 257 |
+
def run(
|
| 258 |
+
self,
|
| 259 |
+
x: torch.Tensor,
|
| 260 |
+
*,
|
| 261 |
+
eps: float,
|
| 262 |
+
one_plus_scale: Optional[torch.Tensor] = None,
|
| 263 |
+
shift: Optional[torch.Tensor] = None,
|
| 264 |
+
g: Optional[torch.Tensor] = None,
|
| 265 |
+
g_order: str = "single",
|
| 266 |
+
n_iters: int = 1,
|
| 267 |
+
swap_rows: bool = False,
|
| 268 |
+
xc_fp32: bool = True,
|
| 269 |
+
) -> LNResult:
|
| 270 |
+
"""LN of ``x`` [64, 1536] (bf16-representable) on every core; ``one_plus_scale``/``shift`` [1536] enable the
|
| 271 |
+
dynamic affine (fetched from a ROW_MAJOR DRAM table like the arena's ``adaln_rows``), ``g`` [64, 1536] the
|
| 272 |
+
in-place residual add (uploaded in ``g_order``), ``n_iters`` repeats the LN in-kernel (timing; with a residual
|
| 273 |
+
every iteration adds g again). ``xc_fp32`` keeps the centred row scratch in fp32 (192 KB, <= 0.125 ulp of
|
| 274 |
+
centring error) instead of bf16 (96 KB, <= 0.5 ulp)."""
|
| 275 |
+
ttnn = self.ttnn
|
| 276 |
+
from models.demos.deepseek_v3_b1.unified_kernel_descriptor import (
|
| 277 |
+
UnifiedCompileTimeCoreDescriptor,
|
| 278 |
+
UnifiedKernelDescriptor,
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
if x.shape != (M_ROWS, WIDTH):
|
| 282 |
+
raise ValueError(f"x must be [64, 1536], got {tuple(x.shape)}")
|
| 283 |
+
if (one_plus_scale is None) != (shift is None):
|
| 284 |
+
raise ValueError("one_plus_scale and shift must be given together")
|
| 285 |
+
if g_order not in G_ORDERS:
|
| 286 |
+
raise KeyError(f"g_order must be one of {sorted(G_ORDERS)}")
|
| 287 |
+
if n_iters < 1:
|
| 288 |
+
raise ValueError("n_iters must be >= 1")
|
| 289 |
+
n = len(self.cores)
|
| 290 |
+
affine = one_plus_scale is not None
|
| 291 |
+
residual = g is not None
|
| 292 |
+
x_rep = self._sharded(x.repeat(n, 1))
|
| 293 |
+
g_img = torch.zeros(M_ROWS, WIDTH)
|
| 294 |
+
if residual:
|
| 295 |
+
if g.shape != (M_ROWS, WIDTH):
|
| 296 |
+
raise ValueError(f"g must be [64, 1536], got {tuple(g.shape)}")
|
| 297 |
+
g_img = to_single_order(g) if g_order == "single" else g.clone()
|
| 298 |
+
g_rep = self._sharded(g_img.repeat(n, 1))
|
| 299 |
+
out_rep = self._sharded(torch.zeros(n * M_ROWS, WIDTH))
|
| 300 |
+
# AdaLN table: 4 ROW_MAJOR rows [pad, 1+s, b, pad] (rows 1 / 2; swapped -> 2 / 1)
|
| 301 |
+
table = torch.zeros(4, WIDTH)
|
| 302 |
+
if affine:
|
| 303 |
+
table[1] = one_plus_scale.reshape(WIDTH)
|
| 304 |
+
table[2] = shift.reshape(WIDTH)
|
| 305 |
+
adaln_t = ttnn.from_torch(
|
| 306 |
+
table.to(torch.bfloat16),
|
| 307 |
+
dtype=ttnn.bfloat16,
|
| 308 |
+
layout=ttnn.ROW_MAJOR_LAYOUT,
|
| 309 |
+
device=self.device,
|
| 310 |
+
memory_config=ttnn.DRAM_MEMORY_CONFIG,
|
| 311 |
+
)
|
| 312 |
+
row_bytes = int(adaln_t.buffer_page_size())
|
| 313 |
+
if row_bytes != WIDTH * 2:
|
| 314 |
+
raise RuntimeError(f"adaln table page {row_bytes} B != one row ({WIDTH * 2} B): the kernel reads rows")
|
| 315 |
+
row_scale, row_shift = (2, 1) if swap_rows else (1, 2)
|
| 316 |
+
|
| 317 |
+
cbs = [
|
| 318 |
+
ttnn.cb_descriptor_from_sharded_tensor(CB["x"], x_rep),
|
| 319 |
+
ttnn.cb_descriptor_from_sharded_tensor(CB["g"], g_rep),
|
| 320 |
+
ttnn.cb_descriptor_from_sharded_tensor(CB["out"], out_rep),
|
| 321 |
+
self._cb("const", 3 * 2048, 2048, ttnn.bfloat16),
|
| 322 |
+
self._cb("stat", 2 * 4096, 4096, ttnn.float32),
|
| 323 |
+
self._cb("stat32", 2 * 4096, 4096, ttnn.float32),
|
| 324 |
+
self._cb("y32", 8 * 4096, 4096, ttnn.float32),
|
| 325 |
+
self._cb("adaln_raw", 4 * 2048, 2048, ttnn.bfloat16),
|
| 326 |
+
self._cb("adaln", 4 * 2048, 2048, ttnn.bfloat16),
|
| 327 |
+
(
|
| 328 |
+
self._cb("xc", N_KT * 4096, 4096, ttnn.float32)
|
| 329 |
+
if xc_fp32
|
| 330 |
+
else self._cb("xc", N_KT * 2048, 2048, ttnn.bfloat16)
|
| 331 |
+
),
|
| 332 |
+
]
|
| 333 |
+
named = [(f"ln_cb_{k}", v) for k, v in CB.items()] + [("ln_n_kt", N_KT)]
|
| 334 |
+
common = [
|
| 335 |
+
int(n_iters),
|
| 336 |
+
1 if affine else 0,
|
| 337 |
+
1 if residual else 0,
|
| 338 |
+
G_ORDERS[g_order],
|
| 339 |
+
f32_bits(eps),
|
| 340 |
+
f32_bits(1.0 / WIDTH),
|
| 341 |
+
int(adaln_t.buffer_address()),
|
| 342 |
+
row_scale,
|
| 343 |
+
row_shift,
|
| 344 |
+
row_bytes,
|
| 345 |
+
]
|
| 346 |
+
cc = ttnn.ComputeConfigDescriptor(
|
| 347 |
+
math_fidelity=ttnn.MathFidelity.HiFi4, math_approx_mode=False, fp32_dest_acc_en=True, dst_full_sync_en=False
|
| 348 |
+
)
|
| 349 |
+
modes = [ttnn.UnpackToDestMode.Default] * 64
|
| 350 |
+
modes[CB["stat32"]] = ttnn.UnpackToDestMode.UnpackToDestFp32 # copy_tile lands exact fp32 (row_layernorm.hpp)
|
| 351 |
+
cc.unpack_to_dest_mode = modes
|
| 352 |
+
uk = UnifiedKernelDescriptor(
|
| 353 |
+
kernel_source=KERNEL,
|
| 354 |
+
core_ranges=self.crs,
|
| 355 |
+
ncrisc_named_compile_time_args=named,
|
| 356 |
+
brisc_named_compile_time_args=named,
|
| 357 |
+
trisc_named_compile_time_args=named,
|
| 358 |
+
ncrisc_common_runtime_args=common,
|
| 359 |
+
brisc_common_runtime_args=common,
|
| 360 |
+
trisc_common_runtime_args=common,
|
| 361 |
+
trisc_compute_config=cc,
|
| 362 |
+
unified_compile_time_core_descriptors=[
|
| 363 |
+
UnifiedCompileTimeCoreDescriptor(
|
| 364 |
+
named_compile_time_arg="is_active_core", core_range=self.crs, value=1, other_value=0
|
| 365 |
+
)
|
| 366 |
+
],
|
| 367 |
+
)
|
| 368 |
+
program = ttnn.ProgramDescriptor(kernels=uk.get_kernel_descriptors().kernels, cbs=cbs)
|
| 369 |
+
t0 = time.perf_counter()
|
| 370 |
+
ttnn.generic_op([x_rep, g_rep, adaln_t, out_rep], program)
|
| 371 |
+
ttnn.synchronize_device(self.device)
|
| 372 |
+
wall = time.perf_counter() - t0
|
| 373 |
+
self.launches += 1
|
| 374 |
+
h = ttnn.to_torch(out_rep).float()
|
| 375 |
+
x_after = ttnn.to_torch(x_rep).float()
|
| 376 |
+
for t in (x_rep, g_rep, out_rep, adaln_t):
|
| 377 |
+
ttnn.deallocate(t)
|
| 378 |
+
return LNResult(h=h, x_after=x_after, wall_s=wall)
|
| 379 |
+
|
| 380 |
+
def per_core(self, t: torch.Tensor, i: int) -> torch.Tensor:
|
| 381 |
+
return t[i * M_ROWS : (i + 1) * M_ROWS]
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def measure_us(launch: Any, n_small: int = 2, n_large: int = 6, repeats: int = 3) -> Dict[str, float]:
|
| 385 |
+
"""dit_program.measure_rate's difference method: per-iteration seconds = (t(n_large) - t(n_small)) / (n_large -
|
| 386 |
+
n_small), min over ``repeats`` (dispatch and readback cancel)."""
|
| 387 |
+
launch(n_small)
|
| 388 |
+
launch(n_large)
|
| 389 |
+
t_small = min(launch(n_small) for _ in range(repeats))
|
| 390 |
+
t_large = min(launch(n_large) for _ in range(repeats))
|
| 391 |
+
per = (t_large - t_small) / (n_large - n_small)
|
| 392 |
+
return {"t_small_s": t_small, "t_large_s": t_large, "per_iter_us": per * 1e6}
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def read_ln_zones(path: Path = PROFILE_CSV) -> Dict[str, Dict[str, float]]:
|
| 396 |
+
"""Mean duration (us) of the ``mk_ln*`` TRISC zones of the device profiler CSV over all launches (bench_mk_stream
|
| 397 |
+
read_zones, restricted to this kernel's zones)."""
|
| 398 |
+
if not path.is_file():
|
| 399 |
+
raise FileNotFoundError(f"{path} not found (GR00T_MK_PROFILE=1 sets TT_METAL_DEVICE_PROFILER)")
|
| 400 |
+
with open(path) as fh:
|
| 401 |
+
header = fh.readline()
|
| 402 |
+
freq_mhz = float(header.split("CHIP_FREQ[MHz]:")[1].split(",")[0])
|
| 403 |
+
reader = csv.reader(fh)
|
| 404 |
+
cols = [c.strip() for c in next(reader)]
|
| 405 |
+
idx = {c: i for i, c in enumerate(cols)}
|
| 406 |
+
starts: Dict[tuple, int] = {}
|
| 407 |
+
durations: Dict[str, List[int]] = defaultdict(list)
|
| 408 |
+
for row in reader:
|
| 409 |
+
if len(row) < len(cols) - 1:
|
| 410 |
+
continue
|
| 411 |
+
zone = row[idx["zone name"]].strip()
|
| 412 |
+
if not zone.startswith("mk_ln"):
|
| 413 |
+
continue
|
| 414 |
+
key = (
|
| 415 |
+
row[idx["run host ID"]].strip(),
|
| 416 |
+
row[idx["core_x"]],
|
| 417 |
+
row[idx["core_y"]],
|
| 418 |
+
row[idx["RISC processor type"]].strip(),
|
| 419 |
+
zone,
|
| 420 |
+
)
|
| 421 |
+
t = int(row[idx["time[cycles since reset]"]])
|
| 422 |
+
typ = row[idx["type"]].strip()
|
| 423 |
+
if typ == "ZONE_START":
|
| 424 |
+
starts[key] = t
|
| 425 |
+
elif typ == "ZONE_END" and key in starts:
|
| 426 |
+
durations[f"{key[3]}:{zone}"].append(t - starts.pop(key))
|
| 427 |
+
return {
|
| 428 |
+
name: {"count": len(ds), "mean_us": sum(ds) / len(ds) / freq_mhz, "max_us": max(ds) / freq_mhz}
|
| 429 |
+
for name, ds in durations.items()
|
| 430 |
+
if ds
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 435 |
+
# CPU
|
| 436 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 437 |
+
def test_helpers_cpu() -> None:
|
| 438 |
+
g = torch.arange(M_ROWS * WIDTH, dtype=torch.float32).reshape(M_ROWS, WIDTH)
|
| 439 |
+
s = to_single_order(g)
|
| 440 |
+
assert not torch.equal(s, g) and torch.equal(from_single_order(s), g)
|
| 441 |
+
# page 2k + r holds tile (r, k): tile (1, 0) -> page 1 -> shard tile (0, 1)
|
| 442 |
+
assert torch.equal(s[:TILE, TILE : 2 * TILE], g[TILE:, :TILE])
|
| 443 |
+
ref = torch.tensor([1.0, 1.5, 3.0, 0.001, -2.0])
|
| 444 |
+
assert torch.allclose(
|
| 445 |
+
bf16_ulp(ref), torch.tensor([2.0**-7, 2.0**-7, 2.0**-6, 2.0**-17, 2.0**-6], dtype=torch.float64)
|
| 446 |
+
)
|
| 447 |
+
got = ref.clone()
|
| 448 |
+
got[0] += 2.0**-7 # 1 ulp at 1.0
|
| 449 |
+
m = ulp_metrics(got, ref)
|
| 450 |
+
assert abs(m["max_ulps_elementwise"] - 1.0) < 1e-6 and m["max_ulps"] <= 1.0 + 1e-6
|
| 451 |
+
assert f32_bits(1.0) == 0x3F800000 and f32_bits(1e-5) == 0x3727C5AC
|
| 452 |
+
x = stress_rows()
|
| 453 |
+
y = ref_layernorm(x, EPS_BLOCK)
|
| 454 |
+
assert torch.isfinite(y).all() and float(y[5].abs().max()) == 0.0
|
| 455 |
+
ops, sh = random_adaln(SEED)
|
| 456 |
+
y2 = ref_layernorm(x, EPS_BLOCK, ops, sh)
|
| 457 |
+
assert pcc_mod.pcc(y2, ref_layernorm(x, EPS_BLOCK, sh, ops)) < 0.95 # swapped rows are a different function
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 461 |
+
# device
|
| 462 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 463 |
+
def _harness(device: Any, n_cores: int = 1) -> LNHarness:
|
| 464 |
+
from models.experimental.gr00t.tt.megakernel.core_map import CoreMap
|
| 465 |
+
|
| 466 |
+
cm = CoreMap.from_device(device)
|
| 467 |
+
return LNHarness(device, list(cm.compute[:n_cores]))
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def _need_golden(version: str) -> None:
|
| 471 |
+
if not golden_root().is_dir() or not have_golden(version):
|
| 472 |
+
pytest.skip(f"{version}: canonical golden sample not present under {golden_root()}")
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def _row(
|
| 476 |
+
results: Any, key: str, got: torch.Tensor, ref: torch.Tensor, expect_pass: bool = True, **extra: Any
|
| 477 |
+
) -> Dict[str, Any]:
|
| 478 |
+
m = pcc_mod.metrics(got.float(), ref.float())
|
| 479 |
+
u = ulp_metrics(got, ref)
|
| 480 |
+
ok = bool(m["finite"] and m["pcc"] >= GATE_PCC and u["max_ulps"] <= GATE_ULPS)
|
| 481 |
+
row = {
|
| 482 |
+
"tap": "mk_k2_ln",
|
| 483 |
+
"key": key,
|
| 484 |
+
"rule": "pcc+ulp",
|
| 485 |
+
"pcc": m["pcc"],
|
| 486 |
+
"gate_pcc": GATE_PCC,
|
| 487 |
+
"gate_ulps": GATE_ULPS,
|
| 488 |
+
"max_abs": u["max_abs"],
|
| 489 |
+
"max_ulps": u["max_ulps"],
|
| 490 |
+
"mean_ulps": u["mean_ulps"],
|
| 491 |
+
"max_ulps_elementwise": u["max_ulps_elementwise"],
|
| 492 |
+
"rms_ref": u["rms_ref"],
|
| 493 |
+
"finite": m["finite"],
|
| 494 |
+
"expect_pass": expect_pass,
|
| 495 |
+
"ok": ok if expect_pass else (not ok),
|
| 496 |
+
}
|
| 497 |
+
row.update(extra)
|
| 498 |
+
results.add_row(row)
|
| 499 |
+
return row
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
@pytest.mark.parametrize("eps", (EPS_BLOCK, EPS_HEAD), ids=("eps1e-5", "eps1e-6"))
|
| 503 |
+
@pytest.mark.parametrize("step", STEPS, ids=[f"k{s}" for s in STEPS])
|
| 504 |
+
def test_ln_plain_golden(gr00t_device: Any, results: Any, version: str, step: int, eps: float) -> None:
|
| 505 |
+
"""Plain LN of the golden sa_embs (P8 / output-head statistics) vs torch fp32: PCC >= 0.9999, <= 2 bf16 ulps."""
|
| 506 |
+
_need_golden(version)
|
| 507 |
+
x = golden_sa_embs(version, step)
|
| 508 |
+
hs = _harness(gr00t_device)
|
| 509 |
+
res = hs.run(x, eps=eps)
|
| 510 |
+
ref = ref_layernorm(x, eps)
|
| 511 |
+
row = _row(results, f"ln-{version}-k{step}-eps{eps:g}", res.h, ref, step=step, eps=eps, wall_s=res.wall_s)
|
| 512 |
+
assert torch.equal(res.x_after, x), "x must be untouched without a residual"
|
| 513 |
+
assert row["finite"], "non-finite LN output"
|
| 514 |
+
assert row["pcc"] >= GATE_PCC, f"PCC {row['pcc']:.6f} < {GATE_PCC}"
|
| 515 |
+
assert (
|
| 516 |
+
row["max_ulps"] <= GATE_ULPS
|
| 517 |
+
), f"max |d| {row['max_ulps']:.2f} bf16 ulps > {GATE_ULPS} (abs {row['max_abs']:.4g})"
|
| 518 |
+
|
| 519 |
+
|
| 520 |
+
@pytest.mark.parametrize("step", STEPS, ids=[f"k{s}" for s in STEPS])
|
| 521 |
+
def test_adaln_golden(gr00t_device: Any, results: Any, version: str, step: int) -> None:
|
| 522 |
+
"""AdaLN with in-kernel tilised ROW_MAJOR (1+s, b) rows on the golden sa_embs; swapped rows must fail."""
|
| 523 |
+
_need_golden(version)
|
| 524 |
+
x = golden_sa_embs(version, step)
|
| 525 |
+
ops, sh = random_adaln(SEED + step)
|
| 526 |
+
hs = _harness(gr00t_device)
|
| 527 |
+
res = hs.run(x, eps=EPS_BLOCK, one_plus_scale=ops, shift=sh)
|
| 528 |
+
ref = ref_layernorm(x, EPS_BLOCK, ops, sh)
|
| 529 |
+
row = _row(results, f"adaln-{version}-k{step}", res.h, ref, step=step, wall_s=res.wall_s)
|
| 530 |
+
swapped = hs.run(x, eps=EPS_BLOCK, one_plus_scale=ops, shift=sh, swap_rows=True)
|
| 531 |
+
row_sw = _row(results, f"adaln-swapped-{version}-k{step}", swapped.h, ref, expect_pass=False, step=step)
|
| 532 |
+
# the swapped launch computes LN * b + (1 + s): it must match *that* reference, i.e. the kernel reads the rows it
|
| 533 |
+
# is told to read
|
| 534 |
+
ref_sw = ref_layernorm(x, EPS_BLOCK, sh, ops)
|
| 535 |
+
row_sw2 = _row(results, f"adaln-swapped-selfcheck-{version}-k{step}", swapped.h, ref_sw, step=step)
|
| 536 |
+
assert row["finite"] and row["pcc"] >= GATE_PCC, f"AdaLN PCC {row['pcc']:.6f} < {GATE_PCC}"
|
| 537 |
+
assert row["max_ulps"] <= GATE_ULPS, f"AdaLN max |d| {row['max_ulps']:.2f} ulps > {GATE_ULPS}"
|
| 538 |
+
assert row_sw["pcc"] < GATE_PCC, f"swapped (scale, shift) still passes: PCC {row_sw['pcc']:.6f}"
|
| 539 |
+
assert row_sw2["ok"], "swapped rows do not match the swapped reference"
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
@pytest.mark.parametrize("g_order", ("single", "row_major"))
|
| 543 |
+
def test_residual_adaln(gr00t_device: Any, results: Any, version: str, g_order: str) -> None:
|
| 544 |
+
"""x <- x + g in place (g in the P12 Single order or RowMajor), then AdaLN (P1 residual-on-receive).
|
| 545 |
+
|
| 546 |
+
The in-place sum is compared with torch's ``bf16(x + g)`` (round-to-nearest-even): the Tensix FPU add + fp32 ->
|
| 547 |
+
bf16 pack differs from RNE on a few percent of near-tie sums by exactly one bf16 ulp (measured: ties round away
|
| 548 |
+
from zero, plus a class of just-below-half sums that round up), so the gate is <= 1 ulp elementwise with the
|
| 549 |
+
fraction of differing elements recorded; the LN of the *device* sum is gated as every other LN."""
|
| 550 |
+
_need_golden(version)
|
| 551 |
+
x = golden_sa_embs(version, 0)
|
| 552 |
+
gen = torch.Generator().manual_seed(SEED)
|
| 553 |
+
g = (0.5 * x.std() * torch.randn(M_ROWS, WIDTH, generator=gen)).to(torch.bfloat16).float()
|
| 554 |
+
ops, sh = random_adaln(SEED)
|
| 555 |
+
hs = _harness(gr00t_device)
|
| 556 |
+
res = hs.run(x, eps=EPS_BLOCK, one_plus_scale=ops, shift=sh, g=g, g_order=g_order)
|
| 557 |
+
x_rne = (x + g).to(torch.bfloat16).float()
|
| 558 |
+
u = ulp_metrics(res.x_after, x_rne)
|
| 559 |
+
n_diff = int((res.x_after != x_rne).sum())
|
| 560 |
+
sum_ok = bool(u["max_ulps_elementwise"] <= 1.0 and n_diff <= 0.1 * x.numel())
|
| 561 |
+
results.add_row(
|
| 562 |
+
{
|
| 563 |
+
"tap": "mk_k2_ln",
|
| 564 |
+
"key": f"residual-x-{version}-{g_order}",
|
| 565 |
+
"rule": "ulp",
|
| 566 |
+
"ok": sum_ok,
|
| 567 |
+
"max_ulps_elementwise": u["max_ulps_elementwise"],
|
| 568 |
+
"n_diff_vs_rne": n_diff,
|
| 569 |
+
"frac_diff_vs_rne": n_diff / x.numel(),
|
| 570 |
+
"max_abs": u["max_abs"],
|
| 571 |
+
}
|
| 572 |
+
)
|
| 573 |
+
ref = ref_layernorm(res.x_after, EPS_BLOCK, ops, sh) # LN of the sum the device actually formed
|
| 574 |
+
row = _row(results, f"residual-adaln-{version}-{g_order}", res.h, ref, wall_s=res.wall_s)
|
| 575 |
+
ref_rne = ref_layernorm(x_rne, EPS_BLOCK, ops, sh)
|
| 576 |
+
_row(results, f"residual-adaln-vs-rne-sum-{version}-{g_order}", res.h, ref_rne)
|
| 577 |
+
assert sum_ok, f"in-place residual: {n_diff} elements differ from bf16(x + g), max {u['max_ulps_elementwise']} ulps"
|
| 578 |
+
assert row["ok"], f"residual AdaLN: PCC {row['pcc']:.6f}, {row['max_ulps']:.2f} ulps"
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def test_stress_rows(gr00t_device: Any, results: Any) -> None:
|
| 582 |
+
"""Synthetic rows: |mean|/std = 100, the mb2 §B.3 outlier channels, near-constant, x300 scale, zero, 1e-3 scale."""
|
| 583 |
+
x = stress_rows()
|
| 584 |
+
hs = _harness(gr00t_device)
|
| 585 |
+
for eps in (EPS_BLOCK, EPS_HEAD):
|
| 586 |
+
res = hs.run(x, eps=eps)
|
| 587 |
+
ref = ref_layernorm(x, eps)
|
| 588 |
+
row = _row(results, f"stress-eps{eps:g}", res.h, ref, eps=eps)
|
| 589 |
+
# per stress row detail
|
| 590 |
+
per_row = {int(r): ulp_metrics(res.h[r], ref[r])["max_ulps"] for r in range(7)}
|
| 591 |
+
results.set_meta(**{f"stress_eps{eps:g}_row_ulps": per_row})
|
| 592 |
+
assert row["finite"], "non-finite"
|
| 593 |
+
assert float(res.h[5].abs().max()) == 0.0, "the zero row must normalise to exactly 0"
|
| 594 |
+
assert row["pcc"] >= GATE_PCC, f"stress PCC {row['pcc']:.6f}"
|
| 595 |
+
assert row["max_ulps"] <= GATE_ULPS, f"stress max |d| {row['max_ulps']:.2f} ulps (rows {per_row})"
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
def test_ln_replicated_96(gr00t_device: Any, results: Any, version: str) -> None:
|
| 599 |
+
"""The same AdaLN on all 96 compute cores of the CoreMap: every core's result is bit-identical to core 0."""
|
| 600 |
+
_need_golden(version)
|
| 601 |
+
x = golden_sa_embs(version, 0)
|
| 602 |
+
ops, sh = random_adaln(SEED)
|
| 603 |
+
hs = _harness(gr00t_device, n_cores=96)
|
| 604 |
+
res = hs.run(x, eps=EPS_BLOCK, one_plus_scale=ops, shift=sh)
|
| 605 |
+
ref = ref_layernorm(x, EPS_BLOCK, ops, sh)
|
| 606 |
+
h0 = hs.per_core(res.h, 0)
|
| 607 |
+
identical = all(torch.equal(hs.per_core(res.h, i), h0) for i in range(96))
|
| 608 |
+
row = _row(results, f"adaln-96cores-{version}", h0, ref, identical=identical, wall_s=res.wall_s)
|
| 609 |
+
results.add_row({"tap": "mk_k2_ln", "key": f"adaln-96cores-identical-{version}", "rule": "exact", "ok": identical})
|
| 610 |
+
assert identical, "compute cores disagree on the replicated LN"
|
| 611 |
+
assert row["ok"]
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
def test_ln_timing(gr00t_device: Any, results: Any, version: str) -> None:
|
| 615 |
+
"""Microseconds per LN on one core (plain / AdaLN / residual + AdaLN): in-kernel iteration difference method;
|
| 616 |
+
Tracy zones when GR00T_MK_PROFILE=1."""
|
| 617 |
+
_need_golden(version)
|
| 618 |
+
x = golden_sa_embs(version, 0)
|
| 619 |
+
ops, sh = random_adaln(SEED)
|
| 620 |
+
gen = torch.Generator().manual_seed(SEED)
|
| 621 |
+
g = (0.01 * torch.randn(M_ROWS, WIDTH, generator=gen)).to(torch.bfloat16).float()
|
| 622 |
+
hs = _harness(gr00t_device)
|
| 623 |
+
if PROFILE_CSV.exists() and os.environ.get("TT_METAL_DEVICE_PROFILER") == "1":
|
| 624 |
+
PROFILE_CSV.unlink()
|
| 625 |
+
timings: Dict[str, Dict[str, float]] = {}
|
| 626 |
+
variants = {
|
| 627 |
+
"plain": dict(eps=EPS_BLOCK),
|
| 628 |
+
"adaln": dict(eps=EPS_BLOCK, one_plus_scale=ops, shift=sh),
|
| 629 |
+
"residual_adaln": dict(eps=EPS_BLOCK, one_plus_scale=ops, shift=sh, g=g),
|
| 630 |
+
}
|
| 631 |
+
for name, kw in variants.items():
|
| 632 |
+
t = measure_us(lambda n, kw=kw: hs.run(x, n_iters=n, **kw).wall_s)
|
| 633 |
+
timings[name] = t
|
| 634 |
+
results.add_row(
|
| 635 |
+
{
|
| 636 |
+
"tap": "mk_k2_ln",
|
| 637 |
+
"key": f"us-{name}-{version}",
|
| 638 |
+
"rule": "time",
|
| 639 |
+
"us": t["per_iter_us"],
|
| 640 |
+
"ok": bool(0 < t["per_iter_us"] < 1e4),
|
| 641 |
+
**t,
|
| 642 |
+
}
|
| 643 |
+
)
|
| 644 |
+
# the iterated residual is deterministic: x + 6 g in bf16 steps
|
| 645 |
+
res6 = hs.run(x, n_iters=6, **variants["residual_adaln"])
|
| 646 |
+
xi = x.clone()
|
| 647 |
+
for _ in range(6):
|
| 648 |
+
xi = (xi + g).to(torch.bfloat16).float()
|
| 649 |
+
# sanity of the in-place recurrence only (the single-step gate is test_residual_adaln): per step the device sum
|
| 650 |
+
# may differ from RNE by one ulp of |x| and cross a binade (g = 0.01 is far below the ulp of the largest |x|),
|
| 651 |
+
# so allow 2 ulps of |x| per step at the row scale; the unfloored per-element ulp is meaningless on the
|
| 652 |
+
# exactly-zero padded rows
|
| 653 |
+
u6 = ulp_metrics(res6.x_after, xi)
|
| 654 |
+
iter_ok = bool(u6["max_ulps"] <= 12.0 and pcc_mod.pcc(res6.x_after, xi) >= 0.99999)
|
| 655 |
+
results.add_row(
|
| 656 |
+
{
|
| 657 |
+
"tap": "mk_k2_ln",
|
| 658 |
+
"key": f"residual-6iters-{version}",
|
| 659 |
+
"rule": "ulp",
|
| 660 |
+
"ok": iter_ok,
|
| 661 |
+
"n_diff_vs_rne": int((res6.x_after != xi).sum()),
|
| 662 |
+
**{k: v for k, v in u6.items()},
|
| 663 |
+
}
|
| 664 |
+
)
|
| 665 |
+
zones: Dict[str, Any] = {}
|
| 666 |
+
if os.environ.get("TT_METAL_DEVICE_PROFILER") == "1":
|
| 667 |
+
hs.ttnn.ReadDeviceProfiler(gr00t_device)
|
| 668 |
+
try:
|
| 669 |
+
zones = read_ln_zones()
|
| 670 |
+
except FileNotFoundError as exc:
|
| 671 |
+
zones = {"error": str(exc)}
|
| 672 |
+
results.set_meta(timings=timings, zones=zones, profiler=os.environ.get("TT_METAL_DEVICE_PROFILER", "0"))
|
| 673 |
+
assert iter_ok, f"6 iterated residual adds drift {u6['max_ulps']} ulps (row scale) from the bf16 recurrence"
|
| 674 |
+
for name, t in timings.items():
|
| 675 |
+
assert 0 < t["per_iter_us"] < 1e4, f"{name}: implausible {t['per_iter_us']} us"
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
def test_ff_branch_vs_s1(gr00t_device: Any, results: Any, version: str) -> None:
|
| 679 |
+
"""FF branch x + ff2(GELU_tanh(ff1(LN(x)))): harness LN -> K1 BlockMatmul (run_single_op ff1, ff2) -> harness
|
| 680 |
+
residual, vs the Stage-1 ttnn path (tt.layers layer_norm / linear / residual_add) on device: PCC >= 0.9999."""
|
| 681 |
+
_need_golden(version)
|
| 682 |
+
from models.experimental.gr00t.tt import layers as L
|
| 683 |
+
from models.experimental.gr00t.tt.megakernel import arena as A
|
| 684 |
+
from models.experimental.gr00t.tt.megakernel import descriptors as D
|
| 685 |
+
from models.experimental.gr00t.tt.megakernel import dit_program as P
|
| 686 |
+
from models.experimental.gr00t.tt.megakernel.core_map import CoreMap
|
| 687 |
+
|
| 688 |
+
ttnn = __import__("ttnn")
|
| 689 |
+
device = gr00t_device
|
| 690 |
+
x = golden_sa_embs(version, 0)
|
| 691 |
+
blk = 1 # the self block of the 2-block test arena
|
| 692 |
+
hs = _harness(device)
|
| 693 |
+
# 1. LN on the megakernel path
|
| 694 |
+
h2 = hs.run(x, eps=EPS_BLOCK).h
|
| 695 |
+
# 2. ff1 (+GELU_tanh) and ff2 with K1's BlockMatmul on a random 2-block arena
|
| 696 |
+
layout = A.ArenaLayout.plan(version, n_blocks=2, dtype_stream="bf16", reader_mode="direct", tile_order="row_major")
|
| 697 |
+
pack = layout.pack(P.random_arena_plan(layout, seed=SEED))
|
| 698 |
+
k_block_bytes = max(op.k_block_bytes for op in layout.stream_ops("stream"))
|
| 699 |
+
mk = P.DiTMegakernel(
|
| 700 |
+
get_config(version),
|
| 701 |
+
None,
|
| 702 |
+
None,
|
| 703 |
+
layout,
|
| 704 |
+
CoreMap.from_device(device),
|
| 705 |
+
device,
|
| 706 |
+
cb_budget=D.CBBudget(cb_w=3 * k_block_bytes),
|
| 707 |
+
)
|
| 708 |
+
try:
|
| 709 |
+
mk.upload(pack)
|
| 710 |
+
t0 = time.perf_counter()
|
| 711 |
+
f = mk.run_single_op(h2, blk, "ff1")
|
| 712 |
+
g = mk.run_single_op(f, blk, "ff2")
|
| 713 |
+
t_mm = time.perf_counter() - t0
|
| 714 |
+
w1, b1 = P.op_weight_and_bias(layout, pack, blk, "ff1")
|
| 715 |
+
w2, b2 = P.op_weight_and_bias(layout, pack, blk, "ff2")
|
| 716 |
+
finally:
|
| 717 |
+
mk.deallocate()
|
| 718 |
+
# 3. residual on the megakernel path (g in the P12 Single order)
|
| 719 |
+
res = hs.run(x, eps=EPS_BLOCK, g=g, g_order="single")
|
| 720 |
+
out_mk = res.x_after
|
| 721 |
+
# 4. Stage-1 ttnn path
|
| 722 |
+
xt = L.to_device(x.reshape(1, 1, M_ROWS, WIDTH), device, dtype="bf16", mem=L.L1)
|
| 723 |
+
w1t = L.to_device(w1.to(torch.bfloat16), device, dtype="bf16", mem=L.DRAM)
|
| 724 |
+
b1t = L.to_device(b1.to(torch.bfloat16).reshape(1, -1), device, dtype="bf16", mem=L.DRAM)
|
| 725 |
+
w2t = L.to_device(w2.to(torch.bfloat16), device, dtype="bf16", mem=L.DRAM)
|
| 726 |
+
b2t = L.to_device(b2.to(torch.bfloat16).reshape(1, -1), device, dtype="bf16", mem=L.DRAM)
|
| 727 |
+
h_s1 = L.layer_norm(xt, eps=EPS_BLOCK)
|
| 728 |
+
f_s1 = L.linear(h_s1, w1t, b1t, act="gelu_tanh")
|
| 729 |
+
g_s1 = L.linear(f_s1, w2t, b2t)
|
| 730 |
+
out_s1 = L.residual_add(xt, g_s1)
|
| 731 |
+
h_s1_t = L.to_torch(h_s1).reshape(M_ROWS, WIDTH)
|
| 732 |
+
g_s1_t = L.to_torch(g_s1).reshape(M_ROWS, WIDTH)
|
| 733 |
+
out_s1_t = L.to_torch(out_s1).reshape(M_ROWS, WIDTH)
|
| 734 |
+
for t in (xt, w1t, b1t, w2t, b2t, h_s1, f_s1, g_s1, out_s1):
|
| 735 |
+
ttnn.deallocate(t)
|
| 736 |
+
# 5. torch fp32 chain (bf16 rounding between ops, as both device paths)
|
| 737 |
+
bf = lambda t: t.to(torch.bfloat16).float() # noqa: E731
|
| 738 |
+
h_ref = ref_layernorm(x, EPS_BLOCK)
|
| 739 |
+
f_ref = bf(torch.nn.functional.gelu(h_ref @ w1 + b1, approximate="tanh"))
|
| 740 |
+
g_ref = bf(f_ref @ w2 + b2)
|
| 741 |
+
out_ref = bf(x + g_ref)
|
| 742 |
+
rows = {
|
| 743 |
+
"ff-ln-vs-s1": (h2, h_s1_t),
|
| 744 |
+
"ff-g-vs-s1": (g, g_s1_t),
|
| 745 |
+
"ff-out-vs-s1": (out_mk, out_s1_t),
|
| 746 |
+
"ff-out-vs-torch": (out_mk, out_ref),
|
| 747 |
+
"ff-s1-out-vs-torch": (out_s1_t, out_ref),
|
| 748 |
+
}
|
| 749 |
+
got_rows = {}
|
| 750 |
+
# informational: the two paths add *different* g (their ff2 matmuls round differently), so the sums cannot be
|
| 751 |
+
# bit-identical; the row records how many elements differ and by how many bf16 ulps (residual rounding + g)
|
| 752 |
+
u_s1 = ulp_metrics(out_mk, out_s1_t)
|
| 753 |
+
n_diff_s1 = int((out_mk != out_s1_t).sum())
|
| 754 |
+
results.add_row(
|
| 755 |
+
{
|
| 756 |
+
"tap": "mk_k2_ff",
|
| 757 |
+
"key": f"ff-out-vs-s1-diff-{version}",
|
| 758 |
+
"rule": "info",
|
| 759 |
+
"ok": bool(u_s1["max_ulps"] <= 2.0),
|
| 760 |
+
"n_diff": n_diff_s1,
|
| 761 |
+
"frac_diff": n_diff_s1 / out_mk.numel(),
|
| 762 |
+
"max_ulps": u_s1["max_ulps"],
|
| 763 |
+
"g_n_diff": int((g != g_s1_t).sum()),
|
| 764 |
+
"note": "megakernel in-place residual of the megakernel g vs ttnn.add of the S1 g (g differs between paths)",
|
| 765 |
+
}
|
| 766 |
+
)
|
| 767 |
+
for key, (a, b) in rows.items():
|
| 768 |
+
m = pcc_mod.metrics(a.float(), b.float())
|
| 769 |
+
got_rows[key] = m["pcc"]
|
| 770 |
+
results.add_row(
|
| 771 |
+
{
|
| 772 |
+
"tap": "mk_k2_ff",
|
| 773 |
+
"key": f"{key}-{version}",
|
| 774 |
+
"rule": "pcc",
|
| 775 |
+
"pcc": m["pcc"],
|
| 776 |
+
"gate_pcc": GATE_PCC,
|
| 777 |
+
"max_abs": m["max_abs"],
|
| 778 |
+
"finite": m["finite"],
|
| 779 |
+
"ok": bool(m["finite"] and m["pcc"] >= GATE_PCC),
|
| 780 |
+
}
|
| 781 |
+
)
|
| 782 |
+
results.set_meta(matmul_wall_s=t_mm, ff_pcc=got_rows)
|
| 783 |
+
assert got_rows["ff-out-vs-s1"] >= GATE_PCC, f"FF branch vs S1: PCC {got_rows['ff-out-vs-s1']:.6f} < {GATE_PCC}"
|
| 784 |
+
assert got_rows["ff-ln-vs-s1"] >= GATE_PCC and got_rows["ff-out-vs-torch"] >= GATE_PCC
|
code/models/experimental/gr00t/tests/tt/test_tt_action_head.py
CHANGED
|
@@ -14,8 +14,13 @@ Two tiers (plan §7.1):
|
|
| 14 |
(``sa_embs``, ``dit_block_last_out``, ``dit_out``, ``action_decoder_out``, ``pred_velocity``) gated by
|
| 15 |
``harness.check_taps``; ``weight_emulation='bfp8'`` emulates the ``mixed_dit`` bfp8 weights. Select with ``-k cpu``.
|
| 16 |
* device tests (``gr00t_device``; only through ``bin/with-device.sh``) -- the same 4-step denoise on the p150a with
|
| 17 |
-
``TTWeights``, the K/V hoisted on device from the golden VL rows, buffer persistence and determinism
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
Run (from the tt-metal root)::
|
| 21 |
|
|
@@ -201,7 +206,7 @@ def test_cpu_head_wiring_on_stub(version: str, golden, stub_ttnn: types.ModuleTy
|
|
| 201 |
if l_pad is not None
|
| 202 |
}
|
| 203 |
kv = hoist_kv(cfg, dl, W, vl_sets_h, lambda t: FakeTensor(t.float(), stub.bfloat16))
|
| 204 |
-
policy = TTPolicy()
|
| 205 |
device = object()
|
| 206 |
|
| 207 |
# own mask buffers: allocated from placeholders, refreshed in place by update_masks(slots)
|
|
@@ -290,9 +295,24 @@ def test_cpu_head_wiring_on_stub(version: str, golden, stub_ttnn: types.ModuleTy
|
|
| 290 |
assert head.x_t.freed and head.action_pred.freed and head.masks == {}
|
| 291 |
assert all(t.freed for t in own_masks), "release() must free the mask buffers the head allocated"
|
| 292 |
|
| 293 |
-
# contracts
|
| 294 |
-
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
h3 = AH.ActionHeadTT(cfg, dl, W, policy, device)
|
| 297 |
with pytest.raises(ValueError):
|
| 298 |
h3.denoise(h3.x_t, state) # no K/V bound
|
|
@@ -444,7 +464,14 @@ def _device_setup(
|
|
| 444 |
)
|
| 445 |
t0 = time.perf_counter()
|
| 446 |
dh = _DeviceHead(
|
| 447 |
-
gs,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
)
|
| 449 |
import ttnn
|
| 450 |
|
|
@@ -556,40 +583,3 @@ def test_device_buffers_persistent_and_deterministic(
|
|
| 556 |
assert det and changed
|
| 557 |
finally:
|
| 558 |
dh.release()
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
def test_device_megakernel_backend_raises(
|
| 562 |
-
version: str, golden, gr00t_device: Any, head_weights: Any, results: ResultsWriter
|
| 563 |
-
) -> None:
|
| 564 |
-
"""``TTPolicy(dit_backend='megakernel')`` constructs but ``denoise`` raises ``NotImplementedError`` until WP-K5."""
|
| 565 |
-
gs, mi, cfg, dl = _golden_setup(version, golden)
|
| 566 |
-
from models.experimental.gr00t.tt import layout as LAY
|
| 567 |
-
|
| 568 |
-
masks = LAY.materialise(gr00t_device, dl, mi.slots)
|
| 569 |
-
kv = hoist_kv(
|
| 570 |
-
cfg,
|
| 571 |
-
dl,
|
| 572 |
-
head_weights,
|
| 573 |
-
gathered_vl_sets(gs, mi, dl),
|
| 574 |
-
lambda t: L.to_device(t, gr00t_device, dtype="bf16", mem="DRAM"),
|
| 575 |
-
)
|
| 576 |
-
head = AH.ActionHeadTT(cfg, dl, head_weights, TTPolicy(dit_backend="megakernel"), gr00t_device, masks=masks, kv=kv)
|
| 577 |
-
state = L.to_device(
|
| 578 |
-
gs.load("state_features").reshape(1, 1, 1, -1).to(torch.bfloat16), gr00t_device, dtype="bf16", mem="DRAM"
|
| 579 |
-
)
|
| 580 |
-
try:
|
| 581 |
-
assert head.backend == "megakernel" and head.megakernel is None
|
| 582 |
-
with pytest.raises(NotImplementedError):
|
| 583 |
-
head.denoise(head.x_t, state)
|
| 584 |
-
results.set_meta(backend="megakernel", raises="NotImplementedError")
|
| 585 |
-
finally:
|
| 586 |
-
import ttnn
|
| 587 |
-
|
| 588 |
-
head.release()
|
| 589 |
-
ttnn.deallocate(state)
|
| 590 |
-
for t in masks.values():
|
| 591 |
-
if t is not None:
|
| 592 |
-
ttnn.deallocate(t)
|
| 593 |
-
for k, v in kv.values():
|
| 594 |
-
ttnn.deallocate(k)
|
| 595 |
-
ttnn.deallocate(v)
|
|
|
|
| 14 |
(``sa_embs``, ``dit_block_last_out``, ``dit_out``, ``action_decoder_out``, ``pred_velocity``) gated by
|
| 15 |
``harness.check_taps``; ``weight_emulation='bfp8'`` emulates the ``mixed_dit`` bfp8 weights. Select with ``-k cpu``.
|
| 16 |
* device tests (``gr00t_device``; only through ``bin/with-device.sh``) -- the same 4-step denoise on the p150a with
|
| 17 |
+
``TTWeights``, the K/V hoisted on device from the golden VL rows, buffer persistence and determinism.
|
| 18 |
+
|
| 19 |
+
Every head built here is the **ttnn** backend (``TTPolicy(dit_backend="ttnn")``): this file tests the Stage-1 op
|
| 20 |
+
sequence, and the session device has the firmware ``worker_l1_size`` the megakernel cannot run under. The megakernel
|
| 21 |
+
backend -- the ``TTPolicy`` default since 2026-09-18 -- is covered end to end by ``test_mk_e2e.py`` (both backends on
|
| 22 |
+
one K5 device), ``test_mk_2cq.py`` (1-CQ vs 2-CQ bit-equality) and the whole-model files; the CPU contract here is
|
| 23 |
+
only that its constructor refuses the stub's small configuration with ``ValueError`` instead of building an arena.
|
| 24 |
|
| 25 |
Run (from the tt-metal root)::
|
| 26 |
|
|
|
|
| 206 |
if l_pad is not None
|
| 207 |
}
|
| 208 |
kv = hoist_kv(cfg, dl, W, vl_sets_h, lambda t: FakeTensor(t.float(), stub.bfloat16))
|
| 209 |
+
policy = TTPolicy(dit_backend="ttnn") # the Stage-1 op path on the stub (module docstring)
|
| 210 |
device = object()
|
| 211 |
|
| 212 |
# own mask buffers: allocated from placeholders, refreshed in place by update_masks(slots)
|
|
|
|
| 295 |
assert head.x_t.freed and head.action_pred.freed and head.masks == {}
|
| 296 |
assert all(t.freed for t in own_masks), "release() must free the mask buffers the head allocated"
|
| 297 |
|
| 298 |
+
# contracts. The megakernel backend builds its DRAM arena for the *real* version config at construction
|
| 299 |
+
# (``ArenaLayout.plan(cfg.version, ...)``: 16 / 32 blocks) and cross-checks it against the head's config, so the
|
| 300 |
+
# stub's 4-block ``small_cfg`` is refused with ValueError before any weight is touched (WP-K5 summary §7 item 6)
|
| 301 |
+
with pytest.raises(ValueError, match="megakernel arena"):
|
| 302 |
+
AH.ActionHeadTT(cfg, dl, W, TTPolicy(dit_backend="megakernel"), device, kv=kv)
|
| 303 |
+
with pytest.raises(ValueError, match="Stage-1"):
|
| 304 |
+
AH.ActionHeadTT(cfg, dl, W, TTPolicy(dit_backend="megakernel"), device, kv=kv, persist_sa_embs=True)
|
| 305 |
+
with pytest.raises(ValueError, match="megakernel-backend argument"):
|
| 306 |
+
AH.ActionHeadTT(cfg, dl, W, policy, device, kv=kv, mk_k4_budget=object())
|
| 307 |
+
mk_taps = [t for k in STEPS for t in (f"action_decoder_out[k={k}]", f"pred_velocity[k={k}]")]
|
| 308 |
+
assert AH.head_tap_names("megakernel", len(STEPS), cfg.dit.n_blocks) == (
|
| 309 |
+
*mk_taps,
|
| 310 |
+
"action_pred_normalized",
|
| 311 |
+
"action_pred_valid",
|
| 312 |
+
)
|
| 313 |
+
assert set(AH.head_tap_names("megakernel", len(STEPS), cfg.dit.n_blocks)) < set(
|
| 314 |
+
AH.head_tap_names("ttnn", len(STEPS), cfg.dit.n_blocks)
|
| 315 |
+
)
|
| 316 |
h3 = AH.ActionHeadTT(cfg, dl, W, policy, device)
|
| 317 |
with pytest.raises(ValueError):
|
| 318 |
h3.denoise(h3.x_t, state) # no K/V bound
|
|
|
|
| 464 |
)
|
| 465 |
t0 = time.perf_counter()
|
| 466 |
dh = _DeviceHead(
|
| 467 |
+
gs,
|
| 468 |
+
mi,
|
| 469 |
+
cfg,
|
| 470 |
+
dl,
|
| 471 |
+
head_weights,
|
| 472 |
+
TTPolicy(dtype_policy=head_weights.policy.dtype_policy, dit_backend="ttnn"),
|
| 473 |
+
gr00t_device,
|
| 474 |
+
**kw,
|
| 475 |
)
|
| 476 |
import ttnn
|
| 477 |
|
|
|
|
| 583 |
assert det and changed
|
| 584 |
finally:
|
| 585 |
dh.release()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code/models/experimental/gr00t/tests/tt/test_tt_backbone.py
CHANGED
|
@@ -72,10 +72,12 @@ REQUIRED_GATED = {
|
|
| 72 |
"n16": ("llm_inputs_embeds", "projector_out", "backbone_features"),
|
| 73 |
"n17": ("llm_inputs_embeds", "projector_out", "vlln_out"),
|
| 74 |
}
|
| 75 |
-
#: D0 device fact carried into D1 (test_tt_qwen3vl_vision.KNOWN_GATE_MISSES): the verbatim tt_dit N1.7 tower
|
| 76 |
-
#: PCC 0.99965 on ``vit_deepstack_mergers[0]`` against the frozen gate 0.9998 (
|
| 77 |
-
#:
|
| 78 |
-
#:
|
|
|
|
|
|
|
| 79 |
KNOWN_GATE_MISSES: Dict[str, float] = {"vit_deepstack_mergers[0]": 0.9995}
|
| 80 |
assert not any(k in KNOWN_GATE_MISSES for keys in REQUIRED_GATED.values() for k in keys)
|
| 81 |
|
|
@@ -727,6 +729,9 @@ def test_cpu_recipe_and_build_contract() -> None:
|
|
| 727 |
r = B.BackboneRecipe.from_config(cfg, dl, pol)
|
| 728 |
flags[v] = (r.tower, r.projector, r.llm_mask, r.rope_table, r.final_norm, r.deepstack, r.has_vlsa)
|
| 729 |
assert r.describe()["per_call_inputs"] == list(r.per_call_inputs())
|
|
|
|
|
|
|
|
|
|
| 730 |
with pytest.raises(ValueError):
|
| 731 |
B.BackboneRecipe.from_config(
|
| 732 |
cfg,
|
|
|
|
| 72 |
"n16": ("llm_inputs_embeds", "projector_out", "backbone_features"),
|
| 73 |
"n17": ("llm_inputs_embeds", "projector_out", "vlln_out"),
|
| 74 |
}
|
| 75 |
+
#: D0 device fact carried into D1 (test_tt_qwen3vl_vision.KNOWN_GATE_MISSES): the **verbatim** tt_dit N1.7 tower
|
| 76 |
+
#: reaches PCC 0.99965 on ``vit_deepstack_mergers[0]`` against the frozen gate 0.9998 (HiFi2 linears in the block
|
| 77 |
+
#: stack). Since 2026-09-17 the default tower is the fork with HiFi3 + fp32 acc on blocks 0-5 (``TTPolicy.vit_fork``,
|
| 78 |
+
#: docs/plan/n17-fidelity.md) and passes the gate; the floor below only applies to ``GR00T_VIT_FORK=0`` (oracle) runs:
|
| 79 |
+
#: the miss is an **xfail** while the PCC stays above the floor, every other gated tap must pass, and the WP-12
|
| 80 |
+
#: acceptance keys (REQUIRED_GATED) are never in this table.
|
| 81 |
KNOWN_GATE_MISSES: Dict[str, float] = {"vit_deepstack_mergers[0]": 0.9995}
|
| 82 |
assert not any(k in KNOWN_GATE_MISSES for keys in REQUIRED_GATED.values() for k in keys)
|
| 83 |
|
|
|
|
| 729 |
r = B.BackboneRecipe.from_config(cfg, dl, pol)
|
| 730 |
flags[v] = (r.tower, r.projector, r.llm_mask, r.rope_table, r.final_norm, r.deepstack, r.has_vlsa)
|
| 731 |
assert r.describe()["per_call_inputs"] == list(r.per_call_inputs())
|
| 732 |
+
# the fork is the TTPolicy default and applies to the Qwen3-VL tower only (n17-fidelity.md)
|
| 733 |
+
assert r.vit_fork is (v == "n17") and r.describe()["vit_fork"] is r.vit_fork
|
| 734 |
+
assert B.BackboneRecipe.from_config(cfg, dl, TTPolicy(vit_fork=False)).vit_fork is False
|
| 735 |
with pytest.raises(ValueError):
|
| 736 |
B.BackboneRecipe.from_config(
|
| 737 |
cfg,
|
code/models/experimental/gr00t/tests/tt/test_tt_determinism.py
CHANGED
|
@@ -110,6 +110,42 @@ def test_cpu_all_equal_helper() -> None:
|
|
| 110 |
# --------------------------------------------------------------------------------------------------------------------
|
| 111 |
# device tier
|
| 112 |
# --------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
@pytest.fixture(scope="module")
|
| 114 |
def model(request: pytest.FixtureRequest, version: str) -> Iterator[M.Gr00tTT]:
|
| 115 |
request.getfixturevalue("golden")
|
|
|
|
| 110 |
# --------------------------------------------------------------------------------------------------------------------
|
| 111 |
# device tier
|
| 112 |
# --------------------------------------------------------------------------------------------------------------------
|
| 113 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 114 |
+
# DiT backend parametrisation (WP-K5): GR00T_DIT_BACKEND=ttnn|megakernel, GR00T_MK_ARENA_DTYPE=auto|bf16|bfp8_b
|
| 115 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 116 |
+
@pytest.fixture(scope="module")
|
| 117 |
+
def policy(policy_name: str, trace_layout: str) -> TTPolicy:
|
| 118 |
+
"""conftest's ``TTPolicy(dtype_policy=--policy, trace_layout=--trace-layout)`` plus the environment overrides of
|
| 119 |
+
``TTPolicy.with_env_overrides`` (the DiT backend and the megakernel arena dtype of this run)."""
|
| 120 |
+
return TTPolicy(dtype_policy=policy_name, trace_layout=trace_layout).with_env_overrides()
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@pytest.fixture(scope="module")
|
| 124 |
+
def gr00t_device(request: pytest.FixtureRequest, policy: TTPolicy) -> Iterator[Any]:
|
| 125 |
+
"""The p150a opened per backend (``tt.model.open_model_device``: the megakernel needs the smaller K4
|
| 126 |
+
``worker_l1_size``); same trace region / L1-small sizes as conftest's session fixture, one device per process."""
|
| 127 |
+
from models.experimental.gr00t.tests.tt.conftest import (
|
| 128 |
+
DEFAULT_L1_SMALL_SIZE,
|
| 129 |
+
DEFAULT_TRACE_REGION_SIZE,
|
| 130 |
+
DEFAULT_TT_METAL_CACHE,
|
| 131 |
+
)
|
| 132 |
+
from models.experimental.gr00t.tt.device import close_gr00t_device
|
| 133 |
+
|
| 134 |
+
os.environ.setdefault("TT_METAL_CACHE", str(DEFAULT_TT_METAL_CACHE))
|
| 135 |
+
device = M.open_model_device(
|
| 136 |
+
policy, trace_region_size=DEFAULT_TRACE_REGION_SIZE, l1_small_size=DEFAULT_L1_SMALL_SIZE
|
| 137 |
+
)
|
| 138 |
+
facts = harness.device_facts(device)
|
| 139 |
+
facts["worker_l1_size"] = M.device_worker_l1_size(device)
|
| 140 |
+
facts["dit_backend"] = policy.dit_backend
|
| 141 |
+
facts["mk_arena_dtype"] = policy.mk_arena_dtype if policy.dit_backend == "megakernel" else None
|
| 142 |
+
request.config._gr00t_device_facts = facts # type: ignore[attr-defined]
|
| 143 |
+
try:
|
| 144 |
+
yield device
|
| 145 |
+
finally:
|
| 146 |
+
close_gr00t_device(device)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
@pytest.fixture(scope="module")
|
| 150 |
def model(request: pytest.FixtureRequest, version: str) -> Iterator[M.Gr00tTT]:
|
| 151 |
request.getfixturevalue("golden")
|
code/models/experimental/gr00t/tests/tt/test_tt_dtype_policy.py
ADDED
|
@@ -0,0 +1,904 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: © 2026 Tenstorrent USA, Inc.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
"""Device sweep of the weight dtype classes (WP-Q; IMPLEMENTATION_PLAN.md §1.2 "After WP-Q admission", §7.2).
|
| 5 |
+
|
| 6 |
+
One pytest process per version, through the device lock, from the tt-metal root (the conftest option is
|
| 7 |
+
``--gr00t-version``; ``TT_METAL_CACHE`` exported)::
|
| 8 |
+
|
| 9 |
+
DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh \\
|
| 10 |
+
python -m pytest -q -rA --timeout=1800 --timeout-method=thread -p no:cacheprovider \\
|
| 11 |
+
models/experimental/gr00t/tests/tt/test_tt_dtype_policy.py --gr00t-version n15
|
| 12 |
+
|
| 13 |
+
Device tier:
|
| 14 |
+
|
| 15 |
+
* ``test_device_bfp_round_matches_ttnn[bfp8_b|bfp4_b]`` -- ``ttnn.from_torch(x, bfloat8_b | bfloat4_b, TILE)``
|
| 16 |
+
(host pack and a device round trip) against ``test_bfp8_emulation_cpu.bfp_round``: every element within one
|
| 17 |
+
mantissa step of its block, exactness fraction recorded (bf16-valued and fp32-valued inputs, ragged shapes).
|
| 18 |
+
* ``test_device_dtype_sweep[<step>]`` -- the class-by-class admission sweep in :data:`SWEEP_STEPS` order
|
| 19 |
+
(``baseline`` = the shipped ``ADMITTED_CLASSES`` -> ``vision`` -> ``projector`` (N1.6 mlp1 / N1.7 mergers) ->
|
| 20 |
+
``llm`` -> ``vl_self_attention`` -> the two experiments ``kv_bfp8`` (hoisted cross K/V rounded to bfp8 after the
|
| 21 |
+
hoist, plan §1.2 row 6) and ``dit_ff_bfp4`` (DiT ``ff1.W`` / ``ff2.W`` pre-rounded to bfp4, plan §1.2 row 1)).
|
| 22 |
+
Every step loads ``Gr00tTT`` with ``TTPolicy.with_admitted(<admitted so far + the class>, cache_tag="wpq")`` (its
|
| 23 |
+
own ``.tensorbin`` tier, removed at the end of the module), runs ``run_untraced(return_taps=True)`` on the canonical
|
| 24 |
+
golden and on every multi sample, gates everything with ``harness.check_taps`` (multi-sample gates, valid slices,
|
| 25 |
+
per-dim action PCC; the e2e module's ``KNOWN_GATE_MISSES`` floors apply exactly as in ``test_tt_model_e2e``),
|
| 26 |
+
measures the traced latency (``bench_e2e`` method: upload -> replays -> D2H, medians of 20 calls + a split pass)
|
| 27 |
+
and records the per-tap PCC deltas against the baseline step. Admission rule of a weight class (never admit a
|
| 28 |
+
class whose worst tap is below its gate): canonical -- every gated row passes except the known misses, which must
|
| 29 |
+
stay above their floors; multi -- no row that passed at baseline fails, and ``action_pred_valid`` drops by at most
|
| 30 |
+
:data:`MULTI_ACTION_SLACK`; an admitted class is carried into the next step, a rejected one keeps bf16. The sweep
|
| 31 |
+
**measures**; the shipped table ``tt/policy.py::ADMITTED_CLASSES`` is written by hand from
|
| 32 |
+
``tests/tt/results/q_summary.md`` (a class must pass on all three versions, the table is version-independent).
|
| 33 |
+
|
| 34 |
+
CPU tier (``-k cpu``): the sweep policies (``with_admitted`` / ``weights_tag`` / ``dtype_of`` histograms per step
|
| 35 |
+
and version), the validation of the new ``TTPolicy`` fields, the step table.
|
| 36 |
+
|
| 37 |
+
Workarounds in this file (owned files only; each one is marked ``WORKAROUND``): the K/V experiment rounds the
|
| 38 |
+
adapter's persistent bf16 K/V buffers in place through ``ttnn.typecast`` (``tt/adapter.py`` refuses
|
| 39 |
+
``kv_dtype="bfp8_b"``; a real bfp8 buffer needs the typecast after the head split there); the bfp4 experiment swaps
|
| 40 |
+
the DiT blocks' ``w_ff1`` / ``w_ff2`` device tensors for bfp8 tensors holding bfp4-rounded values (bfp4 values are
|
| 41 |
+
exactly representable in bfp8 with the same shared exponent, so the matmul numerics equal a bfp4 operand; the
|
| 42 |
+
matmul table of ``tt/layers.py`` has no ``bfp4_b`` rows, so the byte / latency benefit is not measured).
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
from __future__ import annotations
|
| 46 |
+
|
| 47 |
+
import dataclasses
|
| 48 |
+
import shutil
|
| 49 |
+
import statistics
|
| 50 |
+
import time
|
| 51 |
+
from dataclasses import dataclass, field
|
| 52 |
+
from pathlib import Path
|
| 53 |
+
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Set, Tuple
|
| 54 |
+
|
| 55 |
+
import pytest
|
| 56 |
+
import torch
|
| 57 |
+
|
| 58 |
+
from models.experimental.gr00t.common.configs import GR00T_VERSIONS
|
| 59 |
+
from models.experimental.gr00t.common.golden import GoldenSet, list_multi_samples
|
| 60 |
+
from models.experimental.gr00t.reference.model import load_golden_sample
|
| 61 |
+
from models.experimental.gr00t.tests.tt import harness
|
| 62 |
+
from models.experimental.gr00t.tests.tt import test_tt_model_e2e as E2E_T
|
| 63 |
+
from models.experimental.gr00t.tests.tt.test_bfp8_emulation_cpu import bfp4_round, bfp_round, mantissa_step
|
| 64 |
+
from models.experimental.gr00t.tt import model as M
|
| 65 |
+
from models.experimental.gr00t.tt import weights as TTW
|
| 66 |
+
from models.experimental.gr00t.tt.layout import StaticShapeError
|
| 67 |
+
from models.experimental.gr00t.tt.policy import (
|
| 68 |
+
ADMITTED_CLASSES,
|
| 69 |
+
BFP8_CANDIDATE_CATEGORIES,
|
| 70 |
+
TTPolicy,
|
| 71 |
+
dtype_decisions,
|
| 72 |
+
dtype_histogram,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
torch.set_grad_enabled(False)
|
| 76 |
+
|
| 77 |
+
CACHE_TAG = "wpq"
|
| 78 |
+
SWEEP_STEPS: Tuple[str, ...] = (
|
| 79 |
+
"baseline",
|
| 80 |
+
"vision",
|
| 81 |
+
"projector",
|
| 82 |
+
"llm",
|
| 83 |
+
"vl_self_attention",
|
| 84 |
+
"kv_bfp8",
|
| 85 |
+
"dit_ff_bfp4",
|
| 86 |
+
)
|
| 87 |
+
#: weight-class steps -> plan categories switched to bfp8_b on top of the admitted set
|
| 88 |
+
WEIGHT_CLASS_STEPS: Dict[str, Tuple[str, ...]] = {
|
| 89 |
+
"vision": ("vision",),
|
| 90 |
+
"projector": ("projector", "vision_merger"),
|
| 91 |
+
"llm": ("llm",),
|
| 92 |
+
"vl_self_attention": ("vl_self_attention",),
|
| 93 |
+
}
|
| 94 |
+
EXPERIMENT_STEPS: Tuple[str, ...] = ("kv_bfp8", "dit_ff_bfp4")
|
| 95 |
+
MULTI_ACTION_SLACK = 1e-3 # multi-sample admission: action_pred_valid PCC may drop by at most this vs the baseline
|
| 96 |
+
LATENCY_WARMUP = 5
|
| 97 |
+
LATENCY_REPS = 20
|
| 98 |
+
MIN_FREE_DISK_GB = 8.0 # the sweep tier copies one version's tensors (<= 4.8 GB bf16); never fill the disk
|
| 99 |
+
ACTION_KEY = "action_pred_valid"
|
| 100 |
+
BFP_DTYPES: Dict[str, int] = {"bfp8_b": 7, "bfp4_b": 3}
|
| 101 |
+
#: plan §1.2 "After WP-Q admission": the canonical taps each class's row names as its admission gate (the sweep's
|
| 102 |
+
#: automatic decision is stricter -- every canonical tap + no new multi-sample failure; both verdicts are recorded).
|
| 103 |
+
PLAN_GATE_TAPS: Dict[str, Dict[str, Tuple[str, ...]]] = {
|
| 104 |
+
"vision": {v: ("vit_block_last", "projector_out") for v in GR00T_VERSIONS},
|
| 105 |
+
"projector": {v: ("projector_out",) for v in GR00T_VERSIONS},
|
| 106 |
+
"llm": {"n15": ("backbone_features",), "n16": ("backbone_features",), "n17": ("vlln_out",)},
|
| 107 |
+
"vl_self_attention": {v: ("vl_self_attention_out",) for v in GR00T_VERSIONS},
|
| 108 |
+
"kv_bfp8": {
|
| 109 |
+
v: tuple(f"{t}[k={k}]" for t in ("dit_block_last_out", "pred_velocity") for k in range(4))
|
| 110 |
+
for v in GR00T_VERSIONS
|
| 111 |
+
},
|
| 112 |
+
"dit_ff_bfp4": {
|
| 113 |
+
v: tuple(f"{t}[k={k}]" for t in ("dit_block_last_out", "pred_velocity") for k in range(4))
|
| 114 |
+
for v in GR00T_VERSIONS
|
| 115 |
+
},
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def plan_gate_verdict(step: str, version: str, pcc: Mapping[str, float], gates: Mapping[str, float]) -> Optional[bool]:
|
| 120 |
+
"""``True`` when every plan §1.2 gate tap of ``step`` holds on the canonical golden (``None`` for the baseline)."""
|
| 121 |
+
taps = PLAN_GATE_TAPS.get(step, {}).get(version)
|
| 122 |
+
if taps is None:
|
| 123 |
+
return None
|
| 124 |
+
for t in taps:
|
| 125 |
+
if t not in pcc or t not in gates:
|
| 126 |
+
raise KeyError(f"{version}/{step}: plan gate tap {t!r} missing from the canonical report")
|
| 127 |
+
return all(pcc[t] >= gates[t] for t in taps)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 131 |
+
# sweep policies (CPU)
|
| 132 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 133 |
+
def step_admitted(step: str, admitted_so_far: Mapping[str, str]) -> Dict[str, str]:
|
| 134 |
+
"""The admitted-class table a step loads with: the classes admitted so far plus the step's class at bfp8_b
|
| 135 |
+
(experiments and the baseline load the table as is)."""
|
| 136 |
+
if step not in SWEEP_STEPS:
|
| 137 |
+
raise KeyError(f"unknown sweep step {step!r}; expected one of {SWEEP_STEPS}")
|
| 138 |
+
table = dict(admitted_so_far)
|
| 139 |
+
for cat in WEIGHT_CLASS_STEPS.get(step, ()):
|
| 140 |
+
table[cat] = "bfp8_b"
|
| 141 |
+
return table
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def category_dtype_counts(version: str, policy: TTPolicy) -> Dict[str, Dict[str, int]]:
|
| 145 |
+
"""``{category: {device dtype: n tensors}}`` of the canonical embodiment's plan under ``policy`` (headers only)."""
|
| 146 |
+
entries = TTW.upload_plan(version, None, policy)
|
| 147 |
+
out: Dict[str, Dict[str, int]] = {}
|
| 148 |
+
for e in entries.values():
|
| 149 |
+
d = out.setdefault(e.category, {})
|
| 150 |
+
d[e.device_dtype] = d.get(e.device_dtype, 0) + 1
|
| 151 |
+
return {k: dict(sorted(v.items())) for k, v in sorted(out.items())}
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def category_bytes(version: str, policy: TTPolicy) -> Dict[str, float]:
|
| 155 |
+
entries = TTW.upload_plan(version, None, policy)
|
| 156 |
+
out: Dict[str, float] = {}
|
| 157 |
+
for e in entries.values():
|
| 158 |
+
out[e.category] = out.get(e.category, 0.0) + float(e.nbytes)
|
| 159 |
+
return out
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def test_cpu_policy_sweep_tables() -> None:
|
| 163 |
+
"""``with_admitted`` / ``weights_tag`` / ``admitted`` / ``to_dict`` and the field validation of the WP-Q hooks."""
|
| 164 |
+
base = TTPolicy()
|
| 165 |
+
assert base.admitted() == ADMITTED_CLASSES and base.admitted() is not ADMITTED_CLASSES
|
| 166 |
+
assert base.weights_tag() == "mixed_dit" and base.to_dict()["admitted_classes"] is None
|
| 167 |
+
assert base.to_dict()["cache_tag"] is None
|
| 168 |
+
p = base.with_admitted({"dit": "bfp8_b", "llm": "bfp8_b"}, CACHE_TAG)
|
| 169 |
+
assert p.admitted_classes == (("dit", "bfp8_b"), ("llm", "bfp8_b"))
|
| 170 |
+
assert p.admitted() == {"dit": "bfp8_b", "llm": "bfp8_b"}
|
| 171 |
+
assert p.weights_tag() == f"mixed_dit+{CACHE_TAG}"
|
| 172 |
+
assert p.to_dict()["admitted_classes"] == {"dit": "bfp8_b", "llm": "bfp8_b"}
|
| 173 |
+
assert p.to_dict()["cache_tag"] == CACHE_TAG
|
| 174 |
+
assert p.trace_layout == base.trace_layout and p.dtype_policy == "mixed_dit"
|
| 175 |
+
# an explicit table is independent of the module table
|
| 176 |
+
assert base.with_admitted({}, None).admitted() == {}
|
| 177 |
+
for bad in (
|
| 178 |
+
dict(admitted_classes=[("dit", "bfp8_b")]),
|
| 179 |
+
dict(admitted_classes=(("encoders", "bfp8_b"),)),
|
| 180 |
+
dict(admitted_classes=(("dit", "fp32"),)),
|
| 181 |
+
dict(admitted_classes=(("dit", "bfp8_b"), ("dit", "bf16"))),
|
| 182 |
+
dict(admitted_classes=(("dit",),)),
|
| 183 |
+
dict(cache_tag=""),
|
| 184 |
+
dict(cache_tag="has space"),
|
| 185 |
+
dict(cache_tag="/etc"),
|
| 186 |
+
):
|
| 187 |
+
with pytest.raises((TypeError, ValueError)):
|
| 188 |
+
TTPolicy(**bad) # type: ignore[arg-type]
|
| 189 |
+
# the bf16 policy ignores the table; the mixed policy admits every candidate
|
| 190 |
+
for cat in BFP8_CANDIDATE_CATEGORIES:
|
| 191 |
+
p1 = TTPolicy(dtype_policy="bf16").with_admitted({cat: "bfp8_b"}, None)
|
| 192 |
+
assert set(dtype_histogram(dtype_decisions("n16", None, p1))) == {"bf16"}
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
@pytest.mark.parametrize("version", list(GR00T_VERSIONS))
|
| 196 |
+
def test_cpu_sweep_steps_grow_bfp8(version: str) -> None:
|
| 197 |
+
"""Along the sweep the bfp8 tensor count grows monotonically and only inside the step's categories; the known
|
| 198 |
+
no-op steps (N1.5 projector, N1.6 VL-SA) change nothing; the sweep tier never shares the production tag."""
|
| 199 |
+
base = TTPolicy()
|
| 200 |
+
admitted = dict(base.admitted())
|
| 201 |
+
prev = category_dtype_counts(version, base.with_admitted(admitted, CACHE_TAG))
|
| 202 |
+
prev_n = sum(d.get("bfp8_b", 0) for d in prev.values())
|
| 203 |
+
for step in SWEEP_STEPS:
|
| 204 |
+
table = step_admitted(step, admitted)
|
| 205 |
+
pol = base.with_admitted(table, CACHE_TAG)
|
| 206 |
+
assert pol.weights_tag() == f"mixed_dit+{CACHE_TAG}" != base.weights_tag()
|
| 207 |
+
counts = category_dtype_counts(version, pol)
|
| 208 |
+
n = sum(d.get("bfp8_b", 0) for d in counts.values())
|
| 209 |
+
assert n >= prev_n, step
|
| 210 |
+
changed = {c for c in counts if counts[c] != prev.get(c)}
|
| 211 |
+
assert changed <= set(WEIGHT_CLASS_STEPS.get(step, ())), (step, changed)
|
| 212 |
+
if (version, step) in (("n15", "projector"), ("n16", "vl_self_attention")):
|
| 213 |
+
assert n == prev_n, f"{version}/{step} must be a no-op (plan §1.2)"
|
| 214 |
+
elif step in WEIGHT_CLASS_STEPS:
|
| 215 |
+
assert n > prev_n, f"{version}/{step} admitted no tensor"
|
| 216 |
+
for c in WEIGHT_CLASS_STEPS.get(step, ()):
|
| 217 |
+
gain = counts.get(c, {}).get("bfp8_b", 0) - prev.get(c, {}).get("bfp8_b", 0)
|
| 218 |
+
assert counts.get(c, {}).get("bf16", 0) == prev.get(c, {}).get("bf16", 0) - gain, (step, c)
|
| 219 |
+
admitted = table
|
| 220 |
+
prev, prev_n = counts, n
|
| 221 |
+
# every candidate category admitted == the host plan's own "mixed" dtype
|
| 222 |
+
full = base.with_admitted({c: "bfp8_b" for c in BFP8_CANDIDATE_CATEGORIES}, None)
|
| 223 |
+
assert dtype_decisions(version, None, full) == dtype_decisions(version, None, TTPolicy(dtype_policy="mixed"))
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def test_cpu_step_table() -> None:
|
| 227 |
+
assert SWEEP_STEPS[0] == "baseline" and set(WEIGHT_CLASS_STEPS) | set(EXPERIMENT_STEPS) == set(SWEEP_STEPS[1:])
|
| 228 |
+
for cats in WEIGHT_CLASS_STEPS.values():
|
| 229 |
+
assert set(cats) <= set(BFP8_CANDIDATE_CATEGORIES)
|
| 230 |
+
assert set(BFP8_CANDIDATE_CATEGORIES) - {"dit"} == {c for cats in WEIGHT_CLASS_STEPS.values() for c in cats}
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 234 |
+
# device: the rounding primitive vs ttnn
|
| 235 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 236 |
+
def _probe_tensors(seed: int = 0) -> Dict[str, torch.Tensor]:
|
| 237 |
+
g = torch.Generator().manual_seed(seed)
|
| 238 |
+
out: Dict[str, torch.Tensor] = {}
|
| 239 |
+
# per-row scale spread over 6 decades -> many distinct shared exponents, some blocks with huge exponent spread
|
| 240 |
+
x = torch.randn(64, 96, generator=g) * torch.pow(10.0, torch.rand(64, 1, generator=g) * 6 - 3)
|
| 241 |
+
x[3, :8] = 0.0
|
| 242 |
+
out["64x96_spread"] = x
|
| 243 |
+
out["200x72_ragged"] = torch.randn(200, 72, generator=g)
|
| 244 |
+
out["1x32x100x64"] = torch.randn(1, 32, 100, 64, generator=g) * 4.0
|
| 245 |
+
w = torch.randn(1152, 4608, generator=g) * 0.02
|
| 246 |
+
w[::7, ::5] *= 40.0 # outlier weights inside blocks
|
| 247 |
+
out["1152x4608_weightlike"] = w
|
| 248 |
+
return out
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
@pytest.mark.parametrize("dtype_name", list(BFP_DTYPES))
|
| 252 |
+
def test_device_bfp_round_matches_ttnn(gr00t_device: Any, results: harness.ResultsWriter, dtype_name: str) -> None:
|
| 253 |
+
"""``ttnn.from_torch(x, <bfp>, TILE)`` (host pack, and the same through the device) vs :func:`bfp_round`."""
|
| 254 |
+
import ttnn
|
| 255 |
+
|
| 256 |
+
mbits = BFP_DTYPES[dtype_name]
|
| 257 |
+
tt_dt = {"bfp8_b": ttnn.bfloat8_b, "bfp4_b": ttnn.bfloat4_b}[dtype_name]
|
| 258 |
+
worst_steps = 0.0
|
| 259 |
+
for name, x in _probe_tensors().items():
|
| 260 |
+
for kind, xin in (("bf16", x.to(torch.bfloat16).to(torch.float32)), ("fp32", x)):
|
| 261 |
+
emu = bfp_round(xin, mbits)
|
| 262 |
+
step = mantissa_step(xin, mbits)
|
| 263 |
+
host = ttnn.to_torch(ttnn.from_torch(xin, dtype=tt_dt, layout=ttnn.TILE_LAYOUT)).to(torch.float32)
|
| 264 |
+
dev_t = ttnn.from_torch(xin, dtype=tt_dt, layout=ttnn.TILE_LAYOUT, device=gr00t_device)
|
| 265 |
+
dev = ttnn.to_torch(dev_t).to(torch.float32)
|
| 266 |
+
ttnn.deallocate(dev_t)
|
| 267 |
+
assert host.shape == emu.shape == dev.shape, (name, host.shape, emu.shape)
|
| 268 |
+
assert torch.equal(host, dev), f"{name}/{kind}: host pack != device round trip"
|
| 269 |
+
diff_steps = (host - emu).abs() / step
|
| 270 |
+
max_steps = float(diff_steps.max())
|
| 271 |
+
exact = float((host == emu).to(torch.float32).mean())
|
| 272 |
+
worst_steps = max(worst_steps, max_steps)
|
| 273 |
+
ok = max_steps <= 1.0 + 1e-6
|
| 274 |
+
results.add_row(
|
| 275 |
+
{
|
| 276 |
+
"tap": "bfp_round",
|
| 277 |
+
"key": f"{dtype_name}/{name}/{kind}",
|
| 278 |
+
"rule": "abs",
|
| 279 |
+
"ok": ok,
|
| 280 |
+
"shape": list(xin.shape),
|
| 281 |
+
"exact_fraction": exact,
|
| 282 |
+
"max_mantissa_steps": max_steps,
|
| 283 |
+
"n_mismatch": int((host != emu).sum()),
|
| 284 |
+
"max_abs": float((host - emu).abs().max()),
|
| 285 |
+
"rel_l2_vs_input": float((host - xin).norm() / xin.norm()),
|
| 286 |
+
}
|
| 287 |
+
)
|
| 288 |
+
print(f"[{dtype_name}] {name:22s} {kind}: exact {exact:.6f}, max |d| {max_steps:.3f} mantissa steps")
|
| 289 |
+
assert ok, f"{dtype_name}/{name}/{kind}: emulation off by {max_steps} mantissa steps"
|
| 290 |
+
results.set_meta(dtype=dtype_name, mantissa_bits=mbits, worst_mantissa_steps=worst_steps)
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 294 |
+
# device: the class sweep
|
| 295 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 296 |
+
@dataclass
|
| 297 |
+
class StepResult:
|
| 298 |
+
step: str
|
| 299 |
+
admitted_loaded: Dict[str, str]
|
| 300 |
+
pcc: Dict[str, float] = field(default_factory=dict) # canonical: key -> pcc
|
| 301 |
+
gates: Dict[str, float] = field(default_factory=dict)
|
| 302 |
+
fails: List[str] = field(default_factory=list) # canonical failing keys (before known-miss handling)
|
| 303 |
+
known: List[str] = field(default_factory=list)
|
| 304 |
+
unknown: List[str] = field(default_factory=list)
|
| 305 |
+
multi_pcc: Dict[str, Dict[str, float]] = field(default_factory=dict) # sample -> key -> pcc
|
| 306 |
+
multi_fails: Dict[str, List[str]] = field(default_factory=dict)
|
| 307 |
+
multi_errors: Dict[str, str] = field(default_factory=dict)
|
| 308 |
+
latency_ms: Dict[str, Any] = field(default_factory=dict)
|
| 309 |
+
timing: Dict[str, float] = field(default_factory=dict)
|
| 310 |
+
device_bytes: float = 0.0
|
| 311 |
+
dtype_hist: Dict[str, int] = field(default_factory=dict)
|
| 312 |
+
canonical_ok: bool = False
|
| 313 |
+
multi_ok: bool = False
|
| 314 |
+
admitted: bool = False
|
| 315 |
+
note: str = ""
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
@dataclass
|
| 319 |
+
class SweepState:
|
| 320 |
+
version: str
|
| 321 |
+
admitted: Dict[str, str]
|
| 322 |
+
steps: Dict[str, StepResult] = field(default_factory=dict)
|
| 323 |
+
tensorbin_dirs: Set[Path] = field(default_factory=set)
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
_STATE: Dict[str, SweepState] = {}
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def state_for(version: str, base_policy: TTPolicy) -> SweepState:
|
| 330 |
+
st = _STATE.get(version)
|
| 331 |
+
if st is None:
|
| 332 |
+
st = SweepState(version=version, admitted=dict(base_policy.admitted()))
|
| 333 |
+
_STATE[version] = st
|
| 334 |
+
return st
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def _stats_ms(samples: Sequence[float]) -> Dict[str, float]:
|
| 338 |
+
s = sorted(samples)
|
| 339 |
+
return {
|
| 340 |
+
"median": statistics.median(s),
|
| 341 |
+
"p90": s[min(len(s) - 1, int(round(0.9 * (len(s) - 1))))],
|
| 342 |
+
"min": s[0],
|
| 343 |
+
"n": len(s),
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def traced_latency(model: M.Gr00tTT, mi: Any, noise: torch.Tensor, warmup: int, reps: int) -> Dict[str, Any]:
|
| 348 |
+
"""``benchmarks/bench_e2e.py`` method on an already captured model: per call ``upload (+sync) -> every trace
|
| 349 |
+
replay -> one sync -> D2H``; medians / p90 of the main pass, then a split pass with a sync after every trace."""
|
| 350 |
+
tracer = model.tracer
|
| 351 |
+
stages = list(tracer.stage_names)
|
| 352 |
+
|
| 353 |
+
def one(split: bool) -> Dict[str, float]:
|
| 354 |
+
t: Dict[str, float] = {}
|
| 355 |
+
t0 = time.perf_counter()
|
| 356 |
+
tracer.upload(mi, noise)
|
| 357 |
+
tracer.synchronize()
|
| 358 |
+
t1 = time.perf_counter()
|
| 359 |
+
for st in stages:
|
| 360 |
+
ts = time.perf_counter()
|
| 361 |
+
tracer.replay(st)
|
| 362 |
+
if split:
|
| 363 |
+
tracer.synchronize()
|
| 364 |
+
t[f"trace/{st}"] = (time.perf_counter() - ts) * 1e3
|
| 365 |
+
tracer.synchronize()
|
| 366 |
+
t2 = time.perf_counter()
|
| 367 |
+
tracer.read_action_pred()
|
| 368 |
+
t3 = time.perf_counter()
|
| 369 |
+
t.update(upload=(t1 - t0) * 1e3, traces=(t2 - t1) * 1e3, d2h=(t3 - t2) * 1e3, device=(t3 - t0) * 1e3)
|
| 370 |
+
return t
|
| 371 |
+
|
| 372 |
+
for _ in range(warmup):
|
| 373 |
+
one(False)
|
| 374 |
+
main = [one(False) for _ in range(reps)]
|
| 375 |
+
split_pass = [one(True) for _ in range(reps)]
|
| 376 |
+
out: Dict[str, Any] = {k: _stats_ms([c[k] for c in main]) for k in ("device", "traces", "upload", "d2h")}
|
| 377 |
+
for st in stages:
|
| 378 |
+
out[f"trace/{st}"] = _stats_ms([c[f"trace/{st}"] for c in split_pass])
|
| 379 |
+
out["device_split_pass"] = _stats_ms([c["device"] for c in split_pass])
|
| 380 |
+
return out
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def install_kv_rounding(model: M.Gr00tTT, mantissa_bits: int = 7) -> Any:
|
| 384 |
+
"""WORKAROUND (tt/adapter.py not owned; it raises on ``kv_dtype="bfp8_b"``): wrap the executor's adapter stage so
|
| 385 |
+
that, right after the hoist, every persistent ``K_i`` / ``V_i`` buffer is rounded in place to bfp8 values
|
| 386 |
+
(``typecast -> bfloat8_b -> bfloat16 -> copy``). The buffers stay bf16 for the DiT consumers, so the numerics
|
| 387 |
+
equal a bfp8 K/V store (plan §1.2 row 6) while the byte traffic does not. Returns the restore function."""
|
| 388 |
+
import ttnn
|
| 389 |
+
|
| 390 |
+
ex = model.executor
|
| 391 |
+
idx = [i for i, st in enumerate(ex._stages) if st.name == M.STAGE_ADAPTER]
|
| 392 |
+
if len(idx) != 1:
|
| 393 |
+
raise RuntimeError(f"expected one adapter stage, found {len(idx)}")
|
| 394 |
+
orig = ex._stages[idx[0]]
|
| 395 |
+
tt_dt = {7: ttnn.bfloat8_b, 3: ttnn.bfloat4_b}[mantissa_bits]
|
| 396 |
+
|
| 397 |
+
def wrapped(inputs: Mapping[str, Any], state: Mapping[str, Any]) -> Dict[str, Any]:
|
| 398 |
+
out = orig.fn(inputs, state)
|
| 399 |
+
for buffers in (model.adapter.K, model.adapter.V):
|
| 400 |
+
for _, buf in sorted(buffers.items()):
|
| 401 |
+
low = ttnn.typecast(buf, tt_dt)
|
| 402 |
+
back = ttnn.typecast(low, ttnn.bfloat16)
|
| 403 |
+
ttnn.copy(back, buf)
|
| 404 |
+
ttnn.deallocate(low)
|
| 405 |
+
ttnn.deallocate(back)
|
| 406 |
+
return out
|
| 407 |
+
|
| 408 |
+
ex._stages[idx[0]] = dataclasses.replace(orig, fn=wrapped)
|
| 409 |
+
|
| 410 |
+
def restore() -> None:
|
| 411 |
+
ex._stages[idx[0]] = orig
|
| 412 |
+
|
| 413 |
+
return restore
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def swap_dit_ff_bfp4(model: M.Gr00tTT, device: Any) -> Tuple[Any, Dict[str, Any]]:
|
| 417 |
+
"""WORKAROUND (tt/weights.py / tt/layers.py not owned: no ``bfp4_b`` plan dtype or matmul rows): replace every DiT
|
| 418 |
+
block's ``w_ff1`` / ``w_ff2`` by a bfp8_b device tensor whose values were rounded to bfp4_b by ttnn (bfp4 values
|
| 419 |
+
are exactly representable in bfp8 under the same shared exponent). Returns ``(restore, facts)``; ``facts``
|
| 420 |
+
records the agreement of ttnn's bfp4 pack with ``bfp4_round`` on the real weights."""
|
| 421 |
+
import ttnn
|
| 422 |
+
|
| 423 |
+
originals: List[Tuple[Any, str, Any]] = []
|
| 424 |
+
swapped: List[Any] = []
|
| 425 |
+
n_exact = 0
|
| 426 |
+
n_total = 0
|
| 427 |
+
max_steps = 0.0
|
| 428 |
+
rel_l2: List[float] = []
|
| 429 |
+
for blk in model.head.step.blocks:
|
| 430 |
+
for attr in ("w_ff1", "w_ff2"):
|
| 431 |
+
t = getattr(blk, attr)
|
| 432 |
+
host = ttnn.to_torch(t).to(torch.float32) # the bfp8 weight values
|
| 433 |
+
h4 = ttnn.to_torch(ttnn.from_torch(host, dtype=ttnn.bfloat4_b, layout=ttnn.TILE_LAYOUT)).to(torch.float32)
|
| 434 |
+
emu = bfp4_round(host)
|
| 435 |
+
n_exact += int((h4 == emu).sum())
|
| 436 |
+
n_total += h4.numel()
|
| 437 |
+
max_steps = max(max_steps, float(((h4 - emu).abs() / mantissa_step(host, 3)).max()))
|
| 438 |
+
rel_l2.append(float((h4 - host).norm() / host.norm()))
|
| 439 |
+
dev = ttnn.from_torch(
|
| 440 |
+
h4, dtype=ttnn.bfloat8_b, layout=ttnn.TILE_LAYOUT, device=device, memory_config=ttnn.DRAM_MEMORY_CONFIG
|
| 441 |
+
)
|
| 442 |
+
originals.append((blk, attr, t))
|
| 443 |
+
swapped.append(dev)
|
| 444 |
+
setattr(blk, attr, dev)
|
| 445 |
+
|
| 446 |
+
def restore() -> None:
|
| 447 |
+
for blk, attr, t in originals:
|
| 448 |
+
setattr(blk, attr, t)
|
| 449 |
+
for dev in swapped:
|
| 450 |
+
ttnn.deallocate(dev)
|
| 451 |
+
|
| 452 |
+
facts = {
|
| 453 |
+
"n_tensors": len(swapped),
|
| 454 |
+
"bfp4_exact_fraction_vs_emulation": n_exact / max(1, n_total),
|
| 455 |
+
"bfp4_max_mantissa_steps_vs_emulation": max_steps,
|
| 456 |
+
"bfp4_rel_l2_vs_bfp8_weights_mean": sum(rel_l2) / len(rel_l2),
|
| 457 |
+
"bfp4_rel_l2_vs_bfp8_weights_max": max(rel_l2),
|
| 458 |
+
}
|
| 459 |
+
return restore, facts
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def gated_candidates(
|
| 463 |
+
model: M.Gr00tTT, gs: GoldenSet, obs: Any, taps: Mapping[str, torch.Tensor], pred: torch.Tensor
|
| 464 |
+
) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]:
|
| 465 |
+
"""Exactly the e2e test's candidate set: golden-keyed device taps + decoded groups, N1.7 raw LLM taps split off
|
| 466 |
+
as informational rows (``test_tt_model_e2e.INFO_TAPS``)."""
|
| 467 |
+
cand = E2E_T.golden_keyed(dict(taps), gs)
|
| 468 |
+
cand.update(E2E_T.decoded_groups(model, pred, obs))
|
| 469 |
+
cand, info = E2E_T.split_info(model.version, cand)
|
| 470 |
+
return cand, info
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def check_free_disk(path: Path) -> float:
|
| 474 |
+
free_gb = shutil.disk_usage(str(path)).free / 1e9
|
| 475 |
+
if free_gb < MIN_FREE_DISK_GB:
|
| 476 |
+
pytest.fail(f"only {free_gb:.1f} GB free under {path}; the sweep tier needs {MIN_FREE_DISK_GB} GB")
|
| 477 |
+
return free_gb
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
@pytest.fixture(scope="module")
|
| 481 |
+
def sweep_cleanup(version: str) -> Iterator[None]:
|
| 482 |
+
"""Remove the ``+wpq`` tensorbin tiers this module created (one version's tensors, up to 4.8 GB)."""
|
| 483 |
+
yield
|
| 484 |
+
st = _STATE.get(version)
|
| 485 |
+
if st is None:
|
| 486 |
+
return
|
| 487 |
+
for d in sorted(st.tensorbin_dirs):
|
| 488 |
+
if d.is_dir() and d.name.endswith(f"+{CACHE_TAG}"):
|
| 489 |
+
size = sum(p.stat().st_size for p in d.rglob("*") if p.is_file())
|
| 490 |
+
shutil.rmtree(d, ignore_errors=True)
|
| 491 |
+
print(f"\n[test_tt_dtype_policy] removed sweep tier {d} ({size / 1e9:.2f} GB)")
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
def _known_miss_split(version: str, gs: GoldenSet, report: harness.Report, cand: Mapping[str, torch.Tensor]):
|
| 495 |
+
known, unknown = E2E_T.known_miss_rows(version, gs, report, cand)
|
| 496 |
+
return known, unknown
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
@pytest.mark.parametrize("step", list(SWEEP_STEPS))
|
| 500 |
+
def test_device_dtype_sweep(
|
| 501 |
+
version: str,
|
| 502 |
+
step: str,
|
| 503 |
+
golden: Tuple[Any, Any, Any],
|
| 504 |
+
gr00t_device: Any,
|
| 505 |
+
policy: TTPolicy,
|
| 506 |
+
results: harness.ResultsWriter,
|
| 507 |
+
sweep_cleanup: None,
|
| 508 |
+
) -> None:
|
| 509 |
+
"""One sweep step (module docstring): load with the step's table, gate canonical + multi, time the traces, decide."""
|
| 510 |
+
import ttnn
|
| 511 |
+
|
| 512 |
+
if policy.dtype_policy != "mixed_dit":
|
| 513 |
+
pytest.fail(f"the sweep needs --policy mixed_dit (got {policy.dtype_policy!r})")
|
| 514 |
+
st = state_for(version, policy)
|
| 515 |
+
if step != "baseline" and "baseline" not in st.steps:
|
| 516 |
+
pytest.fail(f"{version}: the baseline step did not run; the sweep steps depend on it")
|
| 517 |
+
table = step_admitted(step, st.admitted)
|
| 518 |
+
pol = policy.with_admitted(table, CACHE_TAG)
|
| 519 |
+
counts = category_dtype_counts(version, pol)
|
| 520 |
+
new_cats = WEIGHT_CLASS_STEPS.get(step, ())
|
| 521 |
+
n_new = sum(counts.get(c, {}).get("bfp8_b", 0) for c in new_cats)
|
| 522 |
+
res = StepResult(step=step, admitted_loaded=dict(table))
|
| 523 |
+
results.set_meta(
|
| 524 |
+
step=step,
|
| 525 |
+
admitted_loaded=dict(table),
|
| 526 |
+
admitted_before=dict(st.admitted),
|
| 527 |
+
policy=pol.to_dict(),
|
| 528 |
+
category_dtype_counts=counts,
|
| 529 |
+
category_mib={k: v / 2**20 for k, v in category_bytes(version, pol).items()},
|
| 530 |
+
)
|
| 531 |
+
if step in WEIGHT_CLASS_STEPS and n_new == 0:
|
| 532 |
+
res.note = f"no bfp8 candidates in {new_cats} for {version} (plan §1.2)"
|
| 533 |
+
st.steps[step] = res
|
| 534 |
+
results.set_meta(note=res.note)
|
| 535 |
+
pytest.skip(f"{version}/{step}: {res.note}")
|
| 536 |
+
|
| 537 |
+
gs, obs, mi_auto = golden
|
| 538 |
+
free_gb = check_free_disk(Path(TTW.W.default_cache_root()))
|
| 539 |
+
results.set_meta(free_disk_gb_before=free_gb)
|
| 540 |
+
|
| 541 |
+
# ---- load
|
| 542 |
+
t0 = time.perf_counter()
|
| 543 |
+
model = M.Gr00tTT.from_pretrained(version, policy=pol, device=gr00t_device)
|
| 544 |
+
res.timing["from_pretrained_s"] = time.perf_counter() - t0
|
| 545 |
+
st.tensorbin_dirs.add(Path(model.W.tensorbin_dir))
|
| 546 |
+
res.timing.update({k: v for k, v in model.timing.items() if k.endswith("_s")})
|
| 547 |
+
res.device_bytes = float(model.W.device_bytes)
|
| 548 |
+
res.dtype_hist = model.W.dtype_histogram()
|
| 549 |
+
results.set_meta(
|
| 550 |
+
weights=dict(
|
| 551 |
+
n_tensors=len(model.W),
|
| 552 |
+
device_bytes=res.device_bytes,
|
| 553 |
+
dtype_histogram=res.dtype_hist,
|
| 554 |
+
load_stats=dict(getattr(model.W, "load_stats", {})),
|
| 555 |
+
tensorbin_dir=str(model.W.tensorbin_dir),
|
| 556 |
+
)
|
| 557 |
+
)
|
| 558 |
+
print(
|
| 559 |
+
f"\n[{version}/{step}] loaded {len(model.W)} tensors {res.device_bytes / 1e6:.0f} MB {res.dtype_hist} in "
|
| 560 |
+
f"{res.timing['from_pretrained_s']:.1f} s (weights {model.timing['weights_load_s']:.1f} s) table {table}"
|
| 561 |
+
)
|
| 562 |
+
restore = None
|
| 563 |
+
try:
|
| 564 |
+
if step == "kv_bfp8":
|
| 565 |
+
restore = install_kv_rounding(model, 7)
|
| 566 |
+
elif step == "dit_ff_bfp4":
|
| 567 |
+
t1 = time.perf_counter()
|
| 568 |
+
restore, facts = swap_dit_ff_bfp4(model, gr00t_device)
|
| 569 |
+
res.timing["ff_bfp4_swap_s"] = time.perf_counter() - t1
|
| 570 |
+
results.set_meta(ff_bfp4=facts)
|
| 571 |
+
print(f"[{version}/{step}] FF weights -> bfp4 values: {facts}")
|
| 572 |
+
|
| 573 |
+
# ---- canonical golden, untraced taps
|
| 574 |
+
mi = model.encode(obs)
|
| 575 |
+
assert mi.shape_key == mi_auto.shape_key, (mi.shape_key, mi_auto.shape_key)
|
| 576 |
+
noise = gs.load("initial_noise")
|
| 577 |
+
dl = model.dlayout
|
| 578 |
+
t2 = time.perf_counter()
|
| 579 |
+
pred, taps = model.run_untraced(mi, noise, return_taps=True)
|
| 580 |
+
res.timing["untraced_first_s"] = time.perf_counter() - t2
|
| 581 |
+
res.timing["untraced_device_s"] = float(model.timing["last_untraced_s"])
|
| 582 |
+
assert bool(torch.isfinite(pred).all()), "non-finite action_pred"
|
| 583 |
+
cand, info = gated_candidates(model, gs, obs, taps, pred)
|
| 584 |
+
required = E2E_T.required_gated_keys(version, gs, dl) | {k for k in cand if k.startswith(M.DECODE_TAP)}
|
| 585 |
+
assert set(cand) == required, (sorted(required - set(cand)), sorted(set(cand) - required))
|
| 586 |
+
report = harness.check_taps(version, cand, gs, results=results)
|
| 587 |
+
for r_ in E2E_T.info_rows(gs, info):
|
| 588 |
+
results.add_row(r_)
|
| 589 |
+
res.pcc[r_["key"] + "[info]"] = float(r_["pcc"])
|
| 590 |
+
res.pcc.update({r["key"]: float(r["pcc"]) for r in report.rows if r.get("pcc") is not None})
|
| 591 |
+
res.gates.update({r["key"]: float(r["gate_pcc"]) for r in report.rows if r.get("gate_pcc") is not None})
|
| 592 |
+
res.fails = [r["key"] for r in report.failures()]
|
| 593 |
+
known, unknown = _known_miss_split(version, gs, report, cand)
|
| 594 |
+
for r_ in known:
|
| 595 |
+
results.add_row(r_)
|
| 596 |
+
res.known = [r_["key"] for r_ in known]
|
| 597 |
+
res.unknown = [r_["key"] for r_ in unknown]
|
| 598 |
+
res.canonical_ok = not unknown
|
| 599 |
+
print(report.summary(failures_only=True) if report.failures() else f"[{version}/{step}] canonical: all pass")
|
| 600 |
+
|
| 601 |
+
# ---- multi samples
|
| 602 |
+
for sample in list_multi_samples(version):
|
| 603 |
+
try:
|
| 604 |
+
gs_s, obs_s = load_golden_sample(version, sample)
|
| 605 |
+
mi_s = model.encode(obs_s)
|
| 606 |
+
pred_s, taps_s = model.run_untraced(mi_s, gs_s.load("initial_noise"), return_taps=True)
|
| 607 |
+
cand_s, _ = gated_candidates(model, gs_s, obs_s, taps_s, pred_s)
|
| 608 |
+
rep_s = harness.check_taps(version, cand_s, gs_s, results=results)
|
| 609 |
+
except (StaticShapeError, FileNotFoundError, KeyError) as exc: # recorded, never silent
|
| 610 |
+
res.multi_errors[sample] = f"{type(exc).__name__}: {exc}"
|
| 611 |
+
results.add_row(
|
| 612 |
+
E2E_T.row("multi", f"{sample}/error", False, rule="info", error=res.multi_errors[sample])
|
| 613 |
+
)
|
| 614 |
+
print(f"[{version}/{step}] multi {sample}: {res.multi_errors[sample]}")
|
| 615 |
+
continue
|
| 616 |
+
res.multi_pcc[sample] = {r["key"]: float(r["pcc"]) for r in rep_s.rows if r.get("pcc") is not None}
|
| 617 |
+
res.multi_fails[sample] = [r["key"] for r in rep_s.failures()]
|
| 618 |
+
print(
|
| 619 |
+
f"[{version}/{step}] multi {sample}: rows {len(rep_s.rows)} fail {len(rep_s.failures())} "
|
| 620 |
+
f"{ACTION_KEY} {res.multi_pcc[sample].get(ACTION_KEY, float('nan')):.6f}"
|
| 621 |
+
)
|
| 622 |
+
|
| 623 |
+
# ---- traced latency (bench_e2e method) -- the K/V hook is removed first (its typecasts are not production)
|
| 624 |
+
if step == "kv_bfp8" and restore is not None:
|
| 625 |
+
restore()
|
| 626 |
+
restore = None
|
| 627 |
+
t3 = time.perf_counter()
|
| 628 |
+
model.warm_and_capture(mi, noise)
|
| 629 |
+
res.timing["warm_and_capture_s"] = time.perf_counter() - t3
|
| 630 |
+
res.latency_ms = traced_latency(model, mi, noise, LATENCY_WARMUP, LATENCY_REPS)
|
| 631 |
+
print(
|
| 632 |
+
f"[{version}/{step}] traced: traces (device only) ms median {res.latency_ms['traces']['median']:.1f}, "
|
| 633 |
+
f"upload {res.latency_ms['upload']['median']:.1f}, device (upload..D2H) {res.latency_ms['device']['median']:.1f} "
|
| 634 |
+
f"(p90 {res.latency_ms['device']['p90']:.1f}); split "
|
| 635 |
+
+ " ".join(
|
| 636 |
+
f"{k.split('/')[1]}={v['median']:.1f}" for k, v in res.latency_ms.items() if k.startswith("trace/")
|
| 637 |
+
)
|
| 638 |
+
)
|
| 639 |
+
finally:
|
| 640 |
+
if restore is not None:
|
| 641 |
+
restore()
|
| 642 |
+
model.release()
|
| 643 |
+
ttnn.synchronize_device(gr00t_device)
|
| 644 |
+
|
| 645 |
+
# ---- decision vs the baseline
|
| 646 |
+
base = st.steps.get("baseline")
|
| 647 |
+
deltas: Dict[str, float] = {}
|
| 648 |
+
multi_new_fails: Dict[str, List[str]] = {}
|
| 649 |
+
multi_action_drop: Dict[str, float] = {}
|
| 650 |
+
if base is not None and step != "baseline":
|
| 651 |
+
deltas = {k: res.pcc[k] - base.pcc[k] for k in res.pcc if k in base.pcc}
|
| 652 |
+
for sample, fails in res.multi_fails.items():
|
| 653 |
+
base_fails = set(base.multi_fails.get(sample, ()))
|
| 654 |
+
multi_new_fails[sample] = sorted(set(fails) - base_fails)
|
| 655 |
+
a, b = res.multi_pcc[sample].get(ACTION_KEY), base.multi_pcc.get(sample, {}).get(ACTION_KEY)
|
| 656 |
+
if a is not None and b is not None:
|
| 657 |
+
multi_action_drop[sample] = b - a
|
| 658 |
+
res.multi_ok = (
|
| 659 |
+
not res.multi_errors
|
| 660 |
+
and all(not v for v in multi_new_fails.values())
|
| 661 |
+
and all(d <= MULTI_ACTION_SLACK for d in multi_action_drop.values())
|
| 662 |
+
and set(res.multi_fails) == set(base.multi_fails)
|
| 663 |
+
)
|
| 664 |
+
else:
|
| 665 |
+
res.multi_ok = not res.multi_errors
|
| 666 |
+
res.admitted = res.canonical_ok and res.multi_ok
|
| 667 |
+
# a plan tap missing from the canonical report is a reporting defect: recorded first (the device work of the step
|
| 668 |
+
# is done), re-raised after the JSON meta is complete (never silent, never a lost step).
|
| 669 |
+
plan_gate_error: Optional[str] = None
|
| 670 |
+
try:
|
| 671 |
+
plan_gate_ok = plan_gate_verdict(step, version, res.pcc, res.gates)
|
| 672 |
+
except KeyError as exc:
|
| 673 |
+
plan_gate_ok, plan_gate_error = None, str(exc)
|
| 674 |
+
lat_delta = None
|
| 675 |
+
traces_delta = None
|
| 676 |
+
if base is not None and step != "baseline" and base.latency_ms and res.latency_ms:
|
| 677 |
+
lat_delta = res.latency_ms["device"]["median"] - base.latency_ms["device"]["median"]
|
| 678 |
+
traces_delta = res.latency_ms["traces"]["median"] - base.latency_ms["traces"]["median"]
|
| 679 |
+
st.steps[step] = res
|
| 680 |
+
if step in WEIGHT_CLASS_STEPS and res.admitted:
|
| 681 |
+
st.admitted = dict(table)
|
| 682 |
+
results.set_meta(
|
| 683 |
+
canonical_ok=res.canonical_ok,
|
| 684 |
+
canonical_fails=res.fails,
|
| 685 |
+
known_misses=res.known,
|
| 686 |
+
unknown_misses=res.unknown,
|
| 687 |
+
multi_ok=res.multi_ok,
|
| 688 |
+
multi_fails=res.multi_fails,
|
| 689 |
+
multi_new_fails=multi_new_fails,
|
| 690 |
+
multi_action_pred_valid_drop=multi_action_drop,
|
| 691 |
+
multi_errors=res.multi_errors,
|
| 692 |
+
admitted=res.admitted,
|
| 693 |
+
plan_gate_ok=plan_gate_ok,
|
| 694 |
+
plan_gate_error=plan_gate_error,
|
| 695 |
+
plan_gate_taps=list(PLAN_GATE_TAPS.get(step, {}).get(version, ())),
|
| 696 |
+
latency_delta_note=(
|
| 697 |
+
"n/a by construction: experiment (kv_bfp8 hook removed before capture; dit_ff_bfp4 runs bfp8 tensors)"
|
| 698 |
+
if step in EXPERIMENT_STEPS
|
| 699 |
+
else "traces = device-only replay time (bench_e2e method); 'device' includes the host upload/D2H"
|
| 700 |
+
),
|
| 701 |
+
admitted_after=dict(st.admitted),
|
| 702 |
+
pcc_canonical=res.pcc,
|
| 703 |
+
gates_canonical=res.gates,
|
| 704 |
+
pcc_delta_vs_baseline=deltas,
|
| 705 |
+
latency_ms=res.latency_ms,
|
| 706 |
+
latency_device_median_delta_ms=lat_delta,
|
| 707 |
+
latency_traces_median_delta_ms=traces_delta,
|
| 708 |
+
free_disk_gb_after=shutil.disk_usage(str(TTW.W.default_cache_root())).free / 1e9,
|
| 709 |
+
)
|
| 710 |
+
results.set_timing(**res.timing)
|
| 711 |
+
results.add_row(
|
| 712 |
+
E2E_T.row(
|
| 713 |
+
"sweep",
|
| 714 |
+
f"step/{step}",
|
| 715 |
+
True,
|
| 716 |
+
rule="info",
|
| 717 |
+
admitted=res.admitted,
|
| 718 |
+
canonical_ok=res.canonical_ok,
|
| 719 |
+
multi_ok=res.multi_ok,
|
| 720 |
+
device_ms=res.latency_ms.get("device", {}).get("median"),
|
| 721 |
+
traces_ms=res.latency_ms.get("traces", {}).get("median"),
|
| 722 |
+
latency_delta_ms=lat_delta,
|
| 723 |
+
traces_delta_ms=traces_delta,
|
| 724 |
+
)
|
| 725 |
+
)
|
| 726 |
+
verdict = ("ADMITTED" if res.admitted else "REJECTED (bf16 kept)") + f" [plan §1.2 gate taps hold: {plan_gate_ok}]"
|
| 727 |
+
if step in EXPERIMENT_STEPS:
|
| 728 |
+
verdict = "gates hold" if res.admitted else "gates fail"
|
| 729 |
+
elif step == "baseline":
|
| 730 |
+
verdict = "baseline ok" if res.canonical_ok else "baseline FAILS"
|
| 731 |
+
print(
|
| 732 |
+
f"[{version}/{step}] {verdict}: canonical_ok={res.canonical_ok} (unknown misses {res.unknown}, known "
|
| 733 |
+
f"{res.known}) multi_ok={res.multi_ok} new multi fails {multi_new_fails} action drop {multi_action_drop}; "
|
| 734 |
+
f"device ms {res.latency_ms.get('device', {}).get('median')} (delta {lat_delta}); admitted now {st.admitted}"
|
| 735 |
+
)
|
| 736 |
+
if step == "baseline" and not res.canonical_ok:
|
| 737 |
+
raise AssertionError(
|
| 738 |
+
f"{version}: the baseline (shipped policy) misses gates beyond KNOWN_GATE_MISSES: {res.unknown}"
|
| 739 |
+
)
|
| 740 |
+
if step == "baseline" and res.known:
|
| 741 |
+
results.set_meta(baseline_known_misses=res.known)
|
| 742 |
+
if plan_gate_error is not None:
|
| 743 |
+
raise AssertionError(
|
| 744 |
+
f"{version}/{step}: plan §1.2 gate tap missing from the canonical report: {plan_gate_error}"
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 749 |
+
# summary helper (CPU): markdown over the sweep JSONs of a results directory
|
| 750 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 751 |
+
KEY_TAPS: Tuple[str, ...] = (
|
| 752 |
+
"vit_block_last",
|
| 753 |
+
"vit_post_ln",
|
| 754 |
+
"vit_deepstack_mergers[0]",
|
| 755 |
+
"projector_out",
|
| 756 |
+
"llm_layer_last",
|
| 757 |
+
"backbone_features",
|
| 758 |
+
"vlln_out",
|
| 759 |
+
"vl_self_attention_out",
|
| 760 |
+
"dit_encoder_hidden_states",
|
| 761 |
+
"dit_block_last_out[k=3]",
|
| 762 |
+
"dit_out[k=3]",
|
| 763 |
+
"pred_velocity[k=3]",
|
| 764 |
+
"action_pred_normalized",
|
| 765 |
+
"action_pred_valid",
|
| 766 |
+
)
|
| 767 |
+
|
| 768 |
+
|
| 769 |
+
def sweep_docs(results_dir: Path) -> Dict[str, Dict[str, Dict[str, Any]]]:
|
| 770 |
+
"""``{version: {step: latest results JSON}}`` of ``test_device_dtype_sweep`` under ``results_dir``."""
|
| 771 |
+
out: Dict[str, Dict[str, Dict[str, Any]]] = {}
|
| 772 |
+
for f in sorted(Path(results_dir).glob("test_device_dtype_sweep_*.json")):
|
| 773 |
+
d = harness.ResultsWriter.load(f)
|
| 774 |
+
step = d.get("meta", {}).get("step")
|
| 775 |
+
if step is None:
|
| 776 |
+
continue
|
| 777 |
+
cur = out.setdefault(d["version"], {}).get(step)
|
| 778 |
+
if cur is None or d["created"] > cur["created"]:
|
| 779 |
+
out[d["version"]][step] = d
|
| 780 |
+
return out
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
def summarise_sweep(results_dir: Optional[Path] = None) -> str:
|
| 784 |
+
docs = sweep_docs(Path(results_dir) if results_dir is not None else harness.RESULTS_DIR)
|
| 785 |
+
lines: List[str] = []
|
| 786 |
+
for version in GR00T_VERSIONS:
|
| 787 |
+
steps = docs.get(version)
|
| 788 |
+
if not steps:
|
| 789 |
+
continue
|
| 790 |
+
lines.append(f"### {version}")
|
| 791 |
+
lines.append("")
|
| 792 |
+
lines.append(
|
| 793 |
+
"| step | outcome | strict verdict (all canonical taps + no new multi fails) | plan §1.2 gate taps hold | "
|
| 794 |
+
"canonical unknown misses | known misses | multi new fails | traces ms, device only (delta vs baseline) | "
|
| 795 |
+
"upload ms (host) |"
|
| 796 |
+
)
|
| 797 |
+
lines.append("|---|---|---|---|---|---|---|---|---|")
|
| 798 |
+
base_lat = (steps.get("baseline", {}).get("meta", {}).get("latency_ms") or {}).get("traces", {}).get("median")
|
| 799 |
+
for step in SWEEP_STEPS:
|
| 800 |
+
d = steps.get(step)
|
| 801 |
+
if d is None:
|
| 802 |
+
continue
|
| 803 |
+
m = d["meta"]
|
| 804 |
+
lat = (m.get("latency_ms") or {}).get("traces", {}).get("median")
|
| 805 |
+
upload = (m.get("latency_ms") or {}).get("upload", {}).get("median")
|
| 806 |
+
delta = (lat - base_lat) if (step != "baseline" and lat is not None and base_lat is not None) else None
|
| 807 |
+
if step in EXPERIMENT_STEPS:
|
| 808 |
+
delta = None # n/a by construction: the experiments run bf16 K/V / bfp8 FF tensors in the traces
|
| 809 |
+
canon = {r["key"]: r for r in d["rows"] if r.get("sample") == "canonical" and r.get("gate_pcc") is not None}
|
| 810 |
+
try:
|
| 811 |
+
pg = plan_gate_verdict(
|
| 812 |
+
step, version, {k: r["pcc"] for k, r in canon.items()}, {k: r["gate_pcc"] for k, r in canon.items()}
|
| 813 |
+
)
|
| 814 |
+
except KeyError:
|
| 815 |
+
pg = m.get("plan_gate_ok")
|
| 816 |
+
lines.append(
|
| 817 |
+
"| {s} | {o} | {a} | {pg} | {u} | {k} | {nf} | {lat} | {up} |".format(
|
| 818 |
+
s=step,
|
| 819 |
+
o=d.get("outcome"),
|
| 820 |
+
a=(
|
| 821 |
+
"-"
|
| 822 |
+
if step == "baseline" or d.get("outcome") == "skipped"
|
| 823 |
+
else ("admitted" if m.get("admitted") else "rejected")
|
| 824 |
+
),
|
| 825 |
+
pg="-" if pg is None else pg,
|
| 826 |
+
u=", ".join(m.get("unknown_misses") or []) or "-",
|
| 827 |
+
k=", ".join(m.get("known_misses") or []) or "-",
|
| 828 |
+
nf="; ".join(f"{k}: {v}" for k, v in (m.get("multi_new_fails") or {}).items() if v) or "-",
|
| 829 |
+
lat=(f"{lat:.1f}" if isinstance(lat, (int, float)) else "-")
|
| 830 |
+
+ (
|
| 831 |
+
f" ({delta:+.1f})"
|
| 832 |
+
if isinstance(delta, (int, float))
|
| 833 |
+
else (" (n/a: experiment)" if step in EXPERIMENT_STEPS and lat is not None else "")
|
| 834 |
+
),
|
| 835 |
+
up=f"{upload:.1f}" if isinstance(upload, (int, float)) else "-",
|
| 836 |
+
)
|
| 837 |
+
)
|
| 838 |
+
present = [s for s in SWEEP_STEPS if s in steps]
|
| 839 |
+
lines.append("")
|
| 840 |
+
lines.append("Canonical golden, PCC vs the fp32 golden (gate in parentheses; `-` = step skipped / no row):")
|
| 841 |
+
lines.append("")
|
| 842 |
+
lines.append("| tap (gate) | " + " | ".join(present) + " |")
|
| 843 |
+
lines.append("|---|" + "---|" * len(present))
|
| 844 |
+
for key in KEY_TAPS:
|
| 845 |
+
cells = []
|
| 846 |
+
gate = None
|
| 847 |
+
for s in present:
|
| 848 |
+
m = steps[s]["meta"]
|
| 849 |
+
p = (m.get("pcc_canonical") or {}).get(key)
|
| 850 |
+
gate = (m.get("gates_canonical") or {}).get(key, gate)
|
| 851 |
+
cells.append(f"{p:.6f}" if isinstance(p, (int, float)) else "-")
|
| 852 |
+
if any(c != "-" for c in cells):
|
| 853 |
+
g = f" ({gate:g})" if isinstance(gate, (int, float)) else ""
|
| 854 |
+
lines.append(f"| {key}{g} | " + " | ".join(cells) + " |")
|
| 855 |
+
# multi samples: failing rows / action_pred_valid PCC per sample and step (rows gated by gates_multi.json)
|
| 856 |
+
samples = sorted({smp for s in present for smp in (steps[s]["meta"].get("multi_fails") or {})})
|
| 857 |
+
if samples:
|
| 858 |
+
lines.append("")
|
| 859 |
+
lines.append(
|
| 860 |
+
"Multi-sample goldens: failing gated rows / `action_pred_valid` PCC per sample "
|
| 861 |
+
"(`+n` = rows failing that passed at the baseline step):"
|
| 862 |
+
)
|
| 863 |
+
lines.append("")
|
| 864 |
+
lines.append("| sample | " + " | ".join(present) + " |")
|
| 865 |
+
lines.append("|---|" + "---|" * len(present))
|
| 866 |
+
for smp in samples:
|
| 867 |
+
cells = []
|
| 868 |
+
for s in present:
|
| 869 |
+
m = steps[s]["meta"]
|
| 870 |
+
fails = (m.get("multi_fails") or {}).get(smp)
|
| 871 |
+
err = (m.get("multi_errors") or {}).get(smp)
|
| 872 |
+
if err:
|
| 873 |
+
cells.append(f"error: {err.split(':')[0]}")
|
| 874 |
+
continue
|
| 875 |
+
if fails is None:
|
| 876 |
+
cells.append("-")
|
| 877 |
+
continue
|
| 878 |
+
new = (m.get("multi_new_fails") or {}).get(smp) or []
|
| 879 |
+
apv = None
|
| 880 |
+
for r in steps[s]["rows"]:
|
| 881 |
+
if r.get("sample") == smp and r.get("key") == ACTION_KEY and r.get("pcc") is not None:
|
| 882 |
+
apv = float(r["pcc"])
|
| 883 |
+
cell = f"{len(fails)}"
|
| 884 |
+
if new:
|
| 885 |
+
cell += f" (+{len(new)})"
|
| 886 |
+
cell += f" / {apv:.6f}" if apv is not None else " / -"
|
| 887 |
+
cells.append(cell)
|
| 888 |
+
lines.append(f"| {smp} | " + " | ".join(cells) + " |")
|
| 889 |
+
lines.append("")
|
| 890 |
+
return "\n".join(lines)
|
| 891 |
+
|
| 892 |
+
|
| 893 |
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
| 894 |
+
import argparse
|
| 895 |
+
|
| 896 |
+
ap = argparse.ArgumentParser(description="Summarise the WP-Q sweep JSONs as markdown.")
|
| 897 |
+
ap.add_argument("results_dir", nargs="?", default=str(harness.RESULTS_DIR))
|
| 898 |
+
args = ap.parse_args(argv)
|
| 899 |
+
print(summarise_sweep(Path(args.results_dir)))
|
| 900 |
+
return 0
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
if __name__ == "__main__":
|
| 904 |
+
raise SystemExit(main())
|
code/models/experimental/gr00t/tests/tt/test_tt_model_e2e.py
CHANGED
|
@@ -7,7 +7,12 @@ untraced), §7.5 step 1 (the untraced tap chain is the bisection walk).
|
|
| 7 |
|
| 8 |
Device tier (WP-D2; one pytest process per version, through the device lock, from the tt-metal root; the conftest
|
| 9 |
option is ``--gr00t-version``; ``TT_METAL_TRACE_ALLOC_TRACKING=1`` must be set **in the shell** -- ttnn reads it once
|
| 10 |
-
at import and the tt-metal root ``conftest.py`` imports ttnn during collection)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
TT_METAL_TRACE_ALLOC_TRACKING=1 DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh \\
|
| 13 |
python -m pytest -q -rA --timeout=1800 -p no:cacheprovider \\
|
|
@@ -133,10 +138,10 @@ def info_rows(gs: GoldenSet, info: Mapping[str, torch.Tensor]) -> List[Dict[str,
|
|
| 133 |
return out
|
| 134 |
|
| 135 |
|
| 136 |
-
def required_gated_keys(version: str, gs: GoldenSet, dl: DeviceLayout) -> Set[str]:
|
| 137 |
"""Every expected untraced tap key that has a golden in this sample, minus the informational ones."""
|
| 138 |
cfg = get_config(version)
|
| 139 |
-
expected = set(M.expected_tap_keys(cfg, dl))
|
| 140 |
return {k for k in expected if k in available_keys(gs)} - set(INFO_TAPS[version])
|
| 141 |
|
| 142 |
|
|
@@ -468,7 +473,9 @@ def build_emulated_model(version: str, gs: GoldenSet, mi: Any, device: Any, trac
|
|
| 468 |
|
| 469 |
cfg = get_config(version)
|
| 470 |
dl = DeviceLayout.for_inputs(cfg, mi)
|
| 471 |
-
|
|
|
|
|
|
|
| 472 |
key = f"{version}/{pol.rope_layout}/{pol.gate_up_layout}"
|
| 473 |
if key not in _FULL_FAKE_WEIGHTS:
|
| 474 |
t0 = time.perf_counter()
|
|
@@ -647,6 +654,43 @@ def test_cpu_emulated_e2e_vs_golden(
|
|
| 647 |
report.assert_ok()
|
| 648 |
|
| 649 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
# --------------------------------------------------------------------------------------------------------------------
|
| 651 |
# device fixtures
|
| 652 |
# --------------------------------------------------------------------------------------------------------------------
|
|
@@ -662,7 +706,8 @@ def model(request: pytest.FixtureRequest, version: str) -> Iterator[M.Gr00tTT]:
|
|
| 662 |
print(
|
| 663 |
f"\n[test_tt_model_e2e] {version}: from_pretrained {m.timing['from_pretrained_s']:.1f} s "
|
| 664 |
f"(weights {m.timing['weights_load_s']:.1f} s, {len(m.W)} tensors, {m.W.device_bytes / 1e6:.0f} MB, "
|
| 665 |
-
f"{m.W.load_stats['path']}); trace layout {policy.trace_layout}"
|
|
|
|
| 666 |
)
|
| 667 |
yield m
|
| 668 |
m.release()
|
|
@@ -700,7 +745,7 @@ def test_e2e_untraced_taps_vs_golden(
|
|
| 700 |
results.set_timing(untraced_first_s=time.perf_counter() - t0, untraced_device_s=model.timing["last_untraced_s"])
|
| 701 |
assert tuple(pred.shape) == tuple(dl.noise_shape) and pred.dtype == torch.float32
|
| 702 |
assert bool(torch.isfinite(pred).all()), "non-finite action_pred"
|
| 703 |
-
expected = set(M.expected_tap_keys(model.cfg, dl))
|
| 704 |
produced = {k for k in taps if not k.startswith(M.RAW_PREFIX)}
|
| 705 |
assert expected <= produced, sorted(expected - produced)
|
| 706 |
assert torch.equal(taps["action_pred_normalized"], pred)
|
|
@@ -708,7 +753,9 @@ def test_e2e_untraced_taps_vs_golden(
|
|
| 708 |
cand = golden_keyed(taps, gs)
|
| 709 |
cand.update(decoded_groups(model, pred, obs))
|
| 710 |
cand, info = split_info(version, cand)
|
| 711 |
-
required = required_gated_keys(version, gs, dl) | {
|
|
|
|
|
|
|
| 712 |
assert set(cand) == required, (sorted(required - set(cand)), sorted(set(cand) - required))
|
| 713 |
report = harness.check_taps(version, cand, gs, results=results)
|
| 714 |
print(report.summary())
|
|
|
|
| 7 |
|
| 8 |
Device tier (WP-D2; one pytest process per version, through the device lock, from the tt-metal root; the conftest
|
| 9 |
option is ``--gr00t-version``; ``TT_METAL_TRACE_ALLOC_TRACKING=1`` must be set **in the shell** -- ttnn reads it once
|
| 10 |
+
at import and the tt-metal root ``conftest.py`` imports ttnn during collection). The DiT backend is parametrised
|
| 11 |
+
through the environment (WP-K5): the module-level ``policy`` / ``gr00t_device`` fixtures build ``Gr00tTT`` with
|
| 12 |
+
``TTPolicy(...).with_env_overrides()`` on a device opened through ``tt.model.open_model_device`` -- the **megakernel**
|
| 13 |
+
(the ``TTPolicy`` default since 2026-09-18, reduced ``worker_l1_size``, bfp8_b arena) unless ``GR00T_DIT_BACKEND=ttnn``
|
| 14 |
+
selects the Stage-1 path (+ ``GR00T_MK_ARENA_DTYPE=bf16|bfp8_b`` for the arena); the expected tap set follows the
|
| 15 |
+
backend (``tt.model.expected_tap_keys``)::
|
| 16 |
|
| 17 |
TT_METAL_TRACE_ALLOC_TRACKING=1 DEVICE_LOCK_TIMEOUT=14400 /home/deepgadget/experiments/gr00t/bin/with-device.sh \\
|
| 18 |
python -m pytest -q -rA --timeout=1800 -p no:cacheprovider \\
|
|
|
|
| 138 |
return out
|
| 139 |
|
| 140 |
|
| 141 |
+
def required_gated_keys(version: str, gs: GoldenSet, dl: DeviceLayout, dit_backend: str = "ttnn") -> Set[str]:
|
| 142 |
"""Every expected untraced tap key that has a golden in this sample, minus the informational ones."""
|
| 143 |
cfg = get_config(version)
|
| 144 |
+
expected = set(M.expected_tap_keys(cfg, dl, dit_backend))
|
| 145 |
return {k for k in expected if k in available_keys(gs)} - set(INFO_TAPS[version])
|
| 146 |
|
| 147 |
|
|
|
|
| 473 |
|
| 474 |
cfg = get_config(version)
|
| 475 |
dl = DeviceLayout.for_inputs(cfg, mi)
|
| 476 |
+
# the emulation runs the Stage-1 ttnn op sequence on the stub (the megakernel -- the TTPolicy default since
|
| 477 |
+
# 2026-09-18 -- is a real generic_op the stub cannot execute)
|
| 478 |
+
pol = TTPolicy(dtype_policy="bf16", trace_layout=trace_layout, dit_backend="ttnn")
|
| 479 |
key = f"{version}/{pol.rope_layout}/{pol.gate_up_layout}"
|
| 480 |
if key not in _FULL_FAKE_WEIGHTS:
|
| 481 |
t0 = time.perf_counter()
|
|
|
|
| 654 |
report.assert_ok()
|
| 655 |
|
| 656 |
|
| 657 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 658 |
+
# DiT backend parametrisation (WP-K5): GR00T_DIT_BACKEND=ttnn|megakernel, GR00T_MK_ARENA_DTYPE=auto|bf16|bfp8_b
|
| 659 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 660 |
+
@pytest.fixture(scope="module")
|
| 661 |
+
def policy(policy_name: str, trace_layout: str) -> TTPolicy:
|
| 662 |
+
"""conftest's ``TTPolicy(dtype_policy=--policy, trace_layout=--trace-layout)`` plus the environment overrides of
|
| 663 |
+
``TTPolicy.with_env_overrides`` (the DiT backend and the megakernel arena dtype of this run)."""
|
| 664 |
+
return TTPolicy(dtype_policy=policy_name, trace_layout=trace_layout).with_env_overrides()
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
@pytest.fixture(scope="module")
|
| 668 |
+
def gr00t_device(request: pytest.FixtureRequest, policy: TTPolicy) -> Iterator[Any]:
|
| 669 |
+
"""The p150a opened per backend (``tt.model.open_model_device``: the megakernel needs the smaller K4
|
| 670 |
+
``worker_l1_size``); same trace region / L1-small sizes as conftest's session fixture, one device per process."""
|
| 671 |
+
from models.experimental.gr00t.tests.tt.conftest import (
|
| 672 |
+
DEFAULT_L1_SMALL_SIZE,
|
| 673 |
+
DEFAULT_TRACE_REGION_SIZE,
|
| 674 |
+
DEFAULT_TT_METAL_CACHE,
|
| 675 |
+
)
|
| 676 |
+
from models.experimental.gr00t.tt.device import close_gr00t_device
|
| 677 |
+
|
| 678 |
+
os.environ.setdefault("TT_METAL_CACHE", str(DEFAULT_TT_METAL_CACHE))
|
| 679 |
+
device = M.open_model_device(
|
| 680 |
+
policy, trace_region_size=DEFAULT_TRACE_REGION_SIZE, l1_small_size=DEFAULT_L1_SMALL_SIZE
|
| 681 |
+
)
|
| 682 |
+
facts = harness.device_facts(device)
|
| 683 |
+
facts["worker_l1_size"] = M.device_worker_l1_size(device)
|
| 684 |
+
facts["dit_backend"] = policy.dit_backend
|
| 685 |
+
# the policy field ("auto" = the per-version table); ``meta.recipe.mk_arena_dtype`` carries the resolved value
|
| 686 |
+
facts["mk_arena_dtype"] = policy.mk_arena_dtype if policy.dit_backend == "megakernel" else None
|
| 687 |
+
request.config._gr00t_device_facts = facts # type: ignore[attr-defined]
|
| 688 |
+
try:
|
| 689 |
+
yield device
|
| 690 |
+
finally:
|
| 691 |
+
close_gr00t_device(device)
|
| 692 |
+
|
| 693 |
+
|
| 694 |
# --------------------------------------------------------------------------------------------------------------------
|
| 695 |
# device fixtures
|
| 696 |
# --------------------------------------------------------------------------------------------------------------------
|
|
|
|
| 706 |
print(
|
| 707 |
f"\n[test_tt_model_e2e] {version}: from_pretrained {m.timing['from_pretrained_s']:.1f} s "
|
| 708 |
f"(weights {m.timing['weights_load_s']:.1f} s, {len(m.W)} tensors, {m.W.device_bytes / 1e6:.0f} MB, "
|
| 709 |
+
f"{m.W.load_stats['path']}); trace layout {policy.trace_layout}; dit_backend {policy.dit_backend} "
|
| 710 |
+
f"(arena {m.recipe.mk_arena_dtype})"
|
| 711 |
)
|
| 712 |
yield m
|
| 713 |
m.release()
|
|
|
|
| 745 |
results.set_timing(untraced_first_s=time.perf_counter() - t0, untraced_device_s=model.timing["last_untraced_s"])
|
| 746 |
assert tuple(pred.shape) == tuple(dl.noise_shape) and pred.dtype == torch.float32
|
| 747 |
assert bool(torch.isfinite(pred).all()), "non-finite action_pred"
|
| 748 |
+
expected = set(M.expected_tap_keys(model.cfg, dl, model.policy.dit_backend))
|
| 749 |
produced = {k for k in taps if not k.startswith(M.RAW_PREFIX)}
|
| 750 |
assert expected <= produced, sorted(expected - produced)
|
| 751 |
assert torch.equal(taps["action_pred_normalized"], pred)
|
|
|
|
| 753 |
cand = golden_keyed(taps, gs)
|
| 754 |
cand.update(decoded_groups(model, pred, obs))
|
| 755 |
cand, info = split_info(version, cand)
|
| 756 |
+
required = required_gated_keys(version, gs, dl, model.policy.dit_backend) | {
|
| 757 |
+
k for k in cand if k.startswith(M.DECODE_TAP)
|
| 758 |
+
}
|
| 759 |
assert set(cand) == required, (sorted(required - set(cand)), sorted(set(cand) - required))
|
| 760 |
report = harness.check_taps(version, cand, gs, results=results)
|
| 761 |
print(report.summary())
|
code/models/experimental/gr00t/tests/tt/test_tt_perf.py
CHANGED
|
@@ -17,9 +17,23 @@ Device tests (one pytest process, through the lock)::
|
|
| 17 |
Baseline = the last **committed** ``benchmarks/results/<bench>_<version>_*.json`` (``git ls-files``); because this
|
| 18 |
package is currently untracked in ``tt-metal`` the lookup falls back to the newest *measured* JSON present (dry-run
|
| 19 |
documents never count), and ``bench_stage_ops`` also accepts a ``_all_`` document (its stages carry ``versions``).
|
| 20 |
-
``
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
CPU tests (no ttnn, run in every CI pass) pin the comparison rule, the baseline lookup and the three ``--dry-run``
|
| 25 |
paths; the ``version`` fixture comes from ``tests/tt/conftest.py`` (``--gr00t-version``).
|
|
@@ -30,27 +44,74 @@ from __future__ import annotations
|
|
| 30 |
import json
|
| 31 |
import os
|
| 32 |
from pathlib import Path
|
| 33 |
-
from typing import Any, Dict, List, Mapping, Optional, Sequence
|
| 34 |
|
| 35 |
import pytest
|
| 36 |
|
| 37 |
from models.experimental.gr00t.benchmarks import bench_denoise as BD
|
| 38 |
from models.experimental.gr00t.benchmarks import bench_e2e as E2E
|
|
|
|
| 39 |
from models.experimental.gr00t.benchmarks import bench_stage_ops as SO
|
|
|
|
| 40 |
|
| 41 |
TOL = 0.10 # plan §7.1: +10 %
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
BASELINE_ENV = "GR00T_PERF_BASELINE"
|
| 43 |
BASELINE_MODES = ("committed", "latest", "none")
|
| 44 |
RULE = "perf+10%"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
# --------------------------------------------------------------------------------------------------------------------
|
| 48 |
# Baseline lookup
|
| 49 |
# --------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
def baseline_for(
|
| 51 |
-
bench: str,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
) -> Optional[Path]:
|
| 53 |
-
"""The baseline JSON for ``(bench, version)`` or ``None`` (module docstring rules + ``GR00T_PERF_BASELINE``).
|
|
|
|
|
|
|
| 54 |
mode = os.environ.get(BASELINE_ENV, "committed").strip()
|
| 55 |
if mode == "none":
|
| 56 |
return None
|
|
@@ -61,8 +122,12 @@ def baseline_for(
|
|
| 61 |
doc = SO.load_perf_json(p)
|
| 62 |
if doc["bench"] != bench:
|
| 63 |
raise ValueError(f"{p}: bench {doc['bench']!r} != {bench!r}")
|
|
|
|
|
|
|
| 64 |
return p
|
| 65 |
prefer = mode == "committed"
|
|
|
|
|
|
|
| 66 |
p = SO.latest_perf_json(bench, version, results_dir, prefer_committed=prefer, exclude=exclude)
|
| 67 |
if p is None and bench == SO.BENCH_NAME and version != "all":
|
| 68 |
p = SO.latest_perf_json(bench, "all", results_dir, prefer_committed=prefer, exclude=exclude)
|
|
@@ -83,6 +148,20 @@ def stages_for_version(doc: Mapping[str, Any], version: str) -> Dict[str, Dict[s
|
|
| 83 |
return out
|
| 84 |
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
def comparison_rows(bench: str, version: str, cmp: Sequence[Mapping[str, Any]], baseline: Path) -> List[Dict[str, Any]]:
|
| 87 |
"""Rows in the ``ResultsWriter.add_row`` contract (``tap``, ``key``, ``rule``, ``ok`` + the perf facts)."""
|
| 88 |
rows = []
|
|
@@ -149,6 +228,18 @@ def test_compare_stages_rule():
|
|
| 149 |
SO.compare_stages({}, {}, tol=-0.1)
|
| 150 |
|
| 151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
def test_perf_payload_validation_and_roundtrip(tmp_path: Path):
|
| 153 |
with pytest.raises(KeyError):
|
| 154 |
SO.perf_payload("x", "n16", dry_run=True, args={}, method={}, stages={"s": {"value": 1.0}}, rows=[])
|
|
@@ -224,6 +315,42 @@ def test_baseline_for_env_modes(tmp_path: Path, monkeypatch: pytest.MonkeyPatch)
|
|
| 224 |
assert set(stages_for_version(SO.load_perf_json(pa), "all")) == {"siglip_352/bf16", "llm_384/bf16"}
|
| 225 |
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
def test_stage_ops_dry_run_all_versions():
|
| 228 |
"""Every stage x dtype resolves against the tables without ttnn (the WP-06 acceptance shape list)."""
|
| 229 |
payload = SO.dry_run(version="all", verbose=False)
|
|
@@ -363,31 +490,112 @@ def test_replayer_contract_is_checked():
|
|
| 363 |
|
| 364 |
|
| 365 |
def test_existing_results_documents_load():
|
|
|
|
|
|
|
|
|
|
| 366 |
files = sorted(SO.RESULTS_DIR.glob("*.json"))
|
| 367 |
if not files:
|
| 368 |
pytest.skip("no benchmark results present yet")
|
|
|
|
| 369 |
for f in files:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
doc = SO.load_perf_json(f)
|
| 371 |
-
assert doc["bench"] in (SO.BENCH_NAME, E2E.BENCH_NAME, BD.BENCH_NAME), f
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
|
| 374 |
# --------------------------------------------------------------------------------------------------------------------
|
| 375 |
# Device tests: re-measure and compare with the baseline (skipped without a baseline)
|
| 376 |
# --------------------------------------------------------------------------------------------------------------------
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
if p is None:
|
| 380 |
-
|
|
|
|
|
|
|
|
|
|
| 381 |
return p, SO.load_perf_json(p)
|
| 382 |
|
| 383 |
|
| 384 |
-
def _device(request: pytest.FixtureRequest, results: Any) -> Any:
|
| 385 |
-
"""The
|
| 386 |
-
the
|
|
|
|
| 387 |
from models.experimental.gr00t.tests.tt.harness import device_facts
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
return device
|
| 392 |
|
| 393 |
|
|
@@ -402,43 +610,79 @@ def _record(
|
|
| 402 |
def test_stage_ops_within_10pct(request: pytest.FixtureRequest, version: str, results: Any):
|
| 403 |
"""Plan §7.1 perf tier for the backbone blocks: every canonical stage of ``version`` within +10 % of the baseline."""
|
| 404 |
baseline_path, baseline = _load_baseline(SO.BENCH_NAME, version)
|
| 405 |
-
device = _device(request, results)
|
| 406 |
with results.time("bench_stage_ops"):
|
| 407 |
payload = SO.run(device, version=version, verbose=False)
|
| 408 |
current = SO.write_perf_json(payload)
|
| 409 |
assert not payload["errors"], f"bench_stage_ops reported op failures: {payload['errors']}"
|
| 410 |
cur = stages_for_version(payload, version)
|
| 411 |
base = stages_for_version(baseline, version)
|
| 412 |
-
cmp = SO.compare_stages(cur, base, TOL)
|
| 413 |
_record(results, SO.BENCH_NAME, version, cmp, baseline_path, current)
|
| 414 |
_assert_within(cmp, SO.BENCH_NAME)
|
| 415 |
|
| 416 |
|
| 417 |
def test_denoise_within_10pct(
|
| 418 |
-
request: pytest.FixtureRequest,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
):
|
| 420 |
if E2E.import_gr00t_tt() is None:
|
| 421 |
pytest.skip("tt.model.Gr00tTT not importable (WP-14 pending)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
baseline_path, baseline = _load_baseline(BD.BENCH_NAME, version)
|
| 423 |
-
device = _device(request, results)
|
| 424 |
with results.time("bench_denoise"):
|
| 425 |
payload = BD.run(device, version=version, policy=policy_name, trace_layout=trace_layout, verbose=False)
|
| 426 |
current = SO.write_perf_json(payload)
|
| 427 |
-
cmp =
|
|
|
|
|
|
|
| 428 |
_record(results, BD.BENCH_NAME, version, cmp, baseline_path, current)
|
| 429 |
_assert_within(cmp, BD.BENCH_NAME)
|
| 430 |
|
| 431 |
|
| 432 |
def test_e2e_within_10pct(
|
| 433 |
-
request: pytest.FixtureRequest,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
):
|
|
|
|
|
|
|
| 435 |
if E2E.import_gr00t_tt() is None:
|
| 436 |
pytest.skip("tt.model.Gr00tTT not importable (WP-14 pending)")
|
| 437 |
-
baseline_path, baseline = _load_baseline(E2E.BENCH_NAME, version)
|
| 438 |
device = _device(request, results)
|
| 439 |
with results.time("bench_e2e"):
|
| 440 |
-
payload = E2E.run(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
current = SO.write_perf_json(payload)
|
| 442 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
_record(results, E2E.BENCH_NAME, version, cmp, baseline_path, current)
|
| 444 |
_assert_within(cmp, E2E.BENCH_NAME)
|
|
|
|
| 17 |
Baseline = the last **committed** ``benchmarks/results/<bench>_<version>_*.json`` (``git ls-files``); because this
|
| 18 |
package is currently untracked in ``tt-metal`` the lookup falls back to the newest *measured* JSON present (dry-run
|
| 19 |
documents never count), and ``bench_stage_ops`` also accepts a ``_all_`` document (its stages carry ``versions``).
|
| 20 |
+
For ``bench_e2e`` the baseline must have been measured with the **same DiT backend** as the run (``meta.dit_backend``;
|
| 21 |
+
documents from before WP-K5 carry none and count as ``ttnn``), so the megakernel default -- ``TTPolicy.dit_backend``
|
| 22 |
+
since 2026-09-18 -- compares with the megakernel-default bench (``bench_e2e_<v>_20260918-*`` of the megakernel-default
|
| 23 |
+
pass, ``tests/tt/results/mk_default_summary.md``) and ``GR00T_DIT_BACKEND=ttnn`` with the Stage-1 bench.
|
| 24 |
+
``GR00T_PERF_BASELINE`` overrides: ``committed`` (default) | ``latest`` | ``none`` | ``/path/to/baseline.json`` (its
|
| 25 |
+
backend must match). Every device test **skips** (never fails) when no baseline exists, and writes its own JSON
|
| 26 |
+
through the benchmark writer plus the comparison rows through the ``results`` fixture (``tests/tt/results/``).
|
| 27 |
+
``ms`` stages whose baseline is below :data:`PERF_MIN_BASELINE_MS` (0.5 ms: the per-tensor ``upload/<input>`` copies,
|
| 28 |
+
``d2h``) are recorded but not compared -- one 20 us host-timer jitter is +20 % there (megakernel-default pass).
|
| 29 |
+
|
| 30 |
+
Devices (one open per test, closed after it): the whole-model bench runs on ``tt.model.open_model_device(policy,
|
| 31 |
+
num_command_queues=2)`` -- the production configuration (the megakernel's reduced ``worker_l1_size``, CQ-1 input writes)
|
| 32 |
+
-- for the policy of the run (``TTPolicy(dtype_policy=--policy, trace_layout=--trace-layout).with_env_overrides()``);
|
| 33 |
+
``bench_stage_ops`` and ``bench_denoise`` run on the firmware-default device their baselines were measured on (the
|
| 34 |
+
standalone N1.7 Qwen-ViT ``ff1`` op does not fit its static CBs under the 64 KiB cut). ``bench_denoise`` times the
|
| 35 |
+
Stage-1 (ttnn) denoise trace on a Stage-1 model, so ``test_denoise_within_10pct`` runs only under
|
| 36 |
+
``GR00T_DIT_BACKEND=ttnn`` (skipped otherwise).
|
| 37 |
|
| 38 |
CPU tests (no ttnn, run in every CI pass) pin the comparison rule, the baseline lookup and the three ``--dry-run``
|
| 39 |
paths; the ``version`` fixture comes from ``tests/tt/conftest.py`` (``--gr00t-version``).
|
|
|
|
| 44 |
import json
|
| 45 |
import os
|
| 46 |
from pathlib import Path
|
| 47 |
+
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence
|
| 48 |
|
| 49 |
import pytest
|
| 50 |
|
| 51 |
from models.experimental.gr00t.benchmarks import bench_denoise as BD
|
| 52 |
from models.experimental.gr00t.benchmarks import bench_e2e as E2E
|
| 53 |
+
from models.experimental.gr00t.benchmarks import bench_load as BL
|
| 54 |
from models.experimental.gr00t.benchmarks import bench_stage_ops as SO
|
| 55 |
+
from models.experimental.gr00t.tt.policy import TTPolicy
|
| 56 |
|
| 57 |
TOL = 0.10 # plan §7.1: +10 %
|
| 58 |
+
#: stages whose baseline is below this many ms are recorded but not compared: the +10 % rule is meaningless on the
|
| 59 |
+
#: per-tensor ``upload/<input>`` copies and ``d2h`` (20-150 us, where one 20 us host-timer jitter is +20 %); the
|
| 60 |
+
#: stage totals (``upload``, ``device``, ``traces``, per trace, host encode / decode, ``e2e``) are all above it
|
| 61 |
+
PERF_MIN_BASELINE_MS = 0.5
|
| 62 |
BASELINE_ENV = "GR00T_PERF_BASELINE"
|
| 63 |
BASELINE_MODES = ("committed", "latest", "none")
|
| 64 |
RULE = "perf+10%"
|
| 65 |
+
#: ``meta.dit_backend`` of perf documents written before WP-K5 (the Stage-1 path was the only one)
|
| 66 |
+
LEGACY_BACKEND = "ttnn"
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def doc_backend(doc: Mapping[str, Any]) -> str:
|
| 70 |
+
"""The DiT backend a ``bench_e2e`` document was measured with (``meta.dit_backend``; :data:`LEGACY_BACKEND` when the
|
| 71 |
+
document predates the field)."""
|
| 72 |
+
meta = doc.get("meta") or {}
|
| 73 |
+
b = meta.get("dit_backend")
|
| 74 |
+
if b is None:
|
| 75 |
+
b = (meta.get("policy") or {}).get("dit_backend")
|
| 76 |
+
return str(b) if b else LEGACY_BACKEND
|
| 77 |
|
| 78 |
|
| 79 |
# --------------------------------------------------------------------------------------------------------------------
|
| 80 |
# Baseline lookup
|
| 81 |
# --------------------------------------------------------------------------------------------------------------------
|
| 82 |
+
def _newest_matching(
|
| 83 |
+
bench: str, version: str, results_dir: Path, prefer_committed: bool, exclude: Sequence[Path], backend: str
|
| 84 |
+
) -> Optional[Path]:
|
| 85 |
+
"""Newest measured ``<bench>_<version>_*.json`` whose :func:`doc_backend` is ``backend`` (committed first)."""
|
| 86 |
+
ex = {Path(p).resolve() for p in exclude}
|
| 87 |
+
|
| 88 |
+
def ok(p: Path) -> bool:
|
| 89 |
+
if p.resolve() in ex:
|
| 90 |
+
return False
|
| 91 |
+
try:
|
| 92 |
+
doc = SO.load_perf_json(p)
|
| 93 |
+
except (OSError, ValueError, KeyError):
|
| 94 |
+
return False
|
| 95 |
+
return not doc["dry_run"] and doc_backend(doc) == backend
|
| 96 |
+
|
| 97 |
+
if prefer_committed:
|
| 98 |
+
committed = [p for p in SO.committed_perf_json(bench, version, results_dir) if ok(p)]
|
| 99 |
+
if committed:
|
| 100 |
+
return committed[-1]
|
| 101 |
+
present = [p for p in SO.list_perf_json(bench, version, results_dir) if ok(p)]
|
| 102 |
+
return present[-1] if present else None
|
| 103 |
+
|
| 104 |
+
|
| 105 |
def baseline_for(
|
| 106 |
+
bench: str,
|
| 107 |
+
version: str,
|
| 108 |
+
results_dir: Path = SO.RESULTS_DIR,
|
| 109 |
+
exclude: Sequence[Path] = (),
|
| 110 |
+
backend: Optional[str] = None,
|
| 111 |
) -> Optional[Path]:
|
| 112 |
+
"""The baseline JSON for ``(bench, version)`` or ``None`` (module docstring rules + ``GR00T_PERF_BASELINE``).
|
| 113 |
+
``backend`` (``bench_e2e`` only) restricts the lookup to documents measured with that DiT backend and refuses an
|
| 114 |
+
explicit ``GR00T_PERF_BASELINE`` file of another backend."""
|
| 115 |
mode = os.environ.get(BASELINE_ENV, "committed").strip()
|
| 116 |
if mode == "none":
|
| 117 |
return None
|
|
|
|
| 122 |
doc = SO.load_perf_json(p)
|
| 123 |
if doc["bench"] != bench:
|
| 124 |
raise ValueError(f"{p}: bench {doc['bench']!r} != {bench!r}")
|
| 125 |
+
if backend is not None and doc_backend(doc) != backend:
|
| 126 |
+
raise ValueError(f"{p}: measured with dit_backend {doc_backend(doc)!r}, this run is {backend!r}")
|
| 127 |
return p
|
| 128 |
prefer = mode == "committed"
|
| 129 |
+
if backend is not None:
|
| 130 |
+
return _newest_matching(bench, version, results_dir, prefer, exclude, backend)
|
| 131 |
p = SO.latest_perf_json(bench, version, results_dir, prefer_committed=prefer, exclude=exclude)
|
| 132 |
if p is None and bench == SO.BENCH_NAME and version != "all":
|
| 133 |
p = SO.latest_perf_json(bench, "all", results_dir, prefer_committed=prefer, exclude=exclude)
|
|
|
|
| 148 |
return out
|
| 149 |
|
| 150 |
|
| 151 |
+
def apply_floor(cmp: Sequence[Mapping[str, Any]], floor_ms: float = PERF_MIN_BASELINE_MS) -> List[Dict[str, Any]]:
|
| 152 |
+
"""Mark the compared rows whose ``ms`` baseline is below ``floor_ms`` as not compared (recorded as information,
|
| 153 |
+
:data:`PERF_MIN_BASELINE_MS`); ``us`` rows (``bench_stage_ops``) are never floored."""
|
| 154 |
+
out: List[Dict[str, Any]] = []
|
| 155 |
+
for c in cmp:
|
| 156 |
+
c = dict(c)
|
| 157 |
+
if c.get("compared") and c.get("metric") == "ms" and c.get("baseline") is not None and c["baseline"] < floor_ms:
|
| 158 |
+
c["compared"] = False
|
| 159 |
+
c["ok"] = None
|
| 160 |
+
c["note"] = f"baseline {c['baseline']:.3f} ms below the {floor_ms} ms comparison floor (host-timer jitter)"
|
| 161 |
+
out.append(c)
|
| 162 |
+
return out
|
| 163 |
+
|
| 164 |
+
|
| 165 |
def comparison_rows(bench: str, version: str, cmp: Sequence[Mapping[str, Any]], baseline: Path) -> List[Dict[str, Any]]:
|
| 166 |
"""Rows in the ``ResultsWriter.add_row`` contract (``tap``, ``key``, ``rule``, ``ok`` + the perf facts)."""
|
| 167 |
rows = []
|
|
|
|
| 228 |
SO.compare_stages({}, {}, tol=-0.1)
|
| 229 |
|
| 230 |
|
| 231 |
+
def test_apply_floor_rule():
|
| 232 |
+
base = {"upload/patches": _st(0.093, "ms"), "d2h": _st(0.061, "ms"), "upload": _st(1.295, "ms"), "op": _st(100.0)}
|
| 233 |
+
cur = {"upload/patches": _st(0.113, "ms"), "d2h": _st(0.067, "ms"), "upload": _st(1.5, "ms"), "op": _st(125.0)}
|
| 234 |
+
rows = {r["stage"]: r for r in apply_floor(SO.compare_stages(cur, base, TOL))}
|
| 235 |
+
assert rows["upload/patches"]["compared"] is False and rows["upload/patches"]["ok"] is None # 93 us: floored
|
| 236 |
+
assert rows["d2h"]["compared"] is False and "floor" in rows["d2h"]["note"]
|
| 237 |
+
assert rows["upload"]["compared"] is True and rows["upload"]["ok"] is False # +15.8 % on a 1.3 ms stage fails
|
| 238 |
+
assert rows["op"]["compared"] is True and rows["op"]["ok"] is False # us rows are never floored
|
| 239 |
+
with pytest.raises(pytest.skip.Exception):
|
| 240 |
+
_assert_within([rows["d2h"]], "x") # nothing left to compare -> skip, never a silent pass
|
| 241 |
+
|
| 242 |
+
|
| 243 |
def test_perf_payload_validation_and_roundtrip(tmp_path: Path):
|
| 244 |
with pytest.raises(KeyError):
|
| 245 |
SO.perf_payload("x", "n16", dry_run=True, args={}, method={}, stages={"s": {"value": 1.0}}, rows=[])
|
|
|
|
| 315 |
assert set(stages_for_version(SO.load_perf_json(pa), "all")) == {"siglip_352/bf16", "llm_384/bf16"}
|
| 316 |
|
| 317 |
|
| 318 |
+
def test_baseline_for_backend_aware(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
| 319 |
+
"""``bench_e2e`` baselines are matched on ``meta.dit_backend`` (legacy documents count as ttnn)."""
|
| 320 |
+
|
| 321 |
+
def write(stamp: str, backend: Optional[str], value: float = 1.0) -> Path:
|
| 322 |
+
meta = {} if backend is None else {"dit_backend": backend}
|
| 323 |
+
payload = SO.perf_payload(
|
| 324 |
+
E2E.BENCH_NAME,
|
| 325 |
+
"n16",
|
| 326 |
+
dry_run=False,
|
| 327 |
+
args={},
|
| 328 |
+
method={},
|
| 329 |
+
stages={"e2e": _st(value, "ms")},
|
| 330 |
+
rows=[],
|
| 331 |
+
meta=meta,
|
| 332 |
+
)
|
| 333 |
+
return SO.write_perf_json(payload, tmp_path, path=tmp_path / f"{E2E.BENCH_NAME}_n16_{stamp}.json")
|
| 334 |
+
|
| 335 |
+
monkeypatch.setenv(BASELINE_ENV, "latest")
|
| 336 |
+
legacy = write("20260101-000000", None)
|
| 337 |
+
ttnn_doc = write("20260102-000000", "ttnn")
|
| 338 |
+
mk_doc = write("20260103-000000", "megakernel")
|
| 339 |
+
assert doc_backend(SO.load_perf_json(legacy)) == "ttnn"
|
| 340 |
+
assert baseline_for(E2E.BENCH_NAME, "n16", tmp_path) == mk_doc # backend-agnostic: the newest
|
| 341 |
+
assert baseline_for(E2E.BENCH_NAME, "n16", tmp_path, backend="megakernel") == mk_doc
|
| 342 |
+
assert baseline_for(E2E.BENCH_NAME, "n16", tmp_path, backend="ttnn") == ttnn_doc
|
| 343 |
+
assert baseline_for(E2E.BENCH_NAME, "n16", tmp_path, backend="ttnn", exclude=[ttnn_doc]) == legacy
|
| 344 |
+
assert baseline_for(E2E.BENCH_NAME, "n16", tmp_path, backend="megakernel", exclude=[mk_doc]) is None
|
| 345 |
+
monkeypatch.setenv(BASELINE_ENV, str(mk_doc))
|
| 346 |
+
assert baseline_for(E2E.BENCH_NAME, "n16", tmp_path, backend="megakernel") == mk_doc
|
| 347 |
+
with pytest.raises(ValueError):
|
| 348 |
+
baseline_for(E2E.BENCH_NAME, "n16", tmp_path, backend="ttnn")
|
| 349 |
+
# the policy default is what the device tier compares against unless GR00T_DIT_BACKEND says otherwise
|
| 350 |
+
assert TTPolicy().with_env_overrides({}).dit_backend == "megakernel"
|
| 351 |
+
assert TTPolicy().with_env_overrides({"GR00T_DIT_BACKEND": "ttnn"}).dit_backend == "ttnn"
|
| 352 |
+
|
| 353 |
+
|
| 354 |
def test_stage_ops_dry_run_all_versions():
|
| 355 |
"""Every stage x dtype resolves against the tables without ttnn (the WP-06 acceptance shape list)."""
|
| 356 |
payload = SO.dry_run(version="all", verbose=False)
|
|
|
|
| 490 |
|
| 491 |
|
| 492 |
def test_existing_results_documents_load():
|
| 493 |
+
"""Every perf-schema document under ``benchmarks/results`` loads and names a known bench; documents of the other
|
| 494 |
+
schemas that share the directory (the K1 / K4 megakernel benches, ``gr00t-mk-k1/1`` / ``gr00t-mk-k4/1``) are
|
| 495 |
+
counted, not parsed as perf documents."""
|
| 496 |
files = sorted(SO.RESULTS_DIR.glob("*.json"))
|
| 497 |
if not files:
|
| 498 |
pytest.skip("no benchmark results present yet")
|
| 499 |
+
n_perf, other_schemas = 0, set()
|
| 500 |
for f in files:
|
| 501 |
+
with open(f) as fh:
|
| 502 |
+
schema = json.load(fh).get("schema")
|
| 503 |
+
if schema != SO.PERF_SCHEMA:
|
| 504 |
+
other_schemas.add(str(schema))
|
| 505 |
+
continue
|
| 506 |
doc = SO.load_perf_json(f)
|
| 507 |
+
assert doc["bench"] in (SO.BENCH_NAME, E2E.BENCH_NAME, BD.BENCH_NAME, BL.BENCH_NAME), f
|
| 508 |
+
n_perf += 1
|
| 509 |
+
assert n_perf > 0, files
|
| 510 |
+
# the K1 / K4 megakernel benches (bench_mk_*.py) share the directory; anything new must be listed on purpose
|
| 511 |
+
assert other_schemas <= {"gr00t-mk-k1/1", "gr00t-mk-k4/1"}, other_schemas
|
| 512 |
|
| 513 |
|
| 514 |
# --------------------------------------------------------------------------------------------------------------------
|
| 515 |
# Device tests: re-measure and compare with the baseline (skipped without a baseline)
|
| 516 |
# --------------------------------------------------------------------------------------------------------------------
|
| 517 |
+
@pytest.fixture(scope="module")
|
| 518 |
+
def perf_policy(policy_name: str, trace_layout: str) -> TTPolicy:
|
| 519 |
+
"""The policy of this perf run: the option values plus ``GR00T_DIT_BACKEND`` / ``GR00T_MK_ARENA_DTYPE`` (the
|
| 520 |
+
code default is the megakernel backend)."""
|
| 521 |
+
return TTPolicy(dtype_policy=policy_name, trace_layout=trace_layout).with_env_overrides()
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
def _open(policy: TTPolicy, num_command_queues: int) -> Any:
|
| 525 |
+
from models.experimental.gr00t.tests.tt.conftest import (
|
| 526 |
+
DEFAULT_L1_SMALL_SIZE,
|
| 527 |
+
DEFAULT_TRACE_REGION_SIZE,
|
| 528 |
+
DEFAULT_TT_METAL_CACHE,
|
| 529 |
+
)
|
| 530 |
+
from models.experimental.gr00t.tt import model as M
|
| 531 |
+
|
| 532 |
+
os.environ.setdefault("TT_METAL_CACHE", str(DEFAULT_TT_METAL_CACHE))
|
| 533 |
+
return M.open_model_device(
|
| 534 |
+
policy,
|
| 535 |
+
trace_region_size=DEFAULT_TRACE_REGION_SIZE,
|
| 536 |
+
l1_small_size=DEFAULT_L1_SMALL_SIZE,
|
| 537 |
+
num_command_queues=num_command_queues,
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
@pytest.fixture
|
| 542 |
+
def perf_device(perf_policy: TTPolicy) -> Iterator[Any]:
|
| 543 |
+
"""The production device configuration for ``perf_policy`` (the whole-model bench): ``tt.model.open_model_device``
|
| 544 |
+
(the megakernel's ``worker_l1_size`` when that is the backend) with **two** command queues, as ``bench_e2e
|
| 545 |
+
--num-cqs 2`` and the policy server open it. Function-scoped (one device open per test, closed after it) and
|
| 546 |
+
requested lazily after the baseline check, so a baseline-less run never opens the device and the two device
|
| 547 |
+
configurations of this module never coexist."""
|
| 548 |
+
from models.experimental.gr00t.tt.device import close_gr00t_device
|
| 549 |
+
|
| 550 |
+
device = _open(perf_policy, 2)
|
| 551 |
+
try:
|
| 552 |
+
yield device
|
| 553 |
+
finally:
|
| 554 |
+
close_gr00t_device(device)
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
@pytest.fixture
|
| 558 |
+
def default_device() -> Iterator[Any]:
|
| 559 |
+
"""The firmware-default device (``worker_l1_size`` 1,461,248 B, one CQ) for the Stage-1 microbenches:
|
| 560 |
+
``bench_stage_ops`` places its standalone backbone ops with their own L1 budgets (the N1.7 Qwen-ViT ``ff1`` matmul
|
| 561 |
+
clashes with its static CBs under the megakernel's 64 KiB cut, ``program.cpp:2149``) and ``bench_denoise`` times
|
| 562 |
+
the Stage-1 model; their baselines were measured on this configuration. Function-scoped like :func:`perf_device`.
|
| 563 |
+
"""
|
| 564 |
+
from models.experimental.gr00t.tt.device import close_gr00t_device
|
| 565 |
+
|
| 566 |
+
device = _open(TTPolicy(dit_backend="ttnn"), 1)
|
| 567 |
+
try:
|
| 568 |
+
yield device
|
| 569 |
+
finally:
|
| 570 |
+
close_gr00t_device(device)
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def _load_baseline(bench: str, version: str, backend: Optional[str] = None) -> Any:
|
| 574 |
+
p = baseline_for(bench, version, backend=backend)
|
| 575 |
if p is None:
|
| 576 |
+
what = f" measured with dit_backend {backend!r}" if backend else ""
|
| 577 |
+
pytest.skip(
|
| 578 |
+
f"{bench}: no baseline JSON for {version}{what} in {SO.RESULTS_DIR} (set {BASELINE_ENV} to override)"
|
| 579 |
+
)
|
| 580 |
return p, SO.load_perf_json(p)
|
| 581 |
|
| 582 |
|
| 583 |
+
def _device(request: pytest.FixtureRequest, results: Any, fixture: str = "perf_device") -> Any:
|
| 584 |
+
"""The device of ``fixture`` (``perf_device`` = the run policy's configuration, ``default_device`` = the firmware
|
| 585 |
+
default), requested **after** the baseline check so that a baseline-less run skips without opening the device (a
|
| 586 |
+
fixture in the signature would be instantiated first)."""
|
| 587 |
from models.experimental.gr00t.tests.tt.harness import device_facts
|
| 588 |
+
from models.experimental.gr00t.tt import model as M
|
| 589 |
+
|
| 590 |
+
device = request.getfixturevalue(fixture)
|
| 591 |
+
policy: TTPolicy = request.getfixturevalue("perf_policy")
|
| 592 |
+
facts = device_facts(device)
|
| 593 |
+
facts["worker_l1_size"] = M.device_worker_l1_size(device)
|
| 594 |
+
facts["device_fixture"] = fixture
|
| 595 |
+
facts["dit_backend"] = policy.dit_backend if fixture == "perf_device" else "ttnn"
|
| 596 |
+
facts["mk_arena_dtype"] = policy.mk_arena_dtype if fixture == "perf_device" else None
|
| 597 |
+
facts["num_command_queues"] = 2 if fixture == "perf_device" else 1
|
| 598 |
+
results.set_device_facts(facts)
|
| 599 |
return device
|
| 600 |
|
| 601 |
|
|
|
|
| 610 |
def test_stage_ops_within_10pct(request: pytest.FixtureRequest, version: str, results: Any):
|
| 611 |
"""Plan §7.1 perf tier for the backbone blocks: every canonical stage of ``version`` within +10 % of the baseline."""
|
| 612 |
baseline_path, baseline = _load_baseline(SO.BENCH_NAME, version)
|
| 613 |
+
device = _device(request, results, "default_device")
|
| 614 |
with results.time("bench_stage_ops"):
|
| 615 |
payload = SO.run(device, version=version, verbose=False)
|
| 616 |
current = SO.write_perf_json(payload)
|
| 617 |
assert not payload["errors"], f"bench_stage_ops reported op failures: {payload['errors']}"
|
| 618 |
cur = stages_for_version(payload, version)
|
| 619 |
base = stages_for_version(baseline, version)
|
| 620 |
+
cmp = apply_floor(SO.compare_stages(cur, base, TOL))
|
| 621 |
_record(results, SO.BENCH_NAME, version, cmp, baseline_path, current)
|
| 622 |
_assert_within(cmp, SO.BENCH_NAME)
|
| 623 |
|
| 624 |
|
| 625 |
def test_denoise_within_10pct(
|
| 626 |
+
request: pytest.FixtureRequest,
|
| 627 |
+
version: str,
|
| 628 |
+
results: Any,
|
| 629 |
+
policy_name: str,
|
| 630 |
+
trace_layout: str,
|
| 631 |
+
perf_policy: TTPolicy,
|
| 632 |
):
|
| 633 |
if E2E.import_gr00t_tt() is None:
|
| 634 |
pytest.skip("tt.model.Gr00tTT not importable (WP-14 pending)")
|
| 635 |
+
if perf_policy.dit_backend != "ttnn":
|
| 636 |
+
pytest.skip(
|
| 637 |
+
"bench_denoise times the Stage-1 (ttnn) denoise trace on a Stage-1 model; run with GR00T_DIT_BACKEND=ttnn "
|
| 638 |
+
f"(this run's backend: {perf_policy.dit_backend}, whose device configuration cannot host the L1 adapter)"
|
| 639 |
+
)
|
| 640 |
baseline_path, baseline = _load_baseline(BD.BENCH_NAME, version)
|
| 641 |
+
device = _device(request, results, "default_device")
|
| 642 |
with results.time("bench_denoise"):
|
| 643 |
payload = BD.run(device, version=version, policy=policy_name, trace_layout=trace_layout, verbose=False)
|
| 644 |
current = SO.write_perf_json(payload)
|
| 645 |
+
cmp = apply_floor(
|
| 646 |
+
SO.compare_stages(stages_for_version(payload, version), stages_for_version(baseline, version), TOL)
|
| 647 |
+
)
|
| 648 |
_record(results, BD.BENCH_NAME, version, cmp, baseline_path, current)
|
| 649 |
_assert_within(cmp, BD.BENCH_NAME)
|
| 650 |
|
| 651 |
|
| 652 |
def test_e2e_within_10pct(
|
| 653 |
+
request: pytest.FixtureRequest,
|
| 654 |
+
version: str,
|
| 655 |
+
results: Any,
|
| 656 |
+
policy_name: str,
|
| 657 |
+
trace_layout: str,
|
| 658 |
+
perf_policy: TTPolicy,
|
| 659 |
):
|
| 660 |
+
"""Plan §7.1 perf tier for the whole traced model: every ``bench_e2e`` stage within +10 % of the newest baseline
|
| 661 |
+
measured with the same DiT backend (module docstring)."""
|
| 662 |
if E2E.import_gr00t_tt() is None:
|
| 663 |
pytest.skip("tt.model.Gr00tTT not importable (WP-14 pending)")
|
| 664 |
+
baseline_path, baseline = _load_baseline(E2E.BENCH_NAME, version, backend=perf_policy.dit_backend)
|
| 665 |
device = _device(request, results)
|
| 666 |
with results.time("bench_e2e"):
|
| 667 |
+
payload = E2E.run(
|
| 668 |
+
device,
|
| 669 |
+
version=version,
|
| 670 |
+
policy=policy_name,
|
| 671 |
+
trace_layout=trace_layout,
|
| 672 |
+
verbose=False,
|
| 673 |
+
dit_backend=perf_policy.dit_backend,
|
| 674 |
+
mk_arena_dtype=perf_policy.mk_arena_dtype,
|
| 675 |
+
)
|
| 676 |
current = SO.write_perf_json(payload)
|
| 677 |
+
results.set_meta(
|
| 678 |
+
dit_backend=perf_policy.dit_backend,
|
| 679 |
+
baseline_backend=doc_backend(baseline),
|
| 680 |
+
mk_arena_dtype=payload["meta"].get("mk_arena_dtype"),
|
| 681 |
+
num_command_queues=payload["meta"].get("num_command_queues"),
|
| 682 |
+
)
|
| 683 |
+
assert doc_backend(baseline) == perf_policy.dit_backend, (doc_backend(baseline), perf_policy.dit_backend)
|
| 684 |
+
cmp = apply_floor(
|
| 685 |
+
SO.compare_stages(stages_for_version(payload, version), stages_for_version(baseline, version), TOL)
|
| 686 |
+
)
|
| 687 |
_record(results, E2E.BENCH_NAME, version, cmp, baseline_path, current)
|
| 688 |
_assert_within(cmp, E2E.BENCH_NAME)
|
code/models/experimental/gr00t/tests/tt/test_tt_stale_state.py
CHANGED
|
@@ -426,6 +426,42 @@ def test_cpu_emulated_n_text_toggle(
|
|
| 426 |
# --------------------------------------------------------------------------------------------------------------------
|
| 427 |
# device tier
|
| 428 |
# --------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
@pytest.fixture(scope="module")
|
| 430 |
def pair(version: str) -> List[Tuple[str, GoldenSet, Any]]:
|
| 431 |
"""``[(label, GoldenSet, Observation), ...]`` of the pair (skipped when a sample, its ``obs/`` or the snapshot is
|
|
|
|
| 426 |
# --------------------------------------------------------------------------------------------------------------------
|
| 427 |
# device tier
|
| 428 |
# --------------------------------------------------------------------------------------------------------------------
|
| 429 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 430 |
+
# DiT backend parametrisation (WP-K5): GR00T_DIT_BACKEND=ttnn|megakernel, GR00T_MK_ARENA_DTYPE=auto|bf16|bfp8_b
|
| 431 |
+
# --------------------------------------------------------------------------------------------------------------------
|
| 432 |
+
@pytest.fixture(scope="module")
|
| 433 |
+
def policy(policy_name: str, trace_layout: str) -> TTPolicy:
|
| 434 |
+
"""conftest's ``TTPolicy(dtype_policy=--policy, trace_layout=--trace-layout)`` plus the environment overrides of
|
| 435 |
+
``TTPolicy.with_env_overrides`` (the DiT backend and the megakernel arena dtype of this run)."""
|
| 436 |
+
return TTPolicy(dtype_policy=policy_name, trace_layout=trace_layout).with_env_overrides()
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
@pytest.fixture(scope="module")
|
| 440 |
+
def gr00t_device(request: pytest.FixtureRequest, policy: TTPolicy) -> Iterator[Any]:
|
| 441 |
+
"""The p150a opened per backend (``tt.model.open_model_device``: the megakernel needs the smaller K4
|
| 442 |
+
``worker_l1_size``); same trace region / L1-small sizes as conftest's session fixture, one device per process."""
|
| 443 |
+
from models.experimental.gr00t.tests.tt.conftest import (
|
| 444 |
+
DEFAULT_L1_SMALL_SIZE,
|
| 445 |
+
DEFAULT_TRACE_REGION_SIZE,
|
| 446 |
+
DEFAULT_TT_METAL_CACHE,
|
| 447 |
+
)
|
| 448 |
+
from models.experimental.gr00t.tt.device import close_gr00t_device
|
| 449 |
+
|
| 450 |
+
os.environ.setdefault("TT_METAL_CACHE", str(DEFAULT_TT_METAL_CACHE))
|
| 451 |
+
device = M.open_model_device(
|
| 452 |
+
policy, trace_region_size=DEFAULT_TRACE_REGION_SIZE, l1_small_size=DEFAULT_L1_SMALL_SIZE
|
| 453 |
+
)
|
| 454 |
+
facts = harness.device_facts(device)
|
| 455 |
+
facts["worker_l1_size"] = M.device_worker_l1_size(device)
|
| 456 |
+
facts["dit_backend"] = policy.dit_backend
|
| 457 |
+
facts["mk_arena_dtype"] = policy.mk_arena_dtype if policy.dit_backend == "megakernel" else None
|
| 458 |
+
request.config._gr00t_device_facts = facts # type: ignore[attr-defined]
|
| 459 |
+
try:
|
| 460 |
+
yield device
|
| 461 |
+
finally:
|
| 462 |
+
close_gr00t_device(device)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
@pytest.fixture(scope="module")
|
| 466 |
def pair(version: str) -> List[Tuple[str, GoldenSet, Any]]:
|
| 467 |
"""``[(label, GoldenSet, Observation), ...]`` of the pair (skipped when a sample, its ``obs/`` or the snapshot is
|
image/blobs/sha256/0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"architecture":"amd64","config":{"User":"tt","Env":["PATH=/opt/tt-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","VENV=/opt/tt-venv","VIRTUAL_ENV=/opt/tt-venv","TT_METAL_RUNTIME_ROOT=/opt/tt-metal","TT_METAL_HOME=/opt/tt-metal","PYTHONPATH=/opt/tt-metal","LD_LIBRARY_PATH=/opt/tt-metal/build/lib:/opt/openmpi-v5.0.7-ulfm/lib","EXTRA_MODELS_DIR=","TT_VLLM_BUILTIN_MODELS=","TT_MODEL_KIND=tt-dit-server","HF_HOME=/hf","TT_METAL_CACHE=/cache","HOME=/home/tt","USER=tt","LOGNAME=tt"],"Entrypoint":["/usr/local/bin/entrypoint.sh"],"Cmd":["/usr/local/bin/serve-default.sh"],"WorkingDir":"/home/tt/work","Labels":{"org.opencontainers.image.revision":"668c290757550588d0ce46b180c344a462a2aaf5","org.opencontainers.image.version":"22.04","org.tenstorrent.tt-model":"gr00t-n1.6-3b-p150","org.tenstorrent.tt-model.arch":"blackhole","org.tenstorrent.tt-model.kind":"tt-dit-server","org.tenstorrent.tt-model.plugin":"","org.tenstorrent.tt-model.profiles":"default","org.tenstorrent.tt-model.repo":"changh95/GR00T-N1.6-3B-p150","org.tenstorrent.tt-model.tt-metal":"v0.79.0-dev20260914-1-g668c290757","org.tenstorrent.tt-model.weights":"nvidia/GR00T-N1.6-3B"},"ArgsEscaped":true},"created":"2026-09-18T05:45:28.071165066+09:00","history":[{"created":"2026-09-03T12:14:51.139348462Z","created_by":"/bin/sh -c #(nop) ARG RELEASE","empty_layer":true},{"created":"2026-09-03T12:14:51.170427469Z","created_by":"/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH","empty_layer":true},{"created":"2026-09-03T12:14:51.200307479Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04","empty_layer":true},{"created":"2026-09-03T12:14:53.528689977Z","created_by":"/bin/sh -c #(nop) ADD file:81c01921c5f642ac2fcbfae682e489e8e64b347467d9fa1587707e310e64d790 in / "},{"created":"2026-09-03T12:14:54.045714841Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG OMPI_DIR=/opt/openmpi-v5.0.7-ulfm","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG EXTRA_MODELS_DIR=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG TT_MODEL_KIND","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_NAME","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_REPO","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_WEIGHTS","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_ARCH","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_PROFILES","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_TT_METAL_SHA","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_TT_METAL_DESCRIBE","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"ARG MODEL_PLUGIN_SHA","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-15T00:17:47.388335211+09:00","created_by":"RUN |11 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=gr00t-n1.6-3b-p150 MODEL_REPO=changh95/GR00T-N1.6-3B-p150 MODEL_WEIGHTS=nvidia/GR00T-N1.6-3B MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=668c290757550588d0ce46b180c344a462a2aaf5 MODEL_TT_METAL_DESCRIBE=v0.79.0-dev20260914-1-g668c290757 MODEL_PLUGIN_SHA= /bin/sh -c apt-get update \u0026\u0026 apt-get install -y --no-install-recommends libhwloc15 libnuma1 libatomic1 libudev1 libcap2 zlib1g libmpc3 libmpfr6 libgmp10 libzstd1 libevent-core-2.1-7 libevent-pthreads-2.1-7 libgl1 libsndfile1 ca-certificates \u0026\u0026 apt-get clean \u0026\u0026 rm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-15T00:17:47.741172096+09:00","created_by":"RUN |11 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=gr00t-n1.6-3b-p150 MODEL_REPO=changh95/GR00T-N1.6-3B-p150 MODEL_WEIGHTS=nvidia/GR00T-N1.6-3B MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=668c290757550588d0ce46b180c344a462a2aaf5 MODEL_TT_METAL_DESCRIBE=v0.79.0-dev20260914-1-g668c290757 MODEL_PLUGIN_SHA= /bin/sh -c existing=\"$(getent passwd 1000 | cut -d: -f1)\" \u0026\u0026 if [ -n \"$existing\" ]; then userdel -r \"$existing\" 2\u003e/dev/null || userdel \"$existing\"; fi \u0026\u0026 useradd --uid 1000 --create-home --home-dir /home/tt --shell /bin/bash tt \u0026\u0026 mkdir -p /home/tt/work/logs /cache /opt/tt-metal \u0026\u0026 chown -R tt:tt /home/tt /cache /opt/tt-metal \u0026\u0026 chmod 1777 /home/tt /home/tt/work /home/tt/work/logs /cache # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-15T00:26:50.424500566+09:00","created_by":"COPY /opt/openmpi-v5.0.7-ulfm /opt/openmpi-v5.0.7-ulfm # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-15T00:26:56.847494353+09:00","created_by":"COPY /opt/tenstorrent /opt/tenstorrent # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:31:20.970763383+09:00","created_by":"COPY /usr/local/share/uv /usr/local/share/uv # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:43:55.192165457+09:00","created_by":"COPY /opt/tt-venv /opt/tt-venv # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:43:55.320045222+09:00","created_by":"COPY /opt/vllm /opt/vllm # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:00.176020413+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/runtime /opt/tt-metal/runtime # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:02.947444345+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/build_Release /opt/tt-metal/build_Release # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:05.896621192+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/build /opt/tt-metal/build # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:11.794830059+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/tt_metal /opt/tt-metal/tt_metal # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:16.009790261+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/ttnn /opt/tt-metal/ttnn # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:17.508565672+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/tools /opt/tt-metal/tools # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:18.531013694+09:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/setup.py /opt/tt-metal/pyproject.toml /opt/tt-metal/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:20.210963815+09:00","created_by":"COPY --chown=tt:tt code/ /opt/tt-metal/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:21.69976708+09:00","created_by":"COPY entrypoint.sh /usr/local/bin/entrypoint.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"COPY --chmod=0755 serve-default.sh /usr/local/bin/serve-default.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV VENV=/opt/tt-venv","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV VIRTUAL_ENV=/opt/tt-venv","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV PATH=/opt/tt-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV TT_METAL_RUNTIME_ROOT=/opt/tt-metal","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV TT_METAL_HOME=/opt/tt-metal","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV PYTHONPATH=/opt/tt-metal","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV LD_LIBRARY_PATH=/opt/tt-metal/build/lib:/opt/openmpi-v5.0.7-ulfm/lib","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV EXTRA_MODELS_DIR=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ARG TT_VLLM_BUILTIN_MODELS=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV TT_VLLM_BUILTIN_MODELS=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV TT_MODEL_KIND=tt-dit-server","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV HF_HOME=/hf","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV TT_METAL_CACHE=/cache","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV HOME=/home/tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV USER=tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"ENV LOGNAME=tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:22.388246477+09:00","created_by":"USER tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:44:23.450079066+09:00","created_by":"WORKDIR /home/tt/work","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:44:24.890053981+09:00","created_by":"COPY verify.sh /ctx/verify.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:45:27.833267805+09:00","created_by":"RUN |12 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=gr00t-n1.6-3b-p150 MODEL_REPO=changh95/GR00T-N1.6-3B-p150 MODEL_WEIGHTS=nvidia/GR00T-N1.6-3B MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=668c290757550588d0ce46b180c344a462a2aaf5 MODEL_TT_METAL_DESCRIBE=v0.79.0-dev20260914-1-g668c290757 MODEL_PLUGIN_SHA= TT_VLLM_BUILTIN_MODELS= /bin/sh -c bash /ctx/verify.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:45:27.833267805+09:00","created_by":"USER root","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:45:28.071165066+09:00","created_by":"RUN |12 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=gr00t-n1.6-3b-p150 MODEL_REPO=changh95/GR00T-N1.6-3B-p150 MODEL_WEIGHTS=nvidia/GR00T-N1.6-3B MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=668c290757550588d0ce46b180c344a462a2aaf5 MODEL_TT_METAL_DESCRIBE=v0.79.0-dev20260914-1-g668c290757 MODEL_PLUGIN_SHA= TT_VLLM_BUILTIN_MODELS= /bin/sh -c chmod -R a+rwX /home/tt # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-09-18T05:45:28.071165066+09:00","created_by":"USER tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:45:28.071165066+09:00","created_by":"LABEL org.tenstorrent.tt-model=gr00t-n1.6-3b-p150 org.tenstorrent.tt-model.repo=changh95/GR00T-N1.6-3B-p150 org.tenstorrent.tt-model.weights=nvidia/GR00T-N1.6-3B org.tenstorrent.tt-model.arch=blackhole org.tenstorrent.tt-model.kind=tt-dit-server org.tenstorrent.tt-model.profiles=default org.opencontainers.image.revision=668c290757550588d0ce46b180c344a462a2aaf5 org.tenstorrent.tt-model.tt-metal=v0.79.0-dev20260914-1-g668c290757 org.tenstorrent.tt-model.plugin=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:45:28.071165066+09:00","created_by":"ENTRYPOINT [\"/usr/local/bin/entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-09-18T05:45:28.071165066+09:00","created_by":"CMD [\"/usr/local/bin/serve-default.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34","sha256:2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f","sha256:0c2acf106608fb8af04e412572f17c510576f1086dfcd90a4789ba4fe6ab3102","sha256:12ce46944e0430cb86189d84b92ea4ba5110c40e2b965a27fd5dabea785158d0","sha256:20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89","sha256:ea7f9e8dc4b32aa84e39e01475e6e4ad24f6d4c175388103a9e93d1b021b3b7d","sha256:dddd09bc1156c04e4431e15e7adc290e18581a04d7545030b8115be07d8ba8aa","sha256:569f156189a8b1b0abf25d42c095d4342c66bad7f359f8f44d174a5b7d06d6f1","sha256:27b911cd5c2d8d210063fc08ff3f4326c701ddc06965cdf21c47f64fb5fa7c9f","sha256:f64479d7869b23160b3944a14abfb4b9a40ae5e70fd1b58eba3319d3253f8853","sha256:7cfb2f74d1f012e7c735a943ac188c0b72761cf2c12f815a9768b127b8350f23","sha256:6c625c305958b1775ab7779936b8bb3be1493dce685f9b26f68abebd74c46d98","sha256:15f1b153eb697ee10f2a6b85ef43a386725544ab4e38327b19429b7f5bcf149c","sha256:5c18d58ef8b510cafbfd183ad353acfd6ec015e633ac202ffb0559fc462c048d","sha256:f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5","sha256:660e84fb235e1a8e1281edec1404998d8a70dade16785315b5857ca9b7e48e83","sha256:a5a17edd1988763853752ee15d5bd4d6d2584a3393aec695e8d8b3862758a279","sha256:6f2c01a2e64a5954458cbbe49c32378dcf04517278ee030ae5c00424324f3529","sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef","sha256:91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4","sha256:f07d2440aabdc8a01341c7dbf2a12c605d15a367a972f3c3035401bedeb433c5","sha256:909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418"]}}
|
image/blobs/sha256/0c65a290603e6797836c24eaf4dbad448eb486ddd37d5b22ec615fd70cb133f2
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"b541132a377e8633a4983b59756ce33da5a2151a7b644a20f8e9fe4dc2719ddc","parent":"847be5b832733bb8d6e8ea3d80c5a09566773f3d6677ddce762e7e11bbf3f13e","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/1c590b200182d3f28659786c6b802d5c9fb63bef9a1aa09e5efdde2591a12c20
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"7813508801c5c352cf188ed0de962c1d8096aadaab2def10d03e4bd2139876b6","parent":"d76bfba1989c444294dd385520690ddd3375bf472931f08eb356bf17de161d5b","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/252a9530fcab61e4872b019330842abd14e288837bb494eabf7d961c2c623112
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"f699578393b121282585192635bfea1ff2a5107ed5590bf51a89ec10f8efa818","parent":"18d9869bf524b4834b53475ddb58956c35b519fcf3f76047114982881b3abfb9","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/3c4361cee2aeb8ae93f4ef84a1a82ff11ad20abeea0aeb0c97e9bf3be6859591
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"3d53fe9bdacbd53d9801d22baf7ddd7efdacaab8a8b84a631bdbdf597ddebdf5","parent":"7c9732243332e5b463ca4352fdb3cf27f3b357bddbeec5ae377cc9a3303a08b9","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/3f7cf805baca40c6075bef88421935d03e2e28cbdb906610cd51b7b761523c12
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"b72df2a436cbacbf641552f5620b58733416506634f7459ef7eac667ddbc993d","parent":"3d53fe9bdacbd53d9801d22baf7ddd7efdacaab8a8b84a631bdbdf597ddebdf5","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/5e8401978135bdfadc3dd329a54851b7fa7a06c24bb959b536e75966fea6b278
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60","size":14445},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34","size":80641024},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f","size":204086784},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:0c2acf106608fb8af04e412572f17c510576f1086dfcd90a4789ba4fe6ab3102","size":352256},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:12ce46944e0430cb86189d84b92ea4ba5110c40e2b965a27fd5dabea785158d0","size":33464832},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89","size":454126592},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:ea7f9e8dc4b32aa84e39e01475e6e4ad24f6d4c175388103a9e93d1b021b3b7d","size":187652608},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:dddd09bc1156c04e4431e15e7adc290e18581a04d7545030b8115be07d8ba8aa","size":1237841408},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:569f156189a8b1b0abf25d42c095d4342c66bad7f359f8f44d174a5b7d06d6f1","size":2048},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:27b911cd5c2d8d210063fc08ff3f4326c701ddc06965cdf21c47f64fb5fa7c9f","size":454626304},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:f64479d7869b23160b3944a14abfb4b9a40ae5e70fd1b58eba3319d3253f8853","size":136048640},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:7cfb2f74d1f012e7c735a943ac188c0b72761cf2c12f815a9768b127b8350f23","size":136048640},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6c625c305958b1775ab7779936b8bb3be1493dce685f9b26f68abebd74c46d98","size":195663360},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:15f1b153eb697ee10f2a6b85ef43a386725544ab4e38327b19429b7f5bcf149c","size":69021184},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:5c18d58ef8b510cafbfd183ad353acfd6ec015e633ac202ffb0559fc462c048d","size":5885952},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5","size":23040},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:660e84fb235e1a8e1281edec1404998d8a70dade16785315b5857ca9b7e48e83","size":15580672},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:a5a17edd1988763853752ee15d5bd4d6d2584a3393aec695e8d8b3862758a279","size":4608},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:6f2c01a2e64a5954458cbbe49c32378dcf04517278ee030ae5c00424324f3529","size":5120},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef","size":1024},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4","size":8704},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:f07d2440aabdc8a01341c7dbf2a12c605d15a367a972f3c3035401bedeb433c5","size":1763840},{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418","size":39936}]}
|
image/blobs/sha256/7f8ab8adfddcbb515e20c539f42c507b2bd9f9ad8e27e87719e5487da6125f89
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"0a2097b734d0b43c3ff587ac77bd60b79fab2bade954c1b98eb6e92294f5d8d3","parent":"7813508801c5c352cf188ed0de962c1d8096aadaab2def10d03e4bd2139876b6","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/8dbbd756d63b80f87f9c009aeb284a337a9457d21e97317066c14cdd8f21349f
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"18d9869bf524b4834b53475ddb58956c35b519fcf3f76047114982881b3abfb9","parent":"227145cc5d36b8622ca2eb65f3ed53c2b53ca29eec7b9ee823f530d34a63e74e","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/8eedd596896beb613d02b359b843aadeb06cb5ec8effbdbdf697632d1f238328
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"7c9732243332e5b463ca4352fdb3cf27f3b357bddbeec5ae377cc9a3303a08b9","parent":"a6e66eeb79c678923678ed7399fe67597aa0f66125b3477d930ce351ad58ce3c","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418
ADDED
|
Binary file (39.9 kB). View file
|
|
|
image/blobs/sha256/91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4
ADDED
|
Binary file (8.7 kB). View file
|
|
|
image/blobs/sha256/97a4a5f03aa47006f66965fb4a4f06010d695fc04d7683459bfdd729381c399d
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"da6a3c97d1c3758896789dd2b1486d422669701f2cf7f7625feda5c2f5daca7a","parent":"b72df2a436cbacbf641552f5620b58733416506634f7459ef7eac667ddbc993d","created":"2026-09-18T05:45:28.071165066+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"config":{"Hostname":"","Domainname":"","User":"tt","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/opt/tt-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","VENV=/opt/tt-venv","VIRTUAL_ENV=/opt/tt-venv","TT_METAL_RUNTIME_ROOT=/opt/tt-metal","TT_METAL_HOME=/opt/tt-metal","PYTHONPATH=/opt/tt-metal","LD_LIBRARY_PATH=/opt/tt-metal/build/lib:/opt/openmpi-v5.0.7-ulfm/lib","EXTRA_MODELS_DIR=","TT_VLLM_BUILTIN_MODELS=","TT_MODEL_KIND=tt-dit-server","HF_HOME=/hf","TT_METAL_CACHE=/cache","HOME=/home/tt","USER=tt","LOGNAME=tt"],"Cmd":["/usr/local/bin/serve-default.sh"],"ArgsEscaped":true,"Image":"","Volumes":null,"WorkingDir":"/home/tt/work","Entrypoint":["/usr/local/bin/entrypoint.sh"],"OnBuild":null,"Labels":{"org.opencontainers.image.revision":"668c290757550588d0ce46b180c344a462a2aaf5","org.opencontainers.image.version":"22.04","org.tenstorrent.tt-model":"gr00t-n1.6-3b-p150","org.tenstorrent.tt-model.arch":"blackhole","org.tenstorrent.tt-model.kind":"tt-dit-server","org.tenstorrent.tt-model.plugin":"","org.tenstorrent.tt-model.profiles":"default","org.tenstorrent.tt-model.repo":"changh95/GR00T-N1.6-3B-p150","org.tenstorrent.tt-model.tt-metal":"v0.79.0-dev20260914-1-g668c290757","org.tenstorrent.tt-model.weights":"nvidia/GR00T-N1.6-3B"}},"architecture":"amd64","os":"linux"}
|
image/blobs/sha256/be72b093968ed7fd244f0b1dff9dc05fd87d0031245c3a710ad2a3c0e55a772f
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"c456cdd8530267fc6889f8c1cf87762230e39652fcbad134f88ebc619ab0fc65","parent":"112dc954cd6c04a85cecc65f8188681e5493149aa0b4b7a62698ccda4edff3cc","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/c601ad8dea9cf1cb5c826fd622d446767719a4bdcc005207d9883b0bae3ec2f2
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"c89f024f2263f097b6bd9a82c8a69267c64e82f8cf00e97b0ff7b2970b6e877b","parent":"b541132a377e8633a4983b59756ce33da5a2151a7b644a20f8e9fe4dc2719ddc","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/e219f70f6edabc2003d4bee846c0f04bec4f41d668285011fda98a2ae63bdec7
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"847be5b832733bb8d6e8ea3d80c5a09566773f3d6677ddce762e7e11bbf3f13e","parent":"71d75d164a539e9fa8f69d3f211c58ff68c1d4cd7433c93704bee9980ec4c369","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/e3c4bbf0e1a172c093407d72bb7f93bfe42b5da41e10fbb57164701db74b06af
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"d76bfba1989c444294dd385520690ddd3375bf472931f08eb356bf17de161d5b","parent":"f699578393b121282585192635bfea1ff2a5107ed5590bf51a89ec10f8efa818","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/blobs/sha256/f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5
ADDED
|
Binary file (23 kB). View file
|
|
|
image/blobs/sha256/fb55b0f1dfab1bc8d7a83da0cfb21ce42baa0f8f3b85eaffab714c5fc4119cdc
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"227145cc5d36b8622ca2eb65f3ed53c2b53ca29eec7b9ee823f530d34a63e74e","parent":"c89f024f2263f097b6bd9a82c8a69267c64e82f8cf00e97b0ff7b2970b6e877b","created":"1970-01-01T09:00:00+09:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
|
image/index.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:
|
|
|
|
| 1 |
+
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:5e8401978135bdfadc3dd329a54851b7fa7a06c24bb959b536e75966fea6b278","size":3616,"annotations":{"io.containerd.image.name":"docker.io/tt-model/gr00t-n1.6-3b-p150:0be2c39e4a01","org.opencontainers.image.ref.name":"0be2c39e4a01"}}]}
|
image/manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
[{"Config":"blobs/sha256/
|
|
|
|
| 1 |
+
[{"Config":"blobs/sha256/0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60","RepoTags":["tt-model/gr00t-n1.6-3b-p150:0be2c39e4a01"],"Layers":["blobs/sha256/ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34","blobs/sha256/2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f","blobs/sha256/0c2acf106608fb8af04e412572f17c510576f1086dfcd90a4789ba4fe6ab3102","blobs/sha256/12ce46944e0430cb86189d84b92ea4ba5110c40e2b965a27fd5dabea785158d0","blobs/sha256/20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89","blobs/sha256/ea7f9e8dc4b32aa84e39e01475e6e4ad24f6d4c175388103a9e93d1b021b3b7d","blobs/sha256/dddd09bc1156c04e4431e15e7adc290e18581a04d7545030b8115be07d8ba8aa","blobs/sha256/569f156189a8b1b0abf25d42c095d4342c66bad7f359f8f44d174a5b7d06d6f1","blobs/sha256/27b911cd5c2d8d210063fc08ff3f4326c701ddc06965cdf21c47f64fb5fa7c9f","blobs/sha256/f64479d7869b23160b3944a14abfb4b9a40ae5e70fd1b58eba3319d3253f8853","blobs/sha256/7cfb2f74d1f012e7c735a943ac188c0b72761cf2c12f815a9768b127b8350f23","blobs/sha256/6c625c305958b1775ab7779936b8bb3be1493dce685f9b26f68abebd74c46d98","blobs/sha256/15f1b153eb697ee10f2a6b85ef43a386725544ab4e38327b19429b7f5bcf149c","blobs/sha256/5c18d58ef8b510cafbfd183ad353acfd6ec015e633ac202ffb0559fc462c048d","blobs/sha256/f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5","blobs/sha256/660e84fb235e1a8e1281edec1404998d8a70dade16785315b5857ca9b7e48e83","blobs/sha256/a5a17edd1988763853752ee15d5bd4d6d2584a3393aec695e8d8b3862758a279","blobs/sha256/6f2c01a2e64a5954458cbbe49c32378dcf04517278ee030ae5c00424324f3529","blobs/sha256/5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef","blobs/sha256/91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4","blobs/sha256/f07d2440aabdc8a01341c7dbf2a12c605d15a367a972f3c3035401bedeb433c5","blobs/sha256/909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418"],"LayerSources":{"sha256:0c2acf106608fb8af04e412572f17c510576f1086dfcd90a4789ba4fe6ab3102":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":352256,"digest":"sha256:0c2acf106608fb8af04e412572f17c510576f1086dfcd90a4789ba4fe6ab3102"},"sha256:12ce46944e0430cb86189d84b92ea4ba5110c40e2b965a27fd5dabea785158d0":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":33464832,"digest":"sha256:12ce46944e0430cb86189d84b92ea4ba5110c40e2b965a27fd5dabea785158d0"},"sha256:15f1b153eb697ee10f2a6b85ef43a386725544ab4e38327b19429b7f5bcf149c":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":69021184,"digest":"sha256:15f1b153eb697ee10f2a6b85ef43a386725544ab4e38327b19429b7f5bcf149c"},"sha256:20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":454126592,"digest":"sha256:20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89"},"sha256:27b911cd5c2d8d210063fc08ff3f4326c701ddc06965cdf21c47f64fb5fa7c9f":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":454626304,"digest":"sha256:27b911cd5c2d8d210063fc08ff3f4326c701ddc06965cdf21c47f64fb5fa7c9f"},"sha256:2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":204086784,"digest":"sha256:2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f"},"sha256:569f156189a8b1b0abf25d42c095d4342c66bad7f359f8f44d174a5b7d06d6f1":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":2048,"digest":"sha256:569f156189a8b1b0abf25d42c095d4342c66bad7f359f8f44d174a5b7d06d6f1"},"sha256:5c18d58ef8b510cafbfd183ad353acfd6ec015e633ac202ffb0559fc462c048d":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":5885952,"digest":"sha256:5c18d58ef8b510cafbfd183ad353acfd6ec015e633ac202ffb0559fc462c048d"},"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":1024,"digest":"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"},"sha256:660e84fb235e1a8e1281edec1404998d8a70dade16785315b5857ca9b7e48e83":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":15580672,"digest":"sha256:660e84fb235e1a8e1281edec1404998d8a70dade16785315b5857ca9b7e48e83"},"sha256:6c625c305958b1775ab7779936b8bb3be1493dce685f9b26f68abebd74c46d98":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":195663360,"digest":"sha256:6c625c305958b1775ab7779936b8bb3be1493dce685f9b26f68abebd74c46d98"},"sha256:6f2c01a2e64a5954458cbbe49c32378dcf04517278ee030ae5c00424324f3529":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":5120,"digest":"sha256:6f2c01a2e64a5954458cbbe49c32378dcf04517278ee030ae5c00424324f3529"},"sha256:7cfb2f74d1f012e7c735a943ac188c0b72761cf2c12f815a9768b127b8350f23":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":136048640,"digest":"sha256:7cfb2f74d1f012e7c735a943ac188c0b72761cf2c12f815a9768b127b8350f23"},"sha256:909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":39936,"digest":"sha256:909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418"},"sha256:91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":8704,"digest":"sha256:91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4"},"sha256:a5a17edd1988763853752ee15d5bd4d6d2584a3393aec695e8d8b3862758a279":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":4608,"digest":"sha256:a5a17edd1988763853752ee15d5bd4d6d2584a3393aec695e8d8b3862758a279"},"sha256:dddd09bc1156c04e4431e15e7adc290e18581a04d7545030b8115be07d8ba8aa":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":1237841408,"digest":"sha256:dddd09bc1156c04e4431e15e7adc290e18581a04d7545030b8115be07d8ba8aa"},"sha256:ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":80641024,"digest":"sha256:ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34"},"sha256:ea7f9e8dc4b32aa84e39e01475e6e4ad24f6d4c175388103a9e93d1b021b3b7d":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":187652608,"digest":"sha256:ea7f9e8dc4b32aa84e39e01475e6e4ad24f6d4c175388103a9e93d1b021b3b7d"},"sha256:f07d2440aabdc8a01341c7dbf2a12c605d15a367a972f3c3035401bedeb433c5":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":1763840,"digest":"sha256:f07d2440aabdc8a01341c7dbf2a12c605d15a367a972f3c3035401bedeb433c5"},"sha256:f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":23040,"digest":"sha256:f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5"},"sha256:f64479d7869b23160b3944a14abfb4b9a40ae5e70fd1b58eba3319d3253f8853":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":136048640,"digest":"sha256:f64479d7869b23160b3944a14abfb4b9a40ae5e70fd1b58eba3319d3253f8853"}}}]
|
image/repositories
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"tt-model/gr00t-n1.6-3b-p150":{"
|
|
|
|
| 1 |
+
{"tt-model/gr00t-n1.6-3b-p150":{"0be2c39e4a01":"909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418"}}
|
requirements.lock
CHANGED
|
@@ -12,7 +12,7 @@ distro==1.9.0
|
|
| 12 |
elastic-transport==9.4.2
|
| 13 |
elasticsearch==9.5.1
|
| 14 |
fastapi==0.141.1
|
| 15 |
-
filelock==3.32.
|
| 16 |
fonttools==4.65.0
|
| 17 |
fsspec==2026.7.0
|
| 18 |
graphviz==0.21
|
|
@@ -20,9 +20,9 @@ h11==0.16.0
|
|
| 20 |
hf-xet==1.6.0
|
| 21 |
httpcore==1.0.9
|
| 22 |
httpx==0.28.1
|
| 23 |
-
huggingface_hub==1.
|
| 24 |
identify==2.6.19
|
| 25 |
-
idna==3.
|
| 26 |
Jinja2==3.1.6
|
| 27 |
kiwisolver==1.5.1
|
| 28 |
linkify-it-py==2.2.0
|
|
@@ -41,7 +41,7 @@ opencv-python-headless==4.8.1.78
|
|
| 41 |
packaging==26.3
|
| 42 |
pandas==3.0.5
|
| 43 |
pillow==12.3.0
|
| 44 |
-
platformdirs==4.11.
|
| 45 |
pre_commit==4.6.2
|
| 46 |
psutil==7.2.2
|
| 47 |
pydantic==2.13.5
|
|
@@ -77,7 +77,7 @@ ttnn==0.65.2.dev10011
|
|
| 77 |
typer==0.27.2
|
| 78 |
typing-inspection==0.4.4
|
| 79 |
typing_extensions==4.16.0
|
| 80 |
-
urllib3==2.
|
| 81 |
uvicorn==0.53.0
|
| 82 |
-
virtualenv==21.7.
|
| 83 |
wheel==0.48.0
|
|
|
|
| 12 |
elastic-transport==9.4.2
|
| 13 |
elasticsearch==9.5.1
|
| 14 |
fastapi==0.141.1
|
| 15 |
+
filelock==3.32.7
|
| 16 |
fonttools==4.65.0
|
| 17 |
fsspec==2026.7.0
|
| 18 |
graphviz==0.21
|
|
|
|
| 20 |
hf-xet==1.6.0
|
| 21 |
httpcore==1.0.9
|
| 22 |
httpx==0.28.1
|
| 23 |
+
huggingface_hub==1.32.0
|
| 24 |
identify==2.6.19
|
| 25 |
+
idna==3.20
|
| 26 |
Jinja2==3.1.6
|
| 27 |
kiwisolver==1.5.1
|
| 28 |
linkify-it-py==2.2.0
|
|
|
|
| 41 |
packaging==26.3
|
| 42 |
pandas==3.0.5
|
| 43 |
pillow==12.3.0
|
| 44 |
+
platformdirs==4.11.9
|
| 45 |
pre_commit==4.6.2
|
| 46 |
psutil==7.2.2
|
| 47 |
pydantic==2.13.5
|
|
|
|
| 77 |
typer==0.27.2
|
| 78 |
typing-inspection==0.4.4
|
| 79 |
typing_extensions==4.16.0
|
| 80 |
+
urllib3==2.8.0
|
| 81 |
uvicorn==0.53.0
|
| 82 |
+
virtualenv==21.7.11
|
| 83 |
wheel==0.48.0
|
tt_kernel_manifest.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
"device_count": 1,
|
| 7 |
"producer": {
|
| 8 |
"tt_kernel_version": "0.1.0",
|
| 9 |
-
"created_at": "2026-09-
|
| 10 |
"hostname": "deepgadget"
|
| 11 |
},
|
| 12 |
"weights": {
|
|
@@ -35,8 +35,8 @@
|
|
| 35 |
"image": {
|
| 36 |
"registry": "hf",
|
| 37 |
"repository": "gr00t-n1.6-3b-p150",
|
| 38 |
-
"tag": "tt-model/gr00t-n1.6-3b-p150:
|
| 39 |
-
"digest": "sha256:
|
| 40 |
},
|
| 41 |
"kind": "tt-dit-server",
|
| 42 |
"runtime": {
|
|
@@ -72,6 +72,9 @@
|
|
| 72 |
"GR00T_LAYOUT": "gr1",
|
| 73 |
"GR00T_POLICY": "mixed_dit",
|
| 74 |
"GR00T_TRACE_LAYOUT": "per_stage",
|
|
|
|
|
|
|
|
|
|
| 75 |
"GR00T_TOKENIZER_DIR": "/opt/tt-metal/gr00t_p150/assets/tokenizer/n16",
|
| 76 |
"GR00T_PROJECT_ROOT": "/nonexistent",
|
| 77 |
"GR00T_GOLDEN_ROOT": "/nonexistent",
|
|
@@ -103,6 +106,12 @@
|
|
| 103 |
"import gr00t_p150.server.app as a; assert a.app",
|
| 104 |
"import sys, gr00t_p150.server.app; assert 'ttnn' not in sys.modules and not any(m.startswith('models.experimental.gr00t') for m in sys.modules), 'the server module must import without side effects'",
|
| 105 |
"import models.experimental.gr00t.tt.model as m; assert m.Gr00tTT",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
"from models.experimental.gr00t.common import configs, preprocessing, normalization, prompts, checkpoint, weights; assert configs.HF_SNAPSHOT_SHAS['n16'] == 'd0814e7ecb19202e7c8468b46098b0b7ef3a6d61' and configs.HF_REPOS['n16'] == 'nvidia/GR00T-N1.6-3B'",
|
| 107 |
"import models.tt_dit.utils.matmul as mm; assert mm.get_matmul_config",
|
| 108 |
"import models.demos.deepseek_v3_b1.unified_kernel_descriptor as u; assert u.UnifiedKernelDescriptor",
|
|
@@ -114,14 +123,14 @@
|
|
| 114 |
"from gr00t_p150.server.app import load_demo_request; import numpy as np; r = load_demo_request('n16'); assert set(r['images']) == {'ego_view_bg_crop_pad_res256_freq20'} and np.asarray(r['noise']).squeeze().shape == (50, 128)",
|
| 115 |
"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()",
|
| 116 |
"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()",
|
| 117 |
-
"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'))",
|
| 118 |
"from gr00t_p150.server.app import parse_mesh_shape as p; assert p('1x1') == p('(1, 1)') == p('1,1') == (1, 1)"
|
| 119 |
],
|
| 120 |
"built": {
|
| 121 |
-
"image": "tt-model/gr00t-n1.6-3b-p150:
|
| 122 |
"repo": "changh95/GR00T-N1.6-3B-p150",
|
| 123 |
"tt_model_version": "0.1.0",
|
| 124 |
-
"created_at": "2026-09-
|
| 125 |
"tt_metal": {
|
| 126 |
"sha": "668c290757550588d0ce46b180c344a462a2aaf5",
|
| 127 |
"describe": "v0.79.0-dev20260914-1-g668c290757",
|
|
@@ -132,8 +141,8 @@
|
|
| 132 |
"branch": "HEAD",
|
| 133 |
"pushed": true
|
| 134 |
},
|
| 135 |
-
"code_sha256": "
|
| 136 |
-
"image_digest": "sha256:
|
| 137 |
}
|
| 138 |
}
|
| 139 |
}
|
|
|
|
| 6 |
"device_count": 1,
|
| 7 |
"producer": {
|
| 8 |
"tt_kernel_version": "0.1.0",
|
| 9 |
+
"created_at": "2026-09-17T20:45:45.250616+00:00",
|
| 10 |
"hostname": "deepgadget"
|
| 11 |
},
|
| 12 |
"weights": {
|
|
|
|
| 35 |
"image": {
|
| 36 |
"registry": "hf",
|
| 37 |
"repository": "gr00t-n1.6-3b-p150",
|
| 38 |
+
"tag": "tt-model/gr00t-n1.6-3b-p150:0be2c39e4a01",
|
| 39 |
+
"digest": "sha256:0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60"
|
| 40 |
},
|
| 41 |
"kind": "tt-dit-server",
|
| 42 |
"runtime": {
|
|
|
|
| 72 |
"GR00T_LAYOUT": "gr1",
|
| 73 |
"GR00T_POLICY": "mixed_dit",
|
| 74 |
"GR00T_TRACE_LAYOUT": "per_stage",
|
| 75 |
+
"GR00T_DIT_BACKEND": "megakernel",
|
| 76 |
+
"GR00T_MK_ARENA_DTYPE": "auto",
|
| 77 |
+
"GR00T_NUM_CQS": "2",
|
| 78 |
"GR00T_TOKENIZER_DIR": "/opt/tt-metal/gr00t_p150/assets/tokenizer/n16",
|
| 79 |
"GR00T_PROJECT_ROOT": "/nonexistent",
|
| 80 |
"GR00T_GOLDEN_ROOT": "/nonexistent",
|
|
|
|
| 106 |
"import gr00t_p150.server.app as a; assert a.app",
|
| 107 |
"import sys, gr00t_p150.server.app; assert 'ttnn' not in sys.modules and not any(m.startswith('models.experimental.gr00t') for m in sys.modules), 'the server module must import without side effects'",
|
| 108 |
"import models.experimental.gr00t.tt.model as m; assert m.Gr00tTT",
|
| 109 |
+
"from models.experimental.gr00t.tt.policy import TTPolicy, DEFAULT_DIT_BACKEND; p = TTPolicy(); assert p.dit_backend == 'megakernel' == DEFAULT_DIT_BACKEND and p.mk_arena_dtype == 'auto' and p.mk_arena_dtype_for('n16') == 'bfp8_b', (p.dit_backend, p.mk_arena_dtype)",
|
| 110 |
+
"import models.experimental.gr00t.tt.megakernel.dit_program as d; from models.experimental.gr00t.tt.megakernel import arena, core_map, descriptors; assert d.DiTMegakernel and descriptors.KERNEL_SOURCE.endswith('tt/megakernel/kernels/dit_kernel.cpp')",
|
| 111 |
+
"import os; from pathlib import Path; from models.experimental.gr00t.tt.megakernel.descriptors import KERNEL_SOURCE; k = Path(os.environ.get('TT_METAL_HOME', '/opt/tt-metal')) / KERNEL_SOURCE; assert k.is_file() and len(list((k.parent / 'ops').glob('*.hpp'))) == 7, str(k)",
|
| 112 |
+
"import os, re; from pathlib import Path; from models.experimental.gr00t.tt.megakernel.descriptors import KERNEL_SOURCE; home = Path(os.environ.get('TT_METAL_HOME', '/opt/tt-metal')); k = home / KERNEL_SOURCE; incs = {m for f in [k, *(k.parent / 'ops').glob('*.hpp')] for m in re.findall(r'#include \"([^\"]*unified_kernels/[^\"]+)\"', f.read_text())}; assert incs, 'no unified_kernels includes found'; missing = [i for i in incs if not (k.parent / i).resolve().is_file() and not (home / i).is_file()]; assert not missing, missing",
|
| 113 |
+
"from models.experimental.gr00t.tt.model import open_model_device, device_worker_l1_size, worker_l1_size_for, MK_L1_CUT_BYTES_DEFAULT; from models.experimental.gr00t.tt.policy import TTPolicy; assert MK_L1_CUT_BYTES_DEFAULT == 65536 and worker_l1_size_for(TTPolicy()) is not None and worker_l1_size_for(TTPolicy(dit_backend='ttnn')) is None",
|
| 114 |
+
"from gr00t_p150.server.app import STAGE_SERVED, DEFAULT_NUM_CQS, DIT_BACKENDS, MK_ARENA_DTYPES; assert STAGE_SERVED['megakernel'] == 'stage2-megakernel' and STAGE_SERVED['ttnn'] == 'stage1-ttnn-traces' and DEFAULT_NUM_CQS == 2 and set(DIT_BACKENDS) == {'ttnn', 'megakernel'} and 'bfp8_b' in MK_ARENA_DTYPES",
|
| 115 |
"from models.experimental.gr00t.common import configs, preprocessing, normalization, prompts, checkpoint, weights; assert configs.HF_SNAPSHOT_SHAS['n16'] == 'd0814e7ecb19202e7c8468b46098b0b7ef3a6d61' and configs.HF_REPOS['n16'] == 'nvidia/GR00T-N1.6-3B'",
|
| 116 |
"import models.tt_dit.utils.matmul as mm; assert mm.get_matmul_config",
|
| 117 |
"import models.demos.deepseek_v3_b1.unified_kernel_descriptor as u; assert u.UnifiedKernelDescriptor",
|
|
|
|
| 123 |
"from gr00t_p150.server.app import load_demo_request; import numpy as np; r = load_demo_request('n16'); assert set(r['images']) == {'ego_view_bg_crop_pad_res256_freq20'} and np.asarray(r['noise']).squeeze().shape == (50, 128)",
|
| 124 |
"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()",
|
| 125 |
"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()",
|
| 126 |
+
"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'))",
|
| 127 |
"from gr00t_p150.server.app import parse_mesh_shape as p; assert p('1x1') == p('(1, 1)') == p('1,1') == (1, 1)"
|
| 128 |
],
|
| 129 |
"built": {
|
| 130 |
+
"image": "tt-model/gr00t-n1.6-3b-p150:0be2c39e4a01",
|
| 131 |
"repo": "changh95/GR00T-N1.6-3B-p150",
|
| 132 |
"tt_model_version": "0.1.0",
|
| 133 |
+
"created_at": "2026-09-17T20:40:28+00:00",
|
| 134 |
"tt_metal": {
|
| 135 |
"sha": "668c290757550588d0ce46b180c344a462a2aaf5",
|
| 136 |
"describe": "v0.79.0-dev20260914-1-g668c290757",
|
|
|
|
| 141 |
"branch": "HEAD",
|
| 142 |
"pushed": true
|
| 143 |
},
|
| 144 |
+
"code_sha256": "8e6bd13a23654f91a229745d001c12b7972f9215a95c75fe05258181f7c324b8",
|
| 145 |
+
"image_digest": "sha256:0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60"
|
| 146 |
}
|
| 147 |
}
|
| 148 |
}
|