Repository navigation
cake_gdn: deterministic chunked GDN training kernels (--linear-attention-backend loom) for the head-sharded Qwen GDN layer - #3597
Conversation
…s (SM100a/SM103a) Sixteen forward/backward stages (chunk table, prep, WY solve, register-resident state scan in three grid configurations, output, dv_local, adjoint scan, dq/dk/dw/dg, WY pullback, finalize) as source-only torch CUDA extensions under miles_plugins/models/gdn_chunk_train, with a chunk_gated_delta_rule autograd wrapper that has FLA v0.5.2's signature (cu_seqlens packing, initial/final state, in-kernel q/k L2 norm, fla.ops.cp state passing). No atomics, fixed reduction order, launch geometry a pure function of shape and device: outputs and gradients are bit-identical call to call. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…backend One GatedDeltaRuleAttentionCore serves Qwen3.5/3.6/3.8 and Qwen3-Next: Megatron column/row- parallel projections sharded by key-head group (no replicated GDN compute across TP ranks), head-sharded conv1d/A_log/dt_bias with TP-aware sharded_state_dict, a replicated per-head gated norm whose gradient is summed across TP, fla CP state passing unchanged. HF parameter names are kept; in_proj_qkv (Qwen3.5) and conv1d are stored head-interleaved so contiguous TP chunks are head shards, and the HF<->Megatron bridge plus the Megatron->HF weight update translate the row order. The SP gather now reduce-scatters its gradient for TP-sharded modules. --linear-attention-backend loom selects the generated deterministic kernels. Tests: layout/converter/bridge contracts (CPU), fla-vs-loom parity + bit determinism, torchrun TP2 / CP2 / TP2xCP2 core parity, Qwen3.5-4B e2e with TP=2+SP on the loom backend. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…est; tests/fast/models package Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…only cp_group is set detect_and_setup_hybrid_cp configures cp_group / cp_rank / cp_world_size on the linear_attn module and hf_forward passes only the global packed boundaries, exactly like the previous replicated module, whose forward built the context itself. The unified core only honoured an explicitly passed cp_context, so with hybrid CP the non-CP path ran with global cu_seqlens over a local sequence (out-of-bounds / NaN). Build the context in forward when cp_context is None and cp_group is set; callers that already hold one keep passing it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y check Qwen3.5 is a VLM; the SGLang engines load visual.* but text-only Megatron training never updates those tensors, so --ci-test's equality check must only assert on the language-model weights (incl. the head-interleaved linear_attn.* converters this test exercises). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MILES_E2E_MODE=record dumps the rollout batches of a live run; MILES_E2E_MODE=replay trains on them with --debug-train-only and saves the per-step grad norm under MILES_E2E_RUN_TAG, so two loom replays of the same batches can be compared bit-for-bit (fla replays are the non-deterministic reference). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replays also save --save-debug-train-data (trainer log-probs and the rest of the rollout batch after the training forward), so two loom replays can be compared bit-for-bit on non-trivial tensors even when a batch has zero advantages. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t use The stages built lazily on first use; the register-scan variants are selected by (num_seqs, heads), so a new shape mid-training compiled a stage inside a training step (about a minute per stage, serially). In the Qwen3.5-4B e2e this showed up as a 65 s / 100 s ref_log_probs pass in the second rollout. chunk_gated_delta_rule now triggers a cached, parallel prebuild of every stage for the device architecture on its first call (MILES_GDN_CHUNK_TRAIN_PREBUILD=0 disables it); serial fallback on a concurrent build failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Performance follow-up: loom vs FLA v0.5.2, and the slow
|
| shape (seqs x len, k/v heads) | scan | B200 loom / fla fwd (ms) | B200 fwd x | B200 loom / fla fwd+bwd (ms) | B200 fwd+bwd x | GB300 loom / fla fwd (ms) | GB300 fwd x | GB300 loom / fla fwd+bwd (ms) | GB300 fwd+bwd x |
|---|---|---|---|---|---|---|---|---|---|
| 4 x 4096, 8/16 (TP=2 shard) | 32r/k2 | 0.533 / 0.427 | 0.80 | 1.785 / 1.922 | 1.08 | 0.536 / 0.618 | 1.15 | 1.713 / 1.786 | 1.04 |
| 8 x 4096, 8/16 | 64r/k1 | 0.917 / 0.635 | 0.69 | 3.319 / 2.227 | 0.67 | 0.868 / 0.633 | 0.73 | 3.082 / 2.101 | 0.68 |
| 16 x 4096, 8/16 | 64r/k1 | 1.745 / 1.219 | 0.70 | 6.408 / 3.948 | 0.62 | 1.606 / 1.164 | 0.72 | 5.951 / 3.806 | 0.64 |
| 1 x 16384, 8/16 | 32r/k2 | 0.735 / 0.493 | 0.67 | 2.505 / 1.852 | 0.74 | 0.726 / 0.720 | 0.99 | 2.364 / 1.775 | 0.75 |
| 32 x 512, 8/16 | 64r/k1 | 0.528 / 0.392 | 0.74 | 1.866 / 1.685 | 0.90 | 0.526 / 0.566 | 1.07 | 1.729 / 1.531 | 0.89 |
| real pack, 3 x ~4.25k = 12.9k, 8/16 | 32r/k2 | 0.477 / 0.435 | 0.91 | 1.616 / 1.532 | 0.95 | 0.489 / 0.588 | 1.20 | 1.504 / 1.836 | 1.22 |
| real pack, 7 x ~4.3k = 30.1k, 8/16 | 64r/k1 | 0.875 / 0.618 | 0.71 | 3.174 / 1.979 | 0.62 | 0.845 / 0.675 | 0.80 | 2.959 / 2.096 | 0.71 |
| real pack, 15 x ~4.3k = 64.2k, 8/16 | 64r/k1 | 1.749 / 1.239 | 0.71 | 6.346 / 4.010 | 0.63 | 1.609 / 1.182 | 0.73 | 5.945 / 3.938 | 0.66 |
| 4 x 4096, 16/32 (TP=1) | 64r/k1 | 0.920 / 0.637 | 0.69 | 3.334 / 2.204 | 0.66 | 0.875 / 0.614 | 0.70 | 3.091 / 2.037 | 0.66 |
| 16 x 4096, 16/32 | 64r/k1 | 3.379 / 2.383 | 0.71 | 12.327 / 7.672 | 0.62 | 3.119 / 2.241 | 0.72 | 11.582 / 7.452 | 0.64 |
| 1 x 16384, 16/32 | 32r/k2 | 1.056 / 0.719 | 0.68 | 3.730 / 2.304 | 0.62 | 0.999 / 0.771 | 0.77 | 3.477 / 2.512 | 0.72 |
| real pack 12.9k, 16/32 | 64r/k1 | 0.801 / 0.661 | 0.82 | 2.883 / 2.161 | 0.75 | 0.770 / 0.713 | 0.93 | 2.672 / 2.070 | 0.77 |
| real pack 64.2k, 16/32 | 64r/k1 | 3.374 / 2.415 | 0.72 | 12.241 / 7.793 | 0.64 | 3.106 / 2.280 | 0.73 | 11.515 / 7.650 | 0.66 |
All 30 shapes, both architectures
| case (seqs x len, heads k/v) | T | scan fwd_h/dhu | b200 loom fwd / fla fwd (ms) | b200 fwd x | b200 loom f+b / fla f+b (ms) | b200 f+b x | gb300 loom fwd / fla fwd (ms) | gb300 fwd x | gb300 loom f+b / fla f+b (ms) | gb300 f+b x |
|---|---|---|---|---|---|---|---|---|---|---|
| 4x4096 (16/32) | 16384 | [64, 1]/[64, 1] | 0.920 / 0.637 | 0.69 | 3.334 / 2.204 | 0.66 | 0.875 / 0.614 | 0.70 | 3.091 / 2.037 | 0.66 |
| 8x4096 (16/32) | 32768 | [64, 1]/[64, 1] | 1.755 / 1.219 | 0.69 | 6.428 / 3.941 | 0.61 | 1.608 / 1.160 | 0.72 | 5.959 / 3.820 | 0.64 |
| 16x4096 (16/32) | 65536 | [64, 1]/[64, 1] | 3.379 / 2.383 | 0.71 | 12.327 / 7.672 | 0.62 | 3.119 / 2.241 | 0.72 | 11.582 / 7.452 | 0.64 |
| 1x16384 (16/32) | 16384 | [32, 2]/[32, 2] | 1.056 / 0.719 | 0.68 | 3.730 / 2.304 | 0.62 | 0.999 / 0.771 | 0.77 | 3.477 / 2.512 | 0.72 |
| 32x512 (16/32) | 16384 | [64, 1]/[64, 1] | 0.955 / 0.649 | 0.68 | 3.434 / 2.097 | 0.61 | 0.906 / 0.647 | 0.71 | 3.234 / 2.186 | 0.68 |
| 128x128 (16/32) | 16384 | [64, 1]/[64, 1] | 1.158 / 0.734 | 0.63 | 4.161 / 2.616 | 0.63 | 1.081 / 0.735 | 0.68 | 3.907 / 2.610 | 0.67 |
| 4x4096 (8/16) | 16384 | [32, 2]/[64, 1] | 0.533 / 0.427 | 0.80 | 1.785 / 1.922 | 1.08 | 0.536 / 0.618 | 1.15 | 1.713 / 1.786 | 1.04 |
| 8x4096 (8/16) | 32768 | [64, 1]/[64, 1] | 0.917 / 0.635 | 0.69 | 3.319 / 2.227 | 0.67 | 0.868 / 0.633 | 0.73 | 3.082 / 2.101 | 0.68 |
| 16x4096 (8/16) | 65536 | [64, 1]/[64, 1] | 1.745 / 1.219 | 0.70 | 6.408 / 3.948 | 0.62 | 1.606 / 1.164 | 0.72 | 5.951 / 3.806 | 0.64 |
| 1x16384 (8/16) | 16384 | [32, 2]/[32, 2] | 0.735 / 0.493 | 0.67 | 2.505 / 1.852 | 0.74 | 0.726 / 0.720 | 0.99 | 2.364 / 1.775 | 0.75 |
| 32x512 (8/16) | 16384 | [64, 1]/[64, 1] | 0.528 / 0.392 | 0.74 | 1.866 / 1.685 | 0.90 | 0.526 / 0.566 | 1.07 | 1.729 / 1.531 | 0.89 |
| 128x128 (8/16) | 16384 | [64, 1]/[64, 1] | 0.620 / 0.431 | 0.69 | 2.208 / 1.843 | 0.83 | 0.608 / 0.599 | 0.98 | 2.055 / 1.692 | 0.82 |
| real 16k_0 (16/32) | 12894 | [64, 1]/[64, 1] | 0.801 / 0.661 | 0.82 | 2.883 / 2.161 | 0.75 | 0.770 / 0.713 | 0.93 | 2.672 / 2.070 | 0.77 |
| real 16k_0 (8/16) | 12894 | [32, 2]/[64, 1] | 0.477 / 0.435 | 0.91 | 1.616 / 1.532 | 0.95 | 0.489 / 0.588 | 1.20 | 1.504 / 1.836 | 1.22 |
| real 16k_1 (16/32) | 12894 | [64, 1]/[64, 1] | 0.802 / 0.660 | 0.82 | 2.832 / 1.970 | 0.70 | 0.771 / 0.741 | 0.96 | 2.672 / 2.072 | 0.78 |
| real 16k_1 (8/16) | 12894 | [32, 2]/[64, 1] | 0.476 / 0.436 | 0.92 | 1.566 / 1.502 | 0.96 | 0.483 / 0.594 | 1.23 | 1.504 / 1.772 | 1.18 |
| real 16k_2 (16/32) | 12855 | [64, 1]/[64, 1] | 0.792 / 0.657 | 0.83 | 2.809 / 1.981 | 0.71 | 0.767 / 0.743 | 0.97 | 2.656 / 2.070 | 0.78 |
| real 16k_2 (8/16) | 12855 | [32, 2]/[64, 1] | 0.469 / 0.436 | 0.93 | 1.550 / 1.508 | 0.97 | 0.486 / 0.620 | 1.28 | 1.507 / 1.759 | 1.17 |
| real 32k_0 (16/32) | 30086 | [64, 1]/[64, 1] | 1.678 / 1.196 | 0.71 | 6.081 / 3.886 | 0.64 | 1.549 / 1.139 | 0.74 | 5.554 / 3.862 | 0.70 |
| real 32k_0 (8/16) | 30086 | [64, 1]/[64, 1] | 0.875 / 0.618 | 0.71 | 3.174 / 1.979 | 0.62 | 0.845 / 0.675 | 0.80 | 2.959 / 2.096 | 0.71 |
| real 32k_1 (16/32) | 29852 | [64, 1]/[64, 1] | 1.664 / 1.185 | 0.71 | 6.029 / 3.857 | 0.64 | 1.531 / 1.129 | 0.74 | 5.511 / 3.829 | 0.69 |
| real 32k_1 (8/16) | 29852 | [64, 1]/[64, 1] | 0.870 / 0.612 | 0.70 | 3.105 / 1.956 | 0.63 | 0.827 / 0.650 | 0.79 | 2.923 / 2.078 | 0.71 |
| real 32k_2 (16/32) | 29753 | [64, 1]/[64, 1] | 1.656 / 1.179 | 0.71 | 6.003 / 3.835 | 0.64 | 1.524 / 1.128 | 0.74 | 5.478 / 3.812 | 0.70 |
| real 32k_2 (8/16) | 29753 | [64, 1]/[64, 1] | 0.866 / 0.612 | 0.71 | 3.096 / 1.952 | 0.63 | 0.823 / 0.647 | 0.79 | 2.914 / 2.076 | 0.71 |
| real 64k_0 (16/32) | 64197 | [64, 1]/[64, 1] | 3.374 / 2.415 | 0.72 | 12.241 / 7.793 | 0.64 | 3.106 / 2.280 | 0.73 | 11.515 / 7.650 | 0.66 |
| real 64k_0 (8/16) | 64197 | [64, 1]/[64, 1] | 1.749 / 1.239 | 0.71 | 6.346 / 4.010 | 0.63 | 1.609 / 1.182 | 0.73 | 5.945 / 3.938 | 0.66 |
| real 64k_1 (16/32) | 63747 | [64, 1]/[64, 1] | 3.336 / 2.401 | 0.72 | 12.141 / 7.723 | 0.64 | 3.014 / 2.262 | 0.75 | 11.308 / 7.597 | 0.67 |
| real 64k_1 (8/16) | 63747 | [64, 1]/[64, 1] | 1.735 / 1.230 | 0.71 | 6.298 / 3.961 | 0.63 | 1.593 / 1.174 | 0.74 | 5.898 / 3.921 | 0.66 |
| real 64k_2 (16/32) | 63767 | [64, 1]/[64, 1] | 3.333 / 2.403 | 0.72 | 12.139 / 7.734 | 0.64 | 3.013 / 2.263 | 0.75 | 11.308 / 7.598 | 0.67 |
| real 64k_2 (8/16) | 63767 | [64, 1]/[64, 1] | 1.735 / 1.232 | 0.71 | 6.299 / 3.960 | 0.63 | 1.593 / 1.175 | 0.74 | 5.901 / 3.930 | 0.67 |
Takeaways: the deterministic kernels are at parity with fla only on the small TP=2 shard packs that the e2e actually runs (<= 16k tokens per micro-batch with --max-tokens-per-gpu 16384, 3-4 sequences, r32k2 scan): 0.9-1.3x fwd+bwd. On >= 32k-token packs and on all 16/32-head (TP=1) shapes loom is slower, fwd 0.68-0.77x and fwd+bwd 0.61-0.72x of fla, on both architectures.
Is that the scan-configuration heuristic? No. Forcing each register-scan configuration on the slow shapes (GB300):
T seqs heads auto r32k2 f/fb r32k1 f/fb r64k1 f/fb fla f/fb
32768 8 8/16 (64, 1) 0.942/ 3.385 0.940/ 3.467 0.873/ 3.101 0.548/ 1.718
65536 16 8/16 (64, 1) 1.748/ 6.449 1.745/ 6.562 1.603/ 5.947 0.817/ 3.000
30100 7 8/16 (64, 1) 0.912/ 3.136 0.908/ 3.195 0.837/ 2.941 0.568/ 1.775
16384 4 8/16 (32, 2) 0.537/ 1.789 0.534/ 1.824 0.515/ 1.673 0.504/ 1.565
16384 1 8/16 (32, 2) 0.726/ 2.370 0.788/ 2.578 0.806/ 2.636 0.655/ 1.590
16384 32 8/16 (64, 1) 0.567/ 1.884 0.563/ 1.912 0.532/ 1.736 0.490/ 1.312
32768 8 16/32 (64, 1) 1.759/ 6.481 1.755/ 6.577 1.610/ 5.962 0.796/ 3.028
65536 16 16/32 (64, 1) 3.428/12.812 3.414/12.942 3.114/11.587 1.446/ 5.754
30100 7 16/32 (64, 1) 1.571/ 5.844 1.562/ 5.927 1.542/ 5.529 0.834/ 3.134
16384 4 16/32 (64, 1) 0.942/ 3.396 0.946/ 3.456 0.882/ 3.099 0.580/ 1.738
16384 1 16/32 (32, 2) 1.004/ 3.482 1.076/ 3.701 1.096/ 3.761 0.695/ 2.182
16384 32 16/32 (64, 1) 0.994/ 3.567 0.995/ 3.603 0.907/ 3.235 0.580/ 1.817
choose_register_scan_config already picks the fastest variant on every shape (r64k1 where it is chosen, r32k2 for the single long sequence); the remaining gap to fla is in the kernels themselves (the state-scan and the dhu/dqkwg backward stages scale ~1.6-1.7x worse with tokens than fla's Triton kernels). That is a kernel-side item and is tracked in the kernel generator's issue tracker, not in this PR.
3. End-to-end, warm: Qwen3.5-4B GRPO, TP=2 + SP, 4 GPUs colocated
One live loom run recorded 5 rollouts (8 prompts x 8 samples, 4096-token responses); the same batches were then replayed train-only (--debug-train-only) with the loom backend and with fla, all extensions prebuilt, Triton cache warm from the earlier runs. Medians over rollouts 2-4 (rollouts 0-1 carry module loading / Triton autotuning for both backends):
| metric (median of rollouts 2-4, replay on identical batches) | b200 loom | b200 fla | b200 loom/fla | gb300 loom | gb300 fla | gb300 loom/fla |
|---|---|---|---|---|---|---|
| actor train (s) | 5.91 | 5.58 | 0.94x | 5.98 | 6.75 | 1.13x |
| ref log-probs (s) | 1.50 | 1.44 | 0.96x | 1.90 | 1.91 | 1.00x |
| log-probs (s) | 1.15 | 1.16 | 1.01x | 1.31 | 1.39 | 1.06x |
| step (s) | 22.03 | 21.49 | 0.98x | 12.95 | 13.64 | 1.05x |
| actor train TFLOPS | 263.94 | 279.72 | 0.94x | 260.31 | 231.34 | 1.13x |
| actor train tok/s | 46211.95 | 48974.66 | 0.94x | 45596.57 | 40502.63 | 1.13x |
Per-rollout perf/ref_log_probs_time (s), showing the JIT effect and its removal:
- b200 loom live (record, prebuilt): r0=7.96, r1=8.51, r2=1.77, r3=1.80, r4=1.69; actor_train: r0=15.74, r1=16.79, r2=6.07, r3=5.97, r4=6.01
- b200 loom replay: r0=6.08, r1=2.20, r2=1.45, r3=1.84, r4=1.50; actor_train: r0=15.63, r1=6.77, r2=5.76, r3=6.10, r4=5.91
- b200 fla replay: r0=16.10, r1=2.00, r2=1.80, r3=1.44, r4=1.44; actor_train: r0=67.46, r1=46.27, r2=6.06, r3=5.45, r4=5.58
- gb300 loom live (record, prebuilt): r0=22.56, r1=2.32, r2=2.41, r3=1.87, r4=1.78; actor_train: r0=22.84, r1=6.23, r2=6.26, r3=6.03, r4=6.03
- gb300 loom replay: r0=5.70, r1=2.12, r2=1.72, r3=2.05, r4=1.90; actor_train: r0=13.48, r1=6.15, r2=5.98, r3=5.96, r4=6.14
- gb300 fla replay: r0=17.95, r1=2.23, r2=1.91, r3=2.01, r4=1.90; actor_train: r0=83.16, r1=58.91, r2=6.75, r3=6.83, r4=6.65
At the model level the two backends are within +-13 % of each other (loom 0.94x on B200, 1.13x on GB300 on actor_train_time), and the ref/log-prob passes are identical, consistent with the GDN layers being a small fraction of the 4B model's step and with the operator being at parity on the <= 16k-token micro-batches this recipe produces. fla also pays JIT inside training: its rollouts 0 and 1 in the replay show 67 s / 46 s (B200) and 83 s / 59 s (GB300) actor_train_time from Triton compilation, vs 6 s warm.
4. Gating of the code change
On B200 with eda2814: tests/fast/test_qwen_gdn_backend.py 4 passed (2 pre-existing FlashQLA cases deselected); tests/e2e/precision/test_qwen_gdn_tp_cp_parity.py --backend loom TP2 (both HF layouts) / CP2 / TP2xCP2: 4/4 PASS with the loom bit-determinism assertions; the 5-rollout live loom run and both replays exit 0 with zero backend fallbacks.
resolve-ci-policy rejected the new tests: 'stage-c-4-gpu-b200' is not a CI suite. The only Blackwell suite is stage-c-8-gpu-b200; both tests keep using 4 GPUs (torchrun --nproc_per_node=4 / --actor-num-gpus-per-node 4). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Re-express the loom GDN backend on main's head-sharded linear-attention layer (radixark#3609, radixark#3610, radixark#3634) instead of this branch's own head-sharded GDN module: - `miles_plugins/models/linear_attn.py`: `gdn_kernel("loom")` returns the generated deterministic `chunk_gated_delta_rule`; `gdn_recurrence` allows context parallelism for `loom` (fla's state passing), as it did here. - `--linear-attention-backend loom` joins `LINEAR_ATTENTION_BACKENDS`. - Drop `gdn_attention.py`, `qwen_gdn_backend.py`, `mbridge/gdn_layout.py` and the head-interleaved converter/bridge hooks: main's group-major layout (`megatron_to_hf/linear_attn_layout.py`) is the same row order and its bridges/converters already translate it. This also removes the eager `miles_plugins.mbridge` import that failed the CPU CI shards (no `mbridge` on the GitHub CPU runners). - Tests: the loom routing and fla-vs-loom equivalence/determinism tests join `tests/fast/models/test_linear_attn_kernel_selection.py`; the TP x CP parity test is rebuilt on `tests/fast-gpu/linear_attn_reference.py` (`build_layer(backend=...)`); the Qwen3.5-4B e2e test uses the `command_utils` backend API. - Docs follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…radients are reproducible On main's head-sharded layer the short conv defaults to fla's "mix" backend (Triton forward, causal-conv1d CUDA backward). causal-conv1d's backward accumulates dweight with atomicAdd, so two identical passes under the `loom` backend differed in the last bits of `conv1d.weight.grad` on some ranks (TP x CP parity run on B200) while every other output and gradient reproduced bit for bit. fla's Triton backward stores per-block partials and sums them in a fixed order; the loom layer now pins it unless `FLA_CONV_BACKEND` is set. The parity test names each parameter whose gradient the second pass did not reproduce. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…/qwen-gdn-deterministic-backend Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both tests run on four GPUs (torchrun --nproc_per_node=4; TP=2 + 2 rollout engines), so they take the 4-GPU Blackwell stage and declare the minimum allocation as main's B200 scheduling now expects. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Qwen3.8-27B dense GRPO on this branch (9437f0b)Recipe: Preparation: HF Summary
loom arm (10 rollout steps)
Evaluation (AIME-2024, 30 problems x 16 samples, loom arm;
fla control arm (3 rollout steps)
loom vs fla (same recipe, same machine)
Bit determinism (record one rollout with loom, replay it train-only twice)
Caveats
🤖 Generated with Claude Code |
…on the tape The deterministic GDN plugin rebuilt its chunk table on the device for every layer call (one launch + three allocations, padded launch extent) even though Megatron's packed_seq_params already carries the same boundaries on the host (PackedSeqParamsWithHostCuSeqlens.cu_seqlens_host). The head-sharded layer now passes those host ints down to the loom backend as cu_seqlens_cpu, and the plugin takes the exact table from a per-layout cache shared by all 48 layers and the activation-recompute forward (one pinned, asynchronous host-to-device copy per layout; no device-to-host synchronisation). Not used under context parallelism, where the CP context carries this rank's local boundaries. The forward keeps w/u/h/v_new on the autograd tape by default instead of rerunning the WY and state-scan kernels in the backward (MILES_GDN_CHUNK_TRAIN_SAVE_STATE=0 restores the recompute; both give bit-identical gradients): 9 launches per forward+backward instead of 12. The scan-configuration choice is cached per shape and device, and the launch wrapper validates bindings with one set comparison. Tests (tests/fast-gpu/test_gdn_chunk_train_wiring.py): one table build per layout, host == device table, host-table vs device-table and tape vs recompute results bit-identical, no synchronising / copying CUDA API calls and 9 launches per layer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…led region The input clone that creates the leaves is itself a device-to-device copy (cudaMemcpyAsync) and was counted as a copy of the operator path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Segment tables (host exact / device upper bound with padding), the split policy (pure function of the padded chunk bound, sequence count, head count, device and scan kind; only the preferred register configuration), and the pre-scan -> stitch -> per-segment scan launch sequence for the forward state scan and the backward state-gradient scan. Wiring tests: launches per layer derived from the policy; segment tables host == device incl. padding; segmented vs serial scans within the FLA tolerance; device-table path bit-identical to the host path; repeated segmented passes bit-identical. Needs the regenerated csrc/registry (three more stages) before it can run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the regenerated 19-stage plugin Re-exported from the loom branch at ce0d3cf907f (render-identical to the gate-tested fac5ade759e): the forward and backward state scans gain a pre-scan mode that emits per-segment affine maps, a stitch kernel composes them per sequence (3xBF16 split product, FP32 accumulation) and the scans run over the segment table; three new stages (fwd_h_pre_r32k2, dhu_pre_r32k2, stitch). Host code: segment tables on the host (exact) and device (meta kernel, upper bound) routes, the pure segment-length policy, segmented launches; the loader drops the retired TMA workspace field (thin launchers carry buffers, parameters and the grid only). Wiring tests cover host/device table parity, segmented == serial on unsplit layouts and bit-determinism of the segmented path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ents) + unit tests choose_segment_len now delegates to plan_segments, a bounded search over the segment count with the round-R2 cost model (co-resident CTA factor 1.55, pre-scan step factor 1.16, stitch step = 5 chunk steps, 5 % gain threshold, residency-aware wave model that also splits the backward dhu scan). Verbatim mirror of loom gdn_chunk_training.py at a47025325fd; the wiring test pins the same planner table as loom's unit test (1x8192 HV12 -> L 43 on both scans, 1x16384 -> 86, real 8481+223 -> 46, HV48 -> serial) plus the env override. Kernels, registry and csrc are unchanged (host plan only). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- env-override test no longer calls the Blackwell-only device_arch (pure-function path) - autouse fixture clears MILES_GDN_CHUNK_TRAIN_SEG_CHUNKS for the policy arms - independent argmin recomputation in the planner property test, (68,4) -> 22 row, isolation annotations - calibration comment: GB300 sweep Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…prices seq_lens) Verbatim mirror of loom plan_segments(..., seq_chunks=) / bound_layout_chunks; segment_lens and choose_segment_len take seq_lens and _host_meta passes the exact layout, so balanced packs with two long sequences stay serial on the host route while the device (CP) route keeps the bound model. Pure-function and host-route tests added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dead-SMEM lifetime aliasing, dqkwg/wy_bwd restructure, gate-adaptive warm-up pre-scan + re-fit planner Re-export of the 19 chunk-training stages for sm_100a and sm_103a from loom commit b92b1155795 (kernel sources identical to 8563bcb1b38; csrc + registry manifests: sm_103a aa379e555c412074, sm_100a e493a6800d722c7b; 38 sources each). Host side: the segment planner mirrors the loom planner exactly (gate-adaptive warm-up threshold, re-fit step coefficients), finalize launches on (num_chunks, num_heads, 1), and the wiring test covers the new host scalars. Measured against the previous export on the 8 benchmark shapes (CUPTI device time, same process as the fla reference): forward+backward 17-35 % faster, forward 5-34 % faster on both architectures; 1x8192 TP4, 1x4096 TP4, 1x2048 TP4 and the real single-sequence micro-batch beat fla on device time and wall-clock on both. Determinism: 100-repeat bit-identical on all 12 rows; dk/dg/dbeta differ from the previous export only through the backward restructure, every other tensor bit-equal; synccheck + racecheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ner with (64,1) pre-scans, canonical SMEM vector ops in the register-scan pair reduction Re-export of the 21 chunk-training stages (the 19 round-3 stages plus the (64,1) forward and backward pre-scans) for sm_100a and sm_103a from loom commit ea4ed92124c (42 sources each; manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 6633a6b5fc525d69, sm_103a aa835ca324926569). Host side: the scan planner now chooses the pre-scan configuration ((32,2) or (64,1)) jointly with the segment plan and memoises the result per layout, device and pin state; new pins MILES_GDN_CHUNK_TRAIN_SEG_CHUNKS / MILES_GDN_CHUNK_TRAIN_SCAN_CFG; the wiring test covers the new bindings. The pair reduction inside the register-scan kernels publishes its partials through the shared-memory vector store/load ops instead of a generic pointer store (bit-identical output). Measured against the round-3 export on the 16 benchmark shapes (CUPTI device time, same process as the fla reference): forward+backward up to 7.6 % (sm_103a) / 7.1 % (sm_100a) faster, forward up to 9.2 % / 9.5 %; no shape slower on sm_100a; on sm_103a the smallest shape (1x2048 TP4) is 0.8-0.9 % slower on forward device time in a same-node interleaved A/B while its forward+backward and wall-clock improve. Determinism: 100-repeat bit-identical on all 12 rows on both architectures; the planner's new segment choices change the dk/dv/dg accumulation order on the rows whose plan switched (all within the tolerances), every other tensor bit-equal to round 3. synccheck + racecheck clean on both architectures for the segmented and the (64,1) routes; bench-regression on sm_103a 1.059 ms vs floor 1.90. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gment stitch, one-barrier forward-scan chunk loop, swizzled FP32 shared pipe for the WY build Re-export of the 21 chunk-training stages for sm_100a and sm_103a from loom commit 6281aba9740 (42 sources each; manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 583189cc300be98a, sm_103a 757c07c503cbb3e6). Kernel side (all bit-identical to the round-4 export): the serial segment stitch now runs STITCH_VSPLIT = 4 CTAs per (sequence, head), each owning 32 value columns of the start state with the full K contraction inside the CTA (per launch 17.6 -> 6.9 us on sm_103a, 16.3 -> 7.0 us on sm_100a at 1x8192; SASS 172 -> 98 registers, 1912 -> 992 instructions); the forward register scan stages the next chunk after the chunk barrier and drops the loop-end barrier (one CTA barrier per chunk on the (64,1) scan); the WY build keeps every operation and reduction order but moves its intermediates to swizzled FP32 shared tiles with 128-bit traffic, in-place operand scaling and late v staging (74,240 -> 53,504 B shared memory, 3 -> 4 CTAs per SM, shared wavefronts 0.51x, bank conflicts 0.27x; wy per call -10..-19 %). Host side: the stitch launches use grid z = STITCH_VSPLIT (a launch-site contract shared with the kernel constant; the old (num_seqs, HV, 1) grid would silently compute a quarter of the state). No other host change. Measured against the round-4 export on the 16 benchmark shapes (CUPTI device time, same process as the fla reference): forward up to 10.0 % (sm_103a) / 12.0 % (sm_100a) faster, forward+backward up to 7.4 % / 6.6 %; no shape slower on either architecture; same-node interleaved A/Bs confirm the device-time gains on both architectures (all cells better in both rounds). On the 8192-token TP2 shape sm_103a now beats the fla reference on device time and wall-clock (fwd 0.97x, f+b 0.95x of fla). One open item: on the two-sequence real micro-batch (8481 + 223 tokens) the forward+backward call shows 50-110 us more GPU-idle gap per call on sm_103a in the same-node A/Bs although every kernel is faster, so its host-bound wall reads 4-14 % slower there (still 0.4-0.6x of fla's wall); attribution is scheduled for the next round. Determinism: 100-repeat bit-identical on all 12 rows on both architectures and hash-equal to the round-4 export on every row. synccheck + racecheck clean on both architectures (production policy and forced segmentation, scratch poisoning on); bench-regression on sm_103a 1.086 ms vs floor 1.90. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g/WY pullback on sm_103a, planner wave bound, host-path caches Re-export of the 21 chunk-training stages for sm_100a and sm_103a from loom commit cc2bbfb7d78 (42 sources each; manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 6dab84407e251cfd, sm_103a ee04f16ccb1e4515). sm_100a sources are byte-identical to the round-5 export; the registries carry a new `schedule` field naming the Weave schedule each record was rendered from. Kernel side (sm_103a only): the backward dq/dk/dw/dg GEMM stage (`dqkwg`) and the two WY pullback stages (`wy_bwd`, `wy_bwd_bf16`) are rendered from eight-warp schedules (256 threads, the output columns split across twice the warps at the same shared-memory layout; registers 255 -> 128, local stack 24 -> 64 B / 512 -> 240 B). The exporter selects them per architecture; sm_100a keeps the four-warp kernels. Same-node interleaved A/Bs on sm_103a (two rounds, both arm orders, 40 iters, 10 CUPTI reps): forward+backward device time -2.8 … -5.5 % on every benchmark row (1x8192 TP4 -2.9 %, 1x16384 -3.1 %, real micro-batches -3.2 … -3.8 %, 4x4096 TP1 -3.1 %), forward unchanged. The dg/dbeta reductions change accumulation order on sm_103a: against an fp64 reference the candidate's |error| distribution is within 1.0001x of the previous export's at p99.9 and max with at most one element above the previous maximum, and dq/dk/o are identical; 100-repeat determinism holds on all 12 rows on both architectures. Planner: one-CTA-per-SM scan splits are bounded by sequential waves (MAX_SCAN_WAVES_SERIAL = 4) instead of co-resident rounds, so the two-sequence TP1 and four-sequence TP2 4096-token rows split their state scans into 22-chunk segments (launches 11 -> 13): forward+backward device time -2.4 … -2.8 % on those two rows on both architectures, every other row's plan unchanged; outputs bit-identical to the previous export. Host side: a per-(device, stream) workspace pool keyed by role/shape/dtype, a front cache for the per-call chunk tables (47 hits per 48 layers) and positional kernel launchers bound once per plan replace the per-call empty_like / reshape / view / to chains (per forward+backward call: aten::empty_like 16 -> 0, reshape 12 -> 0, view 12 -> 0, to 4 -> 0; launches unchanged at 13). Host gap per call (CUPTI span minus union) on the real two-sequence micro-batch 252 -> 43 us (sm_103a) and 100 -> 35 us (sm_100a); loop-timed wall on host-bound rows -15 … -27 % (sm_103a), -20 % (sm_100a); forward+backward wall better on every row in both arm orders on sm_103a. Known small effect of the back-to-back launches / pooled buffers on sub-0.2 ms rows: forward device time +0.8 … +1.5 % on 1x4096 (sm_103a) and 1x2048 (sm_100a) with the cache on (unchanged with it off), while their wall improves 12 … 27 %. `GDN_HOST_CACHE=0` disables the caches. Determinism and gates: 100-repeat bit-identical on all 12 rows on both architectures; sm_100a hash-equal to the round-5 export on every row; synccheck + racecheck clean on both architectures (production policy and forced segmentation); bench-regression on sm_103a 1.103 ms vs floor 1.90; 43 plugin tests (27 wiring incl. pool/front-cache/launcher/pass-through tests + 16 kernel-selection tests) pass on both architectures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…resident S tile, finalize grouped MLP, sm_100a per-arch pins Re-export of the chunk-training stages for sm_100a and sm_103a from loom commit e6d722033fb: 22 stages (the round-6 21 plus the host-selected `finalize_mlp`), 44 sources each; manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 19bdfdce9bb32f24, sm_103a ecfb9ebc23369cff. Per architecture the re-rendered kernels are `fwd_o` and `dv_local` (both), the new `finalize_mlp` (both) and the WY pullback pair `wy_bwd` / `wy_bwd_bf16` (sm_100a only, now the eight-warp schedules); every other source is byte-identical to the round-6 export, and the registries carry the new record and the sm_100a WY records' `_w8` schedule names. Kernel side, both architectures: the forward output kernel (`fwd_o`) keeps the gated `S = q k^T` tile in registers -- the m16n8k16 C fragments, masked, scaled and packed pairwise to BF16, are the A fragments of `S @ v_new` -- instead of a 32-store shared-memory round trip, and stages `v_new` late over the dead `kn` tile: 90,368 -> 65,792 B of shared memory, two -> three CTAs per SM, registers 138 -> 168 (sm_103a) / 137 -> 168 (sm_100a) with no spill, STS 33 -> 1. The same lifetime argument writes `dv_local`'s `S^T` tile over `kn` (57,600 -> 49,408 B, three -> four CTAs per SM, 96 -> 128 registers). Same-node interleaved A/Bs against the round-6 export (both orders, worst candidate arm vs best base arm): `fwd_o` -16.6 … -18.1 % and `dv_local` -3.9 … -6.7 % per call on sm_103a, -16.4 … -18.7 % and -3.1 … -6.3 % on sm_100a; forward union -3.4 … -4.5 % (sm_103a) / -3.5 … -4.8 % (sm_100a) and forward+backward union -1.4 … -2.0 % on both on 1x8192 TP4, 1x16384 TP4, 4x4096 TP4, 1x8192 TP1, 4x4096 TP1 and the real micro-batch. Every accumulator keeps its operands, k-block order, scaling point and store mapping: outputs hash-identical on the 12-row determinism set on both architectures. `finalize_mlp` is the backward finalize kernel unrolled for a value-head group of three: eighteen named FP32 fragments receive the partials of a row pair for the three value heads, so every load of the pair is issued before the first accumulate (one exposed HBM round trip per pair instead of three); 72 registers, no shared memory, `__launch_bounds__(128, 4)`. The host selects it from the grid, not from a table of measurements: `finalize_stage(num_chunks, num_heads, num_v_heads, sm_count)` returns `finalize_mlp` when `num_v_heads == 3 * num_heads` and `num_chunks * num_heads <= 4 * sm_count` (the whole grid co-resident in one wave at four CTAs per SM), else the shared `finalize`; with 148 / 160 SMs the 8k-token TP4 rows select it and 1x16384 TP4, 4x4096 TP4 and every TP2 / TP1 row keep `finalize`. The rule is the same text in loom and in `ops.py`; `MILES_GDN_CHUNK_TRAIN_FINALIZE` pins a stage for experiments (a pinned `finalize_mlp` outside its group raises). Accumulation order per row is the loop's, so outputs are bit-identical (rows that select the variant and rows that keep `finalize` alike). Same-node A/Bs (40 iters, both rounds): finalize family 0.0323-0.0333 -> 0.0209-0.0220 ms per call (-34.0 … -35.9 %) on sm_103a and 0.0342-0.0352 -> 0.0219-0.0230 ms (-34.6 … -36.0 %) on sm_100a on the six 8k-token TP4 rows; forward+backward union +2.39 … +2.72 % (sm_103a) / +2.13 … +2.60 % (sm_100a) on each of them in both rounds, +4.67 … +5.28 % / +3.95 … +6.09 % on 1x4096 and 1x2048 TP4, within -0.15 … +0.13 % on the nine rows that keep `finalize`. sm_100a only: the eight-warp WY pullback kernels (`gdn_chunk_bwd_wy_w8`, `gdn_chunk_bwd_wy_bf16_w8`; 256 threads, the same 75,520 B pool, two CTAs per SM) that round 6 shipped on sm_103a are now selected on sm_100a as well through a per-stage target set in the per-architecture tables; `dqkwg` stays four-warp on sm_100a (its eight-warp form is 1.1-3.4 % slower per call there) and the sm_103a sources are unchanged. Register probe on sm_100a: `wy_bwd_bf16` / `wy_bwd` 255 -> 128 registers, 520 -> 240 B stack, LDL/STL 187/144 -> 71/68. sm_100a same-node interleaved A/B against the round-6 export (two rounds of 40 iters, C/Z-1): forward+backward union -2.60 / -2.64 % (1x8192 TP4), -2.61 / -2.66 % (1x16384 TP4), -2.66 / -2.58 % (4x4096 TP4), -2.43 / -2.37 % (1x8192 TP1), -2.51 / -2.47 % (4x4096 TP1), -2.63 / -2.47 % (1x8192 TP2), -2.66 / -2.72 % (8481 + 223 micro-batch), -4.79 / -4.53 % (1x2048 TP4), -1.89 / -1.83 % (five-sequence micro-batch, pre-registered below the bar); forward union within -0.42 … +0.38 % (forward path unchanged); `wy_bwd_bf16` 0.0981 -> 0.0847 ms per call (-13.7 %) at 1x8192 TP4, -11.4 … -12.8 % on the other targets. Numerics on sm_100a (disclosed): the eight-warp WY pullback re-associates the fp32 row sums of `dg` and `dbeta`, exactly as accepted for sm_103a in round 6; `dq` / `dk` / `o` are bit-identical to the four-warp kernels (fp64-reference max |output diff| 0 on all five reference rows). Three-way fp64-reference error distributions (round-6 tree vs round-4 tree: identical on every tensor; candidate vs round-6 tree; candidate vs round-4 tree): `dg` max / mean / p99.9 / p99.99 error ratios 1.0000 on all five rows, no element above the previous maximum, |dg diff| <= 1.43e-6; `dbeta` ratios 1.0000 with 0.015-0.035 % of bf16 outputs moving by one ulp. 100-repeat determinism holds on all 12 rows (production policy and forced segmentation); the sm_100a hashes differ from the round-6 export on `dg` / `dbeta` only and equal the per-architecture change measured alone (no additional numerical effect from the other two changes); sm_103a hashes equal the round-6 export on every tensor and row. Gates on the merged tree: imports, planner tests, compile, e2e, export render, analysis, plugin sync, prebuild, plugin tests and benchmark stages pass on both architectures (the standing 1e-3 dg-vs-reference condition is unchanged from the previous export, identical in the base); synccheck + racecheck clean on sm_103a and sm_100a (production policy and forced segmentation); bench-regression on sm_103a 1.0855 ms vs floor 1.90. 16-row tables vs the round-6 final tables (20 iters, cross-run): sm_103a forward union +1.69 … +4.23 % and forward+backward union +1.45 … +6.01 % better on every row; sm_100a forward union +2.25 … +4.59 % and forward+backward union +3.85 … +10.81 % better on every row. Same-node interleaved A/B on sm_103a, 16 rows, 40 iters, both orders: all 64 union cells faster (forward +2.4 … +4.6 %, forward+backward +1.6 … +6.4 %), launch plans identical; same-node interleaved A/B on sm_100a, 16 rows, 40 iters, both orders: all 64 union cells >= 2 % faster (forward +2.1 … +5.1 %, forward+backward +4.1 … +10.9 %), launch plans identical, the loop-timed wall flags on two host-bound rows unstable across arms and orders (not a reproducible candidate-vs-base effect). Host side: `ops.py` mirrors `finalize_stage` and launches the selected finalize stage (one launch either way; launch counts per call unchanged); the per-architecture pins live in the registries, not in host code. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…vel tiles, prep rows shared across the key-head group Re-export of the chunk-training stages for sm_100a and sm_103a from loom commit ed1d12ef3ad: 22 stages, 44 sources each (unchanged stage set); manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 3e6c2b2e6d04fa42, sm_103a fda8d0de84d77c7a. Per architecture the re-rendered kernels are `wy`, `prep` and `prep_bf16beta`; every other source is byte-identical to the round-7 export and the registries change only those three records. The host side (`ops.py`, `_jit.py`, the wiring test) is byte-identical to round 7. `wy` (both architectures): the three off-diagonal levels of the blocked 64 x 64 triangular inverse ran on 16 lanes of one warp per 16 x 16 block (48 / 32 / 16 of 128 threads), which a single-launch profile showed as the kernel's binding pattern (`barrier` 26-30 % of warp-cycle stalls, issue active 34 %, no SM pipe above a third of peak). The kernel keeps every product, the k order, the fma chain per output element and the conversion points and changes only the thread -> tile map: levels 1-2 one warp per block with 2 x 4 tiles per lane (64-bit `L^T` row pairs, 128-bit `X` column quads), level 3 all 128 threads with 1 x 2 tiles, lane maps conflict-free under the 32-bank model except the two level-3 64-bit stores (2-way, once per level). The in-place `kn <- kn beta 2^g` pass moves onto the idle warps during the diagonal solves (one all-thread pass and one barrier fewer on the solving path) and the `w` / `u` products run as two 32 x 32 passes over a 32-float accumulator (stack 24 -> 0 B, `LDL`/`STL` 3/3 -> 0/0, `LDSM` 80 -> 96; level SASS 3,098 -> 1,300 (sm_103a) / 3,072 -> 1,270 (sm_100a) and kernel 5,600 -> 4,288 (sm_103a) / 5,576 -> 4,304 (sm_100a) instructions per warp). Same-node interleaved A/B against the round-7 export (both orders, 40 iterations x 10 CUPTI repetitions): `wy` per call -16.9 … -19.4 % (sm_103a) / -15.7 … -16.7 % (sm_100a) on the equal TP4 rows (1x8192, 2x4096, 1x16384), -12.3 … -13.3 % on the 19-wave TP1 row; forward union -6.9 … -7.3 % / -6.2 … -6.7 % (1x8192 TP4, 1x16384 TP4), -5.5 … -5.6 % / -5.5 … -5.9 % (4x4096 TP1), -6.4 … -8.8 % on the real micro-batch rows; forward+backward union -1.8 … -3.2 %; every row of the 16-row cross-node gate improves on both architectures. `prep` / `prep_bf16beta` (both architectures): the row normalisation of the 64 q / k rows of a chunk is one dependent load -> fma -> shuffle-reduce -> rsqrt -> store round trip per row and ran on the one CTA per key head with `hv % group == 0` (16 round trips per warp) while its `group - 1` sibling CTAs only scanned their gate. The group's `4 * group` warps now take the rows congruent to their group-wide warp index, so a warp runs `ceil(64 / (4 * group))` round trips: 6 at the production group of 3, 16 at group 1 (the single-CTA schedule). The per-row arithmetic is unchanged and independent of the executing warp; grid, launch arguments and host launchers are unchanged (32 registers, stack 0, no local memory). Same-node A/B against the round-7 export (both orders): `prep_bf16beta` per call -30.7 … -42.6 % on the TP4 rows (sm_103a; sm_100a 17.4 -> 11.5 us at 1x8192 TP4, 15.2 -> 9.3 us at 1x4096 TP4), -12.4 … -15.6 % at 1x16384 TP4, -5.8 … -8.4 % on the TP1 row; forward union -3.8 … -4.1 % / -3.5 … -3.9 % (1x8192 TP4), -3.3 … -3.6 % / -3.5 … -3.8 % (2x4096 TP4), -2.6 … -3.3 % / -3.3 … -3.5 % (8481 + 223 micro-batch), -6.1 … -6.4 % / -5.0 … -6.0 % (1x4096 TP4) on sm_103a / sm_100a; the bandwidth-bound rows move -0.3 … -1.4 %, no row is worse; forward+backward union -1.0 … -2.6 % on those four TP4 rows, -0.1 … -0.5 % on the bandwidth-bound rows. Numerics: both changes are bit-identical by construction (same products, same chain order, same conversion points; the prep rows keep the same code per row and change only the owning warp) and by measurement: 100-repeat determinism holds on all 12 rows and the hashes equal the round-7 export on every tensor and row on sm_103a and sm_100a; the kernel registries show exactly the three re-rendered records. Gates on the merged tree: imports, planner tests, compile, e2e, export render, analysis, plugin sync, prebuild, plugin tests and benchmark stages pass on sm_103a and sm_100a (the standing 1e-3 dg-vs-reference condition is unchanged from the previous export, identical in the base); synccheck + racecheck clean on sm_103a (production target and forced segmentation) and sm_100a (forced segmentation); bench-regression on sm_103a 1.1225 ms vs floor 1.90. 16-row tables vs the round-7 final tables (20 iters, cross-run): sm_100a forward union +6.46 … +14.97 % and forward+backward union +2.30 … +5.85 % better on every row; sm_103a forward union +6.16 … +16.25 % and forward+backward union +2.14 … +7.93 % better on every row. Same-node interleaved A/B, 16 rows, 40 iterations, both orders: sm_103a all 64 union cells faster (forward +5.9 … +16.2 %, forward+backward +1.9 … +7.8 %), one loop-timed wall flag that repeats in both orders, on the host-bound row 1x8192 TP4 (forward+backward wall -2.7 … -6.7 % against a 1.6-3.9 % base/base band while its union is 3.8 % faster with identical launches), plus one single-order wall flag per order not reproduced in the other (base-first: 4x2048 TP4 forward wall -32.8 / -1.8 % against a 19.7 % band; candidate-first: 1x2048 TP4 forward+backward wall -2.1 / -1.5 % against a 1.1 % band); a dedicated single-row rerun of 1x8192 TP4 in both orders does not reproduce its flag (forward+backward wall +0.8 / -4.9 % and +0.0 / -3.3 % against base/base spreads of 8.3 % and 1.1 %, union +3.8 … +4.0 % faster); sm_100a all 64 union cells faster (forward +6.1 … +15.2 %, forward+backward +1.9 … +6.2 %; the candidate-first order has all 32 cells >= 2 % faster and no wall flag, the base-first order flags the loop-timed forward+backward wall of two host-bound short rows, not reproduced in the other order). Launch plans identical to round 7 on every row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Qwen3.8-27B dense GRPO on this branch (9437f0b + updated gdn_chunk_train plugin kernels)Recipe: Preparation: The prepared HF Summary
loom arm (10 rollout steps)
Evaluation (AIME-2024, 30 problems x 16 samples, loom arm;
fla control arm (3 rollout steps)
loom vs fla (same recipe, same machine)
Bit determinism and same-data timing (record three rollouts with loom, replay them train-only twice with loom and once with fla)Per-step timings of the three train-only replays of the same recorded rollouts (seconds;
Caveats
🤖 Generated with Claude Code |
…treamed through per-warp staging Re-export of the chunk-training stages for sm_100a and sm_103a from loom commit f94541bddab: 22 stages, 44 sources each (unchanged stage set); manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a b3078a75cbf0a07e, sm_103a ff331880e1d65b60. Per architecture the only re-rendered kernel is `wy`; every other source is byte-identical to the round-8 export and the registries change only that record. The host side (`ops.py`, `_jit.py`, the wiring test) is byte-identical to round 8. `wy` (both architectures): the w / u epilogues stored each 32 x 32 pass tile as bf16x2 pair stores per lane (one warp-wide store touched eight 128 B lines by 16 B each: 288 store requests and 2,304 store sectors per CTA for 1,280 sectors of data), and a single-launch profile put the L1TEX data pipe at 74-84 % of peak with the barrier / latency regime already removed by round 8. Each warp now parks its pass tile in a 2 KiB per-warp staging slot over the dead upper half of the L^T region (four `stmatrix.x4`, XOR-swizzled chunks, conflict-free for the stmatrix and the 128-bit readback) and streams it out as 16-byte row chunks: 96 store requests and 1,280 store sectors per CTA (`STG` 72 -> 24, `LDS` +15/+16, +144/+160 instructions per warp, stack 0, no local memory). Same bf16 words to the same addresses, same conversions, same products and fma chains: bit-identical output (12 shapes x 100 repetitions hash-equal to the round-8 export on both architectures); compute-sanitizer synccheck + racecheck clean on both; GB300 bench-regression PASS. Same-node interleaved A/B against the round-8 export (both orders, 40 iterations x 10 CUPTI repetitions): `wy` per call -9.6 … -13.2 % (sm_100a) / -11.0 … -13.6 % (sm_103a); forward union -3.4 … -5.5 % on the eight measured rows of both architectures (TP4 1x8192 / 2x4096 / 4x2048 / 1x16384, TP1 1x8192 / 4x4096, two real micro-batches); forward+backward union -0.9 … -1.8 %. Cross-node 16-row tables against the round-8 tables: forward union -0.5 … -5.7 % and forward+backward union -0.4 … -2.0 % on every row of both architectures; the per-shape Goal criterion (CUPTI union and wall both <= fla) holds on the same 6 of 16 shapes per architecture as in round 8. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…try path scoped to the 64-row scan Re-export of the chunk-training stages for sm_100a and sm_103a from loom commit cf309778cc9: 25 stages, 50 sources each (the round-9 set of 22 plus the three split-scan stages fwd_h_r32k2_split / fwd_h_r32k1_split / fwd_h_r64k1_split, which the planner selects on segmented rows); manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 93adbf76ae7f8a93, sm_103a ead71cc0e634b74b. Every one of the 22 round-9 sources is byte-identical to the round-9 export and the registries only gain the three new records. The host side (`ops.py`, `_jit.py`, the wiring test) is byte-identical to round 9. Split scans (both architectures): the previous cut of the split entry path removed the ~1 us per-call entry penalty of `fwd_h_r64k1_split` but compiled `fwd_h_r32k2_split` to a slower chunk-loop schedule (+6.5 % per call on sm_100a). The early first-chunk copies (start-state read before the flag walk, drain in the exact chain, re-issue flag) are now scoped to the 64-row split scan through a build-time bool bound outside the body fragment; the two 32-row split scans trace the base statement order again. `fwd_h_r64k1_split` reads <= `fwd_h_r64k1` per call on both architectures; `fwd_h_r32k2_split` equals `fwd_h_r32k2` on sm_103a and reads +2.4 .. +3.6 % per call on sm_100a (rule <= +4.4 %); the stitch stage per call -47 .. -50 % (forward launch folded into pass 2). Same bf16 words to the same addresses, same conversions and fma chains: bit-identical output (12 shapes x 100 repetitions hash-equal to the round-8/9 exports on both architectures); compute-sanitizer synccheck + racecheck clean on both; GB300 bench-regression PASS. Same-node interleaved A/B (both orders, two rounds each, 40 iterations x 10 CUPTI repetitions) on the three segmented rows: forward union +7.4 .. +8.3 % (1x4096 TP4), +5.7 .. +7.3 % (1x2048 TP4), +2.9 .. +3.4 % (2x4096 TP4) on both architectures; forward+backward union +2.5 .. +3.2 % / +2.2 .. +2.8 % / +0.7 .. +1.5 %. Cross-node 16-row tables against the round-9 tables: forward union -0.0 .. -7.5 % (sm_103a) and -0.2 .. -8.8 % (sm_100a) on every row; forward+backward union within -0.4 .. +2.7 % (sm_103a) and -0.1 .. +3.3 % (sm_100a); the per-shape Goal criterion (CUPTI union and wall both <= fla) holds on the same 6 of 16 shapes per architecture as in round 9. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pullback on a padded-pitch pool with stmatrix.x4 tiles and packed f32x2 epilogues Re-export of the chunk-training stages for sm_100a and sm_103a from loom commit 1852a7e39eb: 25 stages, 50 sources each; manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a 01766a8e96c20290, sm_103a 6cfa5e466b2eeed4. Changed against the round-10 export: the two eight-warp WY pullback stages wy_bwd / wy_bwd_bf16 on both architectures, plus dqkwg on sm_100a (the round-10 four-warp first-wave split + packed pair loads, measured neutral-to-positive there); every other source is byte-identical to round 10 and the registries only swap those records. The host side (`ops.py`, `_jit.py`, the wiring test) and `cake_host_shim.h` are byte-identical to round 10. Eight-warp WY pullback (wy_bwd_bf16, wy_bwd): the kernels get their own shared-memory pool with a padded 144-byte row pitch per 64-column panel instead of the four-warp kernel's SW128 pool, so every ldmatrix / stmatrix / pair-load step is a compile-time immediate and the long-lived address words that spilled at the 128-register cap are gone (stack 240 -> 32 B, LDL/STL 71/68 -> 7/7; pool 75,520 -> 84,736 B, still two CTAs per SM); the four BF16 operand tiles that chain the GEMMs leave the accumulators through one stmatrix.x4 per 16 x 16 sub-tile after cvt.rn.bf16x2.f32 (64 scalar st.shared.b16 -> 8 stores per lane); the independent (col, col + 1) element pairs of the epilogues use mul/fma/add.rn.f32x2 without .ftz (per component the RN rounding of the scalar op; serial dbeta/dg chains and compiler-contracted products stay scalar). Static instructions 2,240 -> 1,560 (sm_100a) / 2,224 -> 1,544 (sm_103a); HMMA / LDSM / BAR unchanged. Same bf16 words to the same addresses, same fma chains: bit-identical output (12 shapes x 100 repetitions hash-equal to the round-8/9/10 exports on both architectures); compute-sanitizer synccheck + racecheck clean on both; GB300 bench-regression PASS (1.0488 ms per kernel, floor 1.9 ms). Same-node interleaved A/B against the round-10 tree (both orders, two rounds each, 40 iterations x 10 CUPTI repetitions) on the four pre-registered rows: forward+backward union +3.4 .. +3.7 % (sm_100a) and +2.8 .. +3.0 % (sm_103a), of which +3.4 .. +3.5 % / +2.7 .. +2.9 % attributed to the changed kernels; wy_bwd_bf16 per call +11.5 .. +11.9 % (sm_100a) / +13.4 .. +14.2 % (sm_103a); forward union within +/-0.3 %. Cross-node 16-row tables against the round-10 tables: forward+backward union +1.9 .. +3.3 % (sm_100a) and +2.6 .. +3.4 % (sm_103a) on every row; forward union within -1.3 .. +0.9 % (forward kernels unchanged); the per-shape Goal criterion (CUPTI union and wall both <= fla) holds on the same 6 of 16 shapes per architecture as in round 10. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a padded-pitch pool with trace-time address folding and a line-aligned cp.async plan Re-export of the chunk-training stages for sm_100a and sm_103a from loom commit 9a9653333cc: 25 stages, 50 sources each; manifest = sha256 of the sorted per-file SHA-256 list of registry + csrc: sm_100a f1b80fd4e31f9d80, sm_103a f5beeb7fba5c3392. Changed against the round-11 export: the forward WY stage on both architectures (sm_100a 08c9000f31081b7dd011 -> d4714aedb34ce4ecd575, sm_103a b9d8c2d470993759748a -> ef0e42dd5e7c8f4ea7b0); every other source is byte-identical to round 11 and the registries only swap those two records. The host side (`ops.py`, `_jit.py`, the wiring test) and `cake_host_shim.h` are byte-identical. Forward WY (gdn_chunk_wy): the SW128 / swizzle address terms are folded at trace time; the BF16 operand tiles sit on a padded pitch (272 B, 144 B for the A tile; dynamic pool 55,552 B, four CTAs per SM kept) so every ldmatrix / cp.async step is a compile-time immediate; the ten lower L blocks are spread 3 / 3 / 2 / 2 over the four warps; the three kn / v cp.async copies use a line-aligned thread -> (row, chunk) plan on the 272-B pitch. Static instructions 4,448 -> 4,200 (sm_100a) / 4,216 (sm_103a), integer class 1,439 -> 1,087 / 1,435 -> 1,101; HMMA / LDSM / MUFU counts unchanged; 128 registers, no stack, no local loads or stores. Same bytes to the same shared addresses before the same wait and barrier, same fma chains: bit-identical output (12 shapes x 100 repetitions hash-equal to the round-11 export on both architectures); compute-sanitizer synccheck + racecheck clean on both; GB300 bench-regression PASS (1.0479 ms per kernel, floor 1.9 ms). Same-node interleaved A/B against the round-11 tree (both orders, two rounds each) on the pre-registered row equal_1x4096_TP4: forward union +4.9 .. +5.5 % (sm_100a) and +5.1 .. +5.8 % (sm_103a), WY per call +13.7 .. +15.9 %; the smaller 1x2048 row moves +0.7 .. +1.2 % (below the bar, non-binding). Cross-node 16-row tables against the round-11 tables: no cell more than 1 % worse (worst -0.46 % forward+backward on sm_103a, -0.26 % forward on sm_100a); equal_1x4096_TP4 forward +4.50 % (sm_100a) / +3.87 % (sm_103a), forward+backward +1.09 % / +1.32 %; the per-shape Goal criterion holds on the same shapes as round 11. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Phase-2 kernels pushed: round-13 GDN chunk kernels (head 40ea59b)The branch now carries the phase-2 kernel work: 17 commits on top of 9437f0b, all under What the commits add
Rounds 12, 14 and 15 kept no candidate (every candidate stayed under the 2 % bar or was closed by the static census), and the optimization loop was closed by its stopping rule (two consecutive rounds without a ≥ 2 % best-row improvement). Validation of the round-13 kernels (the ones on the branch now)
Not re-run, and still blocked
|
…spatch) The round-10 kernels (loom cf309778cc9) export a split-plan instantiation ``fwd_h_r*_split`` of every forward scan stage: its prologue derives the segment's FP32 start state from the pre-scan maps and flags, so a segmented forward plan launches pre-scan + split scan and no forward stitch (the backward keeps its stitch). The round-10..13 kernel commits shipped the csrc and registries but not this host-side dispatch, so the branch still launched pre-scan + stitch + plain scan on segmented forward plans and never ran the split stages. This is the host code every kernel gate since round 10 validated together with these kernels (wiring tests, determinism, A/B). - ``_split_scan_stage`` resolves ``<stage>_split`` from the registry; ``_FWD_H_SPLIT_ARGS`` = the plain plan + ``seg_flag``; the pre-scan plan adds ``warmup_thresh_log2`` on top. - The wiring test counts one launch less per segmented forward scan and asserts the split stage's plan against the plain stage's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Follow-up to the push above: 0993335 adds the round-10 host-side dispatch that the kernel commits had left out. The round-10 kernels export a split-plan instantiation I am re-running the Qwen3.8-27B end-to-end recipe (loom 10 steps + eval, fla 3 steps, same-data record/replay) on this head and will post the numbers here when it finishes. |
Qwen3.8-27B dense GRPO re-run with the round-13 kernels (head 0993335)Same recipe, node type (8 x B200, TP4), prepared checkpoint and datasets as the 2026-10-11 comment; the only change is the plugin on the branch head: round-13 Summary
loom arm (10 rollout steps)
Evaluation (AIME-2024, 30 problems x 16 samples; eval 0 = initial weights, eval 9 = after all 10 steps):
fla control arm (3 rollout steps)
loom vs fla (same recipe, same machine)
Bit determinism and same-data timing (record three rollouts with loom, replay them train-only twice with loom and once with fla)Per-step timings of the three train-only replays of the same recorded rollouts (seconds;
Caveats
🤖 Generated with Claude Code |
Summary
A bit-deterministic
chunk_gated_delta_rulebackend for the Qwen GDN models (Qwen3.5 / 3.6 / 3.8, Qwen3-Next), selected with--linear-attention-backend loom, on the head-sharded linear-attention layer main gained in #3609 / #3610 / #3634.--linear-attention-backend loom: deterministic forward + backward forchunk_gated_delta_rule(chunk 64,K = V = 128, in-kernel q/k L2 norm,cu_seqlenspacking, fla CP state passing) as generated CUDA kernels inmiles_plugins/models/gdn_chunk_train/— register-MMA kernels with an fp32 running state across chunks (the per-chunkhtensor is bf16 as in FLA), bf16 intermediates where FLA stores bf16 and reductions without device atomics, so repeated passes on identical inputs are bit-identical (asserted by the tests below). Built as torch CUDA extensions (torch.utils.cpp_extension.load, cached underTORCH_EXTENSIONS_DIR); by default all stages compile on the first call (MILES_GDN_CHUNK_TRAIN_PREBUILD=0for lazy per-stage builds). NVIDIA SM100a / SM103a (Blackwell).miles_plugins/models/linear_attn.py:gdn_kernel("loom")returns it, andgdn_recurrenceadmits context parallelism forloomas it does forfla(the kernels reuse fla's CP state passing). Grouped value heads go to the kernel directly, as main already does for fla. Underloomthe short conv runs on fla's Triton kernels (loom_conv_backend(),FLA_CONV_BACKENDoverrides) instead of main'smix, whose causal-conv1d CUDA backward accumulatesdweightwith atomics and does not reproduceconv1d.weight's gradient bit for bit.miles/backends/megatron_utils/megatron_to_hf/linear_attn_layout.py;in_proj_qkvfor Qwen3.5,in_proj_qkvzfor Qwen3-Next) is used as is.History. The branch originally carried its own head-sharded GDN module (
miles_plugins/models/gdn_attention.py, head-interleavedin_proj_qkv/conv1drows, bridge and converter hooks). Merging main (6a849af; c5d7db9 then picks up main's B200 CI scheduling change dba9001, and 9437f0b moves the two B200 tests fromstage-c-8-gpu-b200tostage-c-4-gpu-b200withnum_gpus=4) removed it: main's head-sharded layer stores the same row order, so only the backend remains. That merge also deletesmiles_plugins/mbridge/gdn_layout.pyand its import from the bridges; importing it executedmiles_plugins/mbridge/__init__.py, which imports every bridge and the realmbridgepackage, and that is what failed the twostage-a-cpushards of the pre-merge CI run (one test in each;mbridgeis not installed on the GitHub CPU runners). The evidence below marked pre-merge was measured on the earlier branch states and is kept for the record.Tests
tests/fast/models/test_linear_attn_kernel_selection.py: kernel-selection tests on CPU (loomrouting, unknown-backend error, the KDA / short-conv backend defaults); fla vs loom parity (atol = rtol = 1e-2;dg's atol scaled by its reference max-abs) and bit determinism of the output and all five input gradients on packed varlen and equal-length batches, with one and two value heads per key head (needs an SM100a / SM103a GPU with fla; skips elsewhere).tests/e2e/precision/test_qwen_gdn_tp_cp_parity.py(torchrun,stage-c-4-gpu-b200,num_gpus=4): the head-sharded layer with the loom backend under TP=2 and TP=2 × CP=2 (both HF projection layouts) and CP=2 (qwen3_next layout), 5 cases, against the replicated fla reference oftests/fast-gpu/linear_attn_reference.py— output, input gradient and every parameter gradient (TP-sharded ones gathered across TP, all summed across CP) — plus bit determinism of a second identical pass.--backend flaruns the same five parity cases as a control (the second-pass determinism check is loom-only).tests/e2e/megatron/test_qwen3_5_4B_gdn_loom.py(stage-c-4-gpu-b200,num_gpus=4): Qwen3.5-4B full-parameter GRPO, TP=2 + SP, loom backend (MILES_GDN_BACKEND=flafor the A/B reference;record/replaymodes for training-side determinism checks).Evidence after merging main (trees 6a849af, 7020a92, 9437f0b; GPU gates on 4x NVIDIA B200,
radixark/miles:dev)All runs used the
radixark/miles:devcontainer (torch 2.13.0+cu130, fla 0.5.2, as printed by the 6a849af gate step; the later steps ran the same image). The GPU gates ran on single 4x NVIDIA B200 nodes (the generated kernels build for sm_100a); all post-merge evidence is from B200, the SM103a (GB300) build was not re-run after the merge, and the GB300 results in the pre-merge section are from the earlier branch states only. The merged tree was measured at two states: the merge commit 6a849af and the head with the short-conv fix 7020a92. The final head 9437f0b adds main's B200 CI-scheduling merge c5d7db9 (tests/ci, .github workflows, docs/developer, .claude/rules and one-lineregister_cuda_ci(..., num_gpus=N)edits in existing test files; no change undermiles/ormiles_plugins/) and moves the two loom tests tostage-c-4-gpu-b200withnum_gpus=4. No GPU gate was re-run on 9437f0b; on that head only the CI registry check,tests/fast/modelson CPU (51 passed, 8 skipped), pre-commit and the CPU suites below ran. At the time of writing, GitHub CI for 9437f0b is held for approval (the repository approves fork runs through arun-ci*label); the last GitHub-executed CI is from the pre-merge head 48d3a00, and the CPU-suite row is a cluster-side proxy for it, not a substitute.tests/fast/models/test_linear_attn_kernel_selection.py(the 2 fla-vs-flashqla cases deselected)test_loom_short_conv_defaults_to_triton_unless_set)test_qwen_gdn_tp_cp_parity.py --backend fla(control)test_qwen_gdn_tp_cp_parity.py(loom; TP2 qwen3_5 / qwen3_next, CP2 qwen3_next, TP2xCP2 both families)dx5.02e-3); a second pass did not reproduce one parameter gradient bit for bit in the two TP=2 CP=1 cases (qwen3_5 on ranks 1 and 3, qwen3_next on ranks 1 and 2), while outputs and input gradients reproduced on every rank. Cause: main'sshort_conv_backend()selects fla's "mix" backend (Triton forward, causal-conv1d CUDA backward, which accumulatesdweightwithatomicAddincsrc/causal_conv1d_bwd.cu) whenever causal-conv1d imports andFLA_CONV_BACKENDis unset, as in this image; the diagnostic row below pins the gap to that backwarddx4.94e-3; every rank within the 2e-2 tolerance)FLA_CONV_BACKEND=mix(diagnostic, pre-fix behaviour)conv1d.weight's gradient alone (max abs gap 7.8e-3 on qwen3_next TP2, 1.2e-4 on qwen3_5 TP2): consistent with the mixed short-conv backward being the sole source of the pre-fix nondeterminism (the 6a849af run reported exactly one unreproduced parameter gradient per failing case, the fixed tree reproduces every gradient with fla's Triton conv backward, and forcing the mixed backend brings the gap back in that one parameter)tests/fast-gpu/test_linear_attn_head_sharded.py(main's own test, 2 ranks)tests/fast-gpu/test_linear_attn_layer.py(main's own test, 2 ranks)tests/e2e/megatron/test_qwen3_5_4B_gdn_loom.py, live loom run, 2 rollouts (fresh torch_dist conversion with the merged converter)linear_attention_backend = loomin the Megatron args;check_weight_update_equalon (via--ci-test,visual.*skipped): the SGLang-side WeightChecker ran its snapshot / reset / compare cycle on both engines after the weight update without a mismatch; per step (0-3):train_rollout_logprob_abs_diff0.0083 / 0.0096 / 0.0084 / 0.0072,train_rollout_kl3.6e-4 / 4.2e-4 / 3.7e-4 / 3.1e-4 (same level as the pre-merge table below); the only error text in the log is SGLang's non-fatalpost-warmup freeze_gc failedself-call at server start (also present in the record run); no fallback messagesrecordrun of the determinism round below; reuses the torch_dist checkpoint converted in the 6a849af gate)train_rollout_logprob_abs_diff0.0084 / 0.0096 / 0.0087 / 0.0073,train_rollout_kl3.6e-4 / 4.2e-4 / 3.8e-4 / 3.2e-4; grad norm 0 / 0.416 / 0.370 / 0 (non-zero in one of the two optimizer steps of each rollout;rollout/raw_reward0.016 / 0.109; the logs carry no per-step advantage statistic)--debug-train-onlyreplays of them with loom (separate Ray jobs): per-step grad norm BIT-IDENTICAL over the 4 optimizer steps (max abs diff 0; two of the steps have grad norm 0, the other two 0.416 and 0.370), train data of the saving ranks (TP-rank 0 of each DP group, ranks 0 and 2) BIT-IDENTICAL (torch.equal; 900 tensors equal, 0 differ over 4 rollout x rank files)stage-a-cpux 4 shards +stage-b-cpu,mbridgemasked (GitHub CPU-runner proxy) and presenttransformer_engine, which dlopenslibcudaat import, so a CPU-only node cannot collect the suites). Per shard-pass FAILED sets: 4 identical (masked a0 8 = 8, masked a2 2 = 2, plain a3 1 = 1, plain b 0 = 0), 5 differ by exactly one test id (masked a1, masked a3, plain a1, plain a2: one extra on this tree; masked b: one extra on origin/main), plain a0 see below; ERROR sets identical except masked a1 (119 extra ERRORs on this tree, alltests/fast/chartstests). The failures shared by both trees are the 7tests/ci/test/test_build_wheels.pycases, the real-Ray..._with_their_gpu_sliceplacement test (an artifact of hiding the GPUs),test_sglang_anthropic_conversion_contract,test_workplace_launch_uses_the_configured_backend, the dual-stack socket test, the secret-flag classification test, the 4test_native_indexer_export_preserves_projected_rope_and_scores[*-mbridge]cases andtest_the_bridge_opts_out_of_the_group_major_namesunder the mbridge mask, plus thetest_kimi_k3_mbridge.py/test_fp32_param_utils.py/test_mbridge_indexer_importcollection errors under the mask. The 5 differing test ids (router session teststest_full_model_result_reaches_render_backend_and_snapshot[v1]ConnectionError andtest_a_new_root_may_choose_again[v1]HTTP 502; helm chart teststest_grants_nothing_to_a_release_no_platform_client_lives_inandtest_the_shared_helpers_render_the_helm_values, which share the chart directory with the 119helm dependency buildERRORs produced while the masked and present passes ran concurrently in one tree; the origin/main-onlytest_sampling_params_passed_through[old_sglang_rollout]httpx.ReadError) all pass on this tree when the four affected files run alone, masked and present: 504 passed, 10 skipped, 1 failed in each pass, the one failure being the GPU-slice test above. Plain-pass shard a0 of this tree was re-run alone after its first pass hung intest_subproc_agent_metadata_reaches_reward_model[subproc]while the two passes shared the node: 8 failed, 4972 passed, 19 skipped, exactly origin/main's plain a0 FAILED set (8 failed, 4970 passed, 19 skipped): the 7test_build_wheels.pycases (jq: command not foundin the image) and the GPU-slice test; the hung test passed.pre-commit run --fileson the 11 Python / Markdown files of the change (the generatedcsrc/sources andregistry_sm_*.jsonwere not passed)Evidence, pre-merge branch states (own head-sharded module)
All runs used the
radixark/miles:devimage (torch 2.13.0+cu130, fla 0.5.2, TE 2.17.0), 4 GPUs, on NVIDIA B200 (sm_100a) and NVIDIA GB300 (sm_103a).Unit / operator / TP x CP (branch state 96b09d6) (pre-merge)
tests/fast/models/test_gdn_head_sharded_layout.py+tests/fast/test_qwen_gdn_backend.py+tests/fast/backends/megatron_utils/test_fp32_param_utils.pytest_qwen_gdn_tp_cp_parity.py --backend loom(TP2 qwen3_5, TP2 qwen3_next, CP2 qwen3_next, TP2xCP2 qwen3_next; bit-determinism asserted; 4 cases — the merge's rewrite of the test added the fifth case, TP2xCP2 qwen3_5)test_qwen_gdn_tp_cp_parity.py --backend flatest_qwen3_5_cp_correctness.pyCP=2 / CP=4 (existing test on the refactored module)Largest relative deviation of the TP-sharded core vs the replicated single-rank fla reference over output, input gradient and every parameter gradient: 1.04e-2 (
dhidden, CP=2), everything else <= 9.3e-3, tolerance 2.5e-2. The fla-backend TP2 runs reproduce the reference weight gradients exactly (max abs 0) where no CP reduction is involved.Qwen3.5-4B end-to-end (
tests/e2e/megatron/test_qwen3_5_4B_gdn_loom.py, TP=2 + SP, 2 rollouts, branch state 4434cc8) (pre-merge)--check-weight-update-equal(LM tensors incl. head-interleavedlinear_attn.*)train/train_rollout_logprob_abs_diff(steps 0-3)train/train_rollout_kl(steps 0-3)Trainer-vs-inference log-prob agreement is the same with the deterministic backend as with fla. Per-step losses / grad norms of the live runs are not comparable across runs because the sampled rollouts differ (with 8 prompts x 8 samples at 4096 tokens most groups have zero advantage), which is what the replay round below is for.
Training-side bit determinism (record / replay, branch state a6d7601) (pre-merge)
One live loom run per machine (
MILES_E2E_MODE=record, 2 rollouts) dumped its rollout batches; the same batches were then trained on with--debug-train-onlytwice with the loom backend and twice with fla (MILES_E2E_MODE=replay), saving the per-step grad norm and each rank's post-forward train data (log-probs, ref log-probs, advantages, ...). All five stages exit 0 on both machines.The loom backend reproduces every gradient norm and every trainer log-prob bit for bit across independent processes on both architectures. On this TP=2 / SP recipe fla 0.5.2 also replayed bit-identically, so the two backends are compared on equal footing here: their per-token log-probs differ at bf16 level (256 of 900 tensors, the log-prob / ref-log-prob vectors, largest single-token difference 0.88 on B200 and 0.92 on GB300 over 4096-token sequences of a 32-layer model), while both agree with the SGLang inference log-probs to the same degree (
train_rollout_logprob_abs_diff~0.008 in the live runs above).🤖 Generated with Claude Code