Skip to content

Flash Next shared down: remove the scatter with direct slot output - #1792

Open
laudney wants to merge 15 commits into
ashhart:mainfrom
laudney:pr-fn-prompt-kernels
Open

laudney wants to merge 15 commits into
ashhart:mainfrom
laudney:pr-fn-prompt-kernels

Conversation

@laudney

@laudney laudney commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

For whole shared-expert outputs (TP1, or TP2 with dense splitting disabled), this PR writes the FP32 down result directly into slot 10 instead of writing a contiguous result and scattering it. The standalone-on-PR1 4218-row measurement is 0.867 → 0.406 ms (2.13x); its served change is small. Default TP2 split output keeps PR2's gather/full-input projection/raw-copy path and does not use the direct-slot kernel.

Refs #1754. Depends on PR2 #1784, which follows frozen PR1 #1771 (6d2fc036). One operator commit above #1784; head 64acba2f8. Incremental diff.

What

  • Add four strided BF16-weight/FP32-output qmmf entries for whole shared down: width 640, output width 2560, 11 slots, shared slot 10. The whole-output path keeps the shared expert on rank 0. Routed slots and the ordered MoE combination keep their bytes.
  • Require a whole output before selecting direct slot output. Default split TP2 retains its BF16 intermediate gather, two interleave copies, full-K640 down projection and raw FP32 output-column copy. A half-output projection must never select the strided kernel.
  • Use planFor to honor pinned K slices. Preserve qmmf arithmetic and K-slice order; change the whole-output address. Keep the contiguous fallback and the 1536-row prompt default.
  • With dependent BF16 Flash Next BF16 prompts: larger CUDA tiles with identical output bytes #1846/Flash Next BF16 prompts: reduce shared staging with K32 loads #1848 loaded, direct output yields to eligible 128-row prompt projection plus scatter at >=512 rows. Forced-direct fixtures retain kernel coverage when automatic selection yields. The gains are not additive.

Gate (GB10, sm_121, CUDA 13.0.88)

Ash's latest landing order is PR1, then PR2, then #1792. The issue requirements also require one kernel replacement per PR, matching bytes, contract records and a planted control. This branch follows #1784; CI is checked separately on its current head.

  • Replacement bytes: the qualified moetile run passes eight row counts, five repeats, unchanged inputs and guards around every slot. moetile-contract gates 1–4: 4 pass, 0 fail; 1 planted control red, 0 missed, against contiguous down plus scatter.
  • Code generation/resources: CUDA/qmmf blobs retain their qualified images: 400 old qmmf entries unchanged, 20 shared entries added across sm_80/86/89/120/121. CUDA 13.0 records match loaded sm_121 entries: 40/38/56/72 registers, zero user static shared bytes and 128-thread bounds.
  • Current ordered host gate: 324/324 pass. This includes the explicitly discovered test-flashnext-compose-host target, 36/36, plus shared/runtime tests. The composition fixture requires a unique BF16 gather, its two immediate interleave copies, full-K640 down and the raw FP32 output copy, and refuses strided half outputs. It permits the valid gate/up reducers at 64 rows and initializes the mock mixed input.
  • Windows fixture compatibility: the four POSIX socket-pair tests now explicitly skip on Windows. The full FlashNext test module cross-compiles for x86_64-windows-gnu; Windows execution remains a separate CI check. The corrected shared target passes 36/36 on Linux, and the full final series passes 228/228 again. Runtime and CUDA code are unchanged.
  • Full-series fixture regression: the unfiltered FlashNext host module passes 228/228 at two seeds. A planted bad guard that enables direct half-output stores is red (35 pass, 1 fail), preserving the witness.
  • Native/consumer scope: the standalone-on-PR1 native suite passes six draft/plain cases, mixed/cancel lifecycle, cold 2k/8k/32k/64k, four concurrent 8k requests and non-drafting cache reuse. Earlier qualified CUDA-runner/ModelOpt-startup compilation passed. The ordered K32 tip now passes 15/15 consumer build steps (tf-cuda-test fast native and tensorfold-native safe native). These use inert images for compilation only; the binaries were not executed.

The full ordered series has the qualified combined runtime and CUDA source; the qualified-series branch changes only two host-test files relative to 64d499e8f for discovery, launch assertions, mock initialization and Windows fixture skips. The earlier TP1 and TP2 split-on, plus TP2 split-off native receipts therefore remain runtime-equivalent; they are not new GPU runs on this head. PR2 #1784 records that composition scope. The canonical combined qmmf image preserves the 450-entry qualified union.

Other physical SMs and later compilers remain unrun. This is not a clean global CUDA-suite claim: the related full contract had 546 numerical passes, 349 red controls, six known findings and 181 inherited resource issues (exit 1). This operator's four loaded entries match their records.

Numbers

These measurements use the standalone shared operator on PR1, before the PR2-first composition:

Scope Before Direct output Meaning
Shared down, 4218 rows 0.867 ms including scatter 0.406 ms 2.13x operator gain
Isolated cold 32k median TTFT 22.251 s 22.129 s 0.55% lower

The operator measurement is above the unchanged 1536-row default. It does not establish default TP2 or full-series speedup; default TP2 takes PR2's full-input column path.

Overlap

Order: #1771 → #1784 → shared #1792 → BF16 #1846 → K32 #1848. HC normalization #1847 and HC mix #1849 form an independent stack on PR2. GDN prefetch and NVFP4 K32-down stay local and off after losses or no consistent default-chunk gain.

#1764 overlaps the BF16/HC implementations. Direct slot output is distinct from its contiguous shared output plus scatter. Choose one canonical BF16 dispatcher before combining the later alternatives, preserving pinned full-input slices and the split-output guard.

@ashhart

ashhart commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Thanks @laudney. Our light gate's only finding on this stack (#1771 + #1784 + this) is the 600-line rule: zig/src/native/cuda.zig reaches 607 on current main (main has 594). The new test "the CUDA server serves --tp, and Flash Next's two ranks pass family admission" accounts for most of it, so moving that test beside the refusals' own tests, or into a small test file, should bring it back under. Everything else in the gate passes, and the stack's served cells are running on a GB10 now against main, with the Flash Next FP4 checkpoint, base/new/new/base.

@laudney

laudney commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @ashhart. Fixed in #1771 (9c7c77262, on top, no force-push): Fish's and Qwen4's --tp refusal moved into refusals.backend(), which open() already calls before either family opens, and the test now sits beside the refusals' own rank tests in cuda_refusal.zig. open() reads as on main, and zig/src/native/cuda.zig is 598 lines with current main merged in (42eb861a4).

Following your plan on #1754, this pull request will be replaced by one pull request per kernel once #1771 lands.

backend() built the ranked table with a runtime call, so at TP=2 it
returned a pointer into its own stack frame and the first prompt
chunk on two GB10s jumped to address 0x25. The table is now built at
comptime like the scoring table, and a test reads it after a real
call and an overwritten stack, so a table in that frame fails it.
Flash Next's two-rank path was built and consumed --tp, but the CUDA
server refused the flag. It now serves --tp, Fish, Qwen4 and LFM2,
which open outside family admission, refuse it with the same words
family() uses, and --idle-unload is refused with --tp on CUDA, since
closing rank 0 ends its followers and a reload would have none.
Two Flash Next ranks only started when the operator set
TENSORFOLD_CHECKPOINT_SHA256, which nothing documented. Each rank now
sends the cached full digest GLM's ranks use, and the variable still
overrides it. A cold digest reads every byte, so the link and its
hello wait 30 minutes for the peer when the digest is computed.
NVIDIA's NVFP4 Flash Next checkpoint keeps its attention, DeltaNet
and shared-expert projections in BF16, so they are absent from
quantized_layers, and the check refused every one of them. Each
projection may now be MXFP8/32 or unlisted, and the parts the loader
stitches into one projection must all be one or the other.
NVIDIA's NVFP4 Flash Next checkpoint stores its n-gram table as E4M3
rows with one BF16 scale for the whole table, which the loader
refused. A third row format reads them as the reference does, each
code times the scale rounded once to BF16, through the mapped
gather, the positional reader, the distinct gather and warm-ahead.
The MTP head of NVIDIA's NVFP4 Flash Next checkpoint keeps its routed
experts in E4M3 with BF16 128x128 block scales, which only an NVFP4
loader read. They now pack for the existing FP8 expert kernels, the
MoE pass dispatches them from a file of their own, and the head's
memory estimate counts them at a byte a weight with their scales; a
device test runs that call at the head's 512 experts, 640 by 2560,
against an f64 reference, as reply equality cannot catch a wrong
draft that verification rejects.
The recipe gains the two-rank command for
nvidia/Qwen3.8-Flash-Next-NVFP4 with what each rank needs: the same
executable, NCCL 2.30 or later, --prompt-cache-gib 0 with drafts and
the digest's first-start cost, and states that concurrent streams do
not yet share a verify pass; the changelog records the branch.
cuda.zig went past the 600-line rule with main's latest lines, so
Fish's and Qwen4's --tp refusal moves into refusals.backend(), which
open() already calls before either family opens, and the test that
the server serves --tp and admits Flash Next's two ranks moves beside
the refusals' own rank tests; open() reads as it does on main.
The checkpoint's BF16 dense and shared layers run main's qmmf BF16
lane and its MTP head's routed experts run main's fp8_experts
kernels, which the contract had no gates 1-4 fixture for at Flash
Next's shapes. A new set runs the lane's BF16-weight product with
bf16 and fp32 outputs at the dense and shared shapes (decode rows),
and the FP8 expert down at decode and prompt pair counts against
the float64 reference, each with a planted 1.5x scale control; the
other experts hold NaN codes, so a misread expert goes red. It joins
the full contract and runs alone as flashnext-modelopt-contract.
At --tp 2 two of six replies differed from one GPU because each
routed NVFP4 expert was cut by intermediate channels and the ranks'
FP32 halves added in the all-reduce. Each rank now loads 256 whole
experts, fn_ep_picks maps the pass's picks to the rank's own ids
with every other id (and the shared slot) skipped, and the rows a
rank skips stay +0, so the all-reduce adds x + 0 and every pair
keeps one GPU's sums. One GPU and the expert kernels are unchanged;
the TP device test now checks the two ranks' sum equals one GPU's
bytes, and fn_ep_picks has its contract fixture, control and nvcc
13.0 records.
The NVFP4 checkpoint's entry now says that at --tp 2 each rank owns
half the routed experts whole, so replies are one GPU's bytes, which
the expert-parallel fix on this branch made true.
PR1 adds three comment warnings against the current upstream base.
Keep the contract descriptions on one line within 120 columns so
Lean accepts the branch without changing test or runtime behavior.
@laudney
laudney force-pushed the pr-fn-prompt-kernels branch from c559187 to b0e3b24 Compare November 1, 2026 14:04
@laudney laudney changed the title Flash Next on CUDA: faster prompt kernels with the same bytes Flash Next CUDA: write shared down directly into its slot Nov 1, 2026
@laudney
laudney force-pushed the pr-fn-prompt-kernels branch from b0e3b24 to ac19ee2 Compare November 1, 2026 15:35
@laudney laudney changed the title Flash Next CUDA: write shared down directly into its slot Flash Next shared down: remove the scatter with direct slot output Nov 1, 2026
Gather full BF16 head and shared inputs so each rank computes its
own output columns with the original full-K slice order. Copy the
disjoint FP32 outputs as raw bits, preserving replies while reducing
duplicated TP2 work and retaining whole routed experts.
Load an independently built full-head image as the GDN byte oracle.
Compare full and half head layouts on finite and mixed non-finite
inputs at both ranks, with repeated outputs and explicit witnesses
so templated kernels cannot hide a shared arithmetic change.
@laudney
laudney force-pushed the pr-fn-prompt-kernels branch from ac19ee2 to 64acba2 Compare November 1, 2026 16:21
Remove the whole shared expert's scatter by writing FP32 results
straight into slot 10. Retain PR2's full-input column copies for
split outputs, honor pinned K slices, and verify both routes with
replacement bytes, controls, records and a host trace. Skip the
POSIX socket fixture on Windows.
@laudney
laudney force-pushed the pr-fn-prompt-kernels branch from 64acba2 to 28260fa Compare November 1, 2026 16:48
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