max_model_size_kb: 32 could never be satisfied by the reference model it
gates: AnomalyDetectionAE (640/784 -> 128x4 -> 8 -> 128x4 -> 640) has
~266K parameters -- matching this same file's own `params: 0.3M` -- which
is ~1.03MB at FP32 and ~260KB even fully INT8-quantized. A submitter who
trains exactly this reference model and reports the true model size would
automatically fail the size gate regardless of reconstruction quality.
Corrected the budget to 300KB, consistent with the file's own stated 0.3M
parameter count at 1 byte/param (INT8), the standard deployment target for
this suite's "microcontroller" framing.
run_decode() measures prefill_latency with a standalone model() call, then
calls model.generate() separately without handing it the prefill's KV
cache. generate() therefore redoes its own prefill pass internally before
decoding, so generation_latency includes a second, redundant prefill on top
of the actual decode work -- understating output_tokens_per_sec and
inflating inter_token_latency_s, worse for longer contexts since prefill
cost grows with context length while decode_tokens stays small (8-16 by
default).
time_to_first_token_s also incorrectly added a full decode-step latency on
top of prefill_latency instead of just reporting prefill time.
Fixed by subtracting the already-measured prefill_latency from
generation_latency before computing decode-only metrics, and reporting
time_to_first_token_s as prefill_latency alone.
The subscribe modal (shared across the book, site, labs, kits, and
mlsysim subsites) was a plain div overlay:
- No role="dialog", aria-modal, or accessible name, so screen readers
never announced it as a dialog and could wander into the page
behind the overlay.
- No focus trap: Tab walked out of the modal into the visually hidden
background page.
- Focus was never returned to the triggering element on close, dropping
keyboard users at the top of the document.
- Open/close animations played regardless of prefers-reduced-motion.
Changes to the canonical shared/scripts/subscribe-modal.js:
- role="dialog" aria-modal="true" aria-labelledby on the container,
with an id on the existing title.
- Tab/Shift+Tab now cycle within the modal's visible, enabled controls
(computed per keypress, so the trap also works on the post-submit
success view).
- The opener element is saved on openModal() and refocused on close.
- Animations are disabled under prefers-reduced-motion: reduce.
Mirrors regenerated with shared/scripts/sync-mirrors.sh (CI drift
check). Escape-to-close and overlay-click behavior unchanged.
_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).
_process_zip() ran `mlperf verify` as the only anti-cheat check, then caught
FileNotFoundError and silently passed through to grading the submission's
self-reported JSON metrics verbatim with "Cheating": "NO" -- meaning any
environment without the mlperf CLI on PATH (the common case for a bare
grading checkout) accepted hand-forged submissions with no verification at
all.
Reproduced: built a zip with forged metrics (accuracy 0.99, throughput
99999) and confirmed _process_zip() returned Status: VALIDATED,
Cheating: NO. Now it returns Status: VERIFY_UNAVAILABLE, Cheating:
UNVERIFIED and logs the failure instead of grading blind.
Bring Vol1 display code blocks into Black display-70 compliance so the CI pre-commit pass makes no modifications. Tighten Listing 6.7 by putting the attention-scores comment on its own line above the single-line computation and dropping the redundant Core computational pattern comment. Trim the Figure 7.6 caption to pull utilization onto the previous line and remove a one-word widow. Shorten the training Step 3 comment to avoid a wrapped line.
Builds on the PR #1957 merge (Zeljko's Vol1 layout), otherwise byte-for-byte his version:
- Remove the end-of-chapter research-questions callouts from all chapters.
- ml_ops correction-cascade figure: keep Zeljko's Model A-D chain TikZ drawing,
and rewrite the caption, alt-text, and lead-in to describe the chain (they
carried a stale 'timeline' description that no longer matched the figure),
plus an operations-impact line.
- Standardize the matplotlib figure font on a bundled TeX Gyre Heros (a free,
redistributable Helvetica-metric clone) registered at runtime in
book/tools/figures/style.py, so figures render with a real bold identically
on macOS, Linux, and CI instead of falling back to DejaVu Sans or an
unreachable macOS .ttc bold face.
ml_ops "Correction Cascades" reused data_engineering's Sambasivan data-cascade
timeline under a wrong label (and no source credit). Replace it with a
purpose-built SVG showing the actual mechanism: a Model A->B->C->D correction
chain where an upstream change cascades downstream and forces every dependent
correction to be redone. Rewrite the lead-in, caption, and alt text to match,
matching the section's own "model A's outputs influence model B's training
data" framing.
fault_tolerance and robust_ai both rendered the same Jeff Dean SDC keynote
photo, and fault_tolerance captioned it as a "shuffle and merge database"
block diagram it never showed. Remove robust_ai's duplicate (folding the
Dean citation into prose) and correct fault_tolerance's caption + lead-in to
describe the photo that is actually there.
Byte-duplicate image files that are referenced in zero .qmd files across the
book, mostly _-prefixed shelved hyphen/underscore name variants plus a few
unused placeholder and old cover exports. No rendered figure changes.
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.
_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.
In the HTTP-error handler, error_body was only assigned inside the try
block (e.read().decode('utf-8')). If that read/decode itself raised
(network hiccup mid-read, or a non-UTF-8 error body from the server), the
broad except (json.JSONDecodeError, Exception) still ran and referenced
error_body unconditionally, raising UnboundLocalError -- a confusing crash
in the middle of tito module complete's auto-sync step, instead of the
clean "Upload failed" message this code is trying to produce.
Initialize error_body = "" before the try, and only print it in the
except block when it's non-empty, so a read/decode failure degrades to
"nothing to print" instead of crashing.
Verified with two scenarios: (1) the read/decode call raising before
error_body is ever set -- old code crashed with UnboundLocalError, new
code handles it silently; (2) read succeeding but json.loads failing
(the normal case this except block exists for) -- both old and new code
correctly print the error body.
_ensure_module_notebook() regenerates modules/<name>/<name>.ipynb from
src/<name>.py whenever the source file's mtime is newer -- but that exact
path is the same notebook tito module start/resume open for students to
edit. A routine `git pull` that bumps a source file's mtime, followed by
an instructor running `tito nbgrader generate --all` on a shared checkout,
silently overwrote any student notebook whose file mtime was older, with
no backup and no warning beyond a benign "Source file is newer; converting"
message.
Fixed by never writing to the student-facing notebook path once it
exists: if it needs regenerating from a newer source, stage the fresh
conversion into a private .nbgrader_staging/ cache instead, and read from
there for nbgrader's own assignment staging. First-time creation (no
notebook yet) and the already-up-to-date case are unaffected.
Verified with three scenarios against the real _ensure_module_notebook
(jupytext call mocked, real file I/O): (1) notebook missing -> created
directly at the notebook path as before; (2) notebook up to date -> no
conversion happens at all, matching prior behavior; (3) notebook exists
but source is newer -> notebook content is left untouched, fresh content
is staged separately instead.
Three defects in the All-Reduce Rhythm mini-game:
1. Gradient chunks never moved. tween() was called with a two-element
array as the target and ["x","y"] as props, so the multi-prop
branch assigned x/y onto the array itself instead of chunk.position.
Chunks sat frozen at the source GPU for the whole beat, then burst
at the destination — the ring-transfer visual (the point of the
game) never rendered. Now uses tween(chunk, ["position.x",
"position.y"], ...) like moe.mjs, and cancels the tween before
destroying the chunk so the final tick can't touch a destroyed
object.
2. Tap highlight was a no-op. The box was filled with COL.gpu and the
'flash' set tint to 0xffffff — the identity tint — then 'reset' it
to the same value. Pixi tints can only darken, so the box is now
filled white and tinted down to COL.gpu at rest; the white tint on
tap genuinely flashes it, and the reset restores COL.gpu.
3. No cleanup. Unlike sibling games (cluster.mjs, pipeline.mjs), the
module registered an anonymous global keydown listener and returned
no destroy(), stacking dead handlers and leaking a WebGL context on
any remount without a page reload. The handler is now named and a
destroy() matching the sibling convention removes it and destroys
the Pixi app.
Rendering at rest is unchanged (white fill x COL.gpu tint equals the
previous flat fill). No scoring or timing logic touched.
sidebar-auto-collapse.js observed document.body (subtree) and scheduled
a fresh fixQuizNumbering() pass on every DOM mutation with no debounce.
Each pass ran a full-document querySelectorAll over all quiz callouts
and logged per element, so mutation bursts (Bootstrap collapses,
tooltips, math typesetting, SocratiQ) queued dozens of overlapping
full-page scans for the life of every book page. The pass also mutated
textContent, re-triggering the observer it runs under.
Changes:
- Mark processed callouts with data-quiz-number-fixed and exclude them
from the selector, so repeat passes match nothing and are near-free.
Callouts whose title element is not rendered yet stay unmarked, so
the staggered retry passes still pick them up.
- Coalesce mutation bursts into a single debounced pass (one re-armed
timer instead of one timer per mutation).
- Drop per-element console.log calls from fixQuizNumbering() and
setNavbarActiveState() hot paths.
No behavior change: same numbering transform, same retry schedule,
dynamically injected callouts are still processed.
updateIncorrectQuestions() writes incorrect quiz answers to the
'ongoingIncorrectQuestions' store (defined in db_configs_one.js) under
the fixed id 'current', but getIncorrectQuestions() read from a
non-existent 'ongoing-incorrect-questions' store and passed no key.
Opening a transaction on an undefined store throws NotFoundError, so
retrieval could never succeed.
Incorrect questions are recorded on every quiz submission today; this
makes that data actually retrievable and unblocks re-enabling the
'study past incorrect questions' feature (initiateStudyBtn, currently
commented out in index.js).
No bundle rebuild needed: the disabled feature is tree-shaken out of
book/quarto/tools/scripts/socratiQ/bundle.js, so output is unchanged.