Metrics return sums (num_correct_tokens, entropy_sum) instead of means
Browse files- README.md +19 -7
- build/torch-cuda/fused_linear_ce.py +26 -17
README.md
CHANGED
|
@@ -38,23 +38,35 @@ fused_linear_cross_entropy(
|
|
| 38 |
softcap=None,
|
| 39 |
reduction="mean", # "mean" | "sum" | "none"
|
| 40 |
return_metrics=False,
|
| 41 |
-
) -> loss | (loss, {"
|
| 42 |
```
|
| 43 |
|
| 44 |
`hidden`, `weight` and `bias` all receive gradients.
|
| 45 |
|
| 46 |
### Metrics for free
|
| 47 |
|
| 48 |
-
Trainers that switch to a fused loss normally lose their
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
log-sum-exp:
|
| 53 |
|
| 54 |
```python
|
| 55 |
loss, metrics = fused_linear_cross_entropy(hidden, weight, targets, return_metrics=True)
|
| 56 |
-
metrics["
|
| 57 |
-
metrics["
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
```
|
| 59 |
|
| 60 |
Both are forward-only. `return_metrics=True` leaves the loss and the gradients **bit-identical**
|
|
|
|
| 38 |
softcap=None,
|
| 39 |
reduction="mean", # "mean" | "sum" | "none"
|
| 40 |
return_metrics=False,
|
| 41 |
+
) -> loss | (loss, {"num_correct_tokens": ..., "entropy_sum": ...})
|
| 42 |
```
|
| 43 |
|
| 44 |
`hidden`, `weight` and `bias` all receive gradients.
|
| 45 |
|
| 46 |
### Metrics for free
|
| 47 |
|
| 48 |
+
Trainers that switch to a fused loss normally lose their token-accuracy and entropy logging,
|
| 49 |
+
because there are no logits left to compute them from. This kernel carries both through the same
|
| 50 |
+
vocabulary sweep it already performs — the argmax rides along with the running row max, and the
|
| 51 |
+
entropy term `sum_j p_j * logit_j` is accumulated with the same online rescaling as the
|
| 52 |
log-sum-exp:
|
| 53 |
|
| 54 |
```python
|
| 55 |
loss, metrics = fused_linear_cross_entropy(hidden, weight, targets, return_metrics=True)
|
| 56 |
+
metrics["num_correct_tokens"] # int64 scalar, count of argmax == target
|
| 57 |
+
metrics["entropy_sum"] # float32 scalar, summed softmax entropy in nats
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
**Both are sums over non-ignored positions, not means.** Sums are the more general contract: a
|
| 61 |
+
per-rank mean cannot be recombined across distributed ranks holding different token counts, so a
|
| 62 |
+
distributed trainer must gather sums before dividing, and the accuracy count stays an exact
|
| 63 |
+
integer instead of a float that callers have to multiply back up. A single-process caller
|
| 64 |
+
recovers the means by dividing by a count it already has:
|
| 65 |
+
|
| 66 |
+
```python
|
| 67 |
+
n = (targets != -100).sum()
|
| 68 |
+
mean_token_accuracy = metrics["num_correct_tokens"] / n
|
| 69 |
+
mean_entropy = metrics["entropy_sum"] / n
|
| 70 |
```
|
| 71 |
|
| 72 |
Both are forward-only. `return_metrics=True` leaves the loss and the gradients **bit-identical**
|
build/torch-cuda/fused_linear_ce.py
CHANGED
|
@@ -369,24 +369,24 @@ class _FusedLinearCrossEntropy(torch.autograd.Function):
|
|
| 369 |
|
| 370 |
if return_metrics:
|
| 371 |
t = (t_p * alpha).sum(0)
|
| 372 |
-
|
| 373 |
-
|
| 374 |
argmax = a_p.gather(0, gsrc.unsqueeze(0)).squeeze(0)
|
| 375 |
-
|
| 376 |
else:
|
| 377 |
-
|
| 378 |
-
|
| 379 |
|
| 380 |
ctx.save_for_backward(hidden, weight, bias, targets, lse, valid, n_valid)
|
| 381 |
ctx.ignore_index = ignore_index
|
| 382 |
ctx.logit_scale = logit_scale
|
| 383 |
ctx.softcap = softcap
|
| 384 |
ctx.reduction = reduction
|
| 385 |
-
ctx.mark_non_differentiable(*[x for x in (
|
| 386 |
-
return loss,
|
| 387 |
|
| 388 |
@staticmethod
|
| 389 |
-
def backward(ctx, dloss,
|
| 390 |
hidden, weight, bias, targets, lse, valid, n_valid = ctx.saved_tensors
|
| 391 |
N, H = hidden.shape
|
| 392 |
V = weight.shape[0]
|
|
@@ -502,22 +502,31 @@ def fused_linear_cross_entropy(
|
|
| 502 |
One of `"mean"` (sum over non-ignored positions divided by their count), `"sum"` or
|
| 503 |
`"none"` (per-position losses, `0` at ignored positions).
|
| 504 |
return_metrics (`bool`, *optional*, defaults to `False`):
|
| 505 |
-
Whether to also return `
|
| 506 |
-
as the loss.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
|
| 508 |
Returns:
|
| 509 |
`torch.Tensor` when `return_metrics=False`, otherwise a `tuple` of the loss and a `dict`
|
| 510 |
-
|
| 511 |
-
- `
|
| 512 |
-
Scalar,
|
| 513 |
-
|
| 514 |
-
|
|
|
|
|
|
|
| 515 |
|
| 516 |
Examples:
|
| 517 |
|
| 518 |
```python
|
| 519 |
>>> loss = fused_linear_cross_entropy(hidden, lm_head.weight, labels)
|
| 520 |
>>> loss, metrics = fused_linear_cross_entropy(hidden, lm_head.weight, labels, return_metrics=True)
|
|
|
|
|
|
|
|
|
|
| 521 |
```
|
| 522 |
"""
|
| 523 |
if hidden.ndim != 2:
|
|
@@ -535,9 +544,9 @@ def fused_linear_cross_entropy(
|
|
| 535 |
if bias is not None:
|
| 536 |
bias = bias.contiguous()
|
| 537 |
|
| 538 |
-
loss,
|
| 539 |
hidden, weight, targets, bias, ignore_index, logit_scale, softcap, reduction, return_metrics
|
| 540 |
)
|
| 541 |
if return_metrics:
|
| 542 |
-
return loss, {"
|
| 543 |
return loss
|
|
|
|
| 369 |
|
| 370 |
if return_metrics:
|
| 371 |
t = (t_p * alpha).sum(0)
|
| 372 |
+
per_token_entropy = torch.where(valid, lse - t / s, torch.zeros((), dtype=torch.float32, device=device))
|
| 373 |
+
entropy_sum = per_token_entropy.sum()
|
| 374 |
argmax = a_p.gather(0, gsrc.unsqueeze(0)).squeeze(0)
|
| 375 |
+
num_correct_tokens = ((argmax == targets) & valid).sum()
|
| 376 |
else:
|
| 377 |
+
entropy_sum = None
|
| 378 |
+
num_correct_tokens = None
|
| 379 |
|
| 380 |
ctx.save_for_backward(hidden, weight, bias, targets, lse, valid, n_valid)
|
| 381 |
ctx.ignore_index = ignore_index
|
| 382 |
ctx.logit_scale = logit_scale
|
| 383 |
ctx.softcap = softcap
|
| 384 |
ctx.reduction = reduction
|
| 385 |
+
ctx.mark_non_differentiable(*[x for x in (entropy_sum, num_correct_tokens) if x is not None])
|
| 386 |
+
return loss, entropy_sum, num_correct_tokens
|
| 387 |
|
| 388 |
@staticmethod
|
| 389 |
+
def backward(ctx, dloss, dentropy_sum, dnum_correct_tokens):
|
| 390 |
hidden, weight, bias, targets, lse, valid, n_valid = ctx.saved_tensors
|
| 391 |
N, H = hidden.shape
|
| 392 |
V = weight.shape[0]
|
|
|
|
| 502 |
One of `"mean"` (sum over non-ignored positions divided by their count), `"sum"` or
|
| 503 |
`"none"` (per-position losses, `0` at ignored positions).
|
| 504 |
return_metrics (`bool`, *optional*, defaults to `False`):
|
| 505 |
+
Whether to also return `num_correct_tokens` and `entropy_sum`, computed in the same
|
| 506 |
+
pass as the loss. Both are **sums over non-ignored positions**, not means: a mean
|
| 507 |
+
cannot be recombined across distributed ranks holding different token counts, and the
|
| 508 |
+
accuracy count is exact as an integer. Divide by the number of non-ignored positions
|
| 509 |
+
to recover the per-token means. Enabling this does not change the loss or the
|
| 510 |
+
gradients.
|
| 511 |
|
| 512 |
Returns:
|
| 513 |
`torch.Tensor` when `return_metrics=False`, otherwise a `tuple` of the loss and a `dict`
|
| 514 |
+
whose values are both **sums over non-ignored positions**:
|
| 515 |
+
- `num_correct_tokens` (`torch.Tensor`):
|
| 516 |
+
Scalar `int64`, the number of non-ignored positions whose argmax logit is the
|
| 517 |
+
target.
|
| 518 |
+
- `entropy_sum` (`torch.Tensor`):
|
| 519 |
+
Scalar `float32`, the summed softmax entropy (in nats) over non-ignored
|
| 520 |
+
positions.
|
| 521 |
|
| 522 |
Examples:
|
| 523 |
|
| 524 |
```python
|
| 525 |
>>> loss = fused_linear_cross_entropy(hidden, lm_head.weight, labels)
|
| 526 |
>>> loss, metrics = fused_linear_cross_entropy(hidden, lm_head.weight, labels, return_metrics=True)
|
| 527 |
+
>>> n = (labels != -100).sum()
|
| 528 |
+
>>> mean_token_accuracy = metrics["num_correct_tokens"] / n
|
| 529 |
+
>>> mean_entropy = metrics["entropy_sum"] / n
|
| 530 |
```
|
| 531 |
"""
|
| 532 |
if hidden.ndim != 2:
|
|
|
|
| 544 |
if bias is not None:
|
| 545 |
bias = bias.contiguous()
|
| 546 |
|
| 547 |
+
loss, entropy_sum, num_correct_tokens = _FusedLinearCrossEntropy.apply(
|
| 548 |
hidden, weight, targets, bias, ignore_index, logit_scale, softcap, reduction, return_metrics
|
| 549 |
)
|
| 550 |
if return_metrics:
|
| 551 |
+
return loss, {"num_correct_tokens": num_correct_tokens, "entropy_sum": entropy_sum}
|
| 552 |
return loss
|