2525
2626_VALID_LOSS_TYPES = {
2727 "dflash" ,
28+ "vp_drafter" ,
2829 "dpace" ,
2930 "dpace-cumulative-confidence-only" ,
3031 "dpace-continuation-value-only" ,
3132}
32- _DPACE_LOSS_TYPES = _VALID_LOSS_TYPES - {"dflash" }
33+ _DPACE_LOSS_TYPES = _VALID_LOSS_TYPES - {"dflash" , "vp_drafter" }
3334
3435
3536def create_dflash_sdpa_mask (anchor_positions , block_keep_mask , S , block_size , device ):
@@ -121,6 +122,7 @@ def __init__(
121122 loss_decay_gamma : Optional [float ] = None ,
122123 loss_type : str = "dflash" ,
123124 dpace_alpha : float = 0.5 ,
125+ prefix_weight_base : float = 0.9 ,
124126 ):
125127 super ().__init__ ()
126128 if loss_type not in _VALID_LOSS_TYPES :
@@ -129,6 +131,12 @@ def __init__(
129131 )
130132 if not 0.0 <= dpace_alpha <= 1.0 :
131133 raise ValueError (f"dpace_alpha must be in [0, 1], got { dpace_alpha } " )
134+ if prefix_weight_base is None :
135+ prefix_weight_base = 0.9
136+ if prefix_weight_base <= 0.0 :
137+ raise ValueError (
138+ f"prefix_weight_base must be positive, got { prefix_weight_base } "
139+ )
132140
133141 self .draft_model = draft_model
134142 self .lm_head = target_lm_head
@@ -140,6 +148,7 @@ def __init__(
140148 self .loss_decay_gamma = loss_decay_gamma
141149 self .loss_type = loss_type
142150 self .dpace_alpha = dpace_alpha
151+ self .prefix_weight_base = prefix_weight_base
143152
144153 self ._cached_block_mask : Optional [BlockMask ] = None
145154 self ._cached_seq_len : Optional [int ] = None
@@ -183,6 +192,33 @@ def _sample_anchor_positions(
183192
184193 return anchors , keep_mask
185194
195+ def _sample_prefix_lengths (
196+ self , bsz : int , n_blocks : int , device : torch .device
197+ ) -> torch .Tensor :
198+ """Sample visible prefix lengths for VP-Drafter training.
199+
200+ A prefix length i means block positions [0, i) are visible real tokens and
201+ positions [i, block_size) are masked prediction targets. The sampled
202+ range follows D2SD's variable-prefix recipe while avoiding the degenerate
203+ fixed-anchor DFlash case.
204+ """
205+ min_prefix = min (2 , self .block_size - 1 )
206+ max_prefix = self .block_size - 1
207+ if max_prefix <= min_prefix :
208+ return torch .full (
209+ (bsz , n_blocks ), min_prefix , dtype = torch .long , device = device
210+ )
211+
212+ prefix_ids = torch .arange (min_prefix , max_prefix + 1 , device = device )
213+ weights = torch .pow (
214+ torch .full_like (prefix_ids , self .prefix_weight_base , dtype = torch .float32 ),
215+ prefix_ids .float (),
216+ )
217+ samples = torch .multinomial (
218+ weights , num_samples = bsz * n_blocks , replacement = True
219+ ).reshape (bsz , n_blocks )
220+ return samples + min_prefix
221+
186222 def prepare_noise_input (
187223 self , input_ids : torch .Tensor , block_ids : Optional [torch .Tensor ] = None
188224 ) -> torch .Tensor :
@@ -235,6 +271,36 @@ def _create_noise_embed(self, input_ids, anchor_positions, block_keep_mask):
235271
236272 return self .embed_tokens (noise_ids )
237273
274+ def _create_vp_noise_embed (
275+ self ,
276+ input_ids : torch .Tensor ,
277+ anchor_positions : torch .Tensor ,
278+ block_keep_mask : torch .Tensor ,
279+ prefix_lengths : torch .Tensor ,
280+ ) -> torch .Tensor :
281+ """Prepare VP-Drafter inputs with variable visible prefixes."""
282+ bsz , seq_len = input_ids .shape
283+ n = anchor_positions .shape [1 ]
284+ bs = self .block_size
285+ device = input_ids .device
286+
287+ offsets = torch .arange (bs , device = device ).view (1 , 1 , - 1 )
288+ token_positions = anchor_positions .unsqueeze (- 1 ) + offsets
289+ safe_positions = token_positions .clamp (0 , seq_len - 1 )
290+
291+ real_tokens = torch .gather (
292+ input_ids .unsqueeze (1 ).expand (- 1 , n , - 1 ),
293+ 2 ,
294+ safe_positions ,
295+ )
296+ visible_prefix = offsets < prefix_lengths .unsqueeze (- 1 )
297+ valid_positions = token_positions < seq_len
298+ fill_mask = visible_prefix & block_keep_mask .unsqueeze (- 1 ) & valid_positions
299+
300+ mask_tokens = torch .full_like (real_tokens , self .mask_token_id )
301+ noise_ids = torch .where (fill_mask , real_tokens , mask_tokens )
302+ return self .embed_tokens (noise_ids .reshape (bsz , n * bs ))
303+
238304 def _dpace_weight (
239305 self ,
240306 prob : torch .Tensor ,
@@ -285,9 +351,18 @@ def forward(
285351 seq_len , loss_mask , device
286352 )
287353
288- noise_embedding = self ._create_noise_embed (
289- input_ids , anchor_positions , block_keep_mask
290- )
354+ prefix_lengths = None
355+ if self .loss_type == "vp_drafter" :
356+ prefix_lengths = self ._sample_prefix_lengths (
357+ bsz , anchor_positions .shape [1 ], device
358+ )
359+ noise_embedding = self ._create_vp_noise_embed (
360+ input_ids , anchor_positions , block_keep_mask , prefix_lengths
361+ )
362+ else :
363+ noise_embedding = self ._create_noise_embed (
364+ input_ids , anchor_positions , block_keep_mask
365+ )
291366
292367 context_position_ids = (
293368 torch .arange (seq_len , device = device ).unsqueeze (0 ).expand (bsz , - 1 )
@@ -340,7 +415,12 @@ def forward(
340415 weight_mask = weight_mask * valid_label_mask .float ()
341416
342417 pos_in_block = torch .arange (self .block_size , device = device ).view (1 , 1 , - 1 )
343- weight_mask = weight_mask * (pos_in_block > 0 ).float ()
418+ if self .loss_type == "vp_drafter" :
419+ weight_mask = (
420+ weight_mask * (pos_in_block >= prefix_lengths .unsqueeze (- 1 )).float ()
421+ )
422+ else :
423+ weight_mask = weight_mask * (pos_in_block > 0 ).float ()
344424
345425 original_loss_mask_gathered = torch .gather (
346426 loss_mask .unsqueeze (1 ).expand (- 1 , anchor_positions .size (1 ), - 1 ),
@@ -367,6 +447,19 @@ def forward(
367447 )
368448 loss_weights = loss_weights * decay_weights
369449
450+ flat_weights = loss_weights .view (- 1 )
451+ valid_token_count = flat_weights .sum () + 1e-6
452+ loss = (loss_per_token * flat_weights ).sum () / valid_token_count
453+ elif self .loss_type == "vp_drafter" :
454+ loss_weights = weight_mask
455+ if self .loss_decay_gamma is not None and self .loss_decay_gamma > 0 :
456+ k = torch .arange (self .block_size , device = device ).view (1 , 1 , - 1 )
457+ effective_pos = (
458+ k .float () - prefix_lengths .unsqueeze (- 1 ).float ()
459+ ).clamp (min = 0 )
460+ decay_weights = torch .exp (- effective_pos / self .loss_decay_gamma )
461+ loss_weights = loss_weights * decay_weights
462+
370463 flat_weights = loss_weights .view (- 1 )
371464 valid_token_count = flat_weights .sum () + 1e-6
372465 loss = (loss_per_token * flat_weights ).sum () / valid_token_count
0 commit comments