Skip to content

[Draft][None][fix] MegaMoEDeepGemm: use engine max_num_tokens directly - #14605

Closed
mingyangHao wants to merge 2 commits into
NVIDIA:feat/deepseek_v4from
mingyangHao:fix/megamoe-engine-max-tokens
Closed

[Draft][None][fix] MegaMoEDeepGemm: use engine max_num_tokens directly#14605
mingyangHao wants to merge 2 commits into
NVIDIA:feat/deepseek_v4from
mingyangHao:fix/megamoe-engine-max-tokens

Conversation

@mingyangHao

@mingyangHao mingyangHao commented May 27, 2026

Copy link
Copy Markdown
Collaborator

The MegaMoEDeepGemm wrapper previously read
model_config.moe_max_num_tokens to size its DG SymmBuffer pool and then divided by ep_size (per #14213) to undo the auto-multiply that ModelConfig.__post_init__ applies for allgather-based backends.

Under FUSED_COMM scheduler (always used by mega_moe), FusedCommMoEScheduler._strip_adp_padding slices x to the local rank's tokens before run_moe, so the per-call x.shape[0] is bounded by the engine's per-instance max_num_tokens and never by dp_size * max_num_tokens. The auto-multiply is therefore semantically wrong for mega_moe and the divide is a workaround for the wrong source.

Two failure modes of the multiply-then-divide dance:

  1. If a deployed binary lacks the divide (e.g. binaries built before [None][fix] Avoid dp_size x ep_size double-count in MegaMoEDeepGemm SymmBuffer #14213 landed on this branch), the auto-multiplied value is used as- is, so the cubin's num_max_tokens_per_rank is baked at dp_size * engine.max_num_tokens instead of engine.max_num_tokens (e.g. 65664 vs 8448 on DSv4-Pro @ DEP=8). Measured cost on B300 DEP=8, balanced 8192 tok/rank: 4.03 ms -> 3.39 ms median per-call (-19%), plus the SymmBuffer pool drops from ~3.6 GB to ~470 MB (-7.6x).

  2. If a user explicitly sets moe_config.max_num_tokens in yaml, __post_init__ skips the auto-multiply, but the wrapper still divides, so the per-rank cap becomes yaml_value / ep_size (too small by ep_size). The line-667 runtime assert then fires for every request whose per-rank tokens exceed that.

Read model_config.max_num_tokens directly instead. The other consumers of moe_max_num_tokens (fused_moe_cutlass, fused_moe_deepgemm, communication_factory) keep the auto-multiplied value, which is correct for those allgather paths.

Test plan:

  • pre-commit run clean on the modified file (ruff, ruff-format, codespell, copyright, DCO).
  • TestDeepSeekV4Flash::test_nvfp4_4gpus_static_eplb[MEGAMOE_DEEPGEMM] on 4x B300 NVFP4 + static EPLB: PASSED. lm-eval GSM8K accuracy 95.262 vs reference 95.110 (threshold 91.907). Runtime ~15 min.
  • bench_moe at 8192 tok/rank DEP=8 (--analysis kernels --iters 12 --warmup 2) confirms cubin params after fix: sm100_fp8_fp4_mega_moe_impl<8448, 7168, 3072, 384, 6, 2, 192, ..., 414720, 6635520, ...> with mega_moe per-call median 2.62 ms.

@coderabbitai summary

Description

Test Coverage

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

@mingyangHao
mingyangHao requested a review from a team as a code owner May 27, 2026 02:18
@mingyangHao
mingyangHao requested review from QiJune and removed request for a team May 27, 2026 02:18
@mingyangHao
mingyangHao requested review from Barry-Delaney and lfr-0531 and removed request for QiJune May 27, 2026 02:18
@mingyangHao
mingyangHao requested review from a team as code owners May 28, 2026 08:58
@mingyangHao
mingyangHao requested review from JunyiXu-nv and suyoggupta and removed request for a team May 28, 2026 08:58
The MegaMoEDeepGemm wrapper previously read
``model_config.moe_max_num_tokens`` to size its DG SymmBuffer pool and
then divided by ``ep_size`` (per NVIDIA#14213) to undo the auto-multiply that
``ModelConfig.__post_init__`` applies for allgather-based backends.

Under ``FUSED_COMM`` scheduler (always used by mega_moe),
``FusedCommMoEScheduler._strip_adp_padding`` slices ``x`` to the local
rank's tokens before ``run_moe``, so the per-call ``x.shape[0]`` is
bounded by the engine's per-instance ``max_num_tokens`` and never by
``dp_size * max_num_tokens``. The auto-multiply is therefore semantically
wrong for mega_moe and the divide is a workaround for the wrong source.

Two failure modes of the multiply-then-divide dance:

1. If a deployed binary lacks the divide (e.g. binaries built before
   NVIDIA#14213 landed on this branch), the auto-multiplied value is used as-
   is, so the cubin's ``num_max_tokens_per_rank`` is baked at
   ``dp_size * engine.max_num_tokens`` instead of ``engine.max_num_tokens``
   (e.g. 65664 vs 8448 on DSv4-Pro @ DEP=8). Measured cost on B300 DEP=8,
   balanced 8192 tok/rank: 4.03 ms -> 3.39 ms median per-call (-19%),
   plus the SymmBuffer pool drops from ~3.6 GB to ~470 MB (-7.6x).

2. If a user explicitly sets ``moe_config.max_num_tokens`` in yaml,
   ``__post_init__`` skips the auto-multiply, but the wrapper still
   divides, so the per-rank cap becomes ``yaml_value / ep_size``
   (too small by ep_size). The line-667 runtime assert then fires for
   every request whose per-rank tokens exceed that.

Read ``model_config.max_num_tokens`` directly instead. The other
consumers of ``moe_max_num_tokens`` (fused_moe_cutlass,
fused_moe_deepgemm, communication_factory) keep the auto-multiplied
value, which is correct for those allgather paths.

Test plan:
- ``pre-commit run`` clean on the modified file (ruff, ruff-format,
  codespell, copyright, DCO).
- TestDeepSeekV4Flash::test_nvfp4_4gpus_static_eplb[MEGAMOE_DEEPGEMM]
  on 4x B300 NVFP4 + static EPLB: PASSED. lm-eval GSM8K accuracy
  95.262 vs reference 95.110 (threshold 91.907). Runtime ~15 min.
- bench_moe at 8192 tok/rank DEP=8 (--analysis kernels --iters 12
  --warmup 2) confirms cubin params after fix:
  sm100_fp8_fp4_mega_moe_impl<8448, 7168, 3072, 384, 6, 2, 192, ...,
  414720, 6635520, ...> with mega_moe per-call median 2.62 ms.

Signed-off-by: Mingyang Hao <mingyangh@nvidia.com>
@mingyangHao
mingyangHao force-pushed the fix/megamoe-engine-max-tokens branch from dae6fa8 to 606aa08 Compare May 28, 2026 09:12
@mingyangHao
mingyangHao removed request for a team, JunyiXu-nv and suyoggupta May 28, 2026 09:13
…ime threshold

Adds a new ``MoeConfig.backend = "MEGAMOE_AUTO"`` that picks between
``MegaMoEDeepGemm`` and ``TRTLLMGenFusedMoE`` at module construction
time based on per-rank ``max_num_tokens`` vs a configurable threshold
(default 1024, matching SGLang's
``SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK``).

## Motivation

Measured at DEP=8 on B300 DSv4-Pro W4A8_MXFP4_MXFP8 (bench_moe perfect
balanced, mean kernel time):

| Per-rank tokens | MEGAMOE | TRTLLM-gen NVLINK_ONE_SIDED | DG heuristic E/W |
|-----------------|---------|------------------------------|------------------|
| 1024            | 0.53 ms | 0.84 ms                     | 8                |
| 2048            | 0.90 ms | 1.12 ms                     | 6                |
| 4096            | 1.54 ms | 2.08 ms                     | 4                |
| 8192            | 2.93 ms | 4.04 ms                     | 2                |
| 16384           | 5.85 ms | 9.21 ms                     | 2                |

MEGAMOE wins on bench across this range, but under prod sustained
cross-tray NVLink load (GB300 NVL72, CTX worker spanning 2 trays), the
MEGAMOE fused-collective path absorbs ~1.6x extra overhead while
TRTLLM-gen's separate-kernel pipeline is largely insensitive. Measured
on 0528 prod trace: MEGAMOE 4.66 ms/call at 8k tok/rank (vs bench 2.93
ms). TRTLLM-gen at the same shape in prod is ~3.5 ms (normalized from
the 0512 DEP=4 trace). Crossover sits near 1k-2k tokens/rank in real
deployments.

## Behavior

When ``backend = MEGAMOE_AUTO``:

- If ``model_config.max_num_tokens <= megamoe_max_tokens_per_rank``,
  use ``MegaMoEDeepGemm`` (subject to existing capability gate:
  W4A8_MXFP4_MXFP8 quant, SM100/SM103, bundled DG mega_moe surface).
- Otherwise, use ``TRTLLMGenFusedMoE`` (subject to its existing quant
  capability gate).
- On either gate failure, falls back to ``CutlassFusedMoE`` with a
  warning. Each fallback logs the input it had so misconfigurations
  are diagnosable.

This is module-construction-time selection, not per-call dispatch.
Per-call dispatch would require holding both backend instances with
double SymmBuffer / double expert-weight layouts at runtime (~3 GB/rank
extra HBM on DSv4-Pro). Disagg deployments naturally satisfy the
construction-time-only constraint: the CTX worker's large
``max_num_tokens`` (e.g. 8192) selects TRTLLMGen while the GEN
worker's small ``max_num_tokens`` (e.g. 512) selects MegaMoE — both
from a single yaml that says ``backend: MEGAMOE_AUTO``.

## Files

- ``tensorrt_llm/llmapi/llm_args.py``: Add ``MEGAMOE_AUTO`` to
  ``MoeConfig.backend`` literal; add ``megamoe_max_tokens_per_rank``
  field (default 1024).
- ``tensorrt_llm/_torch/model_config.py``: Pass
  ``megamoe_max_tokens_per_rank`` through ``ModelConfig``.
- ``tensorrt_llm/_torch/pyexecutor/model_loader.py``: Wire from
  ``llm_args.moe_config`` into ``ModelConfig``.
- ``tensorrt_llm/_torch/modules/fused_moe/create_moe.py``: Add the
  ``MEGAMOE_AUTO`` factory branch.

Test plan:
- ``pre-commit run`` clean on all four modified files.
- Spot-check with mock ``model_config``: ``max_num_tokens=512`` ->
  MegaMoEDeepGemm; ``max_num_tokens=8192`` -> TRTLLMGenFusedMoE;
  ``max_num_tokens=1024`` (boundary) -> MegaMoEDeepGemm.
- Follow-up: extend ``TestDeepSeekV4Flash::test_nvfp4_4gpus_static_eplb``
  to parametrize ``MEGAMOE_AUTO`` and verify both branches load + run
  GSM8K on a real prefill/decode mixed yaml.

Signed-off-by: Mingyang Hao <mingyangh@nvidia.com>
@mingyangHao
mingyangHao force-pushed the fix/megamoe-engine-max-tokens branch from 606aa08 to b13075a Compare May 28, 2026 09:26
@mingyangHao
mingyangHao requested a review from a team as a code owner May 28, 2026 09:26
@mingyangHao

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #50751 [ run ] triggered by Bot. Commit: b13075a Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #50751 [ run ] completed with state SUCCESS. Commit: b13075a
/LLM/main/L0_MergeRequest_PR pipeline #40230 completed with status: 'SUCCESS'

CI Report

Link to invocation

@lfr-0531
lfr-0531 requested a review from xxi-nv May 29, 2026 02:23
@xxi-nv xxi-nv changed the title [None][fix] MegaMoEDeepGemm: use engine max_num_tokens directly [Draft][None][fix] MegaMoEDeepGemm: use engine max_num_tokens directly Jun 2, 2026
@xxi-nv

xxi-nv commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Close this PR temporarily.

@xxi-nv xxi-nv closed this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants