Skip to content

[Draft][Quantization][MoE] NVFP4 native expert kernels (raw-layout decode GEMV + grouped prefill) - #44

Draft
01554 wants to merge 16 commits into
lab/upstream-base-20260909from
lab/upstream-native-nvfp4-backend
Draft

01554 wants to merge 16 commits into
lab/upstream-base-20260909from
lab/upstream-native-nvfp4-backend

Conversation

@01554

@01554 01554 commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Upstream extraction, unit 1 of the team plan (notes/upstream-pr-plan-draft-2026-09-09.md). Draft: not ready, not to be merged before two reviews (astra_mid: code/tests; astra: independent diff/deps/GPU).

Base: upstream/main a97dacb (fork branch lab/upstream-base-20260909). Head: 848f0ef (a452302 plus one tests-only commit: the native experts run through the real FusedMoEKernel with the no-DP/EP prepare/finalize and WorkspaceManager on CPU). Dependencies: none.

Scope: new package vllm/model_executor/layers/quantization/nvfp4_native/kernels.py (Triton decode GEMV; wide-load kernel derived from FreeToken kernel/triton/nvfp4_fused_moe.py at af71ba43, Apache-2.0, attributed in file headers), bank.py (raw ModelOpt bank validation, workspace, gemv adapter, CPU reference), prefill_kernels.py + prefill.py (grouped prefill with route alignment), loader.py (keeps checkpoint layout, expands per-expert global scales, drops input scales), experts.py (NativeNvFp4Experts, a FusedMoEExpertsModular: decode GEMV for M <= gemv_rows, grouped prefill above, no EP). Selection: NvFp4MoeBackend.NATIVE via moe_backend="native" only (never auto-selected), a16 quant config like Marlin, ModelOptNvFp4FusedMoE.process_weights_after_loading native branch before the Marlin repack; MoEBackend literal gains "native". Device-resident only. Not in this PR: host/UVA sources, expert cache, runner changes.

Executed (Mac, CPU): ruff, ruff format, mypy, pre-commit on all touched files. Not executed locally: the tests (tests/quantization/nvfp4_native/: kernel oracle tests, loader tests, experts tests) import vllm.model_executor normally and need the full environment; not executed anywhere yet: CUDA runs on this base (kernel smoke, model load with --moe-backend native, graph capture/replay) — pending astra on a clean a97dacb build.

Provenance: FreeToken (Apache-2.0) only; no llama.cpp/ggml code in this PR.

Exact upstream/main a97 wheel (astra, 2026-09-09): a452: CPU 36 passed + 4 subtests, decode 4, grouped 2 + decode 4 PASS; 848f0ef: CPU 37 passed + 4 subtests, 0 skipped (results/upstream-native-exact-848f0efca374-20260909). Real-checkpoint smoke on the integration head lab/upstream-native-ple-mmap 0f1a720 (this PR plus the sync PLE base, 96 GiB, no balloon): load, full capture, two requests, same outputs as the Marlin backend on the same head (results/upstream-marlin-checkpoint-0f1a720f3764-20260909). Not a performance comparison; the 48 GiB configuration is not yet run.

Kernel benchmark on the exact base (astra, 2026-09-09; benchmarks/nvfp4_native on lab/upstream-native-bench 5e7deba; layer 0, 512 experts, top-k 10, synthetic inputs, CUDA-graph replay 30x20, both route patterns; results/upstream-native-bench-exact-5e7debaa3c00-full-20260909): the Marlin backend is faster than this native backend at every shape where both pass. M=1 decode: Marlin 0.0287 ms vs native (through the real kernel + WorkspaceManager) 0.0476 ms; grouped prefill M=2..1024: native 5-15x slower. This PR therefore makes no speed claim. What it offers is the raw-checkpoint layout with no repack (weights, block scales and per-row globals stay expert-major and host-source friendly for the expert cache), an independent oracle-checked path, and full correctness at all 16 shapes; Marlin had 1-5 elements over the prior tolerance at 3 of 16 shapes (M=256/1024); the differences are consistent with BF16 rounding-order effects (one ulp of intermediate terms, on outputs near zero), the cause is not separated, and this is not evidence of a bug or of quality inferiority.

🤖 Generated with Claude Code

https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

01554 added 13 commits September 9, 2026 09:51
…MV and grouped prefill (kernels + tests only)

Extracted from the lab expert-tier branch onto upstream main a97dacb.
Device-resident raw ModelOpt NVFP4 bank, Triton decode GEMV (wide-load
kernel derived from FreeToken nvfp4_fused_moe.py at af71ba43, Apache-2.0,
attributed in file headers) and grouped prefill with route alignment; the
loader keeps the checkpoint layout and expands per-expert global scales.
No backend selection hook yet; CPU tests load the package by file location
so they run without the distributed stack.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…nment skip, no file-location shim

Per review: the formal contract is pytest with normal imports (GPU paths
skip without CUDA); the package lives under vllm.model_executor, so the
tests skip when the distributed stack is unavailable instead of loading
the package by file location.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
… selection and device-resident experts

- NvFp4MoeBackend.NATIVE, mapped from moe_backend="native" (explicit only,
  not auto-selected); 16-bit activations like Marlin's a16 path.
- NativeNvFp4Experts (FusedMoEExpertsModular): raw ModelOpt bank kept on
  device, decode GEMV for M <= gemv_rows, grouped prefill above; router
  weights applied in-kernel, finalize is a no-op; no EP.
- ModelOptNvFp4FusedMoE.process_weights_after_loading takes the native
  branch before the Marlin repack (globals expanded per row, input scales
  dropped).
- loader: lab-runtime dependencies removed; tests use normal imports
  (full environment), UVA smoke case behind --with-uva.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…adapters as FP32 (review)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…h across layers of the same shape (review)

Per-layer persistent prefill scratch would cost ~390 MiB per layer at
4096 tokens x top-10 on the 2560/640 model (~18 GiB over 48 layers);
layers run sequentially, so one workspace per (device, bank shape,
capacities) is enough.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…end=native selection (review)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…, LoRA, bias and SwiGLU parameters explicitly (review)

The kernels compute plain SiLU on BF16 activations; configurations they
cannot honour fail at backend selection and construction instead of
silently computing something else, and quant-config gemm1 parameters are
rejected at weight processing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…ethod's kernel untouched (review)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
… stream, shape), resolved on the forward stream (review)

Stream ordering is the exclusivity argument: layers or models sharing a
stream cannot overlap, a different stream gets its own scratch, and graph
replay reuses the capture stream's scratch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…modular kernel workspace (review)

No process-global buffers: workspace_shapes declares the flat kernel
scratch (max of decode and prefill layouts) and apply() carves the
Workspace views from workspace2, which the worker's WorkspaceManager
provides per execution lane. The experts keep only bank views, an
identity row map and a sticky error flag; kernels warm at weight
processing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…l row count (review)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…ported-reason words (review)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…ip guard (full-environment failures must fail)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…WorkspaceManager path (CPU)

Builds the modular kernel with the no-DP/EP prepare/finalize around the
native experts and runs decode (gemv) and prefill rows through
kernel.apply(), so workspace_shapes() -> WorkspaceManager.get_simultaneous()
-> apply() carving is covered by the manager's own buffer rather than a
hand-allocated scratch. Outputs must equal the direct apply and the manager
must hold at least the declared scratch plus output bytes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
… FusedMoEKernel.apply

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
…(order was swapped)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QWXP5rMj1rGh9xasXNLyT

Signed-off-by: 01554 <24953377+01554@users.noreply.github.com>
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.

1 participant