Ruurd's picture
Deploy native LLaDA-Instruct with shared diffusion and PMI Viterbi controls
153c673 verified
Raw History Blame Contribute Delete
8.33 kB
"""Same-position denoising objectives. No autoregressive shift is used."""
from __future__ import annotations
import torch
import torch.nn.functional as F
def _separate_answer_padding_loss(
selected_ce, example_ids, counts, sampled_t, token_weights,
selected_answer_mask, selected_padding_mask, answer_lengths, padding_lengths,
answer_padding_weights, compute_unweighted_metric,
):
"""Normalize each region per example before applying fixed mixing weights.
Region lengths count all eligible positions, not just this draw's masks.
Empty regions/draws contribute zero; coefficients are never redistributed.
Include eligible examples with zero masks in the batch average so masking
fewer positions does not condition the objective on a nonempty draw.
"""
if sampled_t is None:
raise ValueError("Separate answer/padding loss requires sampled_t")
if any(value is None for value in (
selected_answer_mask, selected_padding_mask, answer_lengths, padding_lengths,
)):
raise ValueError("Separate answer/padding loss requires region masks and lengths")
weighted_ce = selected_ce if token_weights is None else selected_ce * token_weights
inverse_t_ce = weighted_ce / sampled_t[example_ids].to(weighted_ce.dtype).clamp_min(1e-8)
component_losses = []
valid = (answer_lengths + padding_lengths) > 0
valid_count = valid.sum()
for selected_mask, lengths in (
(selected_answer_mask, answer_lengths), (selected_padding_mask, padding_lengths),
):
sums = torch.zeros(counts.shape[0], device=weighted_ce.device, dtype=weighted_ce.dtype).scatter_add(
0, example_ids, inverse_t_ce * selected_mask,
)
per_example = sums / lengths.clamp_min(1)
component_losses.append((per_example * valid).sum() / valid_count.clamp_min(1))
answer_loss, padding_loss = component_losses
answer_weight, padding_weight = answer_padding_weights
loss = answer_weight * answer_loss + padding_weight * padding_loss
metrics = {
"weighted_loss": loss.detach(),
"answer_loss": answer_loss.detach(),
"padding_loss": padding_loss.detach(),
"valid_examples": valid_count.detach(),
"supervised_tokens": counts.sum().detach(),
}
if compute_unweighted_metric:
metrics["unweighted_masked_token_ce"] = (selected_ce.sum() / counts.sum().clamp_min(1)).detach()
return loss, metrics
def _finish_selected_loss(
selected_ce_sums,
selected_ce_total,
counts,
sampled_t,
normalization_mask,
compute_unweighted_metric,
):
"""Reduce already-selected token losses with the configured weighting."""
valid = counts > 0
per_example = selected_ce_sums / counts.clamp_min(1)
if sampled_t is None:
weighted = per_example
else:
if normalization_mask is None:
raise ValueError("normalization_mask is required when sampled_t is provided")
response_lengths = normalization_mask.sum(dim=1).clamp_min(1)
weighted = selected_ce_sums / sampled_t.to(selected_ce_sums.device).clamp_min(1e-8) / response_lengths
valid_count = valid.sum()
loss = (weighted * valid).sum() / valid_count.clamp_min(1)
metrics = {
"weighted_loss": loss.detach(),
"valid_examples": valid_count.detach(),
"supervised_tokens": counts.sum().detach(),
}
if compute_unweighted_metric:
metrics["unweighted_masked_token_ce"] = (
selected_ce_total / counts.sum().clamp_min(1)
).detach()
return loss, metrics
def selected_denoising_loss(
selected_logits,
selected_labels,
example_ids,
counts,
sampled_t=None,
normalization_mask=None,
*,
compute_unweighted_metric=True,
token_weights=None,
answer_padding_weights=None,
selected_answer_mask=None,
selected_padding_mask=None,
answer_lengths=None,
padding_lengths=None,
):
"""Compute the objective when the LM head emitted supervised positions only."""
selected_ce = F.cross_entropy(selected_logits, selected_labels, reduction="none")
if answer_padding_weights is not None:
return _separate_answer_padding_loss(
selected_ce, example_ids, counts, sampled_t, token_weights,
selected_answer_mask, selected_padding_mask, answer_lengths, padding_lengths,
answer_padding_weights, compute_unweighted_metric,
)
weighted_ce = selected_ce if token_weights is None else selected_ce * token_weights
selected_ce_sums = torch.zeros(
counts.shape[0], device=selected_logits.device, dtype=weighted_ce.dtype
).scatter_add(0, example_ids, weighted_ce)
return _finish_selected_loss(
selected_ce_sums,
selected_ce.sum(),
counts,
sampled_t,
normalization_mask,
compute_unweighted_metric,
)
def masked_denoising_loss(
logits,
labels,
loss_mask,
sampled_t=None,
normalization_mask=None,
*,
compute_unweighted_metric=True,
sparse_positions=True,
token_weights=None,
answer_padding_weights=None,
answer_mask=None,
padding_mask=None,
):
"""Compute masked CE with optional LLaDA-style inverse-t weighting.
Returns a differentiable scalar and aggregate metrics. Examples without selected
tokens are excluded rather than changing another example's denominator. When
sampled_t is provided, normalization_mask must represent the complete eligible
response, not only the positions selected for corruption. Optional token_weights
correct frontier positions by t / p(position) before inverse-t reduction;
unweighted_masked_token_ce always reports the original CE.
"""
counts = loss_mask.sum(dim=1)
if answer_padding_weights is not None:
if answer_mask is None or padding_mask is None:
raise ValueError("Separate answer/padding loss requires answer_mask and padding_mask")
answer_mask = answer_mask & ~padding_mask
example_ids, token_ids = loss_mask.nonzero(as_tuple=True)
return selected_denoising_loss(
logits[example_ids, token_ids], labels[example_ids, token_ids], example_ids,
counts, sampled_t, normalization_mask,
compute_unweighted_metric=compute_unweighted_metric,
token_weights=None if token_weights is None else token_weights[example_ids, token_ids],
answer_padding_weights=answer_padding_weights,
selected_answer_mask=answer_mask[example_ids, token_ids],
selected_padding_mask=padding_mask[example_ids, token_ids],
answer_lengths=answer_mask.sum(dim=1), padding_lengths=padding_mask.sum(dim=1),
)
if sparse_positions:
# Computing CE over [batch, sequence, vocabulary] wastes a large softmax
# on positions excluded from the objective. Reuse one set of selected
# indices for the logits, labels, and per-example reduction.
example_ids, token_ids = loss_mask.nonzero(as_tuple=True)
selected_ce = F.cross_entropy(
logits[example_ids, token_ids], labels[example_ids, token_ids], reduction="none"
)
weighted_ce = selected_ce if token_weights is None else selected_ce * token_weights[example_ids, token_ids]
return _finish_selected_loss(
torch.zeros(
logits.shape[0], device=logits.device, dtype=weighted_ce.dtype
).scatter_add(0, example_ids, weighted_ce),
selected_ce.sum(),
counts,
sampled_t,
normalization_mask,
compute_unweighted_metric,
)
else:
# Structured all-token training has nothing to compact; avoid copying
# the entire logits tensor through advanced indexing in that mode.
token_ce = F.cross_entropy(logits.transpose(1, 2), labels, reduction="none")
selected_ce_sums = (token_ce * loss_mask).sum(dim=1)
weighted_ce_sums = selected_ce_sums if token_weights is None else (token_ce * loss_mask * token_weights).sum(dim=1)
return _finish_selected_loss(
weighted_ce_sums,
selected_ce_sums.sum(),
counts,
sampled_t,
normalization_mask,
compute_unweighted_metric,
)