Skip to content

Commit e384825

Browse files
orestis-zclaude
andcommitted
fix(domino): address PR #773 review — fix stale test params and lint
- tests/e2e: --domino-lambda-decay-steps → --domino-lambda-decay-ratio - tests/integration: lambda_base_decay_steps → lambda_base_decay_ratio - tests/unit: remove phantom shift_targets/shift_label/max_anchors params, restructure tests to verify domino vs dflash behavior directly, add loss_config to lambda decay test forward calls - core.py: accept LossConfig | None in _compute_domino_metrics, add j_star/lambda_base observability metrics, ruff format fixes - domino.py: fix E501 docstring line lengths - dspark/metrics.py: ruff format fix Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
1 parent 5f1069e commit e384825

6 files changed

Lines changed: 132 additions & 118 deletions

File tree

src/speculators/models/dflash/core.py

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,8 @@ def _auf_mask(
251251
base_mask: torch.Tensor,
252252
num_anchors: int,
253253
block_size: int,
254-
) -> torch.Tensor:
254+
return_j_star: bool = False,
255+
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
255256
"""Compute the Accept-Until-Fail (AUF) loss mask for the base branch.
256257
257258
Truncates the cross-entropy support at the first greedy prediction error
@@ -272,21 +273,43 @@ def _auf_mask(
272273
base_mask: Boolean validity mask [1, T] (e.g. domino_loss_mask).
273274
num_anchors: Number of anchor blocks.
274275
block_size: Tokens per block.
276+
return_j_star: If True, also return j* (first error position) per block.
275277
276278
Returns:
277-
Boolean mask [1, T] with the same shape as base_mask, zeroed out
278-
for all positions strictly after the first error in each block.
279+
If return_j_star is False:
280+
Boolean mask [1, T] with the same shape as base_mask, zeroed out
281+
for all positions strictly after the first error in each block.
282+
If return_j_star is True:
283+
Tuple of (mask, j_star) where j_star is [num_anchors] tensor
284+
containing the first error position (0-indexed) per block.
285+
j* = block_size means no error (all accepted).
279286
"""
280-
base_preds_4d = logits.detach().argmax(dim=-1).reshape(1, num_anchors, block_size)
281-
target_ids_4d = targets.detach().argmax(dim=-1).reshape(1, num_anchors, block_size)
287+
base_preds_4d = (
288+
logits.detach().argmax(dim=-1).reshape(1, num_anchors, block_size)
289+
)
290+
target_ids_4d = (
291+
targets.detach().argmax(dim=-1).reshape(1, num_anchors, block_size)
292+
)
282293
mask_4d = base_mask.reshape(1, num_anchors, block_size)
283294
errors = (base_preds_4d != target_ids_4d) & mask_4d
284295
error_floats = errors.float()
285296
running_errors = error_floats.cumsum(dim=-1)
286297
# errors_before == 0: keep accepted prefix + breaker token j*
287298
# errors_before >= 1: zero the unreachable suffix
288299
errors_before = running_errors - error_floats
289-
return (mask_4d & (errors_before == 0)).reshape_as(base_mask)
300+
auf_mask = (mask_4d & (errors_before == 0)).reshape_as(base_mask)
301+
302+
if not return_j_star:
303+
return auf_mask
304+
305+
# j* = first error position per block (0-indexed within block)
306+
# If no error, j* = block_size (all positions accepted)
307+
first_error = errors.float().argmax(dim=-1) # [1, num_anchors]
308+
has_error = errors.any(dim=-1) # [1, num_anchors]
309+
j_star = torch.where(
310+
has_error, first_error, torch.full_like(first_error, block_size)
311+
)
312+
return auf_mask, j_star.squeeze(0) # [num_anchors]
290313

291314
@property
292315
def mask_token_id(self) -> int:
@@ -421,7 +444,9 @@ def _backbone_forward(
421444
)
422445
# Shift right by 1 so verifier_logits[i] predicts token at position i
423446
verifier_logits = torch.roll(verifier_logits, 1, dims=1)
424-
target_indices = anchored_block_indices + (1 if self.config.projector_type == "domino" else 0)
447+
target_indices = anchored_block_indices + (
448+
1 if self.config.projector_type == "domino" else 0
449+
)
425450
target_indices = target_indices.clamp(max=verifier_logits.shape[1] - 1)
426451
targets = verifier_logits[:, target_indices]
427452
# shape: [1, num_anchors*block_size, draft_vocab_size]
@@ -471,7 +496,7 @@ def _compute_domino_metrics(
471496
loss_mask: torch.Tensor,
472497
aligned_loss_mask: torch.Tensor,
473498
anchored_block_indices: torch.Tensor,
474-
loss_config: "LossConfig",
499+
loss_config: "LossConfig | None",
475500
gamma: float,
476501
normalize_by_decay: bool,
477502
global_step: int,
@@ -506,38 +531,67 @@ def _compute_domino_metrics(
506531
anchor_pos_in_mask = anchored_block_indices[:: self.block_size]
507532
domino_loss_mask[:, :: self.block_size] = loss_mask[:, anchor_pos_in_mask]
508533

509-
# B-AUF+D (arXiv 2607.01893): L_final keeps full mask; L_base is optionally truncated.
534+
# B-AUF+D (arXiv 2607.01893): L_final keeps full mask;
535+
# L_base is optionally truncated.
536+
j_star = None
510537
if self.config.domino_auf:
511-
base_mask = self._auf_mask(
512-
logits, targets, domino_loss_mask, num_anchors, self.block_size
538+
base_mask, j_star = self._auf_mask(
539+
logits,
540+
targets,
541+
domino_loss_mask,
542+
num_anchors,
543+
self.block_size,
544+
return_j_star=True,
513545
)
514546
else:
515547
base_mask = domino_loss_mask
516548

517549
base_loss, base_metrics = compute_metrics(
518-
logits, targets, base_mask, self.block_size,
519-
gamma=gamma, loss_config=loss_config,
520-
normalize_by_decay=normalize_by_decay, decay_mode="domino",
550+
logits,
551+
targets,
552+
base_mask,
553+
self.block_size,
554+
gamma=gamma,
555+
loss_config=loss_config,
556+
normalize_by_decay=normalize_by_decay,
557+
decay_mode="domino",
521558
)
522559
final_loss, final_metrics = compute_metrics(
523-
refined_logits, targets, domino_loss_mask, self.block_size,
524-
gamma=gamma, loss_config=loss_config,
525-
normalize_by_decay=normalize_by_decay, decay_mode="domino",
560+
refined_logits,
561+
targets,
562+
domino_loss_mask,
563+
self.block_size,
564+
gamma=gamma,
565+
loss_config=loss_config,
566+
normalize_by_decay=normalize_by_decay,
567+
decay_mode="domino",
526568
)
527569
loss = (1.0 - lambda_base) * final_loss + lambda_base * base_loss
570+
571+
ones = torch.tensor(1.0, device=logits.device)
528572
metrics = {
529573
"loss_sum": loss.detach().clone(),
530-
"loss_total": torch.tensor(1.0, device=logits.device),
574+
"loss_total": ones,
531575
"base_loss_sum": base_loss.detach().clone(),
532-
"base_loss_total": torch.tensor(1.0, device=logits.device),
576+
"base_loss_total": ones,
533577
"full_acc_sum": base_metrics["full_acc_sum"],
534578
"full_acc_total": base_metrics["full_acc_total"],
535579
"final_loss_sum": final_loss.detach().clone(),
536-
"final_loss_total": torch.tensor(1.0, device=logits.device),
580+
"final_loss_total": ones,
537581
"final_full_acc_sum": final_metrics["full_acc_sum"],
538582
"final_full_acc_total": final_metrics["full_acc_total"],
583+
"lambda_base_sum": torch.tensor(lambda_base, device=logits.device),
584+
"lambda_base_total": ones,
539585
**{k: v for k, v in final_metrics.items() if k.startswith("position_")},
540586
}
587+
588+
# AUF observability: mean j* (first error position) - proxy for acceptance
589+
if j_star is not None:
590+
metrics["j_star_sum"] = j_star.float().sum()
591+
metrics["j_star_total"] = torch.tensor(
592+
float(j_star.numel()), device=logits.device
593+
)
594+
541595
return loss, metrics
542596

543597
@conditional_torch_compile

src/speculators/models/dflash/domino.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ def forward(
4545
"""Apply GRU-based prefix refinement to logits.
4646
4747
Parameters:
48-
hidden_states_4d: Tensor of shape ``(batch, num_anchors, block_size, hidden_size)``.
49-
base_logits_4d: Tensor of shape ``(batch, num_anchors, block_size, vocab_size)``.
50-
prev_token_ids: Tensor of shape ``(batch, num_anchors * block_size)`` with previous token IDs.
48+
hidden_states_4d: ``(batch, num_anchors, block_size, hidden_size)``.
49+
base_logits_4d: ``(batch, num_anchors, block_size, vocab_size)``.
50+
prev_token_ids: ``(batch, num_anchors * block_size)`` previous token IDs.
5151
suffix_start: Position index within a block where the suffix begins.
5252
embed_tokens: Embedding layer for token lookup.
5353

src/speculators/models/dspark/metrics.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ def _masked_decayed_mean(
4040
weighted = elementwise * loss_mask
4141
denominator = loss_mask.sum(dim=1) + _EPS
4242
if decay_fn is not None:
43-
decay_mult = decay_fn(
44-
pos_idx.to(weighted.dtype), elementwise_loss=elementwise
45-
)
43+
decay_mult = decay_fn(pos_idx.to(weighted.dtype), elementwise_loss=elementwise)
4644
weighted = weighted * decay_mult
4745
if normalize_by_decay:
4846
denominator = (loss_mask * decay_mult).sum(dim=1) + _EPS

tests/e2e/smoke/test_offline_training.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
"3",
5555
"--projector-type",
5656
"domino",
57-
"--domino-lambda-decay-steps",
58-
"100",
57+
"--domino-lambda-decay-ratio",
58+
"1.0",
5959
],
6060
[1, 13, 25],
6161
), # Domino (DFlash + Domino correction head)

tests/integration/models/test_model_forward.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,18 @@ def test_domino_head_receives_gradient(self):
328328
assert grad.abs().sum().item() > 0
329329

330330
@pytest.mark.parametrize(
331-
("lambda_base_start", "lambda_base_decay_steps"),
331+
("lambda_base_start", "lambda_base_decay_ratio"),
332332
[
333-
(1.0, 100),
334-
(0.5, 0),
335-
(0.0, 0),
333+
(1.0, 1.0),
334+
(0.5, 0.0),
335+
(0.0, 0.0),
336336
],
337337
)
338-
def test_lambda_schedule_variants(self, lambda_base_start, lambda_base_decay_steps):
338+
def test_lambda_schedule_variants(self, lambda_base_start, lambda_base_decay_ratio):
339339
model = make_dflash_model(
340340
projector_type="domino",
341341
lambda_base_start=lambda_base_start,
342-
lambda_base_decay_steps=lambda_base_decay_steps,
342+
lambda_base_decay_ratio=lambda_base_decay_ratio,
343343
)
344344
samples = _make_samples([128])
345345
batch = make_batch(max_len=MAX_LEN, samples=samples, hidden_size=HIDDEN_SIZE)

0 commit comments

Comments
 (0)