Release-hardening and transformer-era release of TinyTorch, audited as one
tree. This commit brings tinytorch/ and the TinyTorch CI workflows to the
state of the audit/tinytorch-release branch, where the individual changes
are recorded commit by commit.
- Milestones: transformer-era parts (TinyGPT, sequence routing, TinyCopilot,
conversational Q&A), Milestone 06 "MLPerf to Generative Serving (2018)",
Milestone 07 kernels; per-part recording; NumPy-referenced losses and
causality, attention-content and position probes; try-it prompts after a
pass.
- Framework: dropout eval mode, target gradients, float32 stays float32,
layer validation, LoRA adapters.
- tito: loopback login callback with a state nonce, consented progress sync,
TLS-verified update check, Ctrl-C after a pass keeps the pass.
- Grading: graded tests compare values against NumPy references; tests that
could not fail now check something.
- CI: the publish preflight runs its tests and ships the commit it tested;
workflow inputs pass through env; third-party actions pinned.
- Guide and book: read line by line against the code; every demo is a real
recording.
See tinytorch/CHANGELOG.md, section 0.2.0.
"28,106 stars on GitHub" is injected by inject_stats.py from
site/config/stats-cache.json at render time, so it is right when the site
is built and never again.
The main site handles this: site/index.qmd re-fetches on every page load,
both from stats.json (refreshed to gh-pages every six hours) and directly
from the GitHub API, with the comment that the star count "should track
the repo to the second rather than to the last scheduled refresh".
This site got the first half only. It emits the same
data-stat="stars" hook on two elements of the landing page but shipped
nothing to hydrate them, and it never reads stats.json at all, so a
CDN-cached page served the build-time number indefinitely. The committed
cache was generated 2026-09-14 and read 28,106; the API returned 28,258
today, 152 behind after three days.
The star count is public, so this fetches it directly rather than waiting
on a refresh cycle this site does not consume. On rate limit, offline or
a blocked request the rendered value stays, so the page always shows a
real number.
Verified in a browser: both elements read 28,106 at first paint and
28,258 after hydration.
Three separate overclaims, each checked against the code that would have
to produce the number.
1. --tier challenge emits the full reference solution.
solutions.py::solution_role_action strips a region under the challenge
tier only when it carries role="challenge". src/ contains zero such
annotations (236 regions: 189 scaffold, 6 core, 41 unannotated), so the
tier is a no-op. apply_release_tier output for challenge is byte-identical
to instructor, verified directly.
The code matches its design in NBGRADER_RELEASE_TIERS.md and the
assertions in tests/cli/test_release_tiers.py, so this is unfinished
annotation work, not a code defect, and the tier semantics are left
alone. But an instructor running the command we document gets a fully
solved "challenge" assignment, so `tito nbgrader generate --tier
challenge` now refuses when no region in the requested modules is marked
role="challenge", and says what to annotate.
Note for the author: INSTRUCTOR.md described challenge as "All 236 Blocks
(full implementation from scratch)", which is a different design from
NBGRADER_RELEASE_TIERS.md's "keep for student baseline, strip for
challenge". Those two need reconciling before the tier is annotated.
2. The student tier strips 47 regions, not 41.
Unannotated regions default to core (solutions.py::solution_role), so the
student tier strips 41 unannotated plus 6 explicit role="core" = 47, and
leaves the 189 role="scaffold" regions solved. The documented 41/195 split
is 236 minus 41, which silently reclassifies those 6 core regions as
scaffold. Corrected in getting-started.qmd, tito/overview.qmd, and
INSTRUCTOR.md.
3. The home page advertised optimization gains the milestone disclaims.
index.qmd promised "8-16x smaller, 12-40x faster". milestones/06_2018_mlperf
says compression and speedup "are outcomes to measure, not benefits the
script assumes", that it "does not certify a deployment target or
guarantee a fixed compression/speed" ratio, and that a small workload "may
become slower". The repo's own quantization ceiling is 4x. Reworded, along
with the same claims in ml-timeline.js, whose module range 14-18 was also
wrong (14-19).
Also: the CIFAR-10 milestone target was published as "75%+" in seven
places. milestones/04_1998_cnn states 65-75%, its README expects 70%+, and
02_lecun_cifar10.py passes at >= 65. Corrected to the stated range.
And `tito system jupyter` is documented as starting JupyterLab; it starts
the classic notebook unless given --lab.
Found while driving the site with Playwright to check the TITO CLI page.
ml-timeline.js appended #ml-timeline-popup to document.body on every page
load, whether or not the page had a timeline. Its closed state is
`display: flex; opacity: 0` with no pointer-events rule, so it sat as a
full-viewport, z-index 1000, hit-testable box over the content:
elementFromPoint at the middle of the page returned the overlay rather
than the article. Every click on 52 of the 62 pages went into it,
including the home page. The script set display:none only when *closing*
a popup, so a page became usable after opening and dismissing one.
Fixed in two layers, because either alone would have prevented it:
- style.scss gives the closed overlay pointer-events: none, restored to
auto on .active. Invisible now means non-interactive.
- ml-timeline.js returns early when a page has no .ml-timeline-content,
so the overlay is built only on the one page with a timeline.
Verified the feature still works on index.html: six cards, click opens
the popup, Escape closes it, page is clickable afterwards.
Also reworks "Commands by User Role". Three code blocks in a 3-up card
grid left each command roughly 220px, which wrapped
`tito nbgrader generate 01 --tier student` between the flag and its
value. A panel-tabset gives each role the full column width; every line
in all three tabs now fits without wrapping, verified by measuring
rendered height against line count.
And `tito module complete 03 --verbose` in troubleshooting.qmd exits 2,
because --verbose is global and belongs before the subcommand.
guide/tools/check-documented-commands.py now walks tito's real argparse
tree and checks every `tito ...` invocation in the .qmd sources for
unknown subcommands, unknown flags, and invalid values for flags that
declare choices. It re-execs under the project venv when the calling
interpreter lacks tito's dependencies, and turns a skip into a failure
under CI so the gate cannot quietly stop running. Wired into pre-commit.