Skip to content

Add Qwen3, Qwen3-Embedding, Qwen3-Reranker and Qwen3-MoE on iron.lm - #251

Open
hunhoffe wants to merge 21 commits into
develfrom
qwen3-family
Open

hunhoffe wants to merge 21 commits into
develfrom
qwen3-family

Conversation

@hunhoffe

Copy link
Copy Markdown
Collaborator

Stacked on #249. a0162c7 kept #222 to the library, Llama, EmbeddingGemma 2 and Gemma 4, moving eighteen applications to follow-up pull requests. This is the Qwen3 family's: qwen3_embedding and qwen3_reranker build directly on qwen3's Qwen3 class, and qwen3_moe on its Qwen3Layer, so the four travel together.

  • Qwen3 1.7B/0.6B, Qwen3-Embedding 0.6B and Qwen3-Reranker 0.6B restored unchanged from before a0162c7, with their tests and toy goldens.
  • Qwen3-MoE (30B-A3B, reference only) needs two operators a later cleanup commit (0f0a540) also moved out: restored iron/operators/router.py (TopKRouter) and experts.py (Experts), re-exported from iron/operators/__init__.py.
  • Restored TopKRouter's two rejected-shape tests in test_rejected_shapes.py. Recreated test_core_streams.py scoped to Experts alone; 0f0a540's version also covered SelectiveScan and GatedDeltaRule, which belong to lfm2/qwen3_5's own follow-up.
  • README's model table gets its four rows back, folded in beside Granite 4.2 3B and Granite 4.0-H 1B #249's Granite rows.

PR Merge Checklist

  1. The PR is rebased on the latest devel commit and pointing to devel.
  2. Your PR has been reviewed and approved.
  3. All checks are passing.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

CI performance trends

8ae0479 (2026-10-28T21:54:34Z)

Krackan - Operators

Operators added: Experts, SelectiveScan, TopKRouter, core_streams, generation

Benchmarks that moved by at least 5% and by more than 2x the spread the runs measured:

Operator Parametrization Metric Previous Current Change
AXPY npu2-AXPY-size_8388608-num_aie_columns_8-tile_size_4096-scalar_factor_3.0 Latency MAD 2.36 19.40 🔴 +720.60%
Clamp npu2-Clamp-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096-low_-0.75-high_1.25 Latency MAD 1.14 11.69 🔴 +927.91%
Copy npu2-Copy-bench_flat_4mi Latency MAD 3.32 5.39 🔴 +62.14%
DepthwiseConv1d npu2-DepthwiseConv1d-rows_768-channels_1024-num_aie_columns_8 Bandwidth 50.16 45.28 🔴 -9.74%
DepthwiseConv1d npu2-DepthwiseConv1d-rows_768-channels_1024-num_aie_columns_8 Latency 62.91 69.70 🔴 +10.79%
DepthwiseConv1d npu2-DepthwiseConv1d-rows_768-channels_1024-num_aie_columns_8 Latency MAD 1.37 1.57 🔴 +14.57%
DepthwiseConv1d npu2-DepthwiseConv1d-rows_768-channels_1024-num_aie_columns_8 Throughput 125.00 112.83 🔴 -9.74%
ElementwiseAdd npu2-ElementwiseAdd-size_8388608-num_aie_columns_8-tile_size_4096 Latency MAD 2.65 2.32 🟢 -12.30%
ElementwiseMul npu2-ElementwiseMul-size_8388608-num_aie_columns_8-tile_size_4096 Latency MAD 2.66 5.21 🔴 +96.23%
Emit npu2-Emit-eight_carried Bandwidth 0.19 0.11 🔴 -40.63%
Emit npu2-Emit-eight_carried Latency 10.36 17.45 🔴 +68.43%
Emit npu2-Emit-eight_carried Latency MAD 1.11 3.80 🔴 +241.50%
GELU npu2-GELU-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 0.92 5.56 🔴 +503.09%
GEMM npu2-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Bandwidth 8.93 12.75 🟢 +42.81%
GEMM npu2-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Latency 2818.70 1973.77 🟢 -29.98%
GEMM npu2-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Latency MAD 14.29 1.55 🟢 -89.14%
GEMM npu2-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Throughput 6094.96 8704.08 🟢 +42.81%
GEMV npu2-GEMV-M_1536-K_12288-num_aie_columns_8-tile_size_input_1-tile_size_output_64 Latency MAD 0.92 16.67 🔴 +1716.18%
GEMV npu2-GEMV-M_2048-K_8192-num_aie_columns_8-tile_size_input_1-tile_size_output_256 Latency MAD 3.46 1.96 🟢 -43.41%
GEMV npu2-GEMV-M_8192-K_2048-num_aie_columns_8-tile_size_input_4-tile_size_output_1024 Latency MAD 2.21 2.97 🔴 +33.97%
GEMV npu2-GEMV-M_8192-K_2048-num_aie_columns_8-tile_size_input_4-tile_size_output_512-num_channels_2 Latency MAD 1.10 2.26 🔴 +106.29%
LayerNorm npu2-LayerNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096-weighted_True Latency MAD 1.18 10.13 🔴 +757.40%
LayerNorm npu2-LayerNorm-rows_2048-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 2.15 14.88 🔴 +590.81%
Limbs npu2-Limbs-rows_4096-line_320-num_aie_columns_8 Latency MAD 4.12 4.53 🔴 +9.99%
Log npu2-Log-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Bandwidth 14.71 13.77 🔴 -6.38%
Log npu2-Log-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Latency 3420.85 3654.04 🔴 +6.82%
Log npu2-Log-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 7.12 299.38 🔴 +4102.36%
Log npu2-Log-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Throughput 2.45 2.30 🔴 -6.38%
MHA npu2-MHA-num_heads_1-seq_len_16384-num_pipelines_8 Latency 30378.13 31933.68 🔴 +5.12%
MHA npu2-MHA-num_heads_1-seq_len_16384-num_pipelines_8 Latency MAD 14.89 72.29 🔴 +385.45%
Magnitude npu2-Magnitude-rows_4096-width_640-num_aie_columns_8 Latency MAD 4.76 9.14 🔴 +92.07%
Merge npu2-Merge-rows_2048-placeholders_((258881, 2048), (258880, 2560)) Bandwidth 0.17 0.18 🟢 +6.34%
Merge npu2-Merge-rows_2048-placeholders_((258881, 2048), (258880, 2560)) Latency 97.76 91.93 🟢 -5.96%
Merge npu2-Merge-rows_2048-placeholders_((258881, 2048), (258880, 2560)) Latency MAD 2.42 4.08 🔴 +68.14%
PatchPositions npu2-PatchPositions-rows_2560-out_height_672-out_width_912 Bandwidth 0.77 0.70 🔴 -8.95%
PatchPositions npu2-PatchPositions-rows_2560-out_height_672-out_width_912 Latency 66.39 72.91 🔴 +9.83%
PatchPositions npu2-PatchPositions-rows_2560-out_height_672-out_width_912 Latency MAD 1.51 1.07 🟢 -29.25%
RMSNorm npu2-RMSNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096-weighted_True Latency MAD 3.25 7.40 🔴 +127.75%
RMSNorm npu2-RMSNorm-rows_2048-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 2.79 5.92 🔴 +112.04%
ReLU npu2-ReLU-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 1.93 16.32 🔴 +743.91%
Resample npu2-Resample-height_3024-width_4032-out_height_672-out_width_912 Latency MAD 14.13 33.38 🔴 +136.17%
ResampleTaps npu2-ResampleTaps-in_size_4032-out_size_912 Latency MAD 131.34 28.20 🟢 -78.53%
RoPE npu2-RoPE-rows_4096-cols_512-angle_rows_8 Bandwidth 59.03 55.56 🔴 -5.87%
RoPE npu2-RoPE-rows_4096-cols_512-angle_rows_8 Latency 142.25 151.12 🔴 +6.24%
RoPE npu2-RoPE-rows_4096-cols_512-angle_rows_8 Throughput 44.23 41.63 🔴 -5.87%
RowwiseAdd npu2-RowwiseAdd-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 25.96 17.23 🟢 -33.65%
RowwiseMul npu2-RowwiseMul-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 12.02 15.02 🔴 +24.92%
Sample npu2-Sample-llama Latency MAD 1.09 1.71 🔴 +56.84%
SiLU npu2-SiLU-size_8388608-num_aie_columns_8-tile_size_4096 Latency MAD 1.94 9.05 🔴 +366.58%
Sigmoid npu2-Sigmoid-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 20.11 9.86 🟢 -50.96%
Softmax npu2-Softmax-rows_4096-cols_512-num_aie_columns_2-num_channels_2 Latency MAD 1.16 1.22 🔴 +5.62%
Tanh npu2-Tanh-size_8388608-num_aie_columns_8-num_channels_2-tile_size_4096 Latency MAD 0.97 5.66 🔴 +484.85%
flm.DequantBFP npu2-flm.DequantBFP-K_6144-N_1536 Latency MAD 15.62 12.28 🟢 -21.40%
flm/layer gemma4_e2b-global-700 Bandwidth 44.24 41.60 🔴 -5.96%
flm/layer gemma4_e2b-global-700 Latency 685.80 729.27 🔴 +6.34%
flm/layer gemma4_e2b-global-700 Throughput 144.04 135.45 🔴 -5.96%
flm/layer gemma4_e2b-swa-700 Bandwidth 47.94 43.98 🔴 -8.26%
flm/layer gemma4_e2b-swa-700 Latency 521.51 568.46 🔴 +9.00%
flm/layer gemma4_e2b-swa-700 Throughput 148.28 136.04 🔴 -8.26%
Phoenix - Operators

Operators added: Experts, SelectiveScan, TopKRouter, core_streams, generation

Benchmarks that moved by at least 5% and by more than 2x the spread the runs measured:

Operator Parametrization Metric Previous Current Change
AXPY npu1-AXPY-size_8388608-num_aie_columns_4-tile_size_4096-scalar_factor_3.0 Latency MAD 898.60 556.90 🟢 -38.03%
Clamp npu1-Clamp-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096-low_-0.75-high_1.25 Bandwidth 10.15 9.57 🔴 -5.75%
Clamp npu1-Clamp-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096-low_-0.75-high_1.25 Latency 3305.69 3507.43 🔴 +6.10%
Clamp npu1-Clamp-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096-low_-0.75-high_1.25 Latency MAD 334.35 134.19 🟢 -59.87%
Clamp npu1-Clamp-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096-low_-0.75-high_1.25 Throughput 2.54 2.39 🔴 -5.75%
Copy npu1-Copy-bench_flat_4mi Latency MAD 22.62 9.53 🟢 -57.88%
ElementwiseAdd npu1-ElementwiseAdd-size_8388608-num_aie_columns_4-tile_size_4096 Bandwidth 14.91 17.49 🟢 +17.33%
ElementwiseAdd npu1-ElementwiseAdd-size_8388608-num_aie_columns_4-tile_size_4096 Latency 3376.28 2877.51 🟢 -14.77%
ElementwiseAdd npu1-ElementwiseAdd-size_8388608-num_aie_columns_4-tile_size_4096 Latency MAD 495.66 1379.13 🔴 +178.24%
ElementwiseAdd npu1-ElementwiseAdd-size_8388608-num_aie_columns_4-tile_size_4096 Throughput 2.48 2.92 🟢 +17.33%
ElementwiseMul npu1-ElementwiseMul-size_8388608-num_aie_columns_4-tile_size_4096 Bandwidth 32.04 20.59 🔴 -35.75%
ElementwiseMul npu1-ElementwiseMul-size_8388608-num_aie_columns_4-tile_size_4096 Latency 1570.68 2444.71 🔴 +55.65%
ElementwiseMul npu1-ElementwiseMul-size_8388608-num_aie_columns_4-tile_size_4096 Latency MAD 45.41 1001.49 🔴 +2105.36%
ElementwiseMul npu1-ElementwiseMul-size_8388608-num_aie_columns_4-tile_size_4096 Throughput 5.34 3.43 🔴 -35.75%
Emit npu1-Emit-eight_carried Latency MAD 2.34 4.26 🔴 +82.26%
GELU npu1-GELU-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 193.93 218.72 🔴 +12.78%
GEMM npu1-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Bandwidth 3.25 3.01 🔴 -7.39%
GEMM npu1-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Latency 7736.16 8353.31 🔴 +7.98%
GEMM npu1-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Latency MAD 267.51 852.07 🔴 +218.52%
GEMM npu1-GEMM-M_2048-K_2048-N_2048-b_col_maj_True Throughput 2220.72 2056.65 🔴 -7.39%
LayerNorm npu1-LayerNorm-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096-weighted_True Latency MAD 11.09 20.11 🔴 +81.34%
LayerNorm npu1-LayerNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Bandwidth 29.57 19.58 🔴 -33.78%
LayerNorm npu1-LayerNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Latency 1134.68 1713.47 🔴 +51.01%
LayerNorm npu1-LayerNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 8.71 697.55 🔴 +7912.76%
LayerNorm npu1-LayerNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Throughput 44.36 29.37 🔴 -33.78%
Merge npu1-Merge-rows_2048-placeholders_((258881, 2048), (258880, 2560)) Bandwidth 0.18 0.20 🟢 +8.62%
Merge npu1-Merge-rows_2048-placeholders_((258881, 2048), (258880, 2560)) Latency 90.18 83.02 🟢 -7.94%
Merge npu1-Merge-rows_2048-placeholders_((258881, 2048), (258880, 2560)) Latency MAD 7.17 11.67 🔴 +62.71%
PatchPositions npu1-PatchPositions-rows_2560-out_height_672-out_width_912 Latency MAD 8.30 37.85 🔴 +356.04%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096-weighted_True Bandwidth 10.25 12.47 🟢 +21.64%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096-weighted_True Latency 3274.51 2692.07 🟢 -17.79%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096-weighted_True Latency MAD 318.99 879.48 🔴 +175.71%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096-weighted_True Throughput 10.25 12.46 🟢 +21.64%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Bandwidth 29.66 10.06 🔴 -66.10%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Latency 1131.16 3336.36 🔴 +194.95%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 11.32 221.36 🔴 +1856.17%
RMSNorm npu1-RMSNorm-rows_2048-num_aie_columns_4-num_channels_2-tile_size_4096 Throughput 29.66 10.06 🔴 -66.10%
ReLU npu1-ReLU-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 7.39 15.29 🔴 +106.86%
Resample npu1-Resample-height_3024-width_4032-out_height_672-out_width_912 Latency MAD 320.67 374.70 🔴 +16.85%
ResampleTaps npu1-ResampleTaps-in_size_4032-out_size_912 Latency MAD 13.64 106.17 🔴 +678.28%
RoPE npu1-RoPE-rows_4096-cols_512-angle_rows_8 Latency MAD 9.56 10.45 🔴 +9.22%
RowwiseAdd npu1-RowwiseAdd-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Bandwidth 10.98 20.79 🟢 +89.23%
RowwiseAdd npu1-RowwiseAdd-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Latency 3055.62 1614.74 🟢 -47.15%
RowwiseAdd npu1-RowwiseAdd-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Latency MAD 487.57 447.00 🟢 -8.32%
RowwiseAdd npu1-RowwiseAdd-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Throughput 2.75 5.20 🟢 +89.23%
RowwiseMul npu1-RowwiseMul-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Bandwidth 28.12 10.19 🔴 -63.76%
RowwiseMul npu1-RowwiseMul-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Latency 1193.51 3293.28 🔴 +175.93%
RowwiseMul npu1-RowwiseMul-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Latency MAD 34.87 244.43 🔴 +601.07%
RowwiseMul npu1-RowwiseMul-rows_2048-num_aie_columns_2-num_channels_2-tile_size_4096 Throughput 7.03 2.55 🔴 -63.76%
Sample npu1-Sample-llama Latency MAD 19.98 22.30 🔴 +11.64%
Sigmoid npu1-Sigmoid-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Bandwidth 29.43 10.40 🔴 -64.68%
Sigmoid npu1-Sigmoid-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Latency 1140.10 3227.64 🔴 +183.10%
Sigmoid npu1-Sigmoid-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 8.81 358.90 🔴 +3972.91%
Sigmoid npu1-Sigmoid-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Throughput 7.36 2.60 🔴 -64.68%
Softmax npu1-Softmax-rows_4096-cols_512-num_aie_columns_2-num_channels_2 Bandwidth 6.34 2.69 🔴 -57.61%
Softmax npu1-Softmax-rows_4096-cols_512-num_aie_columns_2-num_channels_2 Latency 1322.13 3118.81 🔴 +135.89%
Softmax npu1-Softmax-rows_4096-cols_512-num_aie_columns_2-num_channels_2 Latency MAD 84.63 146.67 🔴 +73.29%
Softmax npu1-Softmax-rows_4096-cols_512-num_aie_columns_2-num_channels_2 Throughput 1.59 0.67 🔴 -57.61%
Tanh npu1-Tanh-size_8388608-num_aie_columns_4-num_channels_2-tile_size_4096 Latency MAD 273.65 289.45 🔴 +5.77%

⚠️ No results from: Krackan - Applications (missing), Phoenix - Applications (missing). This comment covers the remaining suites only.

hunhoffe and others added 21 commits October 29, 2026 12:43
a0162c7 moved eighteen applications out of #222 to their own pull
requests, each with its checkpoints, goldens and the library code only
it exercises. Granite goes first: this ports 46a5d87's model, profiles,
README and device-free tests back in, unchanged, against today's iron.lm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Granite's GraniteConfig covers test_config.py's key-mapping check
alongside LlamaConfig's, and long_context's APPLICATIONS holds it to
the same one-compile, any-context guarantee as Llama and Gemma 4; its
deep_kl figures are 46a5d87's own, measured at 4095 positions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8c25885 gave every model test fixture a model.release() after the
module's (and TestEachStep's) tests, so the next model's CREATE_BO
doesn't meet a full heap behind a reference cycle still awaiting GC.
Granite's fixtures, restored from before that commit, didn't have it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
754e3ae cut iron/lm's and the three #222 applications' docstrings to
what a caller relies on, the rationale left for the commits that made
it. Granite's, restored from before that commit, kept the longer form;
this brings its module and Granite class docstrings to the same register.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
74f7004's table (cut from b1e224c6 to #222's models) left out Granite's
row when it moved out in a0162c7. Restored at its measured figure
(86.5 s on this host today, against b1e224c6's 86.5 s): its deepest
prompt chunk, at its native 131072 context, as one command.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bd6c053, d23627e and fdaaab1 widened long_context's five context
sizes to the per-step form (an xclbin a step, the host starting each on
the token the last one drew) on npu1 and npu2; Granite's entry, restored
from before that work, was still full-ELF only. Granite has an npu1
profile (empty, every operator's device defaults), so it joins at
each_step=("npu1", "npu2") like Llama's and Gemma 4's.

Both forms measured on this host's npu2, 4096 to the whole 131072
context: decode deep in the cache KL 0.0010 to 0.0022, its deepest prompt
chunk 1.7e-8 to 1.4e-4, top-1 agreeing throughout. npu1 is still
unmeasured on this host.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Granite 4.0-H 1B joins Granite 4.2 3B as a follow-on to #222 (a0162c7,
0f0a540): the dense 1B this ports, its README, profile and device-free
tests, unchanged, against today's iron.lm. Its library dependencies
(selective_scan, CausalConv1d, Checkpoints) and the two later fixes
follow in their own commits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Granite's tokenizer adds no BOS, so since prompts begin as its
post-processor begins them, the accuracy checks see 292 and 3261 tokens
where they saw 293 and 3262, and the golden rows, KL figures, README and
bounds were for the old prompts. The golden is re-recorded device-free
(transformers in float32) and the figures re-measured on xcoradaie220.

Measured on Strix Halo (npu2), one compile at the native 131072
positions, projections at HIGH as before:
- KL over 40 steps after 292 tokens: mean 0.0024, p90 0.0048, max
  0.0063 (2 top-1 swaps); two chunks and two decode steps: mean 0.0027,
  max 0.0049. The chat turn is bit for bit the run from scratch;
  determinism 0 differing runs.
- TTFT 2.9 s after 13 characters, 3.6 s after 292 tokens, 8.0 s after
  2048; 6.7 tokens/s.

MAX_KL was about twice the old figures; it is again about twice the new
ones (mean 0.006, p90 0.012, max 0.015). The README's Precision table is
left to the precision pass, which has these numbers.

Net: tests +4/-3, docs +9/-8.

Checked: pytest tests/applications/granite_hybrid/test.py on the NPU
(8 passed, at the old bounds; the new bounds hold the same deterministic
figures). collect-only, black, reuse.

Co-Authored-By: Claude <noreply@anthropic.com>
granite_hybrid was listed as unheld because Experts streams every
expert's weights for each row. That is true of the MoE sizes, but the
dense 1B the package ships runs no Experts. The 1B now joins
APPLICATIONS, and long_context holds it to its native 131072 positions
like the other decoders: nothing grows with the context but a row per
position, and the deepest decode step and prompt chunk match the graph's
reference. The MoE sizes have no checkpoint or test here.

Measured on Strix Halo (npu2), full ELF at 131072 positions:
- decode at 131071: KL 0.0035 against the graph's reference, same
  top-1.
- the deepest prompt chunk: KL 4.1e-5, 14.8 s.
- TTFT 32.8 s at 8192 tokens, 732 s at the whole 131072.

Net: tests +7/-2.

Checked: pytest tests/applications/long_context/test.py -k
granite_4.0_h_1b: test_nothing_grows_with_the_context, decode and
prompt, passes device-free; the 4 extensive device tests pass.
test_every_decoder_application_is_held_to_its_context fails before and
after this change, because qwen3_embedding is a CausalLM missing from
both lists. collect-only, black, reuse.

Co-Authored-By: Claude <noreply@anthropic.com>
0f0a540 left SelectiveScan, its shared state_slices, iron.lm.Checkpoints
and (a0162c7) CausalConv1d with the applications that exercised them,
to come back with whichever follow-on pull request needed them again.
Granite 4.0-H 1B's Mamba2 mixer needs all four: the scan over its state,
the short causal convolution ahead of it (F.causal_conv1d, ConvState),
and the per-chunk checkpointed state both carry between calls.

iron.lm.testing's _fill_caches, used by long_context's deep checks,
regains the Checkpoints branch it had before 0f0a540 trimmed it to
KVCache alone, so a deep check sees the chunk-boundary state a prompt
left, not an empty one.

selective_scan.py drops one case, "granite_small_prefill": the Case it
marked (lower=True, run=False) relied on a run field Case removed, and
the case was for a different checkpoint (Granite 4.0-H Small) out of
scope here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The package 46a5d87 shipped built GraniteMoE for any config with
num_local_experts: Router, then Experts over the checkpoint's stacked
expert weights. a0162c7 and 0f0a540 moved Experts and TopKRouter out
with Qwen3-MoE, untested at any shape that runs at a usable rate (an
unrelated follow-up); restoring them here only to exercise an MoE size
this package stages no checkpoint or test for would be restoring code
nothing checks. The shipped 1B is dense: GraniteHybridLayer no longer
builds a GraniteMoE, and GraniteHybridConfig refuses a checkpoint with
experts outright, rather than silently ignoring num_local_experts.

The device-free reference test held an MoE toy alongside the dense one
(goldens/toy.npz), checking the graph's reference against Hugging Face's
GraniteMoeHybrid at a toy MoE shape. It depends on the same GraniteMoE
this drops, so it goes with it, replaced by a test that the config
refuses experts; the dense toy's own class and golden are what the
shipped 1B is checked against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8c25885 gave every model test fixture a model.release() after the
module's tests, so the next model's CREATE_BO doesn't meet a full heap
behind a reference cycle still awaiting GC. Granite 4.0-H 1B's fixture,
restored from before that commit, didn't have it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Its row names the real checkpoint (granite-4.0-h-1b) now staged, in
place of the "reference only"/"none yet" row from before it existed.
Its own README drops the mixture-of-experts section, which measured
Experts at an MoE size this package no longer builds (27870ec0), and
its intro and reference/ mentions follow the dense-only package.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
generate reads a device loop's draws once the last is drawn: the loop
always draws max_tokens, and generate stops at the first eos among them.
Its rate counted only the tokens through eos, over the time the loop
took to draw all of them. Granite 4.0-H 1B draws eos 8th after the
benched 13-character prompt, so its TPS read 1.21 where the device draws
6.7 tokens a second. The nightly trend check would have compared that
figure.

A Model now says whether generate's draws come at once (draws_at_once:
a CausalLM's device loop, a Multimodal's text model's; False otherwise,
OnHost too). Where they do, generate counts every token drawn in the
time it measures, and keeps returning only those through eos. stream,
which reads each token as it is drawn, and draws stepped from the host
still stop at eos, so no step runs that otherwise wouldn't.

Net: library +31/-5, tests +21.

Checked: a new device test, test_granite_4_0_h_1b_rate_counts_the_draws_past_eos,
compares generate's rate with stream's over the same early-eos draw:
6.68 vs 6.67 tokens/s (generate read 1.21 before the change, measured
with a per-token probe and in the module run). The benched 13-character
case reads 6.69 TPS. tests/applications/granite_hybrid/test.py: all 8
other tests pass. Device-free pytest -m "not supported_devices and not
extensive" iron/tests/common/test_llama_reference.py
iron/tests/common/test_multimodal.py iron/tests/lm
iron/tests/infrastructure/test_lm_host.py: 155 passed. collect-only,
black, reuse.

Co-Authored-By: Claude <noreply@anthropic.com>
Measured on npu2 (xcoradaie220), full ELF, five context sizes 4096 to
the whole 131072: decode deep in the cache KL 0.0007 to 0.0069, its
deepest prompt chunk 4.1e-5 to 0.0052, top-1 agreeing throughout; npu1
still unmeasured. The deepest prompt chunk at the native context, one
command: 14.8 s, matching cbf21694's recorded figure, added to the
driver-timeout table between Llama 3.2 1B and EmbeddingGemma 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Erika's decision: the dense 1B must run on npu1 (Phoenix), not npu2
alone. 7c34b69 brought Granite 4.2 3B to npu1 the same way: an empty
profile (every operator's device defaults), a TestEachStep class
(npu1, npu2) holding the per-step generation cases plus a shallower
accuracy and determinism check, and long_context's each_step=("npu1",
"npu2") so its five-size sweep covers the per-step form too.

CI's Phoenix runner covers the real npu1 run; this host is npu2 only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…test

Co-authored-by: hunhoffe <54562339+hunhoffe@users.noreply.github.com>
Co-authored-by: hunhoffe <54562339+hunhoffe@users.noreply.github.com>
Co-authored-by: hunhoffe <54562339+hunhoffe@users.noreply.github.com>
a0162c7 kept #222 to the library, Llama, EmbeddingGemma 2 and Gemma 4,
moving eighteen applications to follow-up pull requests. This is the
Qwen3 family's: qwen3_embedding and qwen3_reranker build directly on
qwen3's Qwen3 class, and qwen3_moe on its Qwen3Layer, so the four travel
together.

- Qwen3 1.7B/0.6B, Qwen3-Embedding 0.6B and Qwen3-Reranker 0.6B restored
  unchanged from before a0162c7, with their tests and toy goldens.
- Qwen3-MoE (30B-A3B, reference only) needs two operators a later cleanup
  commit (0f0a540) also moved out: restored iron/operators/router.py
  (TopKRouter) and experts.py (Experts), re-exported from
  iron/operators/__init__.py.
- Restored TopKRouter's two rejected-shape tests in
  test_rejected_shapes.py. Recreated test_core_streams.py scoped to
  Experts alone; 0f0a540's version also covered SelectiveScan and
  GatedDeltaRule, which belong to lfm2/granite_hybrid/qwen3_5's own
  follow-up.
- README's model table gets its four rows back.

Checked: black; device-free tests/applications/{qwen3,qwen3_embedding,
qwen3_reranker,qwen3_moe}/reference and test_core_streams.py (20 passed);
iron/tests/operators/test_rejected_shapes.py collects clean (134 tests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restores the decoders and long_context coverage Qwen3, Qwen3-Embedding,
Qwen3-Reranker and Qwen3-MoE already have their own device tests for:
every other model's checkpoint is held to its whole context, and every
full-elf decoder but Gemma 4 is run in the decoders suite's accuracy and
determinism checks.

- Qwen3-MoE's 30B joins decoders (nightly, its toy test already runs per
  PR) and long_context, staged=False as its checkpoint isn't on every
  runner. Its p90 bound is 0.1 (bf16 transformers itself reaches 0.065 on
  its steps, near-tie experts); the figure is carried from before #222's
  numeric fixes and not yet re-measured here, so it's not asserted as
  current. test_determinism skips it: test_host_steps already holds a
  host step's calls exact, and a full redo is 15 minutes at this scale.
- long_context's loaded fixture checked a form by device_loop, which a
  full ELF with a host step (Qwen3-MoE's paged experts) doesn't have; it
  now checks the decode step's packaging instead, what the form actually
  is.
- Qwen3 0.6B/1.7B, Qwen3-Embedding and Qwen3-Reranker join long_context.
  Qwen3-Embedding's row is its final hidden state, held by its cosine to
  the reference, not KL (check_deep_decode/check_deep_prompt gain
  cosine=); Application gains deep_cosine and staged.

Measured on npu2 (Strix Halo), native context (32768), teacher-forced:
Qwen3 0.6B decode KL 0.0020, prompt chunk 0.0021 (39.9s); Qwen3 1.7B
(full ELF and each-step alike) decode KL 0.0385, prompt chunk 0.0565
(40.2-40.9s); Qwen3-Embedding decode cosine 0.98611, prompt chunk
cosine 0.99859 (31.8s), against its 0.97 bound (twice the decode's
distance from 1); Qwen3-Reranker decode KL 0.0212, prompt chunk KL
0.0038 (39.9s), well under DEEP_KL. All ten cases pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Base automatically changed from granite to devel October 30, 2026 00:03

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants