[NVIDIA] Support flashinfer a2a with flashinfer_trtllm_routed moe (#22394)
This commit is contained in:
@@ -96,6 +96,10 @@ if TYPE_CHECKING:
|
||||
StandardCombineInput,
|
||||
StandardDispatchOutput,
|
||||
)
|
||||
from sglang.srt.layers.moe.token_dispatcher.flashinfer import (
|
||||
FlashinferCombineInput,
|
||||
FlashinferDispatchOutput,
|
||||
)
|
||||
|
||||
if is_flashinfer_available():
|
||||
from sglang.srt.layers.quantization.fp4_utils import fp4_quantize
|
||||
@@ -926,7 +930,18 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
topk_output = dispatch_output.topk_output
|
||||
|
||||
# Quantize hidden states to FP4
|
||||
if quant_info.use_per_token_activation:
|
||||
hidden_states_scale = (
|
||||
dispatch_output.hidden_states_scale
|
||||
if hasattr(dispatch_output, "hidden_states_scale")
|
||||
else None
|
||||
)
|
||||
per_token_scale = None
|
||||
if hidden_states_scale is not None:
|
||||
# NVFP4 dispatch, inputs are already quantized.
|
||||
hs_fp4 = hidden_states
|
||||
hs_scale_linear = hidden_states_scale
|
||||
elif quant_info.use_per_token_activation:
|
||||
# Enable FlashInfer TRTLLM per-token NVFP4 activation scaling; ignores checkpoint activation FP32 scale by treating it as
|
||||
from flashinfer import SfLayout, nvfp4_quantize
|
||||
|
||||
e4m3_max = 448.0
|
||||
@@ -949,7 +964,6 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
seq_len, hidden_size // 16
|
||||
)
|
||||
else:
|
||||
per_token_scale = None
|
||||
hs_fp4, hs_scale_linear = quantize_hidden_states_fp4(
|
||||
hidden_states, quant_info.w13_input_scale_quant
|
||||
)
|
||||
@@ -976,12 +990,15 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
hidden_size = (
|
||||
hs_fp4.shape[-1] * 2 if hs_fp4.dtype == torch.uint8 else hs_fp4.shape[-1]
|
||||
)
|
||||
output_dtype = (
|
||||
hidden_states.dtype if hidden_states_scale is None else torch.bfloat16
|
||||
)
|
||||
_provided = _moe_output_buf.get()
|
||||
_symm_required = is_allocation_symmetric()
|
||||
if (
|
||||
_provided is not None
|
||||
and _provided.shape == (num_tokens, hidden_size)
|
||||
and _provided.dtype == hidden_states.dtype
|
||||
and _provided.dtype == output_dtype
|
||||
and _provided.device == hs_fp4.device
|
||||
and (
|
||||
not _symm_required
|
||||
@@ -995,7 +1012,7 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
symm_output = torch.empty(
|
||||
hs_fp4.shape[0],
|
||||
hidden_size,
|
||||
dtype=hidden_states.dtype,
|
||||
dtype=output_dtype,
|
||||
device=hs_fp4.device,
|
||||
)
|
||||
|
||||
@@ -1285,6 +1302,52 @@ def fused_experts_none_to_flashinfer_trtllm_routed(
|
||||
)
|
||||
|
||||
|
||||
@register_fused_func("flashinfer", "flashinfer_trtllm_routed")
|
||||
def fused_experts_flashinfer_to_flashinfer_trtllm_routed(
|
||||
dispatch_output: FlashinferDispatchOutput,
|
||||
quant_info: MoeQuantInfo,
|
||||
runner_config: MoeRunnerConfig,
|
||||
) -> FlashinferCombineInput:
|
||||
"""Fused function for flashinfer A2A + flashinfer_trtllm_routed runner.
|
||||
|
||||
FlashinferDispatchOutput and StandardDispatchOutput share the same field
|
||||
layout (hidden_states, hidden_states_scale, topk_output), so the existing
|
||||
FP8/FP4/BF16 implementations work unchanged. We wrap the returned
|
||||
StandardCombineInput into a FlashinferCombineInput for the FlashinferDispatcher
|
||||
combine path.
|
||||
"""
|
||||
from sglang.srt.layers.moe.token_dispatcher.flashinfer import (
|
||||
FlashinferCombineInput,
|
||||
)
|
||||
|
||||
if isinstance(quant_info, FlashInferTrtllmFp4MoeQuantInfo):
|
||||
result = fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
dispatch_output,
|
||||
quant_info,
|
||||
runner_config,
|
||||
use_routed_topk=True,
|
||||
)
|
||||
elif isinstance(quant_info, FlashInferTrtllmFp8MoeQuantInfo):
|
||||
result = fused_experts_none_to_flashinfer_trtllm_fp8(
|
||||
dispatch_output,
|
||||
quant_info,
|
||||
runner_config,
|
||||
use_routed_topk=True,
|
||||
)
|
||||
elif isinstance(quant_info, FlashInferTrtllmBf16MoeQuantInfo):
|
||||
result = fused_experts_none_to_flashinfer_trtllm_bf16(
|
||||
dispatch_output,
|
||||
quant_info,
|
||||
runner_config,
|
||||
use_routed_topk=True,
|
||||
)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Unexpected quant_info type for flashinfer a2a + flashinfer_trtllm_routed: {type(quant_info)}"
|
||||
)
|
||||
return FlashinferCombineInput(hidden_states=result.hidden_states)
|
||||
|
||||
|
||||
# Register the experimental experimental_sgl_trtllm MoE fused-func (MoeRunner needs it at
|
||||
# build time even for LoRA); gated by the master switch so the upstream path is untouched.
|
||||
if _SGLANG_EXPERIMENTAL_LORA_OPTI:
|
||||
|
||||
@@ -98,7 +98,11 @@ class FlashinferDispatcher(BaseDispatcher):
|
||||
self.hidden_size = hidden_size
|
||||
self.num_experts = num_experts
|
||||
self.num_local_experts = num_local_experts
|
||||
|
||||
self.invalid_token_expert_id = (
|
||||
-1
|
||||
if get_moe_runner_backend().is_flashinfer_trtllm_routed()
|
||||
else self.num_experts
|
||||
)
|
||||
# TODO: Can other moe runners use payload_in_workspace too?
|
||||
self.payload_in_workspace = get_moe_runner_backend().is_flashinfer_cutlass()
|
||||
|
||||
@@ -164,19 +168,6 @@ class FlashinferDispatcher(BaseDispatcher):
|
||||
mnnvl_config=MnnvlConfig(comm_backend=TorchDistributedCommBackend(group)),
|
||||
)
|
||||
|
||||
self.dummy_topk_ids = torch.full(
|
||||
(1, self.router_topk), self.num_experts, dtype=torch.int32, device="cuda"
|
||||
)
|
||||
self.dummy_topk_ids_current_rank = torch.full(
|
||||
(1, self.router_topk),
|
||||
self.ep_rank * self.num_local_experts,
|
||||
dtype=torch.int32,
|
||||
device="cuda",
|
||||
)
|
||||
self.dummy_topk_weights = torch.zeros(
|
||||
(1, self.router_topk), dtype=torch.float32, device="cuda"
|
||||
)
|
||||
|
||||
@debug_kernel_api
|
||||
def dispatch(
|
||||
self, hidden_states: torch.Tensor, topk_output: TopKOutput
|
||||
@@ -187,15 +178,14 @@ class FlashinferDispatcher(BaseDispatcher):
|
||||
topk_ids = topk_output.topk_ids
|
||||
topk_weights = topk_output.topk_weights
|
||||
|
||||
self.has_dummy_token = x.shape[0] == 0
|
||||
if self.has_dummy_token:
|
||||
x = hidden_states.new_zeros((1, self.hidden_size))
|
||||
topk_ids = self.dummy_topk_ids
|
||||
topk_weights = self.dummy_topk_weights
|
||||
|
||||
global_scale = self.quant_config.get("input_global_scale", None)
|
||||
if global_scale is not None:
|
||||
x, x_sf = fp4_quantize(x, global_scale, is_sf_swizzled_layout=False)
|
||||
if x.shape[0] > 0:
|
||||
x, x_sf = fp4_quantize(x, global_scale, is_sf_swizzled_layout=False)
|
||||
else:
|
||||
x_col = x.shape[1]
|
||||
x = torch.zeros(0, x_col // 2, dtype=torch.uint8, device=x.device)
|
||||
x_sf = torch.zeros(0, x_col // 16, dtype=torch.uint8, device=x.device)
|
||||
|
||||
payloads = []
|
||||
payloads.append(x)
|
||||
@@ -248,8 +238,6 @@ class FlashinferDispatcher(BaseDispatcher):
|
||||
else:
|
||||
# Case 3
|
||||
self.runtime_max_tokens_per_rank = x.shape[0]
|
||||
if self.has_dummy_token:
|
||||
self.runtime_max_tokens_per_rank = max(self.runtime_max_tokens_per_rank, 1)
|
||||
|
||||
# Passing topk_ids + invalid_token_expert_id triggers the sanitize step
|
||||
# inside moe_a2a. The recv buffer has shape
|
||||
@@ -258,10 +246,10 @@ class FlashinferDispatcher(BaseDispatcher):
|
||||
# and waste downstream MoE compute. Sanitizing the padding to a
|
||||
# sentinel id is structural, not optional.
|
||||
recv_tensors = self.moe_a2a.dispatch(
|
||||
self.dummy_topk_ids_current_rank if self.has_dummy_token else topk_ids,
|
||||
topk_ids,
|
||||
payloads,
|
||||
self.runtime_max_tokens_per_rank,
|
||||
invalid_token_expert_id=self.num_experts,
|
||||
invalid_token_expert_id=self.invalid_token_expert_id,
|
||||
expert_id_payload_index=expert_id_payload_index,
|
||||
)
|
||||
if x_sf is not None:
|
||||
@@ -301,9 +289,5 @@ class FlashinferDispatcher(BaseDispatcher):
|
||||
payload_in_workspace=self.payload_in_workspace,
|
||||
)
|
||||
|
||||
if self.has_dummy_token:
|
||||
hidden_states = hidden_states[1:, :]
|
||||
|
||||
del self.runtime_max_tokens_per_rank
|
||||
del self.has_dummy_token
|
||||
return hidden_states
|
||||
|
||||
@@ -5351,9 +5351,10 @@ class ServerArgs:
|
||||
], "The expert parallel size must be 1 or the same as the tensor parallel size"
|
||||
|
||||
if self.moe_runner_backend == "flashinfer_cutedsl":
|
||||
assert self.quantization in [
|
||||
"modelopt_fp4"
|
||||
], f"Invalid quantization '{self.quantization}'. \nFlashInfer CuteDSL MOE currently supports only: 'modelopt_fp4'."
|
||||
assert (
|
||||
self.quantization in ["modelopt_fp4"]
|
||||
or self.get_model_config().nvfp4_moe_meta is not None
|
||||
), f"Invalid quantization '{self.quantization}'. \nFlashInfer CuteDSL MOE currently supports only: 'modelopt_fp4' or hybrid NVFP4 models."
|
||||
assert self.ep_size in [
|
||||
1,
|
||||
self.tp_size,
|
||||
@@ -5574,7 +5575,10 @@ class ServerArgs:
|
||||
)
|
||||
if self.deepep_mode != "auto":
|
||||
logger.warning("--deepep-mode is ignored for Flashinfer MoE A2A")
|
||||
if not envs.SGLANG_MOE_NVFP4_DISPATCH.is_set():
|
||||
if not envs.SGLANG_MOE_NVFP4_DISPATCH.is_set() and (
|
||||
self.quantization == "modelopt_fp4"
|
||||
or self.get_model_config().nvfp4_moe_meta is not None
|
||||
):
|
||||
envs.SGLANG_MOE_NVFP4_DISPATCH.set(True)
|
||||
logger.warning(
|
||||
"SGLANG_MOE_NVFP4_DISPATCH is set to True for Flashinfer MoE A2A"
|
||||
@@ -5582,7 +5586,8 @@ class ServerArgs:
|
||||
assert self.moe_runner_backend in [
|
||||
"flashinfer_cutlass",
|
||||
"flashinfer_cutedsl",
|
||||
], "Flashinfer MoE A2A is only supported with flashinfer_cutlass or flashinfer_cutedsl moe runner backend"
|
||||
"flashinfer_trtllm_routed",
|
||||
], "Flashinfer MoE A2A is only supported with flashinfer_cutlass, flashinfer_cutedsl or flashinfer_trtllm_routed moe runner backend"
|
||||
|
||||
if self.moe_a2a_backend == "mori":
|
||||
self.ep_size = self.tp_size
|
||||
|
||||
Reference in New Issue
Block a user