Skip to content

[None][feat] Update CuTeDSL MegaMoE kernels#16190

Draft
Barry-Delaney wants to merge 10 commits into
NVIDIA:mainfrom
Barry-Delaney:user/jinshik/dsv4-megamoe-cutedsl-adaptive-maxt
Draft

[None][feat] Update CuTeDSL MegaMoE kernels#16190
Barry-Delaney wants to merge 10 commits into
NVIDIA:mainfrom
Barry-Delaney:user/jinshik/dsv4-megamoe-cutedsl-adaptive-maxt

Conversation

@Barry-Delaney

Copy link
Copy Markdown
Collaborator

No description provided.

…FP4)

Integrate the MEGAMOE_CUTEDSL backend (in-kernel NVLink all-to-all fused
dispatch + fc1 + fc2 + combine) for DeepSeek-V4 DEP8 serving:
- adaptive max_tokens_per_rank bucketing (decode launches a small kernel
  instead of padding to the prefill max)
- bf16 / fp8 / fp4 combine formats (fp8 combine may use the bulk fc2 store)
- pure cute.compile JIT launch path (drop the AOT plain-arg path)
- workspace zero-init opt (zero only the required leading bytes, once)
- spin_wait dispatch-barrier loads use acquire semantics
- review fixes: tactic legality, adaptive-bucket overflow, EP fence guard,
  reload-safe source-weight free, single-rank shared-counter init, autotune
  fenced-key prune (freed-workspace ABA), python -O safe EP-group guard
- legacy-lint config for the vendored CuteDSL kernel sources (pinned to
  gitlab bangyus/cutedsl_megamoe @ ce712016)

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
Standalone MPI microbench driving MEGAMOE_CUTEDSL via the public
create_moe -> forward path with synthetic NVFP4 weights. Times the eager
forward by default; --cuda-graph gives a kernel-bound number (CUDA-graph
replay; slowest EP rank, min over iters) matching CUDA-graphed serving.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…he load window

The NVFP4 source expert tensors and the packed mega_* buffers used to be
materialized together at init (~229 GB/rank on DSv4-Pro DEP8), which
cannot fit on 180 GB-class GPUs even though the steady state (~131 GB)
would. Keep the four source tensors (w3_w1_weight, w3_w1_weight_scale,
w2_weight, w2_weight_scale) as 0-element placeholders outside the load
window: create_weights shrinks them via the existing
replace_parameter_and_save_metadata idiom, a load_weights override
rematerializes them one module at a time, and the existing post-load
free block re-shrinks them after packing. Peak becomes steady set plus
one layer of sources. Dynamic-EPLB ranks (need_load_shared_weights)
keep full sources after load exactly as before.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…ion)

- Adaptive-maxT ladder cap: derive from the true per-rank chunk bound
  min(moe_max_num_tokens, max_num_tokens) instead of moe_max // dp_size,
  which under-sized the top bucket for explicit moe_config.max_num_tokens
  and made _select_launch_max_tokens raise on legal scheduler chunks.
- Live-token trim: keep the full bucket for num_tokens == 0 (zero-token
  lockstep launches) so TopkReduce never computes a zero grid.
- Pure-DEP invariant: reject WORLD != EP at construction (matching the
  op's forward-time fence guard) and pre-check before the MPI bootstrap
  collectives so PP/CP hybrids fail loud instead of hanging.
- MPI torch.dist bootstrap: rank-0 free-port draw broadcast
  unconditionally over MPI (fixed 29561 collided across same-node jobs;
  per-rank env gating could desync the collective), loopback fallback
  for unresolvable hostnames.
- Streaming load: keep streamed sources as placeholders in
  pre_reload_weights (a model-wide hot reload re-created the ~229 GB/rank
  init peak), rebind quant_scales right after the per-module source free
  (block scales otherwise accumulate until the model-wide sweep), and
  require full local-expert coverage at finalize when sources were
  rematerialized (uncovered rows are uninitialized).
- Microbench: --autotune now wires AutoTuner distributed state and runs
  a real tuning forward (it only set an env var before, so timed runs
  silently used the fallback tactic), releases the tuning-only symmetric
  scratch before timing, and draws its rendezvous port like the backend.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…g, FC1 SF pad granularity

Two review findings inside the vendored mega_moe_nvfp4 kernel (to be
upstreamed to the kernel team's swapab branch as well):

- token_comm token-back push_sf: promote the fc2_output_sf pool row
  index (and the peer combine_sf_u8 row, for symmetry with the adjacent
  Int64 DATA/metadata paths) to Int64 -- the Int32 offset overflows the
  multi-GiB SF pool at large max_tokens_per_rank x EP.
- kernel_fc12 FC1 weight-SF constexpr check: pad the M side to the
  128-row SF atom (as the FC2 path and the host packer already do)
  instead of the K-side sf_vec_size*4 granularity; behavior-identical
  for 128-aligned shapes like DSv4-Pro.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…ion)

- Autotuner: drop the class-scope tuning-config cache -- TuningConfig
  embeds inputs_pre_hook, a bound method reading the per-call runner's
  _profiling_scratch, so a cache hit handed the autotuner a hook bound
  to a dead runner with a stale/cleared scratch (mixing non-symmetric
  profiling tensors with the live runner's peer offsets: CUDA illegal
  access + peer barrier hang class). The config is rebuilt per call;
  the shape-derivation rule is hoisted to module scope so ConstraintSpec
  hashing stays stable and the AutoTuner's unbounded profile lru_cache
  does not grow per call.
- Streamed-coverage guard: verify per COMPONENT instead of counting
  stash entries -- an entry created by a w1-only shard used to satisfy
  the count while the packing loop skipped it, baking uninitialized
  rows; w2/w2-scale direct-copy loaders now record per-row coverage.
- Dynamic EPLB: correct the free-block comment (the raw sources are
  kept because the inherited load-balancer plumbing registers them as
  migration targets, not because the kernel re-reads them) and emit a
  one-time warning about the ~2x expert-weight residency; derived-only
  migration registration (as MegaMoE-DeepGemm) is the tracked follow-up.
- Full-model RLHF hot reload still accumulates sources until the
  model-wide finalize sweep (documented KNOWN LIMIT; per-bucket
  finalization is order-sensitive and needs its own PR).

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…ImplementedError fallback

The kernel re-import dropped main's separate 'except NotImplementedError'
import handlers (PR NVIDIA#14608) in token_comm / fc1_fc2_fuse_sched /
kernel_fc12 / moe_persistent_scheduler: cutlass wheels on CUDA < 13.1
raise NotImplementedError (not ImportError) from cute.experimental, so
the iket_compat no-op shim never engaged and workspace init died after
the capability probe had declared the backend available. Restored as
SEPARATE handlers on purpose -- the CuteDSL 4.5.0 preprocessor
import-walker raises AttributeError on tuple except types, silently
disabling AST preprocessing for the module. Also restores
iket_compat's logger.debug notice (the re-import had reverted it to a
raw print).

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…eak, EPLB residency, fence ABA, scale-family completeness)

- Hot reload: eager per-module finalize on the partial path -- once a
  module's 4 streamed components and aux scale families are fully
  covered, pack + free immediately instead of accumulating every touched
  layer's sources until the model-wide sweep (a layer-contiguous
  full-model reload now peaks at ~one layer of sources instead of the
  ~229 GB/rank init peak). Aux-only shards for placeholder modules stash
  scales without rematerializing (lazy materialization keyed on the flat
  weights dict); an aux tail after the eager finalize refreshes
  scales/alphas only; interleaved-layer bucket orderings keep the
  sweep-time coverage guard as backstop.
- Dynamic EPLB: derived-only migration registration (the MegaMoE-DeepGemm
  pattern). post_load_weights skips the base raw-weight registration,
  _eplb_migrate_raw_block_scales exempts the parent raw block-scale
  registration (other NVFP4 children unchanged), and the raw source set
  is freed unconditionally -- dynamic EPLB no longer doubles the
  expert-weight residency. bias+dynamic-EPLB now rejected at create time.
- Autotune fence ABA: the barrier-reset fence now drops every fenced
  pairing sharing either workspace side before re-fencing (general-warmup
  real -> profiling scratch -> real transitions no longer skip into a
  phase-decoupled dispatch barrier); capture reaching an unfenced pairing
  fails loud; release_megamoe_profiling_scratch prunes scratch-paired
  fence keys and promotes the winner's workspace out of the tuning-evict
  latch (a later eviction would free a graph-captured pointer).
- Aux scale families: all-or-nothing completeness per family
  (weight_scale_2 / w1+w3 / w2 input_scale) -- a partial family raises
  instead of silently mixing fresh and stale alphas or deriving the
  global input scale from a subset; weights-only reloads keep previous
  scales (fc1_norm_const rebuild skipped on an empty stash).
- Microbench: --autotune now mirrors the serving general-warmup ->
  autotuner interleaving and asserts the fence-pairing invariant.
- New unit test (1 GPU, no model weights): streamed load/reload
  lifecycle -- layer-atomic and split-bucket reloads byte-identical to
  the initial load, aux tails, partial-coverage / partial-family /
  aux-only-without-weights failure modes.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
…n's staged-hook contract

Rebasing onto main crossed the Wave-3 MoE staged-hook migration
(TRTLLM-13248): nothing calls quant_method.post_load_weights anymore --
module.post_load_weights is a compat shim over transform_weights /
cache_derived_state, and the base load_weights tail EAGERLY calls
_finalize_shared_weights (where the raw shared-weight registration now
lives). The derived-only EPLB override therefore moves from
post_load_weights (dead on main) to _finalize_shared_weights: skip the
base raw w3_w1/w2 weight_fns registration (the balancer would IndexError
slicing the freed 0-element placeholders), drop the private CPU staging,
and finalize host sharing -- idempotent across the eager call and the
transform_weights safety net, mirroring the base contract. The
set_initial_weight_assignments + setup_quant_scales tail of the old
override is base cache_derived_state, reached via the module shim.

Also: thin transform_weights / cache_derived_state guards on the backend
(ConfigurableMoE delegates to them directly; base MoE dereferences
quant_method without the create-if-None guard the other entry points
carry), drop the dead import_topk_reduce lazy accessor (the rewritten
topk_reduce exposes the TopkReduce class; the referenced functions no
longer exist), and deduplicate the RcpLimit constant block.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@users.noreply.github.com>
@Barry-Delaney Barry-Delaney self-assigned this Jul 9, 2026
…rdering, alpha consistency, scratch lifecycle, store alignment)

- Hot reload: partial-reload sources now stage on CPU
  (_materialize_source_params gained a device param) and the finalize
  packing uploads one layer transiently -- the reload GPU peak is
  ordering-INDEPENDENT, covering the in-tree RLHF flow that buckets by
  tensor-name suffix across all layers (previously re-accumulated every
  layer's CUDA sources until the last suffix). CPU-staged stash shards
  are alias-broken (an already-CPU input's .to('cpu') is a no-op, and
  the stash is consumed buckets later).
- Aux scale families: bidirectional dependency -- input_scale families
  without weight_scale_2 (and vice versa) are rejected; the alphas
  derive from BOTH, so a one-sided refresh silently mismatches the
  runtime quantization scales. The eager-finalize gate mirrors the
  legal set exactly (w1/w3 input_scale may be omitted).
- Profiling scratch lifecycle: serving never called
  release_megamoe_profiling_scratch on main (the call lived in a
  feat-side model_engine hunk the rebase dropped) -- ported into
  _run_autotuner_warmup per engine, also on the enable_autotuner=False
  early return (MEGAMOE_AUTOTUNE=1 allocates the scratch regardless).
  release now EVICTS tuning-latched workspaces instead of bare-clearing
  the latch (default-tactic-wins left the last loser's multi-GiB
  workspace resident). The spec-decode draft engine lazily re-requests
  its scratch after the target's release (collective, MERGE-lockstep
  safe); multi-rank tuning without a scratch fails loud instead of
  peer-mapping non-symmetric tensors. Eager fences over a shared side
  already baked into a captured graph fail loud (captured pairings
  cannot re-fence).
- Vendored kernel: SF-scale and fp4-wire DATA store pointers now claim
  their true alignment (e8m0 scales are 1 B at row base +1/+2/+3; e2m1
  STG vectors are 16 B at row base +16) instead of a blanket 4/32 --
  the fp8/fp4 combine formats executed under an undefined-behavior
  alignment promise.
- EPLB + hot reload: the backend pre_reload_weights override now
  carries the interface's fail-fast (the module walk bypasses the
  interface guard; latent on main since PR NVIDIA#14608).
- Tests: suffix-ordered multi-module reload (CPU staging + bounded GPU
  peak + bit-exact repack), input-scale-only and no-ws2 rejection
  cases; aux-tail test ships both families.

Signed-off-by: Barry Kang <43644113+Barry-Delaney@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