Moonlight556's picture
Add the JetSpec++ Qwen3-8B drafter, adapter and calibration
43d3aa9 verified
Raw History Blame Contribute Delete
17.8 kB
import logging
from typing import Callable, Optional
import torch
from torch import nn
from transformers import DynamicCache
from transformers.cache_utils import Cache
from transformers.modeling_outputs import CausalLMOutputWithPast
from transformers.models.qwen3.modeling_qwen3 import (
ALL_ATTENTION_FUNCTIONS,
FlashAttentionKwargs,
GradientCheckpointingLayer,
Qwen3Config,
Qwen3MLP,
Qwen3PreTrainedModel,
Qwen3RMSNorm,
Qwen3RotaryEmbedding,
eager_attention_forward,
rotate_half,
)
from typing_extensions import Tuple, Unpack
logger = logging.getLogger(__name__)
def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
cos = cos.unsqueeze(unsqueeze_dim)
sin = sin.unsqueeze(unsqueeze_dim)
q_len = q.size(-2)
q_embed = (q * cos[..., -q_len:, :]) + (rotate_half(q) * sin[..., -q_len:, :])
k_embed = (k * cos) + (rotate_half(k) * sin)
return q_embed, k_embed
def _to_additive_attention_mask(
attention_mask: torch.Tensor,
*,
query_dtype: torch.dtype,
device: torch.device,
key_len: int,
) -> torch.Tensor:
if attention_mask.ndim == 4:
attention_mask = attention_mask[:, :, :, :key_len]
if attention_mask.dtype == torch.bool:
additive_mask = torch.zeros_like(attention_mask, dtype=query_dtype, device=device)
return additive_mask.masked_fill(
attention_mask.logical_not().to(device=device),
torch.finfo(query_dtype).min,
)
return attention_mask.to(device=device, dtype=query_dtype)
def _build_dflash_causal_attention_mask(
*,
query: torch.Tensor,
key: torch.Tensor,
cached_kv_len: int,
ctx_len: int,
) -> torch.Tensor:
q_len = query.shape[-2]
kv_len = key.shape[-2]
key_positions = torch.arange(kv_len, device=query.device)
query_positions = cached_kv_len + ctx_len + torch.arange(q_len, device=query.device)
can_attend = key_positions.unsqueeze(0) <= query_positions.unsqueeze(1)
mask = torch.zeros((1, 1, q_len, kv_len), dtype=query.dtype, device=query.device)
return mask.masked_fill(can_attend.logical_not().unsqueeze(0).unsqueeze(0), torch.finfo(query.dtype).min)
class Qwen3DFlashAttention(nn.Module):
"""Multi-headed attention from 'Attention Is All You Need' paper"""
def __init__(self, config: Qwen3Config, layer_idx: int):
super().__init__()
self.config = config
self.layer_idx = layer_idx
self.head_dim = getattr(
config, "head_dim", config.hidden_size // config.num_attention_heads
)
self.num_key_value_groups = (
config.num_attention_heads // config.num_key_value_heads
)
self.scaling = self.head_dim**-0.5
self.attention_dropout = config.attention_dropout
dflash_config = getattr(config, "dflash_config", {}) or {}
self.is_causal = bool(dflash_config.get("causal_head", False))
self.q_proj = nn.Linear(
config.hidden_size,
config.num_attention_heads * self.head_dim,
bias=config.attention_bias,
)
self.k_proj = nn.Linear(
config.hidden_size,
config.num_key_value_heads * self.head_dim,
bias=config.attention_bias,
)
self.v_proj = nn.Linear(
config.hidden_size,
config.num_key_value_heads * self.head_dim,
bias=config.attention_bias,
)
self.o_proj = nn.Linear(
config.num_attention_heads * self.head_dim,
config.hidden_size,
bias=config.attention_bias,
)
self.q_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
self.k_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
self.sliding_window = (
config.sliding_window
if config.layer_types[layer_idx] == "sliding_attention"
else None
)
def forward(
self,
hidden_states: torch.Tensor,
target_hidden: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor],
attention_mask: Optional[torch.Tensor],
inject_target_context: bool = True,
past_key_values: Optional[Cache] = None,
cache_position: Optional[torch.LongTensor] = None,
**kwargs: Unpack[FlashAttentionKwargs],
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
bsz, q_len = hidden_states.shape[:-1]
ctx_len = target_hidden.shape[1] if inject_target_context else 0
is_causal = kwargs.pop("is_causal", None)
if is_causal is None:
is_causal = self.is_causal
q = self.q_proj(hidden_states)
q = q.view(bsz, q_len, -1, self.head_dim)
q = self.q_norm(q).transpose(1, 2)
if inject_target_context:
k_ctx = self.k_proj(target_hidden)
k_noise = self.k_proj(hidden_states)
v_ctx = self.v_proj(target_hidden)
v_noise = self.v_proj(hidden_states)
k = torch.cat([k_ctx, k_noise], dim=1)
v = torch.cat([v_ctx, v_noise], dim=1)
else:
k = self.k_proj(hidden_states)
v = self.v_proj(hidden_states)
k = k.view(bsz, ctx_len + q_len, -1, self.head_dim)
v = v.view(bsz, ctx_len + q_len, -1, self.head_dim)
k = self.k_norm(k).transpose(1, 2)
v = v.transpose(1, 2)
cos, sin = position_embeddings
if not inject_target_context:
cos = cos[..., -q_len:, :]
sin = sin[..., -q_len:, :]
q, k = apply_rotary_pos_emb(q, k, cos, sin)
cached_kv_len = past_key_values.get_seq_length() if past_key_values is not None else 0
if past_key_values is not None:
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
k, v = past_key_values.update(k, v, self.layer_idx, cache_kwargs)
attn_backend = kwargs.pop(
"attention_backend",
self.config._attn_implementation,
)
use_explicit_dflash_causal_mask = bool(is_causal) and attn_backend in {"eager", "sdpa"}
if not inject_target_context and attn_backend in {"eager", "sdpa"}:
block_local_mask = _build_block_local_causal_attention_mask(
query=q,
key=k,
block_size=self.config.block_size,
)
if attention_mask is not None:
if attention_mask.shape[-1] != k.shape[-2]:
attention_mask = attention_mask[..., -k.shape[-2] :]
block_local_mask = block_local_mask + _to_additive_attention_mask(
attention_mask,
query_dtype=q.dtype,
device=q.device,
key_len=k.shape[-2],
)
attention_mask = block_local_mask
is_causal = False
elif use_explicit_dflash_causal_mask:
dflash_causal_mask = _build_dflash_causal_attention_mask(
query=q, key=k, cached_kv_len=cached_kv_len, ctx_len=ctx_len,
)
if attention_mask is not None:
dflash_causal_mask = dflash_causal_mask + _to_additive_attention_mask(
attention_mask, query_dtype=q.dtype, device=q.device, key_len=k.shape[-2],
)
attention_mask = dflash_causal_mask
is_causal = False
kwargs["is_causal"] = is_causal
attn_fn: Callable = eager_attention_forward
if attn_backend != "eager":
attn_fn = ALL_ATTENTION_FUNCTIONS[attn_backend]
attn_output, attn_weights = attn_fn(
self,
q,
k,
v,
attention_mask,
dropout=0.0 if not self.training else self.attention_dropout,
scaling=self.scaling,
sliding_window=self.sliding_window,
**kwargs,
)
attn_output = attn_output.reshape(bsz, q_len, -1)
attn_output = self.o_proj(attn_output)
return attn_output, attn_weights
def _resolve_drafter_mlp(config: Qwen3Config) -> bool:
dflash_config = getattr(config, "dflash_config", {}) or {}
raw_value = dflash_config.get("drafter_mlp", True)
if not isinstance(raw_value, bool):
raise ValueError(
f"dflash_config.drafter_mlp must be a boolean, got {raw_value!r}."
)
return raw_value
def _resolve_inject_layer_ids(config: Qwen3Config) -> Optional[list[int]]:
dflash_config = getattr(config, "dflash_config", {}) or {}
if "inject_layer_ids" not in dflash_config:
return None
inject_layer_ids = dflash_config["inject_layer_ids"]
if not isinstance(inject_layer_ids, list) or any(
not isinstance(layer_idx, int) or isinstance(layer_idx, bool)
for layer_idx in inject_layer_ids
):
raise ValueError(
"dflash_config.inject_layer_ids must be a list of integer layer indices, "
f"got {inject_layer_ids!r}."
)
invalid_ids = [
layer_idx
for layer_idx in inject_layer_ids
if layer_idx < 0 or layer_idx >= config.num_hidden_layers
]
if invalid_ids:
raise ValueError(
"dflash_config.inject_layer_ids contains indices outside "
f"[0, {config.num_hidden_layers}): {invalid_ids}."
)
return inject_layer_ids
class Qwen3DFlashDecoderLayer(GradientCheckpointingLayer):
def __init__(self, config: Qwen3Config, layer_idx: int):
super().__init__()
self.hidden_size = config.hidden_size
self.drafter_mlp = _resolve_drafter_mlp(config)
inject_layer_ids = _resolve_inject_layer_ids(config)
self.inject_target_context = (
inject_layer_ids is None or layer_idx in inject_layer_ids
)
self.self_attn = Qwen3DFlashAttention(config=config, layer_idx=layer_idx)
self.mlp = Qwen3MLP(config)
self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.post_attention_layernorm = Qwen3RMSNorm(
config.hidden_size, eps=config.rms_norm_eps
)
def forward(
self,
target_hidden: Optional[torch.Tensor] = None,
hidden_states: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
position_ids: Optional[torch.LongTensor] = None,
past_key_value: Optional[Cache] = None,
output_attentions: Optional[bool] = False,
use_cache: Optional[bool] = False,
cache_position: Optional[torch.LongTensor] = None,
position_embeddings: Optional[
Tuple[torch.Tensor, torch.Tensor]
] = None, # necessary, but kept here for BC
**kwargs: Unpack[FlashAttentionKwargs],
) -> Tuple[
torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]
]:
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states)
hidden_states = self.self_attn(
hidden_states=hidden_states,
target_hidden=target_hidden,
attention_mask=attention_mask,
inject_target_context=self.inject_target_context,
position_ids=position_ids,
past_key_values=past_key_value,
output_attentions=output_attentions,
use_cache=use_cache,
cache_position=cache_position,
position_embeddings=position_embeddings,
**kwargs,
)[0]
hidden_states = residual + hidden_states
if self.drafter_mlp:
residual = hidden_states
hidden_states = self.post_attention_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
hidden_states = residual + hidden_states
return hidden_states
def build_target_layer_ids(num_target_layers: int, num_draft_layers: int):
if num_draft_layers == 1:
return [(num_target_layers // 2)]
start = 1
end = num_target_layers - 3
span = end - start
target_layer_ids = [
int(round(start + (i * span) / (num_draft_layers - 1)))
for i in range(num_draft_layers)
]
return target_layer_ids
def extract_context_feature(
hidden_states: list[torch.Tensor],
layer_ids: Optional[list[int]],
) -> torch.Tensor:
offset = 1
selected_states = []
for layer_id in layer_ids:
selected_states.append(hidden_states[layer_id + offset])
target_hidden = torch.cat(selected_states, dim=-1)
return target_hidden
class DFlashDraftModel(Qwen3PreTrainedModel):
config_class = Qwen3Config
_no_split_modules = ["Qwen3DFlashDecoderLayer"]
def __init__(self, config) -> None:
super().__init__(config)
self.config = config
if not hasattr(self.config, "dflash_config") or self.config.dflash_config is None:
self.config.dflash_config = {}
self.causal_head = bool(self.config.dflash_config.get("causal_head", False))
self.drafter_mlp = _resolve_drafter_mlp(self.config)
self.config.dflash_config["drafter_mlp"] = self.drafter_mlp
configured_inject_layer_ids = _resolve_inject_layer_ids(self.config)
self.inject_layer_ids = (
list(range(config.num_hidden_layers))
if configured_inject_layer_ids is None
else list(configured_inject_layer_ids)
)
self.has_masked_inject_layers = set(self.inject_layer_ids) != set(
range(config.num_hidden_layers)
)
self.layers = nn.ModuleList(
[
Qwen3DFlashDecoderLayer(config, layer_idx)
for layer_idx in range(config.num_hidden_layers)
]
)
dflash_config = getattr(config, "dflash_config", {}) or {}
self.target_layer_ids = dflash_config.get(
"target_layer_ids",
build_target_layer_ids(config.num_target_layers, config.num_hidden_layers),
)
self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.rotary_emb = Qwen3RotaryEmbedding(config)
target_hidden_size = getattr(config, "target_hidden_size", config.hidden_size)
self.fc = nn.Linear(
len(self.target_layer_ids) * target_hidden_size,
config.hidden_size,
bias=False,
)
self.hidden_norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
jacobi_forcing = dflash_config.get("jacobi_forcing", False)
if not isinstance(jacobi_forcing, bool):
raise ValueError("dflash_config.jacobi_forcing must be a boolean.")
self.jacobi_forcing = jacobi_forcing
self.stock_drafter = dflash_config.get("stock_drafter", False)
if not isinstance(self.stock_drafter, bool):
raise ValueError("dflash_config.stock_drafter must be a boolean.")
if self.stock_drafter and dflash_config.get("fresh_fraction") != 1.0:
raise ValueError("stock_drafter requires an explicit fresh_fraction=1.0.")
self.config.dflash_config["stock_drafter"] = self.stock_drafter
# All-fresh adapter marking contributes only a learned context bias.
# Stock DFlash omits this bias and its embedding tensor.
if self.jacobi_forcing and not self.stock_drafter:
self.adapted_position_embedding = nn.Embedding(
2,
len(self.target_layer_ids) * target_hidden_size,
)
self.block_size = config.block_size
self.mask_token_id = dflash_config.get("mask_token_id", None)
self.post_init()
if self.jacobi_forcing and not self.stock_drafter:
nn.init.zeros_(self.adapted_position_embedding.weight)
if not self.drafter_mlp:
for layer in self.layers:
del layer.mlp
def resolve_causal_head(self, head_type: str = "auto") -> bool:
if head_type == "auto":
return bool(self.causal_head)
if head_type == "bidirectional":
return False
if head_type == "causal":
return True
raise ValueError(
f"Unsupported head_type={head_type!r}. Expected one of: auto, bidirectional, causal."
)
def forward(
self,
position_ids: torch.LongTensor,
attention_mask: Optional[torch.Tensor] = None,
noise_embedding: Optional[torch.Tensor] = None,
target_hidden: Optional[torch.Tensor] = None,
past_key_values: Optional[Cache] = None,
use_cache: bool = False,
**kwargs,
) -> CausalLMOutputWithPast:
no_inject_attention_mask = kwargs.pop("no_inject_attention_mask", None)
attention_backend = kwargs.get(
"attention_backend",
self.config._attn_implementation,
)
hidden_states = noise_embedding
target_hidden = self.hidden_norm(self.fc(target_hidden))
position_embeddings = self.rotary_emb(hidden_states, position_ids)
for layer_idx, layer in enumerate(self.layers):
layer_attention_mask = attention_mask
if not layer.inject_target_context and no_inject_attention_mask is not None:
layer_attention_mask = no_inject_attention_mask
hidden_states = layer(
hidden_states=hidden_states,
target_hidden=target_hidden,
attention_mask=layer_attention_mask,
position_ids=position_ids,
past_key_value=past_key_values,
use_cache=use_cache,
position_embeddings=position_embeddings,
**kwargs,
)
hidden_states = self.norm(hidden_states)
return hidden_states