[AMD][DSV4] perf: bound the MoRI receive buffer during decode (#36130)

This commit is contained in:
karverma-amd
2026-08-27 20:27:52 -07:00
committed by GitHub
parent ce6e1f46b4
commit aa0a0aa3c3
2 changed files with 113 additions and 0 deletions
@@ -2,6 +2,7 @@ from __future__ import annotations
import functools import functools
import inspect import inspect
import logging
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Any, Optional, Union from typing import TYPE_CHECKING, Any, Optional, Union
@@ -36,6 +37,9 @@ if TYPE_CHECKING:
) )
logger = logging.getLogger(__name__)
class AiterQuantType(str, Enum): class AiterQuantType(str, Enum):
NONE = "No" NONE = "No"
PER_TOKEN = "per_Token" PER_TOKEN = "per_Token"
@@ -123,6 +127,98 @@ def _aiter_fused_moe_supports_no_combine() -> bool:
return "no_combine" in inspect.signature(fused_moe).parameters return "no_combine" in inspect.signature(fused_moe).parameters
_RECV_BOUND_LOGGED: set[int] = set()
_RECV_BOUND_WARNED = False
def _warn_recv_bound_unavailable() -> None:
global _RECV_BOUND_WARNED
if not _RECV_BOUND_WARNED:
_RECV_BOUND_WARNED = True
logger.warning(
"SGLANG_MORI_RECV_BOUND is set but the per-rank DP token counts do "
"not cover every mori sender, so the receive fan-in is unknown; "
"leaving the receive buffer unbounded."
)
def _mori_decode_recv_bound(recv_rows: int, topk: int) -> int:
"""Live rows mori's receive buffer can hold in decode, or 0 for "do not bound".
Worst case fan-in is every rank routing all of its tokens to this one, so
`sum(per-rank tokens) * topk`, where topk already includes the fused shared
expert. The per-rank counts come from the DP sync, so this is the fan-in for
the batch actually being run rather than an upper bound over all batches.
That is only sound because enabling this gate also makes
`require_mlp_tp_gather()` true for mori, which gives every rank the same
cuda-graph bucket. The value is baked into a captured graph and has to hold
for every later replay; with per-rank buckets a rank on a narrow tier could
be handed rows by a peer on a wider one, and the only bound valid under that
is the widest tier's -- 4-16x looser than the batch being run, which costs
more in expert-GEMM tiles (M 32/64 -> 128) than the trim saves.
Two cases stay unbounded, because a bound below the real fan-in silently
drops rows from the all-to-all -- wrong output rather than an error:
* Prefill, whose per-rank counts are uneven and not knowable here.
* Anything that leaves the per-rank counts unpopulated, or where the EP world
is wider than the DP world so the counts do not cover every sender.
"""
if not get_bool_env_var("SGLANG_MORI_RECV_BOUND", "false"):
return 0
from sglang.srt.layers.dp_attention import (
get_dp_global_num_tokens,
get_is_extend_in_batch,
)
from sglang.srt.runtime_context import get_parallel
if get_is_extend_in_batch():
return 0
per_rank_tokens = get_dp_global_num_tokens()
ep_size = get_parallel().moe_ep_size
if not per_rank_tokens or len(per_rank_tokens) < ep_size:
# Either the DP sync did not publish counts, or they do not cover every
# mori sender. Both mean the fan-in is unknown here.
_warn_recv_bound_unavailable()
return 0
max_tokens = sum(per_rank_tokens)
bound = max_tokens * topk
# Never grow the tensor, and nothing to do when there is nothing to trim.
if not 0 < bound < recv_rows:
return 0
# One INFO line the first time it engages, so an inert bound is not mistaken
# for an active one in the results. Per-tier values go to DEBUG: capture
# visits every tier, and at INFO on every rank that is dozens of lines.
if get_parallel().tp_rank == 0 and bound not in _RECV_BOUND_LOGGED:
first = not _RECV_BOUND_LOGGED
_RECV_BOUND_LOGGED.add(bound)
if first:
logger.info(
"mori recv bound active: %d rows -> %d for this tier "
"(dp_tokens=%d ep=%d topk=%d); per-tier values at DEBUG",
recv_rows,
bound,
max_tokens,
get_parallel().moe_ep_size,
topk,
)
else:
logger.debug(
"mori recv bound: %d rows -> %d (dp_tokens=%d ep=%d topk=%d)",
recv_rows,
bound,
max_tokens,
get_parallel().moe_ep_size,
topk,
)
return bound
class AiterRunnerCore(MoeRunnerCore): class AiterRunnerCore(MoeRunnerCore):
def run( def run(
self, self,
@@ -322,6 +418,10 @@ def _pre_permute_deepep_to_aiter(
# reads [0, totalRecvTokenNum), so the truncated result needs no # reads [0, totalRecvTokenNum), so the truncated result needs no
# padding back. # padding back.
mori_max = get_int_env_var("SGLANG_MORI_MOE_MAX_INPUT_TOKENS", 0) mori_max = get_int_env_var("SGLANG_MORI_MOE_MAX_INPUT_TOKENS", 0)
if mori_max <= 0:
mori_max = _mori_decode_recv_bound(
hidden_states.shape[0], topk_ids.shape[-1]
)
if mori_max > 0: if mori_max > 0:
hidden_states = hidden_states[:mori_max] hidden_states = hidden_states[:mori_max]
if a1_scale is not None: if a1_scale is not None:
+13
View File
@@ -3753,6 +3753,19 @@ def require_mlp_tp_gather():
# reuse this flag's DP-sync bookkeeping (uniform global_num_tokens + # reuse this flag's DP-sync bookkeeping (uniform global_num_tokens +
# max-based graph bucket). See #30432 re: the misleading flag name. # max-based graph bucket). See #30432 re: the misleading flag name.
return True return True
elif get_moe_a2a_backend().is_mori() and get_bool_env_var(
"SGLANG_MORI_RECV_BOUND", "false"
):
# Same bookkeeping, for the same reason. Bounding mori's receive
# buffer means baking a fan-in size into a captured graph, and the
# fan-in depends on what the *peers* send. Without a DP-synchronized
# bucket every rank buckets its own batch, so a rank on a narrow tier
# can be handed rows by a peer on a wider one; the only bound valid
# under that is the widest tier's, which is 4-16x looser than the
# batch actually being run and costs more in expert-GEMM tiles than
# the trim saves. With uniform buckets the per-tier fan-in is exact.
# Scoped to the opt-in gate so the default path is untouched.
return True
else: else:
return ( return (
get_parallel().moe_dense_tp_size get_parallel().moe_dense_tp_size