Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
292f3ea
finegrained-kernels: row-wise FP8 (per-output-row weight scales, per-…
IlyasMoutawwakil Oct 15, 2026
16912cb
finegrained-kernels: add block and per-tensor FP8 weight-only GEMMs
IlyasMoutawwakil Oct 16, 2026
0c00a27
finegrained-kernels: retain partial gate blocks in FP8 weight-only grids
IlyasMoutawwakil Oct 16, 2026
74fb263
Fix row-wise FP8 activation padding and gated autotuning
IlyasMoutawwakil Oct 16, 2026
969c22f
finegrained-kernels: keep new weight-only schemas compatible with Tor…
IlyasMoutawwakil Oct 16, 2026
73c4266
finegrained-kernels: guard gathered TMA warp-specialization layouts
IlyasMoutawwakil Oct 16, 2026
a4ec78c
Fix UE8M0 zero and NaN exponent-byte decoding
IlyasMoutawwakil Oct 16, 2026
ba9e3a2
Accelerate B200 coarse FP8 weight-only dense inference
IlyasMoutawwakil Oct 16, 2026
08a571c
Tune B200 rowwise FP8 for warm and cold weight execution
IlyasMoutawwakil Oct 16, 2026
b0986ca
Preserve tuning caches for kernels without startup seeds
IlyasMoutawwakil Oct 16, 2026
022578f
Reuse split-K FP8 weight-only decode for sparse routed experts
IlyasMoutawwakil Oct 16, 2026
21cc0ca
Tune B200 rowwise grouped schedules without regressing decode
IlyasMoutawwakil Oct 16, 2026
09ece45
Document FP8 tuning scope and routed prefill limitations
IlyasMoutawwakil Oct 16, 2026
7861a36
Use kernel builder that loads the local package in CI
IlyasMoutawwakil Oct 16, 2026
5d6ea81
Use kernel builder that loads the local package in CI
IlyasMoutawwakil Oct 16, 2026
869a63d
Remove standalone FP8 benchmarks and performance README additions
IlyasMoutawwakil Oct 16, 2026
3bb6055
Remove standalone FP8 benchmarks and performance README additions
IlyasMoutawwakil Oct 16, 2026
8b6028a
Fix FP8 weight-only large expert offsets and UE8M0 boundary decoding
IlyasMoutawwakil Oct 16, 2026
006416e
Keep existing CI builder while retaining direct-import tests
IlyasMoutawwakil Oct 16, 2026
5ddb01d
Keep existing CI builder while retaining direct-import tests
IlyasMoutawwakil Oct 16, 2026
be8122a
Reuse scale routing and epilogue helpers in FP8 weight-only decode
IlyasMoutawwakil Oct 16, 2026
95bc885
Refresh row scale strides after grouped rowwise expansion
IlyasMoutawwakil Oct 16, 2026
886d9a5
Respect calibrated gate scale layout in unfused FP8 weight-only MoE
IlyasMoutawwakil Oct 16, 2026
ec8f142
Cover coarse FP8 weight-only through the canonical scenario matrix
IlyasMoutawwakil Oct 16, 2026
60f2307
Keep per-token intermediate quantization for either MoE projection
IlyasMoutawwakil Oct 16, 2026
7310bf4
Remove rowwise tuning seed API and consolidate existing tests
IlyasMoutawwakil Oct 16, 2026
e0f3e57
Keep rowwise pruner documentation focused on behavior
IlyasMoutawwakil Oct 16, 2026
5485869
Choose interior tuning anchors across ordinal configuration axes
IlyasMoutawwakil Oct 16, 2026
7332e2a
Separate FP8 weight-only GEMMs and use direct format launchers
IlyasMoutawwakil Oct 16, 2026
a4e1c91
Keep rowwise FP8 focused on existing kernel paths and direct imports
IlyasMoutawwakil Oct 16, 2026
a849331
Use existing GEMM tile paths for FP8 weight-only inference
IlyasMoutawwakil Oct 16, 2026
0a05949
Remove the obsolete large-offset decode regression
IlyasMoutawwakil Oct 16, 2026
8b78961
Simplify FP8 weight-only epilogues and gate scale layout
IlyasMoutawwakil Oct 16, 2026
94ccea5
Fix rowwise view dispatch and trim feature scope
IlyasMoutawwakil Oct 16, 2026
0b7e9f2
Drop redundant activation amax helper test
IlyasMoutawwakil Oct 17, 2026
fd6f759
Keep calibrated FP8 scale layout internal to its weight-only path
IlyasMoutawwakil Oct 17, 2026
8c16be5
Leave rowwise activation gradients to the dgrad stream
IlyasMoutawwakil Oct 17, 2026
a1365c9
Use shared end-to-end coverage for rowwise correctness
IlyasMoutawwakil Oct 17, 2026
6993a24
Require matching MoE recipes and fix prequantized default output dtype
IlyasMoutawwakil Oct 17, 2026
9fea860
Benchmark Llama 4 rowwise FP8 against Torch, MSLK and vLLM
IlyasMoutawwakil Oct 17, 2026
06d3ede
Consolidate FP8 weight-only end-to-end scenarios
IlyasMoutawwakil Oct 17, 2026
d179628
Place FP8 weight-only scenarios in the existing group
IlyasMoutawwakil Oct 17, 2026
3550965
Benchmark public rowwise scaled_mm and route measured prefill shapes
IlyasMoutawwakil Oct 17, 2026
6ad73d6
Thread rowwise FP8 activation amax caps through public forwards
IlyasMoutawwakil Oct 17, 2026
5798a44
Match rowwise benchmark activation caps and trim duplicate scenarios
IlyasMoutawwakil Oct 17, 2026
fbdc835
Reuse shared-memory pruning and simplify FP8 weight-only tile loads
IlyasMoutawwakil Oct 17, 2026
afbd7a3
Clarify MoE and linear benchmark plot labels
IlyasMoutawwakil Oct 17, 2026
4ddbd2e
Improve grouped FP8 weight reuse across M tiles
IlyasMoutawwakil Oct 17, 2026
d7f53ca
Compact FP8 weight scales and reuse dense TMA tuning
IlyasMoutawwakil Oct 17, 2026
69b50b2
Refresh Llama4 benchmark with matched activation clipping
IlyasMoutawwakil Oct 17, 2026
ccc2d74
Clarify weight-only dot_scaled lowering
IlyasMoutawwakil Oct 17, 2026
0412e81
Stage FP8 weight-only weights before prefill GEMMs
IlyasMoutawwakil Oct 17, 2026
4727a46
Benchmark FP8 weight-only against native runtime dequantization
IlyasMoutawwakil Oct 17, 2026
7cce382
Keep grouped FP8 weights inline until routed reuse amortizes conversion
IlyasMoutawwakil Oct 17, 2026
9f0d61c
Benchmark anonymous FP8 weight-only shapes against resident BF16 and …
IlyasMoutawwakil Oct 17, 2026
f993732
Use swapped MMA tiles for FP8 weight-only decode
IlyasMoutawwakil Oct 17, 2026
72ac72e
Refresh FP8 weight-only comparisons with Marlin
IlyasMoutawwakil Oct 17, 2026
2fee9ca
Match FP8 decode tiles to token-count buckets
IlyasMoutawwakil Oct 17, 2026
62065ba
Merge commit 'b70b838df064605ed9f8cef3d17c9bf00c6dbf5c' into codex/wo…
IlyasMoutawwakil Oct 17, 2026
acba239
Tune FP8 K tiles and pipeline stages jointly
IlyasMoutawwakil Oct 17, 2026
012ac40
Merge commit 'bb49417e' into codex/wo-cold-tuner
IlyasMoutawwakil Oct 17, 2026
afe696d
finegrained: avoid redundant masking in FP8 weight dequantization
IlyasMoutawwakil Oct 17, 2026
2d7a3c8
finegrained: remove resident BF16 weight-only benchmark controls
IlyasMoutawwakil Oct 17, 2026
c906ca4
Update weight-only tuner parent for stacked review
IlyasMoutawwakil Oct 18, 2026
0753e3e
Stack rowwise FP8 on the shared tuner updates
IlyasMoutawwakil Oct 18, 2026
15b65d7
Merge rowwise FP8 for native GitHub stack
IlyasMoutawwakil Oct 18, 2026
da8a4a0
Propagate tuner phase option documentation through stack
IlyasMoutawwakil Oct 18, 2026
23254c8
Propagate tuner option order through weight-only stack
IlyasMoutawwakil Oct 18, 2026
be36b23
Retain standard tuner timing in rowwise stack
IlyasMoutawwakil Oct 18, 2026
88ce8dd
Retain standard tuner timing in weight-only stack
IlyasMoutawwakil Oct 18, 2026
c9e49f6
Merge rotating graph tuning into rowwise FP8 stack
IlyasMoutawwakil Oct 18, 2026
7415303
Merge rotating graph tuning into FP8 weight-only stack
IlyasMoutawwakil Oct 18, 2026
6395d7d
Restack rowwise FP8 on the separate cold-cache timing change
IlyasMoutawwakil Oct 18, 2026
0ed5c97
Restack weight-only FP8 after separating cold-cache timing
IlyasMoutawwakil Oct 18, 2026
8e28a06
Expand quantization recipes through the shared GEMM and MoE paths
IlyasMoutawwakil Oct 18, 2026
b0460fc
Unify quantization benchmarks and simplify shared validation
IlyasMoutawwakil Oct 18, 2026
1e812c7
Merge remote-tracking branch 'origin/finegrained-cold-cache-bench' in…
IlyasMoutawwakil Oct 18, 2026
b59c88d
Share grouped dot grids and improve small-tile occupancy
IlyasMoutawwakil Oct 18, 2026
3d0c352
Reuse output-tile pipelines for FP8 weight-only MoE
IlyasMoutawwakil Oct 18, 2026
e57b7e2
Keep SM10x launch optimizations NVIDIA-only
IlyasMoutawwakil Oct 18, 2026
44c614f
Merge branch 'finegrained-cold-cache-bench' into finegrained-rowwise-fp8
IlyasMoutawwakil Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 82 additions & 4 deletions finegrained-kernels/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ tags:

# finegrained-kernels

Triton GEMM + MoE kernels for fine-grained quantization — block/tensor FP8, MXFP8, MXFP4, and
two-level NVFP4 weights, with fused gate|up GLU epilogues, fused intermediate requantization, and
Triton GEMM + MoE kernels for fine-grained quantization — block/tensor/row-wise FP8, MXFP8, MXFP4,
two-level NVFP4, and INT2/INT4/INT8 weights, with fused gate|up GLU epilogues, fused intermediate requantization, and
expert routing — developed as part of the HuggingFace Transformers FP8 + MoE optimization effort.

All tuning and validation is on NVIDIA Blackwell (SM100 / B200), whose tcgen05 scaled-MMA the MX
Expand Down Expand Up @@ -62,7 +62,7 @@ matmul_grouped(A, B, As, Bs, *, expert_start, gather_idx=None, scatter_idx=None,
(see the format matrix below).
- `activation_format` is the activations' quantization, in the same vocabulary as transformers'
`FineGrainedConfig`: `None` (default) = the weights' own format, `"bf16"` = weight-only (W4A16 /
W8A16), or an explicit `"fp8"` / `"mxfp8"` / `"mxfp4"` / `"nvfp4"` (`"mxfp8"` on MXFP4 weights =
W8A16), or an explicit `"fp8"` / `"int8"` / `"mxfp8"` / `"mxfp4"` / `"nvfp4"` (`"mxfp8"` on MXFP4 weights =
the W4A8 chain). The weight format is never named — it is read off `B`/`Bs`.
- `gate=True, act_fn="silu", swiglu_alpha=..., swiglu_limit=...` fuses the gate|up GLU: `B` holds
the stacked `(2N, K)` gate|up rows and the op returns the activated `(M, N)` intermediate;
Expand All @@ -75,6 +75,8 @@ matmul_grouped(A, B, As, Bs, *, expert_start, gather_idx=None, scatter_idx=None,
gate|up stack whose halves a checkpoint calibrated separately carries one global per half; the
loader merges those into one per expert (the up half's folds into the down projection's global,
SwiGLU being linear in it) rather than the ops taking a second layout.
- Weight-only FP8 (`activation_format="bf16"`) also accepts coarse block scales and one scale
per tensor/expert, with unquantized activations. Coarse FP8 entrypoints are inference-only.
- Routing: `matmul_batched` reads one expert id per row (`expert_ids`, EP sentinels
`>= num_experts` skipped); `matmul_grouped` takes the launch maps from
`compute_grouped_scheduling(expert_ids, num_experts, top_k)` — no pre-sorted input required.
Expand All @@ -86,9 +88,83 @@ matmul_grouped(A, B, As, Bs, *, expert_start, gather_idx=None, scatter_idx=None,
| full precision | bf16 / fp16 | `None` | — |
| block FP8 (128×128) | `float8_e4m3fn` | fp32 or UE8M0 `(⌈N/128⌉, K/128)` (the block is derived from the K dim, so K must divide) | — |
| tensor FP8 | `float8_e4m3fn` | one scalar | — |
| row-wise FP8 | `float8_e4m3fn` | fp32 `(N, 1)`, one per output row | — |
| MXFP8 | `float8_e4m3fn` | UE8M0 `(N, K/32)` (raw `uint8` accepted) | — |
| MXFP4 | packed E2M1 (`int8`, 2/byte) | UE8M0 `(N, K/32)` | — |
| NVFP4 | packed E2M1 (`int8`, 2/byte) | E4M3 `(N, K/16)` | fp32 global(s) |
| INT2 / INT4 / INT8 | interleaved `uint32` words (`32 / bits` values/word) | bf16 / fp16 / fp32 `(N, K/group)` | — |

Integer weights use raw bf16/fp16 activations and optional `b_zero` values shaped like `Bs`
(default: `2 ** (bits - 1)`). Dequantization rounds `(q - zero) * scale` in the scale dtype,
then casts each weight to the activation dtype before multiplication, including per-channel
scales (`group=K`, one scale per output row). Group widths and K tiles must divide one another;
the K dimension must contain whole groups. Use `interleave_int_words` for naturally packed
words, `repack_gptq_int4` for GPTQ v1/v2 (including act-order), or `repack_awq_int4` for AWQ
GEMM checkpoints. Natural `int32` checkpoint words are rejected to prevent confusing the two layouts.
For ModelOpt's signed exports, `repack_modelopt_int4` reads `uint8` `(N/2, K)` with two
output channels per byte, and `repack_modelopt_int8` reads signed `int8` `(N, K)` with one
scale per output channel. Both also accept expert stacks and return `(weight, scale, None)`
for these same integer GEMMs. Pass an exported AWQ `pre_quant_scale` as `a_pre_quant_scale`
to an integer GEMM, or fuse it upstream. It multiplies the raw input columns before any
act-order permutation, rounded in the activation dtype. Dense scales are contiguous `(K,)`;
routed scales are shared `(K,)` / `(1, K)` or per expert `(E, K)`, on the activation's device
and in its dtype. MoE forwards accept `gate_up_proj_pre_quant_scale` and
`down_proj_pre_quant_scale`, including a separate down input vector per expert. A fused
gate/up stack uses a common input transform for both halves, as after ModelOpt's resmoothing.
For ModelOpt `W4A8_AWQ`, use `activation_format="fp8"`, `As=input_scale`, and
`b_global_scale=weight_scale_2` with the repacked symmetric INT4 weight and its original
group scales. The kernel reconstructs the weight, rounds it to E4M3 against `weight_scale_2`,
and restores both calibrated factors before FP32 bias/GLU. `activation_format="int8"` with
`As=input_scale` selects ModelOpt INT8 SmoothQuant on symmetric per-channel INT8 weights:
the dot accumulates in INT32, then applies per-channel weight scales and input scale in FP32.
Both recipes take raw activations and keep the intermediate unquantized between projections;
the down's own calibration and optional input transform apply before its quantization.

ModelOpt `W4A8_MXFP4_FP8` and `W4A8_NVFP4_FP8` also use `activation_format="fp8"` and
`As=input_scale`. Both use group-32 FP4 weights; NVFP4's E4M3 block scales and separate
`weight_scale_2` differ from group-16 NVFP4 W4A4. Ordinary FP8 activations keep one calibrated
scale per tensor/expert; they are not re-quantized per block as MXFP8. The MX loops consume
unit activation group scales; affine NVFP4 prefill instead reuses the weight-upcast GEMM,
storing FP8-rounded inputs exactly in BF16. Both restore calibration in the FP32 epilogue. Input
`pre_quant_scale` also supports MX/FP4 activation-quantized paths, including NVFP4 AWQ.

ModelOpt `NVFP4_SVD` (SVDQuant) is supported by dense `matmul_2d`: pass the BF16
`svdquant_lora_a` `(rank, K)`, `svdquant_lora_b` `(N, rank)` and input `pre_quant_scale`:

```python
out = matmul_2d(
x, weight.view(torch.int8), Bs=weight_scale,
a_global_scale=input_scale, b_global_scale=weight_scale_2,
a_pre_quant_scale=pre_quant_scale,
svdquant_lora_a=lora_a, svdquant_lora_b=lora_b,
simulate_unfused=True,
)
```

Smoothing, NVFP4 activation quantization and the BF16 low-rank down projection normally share one pass.
Large rank-32 prefill on SM10x quantizes separately from the down projection; both smooth the input
without materializing an extra activation buffer. The up projection is fused into the GEMM epilogue
before GLU/requant. All dot products accumulate in FP32. `simulate_unfused=True` matches the
BF16 materialization points in ModelOpt's two linear branches and their addition; the default
retains FP32 for the final sum.
Factors use the checkpoint's orientation without pre-folding smoothing or global scales into them.
Weight scales may be affine or pre-swizzled at load time. Gate/up weights and their up-factor rows
must use the usual interleaved order; independent gate/up down factors can be concatenated with
matching zero-padded up factors. Fused gate/up also requires shared smoothing and global scales.
This supports dense BF16 inference with ranks divisible by 32 and K divisible by 64;
routed SVDQuant MoE and Nunchaku's INT4/packed checkpoint layouts are not implemented.
`python bench/bench_moe.py SVDQuant` compares the complete linear on Gemma-4-31B up/down shapes
against FlashInfer, including smoothing, activation quantization and the correction projections.

`repack_ggml_int(payload, "Q4_0" | "Q8_0")` reads canonical little-endian uint8 GGML blocks
`(..., N, K/32, 18 | 34)` and returns the same integer operand tuple with FP32 group scales.
Q8_0 is also a ModelOpt GGML recipe; Q4_0 follows upstream GGML. A GGUF loader supplies the
payload and its logical shape; this helper does not parse containers. IQ codebooks, Q4_1
affine minima, and Q5 formats are unsupported.
GPTQ act-order repacking also returns `b_perm` when K reordering is required.
Fused integer MoE blocks require matching widths, group recipes, and zero-point presence across
gate/up and down. Both projections may use per-channel scales despite their different K dimensions;
scale dtypes and calibrated values may differ between projections.

Routed ops carry the expert dim in front (`(E, ...)` weights and scales). MX scales may instead be
passed **pre-swizzled** — the 5D output of `swizzle_mx_scales` — which is the deployment contract:
Expand Down Expand Up @@ -150,7 +226,9 @@ the rows. `rms_norm_rows` exposes the standalone kernel.
`nvfp4_quantize_two_level(weight)` returns `(packed_e2m1, e4m3_block_scales, fp32_global)` — the
canonical two-level NVFP4 weight quant (`global = amax / (6·448)`). The `*_act_quant` helpers are
the offline activation quants the ops use internally, exposed for pre-quantized (`As`) pipelines;
`fp8_act_quant_block_dynamic` / `fp8_act_quant_tensor_wide` are their block-FP8 siblings.
`fp8_act_quant_block_dynamic` / `fp8_act_quant_tensor_wide` are their block-FP8 siblings;
`fp8_act_quant_tensor_wide(x, K, amax_upper_bound=...)` is the per-token quant of the tensor-wide and
row-wise formats, the optional bound capping each row's amax.

## Autotuning

Expand Down
28 changes: 18 additions & 10 deletions finegrained-kernels/bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ reference implementations, on real model shapes.

## What it compares

Three figure rows, each a **decode | prefill** subplot pair:
One figure contains every model and quantization recipe, including Llama 4 rowwise FP8 and
FP8/integer weight-only cases. Its three rows are **decode | prefill** subplot pairs:

- **quantized** — the MoE at every impl's best: `moe_fused_*` against finegrained-fp8, DeepGEMM,
vLLM, TRT-LLM, `transformers@main` and `triton_kernels`. Each impl contributes the shape it has —
fused where there is one, the two-GEMM shape for `transformers@main`, which has only that. There
is no fused/unfused split in the figure: it is an internal distinction of ours that most of these
baselines do not have. The unfused arms (`moe_unfused_*`) stay in `ARMS`, but no problem's
baseline set enlists them, so they do not run by default.
baselines do not have. The unfused arm is included where a problem requests it.
- **linear quantized** — one qkv-shaped `matmul_2d` linear per model, in its deployment format
- **unquantized (BF16)** — fused vs transformers `grouped_mm`/`batched_mm`, SonicMoE, vLLM,
DeepGEMM BF16 and megablocks
Expand All @@ -27,8 +27,17 @@ torch/cuBLAS path), **SonicMoE**, the OpenAI **triton_kernels** MXFP4 path (GPT-
import-guarded — a missing dependency skips that baseline instead of failing the run.

Every cell runs in three modes: `eager`, `cudagraph` (decode's deployment mode), and
`compile` (`torch.compile(max-autotune, fullgraph)`). A cell that raises is a red ✕ marker;
the others still run.
`compile` (`torch.compile(max-autotune, fullgraph)`). Failures stay in the CSV; the other modes
still run. Decode shows CUDA graph latency and prefill shows eager latency, on logarithmic axes
so slow baselines do not flatten the faster kernels. Missing deployment-mode timings use red ✕
markers; decode falls back to dotted eager bars when available. In both
columns, a hatched outline shows compiled latency when it is faster. Every format uses these
same plotting rules; the ordinary Torch weight-only arm also supplies its compiled measurement.

Each case runs in a fresh process, including single-GPU runs, so an optional baseline's CUDA
fault cannot invalidate later cases. Pinned legacy finegrained-fp8 MX prefill readers use 32-bit
expert offsets: larger-than-2-GiB stacks are unavailable, including through Transformers' wrapper.
The fused reader also faults at the 2-GiB boundary and is excluded there.

On CUDA, every mode cycles through identical weight banks whose **active** weights exceed
**twice the device's L2 cache**. For MoE, active bytes count only the experts selected by the
Expand All @@ -43,7 +52,7 @@ CSV rows record `cache_policy`; logs show bank counts, active bytes and checkpoi
storage. Insufficient memory fails explicitly; it never falls back to warm weights. Replotting
or merging old CSVs without matching cache metadata requires rerunning the benchmark. XPU
retains its existing backend timing behavior and is labeled `backend_default`, not CUDA cold.
Previously generated figures used warm CUDA graph timing; regenerate them for cold-weight comparisons.
The committed figure uses this cold-weight policy.

## The pre-swizzled fast path

Expand All @@ -53,7 +62,7 @@ so its numbers reflect the tcgen05 fast path. Only MX weights on 128-aligned dim
to measure the affine path instead.

Correctness is cross-checked in-run: each baseline's output is compared to the finegrained-kernels
anchor (`parity-vs-finegrained-kernels`, relative L2 distance). What the number means, by regime:
anchor (`parity-vs-finegrained-kernels`, normalized maximum absolute difference). What the number means, by regime:

- **fp8 ~3e-3 / MX ~1e-2 / fp4 ~5e-2** — arms that share our activation quant: only reduction order
and epilogue rounding differ.
Expand All @@ -63,8 +72,7 @@ anchor (`parity-vs-finegrained-kernels`, relative L2 distance). What the number
the two quantized intermediates go through a second GEMM. These are measured values, not a
bound, and this oracle cannot tell that regime apart from a bug of the same magnitude — the model
smokes (generation) and the torch-reference tests carry that.
- **of order 1 (measured 1.3–1.7)** — the outputs are uncorrelated: the relative distance between two
independent same-magnitude vectors is √2. A wrong gate|up row order (pairing the wrong halves in
- **of order 1 (measured 1.3–1.7)** — a likely layout or routing mismatch. A wrong gate|up row order (pairing the wrong halves in
the GLU) or a routing error lands here, which is how the stacked-vs-interleaved feed was caught.

The bench's gate|up weights are STACKED (`[gate; up]`); the finegrained-kernels arms are fed the
Expand All @@ -74,7 +82,7 @@ row-interleaved view (`_interleave_gate_up`) because the kernels read gate|up in

```bash
python bench/bench_moe.py # full grid, single GPU -> bench_moe.csv + bench_moe.png
python bench/bench_moe.py --gpus 8 # shard problems across 8 GPUs (one process per GPU), then merge + plot
python bench/bench_moe.py --gpus 8 # run cases across 8 GPUs, then merge + plot
python bench/bench_moe.py --gpus 5 --devices 3,4,5,6,7 # pin the shards to GPUs 3-7
python bench/bench_moe.py --smoke # fast everything-compiles pass (3-trial tunes, 256-tok prefill)
python bench/bench_moe.py --no-preswizzle # affine MX scales instead of the fast path
Expand Down
Binary file modified finegrained-kernels/bench/bench_moe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading