[Bugfix] Restore overridden HF config fields and support index_skip_topk_offset for DSA topk sharing (#27114)
This commit is contained in:
@@ -701,6 +701,7 @@ class TboForwardBatchPreparer:
|
|||||||
"split_index", # for split prefill
|
"split_index", # for split prefill
|
||||||
"orig_seq_lens", # only used by qwen-1m, thus not care
|
"orig_seq_lens", # only used by qwen-1m, thus not care
|
||||||
"return_pooled_hidden_states",
|
"return_pooled_hidden_states",
|
||||||
|
"reuse_mtp_topk_indices", # forward-level flag, inherited by both child batches
|
||||||
]:
|
]:
|
||||||
output_dict[key] = getattr(batch, key)
|
output_dict[key] = getattr(batch, key)
|
||||||
|
|
||||||
|
|||||||
@@ -385,6 +385,10 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
|
|||||||
# For hidden states before normal
|
# For hidden states before normal
|
||||||
return_hidden_states_before_norm: bool = False
|
return_hidden_states_before_norm: bool = False
|
||||||
|
|
||||||
|
# For NSA/DSA topk_indices reuse across forward calls (e.g., EAGLE draft)
|
||||||
|
topk_indices: Optional[torch.Tensor] = None
|
||||||
|
reuse_mtp_topk_indices: Optional[bool] = False
|
||||||
|
|
||||||
# === Forward-derived (built in init_new on the forward stream; FB-owned) ===
|
# === Forward-derived (built in init_new on the forward stream; FB-owned) ===
|
||||||
# Position information
|
# Position information
|
||||||
positions: torch.Tensor = None
|
positions: torch.Tensor = None
|
||||||
|
|||||||
@@ -245,7 +245,15 @@ class DeepseekMLAForwardMixin:
|
|||||||
q = self.q_b_proj(q)[0].view(
|
q = self.q_b_proj(q)[0].view(
|
||||||
-1, self.num_local_heads, self.qk_head_dim
|
-1, self.num_local_heads, self.qk_head_dim
|
||||||
)
|
)
|
||||||
if not self.skip_topk or prev_topk_indices is None:
|
# skip_topk (shared) layers carry no indexer weights in the
|
||||||
|
# checkpoint, so they must reuse the carried topk and never run
|
||||||
|
# the indexer. Do NOT widen this to `or prev_topk_indices is
|
||||||
|
# None` (the upstream gate): that recomputes with an
|
||||||
|
# uninitialized indexer whenever cross-layer propagation is
|
||||||
|
# unavailable (e.g. the TBO op path drops topk_indices),
|
||||||
|
# reintroducing the >index_topk garbling. The is_nextn clause is
|
||||||
|
# the sole intentional fallback (layer 78 has its own weights).
|
||||||
|
if not self.skip_topk or (self.is_nextn and prev_topk_indices is None):
|
||||||
topk_indices = self.indexer(
|
topk_indices = self.indexer(
|
||||||
x=hidden_states,
|
x=hidden_states,
|
||||||
q_lora=q_lora,
|
q_lora=q_lora,
|
||||||
@@ -264,7 +272,12 @@ class DeepseekMLAForwardMixin:
|
|||||||
k_nope = k_nope.unsqueeze(1)
|
k_nope = k_nope.unsqueeze(1)
|
||||||
q = self.q_b_proj(q)[0].view(-1, self.num_local_heads, self.qk_head_dim)
|
q = self.q_b_proj(q)[0].view(-1, self.num_local_heads, self.qk_head_dim)
|
||||||
if q_lora is not None:
|
if q_lora is not None:
|
||||||
if not self.skip_topk or prev_topk_indices is None:
|
# See the skip_topk note above: shared layers have no
|
||||||
|
# indexer weights, so this gate must not fall back to
|
||||||
|
# computing when prev_topk_indices is None.
|
||||||
|
if not self.skip_topk or (
|
||||||
|
self.is_nextn and prev_topk_indices is None
|
||||||
|
):
|
||||||
topk_indices = self.indexer(
|
topk_indices = self.indexer(
|
||||||
x=hidden_states,
|
x=hidden_states,
|
||||||
q_lora=q_lora,
|
q_lora=q_lora,
|
||||||
|
|||||||
@@ -230,7 +230,14 @@ class DeepseekModelNextN(nn.Module):
|
|||||||
forward_batch,
|
forward_batch,
|
||||||
residual,
|
residual,
|
||||||
zero_allocator,
|
zero_allocator,
|
||||||
|
prev_topk_indices=(
|
||||||
|
forward_batch.topk_indices
|
||||||
|
if forward_batch.reuse_mtp_topk_indices
|
||||||
|
else None
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
if forward_batch.reuse_mtp_topk_indices:
|
||||||
|
forward_batch.topk_indices = topk_indices
|
||||||
|
|
||||||
if not forward_batch.forward_mode.is_idle():
|
if not forward_batch.forward_mode.is_idle():
|
||||||
if residual is not None:
|
if residual is not None:
|
||||||
|
|||||||
@@ -1469,6 +1469,7 @@ class DeepseekV2AttentionMLA(
|
|||||||
self.q_lora_rank = q_lora_rank
|
self.q_lora_rank = q_lora_rank
|
||||||
self.kv_lora_rank = kv_lora_rank
|
self.kv_lora_rank = kv_lora_rank
|
||||||
self.quant_config = quant_config
|
self.quant_config = quant_config
|
||||||
|
self.is_nextn = is_nextn
|
||||||
attn_tp_rank = get_attention_tp_rank()
|
attn_tp_rank = get_attention_tp_rank()
|
||||||
attn_tp_size = get_attention_tp_size()
|
attn_tp_size = get_attention_tp_size()
|
||||||
self.use_dsa = is_deepseek_dsa(config)
|
self.use_dsa = is_deepseek_dsa(config)
|
||||||
@@ -1556,12 +1557,33 @@ class DeepseekV2AttentionMLA(
|
|||||||
# skip_topk: when True, this layer will skip computation and reuse previous layer's topk indices.
|
# skip_topk: when True, this layer will skip computation and reuse previous layer's topk indices.
|
||||||
# next_skip_topk: when True, the next layer will skip computation and reuse this layer's topk indices.
|
# next_skip_topk: when True, the next layer will skip computation and reuse this layer's topk indices.
|
||||||
if is_nextn:
|
if is_nextn:
|
||||||
self.skip_topk = False
|
self.skip_topk = True
|
||||||
self.next_skip_topk = False
|
self.next_skip_topk = True
|
||||||
else:
|
else:
|
||||||
self.index_topk_freq = getattr(config, "index_topk_freq", 1)
|
self.index_topk_freq = getattr(config, "index_topk_freq", 1)
|
||||||
self.index_topk_pattern = getattr(config, "index_topk_pattern", None)
|
self.index_topk_pattern = getattr(config, "index_topk_pattern", None)
|
||||||
if self.index_topk_pattern is None:
|
self.index_skip_topk_offset = getattr(
|
||||||
|
config, "index_skip_topk_offset", None
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
self.index_topk_pattern is None
|
||||||
|
and self.index_skip_topk_offset is not None
|
||||||
|
):
|
||||||
|
assert self.index_skip_topk_offset > 0, (
|
||||||
|
"index_skip_topk_offset must be positive; offset <= 0 "
|
||||||
|
"marks layer 0 as skip_topk with no prior topk to reuse"
|
||||||
|
)
|
||||||
|
self.skip_topk = (
|
||||||
|
max(layer_id - self.index_skip_topk_offset + 1, 0)
|
||||||
|
% self.index_topk_freq
|
||||||
|
!= 0
|
||||||
|
)
|
||||||
|
self.next_skip_topk = (
|
||||||
|
max(layer_id - self.index_skip_topk_offset + 2, 0)
|
||||||
|
% self.index_topk_freq
|
||||||
|
!= 0
|
||||||
|
)
|
||||||
|
elif self.index_topk_pattern is None:
|
||||||
self.skip_topk = max(layer_id - 1, 0) % self.index_topk_freq != 0
|
self.skip_topk = max(layer_id - 1, 0) % self.index_topk_freq != 0
|
||||||
self.next_skip_topk = layer_id % self.index_topk_freq != 0
|
self.next_skip_topk = layer_id % self.index_topk_freq != 0
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1851,6 +1851,20 @@ class ServerArgs:
|
|||||||
self.attention_backend = "dsa"
|
self.attention_backend = "dsa"
|
||||||
logger.info("Use dsa attention backend for DeepSeek with DSA.")
|
logger.info("Use dsa attention backend for DeepSeek with DSA.")
|
||||||
|
|
||||||
|
index_topk_freq = getattr(hf_config, "index_topk_freq", 1)
|
||||||
|
index_topk_pattern = getattr(hf_config, "index_topk_pattern", None)
|
||||||
|
if self.enable_two_batch_overlap and (
|
||||||
|
index_topk_freq > 1
|
||||||
|
or (index_topk_pattern is not None and "S" in index_topk_pattern)
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
"--enable-two-batch-overlap is not supported with DSA "
|
||||||
|
"index-topk sharing (index_topk_freq > 1 or an "
|
||||||
|
"index_topk_pattern containing shared layers): the TBO op "
|
||||||
|
"path does not propagate topk indices across layers, so "
|
||||||
|
"shared layers would run sparse attention without indices."
|
||||||
|
)
|
||||||
|
|
||||||
if not is_npu() and not is_xpu(): # CUDA or ROCm GPU
|
if not is_npu() and not is_xpu(): # CUDA or ROCm GPU
|
||||||
if self.enable_dsa_prefill_context_parallel:
|
if self.enable_dsa_prefill_context_parallel:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|||||||
@@ -897,6 +897,17 @@ class EAGLEWorker(TpModelWorker):
|
|||||||
|
|
||||||
# Forward multiple steps
|
# Forward multiple steps
|
||||||
scores = None
|
scores = None
|
||||||
|
# Reuse NSA/DSA topk_indices from the first draft forward step for
|
||||||
|
# subsequent steps, analogous to skip_topk in deepseek_v2.py layers.
|
||||||
|
# Only safe with topk == 1: select_top_k_tokens reorders candidate rows
|
||||||
|
# each step, which would desync the cached indices from their rows.
|
||||||
|
index_share_for_mtp_iteration = (
|
||||||
|
getattr(self.model_config.hf_config, "index_share_for_mtp_iteration", False)
|
||||||
|
and self.topk == 1
|
||||||
|
)
|
||||||
|
if index_share_for_mtp_iteration:
|
||||||
|
forward_batch.reuse_mtp_topk_indices = True
|
||||||
|
forward_batch.topk_indices = None
|
||||||
for i in range(self.speculative_num_steps):
|
for i in range(self.speculative_num_steps):
|
||||||
input_ids, hidden_states, scores, tree_info = select_top_k_tokens(
|
input_ids, hidden_states, scores, tree_info = select_top_k_tokens(
|
||||||
i, topk_p, topk_index, hidden_states, scores, self.topk
|
i, topk_p, topk_index, hidden_states, scores, self.topk
|
||||||
@@ -949,6 +960,9 @@ class EAGLEWorker(TpModelWorker):
|
|||||||
maybe_detect_inf(hidden_states, f"draft_forward step {i}: hidden_states")
|
maybe_detect_inf(hidden_states, f"draft_forward step {i}: hidden_states")
|
||||||
forward_batch.positions.add_(1)
|
forward_batch.positions.add_(1)
|
||||||
|
|
||||||
|
if index_share_for_mtp_iteration:
|
||||||
|
forward_batch.topk_indices = None
|
||||||
|
forward_batch.reuse_mtp_topk_indices = False
|
||||||
parent_list, top_scores_index, draft_tokens = organize_draft_results(
|
parent_list, top_scores_index, draft_tokens = organize_draft_results(
|
||||||
score_list, token_list, parents_list, self.speculative_num_draft_tokens
|
score_list, token_list, parents_list, self.speculative_num_draft_tokens
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -67,6 +67,26 @@ class HfModelConfigParser(ModelConfigParserBase):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.architectures is not None
|
||||||
|
and config.architectures[0] == "GlmMoeDsaForCausalLM"
|
||||||
|
):
|
||||||
|
# GlmMoeDsaConfig drops/clobbers raw checkpoint fields the DSA path
|
||||||
|
# needs, so re-read them from config.json and restore. Fixed upstream
|
||||||
|
# by https://github.com/huggingface/transformers/pull/46338; remove
|
||||||
|
# this block once SGLang requires transformers >= 5.10.
|
||||||
|
from transformers import PretrainedConfig
|
||||||
|
|
||||||
|
raw_config, _ = PretrainedConfig.get_config_dict(model, revision=revision)
|
||||||
|
for key in (
|
||||||
|
"qk_rope_head_dim",
|
||||||
|
"index_topk_freq",
|
||||||
|
):
|
||||||
|
if key in raw_config:
|
||||||
|
setattr(config, key, raw_config[key])
|
||||||
|
if hasattr(config, "qk_head_dim") and hasattr(config, "qk_nope_head_dim"):
|
||||||
|
config.qk_head_dim = config.qk_nope_head_dim + config.qk_rope_head_dim
|
||||||
|
|
||||||
if (
|
if (
|
||||||
config.architectures is not None
|
config.architectures is not None
|
||||||
and config.architectures[0] == "Phi4MMForCausalLM"
|
and config.architectures[0] == "Phi4MMForCausalLM"
|
||||||
|
|||||||
Reference in New Issue
Block a user