cogforge-engine 2.1.2__tar.gz → 2.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/.gitignore +5 -1
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/PKG-INFO +68 -22
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/README.md +67 -21
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/TODO +3 -1
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/cogforge/app.py +96 -162
- cogforge_engine-2.1.3/cogforge/legacy.py +179 -0
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/pyproject.toml +1 -1
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/.github/workflows/release.yml +0 -0
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/LICENSE +0 -0
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/cogforge/__init__.py +0 -0
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/cogforge/backend.py +0 -0
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/cogforge/models.py +0 -0
- {cogforge_engine-2.1.2 → cogforge_engine-2.1.3}/cogforge/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cogforge-engine
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: A custom autograd engine and Transformer block built from scratch.
|
|
5
5
|
Project-URL: Homepage, https://github.com/avikmjd2/cogforge
|
|
6
6
|
Author-email: Avik Majumder <avikmjd2@gmail.com>
|
|
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
|
|
22
22
|
There is no C++, no PyTorch — just NumPy and explicit, hand-derived gradients. Optionally, the entire backend can be swapped to **CuPy** for GPU execution, or accelerated with **numexpr** on CPU, without changing any model code. Every tensor also records the operation that produced it, and the built-in **Graphviz visualizer** can render the full computation graph — data, gradients, and ops — so you can literally *see* backpropagation. The goal is still to *understand* every gradient that flows — speed is a bonus, not the point.
|
|
23
23
|
|
|
24
|
-
> **This release
|
|
24
|
+
> **This release overhauls the loss API.** Seven overlapping cross-entropy variants have been unified into three functions covering every use case — with built-in **label smoothing**, optional padding masks everywhere, and one consistent shape/normalization convention. The superseded losses live on in `cogforge.legacy`. See [What's new](#whats-new).
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -54,7 +54,41 @@ There is no C++, no PyTorch — just NumPy and explicit, hand-derived gradients.
|
|
|
54
54
|
|
|
55
55
|
## What's new
|
|
56
56
|
|
|
57
|
-
This version
|
|
57
|
+
This version consolidates the **loss API** into three functions with a single, consistent convention — and adds **label smoothing** on the road to a full translator.
|
|
58
|
+
|
|
59
|
+
**Unified cross-entropy API (3 losses, one convention)**
|
|
60
|
+
|
|
61
|
+
Seven overlapping cross-entropy variants (`softmax_cross_entropy_old`, `softmax_cross_entropy_masked`, `sparse_softmax_cross_entropy`, `sparse_softmax_cross_entropy_legacy`, `sparse_softmax_cross_entropy_index_legacy`, `cross_entropy_loss`, `cross_entropy_loss_masked`) are replaced by three that tile the whole space with no overlap — see [Losses](#losses):
|
|
62
|
+
|
|
63
|
+
- **`Tensor.sparse_softmax_cross_entropy_index(scores, labels, mask=None, eps=0.0)`** — the workhorse: logits in, integer labels, optional padding mask, optional **label smoothing** via `eps`. No one-hot matrix is ever built.
|
|
64
|
+
- **`Tensor.softmax_cross_entropy(scores, targets, mask=None)`** — logits in, arbitrary target *distributions* (knowledge distillation, mixup, soft labels), optional mask.
|
|
65
|
+
- **`Tensor.cross_entropy_from_probs(predictions, targets, mask=None)`** — for graphs that already end in `.softmax()`; prefer the logits-based losses when you have logits.
|
|
66
|
+
|
|
67
|
+
All three now share the same rules, closing several long-standing traps:
|
|
68
|
+
|
|
69
|
+
- **One shape convention.** Every loss accepts `(N, V)` *and* `(B, T, V)` logits (auto-flattened internally); masks may be `(N,)` or `(B, T)`.
|
|
70
|
+
- **One normalization convention.** Every loss divides by the number of *real* (unmasked) tokens — no more `/B` vs `/B·T` vs `/n_real` drift between variants, which silently changed effective learning rates when switching losses.
|
|
71
|
+
- **Masks are optional everywhere**, defaulting to "all positions are real".
|
|
72
|
+
- **Exact log-probabilities.** Loss values are computed from `log-softmax` directly instead of `log(clip(p))`, so near-zero probabilities are no longer floored at `log(1e-15)`.
|
|
73
|
+
|
|
74
|
+
**Label smoothing**
|
|
75
|
+
|
|
76
|
+
`sparse_softmax_cross_entropy_index(..., eps=0.1)` trains against `(1−eps)` on the true class and `eps/(V−1)` spread uniformly over the rest — the standard regularizer for translation models — at zero extra memory: both loss and gradient are computed straight from the label indices. `eps=0.0` (default) is exact plain cross-entropy.
|
|
77
|
+
|
|
78
|
+
**Performance**
|
|
79
|
+
|
|
80
|
+
- The hot path (`eps=0`, no mask — e.g. char-level GPT training) keeps the fused, **numexpr-accelerated** in-place backward from the previous `sparse_softmax_cross_entropy`.
|
|
81
|
+
- Masked/smoothed paths fold the mask, upstream gradient, and normalization into a single per-row coefficient — one fewer `(N, V)` temporary in every backward.
|
|
82
|
+
|
|
83
|
+
**Legacy module**
|
|
84
|
+
|
|
85
|
+
The superseded losses were **moved, not deleted**: they live in `cogforge.legacy` under `TensorLegacy`, unchanged, for reference and for reproducing old experiments. New code should not import them — note that they retain their old (inconsistent) normalization conventions.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Previous release — autograd correctness pass
|
|
90
|
+
|
|
91
|
+
This version focused on **autograd correctness and debuggability**. All fixes below were validated with finite-difference gradient checks and end-to-end training tests.
|
|
58
92
|
|
|
59
93
|
**Gradient correctness fixes**
|
|
60
94
|
|
|
@@ -97,6 +131,7 @@ The package is organized into four modules:
|
|
|
97
131
|
| `cogforge.app` | The autograd engine (`Tensor`) and every building block — layers, optimizers, losses, normalization, attention, positional encodings. |
|
|
98
132
|
| `cogforge.models` | Ready-to-use models: `GPTV1`, `GPT2`, `Seq2Seq`. |
|
|
99
133
|
| `cogforge.utils` | Computation-graph tracing and Graphviz rendering (`trace_graph`, `draw_graph`). |
|
|
134
|
+
| `cogforge.legacy` | `TensorLegacy` — the superseded cross-entropy variants, kept unchanged for reference and reproducing old experiments. Don't use in new code. |
|
|
100
135
|
|
|
101
136
|
```python
|
|
102
137
|
from cogforge.app import Tensor, Linear, Adam, MultiHeadAttention # building blocks
|
|
@@ -234,20 +269,23 @@ Gradients **accumulate** into `.grad`. Always zero them between optimization ste
|
|
|
234
269
|
|
|
235
270
|
### Losses
|
|
236
271
|
|
|
237
|
-
All losses are **classmethods** on `Tensor` and return a scalar loss tensor you call `.backwards()` on.
|
|
272
|
+
All losses are **classmethods** on `Tensor` and return a scalar loss tensor you call `.backwards()` on. There are exactly **three**, and they share one convention:
|
|
238
273
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
| `Tensor.sparse_softmax_cross_entropy(scores, target_ids)` | `scores` raw logits `(B,T,V)`, `target_ids` integers `(B,T)`. | Language modeling — skips building one-hot targets. |
|
|
243
|
-
| `Tensor.sparse_softmax_cross_entropy_index(scores, labels, mask)` | Raw logits + integer labels + per-token mask (1 = real, 0 = pad). | Padded LM / seq2seq batches. **Recommended for Seq2Seq training.** |
|
|
244
|
-
| `Tensor.softmax_cross_entropy_masked(scores, targets, mask)` | Logits + one-hot targets + per-row mask. | Padded batches, fused softmax. |
|
|
245
|
-
| `Tensor.cross_entropy_loss(predictions, targets)` | `predictions` are **probabilities** (call `.softmax()` first), `targets` one-hot. | You already have a softmax in your graph. |
|
|
246
|
-
| `Tensor.cross_entropy_loss_masked(predictions, targets, mask)` | Probabilities + per-row mask. | Padded batches without fused softmax. |
|
|
274
|
+
- **Shapes:** logits/probs may be `(N, V)` or `(B, T, V)` — 3-D input is auto-flattened. Masks may be `(N,)` or `(B, T)`.
|
|
275
|
+
- **Masks:** optional everywhere (`mask=None` treats all positions as real); `1` = real token, `0` = padding. Padded positions contribute zero loss *and* exactly zero gradient.
|
|
276
|
+
- **Normalization:** always by the number of real tokens (all positions when unmasked).
|
|
247
277
|
|
|
248
|
-
|
|
278
|
+
Pick by what your **target** looks like and what your **input** is:
|
|
279
|
+
|
|
280
|
+
| Loss | Input | Target | Use when |
|
|
281
|
+
| --- | --- | --- | --- |
|
|
282
|
+
| `Tensor.sparse_softmax_cross_entropy_index(scores, labels, mask=None, eps=0.0)` | **raw logits** | integer class ids | **The workhorse.** Classification, LM, seq2seq. `eps>0` enables label smoothing: `1−eps` on the true class, `eps/(V−1)` on the rest — computed straight from indices, no one-hot built. `eps=0.1` is the standard for translation. |
|
|
283
|
+
| `Tensor.softmax_cross_entropy(scores, targets, mask=None)` | **raw logits** | full distribution `(…, V)`, rows sum to 1 | Targets that aren't expressible as an index: knowledge distillation against a teacher's output, mixup, annotator vote distributions. |
|
|
284
|
+
| `Tensor.cross_entropy_from_probs(predictions, targets, mask=None)` | **probabilities** (e.g. output of `.softmax()`) | one-hot or distribution | Your graph already ends in a softmax. Prefer the logits-based losses when you have logits — their gradients are exact and bounded, this one saturates via clipping as probs → 0. |
|
|
285
|
+
|
|
286
|
+
> ℹ️ The first two apply softmax internally — feed them **raw logits**. `cross_entropy_from_probs` is the opposite — it expects probabilities. Mixing these up silently trains the wrong thing.
|
|
249
287
|
>
|
|
250
|
-
> `
|
|
288
|
+
> The previous generation of losses (`softmax_cross_entropy_masked`, `sparse_softmax_cross_entropy`, `cross_entropy_loss`, `cross_entropy_loss_masked`, and the `_old`/`_legacy` variants) now lives in `cogforge.legacy` under `TensorLegacy`, unchanged. They keep their old, mutually inconsistent normalizations (`/B` vs `/B·T` vs `/n_real`) — use them only to reproduce old experiments, and don't mix them with the current losses in one training run.
|
|
251
289
|
|
|
252
290
|
---
|
|
253
291
|
|
|
@@ -451,7 +489,7 @@ def get_batch(bs=32):
|
|
|
451
489
|
for step in range(2000):
|
|
452
490
|
x, y = get_batch()
|
|
453
491
|
logits = model(x) # (B, T, vocab)
|
|
454
|
-
loss = Tensor.
|
|
492
|
+
loss = Tensor.sparse_softmax_cross_entropy_index(logits, y)
|
|
455
493
|
|
|
456
494
|
opt.zero_grad()
|
|
457
495
|
loss.backwards()
|
|
@@ -504,8 +542,9 @@ for step in range(num_steps):
|
|
|
504
542
|
dec_in, labels = tgt[:, :-1], tgt[:, 1:]
|
|
505
543
|
logits = model(src, dec_in) # (B, T_dec, vocab)
|
|
506
544
|
|
|
507
|
-
mask = (labels != PAD).astype(np.float32)
|
|
508
|
-
loss = Tensor.sparse_softmax_cross_entropy_index(logits, labels,
|
|
545
|
+
mask = (labels != PAD).astype(np.float32) # (B, T_dec): 1 = real, 0 = pad
|
|
546
|
+
loss = Tensor.sparse_softmax_cross_entropy_index(logits, labels,
|
|
547
|
+
mask=mask, eps=0.1) # label smoothing
|
|
509
548
|
|
|
510
549
|
opt.zero_grad()
|
|
511
550
|
loss.backwards()
|
|
@@ -550,7 +589,9 @@ Two practical notes: call `draw_graph` **before** `backwards()`, since the backw
|
|
|
550
589
|
## Gotchas
|
|
551
590
|
|
|
552
591
|
- **It's `backwards()`, not `backward()`.** The backward pass method has a trailing `s`.
|
|
553
|
-
- **Logits vs. probabilities.** `softmax_cross_entropy`
|
|
592
|
+
- **Logits vs. probabilities.** `softmax_cross_entropy` and `sparse_softmax_cross_entropy_index` fuse the softmax internally — feed them **raw logits**. `cross_entropy_from_probs` expects **probabilities**. Mixing these up silently trains the wrong thing.
|
|
593
|
+
- **Label smoothing raises the loss floor.** With `eps > 0` the minimum achievable loss is no longer 0 (a perfect model still pays the smoothing term), so judge training by token accuracy or validation metrics, not by how close the raw loss gets to zero — and don't compare loss values across different `eps` settings.
|
|
594
|
+
- **Legacy losses normalize differently.** `cogforge.legacy.TensorLegacy` keeps the old `/B` / `/B·T` conventions; the current losses always divide by real-token count. Swapping one for the other rescales gradients — retune the learning rate if you migrate an old script.
|
|
554
595
|
- **Gradients accumulate.** Call `optimizer.zero_grad()` every step (or `p.grad[...] = 0`), or gradients pile up across iterations.
|
|
555
596
|
- **Switch the backend before building the model.** `use_gpu(True)` after construction leaves your parameters stranded on the CPU while new activations land on the GPU.
|
|
556
597
|
- **RoPE dim is per-head.** `RotatoryPositionalEncoding` takes `d_model // n_heads` (which must be even), not `d_model`. The models handle this internally — it only matters if you wire blocks up yourself.
|
|
@@ -567,13 +608,17 @@ Two practical notes: call `draw_graph` **before** `backwards()`, since the backw
|
|
|
567
608
|
|
|
568
609
|
Shipped in this release:
|
|
569
610
|
|
|
570
|
-
- ✅ **
|
|
571
|
-
- ✅
|
|
572
|
-
- ✅
|
|
573
|
-
- ✅
|
|
611
|
+
- ✅ **Unified loss API** — three cross-entropy losses covering index targets, distribution targets, and probability inputs, with one shape and normalization convention (see [What's new](#whats-new))
|
|
612
|
+
- ✅ **Label smoothing** (`eps`) in `sparse_softmax_cross_entropy_index`, computed from indices with zero extra memory
|
|
613
|
+
- ✅ Optional padding masks and `(B, T, V)` auto-flattening in every loss
|
|
614
|
+
- ✅ `cogforge.legacy` module (`TensorLegacy`) preserving the superseded losses for reproducibility
|
|
574
615
|
|
|
575
616
|
Shipped previously:
|
|
576
617
|
|
|
618
|
+
- ✅ **Autograd correctness pass** — all gradients verified against finite-difference checks; broadcast, indexing, sparse cross-entropy, and BatchNorm backward bugs fixed
|
|
619
|
+
- ✅ Computation-graph visualization via Graphviz (`cogforge.utils`), with per-tensor `op` labels
|
|
620
|
+
- ✅ Per-sequence EOS handling in `Seq2Seq.generate` (finished rows freeze to `pad_id`)
|
|
621
|
+
- ✅ Eager graph freeing in `backwards()` for lower peak memory
|
|
577
622
|
- ✅ RoPE (rotary position embeddings), usable in GPT and at every attention site of the Seq2Seq model
|
|
578
623
|
- ✅ Full encoder–decoder transformer (`Seq2Seq`) with cross-attention and padding masks
|
|
579
624
|
- ✅ Weight tying between embeddings and the output head
|
|
@@ -583,6 +628,7 @@ Shipped previously:
|
|
|
583
628
|
|
|
584
629
|
Planned / under consideration:
|
|
585
630
|
|
|
631
|
+
- Beam search decoding for `Seq2Seq` (length-normalized, per-sequence finished pool)
|
|
586
632
|
- Lazy Gradient Allocation (LGA) for lower peak memory on long sequences
|
|
587
633
|
- Cosine LR function as part of UTILS module
|
|
588
634
|
- Weight decay / AdamW optimizer
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
There is no C++, no PyTorch — just NumPy and explicit, hand-derived gradients. Optionally, the entire backend can be swapped to **CuPy** for GPU execution, or accelerated with **numexpr** on CPU, without changing any model code. Every tensor also records the operation that produced it, and the built-in **Graphviz visualizer** can render the full computation graph — data, gradients, and ops — so you can literally *see* backpropagation. The goal is still to *understand* every gradient that flows — speed is a bonus, not the point.
|
|
8
8
|
|
|
9
|
-
> **This release
|
|
9
|
+
> **This release overhauls the loss API.** Seven overlapping cross-entropy variants have been unified into three functions covering every use case — with built-in **label smoothing**, optional padding masks everywhere, and one consistent shape/normalization convention. The superseded losses live on in `cogforge.legacy`. See [What's new](#whats-new).
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -39,7 +39,41 @@ There is no C++, no PyTorch — just NumPy and explicit, hand-derived gradients.
|
|
|
39
39
|
|
|
40
40
|
## What's new
|
|
41
41
|
|
|
42
|
-
This version
|
|
42
|
+
This version consolidates the **loss API** into three functions with a single, consistent convention — and adds **label smoothing** on the road to a full translator.
|
|
43
|
+
|
|
44
|
+
**Unified cross-entropy API (3 losses, one convention)**
|
|
45
|
+
|
|
46
|
+
Seven overlapping cross-entropy variants (`softmax_cross_entropy_old`, `softmax_cross_entropy_masked`, `sparse_softmax_cross_entropy`, `sparse_softmax_cross_entropy_legacy`, `sparse_softmax_cross_entropy_index_legacy`, `cross_entropy_loss`, `cross_entropy_loss_masked`) are replaced by three that tile the whole space with no overlap — see [Losses](#losses):
|
|
47
|
+
|
|
48
|
+
- **`Tensor.sparse_softmax_cross_entropy_index(scores, labels, mask=None, eps=0.0)`** — the workhorse: logits in, integer labels, optional padding mask, optional **label smoothing** via `eps`. No one-hot matrix is ever built.
|
|
49
|
+
- **`Tensor.softmax_cross_entropy(scores, targets, mask=None)`** — logits in, arbitrary target *distributions* (knowledge distillation, mixup, soft labels), optional mask.
|
|
50
|
+
- **`Tensor.cross_entropy_from_probs(predictions, targets, mask=None)`** — for graphs that already end in `.softmax()`; prefer the logits-based losses when you have logits.
|
|
51
|
+
|
|
52
|
+
All three now share the same rules, closing several long-standing traps:
|
|
53
|
+
|
|
54
|
+
- **One shape convention.** Every loss accepts `(N, V)` *and* `(B, T, V)` logits (auto-flattened internally); masks may be `(N,)` or `(B, T)`.
|
|
55
|
+
- **One normalization convention.** Every loss divides by the number of *real* (unmasked) tokens — no more `/B` vs `/B·T` vs `/n_real` drift between variants, which silently changed effective learning rates when switching losses.
|
|
56
|
+
- **Masks are optional everywhere**, defaulting to "all positions are real".
|
|
57
|
+
- **Exact log-probabilities.** Loss values are computed from `log-softmax` directly instead of `log(clip(p))`, so near-zero probabilities are no longer floored at `log(1e-15)`.
|
|
58
|
+
|
|
59
|
+
**Label smoothing**
|
|
60
|
+
|
|
61
|
+
`sparse_softmax_cross_entropy_index(..., eps=0.1)` trains against `(1−eps)` on the true class and `eps/(V−1)` spread uniformly over the rest — the standard regularizer for translation models — at zero extra memory: both loss and gradient are computed straight from the label indices. `eps=0.0` (default) is exact plain cross-entropy.
|
|
62
|
+
|
|
63
|
+
**Performance**
|
|
64
|
+
|
|
65
|
+
- The hot path (`eps=0`, no mask — e.g. char-level GPT training) keeps the fused, **numexpr-accelerated** in-place backward from the previous `sparse_softmax_cross_entropy`.
|
|
66
|
+
- Masked/smoothed paths fold the mask, upstream gradient, and normalization into a single per-row coefficient — one fewer `(N, V)` temporary in every backward.
|
|
67
|
+
|
|
68
|
+
**Legacy module**
|
|
69
|
+
|
|
70
|
+
The superseded losses were **moved, not deleted**: they live in `cogforge.legacy` under `TensorLegacy`, unchanged, for reference and for reproducing old experiments. New code should not import them — note that they retain their old (inconsistent) normalization conventions.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### Previous release — autograd correctness pass
|
|
75
|
+
|
|
76
|
+
This version focused on **autograd correctness and debuggability**. All fixes below were validated with finite-difference gradient checks and end-to-end training tests.
|
|
43
77
|
|
|
44
78
|
**Gradient correctness fixes**
|
|
45
79
|
|
|
@@ -82,6 +116,7 @@ The package is organized into four modules:
|
|
|
82
116
|
| `cogforge.app` | The autograd engine (`Tensor`) and every building block — layers, optimizers, losses, normalization, attention, positional encodings. |
|
|
83
117
|
| `cogforge.models` | Ready-to-use models: `GPTV1`, `GPT2`, `Seq2Seq`. |
|
|
84
118
|
| `cogforge.utils` | Computation-graph tracing and Graphviz rendering (`trace_graph`, `draw_graph`). |
|
|
119
|
+
| `cogforge.legacy` | `TensorLegacy` — the superseded cross-entropy variants, kept unchanged for reference and reproducing old experiments. Don't use in new code. |
|
|
85
120
|
|
|
86
121
|
```python
|
|
87
122
|
from cogforge.app import Tensor, Linear, Adam, MultiHeadAttention # building blocks
|
|
@@ -219,20 +254,23 @@ Gradients **accumulate** into `.grad`. Always zero them between optimization ste
|
|
|
219
254
|
|
|
220
255
|
### Losses
|
|
221
256
|
|
|
222
|
-
All losses are **classmethods** on `Tensor` and return a scalar loss tensor you call `.backwards()` on.
|
|
257
|
+
All losses are **classmethods** on `Tensor` and return a scalar loss tensor you call `.backwards()` on. There are exactly **three**, and they share one convention:
|
|
223
258
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
| `Tensor.sparse_softmax_cross_entropy(scores, target_ids)` | `scores` raw logits `(B,T,V)`, `target_ids` integers `(B,T)`. | Language modeling — skips building one-hot targets. |
|
|
228
|
-
| `Tensor.sparse_softmax_cross_entropy_index(scores, labels, mask)` | Raw logits + integer labels + per-token mask (1 = real, 0 = pad). | Padded LM / seq2seq batches. **Recommended for Seq2Seq training.** |
|
|
229
|
-
| `Tensor.softmax_cross_entropy_masked(scores, targets, mask)` | Logits + one-hot targets + per-row mask. | Padded batches, fused softmax. |
|
|
230
|
-
| `Tensor.cross_entropy_loss(predictions, targets)` | `predictions` are **probabilities** (call `.softmax()` first), `targets` one-hot. | You already have a softmax in your graph. |
|
|
231
|
-
| `Tensor.cross_entropy_loss_masked(predictions, targets, mask)` | Probabilities + per-row mask. | Padded batches without fused softmax. |
|
|
259
|
+
- **Shapes:** logits/probs may be `(N, V)` or `(B, T, V)` — 3-D input is auto-flattened. Masks may be `(N,)` or `(B, T)`.
|
|
260
|
+
- **Masks:** optional everywhere (`mask=None` treats all positions as real); `1` = real token, `0` = padding. Padded positions contribute zero loss *and* exactly zero gradient.
|
|
261
|
+
- **Normalization:** always by the number of real tokens (all positions when unmasked).
|
|
232
262
|
|
|
233
|
-
|
|
263
|
+
Pick by what your **target** looks like and what your **input** is:
|
|
264
|
+
|
|
265
|
+
| Loss | Input | Target | Use when |
|
|
266
|
+
| --- | --- | --- | --- |
|
|
267
|
+
| `Tensor.sparse_softmax_cross_entropy_index(scores, labels, mask=None, eps=0.0)` | **raw logits** | integer class ids | **The workhorse.** Classification, LM, seq2seq. `eps>0` enables label smoothing: `1−eps` on the true class, `eps/(V−1)` on the rest — computed straight from indices, no one-hot built. `eps=0.1` is the standard for translation. |
|
|
268
|
+
| `Tensor.softmax_cross_entropy(scores, targets, mask=None)` | **raw logits** | full distribution `(…, V)`, rows sum to 1 | Targets that aren't expressible as an index: knowledge distillation against a teacher's output, mixup, annotator vote distributions. |
|
|
269
|
+
| `Tensor.cross_entropy_from_probs(predictions, targets, mask=None)` | **probabilities** (e.g. output of `.softmax()`) | one-hot or distribution | Your graph already ends in a softmax. Prefer the logits-based losses when you have logits — their gradients are exact and bounded, this one saturates via clipping as probs → 0. |
|
|
270
|
+
|
|
271
|
+
> ℹ️ The first two apply softmax internally — feed them **raw logits**. `cross_entropy_from_probs` is the opposite — it expects probabilities. Mixing these up silently trains the wrong thing.
|
|
234
272
|
>
|
|
235
|
-
> `
|
|
273
|
+
> The previous generation of losses (`softmax_cross_entropy_masked`, `sparse_softmax_cross_entropy`, `cross_entropy_loss`, `cross_entropy_loss_masked`, and the `_old`/`_legacy` variants) now lives in `cogforge.legacy` under `TensorLegacy`, unchanged. They keep their old, mutually inconsistent normalizations (`/B` vs `/B·T` vs `/n_real`) — use them only to reproduce old experiments, and don't mix them with the current losses in one training run.
|
|
236
274
|
|
|
237
275
|
---
|
|
238
276
|
|
|
@@ -436,7 +474,7 @@ def get_batch(bs=32):
|
|
|
436
474
|
for step in range(2000):
|
|
437
475
|
x, y = get_batch()
|
|
438
476
|
logits = model(x) # (B, T, vocab)
|
|
439
|
-
loss = Tensor.
|
|
477
|
+
loss = Tensor.sparse_softmax_cross_entropy_index(logits, y)
|
|
440
478
|
|
|
441
479
|
opt.zero_grad()
|
|
442
480
|
loss.backwards()
|
|
@@ -489,8 +527,9 @@ for step in range(num_steps):
|
|
|
489
527
|
dec_in, labels = tgt[:, :-1], tgt[:, 1:]
|
|
490
528
|
logits = model(src, dec_in) # (B, T_dec, vocab)
|
|
491
529
|
|
|
492
|
-
mask = (labels != PAD).astype(np.float32)
|
|
493
|
-
loss = Tensor.sparse_softmax_cross_entropy_index(logits, labels,
|
|
530
|
+
mask = (labels != PAD).astype(np.float32) # (B, T_dec): 1 = real, 0 = pad
|
|
531
|
+
loss = Tensor.sparse_softmax_cross_entropy_index(logits, labels,
|
|
532
|
+
mask=mask, eps=0.1) # label smoothing
|
|
494
533
|
|
|
495
534
|
opt.zero_grad()
|
|
496
535
|
loss.backwards()
|
|
@@ -535,7 +574,9 @@ Two practical notes: call `draw_graph` **before** `backwards()`, since the backw
|
|
|
535
574
|
## Gotchas
|
|
536
575
|
|
|
537
576
|
- **It's `backwards()`, not `backward()`.** The backward pass method has a trailing `s`.
|
|
538
|
-
- **Logits vs. probabilities.** `softmax_cross_entropy`
|
|
577
|
+
- **Logits vs. probabilities.** `softmax_cross_entropy` and `sparse_softmax_cross_entropy_index` fuse the softmax internally — feed them **raw logits**. `cross_entropy_from_probs` expects **probabilities**. Mixing these up silently trains the wrong thing.
|
|
578
|
+
- **Label smoothing raises the loss floor.** With `eps > 0` the minimum achievable loss is no longer 0 (a perfect model still pays the smoothing term), so judge training by token accuracy or validation metrics, not by how close the raw loss gets to zero — and don't compare loss values across different `eps` settings.
|
|
579
|
+
- **Legacy losses normalize differently.** `cogforge.legacy.TensorLegacy` keeps the old `/B` / `/B·T` conventions; the current losses always divide by real-token count. Swapping one for the other rescales gradients — retune the learning rate if you migrate an old script.
|
|
539
580
|
- **Gradients accumulate.** Call `optimizer.zero_grad()` every step (or `p.grad[...] = 0`), or gradients pile up across iterations.
|
|
540
581
|
- **Switch the backend before building the model.** `use_gpu(True)` after construction leaves your parameters stranded on the CPU while new activations land on the GPU.
|
|
541
582
|
- **RoPE dim is per-head.** `RotatoryPositionalEncoding` takes `d_model // n_heads` (which must be even), not `d_model`. The models handle this internally — it only matters if you wire blocks up yourself.
|
|
@@ -552,13 +593,17 @@ Two practical notes: call `draw_graph` **before** `backwards()`, since the backw
|
|
|
552
593
|
|
|
553
594
|
Shipped in this release:
|
|
554
595
|
|
|
555
|
-
- ✅ **
|
|
556
|
-
- ✅
|
|
557
|
-
- ✅
|
|
558
|
-
- ✅
|
|
596
|
+
- ✅ **Unified loss API** — three cross-entropy losses covering index targets, distribution targets, and probability inputs, with one shape and normalization convention (see [What's new](#whats-new))
|
|
597
|
+
- ✅ **Label smoothing** (`eps`) in `sparse_softmax_cross_entropy_index`, computed from indices with zero extra memory
|
|
598
|
+
- ✅ Optional padding masks and `(B, T, V)` auto-flattening in every loss
|
|
599
|
+
- ✅ `cogforge.legacy` module (`TensorLegacy`) preserving the superseded losses for reproducibility
|
|
559
600
|
|
|
560
601
|
Shipped previously:
|
|
561
602
|
|
|
603
|
+
- ✅ **Autograd correctness pass** — all gradients verified against finite-difference checks; broadcast, indexing, sparse cross-entropy, and BatchNorm backward bugs fixed
|
|
604
|
+
- ✅ Computation-graph visualization via Graphviz (`cogforge.utils`), with per-tensor `op` labels
|
|
605
|
+
- ✅ Per-sequence EOS handling in `Seq2Seq.generate` (finished rows freeze to `pad_id`)
|
|
606
|
+
- ✅ Eager graph freeing in `backwards()` for lower peak memory
|
|
562
607
|
- ✅ RoPE (rotary position embeddings), usable in GPT and at every attention site of the Seq2Seq model
|
|
563
608
|
- ✅ Full encoder–decoder transformer (`Seq2Seq`) with cross-attention and padding masks
|
|
564
609
|
- ✅ Weight tying between embeddings and the output head
|
|
@@ -568,6 +613,7 @@ Shipped previously:
|
|
|
568
613
|
|
|
569
614
|
Planned / under consideration:
|
|
570
615
|
|
|
616
|
+
- Beam search decoding for `Seq2Seq` (length-normalized, per-sequence finished pool)
|
|
571
617
|
- Lazy Gradient Allocation (LGA) for lower peak memory on long sequences
|
|
572
618
|
- Cosine LR function as part of UTILS module
|
|
573
619
|
- Weight decay / AdamW optimizer
|
|
@@ -279,25 +279,6 @@ class Tensor:
|
|
|
279
279
|
out._backwards = _backward
|
|
280
280
|
return out
|
|
281
281
|
|
|
282
|
-
#optimized for batches
|
|
283
|
-
@classmethod
|
|
284
|
-
def cross_entropy_loss(cls,predictions, targets):
|
|
285
|
-
|
|
286
|
-
probabilities = backend.np.clip(predictions.data, 1e-15, 1 - 1e-15)
|
|
287
|
-
batch_size = predictions.shape[0]
|
|
288
|
-
|
|
289
|
-
loss_data = - backend.np.sum(targets * backend.np.log(probabilities)) / batch_size
|
|
290
|
-
if backend.NO_GRAD: return cls(loss_data,op="cross_entropy_loss")
|
|
291
|
-
loss = Tensor(loss_data, children=(predictions,),op="cross_entropy_loss")
|
|
292
|
-
|
|
293
|
-
def _backward():
|
|
294
|
-
|
|
295
|
-
# predictions.grad += (predictions.data - targets) / batch_size * loss.grad
|
|
296
|
-
predictions.grad += (-(targets / probabilities)) / batch_size * loss.grad
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
loss._backwards = _backward
|
|
300
|
-
return loss
|
|
301
282
|
|
|
302
283
|
#LEGACY
|
|
303
284
|
def backwards_recursive(self):
|
|
@@ -396,186 +377,139 @@ class Tensor:
|
|
|
396
377
|
|
|
397
378
|
#TODO
|
|
398
379
|
@classmethod
|
|
399
|
-
def
|
|
380
|
+
def cross_entropy_from_probs(cls, predictions, targets, mask=None):
|
|
400
381
|
"""
|
|
401
|
-
predictions: (B, V)
|
|
402
|
-
|
|
403
|
-
|
|
382
|
+
predictions: Tensor (N, V) or (B, T, V) — probabilities (rows sum to 1),
|
|
383
|
+
e.g. output of .softmax(). Prefer the logits-based losses
|
|
384
|
+
(sparse_softmax_cross_entropy_index / softmax_cross_entropy)
|
|
385
|
+
when you have logits: their gradients are exact and bounded,
|
|
386
|
+
this one saturates via the clip when probs -> 0.
|
|
387
|
+
targets: array, same shape — one-hot or arbitrary distribution
|
|
388
|
+
mask: optional (N,) or (B, T); 1 = real, 0 = pad
|
|
404
389
|
"""
|
|
405
|
-
|
|
406
|
-
|
|
390
|
+
orig_shape = predictions.data.shape
|
|
391
|
+
V = orig_shape[-1]
|
|
392
|
+
N = int(numpy.prod(orig_shape[:-1]))
|
|
393
|
+
|
|
394
|
+
probs = backend.np.clip(predictions.data.reshape(N, V), 1e-15, 1 - 1e-15)
|
|
395
|
+
q = backend.np.asarray(targets).reshape(N, V)
|
|
396
|
+
|
|
397
|
+
if mask is None:
|
|
398
|
+
mask = backend.np.ones(N, dtype=probs.dtype)
|
|
399
|
+
else:
|
|
400
|
+
mask = backend.np.asarray(mask).reshape(-1).astype(probs.dtype)
|
|
407
401
|
n_real = mask.sum()
|
|
408
402
|
n_real = n_real if n_real > 0 else 1.0
|
|
409
403
|
|
|
410
|
-
|
|
411
|
-
loss_data = -backend.np.sum(mask * targets * backend.np.log(probs)) / n_real
|
|
412
|
-
|
|
413
|
-
if backend.NO_GRAD: return cls(loss_data)
|
|
414
|
-
loss = cls(loss_data, children=(predictions,))
|
|
404
|
+
loss_data = -backend.np.sum(mask[:, None] * q * backend.np.log(probs)) / n_real
|
|
415
405
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
grad = grad * mask # <-- padded rows get exactly zero gradient
|
|
420
|
-
predictions.grad += grad * loss.grad
|
|
421
|
-
|
|
422
|
-
loss._backwards = _backward
|
|
423
|
-
return loss
|
|
406
|
+
if backend.NO_GRAD:
|
|
407
|
+
return cls(loss_data, op="cross_entropy_from_probs")
|
|
408
|
+
loss = cls(loss_data, children=(predictions,), op="cross_entropy_from_probs")
|
|
424
409
|
|
|
425
|
-
@classmethod
|
|
426
|
-
def sparse_softmax_cross_entropy_legacy(cls, scores, target_ids):
|
|
427
|
-
""" scores: (B, T, V) logits ; target_ids: (B, T) int """
|
|
428
|
-
z = scores.data - scores.data.max(axis=-1, keepdims=True)
|
|
429
|
-
e = backend.np.exp(z)
|
|
430
|
-
p = e / e.sum(axis=-1, keepdims=True)
|
|
431
|
-
N = int(numpy.prod(scores.shape[:-1]))
|
|
432
|
-
flat, idx, rows = p.reshape(N, -1), target_ids.reshape(N), backend.np.arange(N)
|
|
433
|
-
# loss = cls(-backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N, children=(scores,))
|
|
434
|
-
|
|
435
|
-
loss_data = -backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N
|
|
436
|
-
|
|
437
|
-
if backend.NO_GRAD: return cls(loss_data)
|
|
438
|
-
|
|
439
|
-
loss = cls(loss_data, children=(scores,))
|
|
440
|
-
|
|
441
410
|
def _backward():
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
scores.grad += (g.reshape(scores.shape) / N) * loss.grad
|
|
446
|
-
loss._backwards = _backward
|
|
447
|
-
return loss
|
|
448
|
-
|
|
449
|
-
@classmethod
|
|
450
|
-
def sparse_softmax_cross_entropy(cls, scores, target_ids):
|
|
451
|
-
p = _stable_softmax(scores.data)
|
|
452
|
-
N = int(numpy.prod(scores.shape[:-1]))
|
|
453
|
-
flat, idx, rows = p.reshape(N, -1), target_ids.reshape(N), backend.np.arange(N)
|
|
454
|
-
# loss = cls(-backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N, children=(scores,))
|
|
455
|
-
|
|
456
|
-
loss_data = -backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N
|
|
457
|
-
|
|
458
|
-
if backend.NO_GRAD: return cls(loss_data, op="sparse_softmax_crossentropy")
|
|
459
|
-
|
|
460
|
-
loss = cls(loss_data, children=(scores,), op="sparse_softmax_crossentropy")
|
|
461
|
-
|
|
462
|
-
def _backward():
|
|
463
|
-
# g = p.reshape(N, -1).copy() #added copy
|
|
464
|
-
# g[rows, idx] -= 1.0
|
|
465
|
-
# coef = numpy.float32(float(loss.grad) / N)
|
|
466
|
-
# _iadd_scaled(scores.grad, g.reshape(scores.shape), coef)
|
|
467
|
-
|
|
468
|
-
coef = numpy.float32(float(loss.grad) / N)
|
|
469
|
-
_iadd_scaled(scores.grad, p.reshape(scores.shape), coef)
|
|
470
|
-
flat_grad = scores.grad.reshape(N, -1)
|
|
471
|
-
flat_grad[rows, idx] -= coef
|
|
411
|
+
row_coef = (mask * loss.grad) / n_real
|
|
412
|
+
grad = -(q / probs) * row_coef[:, None]
|
|
413
|
+
predictions.grad += grad.reshape(orig_shape)
|
|
472
414
|
loss._backwards = _backward
|
|
473
415
|
return loss
|
|
416
|
+
|
|
474
417
|
|
|
475
418
|
@classmethod
|
|
476
|
-
def sparse_softmax_cross_entropy_index(cls, scores, labels, mask):
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
z = scores.data
|
|
484
|
-
z = z - z.max(axis=1, keepdims=True)
|
|
419
|
+
def sparse_softmax_cross_entropy_index(cls, scores, labels, mask=None, eps=0.0):
|
|
420
|
+
orig_shape = scores.data.shape
|
|
421
|
+
V = orig_shape[-1]
|
|
422
|
+
N = int(numpy.prod(orig_shape[:-1])) # (B,T,V) and (N,V) both fine
|
|
423
|
+
|
|
424
|
+
z = scores.data.reshape(N, V)
|
|
425
|
+
z = z - z.max(axis=-1, keepdims=True)
|
|
485
426
|
e = backend.np.exp(z)
|
|
486
|
-
|
|
427
|
+
denom = e.sum(axis=-1, keepdims=True)
|
|
428
|
+
p = e / denom
|
|
429
|
+
logp = z - backend.np.log(denom)
|
|
487
430
|
|
|
488
|
-
labels = backend.np.asarray(labels).reshape(-1)
|
|
489
|
-
mask = backend.np.asarray(mask).reshape(-1) # (N,)
|
|
490
|
-
N = p.shape[0]
|
|
431
|
+
labels = backend.np.asarray(labels).reshape(-1)
|
|
491
432
|
rows = backend.np.arange(N)
|
|
492
433
|
|
|
434
|
+
if mask is None:
|
|
435
|
+
mask = backend.np.ones(N, dtype=p.dtype)
|
|
436
|
+
else:
|
|
437
|
+
mask = backend.np.asarray(mask).reshape(-1).astype(p.dtype)
|
|
493
438
|
n_real = mask.sum()
|
|
494
439
|
n_real = n_real if n_real > 0 else 1.0
|
|
495
440
|
|
|
496
|
-
logp_correct =
|
|
497
|
-
|
|
441
|
+
logp_correct = logp[rows, labels]
|
|
442
|
+
if eps > 0.0:
|
|
443
|
+
off = eps / (V - 1)
|
|
444
|
+
per_tok = -((1.0 - eps) * logp_correct + off * (logp.sum(axis=-1) - logp_correct))
|
|
445
|
+
else:
|
|
446
|
+
per_tok = -logp_correct
|
|
447
|
+
loss_data = backend.np.sum(mask * per_tok) / n_real
|
|
498
448
|
|
|
499
449
|
if backend.NO_GRAD:
|
|
500
|
-
return cls(loss_data,op="sparse_softmax_crossentropy")
|
|
450
|
+
return cls(loss_data, op="sparse_softmax_crossentropy")
|
|
451
|
+
loss = cls(loss_data, children=(scores,), op="sparse_softmax_crossentropy")
|
|
501
452
|
|
|
502
|
-
loss = cls(loss_data, children=(scores,),op="sparse_softmax_crossentropy")
|
|
503
453
|
def _backward():
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
# grad *= (mask / n_real)[:, None]
|
|
507
|
-
# scores.grad += grad * loss.grad
|
|
508
|
-
|
|
509
|
-
row_coef = (mask * loss.grad) / n_real
|
|
510
|
-
row_coef_expanded = row_coef[:, None]
|
|
511
|
-
scores.grad += p * row_coef_expanded
|
|
512
|
-
scores.grad[rows, labels] -= row_coef
|
|
513
|
-
loss._backwards = _backward
|
|
514
|
-
return loss
|
|
515
|
-
|
|
516
|
-
#Legacy
|
|
517
|
-
@classmethod
|
|
518
|
-
def softmax_cross_entropy_old(cls, scores, targets):
|
|
519
|
-
z = scores.data
|
|
520
|
-
z = z - z.max(axis=1, keepdims=True) # stability
|
|
521
|
-
e = backend.np.exp(z)
|
|
522
|
-
p = e / e.sum(axis=1, keepdims=True) # softmax, computed privately
|
|
523
|
-
B = scores.shape[0]
|
|
524
|
-
|
|
525
|
-
# loss = cls(-backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / B, children=(scores,))
|
|
454
|
+
no_mask = mask.min() == 1.0
|
|
455
|
+
flat_grad = scores.grad.reshape(N, V)
|
|
526
456
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
457
|
+
if eps == 0.0 and no_mask:
|
|
458
|
+
coef = numpy.float32(float(loss.grad) / n_real)
|
|
459
|
+
_iadd_scaled(flat_grad, p, coef)
|
|
460
|
+
flat_grad[rows, labels] -= coef
|
|
461
|
+
else:
|
|
462
|
+
row_coef = (mask * loss.grad) / n_real
|
|
463
|
+
base = p - eps / (V - 1) if eps > 0.0 else p
|
|
464
|
+
flat_grad += base * row_coef[:, None]
|
|
465
|
+
spike = (1.0 - eps) - (eps / (V - 1)) if eps > 0.0 else 1.0
|
|
466
|
+
flat_grad[rows, labels] -= spike * row_coef
|
|
535
467
|
loss._backwards = _backward
|
|
536
468
|
return loss
|
|
537
469
|
|
|
470
|
+
|
|
538
471
|
@classmethod
|
|
539
|
-
def softmax_cross_entropy(cls, scores, targets):
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
loss = cls(loss_data, children=(scores,))
|
|
549
|
-
def _backward():
|
|
550
|
-
scores.grad += (p - targets) / N * loss.grad
|
|
551
|
-
loss._backwards = _backward
|
|
552
|
-
return loss
|
|
553
|
-
|
|
472
|
+
def softmax_cross_entropy(cls, scores, targets, mask=None):
|
|
473
|
+
"""
|
|
474
|
+
scores: Tensor (N, V) or (B, T, V) logits
|
|
475
|
+
targets: array, same shape — arbitrary distribution per row (rows sum to 1)
|
|
476
|
+
mask: optional (N,) or (B, T); 1 = real, 0 = pad
|
|
477
|
+
"""
|
|
478
|
+
orig_shape = scores.data.shape
|
|
479
|
+
V = orig_shape[-1]
|
|
480
|
+
N = int(numpy.prod(orig_shape[:-1]))
|
|
554
481
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
z = scores.data
|
|
558
|
-
z = z - z.max(axis=1, keepdims=True)
|
|
482
|
+
z = scores.data.reshape(N, V)
|
|
483
|
+
z = z - z.max(axis=-1, keepdims=True)
|
|
559
484
|
e = backend.np.exp(z)
|
|
560
|
-
|
|
485
|
+
denom = e.sum(axis=-1, keepdims=True)
|
|
486
|
+
p = e / denom
|
|
487
|
+
logp = z - backend.np.log(denom)
|
|
561
488
|
|
|
562
|
-
|
|
489
|
+
q = backend.np.asarray(targets).reshape(N, V)
|
|
490
|
+
|
|
491
|
+
if mask is None:
|
|
492
|
+
mask = backend.np.ones(N, dtype=p.dtype)
|
|
493
|
+
else:
|
|
494
|
+
mask = backend.np.asarray(mask).reshape(-1).astype(p.dtype)
|
|
563
495
|
n_real = mask.sum()
|
|
564
496
|
n_real = n_real if n_real > 0 else 1.0
|
|
565
497
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
498
|
+
loss_data = -backend.np.sum(mask[:, None] * q * logp) / n_real
|
|
499
|
+
|
|
500
|
+
if backend.NO_GRAD:
|
|
501
|
+
return cls(loss_data, op="soft_cross_entropy")
|
|
502
|
+
loss = cls(loss_data, children=(scores,), op="soft_cross_entropy")
|
|
503
|
+
|
|
573
504
|
def _backward():
|
|
574
|
-
|
|
575
|
-
grad =
|
|
576
|
-
scores.grad += grad
|
|
505
|
+
row_coef = (mask * loss.grad) / n_real
|
|
506
|
+
grad = (p - q) * row_coef[:, None]
|
|
507
|
+
scores.grad += grad.reshape(orig_shape)
|
|
508
|
+
|
|
577
509
|
loss._backwards = _backward
|
|
578
510
|
return loss
|
|
511
|
+
|
|
512
|
+
|
|
579
513
|
|
|
580
514
|
def transpose(self,axes):
|
|
581
515
|
if backend.NO_GRAD: return Tensor(backend.np.transpose(self.data,axes=axes),op=f"transpose[{axes}]")
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from . import backend
|
|
2
|
+
import numpy
|
|
3
|
+
from .app import _iadd_scaled,_stable_softmax,Tensor
|
|
4
|
+
|
|
5
|
+
class TensorLegacy:
|
|
6
|
+
#Legacy DONT INCLUDE IN DOCS
|
|
7
|
+
@classmethod
|
|
8
|
+
def sparse_softmax_cross_entropy_index_legacy(cls, scores, labels, mask):
|
|
9
|
+
"""
|
|
10
|
+
scores: Tensor (N, V) logits
|
|
11
|
+
labels: (N,) int correct class id per row
|
|
12
|
+
mask: (N,) {0,1} 1 = real token, 0 = padding
|
|
13
|
+
Same loss/gradient as the one-hot masked version, no (N, V) target built.
|
|
14
|
+
"""
|
|
15
|
+
z = scores.data
|
|
16
|
+
z = z - z.max(axis=1, keepdims=True)
|
|
17
|
+
e = backend.np.exp(z)
|
|
18
|
+
p = e / e.sum(axis=1, keepdims=True) # (N, V)
|
|
19
|
+
|
|
20
|
+
labels = backend.np.asarray(labels).reshape(-1) # (N,)
|
|
21
|
+
mask = backend.np.asarray(mask).reshape(-1) # (N,)
|
|
22
|
+
N = p.shape[0]
|
|
23
|
+
rows = backend.np.arange(N)
|
|
24
|
+
|
|
25
|
+
n_real = mask.sum()
|
|
26
|
+
n_real = n_real if n_real > 0 else 1.0
|
|
27
|
+
|
|
28
|
+
logp_correct = backend.np.log(backend.np.clip(p[rows, labels], 1e-15, 1.0))
|
|
29
|
+
loss_data = -backend.np.sum(mask * logp_correct) / n_real
|
|
30
|
+
|
|
31
|
+
if backend.NO_GRAD:
|
|
32
|
+
return cls(loss_data,op="sparse_softmax_crossentropy")
|
|
33
|
+
|
|
34
|
+
loss = cls(loss_data, children=(scores,),op="sparse_softmax_crossentropy")
|
|
35
|
+
def _backward():
|
|
36
|
+
# grad = p.copy()
|
|
37
|
+
# grad[rows, labels] -= 1.0
|
|
38
|
+
# grad *= (mask / n_real)[:, None]
|
|
39
|
+
# scores.grad += grad * loss.grad
|
|
40
|
+
|
|
41
|
+
row_coef = (mask * loss.grad) / n_real
|
|
42
|
+
row_coef_expanded = row_coef[:, None]
|
|
43
|
+
scores.grad += p * row_coef_expanded
|
|
44
|
+
scores.grad[rows, labels] -= row_coef
|
|
45
|
+
loss._backwards = _backward
|
|
46
|
+
return loss
|
|
47
|
+
#Legacy
|
|
48
|
+
@classmethod
|
|
49
|
+
def softmax_cross_entropy_old(cls, scores, targets):
|
|
50
|
+
z = scores.data
|
|
51
|
+
z = z - z.max(axis=1, keepdims=True) # stability
|
|
52
|
+
e = backend.np.exp(z)
|
|
53
|
+
p = e / e.sum(axis=1, keepdims=True) # softmax, computed privately
|
|
54
|
+
B = scores.shape[0]
|
|
55
|
+
|
|
56
|
+
# loss = cls(-backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / B, children=(scores,))
|
|
57
|
+
|
|
58
|
+
loss_data = -backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / B
|
|
59
|
+
|
|
60
|
+
if backend.NO_GRAD: return cls(loss_data)
|
|
61
|
+
|
|
62
|
+
loss = cls(loss_data, children=(scores,))
|
|
63
|
+
|
|
64
|
+
def _backward():
|
|
65
|
+
scores.grad += (p - targets) / B * loss.grad
|
|
66
|
+
loss._backwards = _backward
|
|
67
|
+
return loss
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def softmax_cross_entropy_masked(cls, scores, targets, mask):
|
|
71
|
+
z = scores.data
|
|
72
|
+
z = z - z.max(axis=1, keepdims=True)
|
|
73
|
+
e = backend.np.exp(z)
|
|
74
|
+
p = e / e.sum(axis=1, keepdims=True)
|
|
75
|
+
|
|
76
|
+
mask = mask.reshape(-1, 1)
|
|
77
|
+
n_real = mask.sum()
|
|
78
|
+
n_real = n_real if n_real > 0 else 1.0
|
|
79
|
+
|
|
80
|
+
# loss = cls(-backend.np.sum(mask * targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / n_real, children=(scores,))
|
|
81
|
+
loss_data = -backend.np.sum(mask * targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / n_real
|
|
82
|
+
|
|
83
|
+
if backend.NO_GRAD: return cls(loss_data)
|
|
84
|
+
|
|
85
|
+
loss = cls(loss_data, children=(scores,))
|
|
86
|
+
|
|
87
|
+
def _backward():
|
|
88
|
+
grad = (p - targets) / n_real
|
|
89
|
+
grad = grad * mask
|
|
90
|
+
scores.grad += grad * loss.grad
|
|
91
|
+
loss._backwards = _backward
|
|
92
|
+
return loss
|
|
93
|
+
|
|
94
|
+
#Older Version. Newer index function should be used for eps improvement. Dont use even without padding, write clearly in docs
|
|
95
|
+
@classmethod
|
|
96
|
+
def sparse_softmax_cross_entropy(cls, scores, target_ids):
|
|
97
|
+
p = _stable_softmax(scores.data)
|
|
98
|
+
N = int(numpy.prod(scores.shape[:-1]))
|
|
99
|
+
flat, idx, rows = p.reshape(N, -1), target_ids.reshape(N), backend.np.arange(N)
|
|
100
|
+
# loss = cls(-backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N, children=(scores,))
|
|
101
|
+
|
|
102
|
+
loss_data = -backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N
|
|
103
|
+
|
|
104
|
+
if backend.NO_GRAD: return cls(loss_data, op="sparse_softmax_crossentropy")
|
|
105
|
+
|
|
106
|
+
loss = cls(loss_data, children=(scores,), op="sparse_softmax_crossentropy")
|
|
107
|
+
|
|
108
|
+
def _backward():
|
|
109
|
+
# g = p.reshape(N, -1).copy() #added copy
|
|
110
|
+
# g[rows, idx] -= 1.0
|
|
111
|
+
# coef = numpy.float32(float(loss.grad) / N)
|
|
112
|
+
# _iadd_scaled(scores.grad, g.reshape(scores.shape), coef)
|
|
113
|
+
|
|
114
|
+
coef = numpy.float32(float(loss.grad) / N)
|
|
115
|
+
_iadd_scaled(scores.grad, p.reshape(scores.shape), coef)
|
|
116
|
+
flat_grad = scores.grad.reshape(N, -1)
|
|
117
|
+
flat_grad[rows, idx] -= coef
|
|
118
|
+
loss._backwards = _backward
|
|
119
|
+
return loss
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def sparse_softmax_cross_entropy_legacy(cls, scores, target_ids):
|
|
123
|
+
""" scores: (B, T, V) logits ; target_ids: (B, T) int """
|
|
124
|
+
z = scores.data - scores.data.max(axis=-1, keepdims=True)
|
|
125
|
+
e = backend.np.exp(z)
|
|
126
|
+
p = e / e.sum(axis=-1, keepdims=True)
|
|
127
|
+
N = int(numpy.prod(scores.shape[:-1]))
|
|
128
|
+
flat, idx, rows = p.reshape(N, -1), target_ids.reshape(N), backend.np.arange(N)
|
|
129
|
+
# loss = cls(-backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N, children=(scores,))
|
|
130
|
+
|
|
131
|
+
loss_data = -backend.np.log(backend.np.clip(flat[rows, idx], 1e-15, 1.0)).sum() / N
|
|
132
|
+
|
|
133
|
+
if backend.NO_GRAD: return cls(loss_data)
|
|
134
|
+
|
|
135
|
+
loss = cls(loss_data, children=(scores,))
|
|
136
|
+
|
|
137
|
+
def _backward():
|
|
138
|
+
# g = p.reshape(N, -1).copy()
|
|
139
|
+
g = p.reshape(N, -1)
|
|
140
|
+
g[rows, idx] -= 1.0 # (softmax - onehot)
|
|
141
|
+
scores.grad += (g.reshape(scores.shape) / N) * loss.grad
|
|
142
|
+
loss._backwards = _backward
|
|
143
|
+
return loss
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def softmax_cross_entropy(cls, scores, targets):
|
|
147
|
+
z = scores.data
|
|
148
|
+
z = z - z.max(axis=-1, keepdims=True)
|
|
149
|
+
e = backend.np.exp(z)
|
|
150
|
+
p = e / e.sum(axis=-1, keepdims=True)
|
|
151
|
+
N = numpy.prod(scores.shape[:-1]) # B for 2D, B*T for 3D
|
|
152
|
+
# loss = cls(-backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / N, children=(scores,))
|
|
153
|
+
loss_data = -backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / N
|
|
154
|
+
if backend.NO_GRAD: return cls(loss_data)
|
|
155
|
+
loss = cls(loss_data, children=(scores,))
|
|
156
|
+
def _backward():
|
|
157
|
+
scores.grad += (p - targets) / N * loss.grad
|
|
158
|
+
loss._backwards = _backward
|
|
159
|
+
return loss
|
|
160
|
+
|
|
161
|
+
#optimized for batches
|
|
162
|
+
@classmethod
|
|
163
|
+
def cross_entropy_loss(cls,predictions, targets):
|
|
164
|
+
|
|
165
|
+
probabilities = backend.np.clip(predictions.data, 1e-15, 1 - 1e-15)
|
|
166
|
+
batch_size = predictions.shape[0]
|
|
167
|
+
|
|
168
|
+
loss_data = - backend.np.sum(targets * backend.np.log(probabilities)) / batch_size
|
|
169
|
+
if backend.NO_GRAD: return cls(loss_data,op="cross_entropy_loss")
|
|
170
|
+
loss = Tensor(loss_data, children=(predictions,),op="cross_entropy_loss")
|
|
171
|
+
|
|
172
|
+
def _backward():
|
|
173
|
+
|
|
174
|
+
# predictions.grad += (predictions.data - targets) / batch_size * loss.grad
|
|
175
|
+
predictions.grad += (-(targets / probabilities)) / batch_size * loss.grad
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
loss._backwards = _backward
|
|
179
|
+
return loss
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|