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.
@@ -1,6 +1,7 @@
1
1
  # Python caches
2
2
  __pycache__/
3
3
  *.py[cod]
4
+ .gitignore
4
5
 
5
6
  # Build folders (created by PyPI later)
6
7
  dist/
@@ -9,4 +10,7 @@ build/
9
10
 
10
11
  # Saved Model Weights
11
12
  *.npz
12
- *.pt
13
+ *.pt
14
+
15
+ project_review_report.md
16
+ ROADMAP.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cogforge-engine
3
- Version: 2.1.2
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 is a correctness pass.** Every gradient in the engine has now been verified against finite-difference numerical checks, and several subtle autograd bugs were found and fixed see [What's new](#whats-new).
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 focuses on **autograd correctness and debuggability**. All fixes below were validated with finite-difference gradient checks and end-to-end training tests.
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. Mind the distinction between losses that take **probabilities** and losses that take **raw logits** — this is the most common mistake.
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
- | Loss | Input expectation | Use when |
240
- | --- | --- | --- |
241
- | `Tensor.softmax_cross_entropy(scores, targets)` | `scores` are **raw logits**, `targets` one-hot. Softmax fused inside (stable). Works for 2-D `(B,V)` and 3-D `(B,T,V)`. | Standard classification / LM. **Recommended.** |
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
- > ℹ️ The `softmax_*` and `sparse_softmax_*` variants apply softmax internally — feed them **raw logits**. `cross_entropy_loss*` is the opposite — it expects probabilities.
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
- > `sparse_softmax_cross_entropy_legacy` and `softmax_cross_entropy_old` are kept for reference; prefer the current versions.
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.sparse_softmax_cross_entropy(logits, y)
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, mask)
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` / `sparse_softmax_cross_entropy*` fuse the softmax internally — feed them **raw logits**. `cross_entropy_loss*` expects **probabilities**. Mixing these up silently trains the wrong thing.
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
- - ✅ **Autograd correctness pass** — all gradients verified against finite-difference checks; broadcast, indexing, sparse cross-entropy, and BatchNorm backward bugs fixed (see [What's new](#whats-new))
571
- - ✅ Computation-graph visualization via Graphviz (`cogforge.utils`), with per-tensor `op` labels
572
- - ✅ Per-sequence EOS handling in `Seq2Seq.generate` (finished rows freeze to `pad_id`)
573
- - ✅ Eager graph freeing in `backwards()` for lower peak memory
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 is a correctness pass.** Every gradient in the engine has now been verified against finite-difference numerical checks, and several subtle autograd bugs were found and fixed see [What's new](#whats-new).
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 focuses on **autograd correctness and debuggability**. All fixes below were validated with finite-difference gradient checks and end-to-end training tests.
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. Mind the distinction between losses that take **probabilities** and losses that take **raw logits** — this is the most common mistake.
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
- | Loss | Input expectation | Use when |
225
- | --- | --- | --- |
226
- | `Tensor.softmax_cross_entropy(scores, targets)` | `scores` are **raw logits**, `targets` one-hot. Softmax fused inside (stable). Works for 2-D `(B,V)` and 3-D `(B,T,V)`. | Standard classification / LM. **Recommended.** |
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
- > ℹ️ The `softmax_*` and `sparse_softmax_*` variants apply softmax internally — feed them **raw logits**. `cross_entropy_loss*` is the opposite — it expects probabilities.
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
- > `sparse_softmax_cross_entropy_legacy` and `softmax_cross_entropy_old` are kept for reference; prefer the current versions.
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.sparse_softmax_cross_entropy(logits, y)
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, mask)
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` / `sparse_softmax_cross_entropy*` fuse the softmax internally — feed them **raw logits**. `cross_entropy_loss*` expects **probabilities**. Mixing these up silently trains the wrong thing.
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
- - ✅ **Autograd correctness pass** — all gradients verified against finite-difference checks; broadcast, indexing, sparse cross-entropy, and BatchNorm backward bugs fixed (see [What's new](#whats-new))
556
- - ✅ Computation-graph visualization via Graphviz (`cogforge.utils`), with per-tensor `op` labels
557
- - ✅ Per-sequence EOS handling in `Seq2Seq.generate` (finished rows freeze to `pad_id`)
558
- - ✅ Eager graph freeing in `backwards()` for lower peak memory
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
@@ -6,4 +6,6 @@ cosine LR function
6
6
 
7
7
  Batch acummalator normalization
8
8
 
9
- The 50% Memory Tax (Lazy Gradient Allocation)
9
+ The 50% Memory Tax (Lazy Gradient Allocation)
10
+
11
+ Beam search decoding with length penalty
@@ -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 cross_entropy_loss_masked(cls, predictions, targets, mask):
380
+ def cross_entropy_from_probs(cls, predictions, targets, mask=None):
400
381
  """
401
- predictions: (B, V) softmax probs
402
- targets: (B, V) one-hot
403
- mask: (B,) 1.0 for real positions, 0.0 for <PAD>
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
- probs = backend.np.clip(predictions.data, 1e-15, 1 - 1e-15)
406
- mask = mask.reshape(-1, 1) # (B, 1)
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
- # only real rows contribute to the loss value
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
- def _backward():
417
- # grad = (predictions.data - targets) / n_real
418
- grad = -(targets / probs) / n_real
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
- # g = p.reshape(N, -1).copy()
443
- g = p.reshape(N, -1)
444
- g[rows, idx] -= 1.0 # (softmax - onehot)
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
- scores: Tensor (N, V) logits
479
- labels: (N,) int correct class id per row
480
- mask: (N,) {0,1} 1 = real token, 0 = padding
481
- Same loss/gradient as the one-hot masked version, no (N, V) target built.
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
- p = e / e.sum(axis=1, keepdims=True) # (N, V)
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) # (N,)
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 = backend.np.log(backend.np.clip(p[rows, labels], 1e-15, 1.0))
497
- loss_data = -backend.np.sum(mask * logp_correct) / n_real
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
- # grad = p.copy()
505
- # grad[rows, labels] -= 1.0
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
- loss_data = -backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / B
528
-
529
- if backend.NO_GRAD: return cls(loss_data)
530
-
531
- loss = cls(loss_data, children=(scores,))
532
-
533
- def _backward():
534
- scores.grad += (p - targets) / B * loss.grad
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
- z = scores.data
541
- z = z - z.max(axis=-1, keepdims=True)
542
- e = backend.np.exp(z)
543
- p = e / e.sum(axis=-1, keepdims=True)
544
- N = numpy.prod(scores.shape[:-1]) # B for 2D, B*T for 3D
545
- # loss = cls(-backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / N, children=(scores,))
546
- loss_data = -backend.np.sum(targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / N
547
- if backend.NO_GRAD: return cls(loss_data)
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
- @classmethod
556
- def softmax_cross_entropy_masked(cls, scores, targets, mask):
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
- p = e / e.sum(axis=1, keepdims=True)
485
+ denom = e.sum(axis=-1, keepdims=True)
486
+ p = e / denom
487
+ logp = z - backend.np.log(denom)
561
488
 
562
- mask = mask.reshape(-1, 1)
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
- # loss = cls(-backend.np.sum(mask * targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / n_real, children=(scores,))
567
- loss_data = -backend.np.sum(mask * targets * backend.np.log(backend.np.clip(p, 1e-15, 1.0))) / n_real
568
-
569
- if backend.NO_GRAD: return cls(loss_data)
570
-
571
- loss = cls(loss_data, children=(scores,))
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
- grad = (p - targets) / n_real
575
- grad = grad * mask
576
- scores.grad += grad * loss.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cogforge-engine"
7
- version = "2.1.2"
7
+ version = "2.1.3"
8
8
  authors = [
9
9
  { name="Avik Majumder", email="avikmjd2@gmail.com" },
10
10
  ]
File without changes