changh95 commited on
Commit
8e1e774
·
verified ·
1 Parent(s): 7f8a797

Add files using upload-large-folder tool

Browse files
Files changed (36) hide show
  1. README.md +24 -36
  2. code/models/experimental/gr00t/tests/tt/test_bfp8_emulation_cpu.py +617 -0
  3. code/models/experimental/gr00t/tests/tt/test_mk_e2e.py +591 -0
  4. code/models/experimental/gr00t/tests/tt/test_mk_head_sdpa.py +754 -0
  5. code/models/experimental/gr00t/tests/tt/test_mk_layernorm.py +784 -0
  6. code/models/experimental/gr00t/tests/tt/test_tt_action_head.py +34 -44
  7. code/models/experimental/gr00t/tests/tt/test_tt_backbone.py +9 -4
  8. code/models/experimental/gr00t/tests/tt/test_tt_determinism.py +36 -0
  9. code/models/experimental/gr00t/tests/tt/test_tt_dtype_policy.py +904 -0
  10. code/models/experimental/gr00t/tests/tt/test_tt_model_e2e.py +54 -7
  11. code/models/experimental/gr00t/tests/tt/test_tt_perf.py +269 -25
  12. code/models/experimental/gr00t/tests/tt/test_tt_stale_state.py +36 -0
  13. image/blobs/sha256/0be2c39e4a01f307b061822a3a6d0b098e495a20c8ca0870953f0a73a05e4e60 +1 -0
  14. image/blobs/sha256/0c65a290603e6797836c24eaf4dbad448eb486ddd37d5b22ec615fd70cb133f2 +1 -0
  15. image/blobs/sha256/1c590b200182d3f28659786c6b802d5c9fb63bef9a1aa09e5efdde2591a12c20 +1 -0
  16. image/blobs/sha256/252a9530fcab61e4872b019330842abd14e288837bb494eabf7d961c2c623112 +1 -0
  17. image/blobs/sha256/3c4361cee2aeb8ae93f4ef84a1a82ff11ad20abeea0aeb0c97e9bf3be6859591 +1 -0
  18. image/blobs/sha256/3f7cf805baca40c6075bef88421935d03e2e28cbdb906610cd51b7b761523c12 +1 -0
  19. image/blobs/sha256/5e8401978135bdfadc3dd329a54851b7fa7a06c24bb959b536e75966fea6b278 +1 -0
  20. image/blobs/sha256/7f8ab8adfddcbb515e20c539f42c507b2bd9f9ad8e27e87719e5487da6125f89 +1 -0
  21. image/blobs/sha256/8dbbd756d63b80f87f9c009aeb284a337a9457d21e97317066c14cdd8f21349f +1 -0
  22. image/blobs/sha256/8eedd596896beb613d02b359b843aadeb06cb5ec8effbdbdf697632d1f238328 +1 -0
  23. image/blobs/sha256/909e145eca13ac01c3f52611518262d779e8afdbf1d6f9330502727bcf54a418 +0 -0
  24. image/blobs/sha256/91a77e841802c172ee3d5b6b162272bf0732cd8a54a7600d7250c5b788f2bbd4 +0 -0
  25. image/blobs/sha256/97a4a5f03aa47006f66965fb4a4f06010d695fc04d7683459bfdd729381c399d +1 -0
  26. image/blobs/sha256/be72b093968ed7fd244f0b1dff9dc05fd87d0031245c3a710ad2a3c0e55a772f +1 -0
  27. image/blobs/sha256/c601ad8dea9cf1cb5c826fd622d446767719a4bdcc005207d9883b0bae3ec2f2 +1 -0
  28. image/blobs/sha256/e219f70f6edabc2003d4bee846c0f04bec4f41d668285011fda98a2ae63bdec7 +1 -0
  29. image/blobs/sha256/e3c4bbf0e1a172c093407d72bb7f93bfe42b5da41e10fbb57164701db74b06af +1 -0
  30. image/blobs/sha256/f0ef8bb8e669c94f302f8c3bb22f260910987963e3b64dd82d715f00df9ce9e5 +0 -0
  31. image/blobs/sha256/fb55b0f1dfab1bc8d7a83da0cfb21ce42baa0f8f3b85eaffab714c5fc4119cdc +1 -0
  32. image/index.json +1 -1
  33. image/manifest.json +1 -1
  34. image/repositories +1 -1
  35. requirements.lock +6 -6
  36. 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
- # GR00T-N1.6-3B-p150
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-1 release: TTNN ops replayed from Metal traces (DiT weights bfp8, everything else bf16); the persistent-megakernel denoise is in progress and not in this image.
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
- - Weights [`nvidia/GR00T-N1.6-3B`](https://huggingface.co/nvidia/GR00T-N1.6-3B) at `d0814e7ecb19` (6.57 GB) go to your HF cache; the image does not contain them.
41
- - Serves on port 20000 (or the next free port); ready when the log says `Application startup complete`.
42
 
43
  ### Run with tt-cli
44
 
@@ -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.0260, -0.0414], "... 16 steps"],
64
- "right_arm": [[-0.0024, -0.1502, 0.0280, -2.1461, -0.0091, 0.0132, 0.0044], "..."],
65
- "left_hand": [[0.0097, -0.0177, 0.0041, 0.0078, 0.0539, 0.0281], "..."],
66
- "right_hand": [[-0.2339, -0.2466, -0.2431, -0.2311, -0.2423, 0.3333], "..."],
67
- "waist": [[-0.0010, -0.0060, 0.0008], "..."]},
68
  "action_horizon": 16, "embodiment": "gr1", "seq_len": 116, "seed": 42,
69
- "timing_ms": {"decode": 0.8, "encode": 1.5, "device": 56.5, "decode_actions": 0.1, "total": 58.9}}
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.99991 · `right_hand` 0.99312 · `left_hand` / `waist` max\|d\| 0.059 / 0.017 (near-constant groups) · all 29 action dims 0.999209 |
85
- | Intermediate taps vs the fp32 reference (45 canonical taps, from pixels) | 52 of 56 gated rows pass; 4 marginal misses on the DiT state-token row that no action reads (`dit_out[k=1,2]` 0.999556 / 0.999496 vs gates 0.99961 / 0.99964, `action_decoder_out[k=1,2]` 0.999579 / 0.999556 vs 0.9997 / 0.99978), caused by bf16 error in the SigLIP2 tower |
86
- | Traced vs untraced, determinism | bit-identical; 5 trace replays bit-identical |
87
- | Inference on p150a (warm, batch 1, median of 50) | **59.4 ms end-to-end** = host encode 2.0 + upload 3.8 + traces 53.3 (vision 11.5 · LLM 9.5 · adapter 2.0 · denoise 30.4) + readback and decode 0.3 |
88
- | Served over HTTP by this image (warm, batch 1) | 56.8 ms device · 59.4 ms server-side incl. PNG decode |
89
- | 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.06× faster; with `torch.compile` on the DiT the GPU reaches 33.1 ms GPU 1. faster than this Stage-1 path |
 
90
 
91
  ### Caveats
92
 
93
- - Stage-1 path: TTNN ops in four Metal traces, DiT matmul weights bfp8, everything else bf16. The megakernel denoise (weight streaming measured at 464 GB/s bf16 / 414 GB/s bfp8 over the 32-block N1.6 weight set) is not in this image.
94
- - Fidelity margin: the served chunk matches the fp32 reference at PCC 0.99921 on the normalised valid slice (NVIDIA's own bf16 GPU run scores 0.99956), 2e-4 above the 0.999 boot self-check.
 
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, 137 s); later boots take 13–21 s. Validated on tt-metal `668c2907575`, single p150a only. Details: [`SERVING.md`](SERVING.md), [`GPU_COMPARISON.md`](GPU_COMPARISON.md).
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 | `7a456e1065da7159` (sha256, first 16 hex digits) |
114
- | built | 2026-09-14T16:25:52+00:00 by tt-model 0.1.0 |
 
 
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, and the
18
- megakernel backend raising until WP-K5.
 
 
 
 
 
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
- with pytest.raises(NotImplementedError):
295
- AH.ActionHeadTT(cfg, dl, W, TTPolicy(dit_backend="megakernel"), device, kv=kv).denoise(head2.x_t, state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, mi, cfg, dl, head_weights, TTPolicy(dtype_policy=head_weights.policy.dtype_policy), gr00t_device, **kw
 
 
 
 
 
 
 
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 reaches
76
- #: PCC 0.99965 on ``vit_deepstack_mergers[0]`` against the frozen gate 0.9998 (bf16 block stack, HiFi2 linears); the
77
- #: miss is an **xfail** while the PCC stays above the documented floor, every other gated tap must pass, and the
78
- #: WP-12 acceptance keys (REQUIRED_GATED) are never in this table. Plan owner: relax to 0.9995 or fork the tower (WP-P).
 
 
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
- pol = TTPolicy(dtype_policy="bf16", trace_layout=trace_layout)
 
 
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) | {k for k in cand if k.startswith(M.DECODE_TAP)}
 
 
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
- ``GR00T_PERF_BASELINE`` overrides: ``committed`` (default) | ``latest`` | ``none`` | ``/path/to/baseline.json``.
21
- Every device test **skips** (never fails) when no baseline exists, and writes its own JSON through the benchmark
22
- writer plus the comparison rows through the ``results`` fixture (``tests/tt/results/``).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, version: str, results_dir: Path = SO.RESULTS_DIR, exclude: Sequence[Path] = ()
 
 
 
 
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
- def _load_baseline(bench: str, version: str) -> Any:
378
- p = baseline_for(bench, version)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  if p is None:
380
- pytest.skip(f"{bench}: no baseline JSON for {version} in {SO.RESULTS_DIR} (set {BASELINE_ENV} to override)")
 
 
 
381
  return p, SO.load_perf_json(p)
382
 
383
 
384
- def _device(request: pytest.FixtureRequest, results: Any) -> Any:
385
- """The session device, requested **after** the baseline check so that a baseline-less run skips without opening
386
- the device (a session-scoped fixture in the signature would be instantiated first)."""
 
387
  from models.experimental.gr00t.tests.tt.harness import device_facts
388
-
389
- device = request.getfixturevalue("gr00t_device")
390
- results.set_device_facts(device_facts(device))
 
 
 
 
 
 
 
 
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, version: str, results: Any, policy_name: str, trace_layout: str
 
 
 
 
 
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 = SO.compare_stages(stages_for_version(payload, version), stages_for_version(baseline, version), TOL)
 
 
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, version: str, results: Any, policy_name: str, trace_layout: str
 
 
 
 
 
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(device, version=version, policy=policy_name, trace_layout=trace_layout, verbose=False)
 
 
 
 
 
 
 
 
441
  current = SO.write_perf_json(payload)
442
- cmp = SO.compare_stages(stages_for_version(payload, version), stages_for_version(baseline, version), TOL)
 
 
 
 
 
 
 
 
 
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:fbd897255c55c5be409bc1041428ff0b3c9ee37b59522a9962bd18c3884662c0","size":3616,"annotations":{"io.containerd.image.name":"docker.io/tt-model/gr00t-n1.6-3b-p150:feb77048a2ed","org.opencontainers.image.ref.name":"feb77048a2ed"}}]}
 
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/feb77048a2ed1772037207da1e3fbf571555db9c9554509bad74076e7c94fa55","RepoTags":["tt-model/gr00t-n1.6-3b-p150:feb77048a2ed"],"Layers":["blobs/sha256/ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34","blobs/sha256/2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f","blobs/sha256/0c2acf106608fb8af04e412572f17c510576f1086dfcd90a4789ba4fe6ab3102","blobs/sha256/12ce46944e0430cb86189d84b92ea4ba5110c40e2b965a27fd5dabea785158d0","blobs/sha256/20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89","blobs/sha256/456928125bdb6641fde495b5089401d6eefdf8a6b8c9c52f14b0836c29f931d7","blobs/sha256/6ef3d5458860fc96e862cfc6df467f80a4db334ebe6bae7da8f09fda2d1cd758","blobs/sha256/197405c374ca76fc01e68a8f55f41a379d40389f10e9cfe029cca8e00a5abd0f","blobs/sha256/58c2b8faf6877c94ecd1468ab1ad99d4c4c73fb376613d8517b659fc09309496","blobs/sha256/a08d44c169e46f95b649984d7adb7cbf9171a6c3343c1ceafdf3cc23512b008c","blobs/sha256/ea07a201af443d5a600b22c02de0009e907acab32e5b033ed8df86d0a0113eb5","blobs/sha256/256e0cf0f7d85bbf0d3362be27dee0534059bc45011db9b5aad683550e991905","blobs/sha256/425f5a58dfa6749ff395b01ab459d4cc946046a8ef0d87280808494adea78ea0","blobs/sha256/da48df271e2ffd8c89608ed1e938fd4f5c1d2599b134f3218b4751f3fdc7acda","blobs/sha256/3c99da1ba1a01d71278c50c0d1a0571372f3a5026536fbe7e3428615b0274bd7","blobs/sha256/7f6c689c3e7be5fee57c085083155ae0721468b07eb39d97dbcb2f3b010404c2","blobs/sha256/658567390c2a80da8263482cbb57632be7bd0386b8a9c71d8f02b1657b2fdf82","blobs/sha256/77ef632adb98b5a998318d8a04d1d5dd6a73010530c50bfe6c18c47a92032ca2","blobs/sha256/5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef","blobs/sha256/71bfc9a481add3ed85649644d61cdbdc195c0cb06ddf2a79f0704eb71ac4e93d","blobs/sha256/f996a3012184d72d82d3fce69523ef7e4f47d317339d5286c93ea6a463d63466","blobs/sha256/aa10b8db9ce55f80f832cc35d23d3776ad72b6dc6dd95bc6b8dd9f1a9cde331b"],"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:197405c374ca76fc01e68a8f55f41a379d40389f10e9cfe029cca8e00a5abd0f":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":2048,"digest":"sha256:197405c374ca76fc01e68a8f55f41a379d40389f10e9cfe029cca8e00a5abd0f"},"sha256:20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":454126592,"digest":"sha256:20cad74cf2e0718048ce7cad5032e4837d231af0ead9812353883170b27a8c89"},"sha256:256e0cf0f7d85bbf0d3362be27dee0534059bc45011db9b5aad683550e991905":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":195663360,"digest":"sha256:256e0cf0f7d85bbf0d3362be27dee0534059bc45011db9b5aad683550e991905"},"sha256:2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":204086784,"digest":"sha256:2c86a37deecae8c6c4d64575b68d161cde8651d057ce91ee6ad9df65ce22535f"},"sha256:3c99da1ba1a01d71278c50c0d1a0571372f3a5026536fbe7e3428615b0274bd7":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":23040,"digest":"sha256:3c99da1ba1a01d71278c50c0d1a0571372f3a5026536fbe7e3428615b0274bd7"},"sha256:425f5a58dfa6749ff395b01ab459d4cc946046a8ef0d87280808494adea78ea0":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":69021184,"digest":"sha256:425f5a58dfa6749ff395b01ab459d4cc946046a8ef0d87280808494adea78ea0"},"sha256:456928125bdb6641fde495b5089401d6eefdf8a6b8c9c52f14b0836c29f931d7":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":187652608,"digest":"sha256:456928125bdb6641fde495b5089401d6eefdf8a6b8c9c52f14b0836c29f931d7"},"sha256:58c2b8faf6877c94ecd1468ab1ad99d4c4c73fb376613d8517b659fc09309496":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":454626304,"digest":"sha256:58c2b8faf6877c94ecd1468ab1ad99d4c4c73fb376613d8517b659fc09309496"},"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":1024,"digest":"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"},"sha256:658567390c2a80da8263482cbb57632be7bd0386b8a9c71d8f02b1657b2fdf82":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":4608,"digest":"sha256:658567390c2a80da8263482cbb57632be7bd0386b8a9c71d8f02b1657b2fdf82"},"sha256:6ef3d5458860fc96e862cfc6df467f80a4db334ebe6bae7da8f09fda2d1cd758":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":1237653504,"digest":"sha256:6ef3d5458860fc96e862cfc6df467f80a4db334ebe6bae7da8f09fda2d1cd758"},"sha256:71bfc9a481add3ed85649644d61cdbdc195c0cb06ddf2a79f0704eb71ac4e93d":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":6656,"digest":"sha256:71bfc9a481add3ed85649644d61cdbdc195c0cb06ddf2a79f0704eb71ac4e93d"},"sha256:77ef632adb98b5a998318d8a04d1d5dd6a73010530c50bfe6c18c47a92032ca2":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":5120,"digest":"sha256:77ef632adb98b5a998318d8a04d1d5dd6a73010530c50bfe6c18c47a92032ca2"},"sha256:7f6c689c3e7be5fee57c085083155ae0721468b07eb39d97dbcb2f3b010404c2":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":10250752,"digest":"sha256:7f6c689c3e7be5fee57c085083155ae0721468b07eb39d97dbcb2f3b010404c2"},"sha256:a08d44c169e46f95b649984d7adb7cbf9171a6c3343c1ceafdf3cc23512b008c":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":136048640,"digest":"sha256:a08d44c169e46f95b649984d7adb7cbf9171a6c3343c1ceafdf3cc23512b008c"},"sha256:aa10b8db9ce55f80f832cc35d23d3776ad72b6dc6dd95bc6b8dd9f1a9cde331b":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":39936,"digest":"sha256:aa10b8db9ce55f80f832cc35d23d3776ad72b6dc6dd95bc6b8dd9f1a9cde331b"},"sha256:da48df271e2ffd8c89608ed1e938fd4f5c1d2599b134f3218b4751f3fdc7acda":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":5885952,"digest":"sha256:da48df271e2ffd8c89608ed1e938fd4f5c1d2599b134f3218b4751f3fdc7acda"},"sha256:ea07a201af443d5a600b22c02de0009e907acab32e5b033ed8df86d0a0113eb5":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":136048640,"digest":"sha256:ea07a201af443d5a600b22c02de0009e907acab32e5b033ed8df86d0a0113eb5"},"sha256:ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":80641024,"digest":"sha256:ea16cace89338c84eb6bcb91a7efdfcae6838fff359efe951858227436486c34"},"sha256:f996a3012184d72d82d3fce69523ef7e4f47d317339d5286c93ea6a463d63466":{"mediaType":"application/vnd.oci.image.layer.v1.tar","size":1316864,"digest":"sha256:f996a3012184d72d82d3fce69523ef7e4f47d317339d5286c93ea6a463d63466"}}}]
 
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":{"feb77048a2ed":"aa10b8db9ce55f80f832cc35d23d3776ad72b6dc6dd95bc6b8dd9f1a9cde331b"}}
 
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.6
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.31.0
24
  identify==2.6.19
25
- idna==3.19
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.8
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.7.0
81
  uvicorn==0.53.0
82
- virtualenv==21.7.9
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-14T16:29:57.941499+00:00",
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:feb77048a2ed",
39
- "digest": "sha256:feb77048a2ed1772037207da1e3fbf571555db9c9554509bad74076e7c94fa55"
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:feb77048a2ed",
122
  "repo": "changh95/GR00T-N1.6-3B-p150",
123
  "tt_model_version": "0.1.0",
124
- "created_at": "2026-09-14T16:25:52+00:00",
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": "7a456e1065da7159a7c2bb83c70e115542e43c5a655f5fe8c14e310f446da0d5",
136
- "image_digest": "sha256:feb77048a2ed1772037207da1e3fbf571555db9c9554509bad74076e7c94fa55"
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
  }