Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions python/sglang/srt/models/dflash.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,31 @@ def set_block_size(self, block_size: int) -> None:
for conv in (layer.attention_conv, layer.mlp_conv):
if conv is not None:
conv.block_size = self.block_size
self.projector_type = draft_config.projector_type
self.shift_label = draft_config.shift_label
self.prefix_gru: Optional[nn.GRU] = None
self.embed_proj: Optional[nn.Sequential] = None
if draft_config.is_domino:
assert draft_config.gru_hidden_dim is not None
assert draft_config.emb_dim is not None
self.prefix_gru = nn.GRU(
input_size=hidden_size,
hidden_size=int(draft_config.gru_hidden_dim),
num_layers=1,
batch_first=True,
bias=False,
)
self.embed_proj = nn.Sequential(
nn.Linear(
hidden_size + int(draft_config.gru_hidden_dim),
int(draft_config.emb_dim),
bias=False,
),
nn.SiLU(),
nn.Linear(
int(draft_config.emb_dim), int(config.vocab_size), bias=False
),
)

def get_attention_sliding_window_size(self) -> Optional[int]:
return get_dflash_attention_sliding_window_size(self.config)
Expand Down Expand Up @@ -729,6 +754,7 @@ def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]):
]

params_dict = dict(self.named_parameters())
loaded_params = set()

# Alias the native export's "encoder." names.
_VENDOR_ENCODER_ALIASES = {
Expand All @@ -753,6 +779,14 @@ def resolve_param_name(name: str) -> Optional[str]:
return None

for name, loaded_weight in weights:
unprefixed_name = name.removeprefix("model.")
if self.projector_type != "domino" and unprefixed_name.startswith(
("prefix_gru.", "embed_proj.")
):
raise ValueError(
"DFLASH checkpoint contains Domino projector weights but "
f"projector_type={self.projector_type!r}."
)
for param_name, weight_name, shard_id in stacked_params_mapping:
if f".{weight_name}." not in name:
continue
Expand All @@ -763,6 +797,7 @@ def resolve_param_name(name: str) -> Optional[str]:
param = params_dict[resolved_name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight, shard_id)
loaded_params.add(resolved_name)
break
else:
resolved_name = resolve_param_name(name)
Expand Down Expand Up @@ -797,8 +832,31 @@ def resolve_param_name(name: str) -> Optional[str]:
f"(num_context_features={self.num_context_features}, hidden_size={int(self.config.hidden_size)}), "
f"but got {loaded_shape} for weight '{name}'."
)
if resolved_name.startswith(("prefix_gru.", "embed_proj.")) and tuple(
loaded_weight.shape
) != tuple(param.shape):
raise ValueError(
"DFLASH Domino projector weight shape mismatch: "
f"expected {resolved_name}{tuple(param.shape)}, got "
f"{tuple(loaded_weight.shape)} from {name!r}."
)
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded_params.add(resolved_name)

if self.projector_type == "domino":
required = {
"prefix_gru.weight_ih_l0",
"prefix_gru.weight_hh_l0",
"embed_proj.0.weight",
"embed_proj.2.weight",
}
missing = required - loaded_params
Comment thread
jianuo-huang marked this conversation as resolved.
if missing:
raise ValueError(
"DFLASH Domino checkpoint is missing required projector weights: "
f"{sorted(missing)}."
)


class DFlashLagunaAttention(DFlashAttention):
Expand Down
4 changes: 4 additions & 0 deletions python/sglang/srt/server_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -2102,6 +2102,10 @@ class ServerArgs:
"DFLASH only. Block size (verify window length). Alias of --speculative-num-draft-tokens for DFLASH.",
NS("spec"),
] = None
speculative_domino_candidate_pool_size: A[
int,
"Domino only. Size of the approximate block-shared base-logit candidate pool. Set to 0 to score the full vocabulary.",
] = 2048
speculative_dspark_block_size: A[
Optional[int],
"DSPARK only. Draft block size gamma (number of proposed draft tokens). The verify window is gamma + 1, so this sets --speculative-num-draft-tokens = gamma + 1. Omit to auto-infer gamma from the draft checkpoint block_size.",
Expand Down
80 changes: 80 additions & 0 deletions python/sglang/srt/speculative/dflash_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,15 @@ class DFlashDraftConfig:
target_layer_ids: Optional[List[int]]
mask_token: str
mask_token_id: Optional[int]
projector_type: Optional[str]
shift_label: Optional[bool]
pure_draft_prefix_len: Optional[int]
gru_hidden_dim: Optional[int]
emb_dim: Optional[int]

@property
def is_domino(self) -> bool:
return self.projector_type == "domino"

def require_num_layers(self) -> int:
if self.num_hidden_layers is None:
Expand Down Expand Up @@ -697,6 +706,72 @@ def parse_dflash_draft_config(*, draft_hf_config: Any) -> DFlashDraftConfig:
f"got {mask_token_id}."
)

projector_type = dflash_cfg.get(
"projector_type", _cfg_get(draft_hf_config, "projector_type", None)
)
shift_label = None
pure_draft_prefix_len = None
gru_hidden_dim = None
emb_dim = None
if projector_type == "domino":
shift_label = dflash_cfg.get(
"shift_label", _cfg_get(draft_hf_config, "shift_label", None)
)
pure_draft_prefix_len = _parse_optional_int(
dflash_cfg.get(
"pure_draft_prefix_len",
_cfg_get(draft_hf_config, "pure_draft_prefix_len", None),
),
field_name="DFLASH Domino pure_draft_prefix_len",
min_value=0,
)
gru_hidden_dim = _parse_optional_int(
dflash_cfg.get(
"gru_hidden_dim", _cfg_get(draft_hf_config, "gru_hidden_dim", None)
),
field_name="DFLASH Domino gru_hidden_dim",
min_value=1,
)
nested_emb_dim = _parse_optional_int(
dflash_cfg.get("emb_dim", None),
field_name="DFLASH Domino dflash_config.emb_dim",
min_value=1,
)
top_level_emb_dim = _parse_optional_int(
_cfg_get(draft_hf_config, "emb_dim", None),
field_name="DFLASH Domino top-level emb_dim",
min_value=1,
)
if (
nested_emb_dim is not None
and top_level_emb_dim is not None
and nested_emb_dim != top_level_emb_dim
):
raise ValueError(
"DFLASH Domino emb_dim differs between dflash_config and the "
f"top-level config: {nested_emb_dim} != {top_level_emb_dim}."
)
emb_dim = nested_emb_dim if nested_emb_dim is not None else top_level_emb_dim

if not isinstance(shift_label, bool):
raise ValueError(
"DFLASH Domino requires dflash_config.shift_label to be a bool, "
f"got {shift_label!r}."
)
if pure_draft_prefix_len != 1:
raise ValueError(
"DFLASH Domino currently requires pure_draft_prefix_len=1, "
f"got {pure_draft_prefix_len!r}."
)
if gru_hidden_dim is None:
raise ValueError("DFLASH Domino requires dflash_config.gru_hidden_dim.")
if emb_dim is None:
raise ValueError("DFLASH Domino requires dflash_config.emb_dim.")
if block_size is not None and block_size <= 1:
raise ValueError(
f"DFLASH Domino requires block_size > 1, got {block_size}."
)

return DFlashDraftConfig(
num_hidden_layers=num_hidden_layers,
num_target_layers=num_target_layers,
Expand All @@ -710,6 +785,11 @@ def parse_dflash_draft_config(*, draft_hf_config: Any) -> DFlashDraftConfig:
target_layer_ids=parsed_target_layer_ids,
mask_token=mask_token,
mask_token_id=mask_token_id,
projector_type=projector_type,
shift_label=shift_label,
pure_draft_prefix_len=pure_draft_prefix_len,
gru_hidden_dim=gru_hidden_dim,
emb_dim=emb_dim,
)


Expand Down
144 changes: 142 additions & 2 deletions python/sglang/srt/speculative/dflash_worker_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
is_dflash_sampling_verify_available,
parse_dflash_draft_config,
)
from sglang.srt.speculative.domino_utils import (
domino_greedy_rollout,
validate_domino_runtime,
)
from sglang.srt.speculative.draft_worker_common import (
build_block_pos_offsets,
build_draft_tp_worker,
Expand Down Expand Up @@ -267,6 +271,53 @@ def __call__(self, hidden_states, input_ids):
self.out[: tokens.numel()].copy_(tokens.reshape(-1))
self.candidate_out[:bs].copy_(candidate_ids)
self.q_out[:bs].copy_(q_rows)
class _DominoDraftSampler:
"""Capture-safe TP=1 Domino rollout over a fixed-size draft block."""

def __init__(
self,
*,
target_embedding,
lm_head_weight,
prefix_gru,
embed_proj,
vocab_size,
block_size,
shift_label,
max_bs,
candidate_pool_size=2048,
):
self.target_embedding = target_embedding
self.lm_head_weight = lm_head_weight
self.prefix_gru = prefix_gru
self.embed_proj = embed_proj
self.vocab_size = int(vocab_size)
self.block_size = int(block_size)
self.shift_label = bool(shift_label)
self.candidate_pool_size = int(candidate_pool_size)
max_tokens = int(max_bs) * (self.block_size - 1)
self.out = torch.empty(
(max_tokens,), dtype=torch.int64, device=lm_head_weight.device
)

def __call__(self, hidden_states, input_ids=None):
if input_ids is None:
raise RuntimeError("Domino draft sampler requires block input_ids.")
bs = hidden_states.shape[0] // self.block_size
draft_hidden = hidden_states.view(bs, self.block_size, -1)
verified_ids = input_ids.view(bs, self.block_size)[:, 0]
proposals = domino_greedy_rollout(
draft_hidden=draft_hidden,
verified_ids=verified_ids,
target_embedding=self.target_embedding,
lm_head_weight=self.lm_head_weight,
prefix_gru=self.prefix_gru,
embed_proj=self.embed_proj,
vocab_size=self.vocab_size,
shift_label=self.shift_label,
candidate_pool_size=self.candidate_pool_size,
)
self.out[: bs * (self.block_size - 1)].copy_(proposals.reshape(-1))


class DFlashWorkerV2(BaseSpecWorker):
Expand Down Expand Up @@ -319,6 +370,36 @@ def __init__(
draft_config = parse_dflash_draft_config(
draft_hf_config=self.draft_model_runner.model_config.hf_config
)
self._is_domino = draft_config.is_domino
self.domino_candidate_pool_size = int(
server_args.speculative_domino_candidate_pool_size
)
if self._is_domino:
if self.domino_candidate_pool_size < 0:
raise ValueError(
"--speculative-domino-candidate-pool-size must be non-negative, "
f"got {self.domino_candidate_pool_size}."
)
target_model = self.target_worker.model_runner.model
target_embedding = target_model.get_input_embeddings()
lm_head = getattr(target_model, "lm_head", None)
prefix_gru = getattr(self.draft_model, "prefix_gru", None)
embed_proj = getattr(self.draft_model, "embed_proj", None)
if lm_head is None or prefix_gru is None or embed_proj is None:
raise ValueError(
"DFLASH Domino requires target lm_head and loaded Domino projector modules."
)
validate_domino_runtime(
device=torch.device(self.device),
tp_size=int(get_tp_group().world_size),
target_vocab_size=int(self.model_runner.model_config.vocab_size),
draft_vocab_size=int(self.draft_model_runner.model_config.vocab_size),
hidden_size=int(self.draft_model.config.hidden_size),
target_embedding=target_embedding,
lm_head=lm_head,
prefix_gru=prefix_gru,
embed_proj=embed_proj,
)
if get_spec().speculative_num_draft_tokens is None:
# Should not happen (ServerArgs should have inferred it), but keep a fallback.
self.block_size = int(draft_config.resolve_block_size(default=16))
Expand All @@ -337,6 +418,11 @@ def __init__(
)
self.draft_model.set_block_size(self.block_size)
self.speculative_num_draft_tokens = int(self.block_size)
if self._is_domino and self.block_size <= 1:
raise ValueError(
"DFLASH Domino requires speculative_num_draft_tokens > 1, "
f"got {self.block_size}."
)

self._mask_token = draft_config.mask_token
self._mask_token_id_override = draft_config.mask_token_id
Expand All @@ -359,6 +445,11 @@ def __init__(
self.draft_window_size,
self.use_compact_draft_cache,
)
if self._is_domino:
logger.info(
"DFLASH Domino rollout enabled (eager BF16, TP=1, block-shared candidate pool size=%s).",
self.domino_candidate_pool_size,
)
logger.info(
"DFLASH draft runner ready. mask_token=%s, mask_token_id=%s, mask_token_id_override=%s, noise_embed_scale=%s",
self._mask_token,
Expand Down Expand Up @@ -527,6 +618,28 @@ def _eager(reason):
# Quantized lm_head (FP8/INT) would break the static matmul.
return _eager("quantized lm_head")
tp_group = get_tp_group()
if self._is_domino:
if tp_group.world_size != 1:
return _eager("Domino cuda graph currently requires tp=1")
prefix_gru = self.draft_model.prefix_gru
embed_proj = self.draft_model.embed_proj
if prefix_gru is None or embed_proj is None:
return _eager("Domino projector modules are unavailable")
if self.ps.tp_rank == 0:
logger.info(
"DFLASH Domino rollout folded into the draft cuda graph (tp=1)."
)
return _DominoDraftSampler(
target_embedding=target_model.get_input_embeddings(),
lm_head_weight=lm_head.weight,
prefix_gru=prefix_gru,
embed_proj=embed_proj,
vocab_size=int(self.model_runner.model_config.vocab_size),
block_size=self.block_size,
shift_label=self.draft_model.shift_label,
max_bs=max(self.server_args.cuda_graph_config.decode.bs),
candidate_pool_size=self.domino_candidate_pool_size,
)
if not hasattr(lm_head, "shard_indices"):
if tp_group.world_size != 1:
# No shard metadata to recover per-rank vocab offsets from.
Expand Down Expand Up @@ -1920,8 +2033,35 @@ def forward_batch_generation(
draft_out = self.draft_model_runner.forward(forward_batch)
draft_logits_output = draft_out.logits_output

folded = self._draft_sampler is not None and draft_out.can_run_graph
if folded:
if (
self._is_domino
and self._draft_sampler is not None
and draft_out.can_run_graph
):
draft_next = self._draft_sampler.out[
: bs * (int(self.block_size) - 1)
].view(bs, int(self.block_size) - 1)
elif self._is_domino:
draft_hidden = draft_logits_output.hidden_states
if draft_hidden is None:
raise RuntimeError("DFLASH draft model returned no hidden states.")
draft_hidden = draft_hidden.view(bs, int(self.block_size), -1)
prefix_gru = self.draft_model.prefix_gru
embed_proj = self.draft_model.embed_proj
if prefix_gru is None or embed_proj is None:
raise RuntimeError("DFLASH Domino projector modules are unavailable.")
draft_next = domino_greedy_rollout(
draft_hidden=draft_hidden,
verified_ids=block_ids[:, 0],
target_embedding=embed_module,
lm_head_weight=lm_head.weight,
prefix_gru=prefix_gru,
embed_proj=embed_proj,
vocab_size=int(self.model_runner.model_config.vocab_size),
shift_label=bool(self.draft_model.shift_label),
candidate_pool_size=self.domino_candidate_pool_size,
)
elif self._draft_sampler is not None and draft_out.can_run_graph:
draft_next = self._draft_sampler.out[
: bs * (int(self.block_size) - 1)
].view(bs, int(self.block_size) - 1)
Expand Down
Loading
Loading