cm00cm commited on
Commit
21e29a6
·
verified ·
1 Parent(s): 877bc11

final checkpoint: epoch_10_step_311200

Browse files
Files changed (4) hide show
  1. config.json +64 -0
  2. dflash.py +484 -0
  3. dspark.py +160 -0
  4. model.safetensors +3 -0
config.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "DSparkDraftModel"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "auto_map": {
8
+ "AutoModel": "dspark.DSparkDraftModel"
9
+ },
10
+ "block_size": 7,
11
+ "bos_token_id": null,
12
+ "confidence_head_with_markov": true,
13
+ "dflash_config": {
14
+ "mask_token_id": 200064,
15
+ "target_layer_ids": [
16
+ 5,
17
+ 17,
18
+ 35,
19
+ 47,
20
+ 59
21
+ ]
22
+ },
23
+ "dtype": "bfloat16",
24
+ "enable_confidence_head": true,
25
+ "eos_token_id": [
26
+ 200006,
27
+ 200000,
28
+ 200002,
29
+ 200003
30
+ ],
31
+ "head_dim": 64,
32
+ "hidden_act": "silu",
33
+ "hidden_size": 6144,
34
+ "initializer_range": 0.02,
35
+ "intermediate_size": 12288,
36
+ "layer_types": [
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention"
42
+ ],
43
+ "markov_head_type": "vanilla",
44
+ "markov_rank": 256,
45
+ "max_position_embeddings": 1048576,
46
+ "max_window_layers": 5,
47
+ "model_type": "qwen3",
48
+ "num_attention_heads": 64,
49
+ "num_hidden_layers": 5,
50
+ "num_key_value_heads": 16,
51
+ "num_target_layers": 66,
52
+ "pad_token_id": 200006,
53
+ "rms_norm_eps": 1e-05,
54
+ "rope_parameters": {
55
+ "rope_theta": 8000000,
56
+ "rope_type": "default"
57
+ },
58
+ "sliding_window": null,
59
+ "tie_word_embeddings": false,
60
+ "transformers_version": "5.8.1",
61
+ "use_cache": true,
62
+ "use_sliding_window": false,
63
+ "vocab_size": 201024
64
+ }
dflash.py ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from functools import partial
3
+ from typing import Callable, Optional
4
+
5
+ import torch
6
+ from torch import nn
7
+ from torch.nn.attention.flex_attention import BlockMask, flex_attention
8
+ from transformers import DynamicCache
9
+ from transformers.cache_utils import Cache
10
+ from transformers.modeling_outputs import CausalLMOutputWithPast
11
+ from transformers.models.qwen3.modeling_qwen3 import (
12
+ ALL_ATTENTION_FUNCTIONS,
13
+ FlashAttentionKwargs,
14
+ GradientCheckpointingLayer,
15
+ Qwen3Config,
16
+ Qwen3MLP,
17
+ Qwen3PreTrainedModel,
18
+ Qwen3RMSNorm,
19
+ Qwen3RotaryEmbedding,
20
+ eager_attention_forward,
21
+ rotate_half,
22
+ )
23
+ from typing_extensions import Tuple, Unpack
24
+
25
+ # FlashAttention-4 flex backend, opt-in via env SPECFORGE_DRAFT_FLEX_BACKEND=fa4.
26
+ # flex_attention with kernel_options={"BACKEND": "FLASH"} runs the FA4 kernel instead
27
+ # of the Triton flex kernel (ref: meta-pytorch/attention-gym flex_flash_attention.py).
28
+ #
29
+ # STATUS on this stack (torch 2.11 / GB300 sm_10.3): the FA4 kernel works for a small
30
+ # head_dim (64/128) BUT ONLY for a mask_mod that captures no tensors (it needs the
31
+ # asymmetric block sparsity BLOCK_SIZE=(q=256, kv=128); see core/dflash.py). The DSpark
32
+ # dual-source mask (`create_dflash_block_mask`) MUST capture per-sample `anchor_positions`
33
+ # / `block_keep_mask` tensors, and the FA4 CuteDSL template fails on any captured-tensor
34
+ # mask_mod ("CuteDSL template failed"), both dynamic=True and False. => FA4 is currently
35
+ # NOT usable for the DSpark drafter; the default Triton flex backend (used when this env
36
+ # is unset) handles the captured-tensor mask correctly and is the supported path.
37
+ # (The DeepSeek-V4 DSpark draft was likewise FA4-ruled-out, there for head_dim 512.)
38
+ # The code path is kept, gated + off by default, for a future stack / a captured-tensor-
39
+ # free mask formulation. dynamic=True: draft Q/context lengths vary per batch.
40
+ _FLEX_FA4_COMPILED = None
41
+
42
+
43
+ def _flex_fa4():
44
+ global _FLEX_FA4_COMPILED
45
+ if _FLEX_FA4_COMPILED is None:
46
+ _FLEX_FA4_COMPILED = torch.compile(
47
+ partial(flex_attention, kernel_options={"BACKEND": "FLASH"}),
48
+ dynamic=True,
49
+ )
50
+ return _FLEX_FA4_COMPILED
51
+
52
+
53
+ def sample(logits: torch.Tensor, temperature: float = 0.0) -> torch.Tensor:
54
+ if temperature < 1e-5:
55
+ return torch.argmax(logits, dim=-1)
56
+ bsz, seq_len, vocab_size = logits.shape
57
+ logits = logits.view(-1, vocab_size)
58
+ logits = logits / temperature
59
+ probs = torch.softmax(logits, dim=-1)
60
+ return torch.multinomial(probs, num_samples=1).view(bsz, seq_len)
61
+
62
+
63
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
64
+ cos = cos.unsqueeze(unsqueeze_dim)
65
+ sin = sin.unsqueeze(unsqueeze_dim)
66
+ q_len = q.size(-2)
67
+ q_embed = (q * cos[..., -q_len:, :]) + (rotate_half(q) * sin[..., -q_len:, :])
68
+ k_embed = (k * cos) + (rotate_half(k) * sin)
69
+ return q_embed, k_embed
70
+
71
+
72
+ class Qwen3DFlashAttention(nn.Module):
73
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
74
+
75
+ def __init__(self, config: Qwen3Config, layer_idx: int):
76
+ super().__init__()
77
+ self.config = config
78
+ self.layer_idx = layer_idx
79
+ self.head_dim = getattr(
80
+ config, "head_dim", config.hidden_size // config.num_attention_heads
81
+ )
82
+ num_attention_heads = int(config.num_attention_heads)
83
+ num_key_value_heads = int(config.num_key_value_heads)
84
+ if (
85
+ num_attention_heads <= 0
86
+ or num_key_value_heads <= 0
87
+ or num_attention_heads % num_key_value_heads != 0
88
+ ):
89
+ raise ValueError(
90
+ "Qwen3DFlashAttention requires positive attention head counts and "
91
+ "num_attention_heads divisible by num_key_value_heads, got "
92
+ f"num_attention_heads={num_attention_heads}, "
93
+ f"num_key_value_heads={num_key_value_heads}."
94
+ )
95
+ self.num_key_value_groups = num_attention_heads // num_key_value_heads
96
+ self.scaling = self.head_dim**-0.5
97
+ self.attention_dropout = config.attention_dropout
98
+ self.is_causal = False
99
+ self.q_proj = nn.Linear(
100
+ config.hidden_size,
101
+ config.num_attention_heads * self.head_dim,
102
+ bias=config.attention_bias,
103
+ )
104
+ self.k_proj = nn.Linear(
105
+ config.hidden_size,
106
+ config.num_key_value_heads * self.head_dim,
107
+ bias=config.attention_bias,
108
+ )
109
+ self.v_proj = nn.Linear(
110
+ config.hidden_size,
111
+ config.num_key_value_heads * self.head_dim,
112
+ bias=config.attention_bias,
113
+ )
114
+ self.o_proj = nn.Linear(
115
+ config.num_attention_heads * self.head_dim,
116
+ config.hidden_size,
117
+ bias=config.attention_bias,
118
+ )
119
+ self.q_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
120
+ self.k_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
121
+ self.sliding_window = (
122
+ config.sliding_window
123
+ if config.layer_types[layer_idx] == "sliding_attention"
124
+ else None
125
+ )
126
+
127
+ # Keep the dual-source attention OUT of any outer torch.compile region: the
128
+ # flex_attention block-mask HOP fails inductor lowering when nested inside a
129
+ # larger dynamic-shape graph on this stack (CantSplit / "unsupported operand &"),
130
+ # even though it compiles fine on its own (HF's flex integration compiles it
131
+ # separately). Marking the attention compiler-disabled lets SPECFORGE_COMPILE_DRAFT
132
+ # fuse the rest of the block (RoPE, RMSNorm, MLP, residual) while the flex
133
+ # attention keeps its own (block-sparse, separately-compiled) fast path.
134
+ @torch.compiler.disable
135
+ def forward(
136
+ self,
137
+ hidden_states: torch.Tensor,
138
+ target_hidden: torch.Tensor,
139
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
140
+ attention_mask: Optional[torch.Tensor],
141
+ past_key_values: Optional[Cache] = None,
142
+ cache_position: Optional[torch.LongTensor] = None,
143
+ **kwargs: Unpack[FlashAttentionKwargs],
144
+ ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
145
+ bsz, q_len = hidden_states.shape[:-1]
146
+ ctx_len = target_hidden.shape[1]
147
+ q = self.q_proj(hidden_states)
148
+ q = q.view(bsz, q_len, -1, self.head_dim)
149
+ q = self.q_norm(q).transpose(1, 2)
150
+ k_ctx = self.k_proj(target_hidden)
151
+ k_noise = self.k_proj(hidden_states)
152
+ v_ctx = self.v_proj(target_hidden)
153
+ v_noise = self.v_proj(hidden_states)
154
+ k = torch.cat([k_ctx, k_noise], dim=1).view(
155
+ bsz, ctx_len + q_len, -1, self.head_dim
156
+ )
157
+ v = torch.cat([v_ctx, v_noise], dim=1).view(
158
+ bsz, ctx_len + q_len, -1, self.head_dim
159
+ )
160
+ k = self.k_norm(k).transpose(1, 2)
161
+ v = v.transpose(1, 2)
162
+ cos, sin = position_embeddings
163
+ q, k = apply_rotary_pos_emb(q, k, cos, sin)
164
+ if past_key_values is not None:
165
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
166
+ k, v = past_key_values.update(k, v, self.layer_idx, cache_kwargs)
167
+ # FA4 flex path (opt-in): call flex_attention with the FLASH kernel directly on
168
+ # the prebuilt dual-source BlockMask, bypassing HF's Triton-flex wrapper. q/k/v
169
+ # are already [B, H, S, D]; flex returns [B, H, S, D] -> transpose to [B, S, H, D]
170
+ # to match the reshape below. GQA (num_kv_heads < num_heads) via enable_gqa.
171
+ if (
172
+ self.config._attn_implementation in ("flex_attention", "flex")
173
+ and os.environ.get("SPECFORGE_DRAFT_FLEX_BACKEND") == "fa4"
174
+ and isinstance(attention_mask, BlockMask)
175
+ ):
176
+ attn_output = _flex_fa4()(
177
+ q,
178
+ k,
179
+ v,
180
+ block_mask=attention_mask,
181
+ scale=self.scaling,
182
+ enable_gqa=(self.num_key_value_groups > 1),
183
+ )
184
+ attn_output = attn_output.transpose(1, 2).contiguous()
185
+ attn_weights = None
186
+ else:
187
+ attn_fn: Callable = eager_attention_forward
188
+ if self.config._attn_implementation != "eager":
189
+ attn_fn = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
190
+ # PyTorch may route short-query GQA shapes to flex_decoding. On the
191
+ # GB300 torch 2.11 stack that path can produce no valid Inductor
192
+ # choices (for example q=[1,64,Q,64], kv=[1,16,K,64]). Keep MHA on
193
+ # the normal auto-selected path, but make GQA use the regular Triton
194
+ # flex-attention kernel. An explicitly selected BACKEND supersedes
195
+ # this legacy knob and is left untouched.
196
+ if (
197
+ self.config._attn_implementation in ("flex_attention", "flex")
198
+ and self.num_key_value_groups > 1
199
+ ):
200
+ kernel_options = dict(kwargs.get("kernel_options") or {})
201
+ if "BACKEND" not in kernel_options:
202
+ kernel_options["FORCE_USE_FLEX_ATTENTION"] = True
203
+ kwargs["kernel_options"] = kernel_options
204
+ attn_output, attn_weights = attn_fn(
205
+ self,
206
+ q,
207
+ k,
208
+ v,
209
+ attention_mask,
210
+ dropout=0.0 if not self.training else self.attention_dropout,
211
+ scaling=self.scaling,
212
+ sliding_window=self.sliding_window,
213
+ **kwargs,
214
+ )
215
+ attn_output = attn_output.reshape(bsz, q_len, -1)
216
+ attn_output = self.o_proj(attn_output)
217
+ return attn_output, attn_weights
218
+
219
+
220
+ class Qwen3DFlashDecoderLayer(GradientCheckpointingLayer):
221
+ def __init__(self, config: Qwen3Config, layer_idx: int):
222
+ super().__init__()
223
+ self.hidden_size = config.hidden_size
224
+ self.self_attn = Qwen3DFlashAttention(config=config, layer_idx=layer_idx)
225
+ self.mlp = Qwen3MLP(config)
226
+ self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
227
+ self.post_attention_layernorm = Qwen3RMSNorm(
228
+ config.hidden_size, eps=config.rms_norm_eps
229
+ )
230
+
231
+ def forward(
232
+ self,
233
+ target_hidden: Optional[torch.Tensor] = None,
234
+ hidden_states: Optional[torch.Tensor] = None,
235
+ attention_mask: Optional[torch.Tensor] = None,
236
+ position_ids: Optional[torch.LongTensor] = None,
237
+ past_key_value: Optional[Cache] = None,
238
+ output_attentions: Optional[bool] = False,
239
+ use_cache: Optional[bool] = False,
240
+ cache_position: Optional[torch.LongTensor] = None,
241
+ position_embeddings: Optional[
242
+ Tuple[torch.Tensor, torch.Tensor]
243
+ ] = None, # necessary, but kept here for BC
244
+ **kwargs: Unpack[FlashAttentionKwargs],
245
+ ) -> Tuple[
246
+ torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]
247
+ ]:
248
+ residual = hidden_states
249
+ hidden_states = self.input_layernorm(hidden_states)
250
+ hidden_states = self.self_attn(
251
+ hidden_states=hidden_states,
252
+ target_hidden=target_hidden,
253
+ attention_mask=attention_mask,
254
+ position_ids=position_ids,
255
+ past_key_values=past_key_value,
256
+ output_attentions=output_attentions,
257
+ use_cache=use_cache,
258
+ cache_position=cache_position,
259
+ position_embeddings=position_embeddings,
260
+ **kwargs,
261
+ )[0]
262
+ hidden_states = residual + hidden_states
263
+ residual = hidden_states
264
+ hidden_states = self.post_attention_layernorm(hidden_states)
265
+ hidden_states = self.mlp(hidden_states)
266
+ hidden_states = residual + hidden_states
267
+ return hidden_states
268
+
269
+
270
+ def build_target_layer_ids(num_target_layers: int, num_draft_layers: int):
271
+ if num_draft_layers == 1:
272
+ return [(num_target_layers // 2)]
273
+ start = 1
274
+ end = num_target_layers - 3
275
+ span = end - start
276
+ target_layer_ids = [
277
+ int(round(start + (i * span) / (num_draft_layers - 1)))
278
+ for i in range(num_draft_layers)
279
+ ]
280
+ return target_layer_ids
281
+
282
+
283
+ def extract_context_feature(
284
+ hidden_states: list[torch.Tensor],
285
+ layer_ids: Optional[list[int]],
286
+ ) -> torch.Tensor:
287
+ offset = 1
288
+ selected_states = []
289
+ for layer_id in layer_ids:
290
+ selected_states.append(hidden_states[layer_id + offset])
291
+ target_hidden = torch.cat(selected_states, dim=-1)
292
+ return target_hidden
293
+
294
+
295
+ class DFlashDraftModel(Qwen3PreTrainedModel):
296
+ config_class = Qwen3Config
297
+ _no_split_modules = ["Qwen3DFlashDecoderLayer"]
298
+
299
+ def __init__(self, config) -> None:
300
+ super().__init__(config)
301
+ self.config = config
302
+ self.layers = nn.ModuleList(
303
+ [
304
+ Qwen3DFlashDecoderLayer(config, layer_idx)
305
+ for layer_idx in range(config.num_hidden_layers)
306
+ ]
307
+ )
308
+ dflash_config = getattr(config, "dflash_config", {}) or {}
309
+ self.target_layer_ids = dflash_config.get(
310
+ "target_layer_ids",
311
+ build_target_layer_ids(config.num_target_layers, config.num_hidden_layers),
312
+ )
313
+ self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
314
+ self.rotary_emb = Qwen3RotaryEmbedding(config)
315
+ self.fc = nn.Linear(
316
+ len(self.target_layer_ids) * config.hidden_size,
317
+ config.hidden_size,
318
+ bias=False,
319
+ )
320
+ self.hidden_norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
321
+ self.block_size = config.block_size
322
+ self.mask_token_id = dflash_config.get("mask_token_id", None)
323
+ self.projector_type = dflash_config.get("projector_type", None)
324
+ self.pure_draft_prefix_len = dflash_config.get("pure_draft_prefix_len", 0)
325
+ self.shift_label = dflash_config.get("shift_label", False)
326
+
327
+ if self.projector_type == "domino":
328
+ self.emb_dim = dflash_config["emb_dim"]
329
+ self.gru_hidden_dim = dflash_config["gru_hidden_dim"]
330
+ self.prefix_gru = nn.GRU(
331
+ input_size=config.hidden_size,
332
+ hidden_size=self.gru_hidden_dim,
333
+ num_layers=1,
334
+ batch_first=True,
335
+ bias=False,
336
+ )
337
+ in_dim = config.hidden_size + self.gru_hidden_dim
338
+ self.embed_proj = nn.Sequential(
339
+ nn.Linear(in_dim, self.emb_dim, bias=False),
340
+ nn.SiLU(),
341
+ nn.Linear(self.emb_dim, config.vocab_size, bias=False),
342
+ )
343
+ elif self.projector_type is not None:
344
+ raise ValueError(f"Unknown draft projector_type: {self.projector_type}")
345
+ self.post_init()
346
+
347
+ def forward(
348
+ self,
349
+ position_ids: torch.LongTensor,
350
+ attention_mask: Optional[torch.Tensor] = None,
351
+ noise_embedding: Optional[torch.Tensor] = None,
352
+ target_hidden: Optional[torch.Tensor] = None,
353
+ past_key_values: Optional[Cache] = None,
354
+ use_cache: bool = False,
355
+ **kwargs,
356
+ ) -> CausalLMOutputWithPast:
357
+ hidden_states = noise_embedding
358
+ target_hidden = self.hidden_norm(self.fc(target_hidden))
359
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
360
+ for layer in self.layers:
361
+ hidden_states = layer(
362
+ hidden_states=hidden_states,
363
+ target_hidden=target_hidden,
364
+ attention_mask=attention_mask,
365
+ position_ids=position_ids,
366
+ past_key_value=past_key_values,
367
+ use_cache=use_cache,
368
+ position_embeddings=position_embeddings,
369
+ **kwargs,
370
+ )
371
+ return self.norm(hidden_states)
372
+
373
+ @torch.inference_mode()
374
+ def spec_generate(
375
+ self,
376
+ target: nn.Module,
377
+ input_ids: torch.LongTensor,
378
+ max_new_tokens: int,
379
+ stop_token_ids: list[int],
380
+ temperature: float,
381
+ ):
382
+ self.eval()
383
+ num_input_tokens = input_ids.shape[1]
384
+ max_length = num_input_tokens + max_new_tokens
385
+
386
+ block_size = self.block_size
387
+ output_ids = torch.full(
388
+ (1, max_length + block_size),
389
+ self.mask_token_id,
390
+ dtype=torch.long,
391
+ device=target.device,
392
+ )
393
+ position_ids = torch.arange(
394
+ output_ids.shape[1], device=target.device
395
+ ).unsqueeze(0)
396
+
397
+ past_key_values_target = DynamicCache()
398
+ past_key_values_draft = DynamicCache()
399
+
400
+ # Prefill stage
401
+ output = target(
402
+ input_ids,
403
+ position_ids=position_ids[:, :num_input_tokens],
404
+ past_key_values=past_key_values_target,
405
+ use_cache=True,
406
+ logits_to_keep=1,
407
+ output_hidden_states=True,
408
+ )
409
+
410
+ output_ids[:, :num_input_tokens] = input_ids
411
+ output_ids[:, num_input_tokens : num_input_tokens + 1] = sample(
412
+ output.logits, temperature
413
+ )
414
+ target_hidden = extract_context_feature(
415
+ output.hidden_states, self.target_layer_ids
416
+ )
417
+
418
+ # Decode stage
419
+ acceptance_lengths = []
420
+ start = input_ids.shape[1]
421
+ while start < max_length:
422
+ block_output_ids = output_ids[:, start : start + block_size].clone()
423
+ block_position_ids = position_ids[:, start : start + block_size]
424
+ noise_embedding = target.model.embed_tokens(block_output_ids)
425
+ draft_logits = target.lm_head(
426
+ self(
427
+ target_hidden=target_hidden,
428
+ noise_embedding=noise_embedding,
429
+ position_ids=position_ids[
430
+ :, past_key_values_draft.get_seq_length() : start + block_size
431
+ ],
432
+ past_key_values=past_key_values_draft,
433
+ use_cache=True,
434
+ is_causal=False,
435
+ )[:, -block_size + 1 :, :]
436
+ )
437
+ past_key_values_draft.crop(start)
438
+ block_output_ids[:, 1:] = sample(draft_logits)
439
+
440
+ output = target(
441
+ block_output_ids,
442
+ position_ids=block_position_ids,
443
+ past_key_values=past_key_values_target,
444
+ use_cache=True,
445
+ output_hidden_states=True,
446
+ )
447
+
448
+ posterior = sample(output.logits, temperature)
449
+ acceptance_length = (
450
+ (block_output_ids[:, 1:] == posterior[:, :-1])
451
+ .cumprod(dim=1)
452
+ .sum(dim=1)[0]
453
+ .item()
454
+ )
455
+ output_ids[:, start : start + acceptance_length + 1] = block_output_ids[
456
+ :, : acceptance_length + 1
457
+ ]
458
+ output_ids[:, start + acceptance_length + 1] = posterior[
459
+ :, acceptance_length
460
+ ]
461
+ start += acceptance_length + 1
462
+ past_key_values_target.crop(start)
463
+ target_hidden = extract_context_feature(
464
+ output.hidden_states, self.target_layer_ids
465
+ )[:, : acceptance_length + 1, :]
466
+ acceptance_lengths.append(acceptance_length + 1)
467
+ if stop_token_ids is not None and any(
468
+ stop_token_id in output_ids[:, num_input_tokens:]
469
+ for stop_token_id in stop_token_ids
470
+ ):
471
+ break
472
+ output_ids = output_ids[:, :max_length]
473
+ output_ids = output_ids[:, output_ids[0] != self.mask_token_id]
474
+ if stop_token_ids is not None:
475
+ stop_token_ids = torch.tensor(stop_token_ids, device=output_ids.device)
476
+ stop_token_indices = torch.isin(
477
+ output_ids[0][num_input_tokens:], stop_token_ids
478
+ ).nonzero(as_tuple=True)[0]
479
+ if stop_token_indices.numel() > 0:
480
+ output_ids = output_ids[
481
+ :, : num_input_tokens + stop_token_indices[0] + 1
482
+ ]
483
+
484
+ return output_ids
dspark.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ """DSpark draft model: DFlash backbone + EAGLE-style Markov and confidence heads.
3
+
4
+ DSpark shares SpecForge's DFlash block-diffusion drafter (dual-source KV
5
+ injection via :class:`DFlashDraftModel`, anchor sampling, MASK-token noise
6
+ stream) and adds two heads on top:
7
+
8
+ - Markov head: a low-rank learned bigram bias added to the draft logits,
9
+ conditioned on the (teacher-forced) previous token. Improves the per-token
10
+ distribution without touching the backbone.
11
+ - Confidence head (AcceptRatePredictor): predicts a per-draft-position
12
+ acceptance probability, trained against the empirical draft-vs-target
13
+ accept rate (used at inference time for adaptive block length).
14
+
15
+ Ported from TorchSpec PR #129 (``torchspec/models/draft/dspark.py``). The Markov
16
+ / confidence modeling code is adapted from DeepSeek's DeepSpec
17
+ (``deepspec/modeling/dspark/{markov_head,common}.py``, MIT License).
18
+
19
+ SpecForge differences vs TorchSpec (load-bearing):
20
+ - There is no ``DFlashConfig``; SpecForge's :class:`DFlashDraftModel` uses a
21
+ plain ``Qwen3Config`` plus a ``config.dflash_config`` dict. So
22
+ :class:`DSparkConfig` subclasses ``Qwen3Config`` and declares the DSpark
23
+ fields as top-level attributes; DFlash-carried fields (``block_size``,
24
+ ``num_target_layers``, ``dflash_config``) stay as before.
25
+ - The draft model has no ``embed_tokens`` of its own (the embedding lives on
26
+ the target and is passed into the online wrapper), and the context
27
+ projection is ``self.fc`` (not ``context_proj``). The heads only depend on
28
+ ``config.hidden_size`` / ``config.vocab_size``, so this does not matter for
29
+ construction.
30
+ """
31
+
32
+ from typing import Optional
33
+
34
+ import torch
35
+ import torch.nn as nn
36
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3Config
37
+
38
+ from specforge.modeling.draft.dflash import DFlashDraftModel
39
+
40
+
41
+ class DSparkConfig(Qwen3Config):
42
+ """Configuration for the DSpark draft model.
43
+
44
+ Extends ``Qwen3Config`` (SpecForge's DFlash draft is config-light and reads a
45
+ plain ``Qwen3Config``). DSpark-specific fields are declared here; the
46
+ DFlash-carried fields (``block_size``, ``num_target_layers``, and the nested
47
+ ``dflash_config`` dict holding ``target_layer_ids`` / ``mask_token_id``) are
48
+ consumed by the :class:`DFlashDraftModel` base ``__init__`` and must be
49
+ present on the config object before constructing the model.
50
+ """
51
+
52
+ model_type = "dspark"
53
+
54
+ def __init__(
55
+ self,
56
+ markov_rank: int = 256,
57
+ markov_head_type: str = "vanilla",
58
+ enable_confidence_head: bool = True,
59
+ confidence_head_with_markov: bool = True,
60
+ **kwargs,
61
+ ):
62
+ super().__init__(**kwargs)
63
+ self.markov_rank = markov_rank
64
+ self.markov_head_type = markov_head_type
65
+ self.enable_confidence_head = enable_confidence_head
66
+ self.confidence_head_with_markov = confidence_head_with_markov
67
+
68
+
69
+ class VanillaMarkov(nn.Module):
70
+ """Low-rank learned bigram bias added to the draft logits.
71
+
72
+ Adapted from DeepSpec's ``deepspec/modeling/dspark/markov_head.py``.
73
+ """
74
+
75
+ def __init__(self, *, vocab_size: int, markov_rank: int):
76
+ super().__init__()
77
+ self.vocab_size = int(vocab_size)
78
+ self.markov_rank = int(markov_rank)
79
+ self.markov_head_type = "vanilla"
80
+ assert (
81
+ self.markov_rank > 0
82
+ ), f"VanillaMarkov requires markov_rank > 0, got {self.markov_rank}."
83
+ self.markov_w1 = nn.Embedding(self.vocab_size, self.markov_rank)
84
+ self.markov_w2 = nn.Linear(self.markov_rank, self.vocab_size, bias=False)
85
+
86
+ def get_prev_embeddings(self, token_ids: torch.Tensor) -> torch.Tensor:
87
+ return self.markov_w1(token_ids.long())
88
+
89
+ def project_bias(self, latent_states: torch.Tensor) -> torch.Tensor:
90
+ return self.markov_w2(latent_states)
91
+
92
+ def compute_step_bias(self, token_ids: torch.Tensor) -> torch.Tensor:
93
+ return self.project_bias(self.get_prev_embeddings(token_ids))
94
+
95
+ def apply_block_logits(
96
+ self,
97
+ base_logits: torch.Tensor,
98
+ *,
99
+ token_ids: torch.Tensor,
100
+ ) -> torch.Tensor:
101
+ if base_logits.size(2) == 0:
102
+ return base_logits
103
+ return base_logits + self.compute_step_bias(token_ids)
104
+
105
+
106
+ class AcceptRatePredictor(nn.Module):
107
+ """Per-position acceptance-probability predictor (a single linear head).
108
+
109
+ Adapted from DeepSpec's ``deepspec/modeling/dspark/common.py``.
110
+ """
111
+
112
+ def __init__(self, input_dim: int):
113
+ super().__init__()
114
+ self.proj = nn.Linear(int(input_dim), 1)
115
+
116
+ def forward(self, features: torch.Tensor) -> torch.Tensor:
117
+ return self.proj(features).squeeze(-1)
118
+
119
+
120
+ def build_markov_head(config) -> Optional[nn.Module]:
121
+ markov_rank = int(getattr(config, "markov_rank", 0))
122
+ assert markov_rank >= 0, f"markov_rank must be >= 0, got {markov_rank}"
123
+ if markov_rank == 0:
124
+ return None
125
+
126
+ markov_head_type = str(getattr(config, "markov_head_type", "vanilla")).lower()
127
+ if markov_head_type == "vanilla":
128
+ return VanillaMarkov(vocab_size=config.vocab_size, markov_rank=markov_rank)
129
+ raise NotImplementedError(
130
+ f"markov_head_type={markov_head_type!r} is not supported yet; only 'vanilla' "
131
+ "is implemented as it is recommended by the authors."
132
+ )
133
+
134
+
135
+ class DSparkDraftModel(DFlashDraftModel):
136
+ """DSpark draft network: DFlash backbone + Markov / confidence heads."""
137
+
138
+ config_class = DSparkConfig
139
+
140
+ def __init__(self, config) -> None:
141
+ super().__init__(config)
142
+
143
+ self.markov_rank = int(getattr(config, "markov_rank", 0))
144
+ self.confidence_head_with_markov = bool(
145
+ getattr(config, "confidence_head_with_markov", True)
146
+ )
147
+
148
+ self.markov_head = build_markov_head(config)
149
+
150
+ self.confidence_head: Optional[nn.Module] = None
151
+ if getattr(config, "enable_confidence_head", False):
152
+ conf_input_dim = config.hidden_size
153
+ if self.confidence_head_with_markov:
154
+ if self.markov_head is None:
155
+ raise ValueError(
156
+ "confidence_head_with_markov=True requires a Markov head "
157
+ "(markov_rank > 0)."
158
+ )
159
+ conf_input_dim += self.markov_rank
160
+ self.confidence_head = AcceptRatePredictor(conf_input_dim)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:82aef55e32f5b45d023b73e8c91fa2b34b687b47de9de1db441f4ff2f59f0713
3
+ size 3477573874