[Model] Support Nemotron 3.5 Lightning speculative decoding (#36186)

Co-authored-by: Ryan Stewart <rystewart@nvidia.com>
This commit is contained in:
Baizhou Zhang
2026-08-25 16:43:58 -07:00
committed by GitHub
co-authored by Ryan Stewart
parent 2d88c79b3e
commit 41e7612dee
18 changed files with 792 additions and 122 deletions
@@ -19,7 +19,7 @@ For all methods and hardware platforms, see the [official SGLang installation gu
```bash Command ```bash Command
pip install --upgrade pip pip install --upgrade pip
pip install uv pip install uv
SGLANG_BUILD_RUST_EXTS=none uv pip install --prerelease=allow 'git+https://github.com/sgl-project/sglang.git@refs/pull/33554/head#subdirectory=python' SGLANG_BUILD_RUST_EXTS=none uv pip install --prerelease=allow 'git+https://github.com/sgl-project/sglang.git#subdirectory=python'
``` ```
Then run the **Python** output of the command panel below in that environment. Then run the **Python** output of the command panel below in that environment.
+26 -2
View File
@@ -1482,8 +1482,32 @@ def _nemotron_h_overrides(server_args: Any, hf_config: Any) -> dict:
else: else:
overrides["moe_runner_backend"] = "flashinfer_cutlass" overrides["moe_runner_backend"] = "flashinfer_cutlass"
if is_sm100_supported() and server_args.attention_backend is None: if is_blackwell_supported() and server_args.is_attention_backend_not_set():
overrides["attention_backend"] = "flashinfer" if server_args.speculative_algorithm is not None:
speculative_algorithm = server_args.speculative_algorithm.upper()
if is_sm100_supported() and server_args.speculative_eagle_topk in (
None,
1,
):
overrides["attention_backend"] = "trtllm_mha"
if server_args.page_size is None:
overrides["page_size"] = 64
if server_args.mamba_radix_cache_strategy == "auto":
overrides["mamba_radix_cache_strategy"] = "extra_buffer"
if (
server_args.speculative_draft_attention_backend is None
and speculative_algorithm in ("EAGLE", "NEXTN", "DSPARK")
):
overrides["speculative_draft_attention_backend"] = "trtllm_mha"
else:
overrides["attention_backend"] = "triton"
if (
server_args.speculative_draft_attention_backend is None
and speculative_algorithm in ("EAGLE", "NEXTN", "DFLASH", "DSPARK")
):
overrides["speculative_draft_attention_backend"] = "flashinfer"
elif is_sm100_supported():
overrides["attention_backend"] = "trtllm_mha"
return overrides return overrides
@@ -447,6 +447,21 @@ def _handle_dspark(server_args: ServerArgs) -> None:
speculative_eagle_topk=1, speculative_eagle_topk=1,
) )
from sglang.srt.speculative.dspark_components.dspark_config import (
DEFAULT_DSPARK_GAMMA,
read_draft_checkpoint_config,
)
draft_config = None
try:
draft_config = read_draft_checkpoint_config(server_args=server_args)
except Exception as e:
logger.warning(
"Failed to read DSpark draft config; preserving explicit/default "
"gamma resolution. Error: %s",
e,
)
gamma: Optional[int] = None gamma: Optional[int] = None
if server_args.speculative_dspark_block_size is not None: if server_args.speculative_dspark_block_size is not None:
if int(server_args.speculative_dspark_block_size) <= 0: if int(server_args.speculative_dspark_block_size) <= 0:
@@ -456,19 +471,8 @@ def _handle_dspark(server_args: ServerArgs) -> None:
) )
gamma = int(server_args.speculative_dspark_block_size) gamma = int(server_args.speculative_dspark_block_size)
else: else:
from sglang.srt.speculative.dspark_components.dspark_config import ( if draft_config is not None:
DEFAULT_DSPARK_GAMMA, gamma = draft_config.resolve_gamma(default=None)
read_draft_checkpoint_gamma,
)
try:
gamma = read_draft_checkpoint_gamma(server_args=server_args)
except Exception as e:
logger.warning(
"Failed to read DSpark gamma from draft model config; "
"cannot cross-check --speculative-num-draft-tokens. Error: %s",
e,
)
if gamma is None and server_args.speculative_num_draft_tokens is None: if gamma is None and server_args.speculative_num_draft_tokens is None:
gamma = DEFAULT_DSPARK_GAMMA gamma = DEFAULT_DSPARK_GAMMA
logger.warning( logger.warning(
@@ -1407,6 +1407,7 @@ class ModelOptFp4Config(ModelOptQuantConfig):
"format is experimental and subject to change." "format is experimental and subject to change."
) )
self.is_awq = is_awq self.is_awq = is_awq
self.is_w4a16 = False
self.group_size = group_size self.group_size = group_size
if not is_checkpoint_nvfp4_serialized: if not is_checkpoint_nvfp4_serialized:
if use_per_token_activation: if use_per_token_activation:
@@ -1561,9 +1562,10 @@ class ModelOptFp4Config(ModelOptQuantConfig):
"Expected either flat format (config.json) or nested format (hf_quant_config.json)." "Expected either flat format (config.json) or nested format (hf_quant_config.json)."
) )
if quant_method not in ["FP8", "NVFP4", "NVFP4_AWQ"]: if quant_method not in ["FP8", "NVFP4", "NVFP4_AWQ", "W4A16_NVFP4"]:
raise ValueError( raise ValueError(
"ModelOpt currently only supports: FP8, NVFP4, NVFP4_AWQ " "ModelOpt currently only supports: FP8, NVFP4, NVFP4_AWQ, "
"W4A16_NVFP4 "
"quantizations in sglang. Please check the " "quantizations in sglang. Please check the "
"quantization config for your model's configuration." "quantization config for your model's configuration."
) )
@@ -1579,14 +1581,17 @@ class ModelOptFp4Config(ModelOptQuantConfig):
"NVFP4 quantization requires group_size and exclude_modules " "NVFP4 quantization requires group_size and exclude_modules "
"specified in the quantization config" "specified in the quantization config"
) )
return cls( quant_config = cls(
is_checkpoint_nvfp4_serialized, is_checkpoint_nvfp4_serialized,
kv_cache_quant_algo, kv_cache_quant_algo,
group_size, group_size,
exclude_modules, exclude_modules,
config.get("packed_modules_mapping"), config.get("packed_modules_mapping"),
is_awq="AWQ" in quant_method, is_awq="AWQ" in quant_method,
use_per_token_activation=(False if quant_method == "W4A16_NVFP4" else None),
) )
quant_config.is_w4a16 = quant_method == "W4A16_NVFP4"
return quant_config
def get_quant_method(self, layer: torch.nn.Module, prefix: str): def get_quant_method(self, layer: torch.nn.Module, prefix: str):
from sglang.srt.layers.linear import LinearBase from sglang.srt.layers.linear import LinearBase
@@ -1606,7 +1611,11 @@ class ModelOptFp4Config(ModelOptQuantConfig):
return self._get_quant_method( return self._get_quant_method(
layer, layer,
prefix, prefix,
Linear=ModelOptFp4LinearMethod, Linear=(
ModelOptNvFp4A16LinearMethod
if self.is_w4a16
else ModelOptFp4LinearMethod
),
Moe=ModelOptNvFp4FusedMoEMethod, Moe=ModelOptNvFp4FusedMoEMethod,
) )
@@ -807,6 +807,15 @@ class ModelRunner:
) -> int: ) -> int:
"""Logits rows per decode batch slot.""" """Logits rows per decode batch slot."""
if self.spec_algorithm.is_speculative(): if self.spec_algorithm.is_speculative():
if self.spec_algorithm.is_dspark() and self.is_draft_worker:
from sglang.srt.speculative.dspark_components.dspark_config import (
get_dspark_sample_from_anchor,
)
if not get_dspark_sample_from_anchor(self.model_config.hf_config):
if num_draft_tokens is None:
num_draft_tokens = get_spec().speculative_num_draft_tokens
return int(num_draft_tokens)
return resolve_num_tokens_per_req( return resolve_num_tokens_per_req(
phase="target_verify", phase="target_verify",
spec_algorithm=self.spec_algorithm, spec_algorithm=self.spec_algorithm,
+154 -34
View File
@@ -1,7 +1,6 @@
# Adapted from the DFlash reference implementation (HF) but implemented with # Adapted from the DFlash reference implementation (HF) but implemented with
# SGLang primitives (RadixAttention + SGLang KV cache). This model intentionally # SGLang primitives (RadixAttention + SGLang KV cache). Most drafts borrow the
# does not include token embeddings or an LM head; DFlash uses the target model's # target embedding and LM head; Nemotron 3.5 drafts carry their own embedding.
# embedding/lm_head.
from __future__ import annotations from __future__ import annotations
@@ -21,6 +20,7 @@ from sglang.srt.layers.linear import (
ColumnParallelLinear, ColumnParallelLinear,
MergedColumnParallelLinear, MergedColumnParallelLinear,
QKVParallelLinear, QKVParallelLinear,
ReplicatedLinear,
RowParallelLinear, RowParallelLinear,
) )
from sglang.srt.layers.logits_processor import ( from sglang.srt.layers.logits_processor import (
@@ -29,18 +29,23 @@ from sglang.srt.layers.logits_processor import (
) )
from sglang.srt.layers.radix_attention import AttentionType, RadixAttention from sglang.srt.layers.radix_attention import AttentionType, RadixAttention
from sglang.srt.layers.rotary_embedding import get_rope from sglang.srt.layers.rotary_embedding import get_rope
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
from sglang.srt.model_executor.forward_batch_info import ForwardBatch from sglang.srt.model_executor.forward_batch_info import ForwardBatch
from sglang.srt.model_loader.weight_utils import default_weight_loader from sglang.srt.model_loader.weight_utils import (
default_weight_loader,
sharded_weight_loader,
)
from sglang.srt.models.utils import apply_qk_norm from sglang.srt.models.utils import apply_qk_norm
from sglang.srt.runtime_context import get_parallel from sglang.srt.runtime_context import get_parallel, get_spec
from sglang.srt.speculative.dflash_utils import ( from sglang.srt.speculative.dflash_utils import (
can_dflash_slice_qkv_weight, can_dflash_slice_qkv_weight,
get_dflash_attention_sliding_window_size, get_dflash_attention_sliding_window_size,
get_dflash_layer_types, get_dflash_layer_types,
is_dense_head_weight, is_dense_head_weight,
is_nemotron_35_draft_config,
parse_dflash_draft_config, parse_dflash_draft_config,
) )
from sglang.srt.utils import is_npu from sglang.srt.utils import is_npu, set_weight_attrs
from sglang.srt.utils.common import get_compiler_backend from sglang.srt.utils.common import get_compiler_backend
from sglang.srt.utils.hf_transformers_utils import get_rope_config from sglang.srt.utils.hf_transformers_utils import get_rope_config
@@ -62,6 +67,24 @@ def _radix_topk(scores: torch.Tensor, k: int) -> Tuple[torch.Tensor, torch.Tenso
return torch.topk(scores, k, dim=-1) return torch.topk(scores, k, dim=-1)
def _logical_linear_weight_shape(
param: torch.Tensor,
loaded_weight: torch.Tensor,
*,
output_features: int,
) -> Tuple[int, ...]:
"""Return a checkpoint linear weight shape in logical elements."""
loaded_shape = tuple(loaded_weight.shape)
pack_factor = getattr(param, "pack_factor", None)
if pack_factor is None or loaded_shape != tuple(param.shape):
return loaded_shape
logical_numel = int(loaded_weight.numel() * pack_factor)
if logical_numel % output_features == 0:
return (output_features, logical_numel // output_features)
return (logical_numel,)
def _project_candidate_logits( def _project_candidate_logits(
hidden: torch.Tensor, lm_head: nn.Module, *, num_org: int, use_quant_head: bool hidden: torch.Tensor, lm_head: nn.Module, *, num_org: int, use_quant_head: bool
) -> torch.Tensor: ) -> torch.Tensor:
@@ -117,7 +140,9 @@ def _get_dflash_layer_attention_params(
class DFlashAttention(nn.Module): class DFlashAttention(nn.Module):
def __init__(self, config, layer_id: int, quant_config=None) -> None: def __init__(
self, config, layer_id: int, quant_config=None, prefix: str = ""
) -> None:
super().__init__() super().__init__()
hidden_size = int(config.hidden_size) hidden_size = int(config.hidden_size)
tp_size = int(get_parallel().tp_size) tp_size = int(get_parallel().tp_size)
@@ -160,14 +185,14 @@ class DFlashAttention(nn.Module):
total_num_kv_heads=self.total_num_kv_heads, total_num_kv_heads=self.total_num_kv_heads,
bias=attention_bias, bias=attention_bias,
quant_config=quant_config, quant_config=quant_config,
prefix="qkv_proj", prefix=f"{prefix}.qkv_proj" if prefix else "qkv_proj",
) )
self.o_proj = RowParallelLinear( self.o_proj = RowParallelLinear(
self.total_num_heads * head_dim, self.total_num_heads * head_dim,
hidden_size, hidden_size,
bias=attention_bias, bias=attention_bias,
quant_config=quant_config, quant_config=quant_config,
prefix="o_proj", prefix=f"{prefix}.o_proj" if prefix else "o_proj",
) )
# Per-head Q/K RMSNorm, matching HF Qwen3. # Per-head Q/K RMSNorm, matching HF Qwen3.
@@ -201,6 +226,24 @@ class DFlashAttention(nn.Module):
self.sliding_window_size, self.attn_type = _get_dflash_layer_attention_params( self.sliding_window_size, self.attn_type = _get_dflash_layer_attention_params(
config, layer_id config, layer_id
) )
self.attention_sink_bias = None
if is_nemotron_35_draft_config(config) and bool(
getattr(config, "attention_sink_bias", False)
):
draft_attention_backend = get_spec().speculative_draft_attention_backend
if draft_attention_backend != "trtllm_mha":
raise ValueError(
"Nemotron 3.5 DSpark attention sinks require "
"--speculative-draft-attention-backend trtllm_mha, "
f"got {draft_attention_backend!r}."
)
self.attention_sink_bias = nn.Parameter(
torch.empty(self.num_heads, dtype=torch.float32), requires_grad=False
)
set_weight_attrs(
self.attention_sink_bias,
{"weight_loader": sharded_weight_loader(0)},
)
self.attn = RadixAttention( self.attn = RadixAttention(
num_heads=self.num_heads, num_heads=self.num_heads,
head_dim=head_dim, head_dim=head_dim,
@@ -259,7 +302,12 @@ class DFlashAttention(nn.Module):
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
q, k = apply_qk_norm(q, k, self.q_norm, self.k_norm, self.head_dim) q, k = apply_qk_norm(q, k, self.q_norm, self.k_norm, self.head_dim)
q, k = self.rotary_emb(positions, q, k) q, k = self.rotary_emb(positions, q, k)
attn_output = self.attn(q, k, v, forward_batch) if self.attention_sink_bias is None:
attn_output = self.attn(q, k, v, forward_batch)
else:
attn_output = self.attn(
q, k, v, forward_batch, sinks=self.attention_sink_bias
)
attn_output = self.apply_attention_output(attn_output, hidden_states) attn_output = self.apply_attention_output(attn_output, hidden_states)
output, _ = self.o_proj(attn_output) output, _ = self.o_proj(attn_output)
return output return output
@@ -428,17 +476,25 @@ class DFlashDecoderLayer(nn.Module):
attention_conv: Optional[DFlashGroupedConv] = None, attention_conv: Optional[DFlashGroupedConv] = None,
mlp_conv: Optional[DFlashGroupedConv] = None, mlp_conv: Optional[DFlashGroupedConv] = None,
quant_config=None, quant_config=None,
prefix: str = "",
) -> None: ) -> None:
super().__init__() super().__init__()
hidden_size = int(config.hidden_size) hidden_size = int(config.hidden_size)
rms_norm_eps = float(getattr(config, "rms_norm_eps", 1e-6)) rms_norm_eps = float(getattr(config, "rms_norm_eps", 1e-6))
self.input_layernorm = RMSNorm(hidden_size, eps=rms_norm_eps) self.input_layernorm = RMSNorm(hidden_size, eps=rms_norm_eps)
attention_prefix = f"{prefix}.self_attn" if prefix else ""
self.self_attn = self.attention_cls( self.self_attn = self.attention_cls(
config=config, layer_id=layer_id, quant_config=quant_config config=config,
layer_id=layer_id,
quant_config=quant_config,
prefix=attention_prefix,
) )
self.post_attention_layernorm = RMSNorm(hidden_size, eps=rms_norm_eps) self.post_attention_layernorm = RMSNorm(hidden_size, eps=rms_norm_eps)
self.mlp = DFlashMLP(config=config, quant_config=quant_config) mlp_prefix = f"{prefix}.mlp" if prefix else ""
self.mlp = DFlashMLP(
config=config, quant_config=quant_config, prefix=mlp_prefix
)
self.attention_conv = attention_conv self.attention_conv = attention_conv
self.mlp_conv = mlp_conv self.mlp_conv = mlp_conv
@@ -487,7 +543,7 @@ class DFlashDecoderLayer(nn.Module):
class DFlashDraftModel(nn.Module): class DFlashDraftModel(nn.Module):
"""SGLang DFlash draft model (no embedding / lm_head weights). """SGLang DFlash draft model with an optional Nemotron embedding.
The checkpoint provides: The checkpoint provides:
- transformer weights for `layers.*` - transformer weights for `layers.*`
@@ -510,6 +566,16 @@ class DFlashDraftModel(nn.Module):
) )
self.block_size = draft_config.resolve_block_size(default=16) self.block_size = draft_config.resolve_block_size(default=16)
self.candidate_selector: Optional[nn.Module] = None self.candidate_selector: Optional[nn.Module] = None
self.is_nemotron_35_draft = is_nemotron_35_draft_config(config)
self.embed_tokens: Optional[VocabParallelEmbedding] = None
if self.is_nemotron_35_draft:
embed_prefix = f"{prefix}.embed_tokens" if prefix else "embed_tokens"
self.embed_tokens = VocabParallelEmbedding(
config.vocab_size,
hidden_size,
quant_config=quant_config,
prefix=embed_prefix,
)
def grouped_conv(): def grouped_conv():
if not draft_config.conv_kernel_size: if not draft_config.conv_kernel_size:
@@ -529,6 +595,11 @@ class DFlashDraftModel(nn.Module):
attention_conv=grouped_conv(), attention_conv=grouped_conv(),
mlp_conv=grouped_conv(), mlp_conv=grouped_conv(),
quant_config=quant_config, quant_config=quant_config,
prefix=(
(f"{prefix}.layers.{i}" if prefix else f"layers.{i}")
if self.is_nemotron_35_draft
else ""
),
) )
for i in range(num_layers) for i in range(num_layers)
] ]
@@ -550,9 +621,19 @@ class DFlashDraftModel(nn.Module):
num_context_features = len(target_layer_ids) num_context_features = len(target_layer_ids)
self.num_context_features = int(num_context_features) self.num_context_features = int(num_context_features)
self.fc = nn.Linear( if self.is_nemotron_35_draft:
self.num_context_features * hidden_size, hidden_size, bias=False fc_prefix = f"{prefix}.fc" if prefix else "fc"
) self.fc = ReplicatedLinear(
self.num_context_features * hidden_size,
hidden_size,
bias=False,
quant_config=quant_config,
prefix=fc_prefix,
)
else:
self.fc = nn.Linear(
self.num_context_features * hidden_size, hidden_size, bias=False
)
self.hidden_norm = RMSNorm(hidden_size, eps=rms_norm_eps) self.hidden_norm = RMSNorm(hidden_size, eps=rms_norm_eps)
def set_block_size(self, block_size: int) -> None: def set_block_size(self, block_size: int) -> None:
@@ -571,6 +652,9 @@ class DFlashDraftModel(nn.Module):
def get_attention_sliding_window_size(self) -> Optional[int]: def get_attention_sliding_window_size(self) -> Optional[int]:
return get_dflash_attention_sliding_window_size(self.config) return get_dflash_attention_sliding_window_size(self.config)
def get_input_embeddings(self) -> Optional[VocabParallelEmbedding]:
return self.embed_tokens
def prepare_context_hidden_for_kv( def prepare_context_hidden_for_kv(
self, layer: DFlashDecoderLayer, ctx_hidden: torch.Tensor self, layer: DFlashDecoderLayer, ctx_hidden: torch.Tensor
) -> torch.Tensor: ) -> torch.Tensor:
@@ -578,7 +662,9 @@ class DFlashDraftModel(nn.Module):
def project_target_hidden(self, target_hidden: torch.Tensor) -> torch.Tensor: def project_target_hidden(self, target_hidden: torch.Tensor) -> torch.Tensor:
"""Project concatenated target-layer hidden states into draft hidden_size.""" """Project concatenated target-layer hidden states into draft hidden_size."""
expected = int(self.fc.in_features) expected = int(
self.fc.input_size if self.is_nemotron_35_draft else self.fc.in_features
)
if target_hidden.ndim != 2 or int(target_hidden.shape[-1]) != expected: if target_hidden.ndim != 2 or int(target_hidden.shape[-1]) != expected:
raise ValueError( raise ValueError(
"DFLASH target_hidden feature dim mismatch. " "DFLASH target_hidden feature dim mismatch. "
@@ -588,7 +674,10 @@ class DFlashDraftModel(nn.Module):
"This usually means the target model is capturing a different number of layer features than " "This usually means the target model is capturing a different number of layer features than "
"the draft checkpoint/config expects." "the draft checkpoint/config expects."
) )
return self.hidden_norm(self.fc(target_hidden)) projected = self.fc(target_hidden)
if self.is_nemotron_35_draft:
projected = projected[0]
return self.hidden_norm(projected)
@torch.no_grad() @torch.no_grad()
def forward( def forward(
@@ -601,7 +690,9 @@ class DFlashDraftModel(nn.Module):
pp_proxy_tensors=None, pp_proxy_tensors=None,
) -> LogitsProcessorOutput: ) -> LogitsProcessorOutput:
if input_embeds is None: if input_embeds is None:
if hasattr(self, "forward_embed"): if self.embed_tokens is not None:
input_embeds = self.embed_tokens(input_ids)
elif hasattr(self, "forward_embed"):
input_embeds = self.forward_embed(input_ids) input_embeds = self.forward_embed(input_ids)
else: else:
raise ValueError( raise ValueError(
@@ -679,16 +770,33 @@ class DFlashDraftModel(nn.Module):
# Ignore unexpected weights (e.g., HF rotary caches). # Ignore unexpected weights (e.g., HF rotary caches).
continue continue
param = params_dict[resolved_name] param = params_dict[resolved_name]
if resolved_name.endswith("fc.weight") and tuple( if resolved_name.endswith("fc.weight"):
loaded_weight.shape if self.is_nemotron_35_draft:
) != tuple(param.shape): expected_shape = (
raise ValueError( int(self.config.hidden_size),
"DFLASH fc.weight shape mismatch. This usually means the draft checkpoint's " int(self.num_context_features * self.config.hidden_size),
"number of context features (K) does not match this config. " )
f"Expected fc.weight.shape={tuple(param.shape)} " loaded_shape = _logical_linear_weight_shape(
f"(num_context_features={self.num_context_features}, hidden_size={int(self.config.hidden_size)}), " param,
f"but got {tuple(loaded_weight.shape)} for weight '{name}'." loaded_weight,
) output_features=expected_shape[0],
)
shape_matches = loaded_shape == expected_shape or (
getattr(param, "pack_factor", None) is None
and tuple(loaded_weight.shape) == tuple(param.shape)
)
else:
expected_shape = tuple(param.shape)
loaded_shape = tuple(loaded_weight.shape)
shape_matches = loaded_shape == expected_shape
if not shape_matches:
raise ValueError(
"DFLASH fc.weight shape mismatch. This usually means the draft checkpoint's "
"number of context features (K) does not match this config. "
f"Expected fc.weight.shape={expected_shape} "
f"(num_context_features={self.num_context_features}, hidden_size={int(self.config.hidden_size)}), "
f"but got {loaded_shape} for weight '{name}'."
)
weight_loader = getattr(param, "weight_loader", default_weight_loader) weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight) weight_loader(param, loaded_weight)
@@ -696,8 +804,15 @@ class DFlashDraftModel(nn.Module):
class DFlashLagunaAttention(DFlashAttention): class DFlashLagunaAttention(DFlashAttention):
"""Laguna DFlash attention with the trained Laguna softplus gate.""" """Laguna DFlash attention with the trained Laguna softplus gate."""
def __init__(self, config, layer_id: int, quant_config=None) -> None: def __init__(
super().__init__(config=config, layer_id=layer_id, quant_config=quant_config) self, config, layer_id: int, quant_config=None, prefix: str = ""
) -> None:
super().__init__(
config=config,
layer_id=layer_id,
quant_config=quant_config,
prefix=prefix,
)
hidden_size = int(config.hidden_size) hidden_size = int(config.hidden_size)
total_num_heads = self.total_num_heads total_num_heads = self.total_num_heads
gating = normalize_gating(getattr(config, "gating", True)) gating = normalize_gating(getattr(config, "gating", True))
@@ -716,7 +831,7 @@ class DFlashLagunaAttention(DFlashAttention):
g_out, g_out,
bias=False, bias=False,
quant_config=quant_config, quant_config=quant_config,
prefix="g_proj", prefix=f"{prefix}.g_proj" if prefix else "g_proj",
) )
def apply_attention_output( def apply_attention_output(
@@ -764,7 +879,9 @@ class DFlashLagunaForCausalLM(DFlashDraftModel):
return layer.input_layernorm(ctx_hidden) return layer.input_layernorm(ctx_hidden)
def project_target_hidden(self, target_hidden: torch.Tensor) -> torch.Tensor: def project_target_hidden(self, target_hidden: torch.Tensor) -> torch.Tensor:
expected = int(self.fc.in_features) expected = int(
self.fc.input_size if self.is_nemotron_35_draft else self.fc.in_features
)
if target_hidden.ndim != 2 or int(target_hidden.shape[-1]) != expected: if target_hidden.ndim != 2 or int(target_hidden.shape[-1]) != expected:
raise ValueError( raise ValueError(
"Laguna DFLASH target_hidden feature dim mismatch. " "Laguna DFLASH target_hidden feature dim mismatch. "
@@ -783,7 +900,10 @@ class DFlashLagunaForCausalLM(DFlashDraftModel):
for i, norm in enumerate(self.aux_hidden_norms): for i, norm in enumerate(self.aux_hidden_norms):
normed[:, i, :] = norm(slices[:, i, :]) normed[:, i, :] = norm(slices[:, i, :])
fused = normed.reshape(target_hidden.shape[0], -1) fused = normed.reshape(target_hidden.shape[0], -1)
return self.hidden_norm(self.fc(fused)) projected = self.fc(fused)
if self.is_nemotron_35_draft:
projected = projected[0]
return self.hidden_norm(projected)
@torch.compile(dynamic=True, backend=get_compiler_backend(), disable=_is_npu) @torch.compile(dynamic=True, backend=get_compiler_backend(), disable=_is_npu)
+69 -8
View File
@@ -9,11 +9,13 @@ from torch import nn
from sglang.srt.distributed.communication_op import tensor_model_parallel_all_gather from sglang.srt.distributed.communication_op import tensor_model_parallel_all_gather
from sglang.srt.environ import envs from sglang.srt.environ import envs
from sglang.srt.layers.linear import ReplicatedLinear
from sglang.srt.layers.logits_processor import should_apply_lm_head_quant_method from sglang.srt.layers.logits_processor import should_apply_lm_head_quant_method
from sglang.srt.model_loader.weight_utils import default_weight_loader from sglang.srt.model_loader.weight_utils import default_weight_loader
from sglang.srt.models.dflash import DFlashDraftModel from sglang.srt.models.dflash import DFlashDraftModel
from sglang.srt.speculative.dflash_utils import can_dflash_slice_qkv_weight from sglang.srt.speculative.dflash_utils import can_dflash_slice_qkv_weight
from sglang.srt.speculative.dspark_components.dspark_config import ( from sglang.srt.speculative.dspark_components.dspark_config import (
get_dspark_sample_from_anchor,
parse_dspark_draft_config, parse_dspark_draft_config,
) )
from sglang.srt.speculative.ragged_verify import ( from sglang.srt.speculative.ragged_verify import (
@@ -142,6 +144,39 @@ class VanillaMarkov(nn.Module):
) )
class Nemotron35VanillaMarkov(VanillaMarkov):
"""Checkpoint-quantized Markov head used only by Nemotron 3.5 DSpark."""
def __init__(
self,
*,
vocab_size: int,
markov_rank: int,
quant_config,
prefix: str,
) -> None:
nn.Module.__init__(self)
self.vocab_size = int(vocab_size)
self.markov_rank = int(markov_rank)
if self.markov_rank <= 0:
raise ValueError(
"Nemotron35VanillaMarkov requires markov_rank > 0, "
f"got {self.markov_rank}."
)
self.markov_w1 = nn.Embedding(self.vocab_size, self.markov_rank)
self.markov_w2 = ReplicatedLinear(
self.markov_rank,
self.vocab_size,
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.markov_w2" if prefix else "markov_w2",
)
def project_bias(self, latent_states: torch.Tensor) -> torch.Tensor:
bias, _ = self.markov_w2(latent_states)
return bias
class GatedMarkovHead(VanillaMarkov): class GatedMarkovHead(VanillaMarkov):
markov_head_type = "gated" markov_head_type = "gated"
@@ -298,6 +333,22 @@ def build_markov_head(config) -> Optional[nn.Module]:
raise ValueError(f"Unsupported DSpark markov_head_type={markov_head_type!r}.") raise ValueError(f"Unsupported DSpark markov_head_type={markov_head_type!r}.")
def build_nemotron_35_markov_head(config, quant_config, prefix: str) -> nn.Module:
markov_head_type = str(getattr(config, "markov_head_type", "vanilla")).lower()
if markov_head_type != "vanilla":
raise ValueError(
"Nemotron 3.5 DSpark requires markov_head_type='vanilla', "
f"got {markov_head_type!r}."
)
markov_prefix = f"{prefix}.markov_head" if prefix else "markov_head"
return Nemotron35VanillaMarkov(
vocab_size=int(config.vocab_size),
markov_rank=int(config.markov_rank),
quant_config=quant_config,
prefix=markov_prefix,
)
class DSparkConfidenceHead(nn.Module): class DSparkConfidenceHead(nn.Module):
def __init__( def __init__(
@@ -365,11 +416,7 @@ def build_confidence_head(config) -> Optional[nn.Module]:
) )
_DSPARK_SKIPPED_WEIGHT_PREFIXES = ( _DSPARK_SKIPPED_WEIGHT_PREFIXES = ("lm_head.", "rotary_emb.")
"embed_tokens.",
"lm_head.",
"rotary_emb.",
)
class DSparkDraftMixin: class DSparkDraftMixin:
@@ -385,14 +432,21 @@ class DSparkDraftMixin:
f"got markov_rank={dspark_config.markov_rank}." f"got markov_rank={dspark_config.markov_rank}."
) )
self.gamma = int(dspark_config.resolve_gamma(default=self.block_size)) self.gamma = int(dspark_config.resolve_gamma(default=self.block_size))
self.markov_head = build_markov_head(config) self.sample_from_anchor = get_dspark_sample_from_anchor(config)
if self.is_nemotron_35_draft:
self.markov_head = build_nemotron_35_markov_head(
config, quant_config, prefix
)
else:
self.markov_head = build_markov_head(config)
self.confidence_head = build_confidence_head(config) self.confidence_head = build_confidence_head(config)
self.lm_head: Optional[nn.Module] = None self.lm_head: Optional[nn.Module] = None
def attach_shared_modules( def attach_shared_modules(
self, *, embed_tokens: nn.Module, lm_head: nn.Module self, *, embed_tokens: nn.Module, lm_head: nn.Module
) -> None: ) -> None:
self.embed_tokens = embed_tokens if not self.is_nemotron_35_draft:
self.embed_tokens = embed_tokens
self.lm_head = lm_head self.lm_head = lm_head
def forward_embed(self, input_ids: torch.Tensor) -> torch.Tensor: def forward_embed(self, input_ids: torch.Tensor) -> torch.Tensor:
@@ -430,7 +484,14 @@ class DSparkDraftMixin:
backbone_weights = [] backbone_weights = []
params_dict = dict(self.named_parameters()) params_dict = dict(self.named_parameters())
for name, loaded_weight in weights: for name, loaded_weight in weights:
if any(name.startswith(p) for p in _DSPARK_SKIPPED_WEIGHT_PREFIXES): normalized_name = name.removeprefix("model.")
if any(
normalized_name.startswith(p) for p in _DSPARK_SKIPPED_WEIGHT_PREFIXES
):
continue
if normalized_name.startswith("embed_tokens.") and not (
self.is_nemotron_35_draft
):
continue continue
if name.startswith("confidence_head."): if name.startswith("confidence_head."):
if self.confidence_head is None: if self.confidence_head is None:
+42 -1
View File
@@ -833,6 +833,7 @@ class NemotronHModel(nn.Module):
self.norm_f = RMSNorm(config.hidden_size, eps=config.layer_norm_epsilon) self.norm_f = RMSNorm(config.hidden_size, eps=config.layer_norm_epsilon)
else: else:
self.norm_f = PPMissingLayer(return_tuple=True) self.norm_f = PPMissingLayer(return_tuple=True)
self.layers_to_capture: set[int] = set()
def forward( def forward(
self, self,
@@ -853,7 +854,17 @@ class NemotronHModel(nn.Module):
hidden_states = pp_proxy_tensors["hidden_states"] hidden_states = pp_proxy_tensors["hidden_states"]
residual = pp_proxy_tensors["residual"] residual = pp_proxy_tensors["residual"]
aux_hidden_states = []
for i in range(self.start_layer, self.end_layer): for i in range(self.start_layer, self.end_layer):
if i in self.layers_to_capture:
if residual is not None and getattr(
hidden_states, "_sglang_needs_allreduce_fusion", False
):
hidden_states = tensor_model_parallel_all_reduce(hidden_states)
hidden_states._sglang_needs_allreduce_fusion = False
aux_hidden_states.append(
hidden_states if residual is None else hidden_states + residual
)
layer = self.layers[i] layer = self.layers[i]
if not isinstance(layer, Layers): if not isinstance(layer, Layers):
raise ValueError(f"Unknown layer type: {type(layer)}") raise ValueError(f"Unknown layer type: {type(layer)}")
@@ -867,7 +878,18 @@ class NemotronHModel(nn.Module):
return PPProxyTensors( return PPProxyTensors(
{"hidden_states": hidden_states, "residual": residual} {"hidden_states": hidden_states, "residual": residual}
) )
if self.end_layer in self.layers_to_capture:
if residual is not None and getattr(
hidden_states, "_sglang_needs_allreduce_fusion", False
):
hidden_states = tensor_model_parallel_all_reduce(hidden_states)
hidden_states._sglang_needs_allreduce_fusion = False
aux_hidden_states.append(
hidden_states if residual is None else hidden_states + residual
)
hidden_states, _ = self.norm_f(hidden_states, residual) hidden_states, _ = self.norm_f(hidden_states, residual)
if aux_hidden_states:
return hidden_states, aux_hidden_states
return hidden_states return hidden_states
@@ -962,6 +984,7 @@ class NemotronHForCausalLM(nn.Module):
self.lm_head.weight.copy_(emb_token_weight) self.lm_head.weight.copy_(emb_token_weight)
self.logits_processor = LogitsProcessor(config) self.logits_processor = LogitsProcessor(config)
self.capture_aux_hidden_states = False
def _init_model( def _init_model(
self, self,
@@ -1082,9 +1105,16 @@ class NemotronHForCausalLM(nn.Module):
hidden_states = self.model.forward( hidden_states = self.model.forward(
input_ids, positions, forward_batch, pp_proxy_tensors, input_embeds input_ids, positions, forward_batch, pp_proxy_tensors, input_embeds
) )
aux_hidden_states = None
if self.capture_aux_hidden_states:
hidden_states, aux_hidden_states = hidden_states
if self.pp_group.is_last_rank: if self.pp_group.is_last_rank:
return self.logits_processor( return self.logits_processor(
input_ids, hidden_states, self.lm_head, forward_batch input_ids,
hidden_states,
self.lm_head,
forward_batch,
aux_hidden_states,
) )
else: else:
return hidden_states return hidden_states
@@ -1106,6 +1136,17 @@ class NemotronHForCausalLM(nn.Module):
torch.cuda.empty_cache() torch.cuda.empty_cache()
torch.cuda.synchronize() torch.cuda.synchronize()
def set_dflash_layers_to_capture(self, layer_ids: list[int]):
if not self.pp_group.is_last_rank:
return
if layer_ids is None:
raise ValueError(
"DFLASH requires explicit layer_ids for aux hidden capture."
)
self.capture_aux_hidden_states = True
self.model.layers_to_capture = {layer_id + 1 for layer_id in layer_ids}
def load_weights( def load_weights(
self, weights: Iterable[tuple[str, torch.Tensor]], is_mtp: bool = False self, weights: Iterable[tuple[str, torch.Tensor]], is_mtp: bool = False
) -> None: ) -> None:
+4 -9
View File
@@ -2249,7 +2249,10 @@ class ServerArgs:
] = "prefill" ] = "prefill"
speculative_draft_attention_backend: A[ speculative_draft_attention_backend: A[
Optional[str], Optional[str],
"Attention backend for speculative decoding drafting.", Arg(
help="Attention backend for speculative decoding drafting.",
resolvable=True,
),
NS("spec"), NS("spec"),
] = None ] = None
speculative_draft_kv_cache_dtype: A[ speculative_draft_kv_cache_dtype: A[
@@ -6069,14 +6072,6 @@ class ServerArgs:
logger.info( logger.info(
f"Using {attention_backend} as attention backend for {model_arch}." f"Using {attention_backend} as attention backend for {model_arch}."
) )
elif model_arch in ["NemotronHForCausalLM", "NemotronHPuzzleForCausalLM"]:
# Quantization / MoE runner / attention backend defaults moved to
# the override registry (arg_groups/overrides.py:
# _nemotron_h_overrides).
assert resolved_view(self).attention_backend != "triton", (
"NemotronHForCausalLM does not support triton attention backend,"
"as the first layer might not be an attention layer"
)
elif model_arch in [ elif model_arch in [
"Qwen3MoeForCausalLM", "Qwen3MoeForCausalLM",
"Qwen3VLMoeForConditionalGeneration", "Qwen3VLMoeForConditionalGeneration",
@@ -413,6 +413,8 @@ def get_dflash_attention_sliding_window_size(config: Any) -> Optional[int]:
sliding_window = _cfg_get( sliding_window = _cfg_get(
text_config, "sliding_window", _cfg_get(config, "sliding_window") text_config, "sliding_window", _cfg_get(config, "sliding_window")
) )
if sliding_window is None and is_nemotron_35_draft_config(config):
sliding_window = _get_dflash_config(config).get("swa_window_size")
if sliding_window is None: if sliding_window is None:
raise ValueError( raise ValueError(
"DFLASH sliding_attention layers require config.sliding_window." "DFLASH sliding_attention layers require config.sliding_window."
@@ -463,6 +465,46 @@ def _get_dflash_config(config: Any) -> dict:
return {} return {}
def is_nemotron_35_draft_config(config: Any) -> bool:
"""Identify the published Nemotron 3.5 DFlash/DSpark draft layout.
Keep the non-anchor query layout and checkpoint-local vocabulary modules
scoped to this structurally distinct family instead of changing every
DFlash/DSpark checkpoint that happens to expose one of these fields.
"""
architectures = _cfg_get(config, "architectures", None) or []
if not {"DFlashDraftModel", "Qwen3DSparkModel"}.intersection(architectures):
return False
if not bool(_cfg_get(config, "has_embed_tokens", False)):
return False
if bool(_cfg_get(config, "has_lm_head", False)):
return False
quant_config = _cfg_get(config, "quantization_config", None) or {}
if _cfg_get(quant_config, "quant_algo", None) != "W4A16_NVFP4":
return False
dflash_config = _get_dflash_config(config)
target_layer_ids = dflash_config.get(
"target_layer_ids", _cfg_get(config, "target_layer_ids", None)
)
aux_layer_ids = _cfg_get(config, "eagle_aux_hidden_state_layer_ids", None)
if not target_layer_ids or not aux_layer_ids:
return False
if len(target_layer_ids) != len(aux_layer_ids):
return False
if any(
int(target) + 1 != int(aux)
for target, aux in zip(target_layer_ids, aux_layer_ids)
):
return False
sample_from_anchor = dflash_config.get(
"sample_from_anchor", _cfg_get(config, "sample_from_anchor", True)
)
return sample_from_anchor is False
def _parse_optional_int( def _parse_optional_int(
value: Any, value: Any,
*, *,
@@ -178,6 +178,17 @@ def _commit_accept(candidates, accept_len, bonus_tokens):
return out_tokens, accept_len.to(torch.int32) + 1 return out_tokens, accept_len.to(torch.int32) + 1
def _resolve_dflash_embedding_module(draft_model, target_model):
if getattr(draft_model, "is_nemotron_35_draft", False):
embed_module = draft_model.get_input_embeddings()
if embed_module is None:
raise RuntimeError(
"Nemotron 3.5 DFLASH draft requires its checkpoint embedding."
)
return embed_module
return target_model.get_input_embeddings()
def _is_all_greedy(sampling_info) -> bool: def _is_all_greedy(sampling_info) -> bool:
return sampling_info is None or sampling_info.is_all_greedy return sampling_info is None or sampling_info.is_all_greedy
@@ -1753,7 +1764,9 @@ class DFlashWorkerV2(BaseSpecWorker):
# --- 1) Draft a fixed block with the draft model. # --- 1) Draft a fixed block with the draft model.
target_model = self.target_worker.model_runner.model target_model = self.target_worker.model_runner.model
embed_module = unwrap_lora_layer(target_model.get_input_embeddings()) embed_module = unwrap_lora_layer(
_resolve_dflash_embedding_module(self.draft_model, target_model)
)
lm_head = unwrap_lora_layer(getattr(target_model, "lm_head", None)) lm_head = unwrap_lora_layer(getattr(target_model, "lm_head", None))
if lm_head is None or not ( if lm_head is None or not (
hasattr(lm_head, "weight") hasattr(lm_head, "weight")
@@ -25,6 +25,11 @@ SUPPORTED_DSPARK_MARKOV_HEAD_TYPES = ("vanilla", "gated", "rnn")
DSV4_DRAFT_ATTENTION_BACKEND = "dsv4" DSV4_DRAFT_ATTENTION_BACKEND = "dsv4"
def get_dspark_sample_from_anchor(draft_hf_config: Any) -> bool:
"""Return whether a DSpark checkpoint samples the anchor query row."""
return bool(_cfg_get(draft_hf_config, "sample_from_anchor", True))
def draft_is_deepseek_v4(*, server_args: ServerArgs) -> bool: def draft_is_deepseek_v4(*, server_args: ServerArgs) -> bool:
from sglang.srt.configs.model_config import is_deepseek_v4 from sglang.srt.configs.model_config import is_deepseek_v4
from sglang.srt.utils.hf_transformers_utils import get_config from sglang.srt.utils.hf_transformers_utils import get_config
@@ -128,9 +133,8 @@ def resolve_runtime_config(
) )
def read_draft_checkpoint_gamma(*, server_args: ServerArgs) -> Optional[int]: def read_draft_checkpoint_config(*, server_args: ServerArgs) -> DSparkDraftConfig:
"""Load the draft checkpoint's hf config and read its DSpark gamma """Load and normalize the DSpark draft checkpoint configuration.
(block_size). Raises on config-load failure; callers pick the fallback.
Reads the *resolving* configuration, not the bags: the speculative hook Reads the *resolving* configuration, not the bags: the speculative hook
calls this from inside resolution, where no bag exists yet -- and the calls this from inside resolution, where no bag exists yet -- and the
@@ -148,7 +152,11 @@ def read_draft_checkpoint_gamma(*, server_args: ServerArgs) -> Optional[int]:
revision=resolving.speculative_draft_model_revision, revision=resolving.speculative_draft_model_revision,
model_override_args=json.loads(resolving.json_model_override_args), model_override_args=json.loads(resolving.json_model_override_args),
) )
return parse_dspark_draft_config(draft_hf_config=draft_hf_config).resolve_gamma( return parse_dspark_draft_config(draft_hf_config=draft_hf_config)
def read_draft_checkpoint_gamma(*, server_args: ServerArgs) -> Optional[int]:
return read_draft_checkpoint_config(server_args=server_args).resolve_gamma(
default=None default=None
) )
@@ -81,6 +81,27 @@ class DraftProposal(msgspec.Struct, frozen=True):
folded: bool = False folded: bool = False
def select_draft_hidden_without_anchor(
hidden_states: torch.Tensor,
*,
bs: int,
gamma: int,
) -> tuple[torch.Tensor, torch.Tensor]:
query_token_num = gamma + 1
expected_rows = bs * query_token_num
if hidden_states.shape[0] != expected_rows:
raise RuntimeError(
f"DSpark draft returned {hidden_states.shape[0]} hidden rows, "
f"expected {expected_rows}."
)
hidden_by_query = hidden_states.view(bs, query_token_num, *hidden_states.shape[1:])
selected = hidden_by_query[:, 1:].contiguous()
return (
selected.view(bs * gamma, *hidden_states.shape[1:]),
selected.view(bs, gamma, -1),
)
def make_next_draft_input( def make_next_draft_input(
*, *,
bonus_tokens: torch.Tensor, bonus_tokens: torch.Tensor,
@@ -178,6 +199,8 @@ class DraftBlockProposer:
self.draft_model = draft_model self.draft_model = draft_model
self.draft_model_runner = draft_model_runner self.draft_model_runner = draft_model_runner
self.gamma = gamma self.gamma = gamma
self.sample_from_anchor = bool(draft_model.sample_from_anchor)
self.query_token_num = self.gamma if self.sample_from_anchor else self.gamma + 1
self._mask_token_id = mask_token_id self._mask_token_id = mask_token_id
self._draft_block_spec_info = draft_block_spec_info self._draft_block_spec_info = draft_block_spec_info
self._draft_sampler = None self._draft_sampler = None
@@ -202,7 +225,11 @@ class DraftBlockProposer:
target_model, target_model,
sampling_info, sampling_info,
) -> DraftProposal: ) -> DraftProposal:
embed_module = unwrap_lora_layer(target_model.get_input_embeddings()) embed_module = unwrap_lora_layer(
self.draft_model.embed_tokens
if not self.sample_from_anchor
else target_model.get_input_embeddings()
)
draft_sampler = self._draft_sampler draft_sampler = self._draft_sampler
all_greedy = sampling_info is None or sampling_info.is_all_greedy all_greedy = sampling_info is None or sampling_info.is_all_greedy
fwd = self._run_forward( fwd = self._run_forward(
@@ -276,8 +303,13 @@ class DraftBlockProposer:
markov_head=self.draft_model.markov_head, markov_head=self.draft_model.markov_head,
device=device, device=device,
) )
proposal_block_ids = (
draft_block_ids
if self.sample_from_anchor
else draft_block_ids[:, : self.gamma].contiguous()
)
return DraftProposal( return DraftProposal(
draft_block_ids=draft_block_ids, draft_block_ids=proposal_block_ids,
draft_block=draft_block, draft_block=draft_block,
draft_hidden=fwd.draft_hidden_3d, draft_hidden=fwd.draft_hidden_3d,
confidence=folded_confidence, confidence=folded_confidence,
@@ -321,16 +353,20 @@ class DraftBlockProposer:
sampling_info=None, sampling_info=None,
) -> DraftForwardResult: ) -> DraftForwardResult:
gamma = self.gamma gamma = self.gamma
query_token_num = self.query_token_num
prefix_lens = batch.seq_lens prefix_lens = batch.seq_lens
positions_2d = verify_window.positions_2d positions_2d = verify_window.positions_2d
verify_cache_loc_2d = verify_window.verify_cache_loc_2d verify_cache_loc_2d = verify_window.verify_cache_loc_2d
draft_block_ids = torch.full( draft_block_ids = torch.full(
(bs, gamma), int(self._mask_token_id), dtype=torch.long, device=device (bs, query_token_num),
int(self._mask_token_id),
dtype=torch.long,
device=device,
) )
draft_block_ids[:, 0].copy_(draft_input.bonus_tokens.view(-1)) draft_block_ids[:, 0].copy_(draft_input.bonus_tokens.view(-1))
draft_positions = positions_2d[:, :gamma].reshape(-1) draft_positions = positions_2d[:, :query_token_num].reshape(-1)
draft_cache_loc = verify_cache_loc_2d[:, :gamma].reshape(-1) draft_cache_loc = verify_cache_loc_2d[:, :query_token_num].reshape(-1)
draft_owns_embed = envs.SGLANG_DSPARK_EMBED_IN_GRAPH.get() and hasattr( draft_owns_embed = envs.SGLANG_DSPARK_EMBED_IN_GRAPH.get() and hasattr(
self.draft_model, "forward_embed" self.draft_model, "forward_embed"
@@ -341,7 +377,7 @@ class DraftBlockProposer:
draft_input_embeds = noise_embedding.view(-1, noise_embedding.shape[-1]) draft_input_embeds = noise_embedding.view(-1, noise_embedding.shape[-1])
if batch.seq_lens_cpu is not None: if batch.seq_lens_cpu is not None:
draft_seq_lens_cpu = batch.seq_lens_cpu + gamma draft_seq_lens_cpu = batch.seq_lens_cpu + query_token_num
draft_seq_lens_sum = int(draft_seq_lens_cpu.sum()) draft_seq_lens_sum = int(draft_seq_lens_cpu.sum())
elif draft_input.nxt_kv_lens_cpu is not None: elif draft_input.nxt_kv_lens_cpu is not None:
draft_seq_lens_cpu = draft_input.nxt_kv_lens_cpu draft_seq_lens_cpu = draft_input.nxt_kv_lens_cpu
@@ -349,7 +385,7 @@ class DraftBlockProposer:
else: else:
raise RuntimeError("DSpark decode expected batch.seq_lens_cpu, got None") raise RuntimeError("DSpark decode expected batch.seq_lens_cpu, got None")
draft_num_tokens = bs * gamma draft_num_tokens = bs * query_token_num
draft_forward_batch = ForwardBatch( draft_forward_batch = ForwardBatch(
forward_mode=ForwardMode.TARGET_VERIFY, forward_mode=ForwardMode.TARGET_VERIFY,
batch_size=bs, batch_size=bs,
@@ -381,10 +417,24 @@ class DraftBlockProposer:
raw_hidden = logits_output.hidden_states raw_hidden = logits_output.hidden_states
if raw_hidden is None: if raw_hidden is None:
raise RuntimeError("DSpark draft model returned no hidden states.") raise RuntimeError("DSpark draft model returned no hidden states.")
draft_hidden_3d = raw_hidden.view(bs, gamma, -1) if self.sample_from_anchor:
expected_rows = bs * gamma
if raw_hidden.shape[0] != expected_rows:
raise RuntimeError(
f"DSpark draft returned {raw_hidden.shape[0]} hidden rows, "
f"expected {expected_rows}."
)
model_hidden = raw_hidden
draft_hidden_3d = raw_hidden.view(bs, gamma, -1)
else:
model_hidden, draft_hidden_3d = select_draft_hidden_without_anchor(
raw_hidden,
bs=bs,
gamma=gamma,
)
return DraftForwardResult( return DraftForwardResult(
draft_block_ids=draft_block_ids, draft_block_ids=draft_block_ids,
raw_hidden=raw_hidden, raw_hidden=model_hidden,
draft_hidden_3d=draft_hidden_3d, draft_hidden_3d=draft_hidden_3d,
can_run_graph=draft_out.can_run_graph, can_run_graph=draft_out.can_run_graph,
) )
@@ -9,6 +9,9 @@ from sglang.kernels.ops.speculative.dspark.dspark_draft_model import (
SampleStepTokens, SampleStepTokens,
) )
from sglang.srt.environ import DsparkFoldedSampling, envs from sglang.srt.environ import DsparkFoldedSampling, envs
from sglang.srt.speculative.dspark_components.dspark_draft import (
select_draft_hidden_without_anchor,
)
from sglang.srt.utils import get_available_gpu_memory from sglang.srt.utils import get_available_gpu_memory
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -49,6 +52,8 @@ class DsparkDraftSampler:
self.model = model self.model = model
self.markov_head = model.markov_head self.markov_head = model.markov_head
self.gamma = int(gamma) self.gamma = int(gamma)
self.sample_from_anchor = bool(model.sample_from_anchor)
self.query_token_num = self.gamma if self.sample_from_anchor else self.gamma + 1
max_bs = int(max_bs) max_bs = int(max_bs)
if out is not None: if out is not None:
assert out.shape == (max_bs * self.gamma,) and out.dtype == torch.int64 assert out.shape == (max_bs * self.gamma,) and out.dtype == torch.int64
@@ -100,10 +105,19 @@ class DsparkDraftSampler:
self.greedy_mask[:bs].copy_((sampling_info.top_ks <= 1).view(-1)[:bs]) self.greedy_mask[:bs].copy_((sampling_info.top_ks <= 1).view(-1)[:bs])
def __call__(self, hidden_states, input_ids): def __call__(self, hidden_states, input_ids):
bs = hidden_states.shape[0] // self.gamma bs = hidden_states.shape[0] // self.query_token_num
base_logits, confidence_tap = self.model.compute_base_logits(hidden_states) if self.sample_from_anchor:
model_hidden = hidden_states
sample_hidden = hidden_states.view(bs, self.gamma, -1)
else:
model_hidden, sample_hidden = select_draft_hidden_without_anchor(
hidden_states,
bs=bs,
gamma=self.gamma,
)
base_logits, confidence_tap = self.model.compute_base_logits(model_hidden)
base_logits = base_logits.view(bs, self.gamma, -1) base_logits = base_logits.view(bs, self.gamma, -1)
anchor = input_ids.view(bs, self.gamma)[:, 0] anchor = input_ids.view(bs, self.query_token_num)[:, 0]
if self.folded_sampling: if self.folded_sampling:
@@ -125,7 +139,7 @@ class DsparkDraftSampler:
draft_tokens, corrected_logits = self.markov_head.sample_block( draft_tokens, corrected_logits = self.markov_head.sample_block(
base_logits, base_logits,
first_prev_tokens=anchor, first_prev_tokens=anchor,
hidden_states=hidden_states.view(bs, self.gamma, -1), hidden_states=sample_hidden,
sampler=sampler, sampler=sampler,
) )
self.out[: draft_tokens.numel()].copy_(draft_tokens.reshape(-1)) self.out[: draft_tokens.numel()].copy_(draft_tokens.reshape(-1))
@@ -135,7 +149,7 @@ class DsparkDraftSampler:
) )
if self.confidence_out is not None: if self.confidence_out is not None:
confidence = self.confidence_fn( confidence = self.confidence_fn(
draft_hidden=hidden_states.view(bs, self.gamma, -1), draft_hidden=sample_hidden,
anchor_tokens=anchor, anchor_tokens=anchor,
draft_tokens=draft_tokens, draft_tokens=draft_tokens,
confidence_tap=confidence_tap, confidence_tap=confidence_tap,
@@ -162,18 +162,22 @@ class DSparkWorkerV2(BaseSpecWorker):
) )
self.gamma = runtime_config.gamma self.gamma = runtime_config.gamma
self.verify_num_draft_tokens = runtime_config.verify_num_draft_tokens self.verify_num_draft_tokens = runtime_config.verify_num_draft_tokens
self.sample_from_anchor = bool(self.draft_model.sample_from_anchor)
self.query_token_num = self.gamma if self.sample_from_anchor else self.gamma + 1
self.speculative_num_draft_tokens = self.verify_num_draft_tokens self.speculative_num_draft_tokens = self.verify_num_draft_tokens
self._mask_token_id = runtime_config.mask_token_id self._mask_token_id = runtime_config.mask_token_id
if self.ps.tp_rank == 0: if self.ps.tp_rank == 0:
logger.info( logger.info(
"Initialized DSpark draft runner. attention_backend=%s, model=%s, " "Initialized DSpark draft runner. attention_backend=%s, model=%s, "
"gamma=%s, verify_num_draft_tokens=%s, mask_token_id=%s, " "gamma=%s, verify_num_draft_tokens=%s, query_token_num=%s, "
"markov_head=%s", "sample_from_anchor=%s, mask_token_id=%s, markov_head=%s",
bundle.resolved_attention_backend, bundle.resolved_attention_backend,
self.draft_model.__class__.__name__, self.draft_model.__class__.__name__,
self.gamma, self.gamma,
self.verify_num_draft_tokens, self.verify_num_draft_tokens,
self.query_token_num,
self.sample_from_anchor,
self._mask_token_id, self._mask_token_id,
type(self.draft_model.markov_head).__name__, type(self.draft_model.markov_head).__name__,
) )
@@ -182,7 +186,7 @@ class DSparkWorkerV2(BaseSpecWorker):
length=self.verify_num_draft_tokens, device=self.device length=self.verify_num_draft_tokens, device=self.device
) )
self._draft_block_spec_info = make_draft_block_spec_info( self._draft_block_spec_info = make_draft_block_spec_info(
draft_token_num=int(self.gamma), device=self.device draft_token_num=int(self.query_token_num), device=self.device
) )
if getattr(self.draft_model, "uses_own_vocab_modules", False): if getattr(self.draft_model, "uses_own_vocab_modules", False):
@@ -192,6 +192,7 @@ class GSM8KMixin:
gsm8k_num_shots: int = 5 # run_eval backend only gsm8k_num_shots: int = 5 # run_eval backend only
gsm8k_backend: str = "run_eval" # "run_eval" | "sgl_eval" gsm8k_backend: str = "run_eval" # "run_eval" | "sgl_eval"
gsm8k_thinking: bool = False # sgl_eval backend gsm8k_thinking: bool = False # sgl_eval backend
gsm8k_max_tokens: Optional[int] = None # sgl_eval backend
gsm8k_n_repeats: int = 1 # sgl_eval backend gsm8k_n_repeats: int = 1 # sgl_eval backend
def test_gsm8k(self): def test_gsm8k(self):
@@ -213,6 +214,7 @@ class GSM8KMixin:
num_examples=num_examples, num_examples=num_examples,
num_threads=self.gsm8k_num_threads, num_threads=self.gsm8k_num_threads,
thinking=self.gsm8k_thinking, thinking=self.gsm8k_thinking,
max_tokens=self.gsm8k_max_tokens,
accept_length_thres=self.gsm8k_accept_length_thres, accept_length_thres=self.gsm8k_accept_length_thres,
) )
else: else:
@@ -1,33 +1,110 @@
"""B200 NVFP4 E2E coverage for NVIDIA Nemotron 3.5 Lightning.
The three cases exercise the production NVFP4 checkpoint without speculation,
with DFlash, and with DSpark. MTP is already covered by the Nemotron model
family tests; the external-draft paths are the new coverage in this file.
"""
import unittest import unittest
from sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kits.lm_eval_kit import LMEvalMixin from sglang.test.kits.eval_accuracy_kit import GSM8KMixin
from sglang.test.server_fixtures.default_fixture import DefaultServerBase from sglang.test.test_utils import (
DEFAULT_URL_FOR_TEST,
register_cuda_ci( CustomTestCase,
est_time=190, popen_launch_server,
stage="base-b", try_cached_model,
runner_config="2-gpu-large",
) )
NEMOTRON_3_NANO_THINKING_ARGS = [ register_cuda_ci(est_time=500, stage="extra-b", runner_config="4-gpu-b200")
"--trust-remote-code",
MODEL = "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4"
DFLASH_DRAFT_MODEL = "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DFlash"
DSPARK_DRAFT_MODEL = "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DSpark"
SERVER_LAUNCH_TIMEOUT = 3600
GSM8K_SCORE_THRESHOLD = 0.80
BASE_ARGS = [
"--mamba-backend",
"flashinfer",
"--mamba-ssm-dtype",
"float16",
"--enable-mamba-cache-stochastic-rounding",
"--mamba-cache-philox-rounds",
"5",
"--mem-fraction-static",
"0.85",
"--cuda-graph-max-bs-decode",
"16",
"--reasoning-parser",
"nemotron_3",
"--tool-call-parser", "--tool-call-parser",
"qwen3_coder", "qwen3_coder",
"--reasoning-parser",
"deepseek-r1",
] ]
class TestNvidiaNemotron3Nano30BFP8(LMEvalMixin, DefaultServerBase): class _Nemotron35LightningServer:
"""Test Nemotron-3-Nano-30B FP8 model with lm-eval GSM8K evaluation.""" speculative_args: list[str] = []
model = try_cached_model(MODEL)
base_url = DEFAULT_URL_FOR_TEST
gsm8k_backend = "sgl_eval"
gsm8k_thinking = True
gsm8k_num_examples = 200
gsm8k_num_threads = 32
gsm8k_max_tokens = 16384
gsm8k_score_threshold = GSM8K_SCORE_THRESHOLD
model = "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8" @classmethod
model_config_name = "lm_eval_configs/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8.yaml" def setUpClass(cls):
other_args = [ cls.process = popen_launch_server(
"--tp-size", cls.model,
"2", cls.base_url,
] + NEMOTRON_3_NANO_THINKING_ARGS timeout=SERVER_LAUNCH_TIMEOUT,
other_args=BASE_ARGS + cls.speculative_args,
)
@classmethod
def tearDownClass(cls):
if hasattr(cls, "process") and cls.process:
kill_process_tree(cls.process.pid)
class TestNvidiaNemotron35LightningNVFP4(
_Nemotron35LightningServer, GSM8KMixin, CustomTestCase
):
"""Normal autoregressive serving."""
class TestNvidiaNemotron35LightningNVFP4DFlash(
_Nemotron35LightningServer, GSM8KMixin, CustomTestCase
):
"""DFlash with the published W4A16 draft checkpoint."""
speculative_args = [
"--speculative-algorithm",
"DFLASH",
"--speculative-draft-model-path",
DFLASH_DRAFT_MODEL,
"--speculative-dflash-block-size",
"6",
]
class TestNvidiaNemotron35LightningNVFP4DSpark(
_Nemotron35LightningServer, GSM8KMixin, CustomTestCase
):
"""DSpark with the published bonus-anchor W4A16 draft checkpoint."""
speculative_args = [
"--speculative-algorithm",
"DSPARK",
"--speculative-draft-model-path",
DSPARK_DRAFT_MODEL,
"--speculative-dspark-block-size",
"3",
]
if __name__ == "__main__": if __name__ == "__main__":
+206 -9
View File
@@ -97,11 +97,25 @@ class TestModelOverridableWhitelist(CustomTestCase):
"enable_aiter_allreduce_fusion", "enable_aiter_allreduce_fusion",
"enable_symm_mem", "enable_symm_mem",
"speculative_attention_mode", "speculative_attention_mode",
"speculative_draft_attention_backend",
} }
), ),
) )
class TestDSparkCheckpointConfig(CustomTestCase):
def test_sample_from_anchor_is_read_from_checkpoint_config(self):
from sglang.srt.speculative.dspark_components.dspark_config import (
get_dspark_sample_from_anchor,
)
config = SimpleNamespace(
architectures=["UnrelatedDSparkModel"], sample_from_anchor=False
)
self.assertFalse(get_dspark_sample_from_anchor(config))
self.assertTrue(get_dspark_sample_from_anchor(SimpleNamespace()))
class _IsolatedRegistry(CustomTestCase): class _IsolatedRegistry(CustomTestCase):
"""Run each test against empty registries (they are process-global).""" """Run each test against empty registries (they are process-global)."""
@@ -709,6 +723,14 @@ class TestGoldenModelOverrides(_IsolatedPublish):
moe_runner_backend="auto", moe_runner_backend="auto",
moe_a2a_backend="none", moe_a2a_backend="none",
attention_backend=None, attention_backend=None,
prefill_attention_backend=None,
decode_attention_backend=None,
speculative_algorithm=None,
speculative_eagle_topk=None,
speculative_draft_attention_backend=None,
page_size=None,
mamba_radix_cache_strategy="auto",
is_attention_backend_not_set=lambda: True,
get_model_config=lambda: model_config, get_model_config=lambda: model_config,
), ),
hf_config, hf_config,
@@ -731,13 +753,16 @@ class TestGoldenModelOverrides(_IsolatedPublish):
} }
) )
with patch.object(overrides_module, "is_sm100_supported", return_value=True): with (
patch.object(overrides_module, "is_sm100_supported", return_value=True),
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
):
self.assertEqual( self.assertEqual(
_nemotron_h_overrides(server_args, hf_config), _nemotron_h_overrides(server_args, hf_config),
{ {
"quantization": "modelopt_mixed", "quantization": "modelopt_mixed",
"moe_runner_backend": "marlin", "moe_runner_backend": "marlin",
"attention_backend": "flashinfer", "attention_backend": "trtllm_mha",
}, },
) )
@@ -758,16 +783,169 @@ class TestGoldenModelOverrides(_IsolatedPublish):
} }
) )
with patch.object(overrides_module, "is_sm100_supported", return_value=True): with (
patch.object(overrides_module, "is_sm100_supported", return_value=True),
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
):
self.assertEqual( self.assertEqual(
_nemotron_h_overrides(server_args, hf_config), _nemotron_h_overrides(server_args, hf_config),
{ {
"quantization": "modelopt_mixed", "quantization": "modelopt_mixed",
"moe_runner_backend": "flashinfer_trtllm", "moe_runner_backend": "flashinfer_trtllm",
"attention_backend": "flashinfer", "attention_backend": "trtllm_mha",
}, },
) )
def test_nemotron_h_speculation_uses_arch_specific_attention_on_blackwell(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
cases = {
True: {
"attention_backend": "trtllm_mha",
"page_size": 64,
"mamba_radix_cache_strategy": "extra_buffer",
"speculative_draft_attention_backend": "trtllm_mha",
},
False: {
"attention_backend": "triton",
"speculative_draft_attention_backend": "flashinfer",
},
}
for is_sm100, expected in cases.items():
with self.subTest(is_sm100=is_sm100):
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "EAGLE"
with (
patch.object(
overrides_module,
"is_blackwell_supported",
return_value=True,
),
patch.object(
overrides_module,
"is_sm100_supported",
return_value=is_sm100,
),
):
overrides = _nemotron_h_overrides(server_args, hf_config)
for key, value in expected.items():
self.assertEqual(overrides[key], value)
def test_nemotron_h_sm100_speculative_draft_backend_matrix(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
for algorithm in ("EAGLE", "NEXTN", "DSPARK"):
with self.subTest(algorithm=algorithm):
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = algorithm
with (
patch.object(
overrides_module, "is_blackwell_supported", return_value=True
),
patch.object(
overrides_module, "is_sm100_supported", return_value=True
),
):
overrides = _nemotron_h_overrides(server_args, hf_config)
self.assertEqual(overrides["attention_backend"], "trtllm_mha")
self.assertEqual(
overrides["speculative_draft_attention_backend"],
"trtllm_mha",
)
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DFLASH"
with (
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
patch.object(overrides_module, "is_sm100_supported", return_value=True),
):
overrides = _nemotron_h_overrides(server_args, hf_config)
self.assertEqual(overrides["attention_backend"], "trtllm_mha")
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_sm100_speculation_preserves_explicit_cache_and_draft(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DSPARK"
server_args.page_size = 128
server_args.mamba_radix_cache_strategy = "extra_buffer_lazy"
server_args.speculative_draft_attention_backend = "flashinfer"
with (
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
patch.object(overrides_module, "is_sm100_supported", return_value=True),
):
overrides = _nemotron_h_overrides(server_args, hf_config)
self.assertEqual(overrides["attention_backend"], "trtllm_mha")
self.assertNotIn("page_size", overrides)
self.assertNotIn("mamba_radix_cache_strategy", overrides)
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_sm100_topk_tree_falls_back_to_triton(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "EAGLE"
server_args.speculative_eagle_topk = 4
with (
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
patch.object(overrides_module, "is_sm100_supported", return_value=True),
):
overrides = _nemotron_h_overrides(server_args, hf_config)
self.assertEqual(overrides["attention_backend"], "triton")
self.assertEqual(overrides["speculative_draft_attention_backend"], "flashinfer")
self.assertNotIn("page_size", overrides)
self.assertNotIn("mamba_radix_cache_strategy", overrides)
def test_nemotron_h_target_only_sm120_defers_to_generic_attention_default(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
with (
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
patch.object(overrides_module, "is_sm100_supported", return_value=False),
):
self.assertNotIn(
"attention_backend", _nemotron_h_overrides(server_args, hf_config)
)
def test_nemotron_h_target_only_sm100_uses_trtllm_mha(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
with (
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
patch.object(overrides_module, "is_sm100_supported", return_value=True),
):
self.assertEqual(
_nemotron_h_overrides(server_args, hf_config)["attention_backend"],
"trtllm_mha",
)
def test_nemotron_h_explicit_split_attention_backend_wins(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
server_args, hf_config = self._nemotron_h_args(quantized_layers={})
server_args.speculative_algorithm = "DFLASH"
server_args.prefill_attention_backend = "triton"
server_args.speculative_draft_attention_backend = "fa3"
server_args.is_attention_backend_not_set = lambda: False
with (
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
patch.object(overrides_module, "is_sm100_supported", return_value=True),
):
overrides = _nemotron_h_overrides(server_args, hf_config)
self.assertNotIn("attention_backend", overrides)
self.assertNotIn("speculative_draft_attention_backend", overrides)
def test_nemotron_h_w4a16_moe_rejects_a2a_backend(self): def test_nemotron_h_w4a16_moe_rejects_a2a_backend(self):
from sglang.srt.arg_groups.overrides import _nemotron_h_overrides from sglang.srt.arg_groups.overrides import _nemotron_h_overrides
@@ -1334,20 +1512,36 @@ class TestGoldenModelOverrides(_IsolatedPublish):
moe_runner_backend="auto", moe_runner_backend="auto",
moe_a2a_backend="none", moe_a2a_backend="none",
attention_backend=None, attention_backend=None,
prefill_attention_backend=None,
decode_attention_backend=None,
speculative_algorithm=None,
speculative_eagle_topk=None,
speculative_draft_attention_backend=None,
page_size=None,
mamba_radix_cache_strategy="auto",
get_model_config=lambda: mc, get_model_config=lambda: mc,
) )
defaults.update(kw) defaults.update(kw)
return SimpleNamespace(**defaults) args = SimpleNamespace(**defaults)
args.is_attention_backend_not_set = lambda: (
args.attention_backend is None
and args.prefill_attention_backend is None
and args.decode_attention_backend is None
)
return args
hf = _hf() hf = _hf()
with patch.object(overrides_module, "is_sm100_supported", return_value=True): with (
patch.object(overrides_module, "is_sm100_supported", return_value=True),
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
):
# modelopt checkpoint: quant algo resolution + sm100 defaults # modelopt checkpoint: quant algo resolution + sm100 defaults
self.assertEqual( self.assertEqual(
_nemotron_h_overrides(_args("modelopt", hf), hf), _nemotron_h_overrides(_args("modelopt", hf), hf),
{ {
"quantization": "modelopt_fp4", "quantization": "modelopt_fp4",
"moe_runner_backend": "flashinfer_trtllm", "moe_runner_backend": "flashinfer_trtllm",
"attention_backend": "flashinfer", "attention_backend": "trtllm_mha",
}, },
) )
hf_mixed = _hf("MIXED_PRECISION") hf_mixed = _hf("MIXED_PRECISION")
@@ -1383,7 +1577,10 @@ class TestGoldenModelOverrides(_IsolatedPublish):
) )
hf_without_quant_cfg = _hf(include_quantization_config=False) hf_without_quant_cfg = _hf(include_quantization_config=False)
with patch.object(overrides_module, "is_sm100_supported", return_value=True): with (
patch.object(overrides_module, "is_sm100_supported", return_value=True),
patch.object(overrides_module, "is_blackwell_supported", return_value=True),
):
for modelopt_quantization in ("modelopt_fp8", "modelopt_fp4"): for modelopt_quantization in ("modelopt_fp8", "modelopt_fp4"):
with self.subTest(modelopt_quantization=modelopt_quantization): with self.subTest(modelopt_quantization=modelopt_quantization):
self.assertEqual( self.assertEqual(
@@ -1394,7 +1591,7 @@ class TestGoldenModelOverrides(_IsolatedPublish):
{ {
"quantization": modelopt_quantization, "quantization": modelopt_quantization,
"moe_runner_backend": "flashinfer_trtllm", "moe_runner_backend": "flashinfer_trtllm",
"attention_backend": "flashinfer", "attention_backend": "trtllm_mha",
}, },
) )