204 Commits

Author SHA1 Message Date
Rocky
e027d8d1ee fix(tinytorch): unguarded accuracy_retention division crashes on zero baseline (#1954)
_calculate_improvements() guards latency/memory/energy division against
opt_metrics[metric] <= 0 with a documented "fallback to 1.0", but
accuracy_retention = opt_metrics['accuracy'] / base_metrics['accuracy']
had no such guard on the denominator. A baseline model with 0.0 accuracy
(a broken/failing baseline -- exactly the kind of input a benchmarking
suite should handle gracefully) raises ZeroDivisionError and crashes the
whole comparison, while the sibling metrics computed two lines above
degrade gracefully.

Added the same guard pattern already used for the other three metrics.
The function's own docstring already promised "Handle division by zero
with fallback to 1.0" as a general rule -- this fix makes the accuracy
branch actually honor it.

Verified: the old code raises ZeroDivisionError for
base_metrics={'accuracy': 0.0}; the fixed code returns
accuracy_retention=1.0 instead, and the normal (non-zero) case is
unaffected (0.6/0.5 = 1.2, unchanged).
2026-07-15 11:47:23 +02:00
Vijay Janapa Reddi
74a2b82cca Merge pull request #1953 from Shashank-Tripathi-07/fix/memoization-cached-generation-missing-self-attention
fix(tinytorch): cached generation step excludes current token from self-attention
2026-07-10 23:42:38 +02:00
Rocky
49033c73f4 fix(tinytorch): capstone latency divisions crash on coarse timer resolution
benchmark_model()'s throughput_samples_per_sec (float(1000 / avg_latency))
and generate_submission()'s speedup (float(baseline_latency /
optimized_latency)) both divide by a latency measured with time.time()
around a single tiny forward pass, with no zero-guard. time.time() has
coarse resolution on Windows (commonly ~15.6ms); for a small model, a
fast forward pass can legitimately measure 0.0 elapsed time across all
runs, making avg_latency (and therefore optimized_latency downstream)
exactly 0.0 and raising ZeroDivisionError.

An existing test (line ~1347) asserts throughput_samples_per_sec > 0, and
another (line ~1564) asserts speedup > 0, so the fix floors each
denominator at a small epsilon (1e-6) rather than returning 0.0 or None --
both of which would fail those assertions and would also misrepresent an
immeasurably-fast result as zero throughput.

Verified: reproduced ZeroDivisionError for avg_latency=0.0 and
optimized_latency=0.0 with the old expressions; confirmed the floored
versions return large-but-finite positive numbers instead, and that
normal (non-zero) latency values are unaffected.
2026-07-08 01:48:44 +05:30
Rocky
267a53476f fix(tinytorch): cached generation step excludes current token from self-attention
_cached_generation_step() writes the new token's K/V into the cache via
update(), then calls cache_obj.get() and uses its result directly as
K_all/V_all for the attention computation. But KVCache.get() is designed
(and tested in test_unit_kvcache) to return only tokens already made
visible by advance() -- exactly NOT the token just written by update(),
since advance() is meant to run once per token after all layers finish.
So the new token's own K/V was never included in its own attention
computation, and on the very first generation step (cache empty, nothing
advanced yet), cache.get() returns a zero-length sequence, crashing the
matmul with "zero-size array to reduction operation maximum which has no
identity" -- cached generation was broken from the first token onward.

Fixed by concatenating the current step's own K_heads/V_heads (already
computed, no extra work needed) onto cache.get()'s history, instead of
using the cache's history alone.

Verified with a stub attention module (identity projections) against the
real KVCache: reproduced the crash with the old logic on the first
token (zero-length K/V from cache.get()), and confirmed the fixed
function produces the correct self-attention output ([1,0,0,0], the
token attending fully to itself) instead of crashing.
2026-07-08 01:43:26 +05:30
Vijay Janapa Reddi
435760b5a9 fix(tinytorch): set requires_grad via attribute in broadcast-gradient test
Module 06's test_unit_broadcast_gradients ran Tensor(data, requires_grad=True)
before enable_autograd() upgrades the constructor to accept it, so the exported
notebook cell raised TypeError. Match the sibling test_unit_function_classes
convention and set requires_grad as an attribute instead; the constructor stays
minimal by design. Fixes #1893.
2026-07-02 18:33:00 +02:00
Vijay Janapa Reddi
1748cbe8e0 fix(tinytorch): single backslashes in SGD diagrams under raw string
The PR converted the SGD markdown cell to a raw string (r""") but kept \\
in the redrawn ASCII art. In a raw string \\ is two literal backslashes, so
the loss-surface, oscillation, and momentum diagrams rendered with doubled
backslashes in the generated notebook (correct-looking in the .py source).
Normalize the art to single backslashes, matching the foundations cell.
2026-06-26 10:36:29 -04:00
Vedant Joshi
6ba9e9c9d5 tinytorch modules 07 and 08 diagram fixes
(cherry picked from commit 7f44ee1a27)
2026-06-26 10:28:00 -04:00
github-actions[bot]
72d48c5f96 🌐 TinyTorch website update: Release-quality audit for the AI engineering textbook 2026-06-22 17:48:22 +00:00
Vedant Joshi
293b4fc119 feat(convolutions): add autograd backward for AvgPool2d
AvgPool2d returned a tensor with no _grad_fn, so average pooling had no
gradient path (MaxPool2d and Conv2d both wire theirs). Add AvgPool2dBackward,
which distributes each output gradient equally (1/kernel_area) across its
window and accumulates overlaps, padding the gradient buffer with zeros to
match the forward pass. Wire it into AvgPool2d.forward.

From #1885, taking the AvgPool backward feature only: singular name to match
the *Backward convention, zero (not -inf) padding, and verified by numeric
gradient check. The PR diagram edits are not included.
2026-06-21 22:26:58 -04:00
Rocky
ecee1841ea fix(activations): numerically stable sigmoid without overflow warnings
The naive 1/(1+exp(-x)) overflows exp for large negative x and the unit
test hid the resulting RuntimeWarning by suppressing the whole category.
Use the piecewise-stable form (each branch keeps its exponent <= 0) under
np.errstate, since np.where evaluates both branches and the discarded one
can still overflow harmlessly. Drop the test-wide warning suppressor.

Based on #1870; uses errstate so the piecewise form is actually silent.
2026-06-21 22:26:57 -04:00
Rocky
acc31e411f fix(quantization): recover constant tensors with |value| > 127
quantize_int8 set scale=1.0 for a constant tensor and clamped zero_point
to the INT8 range, so for any constant |c| > 127 the zero_point saturated
and dequantization silently returned a wrong value (c=500 recovered as 128).
For |c| > 127 use zero_point = +-1 and scale = |c| so
(0 - zero_point) * scale = c holds exactly. Adds a test exercising that branch.

Reapplies the fix from #1874 on dev with a corrected scale (the PR used
scale = -c/(-zero_point), which flips the sign and recovers -c).
2026-06-21 20:54:20 -04:00
Rocky
a30b36cd76 fix(attention): preserve seq_q and seq_k in MultiHeadAttention mask reshape
The 3-D mask reshape used seq_len for both the query and key dimensions,
raising ValueError for cross-attention (seq_q != seq_k) and only working
when seq_q == seq_k. Read both dimensions and reshape to
(batch, 1, seq_q, seq_k) so the mask broadcasts across heads correctly.

Extracted from #1875 (branch was based on main; reapplied on dev).
2026-06-21 11:32:03 -04:00
Rocky
a9609d6475 fix Dropout to use the module-level seeded rng instead of global np.random (#1869)
The module declares rng = np.random.default_rng(7) at the top so that all
stochastic operations are reproducible for a given seed. Dropout ignored this
and called np.random.random(shape), which draws from the global unseeded state.
Masks were different on every run even when the module seed was set, making
Dropout unit tests non-reproducible and causing hard-to-debug numerical
differences between training runs.

Changed to rng.random(shape) so Dropout participates in the same reproducible
stream as every other random operation in the module.
2026-06-16 18:58:38 -04:00
Rocky
0a2f02830c remove misleading super().__init__() calls from Conv2d, MaxPool2d and AvgPool2d (#1868)
All three classes are declared with no base class, so super().__init__()
resolves to object.__init__(), a silent no-op. The calls made these look like
they inherited from Layer (as Linear does in 03_layers) when they do not.
Removing them makes the code honest: these are standalone modules that
implement parameters() directly. The right long-term fix is to export Layer
from tinytorch.core and wire these classes into that hierarchy, but that
requires a separate refactor across multiple modules.
2026-06-16 18:58:34 -04:00
Rocky
aa7fb4fd4c fix transpose test to use a non-symmetric tensor that can catch wrong axis swaps (#1873)
The old test called transpose(0, 2) on a (2, 2, 2) tensor and only checked
that the output shape was (2, 2, 2). Because all three dims are equal, any axis
swap produces the same shape, so the test would have passed even if transpose
was completely broken and returned the input unchanged.

Switched to a (2, 3, 4) tensor where all three dimension sizes are distinct.
Now transpose(0, 2) must produce (4, 3, 2), a shape only achievable by swapping
exactly axis 0 and axis 2. Also added a data-value check verifying that element
[i,j,k] in the original appears at [k,j,i] in the transposed result, catching
implementations that swap the wrong pair of axes.
2026-06-16 18:58:31 -04:00
Rocky
f693e63f81 fix reshape -1 to check divisibility before inferring the dimension (#1872)
When the shape contains -1, the inferred size was computed with integer
division (self.size // known_size). If the total size is not evenly divisible
by the known dimensions, floor division silently produces a truncated value
and the element-count check catches it only after the wrong number has already
been placed in new_shape, producing a confusing error.

For example, Tensor([1..7]).reshape(-1, 3) should fail immediately because 7
is not divisible by 3, but the old code inferred 2 (7//3), then raised a
generic element-count mismatch that did not explain the actual problem.

Added a divisibility check before the division so the error fires at the right
place with a clear message, matching PyTorch behavior.
2026-06-16 18:58:28 -04:00
Rocky
cd4eab9a62 fix tracked_mul passing raw scalar to MulBackward instead of a Tensor (#1871)
tracked_mul() wraps a scalar other into other_tensor = Tensor(other) and
calls _ensure_grad_attrs() on it correctly, but then passed the original raw
scalar to MulBackward instead of other_tensor. MulBackward.apply() pulls both
operands out of saved_tensors and calls .data on them, so any scalar multiply
in a tracked computation graph crashed on the backward pass with AttributeError
because int and float have no .data attribute.

Fixed by passing other_tensor to MulBackward so both operands in saved_tensors
are always Tensor instances. The forward computation still uses the original
scalar unchanged.
2026-06-16 18:58:25 -04:00
Rocky
63aa703fa1 fix Conv2dBackward return tuple to match saved_tensors length (#1866)
When bias=None, only x and weight are saved in saved_tensors (2 entries),
but apply() always returned a 3-tuple of grad_input, grad_weight, grad_bias.
The backward walk zips saved_tensors with the return tuple so the extra
grad_bias (None) was silently dropped by zip truncation. This made bias-free
Conv2d appear to train correctly while the tuple length mismatch left the
code one refactor away from corrupting gradients silently.

Fixed by returning only (grad_input, grad_weight) when bias is None so
the return tuple always has the same length as saved_tensors.
2026-06-16 18:58:22 -04:00
Vijay Janapa Reddi
ccbc069426 fix(tito): restore nbgrader workflow 2026-06-02 13:40:42 -04:00
Vijay Janapa Reddi
fd9fea0e90 fix(tinytorch): repair release regressions 2026-06-02 13:40:09 -04:00
Vijay Janapa Reddi
f55a9a1061 Merge pull request #1785 from Shashank-Tripathi-07/fix/tinytorch-modules-10-20-audit
Fix GPT causal mask convention in module 13 to match module 12's _apply_mask expectation (1-mask convention).
2026-05-27 08:26:22 -04:00
Rocky
b9ce5b2522 fix(tinytorch): fix GPT causal mask convention in module 13
GPT._create_causal_mask was returning an additive -inf mask
(upper-triangular with -inf, 0 for allowed positions) but
_apply_mask in module 12 expects a binary mask (1=allow, 0=block)
and computes adder = (1 - mask) * MASK_VALUE.

With the old mask, every allowed position (value 0) got masked by
(1-0)*(-1e9) = -1e9, blocking ALL attention -- both past and future
tokens. Only an infinite value in the mask produced the correct
-inf addend for blocked positions.

Fix: use np.tril(np.ones(...)) which is the binary lower-triangular
convention that _apply_mask expects.
2026-05-18 20:03:05 +05:30
Rocky
b4fae46e18 fix(tinytorch): correct MatmulBackward gradients for 1D vector inputs
When B is a 1D vector, numpy's .T is a no-op (returns the same 1D
array), so the previous code called np.matmul(grad_output, b.data)
which either failed on mismatched shapes or silently computed a dot
product instead of the outer product needed for grad_A.

For A(m,k) @ b(k,) -> out(m,):
  grad_A = np.outer(grad_output, b)  (m,k)  -- was broken
  grad_b = a.T @ grad_output         (k,)   -- was broken for 1D a

Fixes the symmetric case (1D a) with grad_B = np.outer(a, grad_output).
2026-05-18 18:54:50 +05:30
Vijay Janapa Reddi
d042517735 revert(tinytorch): restore vertical Dataset Interface diagram annotations
The PR's inline ← arrow form crams annotations into the line ends and
clutters the diagram box. The original vertical layout keeps the box
clean and places the explanations below where they're visually separate.
2026-05-14 10:27:46 -04:00
Vijay Janapa Reddi
ef9d715eca revert(tinytorch): drop unnecessary tilde escapes in 05_dataloader prose
Pandoc subscript requires no spaces between paired tildes. The two tildes
on this line are separated by sentence text with spaces, so the bare ~
renders as literal. The backslash escape was defensive but unnecessary.
2026-05-14 10:27:33 -04:00
Vedant Joshi
90dba896d2 tinytorch modules clarity (#1738)
* separated into different blocks for easier readability

* fix(tinytorch): typos and better diagram representations
2026-05-14 10:24:59 -04:00
Rocky
4e096cc19c fix(tinytorch): reuse Sigmoid class in GELU solution
Use the existing Sigmoid activation inside the GELU solution so the implementation matches the module's composition-based teaching intent.
2026-05-10 17:56:42 -04:00
Vijay Janapa Reddi
99249d00b3 fix(tinytorch): restore seeded Linear init, scope unseeding to perceptron demo
#1617 unseeded the module-level rng in tinytorch/src/03_layers/03_layers.py
to make the perceptron milestone produce different weights on every run.
But Linear.__init__ reads weight init from that rng, so the change made
Linear init nondeterministic across the entire codebase. The integration
test tests/integration/test_training_flow.py::test_deep_network_gradient_chain
specifically depends on Linear having stable init; empirical sweep over
5000 random draws shows the test fails 27.5% of the time under unseeded
init, which is why the Windows CI run failed in #1617.

Restore the seed=7 default in 03_layers.py and instead rebind layers.rng
to an unseeded RNG locally inside the perceptron milestone, mirroring
the pattern already used by the XOR milestone (#1618). This keeps the
perceptron's "different weights every run" promise without breaking
unrelated tests.
2026-05-01 13:18:43 -04:00
Vijay Janapa Reddi
6220345038 fix(tinytorch): perceptron weights deterministic across runs (#1611)
The module-level `rng = np.random.default_rng(7)` at the top of
`src/03_layers/03_layers.py` was reused for every `Linear(...)` weight
init, so every run produced identical weights. Milestone 1's UI text
("No random seed - each run will be different!") contradicted this, and
the issue reporter rightly expected weights to vary.

Drop the seed in two places:

- `src/03_layers/03_layers.py`: the module-level `rng` is now unseeded.
  Local seeded RNGs inside test/demo blocks (around lines 829, 848, 981)
  are unchanged - those want determinism for reproducible self-tests.
- `milestones/01_1958_perceptron/01_rosenblatt_forward.py`: the data-gen
  RNG is also unseeded so the cluster points vary too, matching the
  on-screen claim.

Verified by running the milestone twice: cluster positions, predictions,
and accuracy now differ between runs (e.g., 100% "got lucky" vs 0%
"random guessing"). `Linear(...)` weights also differ across fresh
constructions within a single run.

Relates to #1611
2026-04-30 18:35:50 -04:00
Rocky
c19109f9a2 fix(tinytorch): LayerNorm gamma/beta missing requires_grad=True
gamma and beta were created as Tensor(np.ones/zeros(n)) with no
requires_grad flag, defaulting to False after enable_autograd() patches
Tensor.__init__. The _LayerNormBackward.apply() guards on
beta.requires_grad and gamma.requires_grad, so gradients were silently
never computed for either parameter -- LayerNorm could not learn its
scale and shift during training.

Fix: pass requires_grad=True at construction so the backward pass
computes grad_gamma and grad_beta correctly.

Also remove the manual param.requires_grad = True workaround from
test_layernorm_gradient_flow() which was masking the bug.
2026-04-23 23:19:22 +05:30
Rocky
18428a6efa fix(tinytorch): constant tensor quantized to all-zeros, losing original value (#1444)
quantize_int8() had a special case for tensors where all elements are the
same value (max == min). It set scale=1.0 and zero_point=0 and returned an
all-zeros INT8 tensor. On dequantization: (0 - 0) * 1.0 = 0.0 for every
element, so the reconstructed tensor is all zeros regardless of what the
original constant was. A bias layer initialised to a non-zero constant, or
any weight tensor that happens to be uniform, is silently zeroed out after
a quantize/dequantize roundtrip.

Root cause: the contributor correctly handled the zero-range edge case to
avoid division by zero when computing scale, but forgot that zero_point must
encode the constant so that dequantization can recover it.

Fix: compute zero_point = round(-min_val) (clamped to [-128, 127]) so that
the invariant (0 - zero_point) * scale = min_val holds for constant tensors.

Also tightens the unit test: the old test only asserted scale_const == 1.0
and never checked that the value survived the roundtrip, which is why this
went undetected. New test explicitly dequantizes and asserts recovery of both
positive and negative constants.
2026-04-22 18:52:07 -04:00
Vijay Janapa Reddi
78dd6c1a92 chore(tinytorch): delete 20 stale src/*/ABOUT.md duplicates
Audit (parent commit): every tinytorch/src/NN_xxx/ABOUT.md is a stale
duplicate of tinytorch/quarto/modules/NN_xxx.qmd, with the QMD strictly
newer/richer (each QMD has additional sections like Your TinyTorch,
PyTorch comparison, MLPerf section that ABOUT.md is missing).

Drift confirms ABOUT.md is unmaintained: e.g., 12_attention/ABOUT.md
still says "GPT-3 training (4x inference)" while the QMD was corrected
to 5x. Single-source-of-truth in quarto/modules/ stops the recurrence.

The retired Jupyter Book site (tinytorch/site-legacy/) was the only
historical consumer; it is deleted in the next commit.

Also deleted:
  - tools/dev/fix_about_titles.py     -- one-shot ABOUT.md title fixer
  - tools/dev/fix_mermaid_diagrams.py -- one-shot ABOUT.md mermaid tweaker
Both operated on the now-deleted ABOUT.md files and have no other use.

Updated tinytorch/README.md so the documented repo tree no longer
shows ABOUT.md under each module folder, and repointed a stale
"setup guide" link from site/getting-started.md to quarto/getting-started.qmd.
2026-04-22 15:53:35 -04:00
Rocky
d2c20f9902 fix(tinytorch): regression models report wrong accuracy in Trainer.evaluate
The condition guarding the accuracy branch was len(outputs.data.shape) > 1,
intended to detect multi-class classification. A single-output regression
model (shape (N,1)) also satisfies this, so it entered the argmax path where
argmax of a single column is always 0. For any regression task evaluate()
silently returned meaningless accuracy instead of 0.0, with no error raised.

The root assumption -- 2D outputs implies classification -- breaks for the
common (N,1) regression case. Fix: add outputs.data.shape[-1] > 1 so only
outputs with more than one logit enter the classification branch.

Also tightens the unit test: the existing test used Linear(2,1)+MSELoss but
only checked isinstance(accuracy, float), which passed even when the bug
fired. New test asserts regression accuracy == 0.0 and adds a separate
classification sub-case (Linear(2,3)) to verify the argmax path still works.
2026-04-22 06:44:21 +05:30
Vijay Janapa Reddi
0a9a2e9028 Merge pull request #1392 from Shashank-Tripathi-07/feat/tensor-pytorch-compat-api
test(tinytorch): PyTorch-compat test coverage for Tensor API additions
2026-04-20 19:58:07 -04:00
Vijay Janapa Reddi
96f70ec4b5 fix(tinytorch): fix GELU gradient mismatch and float32 test precision
Two root causes for the 20 failing gradient correctness tests:

1. GELU forward/backward approximation mismatch: forward used
   sigmoid(1.702*x) but backward computed derivative of the tanh
   approximation. Now both use the sigmoid form consistently.

2. Finite-difference tests used float64 arithmetic but Tensor stores
   float32, causing precision loss in numerical gradients. Tests now
   use float32 throughout with appropriate tolerances.

Also fixed BCE and composed gradient tests that created disconnected
Tensors from .data copies, breaking the autograd graph.
2026-04-18 15:26:44 -04:00
Rocky
4de0f7164c feat(tinytorch): add view(), masked_fill(), and Tensor stacking to src
view() and masked_fill() were present in the exported core/tensor.py
(landed via #1391) but missing from the source file, causing the
TestTensorPyTorchCompat tests to fail CI with AttributeError.

Syncs src/01_tensor/01_tensor.py with the exported package:
- Tensor([t1, t2]) stacking via np.stack in __init__
- view(*shape) as a reshape alias
- masked_fill(mask, value) for transformer attention patterns
- contiguous() upgraded to np.ascontiguousarray for C-order memory layout
2026-04-18 05:14:47 +05:30
Vijay Janapa Reddi
3bbf2eb15b feat(tinytorch): add ndim, numel(), contiguous() to Tensor (closes #1298)
Add PyTorch-compatible utility methods so students can follow PyTorch
tutorials without hitting AttributeError. All three are thin wrappers
over existing attributes — zero risk to downstream modules.
2026-04-17 18:54:51 -04:00
Vijay Janapa Reddi
242093e407 fix(tinytorch): wire Tanh into enable_autograd()
Tanh was the only activation not patched by enable_autograd(). calling Tanh()(x).backward() silently left x.grad as None because the forward never attached a computation graph.

changes mirror the existing tracked_sigmoid_forward pattern:
- new TanhBackward(Function) class using saved output, since d/dx tanh(x) = 1 - tanh(x)^2 can be computed from the forward result
- new tracked_tanh_forward in enable_autograd() that attaches TanhBackward when input requires_grad
- Tanh added to the activations import and patch install lines
- regression test in test_06_autograd_progressive.py asserts tanh grad values at known points

closes #1341
2026-04-16 13:41:32 -04:00
Rocky
bc501a25b5 fix(tinytorch): guard requires_grad loop against non-Tensor params in trainer_init
The requires_grad loop now checks isinstance(param, Tensor) before setting
the attribute, so test stubs or models that return raw numpy arrays from
parameters() do not crash with AttributeError.
2026-04-16 17:39:42 +05:30
Rocky
6e8ef6616a fix(tinytorch): ensure model params have requires_grad=True in trainer_init
Linear layers create Tensors with requires_grad=False by default. If the
optimizer hasn't been constructed yet (or Module 07 is incomplete), the
tracked_mse_forward gate fails silently: loss gets no _grad_fn, backward()
returns immediately, and param.grad stays None — causing optimizer.step()
to skip all updates and the test assertion "Parameters should change after
optimizer update" to fail.

Fix: explicitly iterate model.parameters() in trainer_init and set
requires_grad=True, making the Trainer the authoritative owner of gradient
tracking regardless of optimizer setup order.

Fixes #1334
2026-04-16 16:55:03 +05:30
Vijay Janapa Reddi
d30257577c refactor(tinytorch): migrate from legacy np.random to default_rng(7)
Replace np.random.randn/rand/seed with np.random.default_rng(7) across
all 93 source modules, tests, and milestones for reproducible, isolated
random state.
2026-04-03 17:57:51 -04:00
Vijay Janapa Reddi
a66f2c66a6 fix(tinytorch): fix MaxPool2d API mismatch in milestone 04 CIFAR script (#1278)
- Fix pool_size=(2,2) → kernel_size=2, stride=2 in 02_lecun_cifar10.py
- Normalize all Conv2D/MaxPool2D/BatchNorm2D references to lowercase d
  (Conv2d, MaxPool2d, BatchNorm2d) across comments, strings, and ASCII art
- Fix Conv2d error string in 09_convolutions.py source module
- Delete milestones/extras/ directory (drifted copies, 2 placeholders,
  1 broken script — maintenance liability that caused the duplicate bug)
- Add test_milestones_smoke.py: 19 lightweight tests that import every
  milestone script and construct every model class in 0.07s, catching
  API drift without data downloads
2026-03-24 17:24:14 -04:00
Vijay Janapa Reddi
0e798b1f09 fix(tinytorch): clean up token constant refactor from PR #1279
- Revert ASCII diagrams to literal '</w>' and '<UNK>' values for
  pedagogical clarity (diagrams should be concrete, not symbolic)
- Fix incomplete docstring migration where expected outputs still
  used raw string literals while inputs used Tokenizer.TOK_EOW
- Standardize assertion error message interpolation style
2026-03-24 08:52:33 -04:00
Vijay Janapa Reddi
897342d8bd feat(tinytorch): add no_grad() context manager and graph cleanup to autograd
Implement two systems engineering enhancements to Module 06:

- no_grad() context manager with global _GRAD_TRACKING_ENABLED flag,
  checked by all tracked operations to skip graph construction during
  inference. Supports safe nesting and matches PyTorch's API.

- retain_graph=False parameter on backward() that releases _grad_fn
  references after gradient propagation, fixing memory growth across
  training iterations. Matches PyTorch's default behavior.

847/847 tests passing, zero regressions.
2026-03-24 08:49:47 -04:00
Vijay Janapa Reddi
dc45f0f199 style(tinytorch): align M06 _reduce_broadcast_grad with module conventions
- Add missing markdown cell before test_unit_reduce_broadcast_grad
  (every other unit test in the module has one)
- Restructure _reduce_broadcast_grad docstring from Args/Returns/Algorithm
  to TODO/APPROACH/EXAMPLE/HINT to match the module's scaffolding pattern
2026-03-24 08:49:47 -04:00
Vijay Janapa Reddi
743c8df9bd fix(tinytorch): batch 6 medium/low documentation and accuracy fixes
M01: Remove duplicate matmul rows, fix transpose timing O(1),
     "stride reinterpretation" header, deduplicate "statistics"
M02: Fix "sppress" typo, remove false "clipping" claim,
     GELU docstring values to sigmoid approximation
M03: Memory KB values to binary (784/785 KB), FLOPs -> MACs label
M04: Fix analyze_loss_sensitivity hardcoded index (was showing
     prediction=0.98 labeled as 0.5)
M05: Remove nonexistent download_mnist/download_cifar10 imports
M07: SGD momentum formula raw form (not EMA), AdamW prose
     formula to match code (lr * weight_decay)
M08: Memory estimate header 5-6x -> 4-6x with explanation
M13: Remove weight tying hint (not implemented)
M16: Replace "Sequential FORBIDDEN" section with accurate note,
     add T-squared simplification comment to distillation loss
M17: NumPy fusion comment corrected (does not fuse expressions)
2026-03-24 08:49:47 -04:00
Vijay Janapa Reddi
6672a83f1c fix(tinytorch): batch 5 fixes from module audit
M05 DataLoader:
- Standardize channel-format heuristic across RandomHorizontalFlip,
  RandomCrop, and _pad_image to all use shape[0] for channels-first
  detection (was inconsistent: flip used shape[-1], crop used shape[0])

M12 Attention:
- _apply_mask now uses Tensor API (ones_like - mask) instead of raw
  mask.data access, preserving autograd gradient flow
- MultiHeadAttention.forward uses mask.reshape() instead of
  mask.data.reshape() for same reason

M18 Memoization:
- Fix prefill: process prompt tokens one-at-a-time instead of as a
  batch, so KV cache is populated through the normal generation path.
  Previously cache slots remained zeros after prefill.
2026-03-24 08:49:46 -04:00
Vijay Janapa Reddi
25349ca426 fix(tinytorch): batch 4 fixes from module audit
M05 DataLoader:
- Fix next-module pointer: "Module 09" -> "Module 06 (Autograd)"

M09 Convolutions:
- Fix analyze_convolution_complexity: read kernel_size from index [2]
  not [1] (5x5 config was silently running 3x3)
- Fix SimpleCNN hint: 32->16->8 (2 pools), 2048 features (not 512)

M10 Tokenization:
- Fix decode error message: [0,1,2] -> [1,2,3] to match docstring

M16 Compression:
- Fix weight aliasing in analyze_compression_techniques: deep copy
  weights instead of aliasing, so magnitude prune no longer corrupts
  structured prune input

M17 Acceleration:
- Fix fused_gelu docstring: exact GELU -> tanh approximation values
- Fix arithmetic intensity: 0.33 -> 0.083 FLOPs/byte
- Fix matmul AI formula: 2N/3 -> N/6 (was missing 4 bytes/float32)
- Fix decision table: remove phantom "Mixed Prec" column from header

M19 Benchmarking:
- Fix reflection Q3: reference BenchmarkSuite params instead of
  nonexistent optimize_benchmark_configuration()
2026-03-24 08:49:46 -04:00
Vijay Janapa Reddi
be245aef22 fix(tinytorch): batch 3 critical fixes from module audit
M03 Layers:
- Sequential.forward/.__call__ now accept training= parameter and
  pass it through to Dropout layers (eval mode was broken)

M12 Attention:
- Fix memory multiplier: was 7x (double-counted), now 4x incremental
  (1x forward + 1x grad + 2x optimizer). Aligned ABOUT.md to match.
- Fix mask docstring: True/False -> 1/0 to match implementation

M14 Profiling:
- Add __enter__/__exit__ to Profiler (6 progressive tests used
  context manager but class had no protocol support)
- Fix FLOP prose formula: remove batch_size to match implementation

M15 Quantization:
- Fix "symmetric" -> "asymmetric (min-max)" terminology throughout
- Fix Magic Formula prose to match code: q = x/s + zp (not (x-zp)/s)
- Add parentheses to zero-point hint formula for clarity
2026-03-24 08:49:46 -04:00
Vijay Janapa Reddi
6fdfe203cb fix(tinytorch): batch 2 critical fixes from module audit
M06 Autograd:
- SumBackward now stores axis/keepdims and expands grad_output
  correctly for axis-reduced sums (was producing wrong gradients
  for bias accumulation and axis-specific reductions)

M13 Transformers:
- Convert 8 TransformerBlock calls from positional to keyword args
  (ff_dim=) across test files for M13, M14, M18 — prevents silent
  mlp_ratio interpretation creating 64x larger hidden layers
- Fix parameter count: 4*embed_dim^2 -> 12*embed_dim^2, 11.4M -> 29.7M

M18 Memoization:
- Add missing 2x factor in KV cache formula (K and V are two tensors)
- Fix GPT-2 37MB -> 72MB, GPT-3 4.7GB -> 18GB
- Fix speedup formula: sum(i^2) -> sum(i), 170x -> ~50x

M01 Tensor:
- Fix analyze_memory_layout transpose text (was still saying
  "changes memory layout" contradicting the earlier fix)
2026-03-24 08:49:46 -04:00