Skip to content

Commit c0a39ee

Browse files
feat(training): support configurable loss fn via training args
Signed-off-by: Christina Xu <chrxu@redhat.com>
1 parent ed52632 commit c0a39ee

9 files changed

Lines changed: 143 additions & 18 deletions

File tree

scripts/train.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from speculators.model import SpeculatorModel
1919
from speculators.models.eagle3.data import shift_batch
20+
from speculators.models.metrics import resolve_loss_fn
2021
from speculators.train.data import (
2122
ArrowDataset,
2223
BaseDataset,
@@ -575,6 +576,17 @@ def parse_args():
575576
parser.add_argument("--mask-token-id", type=int, default=None)
576577
parser.add_argument("--ttt-steps", type=int, default=3)
577578
parser.add_argument("--ttt-step-loss-decay", type=float, default=1.0)
579+
parser.add_argument(
580+
"--loss-fn",
581+
type=str,
582+
default="kl_div",
583+
choices=["kl_div", "ce"],
584+
help=(
585+
"Loss function used during draft model training. "
586+
"'kl_div' = KL divergence (default). "
587+
"'ce' = cross-entropy."
588+
),
589+
)
578590
parser.add_argument(
579591
"--seed", type=int, default=42, help="Random seed for reproducibility"
580592
)
@@ -680,7 +692,10 @@ def parse_args():
680692
parser.add_argument("--scheduler-warmup-steps", type=int, default=None)
681693
parser.add_argument("--scheduler-total-steps", type=int, default=None)
682694
parser.add_argument("--scheduler-num-cosine-cycles", type=float, default=0.5)
683-
return parser.parse_args()
695+
696+
args = parser.parse_args()
697+
resolve_loss_fn(args.loss_fn)
698+
return args
684699

685700

686701
if __name__ == "__main__":

src/speculators/models/dflash/core.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, ClassVar
1+
from typing import ClassVar
22

33
import torch
44
from torch import nn
@@ -18,6 +18,7 @@
1818
get_base_indices_for_anchored_blocks,
1919
select_anchors,
2020
)
21+
from speculators.models.metrics import kl_div_loss, resolve_loss_fn
2122
from speculators.models.utils import resolve_target_layer_ids
2223

2324

@@ -162,7 +163,7 @@ def from_training_args(
162163
return model
163164

164165
@staticmethod
165-
def get_trainer_kwargs(**kwargs) -> tuple[dict, dict]: # noqa: ARG004
166+
def get_trainer_kwargs(**kwargs) -> tuple[dict, dict]:
166167
"""Get training and validation kwargs for DFlash.
167168
168169
Args:
@@ -171,9 +172,8 @@ def get_trainer_kwargs(**kwargs) -> tuple[dict, dict]: # noqa: ARG004
171172
Returns:
172173
Tuple of (train_call_kwargs, val_call_kwargs)
173174
"""
174-
train_kwargs: dict[str, Any] = {}
175-
val_kwargs: dict[str, Any] = {}
176-
return train_kwargs, val_kwargs
175+
loss_fn = resolve_loss_fn(kwargs["loss_fn"])
176+
return {"loss_fn": loss_fn}, {"loss_fn": loss_fn}
177177

178178
@property
179179
def mask_token_id(self) -> int:
@@ -194,6 +194,7 @@ def forward(
194194
verifier_last_hidden_states: torch.Tensor, # shape: [1, total_seq_len, hidden_size] # noqa: E501
195195
lengths: torch.Tensor | None = None, # shape: [batch_size]
196196
position_ids: torch.Tensor | None = None, # shape: [1, total_seq_len]
197+
loss_fn=kl_div_loss,
197198
**kwargs,
198199
):
199200
device = hidden_states.device
@@ -293,7 +294,7 @@ def forward(
293294

294295
aligned_loss_mask[:, :: self.block_size] = 0
295296
loss, metrics = compute_metrics(
296-
logits, targets, aligned_loss_mask, self.block_size
297+
logits, targets, aligned_loss_mask, self.block_size, loss_fn=loss_fn
297298
)
298299
draft_tokens = torch.argmax(logits, dim=-1)
299300

src/speculators/models/dflash/metrics.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""Metrics and loss functions for DFlash draft model."""
22

3+
from collections.abc import Callable
34
from functools import partial
45
from typing import Any
56

67
import torch
78

89
from speculators.models.metrics import (
9-
ce_loss,
1010
compute_accuracy_multi_step,
1111
dflash_loss_decay,
12+
kl_div_loss,
1213
loss_function,
1314
)
1415

@@ -19,6 +20,7 @@ def compute_metrics(
1920
loss_mask: torch.Tensor, # shape: [1, num_anchors*block_size]
2021
block_size: int = 1,
2122
gamma: float = 4.0,
23+
loss_fn: Callable[[torch.Tensor, torch.Tensor], torch.Tensor] | None = kl_div_loss,
2224
) -> tuple[torch.Tensor, dict]:
2325
"""Compute loss and accuracy metrics for draft model predictions.
2426
@@ -28,13 +30,16 @@ def compute_metrics(
2830
loss_mask: Binary mask [1, T]
2931
block_size: Block size for per-position metrics
3032
gamma: Temperature for exponential decay in loss weighting
33+
loss_fn: Loss function
3134
3235
Returns:
3336
Tuple of (loss, metrics_dict) where metrics_dict contains:
3437
- loss: Scalar loss value
3538
- full_acc: Overall accuracy
3639
- position {i} acc: Accuracy at position i within blocks
3740
"""
41+
if loss_fn is None:
42+
loss_fn = kl_div_loss
3843
seq_len = logits.shape[1]
3944
pos_idx = torch.arange(seq_len, device=logits.device) % block_size
4045
pos_idx = pos_idx.unsqueeze(0) # shape: [1, T]
@@ -44,7 +49,7 @@ def compute_metrics(
4449
targets,
4550
loss_mask,
4651
pos_idx,
47-
loss_fn=ce_loss,
52+
loss_fn=loss_fn,
4853
decay_fn=partial(dflash_loss_decay, gamma=gamma),
4954
)
5055

src/speculators/models/eagle3/core.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from speculators.models.eagle3.metrics import compute_metrics
1616
from speculators.models.eagle3.model_definitions import model_classes
17+
from speculators.models.metrics import kl_div_loss, resolve_loss_fn
1718
from speculators.models.utils import resolve_target_layer_ids
1819
from speculators.proposals.greedy import GreedyTokenProposalConfig
1920

@@ -130,8 +131,10 @@ def forward( # noqa: C901
130131
ttt_steps: int = 3,
131132
ttt_step_loss_decay: float = 1.0,
132133
use_off_policy_tokens: bool = False,
134+
loss_fn=kl_div_loss,
133135
**kwargs,
134136
):
137+
loss_fn = loss_fn or kl_div_loss
135138
device = hidden_states.device
136139
total_seq_len = hidden_states.shape[1]
137140

@@ -222,6 +225,7 @@ def forward( # noqa: C901
222225
prev_correct,
223226
ttt_step,
224227
ttt_step_loss_decay,
228+
loss_fn=loss_fn,
225229
)
226230
loss += s_loss
227231
metrics.update(s_metrics)
@@ -324,14 +328,17 @@ def get_trainer_kwargs(**kwargs) -> tuple[dict, dict]:
324328
Returns:
325329
Tuple of (train_call_kwargs, val_call_kwargs)
326330
"""
331+
loss_fn = resolve_loss_fn(kwargs["loss_fn"])
327332
train_kwargs = {
328333
"use_off_policy_tokens": kwargs["use_off_policy_tokens"],
329334
"ttt_steps": kwargs["ttt_steps"],
330335
"ttt_step_loss_decay": kwargs["ttt_step_loss_decay"],
336+
"loss_fn": loss_fn,
331337
}
332338
val_kwargs = {
333339
"use_off_policy_tokens": False,
334340
"ttt_steps": kwargs["ttt_steps"],
335341
"ttt_step_loss_decay": kwargs["ttt_step_loss_decay"],
342+
"loss_fn": loss_fn,
336343
}
337344
return train_kwargs, val_kwargs

src/speculators/models/eagle3/metrics.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Metrics and loss functions for Eagle3 draft model."""
22

3+
from collections.abc import Callable
34
from functools import partial
45

56
import torch
@@ -54,6 +55,7 @@ def compute_metrics(
5455
prev_correct: torch.Tensor | None,
5556
ttt_step: int,
5657
ttt_step_loss_decay: float,
58+
loss_fn: Callable[[torch.Tensor, torch.Tensor], torch.Tensor] | None = kl_div_loss,
5759
) -> tuple[torch.Tensor, dict]:
5860
"""Compute metrics for a given ttt_step.
5961
@@ -64,13 +66,16 @@ def compute_metrics(
6466
prev_correct: The previous correct predictions for the current ttt_step.
6567
ttt_step: The current ttt_step.
6668
ttt_step_loss_decay: The loss decay for the current ttt_step.
69+
loss_fn: Loss function.
6770
6871
Effects:
6972
Modifies prev_correct in place.
7073
7174
Returns:
7275
Loss value and metrics dictionary.
7376
"""
77+
if loss_fn is None:
78+
loss_fn = kl_div_loss
7479
s_logits, s_targets, s_loss_mask, s_prev_correct = align_for_step(
7580
logits, targets, loss_mask, prev_correct, ttt_step
7681
)
@@ -88,7 +93,7 @@ def compute_metrics(
8893
s_targets,
8994
s_loss_mask,
9095
pos_idx,
91-
loss_fn=kl_div_loss,
96+
loss_fn=loss_fn,
9297
decay_fn=partial(exp_loss_decay, gamma=ttt_step_loss_decay),
9398
)
9499

src/speculators/models/metrics.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,31 @@ def exp_loss_decay(pos_idx: torch.Tensor, gamma: float):
153153
return gamma**pos_idx
154154

155155

156+
def resolve_loss_fn(
157+
name: str,
158+
) -> "Callable[[torch.Tensor, torch.Tensor], torch.Tensor]":
159+
"""Resolves a loss function given its abbreviated name.
160+
161+
Args:
162+
name: ``"kl_div"`` for KL-divergence or ``"ce"`` for cross-entropy.
163+
164+
Returns:
165+
The corresponding loss function.
166+
167+
Raises:
168+
ValueError: If *name* is not a recognised loss function.
169+
"""
170+
loss_fn_map: dict[str, Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = {
171+
"kl_div": kl_div_loss,
172+
"ce": ce_loss,
173+
}
174+
if name not in loss_fn_map:
175+
raise ValueError(
176+
f"Unknown loss function '{name}'. Choose from: {sorted(loss_fn_map.keys())}"
177+
)
178+
return loss_fn_map[name]
179+
180+
156181
def loss_function(
157182
logits: torch.Tensor, # shape: [1, seq_len, draft_vocab_size]
158183
targets: torch.Tensor, # shape: [1, seq_len, draft_vocab_size]

src/speculators/models/peagle/core.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from speculators.config import SpeculatorsConfig, VerifierConfig
1010
from speculators.model import SpeculatorModel
1111
from speculators.models.eagle3.core import Eagle3DraftModel, conditional_torch_compile
12+
from speculators.models.metrics import kl_div_loss, resolve_loss_fn
1213
from speculators.models.peagle.attention import create_peagle_mask_mod
1314
from speculators.models.peagle.config import PEagleSpeculatorConfig
1415
from speculators.models.peagle.data import generate_cod_sample_indices
@@ -55,6 +56,7 @@ def forward(
5556
position_ids: torch.Tensor | None = None,
5657
loss_mask: torch.Tensor | None = None,
5758
verifier_last_hidden_states: torch.Tensor | None = None,
59+
loss_fn=kl_div_loss,
5860
**kwargs,
5961
):
6062
"""
@@ -172,6 +174,7 @@ def forward(
172174
anchor_pos=anchor_pos,
173175
depth=depth,
174176
num_depths=self.num_depths,
177+
loss_fn=loss_fn,
175178
)
176179

177180
return None, loss, metrics
@@ -237,17 +240,15 @@ def from_training_args(
237240
return model
238241

239242
@staticmethod
240-
def get_trainer_kwargs(**kwargs) -> tuple[dict, dict]: # noqa: ARG004
243+
def get_trainer_kwargs(**kwargs) -> tuple[dict, dict]:
241244
"""
242245
Get training and validation kwargs for P-EAGLE.
243246
244-
P-EAGLE doesn't need extra kwargs for forward() - all parameters
245-
are handled in the forward method (num_depths, down_sample_ratio, etc.)
246-
247247
Args:
248-
**kwargs: Training arguments (unused)
248+
**kwargs: Training arguments
249249
250250
Returns:
251-
Tuple of (train_call_kwargs, val_call_kwargs) - both empty for P-EAGLE
251+
Tuple of (train_call_kwargs, val_call_kwargs)
252252
"""
253-
return {}, {}
253+
loss_fn = resolve_loss_fn(kwargs["loss_fn"])
254+
return {"loss_fn": loss_fn}, {"loss_fn": loss_fn}

src/speculators/models/peagle/metrics.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Metrics and loss functions for P-EAGLE draft model."""
22

3+
from collections.abc import Callable
34
from typing import Any
45

56
import torch
@@ -18,6 +19,7 @@ def compute_metrics(
1819
anchor_pos: torch.Tensor,
1920
depth: torch.Tensor,
2021
num_depths: int,
22+
loss_fn: Callable[[torch.Tensor, torch.Tensor], torch.Tensor] | None = kl_div_loss,
2123
) -> tuple[torch.Tensor, dict[str, Any]]:
2224
"""Compute loss and accuracy metrics for P-EAGLE predictions.
2325
@@ -29,10 +31,13 @@ def compute_metrics(
2931
sampling chain started from [total_sampled]
3032
depth: Which COD sampling round each element belongs to [total_sampled]
3133
num_depths: Number of parallel depths
34+
loss_fn: Loss function.
3235
3336
Returns:
3437
Tuple of (loss, metrics_dict)
3538
"""
39+
if loss_fn is None:
40+
loss_fn = kl_div_loss
3641
device = logits.device
3742

3843
# TODO: batch size is always 1 for P-EAGLE; unsqueeze is only to match the
@@ -41,7 +46,7 @@ def compute_metrics(
4146
sampled_loss_mask = loss_mask[:, orig_positions] # [1, total_sampled]
4247

4348
loss = loss_function(
44-
logits, targets, sampled_loss_mask, depth.unsqueeze(0), loss_fn=kl_div_loss
49+
logits, targets, sampled_loss_mask, depth.unsqueeze(0), loss_fn=loss_fn
4550
)
4651

4752
with torch.no_grad():

0 commit comments

Comments
 (0)