[Inkling] Add minimal DFLASH support (#31840)

This commit is contained in:
James Liu
2026-07-27 12:01:13 -07:00
committed by GitHub
parent 7cae831e41
commit 1da062f018
6 changed files with 186 additions and 26 deletions
@@ -438,7 +438,14 @@ def _resolve_dflash_draft_attention_backend(server_args: ServerArgs) -> None:
"""
from sglang.srt.utils import is_hip
supported_draft_backends = ("flashinfer", "fa3", "fa4", "triton", "ascend")
supported_draft_backends = (
"flashinfer",
"fa3",
"fa4",
"triton",
"trtllm_mha",
"ascend",
)
# Use triton on ROCm (no FlashInfer), flashinfer on CUDA.
fallback_backend = "triton" if is_hip() else "flashinfer"
@@ -453,13 +460,37 @@ def _resolve_dflash_draft_attention_backend(server_args: ServerArgs) -> None:
if draft_backend is None:
draft_backend = fallback_backend
elif draft_backend == "trtllm_mha":
logger.warning(
"DFLASH draft worker does not support 'trtllm_mha' because the "
"draft path requires per-layer DFlash attention. Falling back to "
"'%s'.",
fallback_backend,
from sglang.srt.speculative.dflash_utils import get_dflash_layer_types
from sglang.srt.utils.hf_transformers_utils import get_config
draft_hf_config = get_config(
server_args.speculative_draft_model_path,
trust_remote_code=server_args.trust_remote_code,
revision=server_args.speculative_draft_model_revision,
model_override_args=json.loads(server_args.json_model_override_args),
)
draft_backend = fallback_backend
draft_text_config = (
getattr(draft_hf_config, "text_config", None) or draft_hf_config
)
layer_types = get_dflash_layer_types(draft_hf_config)
num_layers = getattr(draft_text_config, "num_hidden_layers", None)
all_sliding = (
layer_types
and len(layer_types) == num_layers
and set(layer_types) == {"sliding_attention"}
)
all_causal = getattr(draft_text_config, "is_causal", False) is True
if not (all_sliding or all_causal):
logger.warning(
"DFLASH only enables 'trtllm_mha' when all layers use sliding "
"attention or the draft is explicitly causal; got "
"layer_types=%r, is_causal=%r. "
"Falling back to '%s'.",
layer_types,
getattr(draft_text_config, "is_causal", None),
fallback_backend,
)
draft_backend = fallback_backend
elif draft_backend not in supported_draft_backends:
logger.warning(
"DFLASH draft worker only supports attention_backend in %s for now, "
@@ -41,7 +41,7 @@ import copy
import inspect
import logging
from contextlib import contextmanager
from typing import TYPE_CHECKING, Dict, Optional, Union
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
import torch
import tqdm
@@ -120,6 +120,25 @@ logger = logging.getLogger(__name__)
_MAX_PREFILL_CUDA_GRAPH_PADDING_FACTOR = 2
def _slice_output_rows(output: Any, num_tokens: int) -> Any:
"""Slice every tensor leaf in a transformer-body output by token rows.
Full prefill graphs replay at a padded token bucket. Most models return a
single hidden-state tensor, while auxiliary-hidden-state models (for
example DFLASH targets) return nested tuple/list structures. Slicing the
outer sequence would change its structure instead of removing padded rows.
"""
if output is None:
return None
if torch.is_tensor(output) or isinstance(output, PPProxyTensors):
return output[:num_tokens]
if isinstance(output, tuple):
return tuple(_slice_output_rows(item, num_tokens) for item in output)
if isinstance(output, list):
return [_slice_output_rows(item, num_tokens) for item in output]
raise TypeError(f"Unsupported full prefill CUDA graph output: {type(output)}")
def prefill_failure_msg(backend_name: str) -> str:
"""Render PREFILL_CUDA_GRAPH_CAPTURE_FAILED_MSG with a backend-specific
numbered suggestion list. The runner is only constructed for BREAKABLE
@@ -1189,7 +1208,7 @@ class PrefillCudaGraphRunner(BaseCudaGraphRunner):
1, static_n
)[: ie.shape[0]].copy_(ie)
hs = self.backend.replay(shape_key, static_forward_batch, **kwargs)
return hs[:raw_num_tokens] if full_path else hs
return _slice_output_rows(hs, raw_num_tokens) if full_path else hs
original_layer_forward = self.layer_model.forward
self.layer_model.forward = replay_layer_forward
+7 -1
View File
@@ -57,7 +57,13 @@ def _get_dflash_layer_attention_params(
layer_type = layer_types[layer_id]
if layer_type == "full_attention":
return -1, AttentionType.ENCODER_ONLY
text_config = getattr(config, "text_config", None) or config
attention_type = (
AttentionType.DECODER
if getattr(text_config, "is_causal", False)
else AttentionType.ENCODER_ONLY
)
return -1, attention_type
if layer_type == "sliding_attention":
sliding_window_size = get_dflash_attention_sliding_window_size(config)
assert sliding_window_size is not None
+86 -6
View File
@@ -677,6 +677,32 @@ class InklingCausalLLM(nn.Module):
prefix=add_prefix("lm_head", prefix),
)
self.logits_processor = LogitsProcessor(config)
self._dflash_layers_to_capture: set[int] = set()
def set_dflash_layers_to_capture(self, layer_ids: list[int]) -> None:
"""Capture post-layer hidden states consumed by a DFLASH drafter."""
if layer_ids is None:
raise ValueError("DFLASH requires explicit target layer IDs.")
if len(layer_ids) != len(set(layer_ids)):
raise ValueError(f"DFLASH target layer IDs must be unique: {layer_ids}")
if layer_ids != sorted(layer_ids):
raise ValueError(f"DFLASH target layer IDs must be sorted: {layer_ids}")
invalid = [idx for idx in layer_ids if idx < 0 or idx >= len(self.layers)]
if invalid:
raise ValueError(
f"DFLASH target layer IDs out of range [0, {len(self.layers)}): {invalid}"
)
self._dflash_layers_to_capture = set(layer_ids)
# Inkling can defer an MoE all-reduce into the next layer. A tapped
# layer must instead materialize a complete hidden state at its tap.
for layer in self.layers:
if not hasattr(layer, "_mlp_ar_fusable_without_dflash"):
layer._mlp_ar_fusable_without_dflash = layer.mlp_ar_fusable
layer.mlp_ar_fusable = (
layer._mlp_ar_fusable_without_dflash
and layer.layer_id not in self._dflash_layers_to_capture
)
def get_input_embeddings(self):
# Fold embed_norm into the embedding so general_mm_embed_routine norms the text
@@ -778,6 +804,12 @@ class InklingCausalLLM(nn.Module):
fuse_ar_sconv = True
fuse_attn_ar = True
prev_mlp_partial = False
aux_hidden_states: Optional[list[torch.Tensor]] = (
[]
if self._dflash_layers_to_capture
and not forward_batch.forward_mode.is_idle()
else None
)
for layer in self.layers:
hidden_states, residual = layer(
hidden_states,
@@ -792,6 +824,18 @@ class InklingCausalLLM(nn.Module):
)
prev_mlp_sconv = layer.mlp_sconv
prev_mlp_partial = fuse_ar_sconv and layer.mlp_ar_fusable
if (
aux_hidden_states is not None
and layer.layer_id in self._dflash_layers_to_capture
):
# The trained taps are post-layer and precede the deferred
# mlp_sconv belonging to this layer.
tap_hidden = hidden_states
if layer.scattered_sconv:
tap_hidden = all_gather_hidden(tap_hidden, layer.attn_tp_group)
aux_hidden_states.append(
tap_hidden if residual is None else tap_hidden + residual
)
# The final layer's mlp_sconv was deferred; run it now — as an eager break
# under BCG (so it re-reads live per-seq metadata at replay), else inline.
if prev_mlp_sconv is not None and not forward_batch.forward_mode.is_idle():
@@ -808,7 +852,11 @@ class InklingCausalLLM(nn.Module):
norm=self.norm,
norm_residual=residual,
)
return hidden_states
return (
(hidden_states, aux_hidden_states)
if self._dflash_layers_to_capture
else hidden_states
)
# Fused extend tail: {AR + scattered sconv}, then the final
# norm unfused on the gathered [T, H].
hidden_states = ar_scattered_sconv_fused(
@@ -818,7 +866,11 @@ class InklingCausalLLM(nn.Module):
get_tensor_model_parallel_group(),
)
hidden_states, _ = self.norm(hidden_states, residual)
return hidden_states
return (
(hidden_states, aux_hidden_states)
if self._dflash_layers_to_capture
else hidden_states
)
if prev_mlp_partial:
fm = forward_batch.forward_mode
if fm.is_decode() or fm.is_target_verify():
@@ -832,7 +884,11 @@ class InklingCausalLLM(nn.Module):
forward_batch,
get_tensor_model_parallel_group(),
)
return hidden_states
return (
(hidden_states, aux_hidden_states)
if self._dflash_layers_to_capture
else hidden_states
)
# Fused extend tail: {AR + full-width sconv + cache update}
# (non-scattered), then the final norm unfused.
hidden_states = ar_fullwidth_sconv_fused(
@@ -842,7 +898,11 @@ class InklingCausalLLM(nn.Module):
get_tensor_model_parallel_group(),
)
hidden_states, _ = self.norm(hidden_states, residual)
return hidden_states
return (
(hidden_states, aux_hidden_states)
if self._dflash_layers_to_capture
else hidden_states
)
# Same gate as the per-layer group: the eager break needs the tc_piecewise
# context (installed only by the prefill BCG runner) to read the live
# forward_batch at replay; else run inline with the passed forward_batch.
@@ -870,7 +930,11 @@ class InklingCausalLLM(nn.Module):
hidden_states, self.layers[-1].attn_tp_group
)
hidden_states, _ = self.norm(hidden_states, residual)
return hidden_states
return (
(hidden_states, aux_hidden_states)
if self._dflash_layers_to_capture
else hidden_states
)
class InklingAudio(nn.Module):
@@ -1066,6 +1130,14 @@ class InklingForConditionalGeneration(nn.Module):
def get_embed_and_head(self):
return self.llm.embed_tokens.weight, self.llm.lm_head.weight
@property
def lm_head(self) -> nn.Module:
"""Expose the target head through the common speculative API."""
return self.llm.lm_head
def set_dflash_layers_to_capture(self, layer_ids: list[int]) -> None:
self.llm.set_dflash_layers_to_capture(layer_ids)
def get_num_kv_cache_layers(self) -> int:
return self.text_config.num_hidden_layers
@@ -1101,6 +1173,9 @@ class InklingForConditionalGeneration(nn.Module):
data_embedding_funcs=data_embedding_funcs,
positions=positions,
)
aux_hidden_states = None
if self.llm._dflash_layers_to_capture:
hidden_states, aux_hidden_states = hidden_states
mup_width_multiplier = self.config.text_config.logits_mup_width_multiplier
hidden_states_for_logits = (
hidden_states
@@ -1115,7 +1190,12 @@ class InklingForConditionalGeneration(nn.Module):
hidden_states_for_logits,
self.llm.lm_head,
forward_batch,
hidden_states_before_norm=hidden_states,
aux_hidden_states=aux_hidden_states,
# DFLASH needs the concatenated tap states. LogitsProcessor gives
# hidden_states_before_norm precedence, so omit it in this mode.
hidden_states_before_norm=(
None if aux_hidden_states is not None else hidden_states
),
)
def update_conv_state_after_mtp_verify(
@@ -324,11 +324,15 @@ class DFlashWorkerV2(BaseSpecWorker):
def init_attention_backends(self):
self._draft_worker.init_attention_backends()
target_model = self.model_runner.model
self._need_mamba_verify_commit = mambaish_config(
self.model_runner.model_config
) is not None and hasattr(
self.model_runner.attn_backend,
"update_mamba_state_after_mtp_verify",
) is not None and (
hasattr(
self.model_runner.attn_backend,
"update_mamba_state_after_mtp_verify",
)
or hasattr(target_model, "update_conv_state_after_mtp_verify")
)
def init_cuda_graphs(self):
@@ -1278,12 +1282,25 @@ class DFlashWorkerV2(BaseSpecWorker):
torch.full_like(to_track_ith, -1, dtype=torch.int64),
)
attn_backend.update_mamba_state_after_mtp_verify(
last_correct_step_indices=last_correct_step_indices,
mamba_track_indices=batch.mamba_track_indices,
mamba_steps_to_track=mamba_steps_to_track,
model=self.target_worker.model_runner.model,
)
model_runner = self.target_worker.model_runner
if hasattr(attn_backend, "update_mamba_state_after_mtp_verify"):
attn_backend.update_mamba_state_after_mtp_verify(
last_correct_step_indices=last_correct_step_indices,
mamba_track_indices=batch.mamba_track_indices,
mamba_steps_to_track=mamba_steps_to_track,
model=model_runner.model,
)
elif hasattr(model_runner.model, "update_conv_state_after_mtp_verify"):
# Inkling's short convolutions access the mamba pool directly, so
# their accepted verify state is committed by the model rather
# than an attention-backend wrapper.
model_runner.model.update_conv_state_after_mtp_verify(
req_to_token_pool=model_runner.req_to_token_pool,
req_pool_indices=batch.req_pool_indices[: commit_lens.shape[0]],
last_correct_step_indices=last_correct_step_indices,
mamba_track_indices=batch.mamba_track_indices,
mamba_steps_to_track=mamba_steps_to_track,
)
def _ensure_accept_bonus_buffers(self, bs: int) -> None:
if self._accept_bonus_buffer_cap >= int(bs):
@@ -22,7 +22,14 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
_SUPPORTED_DRAFT_BACKENDS = ("flashinfer", "fa3", "fa4", "triton", "ascend")
_SUPPORTED_DRAFT_BACKENDS = (
"flashinfer",
"fa3",
"fa4",
"triton",
"trtllm_mha",
"ascend",
)
class DraftWorkerBundle(msgspec.Struct, frozen=True):