12 KiB
LEGO Cell Contract
Inline {python} cells in MLSysBook chapters follow a LEGO pattern: a small
class computes scenario values once, produces formatted *_str fields, and prose
references them with `{python} Class.field_str`.
This document defines how to author and review LEGO cells. It complements the
fmt/notation audit lane (book/tools/audit/fmt/README.md), the agent verify
playbook (.claude/rules/lego-verify.md), and pre-commit checks
(lego-dead-code, ./book/binder check math --scope canonical).
Core rule
One cell ≈ one narrative anchor — a callout, table, or tight paragraph
cluster. Place the cell immediately above the first {python} reference that
uses its output values.
Chapter-anchor exception: When one scenario's numbers must stay consistent
across a problem callout, a later walkthrough, and a summary bullet, mark the
header with # │ Scope: chapter-anchor and a one-line rationale. Focal verify
then allows multi-section reference span; render and cell-exec gates still apply.
This is not the unrelated mega-class anti-pattern (different output values in
disconnected narratives hundreds of lines apart).
Cell contract
Each LEGO cell should include:
- Header comment — Context (section/callout ID), Goal, Show, and How.
- Class name — Scenario-specific (
Gpt4ClusterMtbf), not generic (Calc). - Four blocks (when applicable): LOAD → EXECUTE → GUARD → OUTPUT.
- Output values — Formatted strings (
*_str) via typed helpers such asfmt_qty(),fmt_usd(),fmt_percent(), andfmt_multiple(). Units and fixed glyphs live in OUTPUT only; prose must not repeat units, percent signs, currency symbols, or multiplier glyphs after`{python} *_str`. Multiplier outputs use a semanticmulttoken, for examplespeedup_mult_strorspeedup_range_mult_str.
Example shape:
```{python}
#| echo: false
# ┌── LEGO ───────────────────────────────────────────────
# │ Goal: A100 ridge point for the latency callout below.
# │ Show: The formatted ridge point consumed by the following prose.
# │ How: Read the registry value and format it for inline prose.
class A100RidgeExample:
ridge = Hardware.Cloud.A100.ridge_fp16
ridge_str = fmt_int(round(ridge.magnitude), commas=False)
```
The A100 ridge is `{python} A100RidgeExample.ridge_str` FLOP/byte.
Do
- Keep ≤ 5–8 output values per cell when possible.
- Use registry paths for shared specs —
Hardware.Cloud.H100,Models.Vision.ResNet50,Systems.Reliability.Gpu.mttf_hours,Literature.Training.MfuHigh, andmlsysim.physics.calc_*for architecture formulas. Reservemlsysim.core.unitsfor physics/units only (HOURS_PER_DAY,BYTES_FP16, latency stack). - Put
#| echo: falseas the first line after```{python}(required bybook-check-code). - Use
fmt_int(round(x))for computed integers;precision=0only when the value is already integer-like at the source.
Don't
- Mega-classes — one class whose output values appear in multiple distant sections
or unrelated callouts (anti-pattern:
TrainingDimensionsspanning forward-pass prose and a wave-quantization table hundreds of lines apart). Exception:# │ Scope: chapter-anchorwhen the same scenario thread intentionally reuses the same output values (KWS case-study targets, a lighthouse profile + takeaway, GPT-3 household-year anchor, build/buy TCO + summary). - Cross-cell reads — a later cell referencing
OtherClass.some_strwithout redefining inputs in the same cell (hidden exec-order dependency). - Duplicate classes — two cells for the same story (e.g. separate table and prose calcs for one latency budget); merge or split by narrative beat, not by output type.
- Kitchen-sink output sections — a cell emits ten fields used once each across the chapter; split by callout instead.
- Legacy constant output names — do not mirror removed
constants.pysymbols (H100_FLOPS_FP16_TENSOR_val_str,GPUS_PER_HOST_str, …). Use scenario-descriptive names (h100_peak_fp16_val_str,dgx_h100_gpus_per_node_str) with registry paths on the RHS../binder check registry --scope sourcesenforces this. - Hardcoded walkthrough operands — in callout Problem / Setup / Step prose that
already uses
{python} Class.field_str, do not type scenario inputs or intermediate numbers (100 GPUs,70B × 2,10×,/365,\$2/GPU-hour). Provide setup inputs, operands, rates, and multipliers from the cell../binder check code --scope lego-prose-literalsflags common cases.
Scope boundary (judgment): The gate targets computational callouts—where a
LEGO cell derives {python} *_str from scenario inputs. Pure narrative
(100--1,000× more expensive than arithmetic), teaching asides, and footnotes
stay literal. If a number is an input to or step in worked math, compute it in the cell.
Review checklist
When editing or auditing a chapter:
| Check | Question |
|---|---|
| Locality | Is the cell within ~50–100 lines of the first ref? |
| Span | Do all output values appear in the same callout / ## section? |
| Coupling | Does any other cell read this class's attributes? |
| Dead code | Does lego-dead-code report unused output values? |
| Walkthrough literals | Does lego-prose-literals pass on touched callouts? |
| Fmt | Do prose preview + canonical audit pass (fmt/ workflow)? |
Migration (existing chapters)
Do not big-bang refactor large chapters. When a file is already open for copyedit, fact-check, or fmt audit:
- Split the mega-class at callout boundaries.
- Rename classes to match the narrative beat.
- Inline duplicated constants from
mlsysimwhere it reduces drift. - Re-run
fmt/audit_prose.pyand./book/binder check math --scope canonicalon that chapter.
Future optional lints (lego-locality, lego-span, lego-cross-ref) may
automate the checklist; until then, apply this contract in review.
Related
book/tools/audit/fmt/README.md— spurious.0/ fmt precision workflow./book/binder check math --scope canonical— static fmt and suffix lint./book/binder check code --scope lego-prose-literals— walkthrough prose must not hardcode computed operands./book/binder check refs --scope inline-python— chapter exec validationbook/tools/audit/book_check_registry_sources.py— legacy alias / constant import gatemlsysim/tests/test_constants_allowlist.py— CI lock onconstants.pysurface
Audit runbook (last-minute cleanup)
Use this pass before chapter sign-off or pre-push /precheck.
Phase 0 — Inventory
export PYTHONPATH=mlsysim
python3 book/tools/audit/lego_focal_verify.py book/quarto/contents/vol1 book/quarto/contents/vol2
python3 book/tools/audit/fmt/audit_fmt_usage.py --root book/quarto/contents
python3 book/tools/audit/fmt/fmt_prose_contract.py --root book/quarto/contents
Rank chapters by focal-verify failures + {python} ref density. Ledger:
book/tools/audit/artifacts/lego_audit_ranked_chapters.md.
Phase 1 — Gates (per chapter or corpus)
CH=book/quarto/contents/vol1/introduction/introduction.qmd
./book/binder check code --scope lego-dead-code --path "$CH"
./book/binder check code --scope lego-prose-units --path "$CH"
python3 book/tools/audit/book_check_lego_prose_units.py "$CH"
python3 book/tools/audit/fmt/fmt_prose_contract.py "$CH"
python3 book/tools/audit/audit_math_canonical.py "$CH"
./book/binder check math --scope canonical --path "$CH"
python3 book/tools/scripts/maintenance/validate_inline_refs.py --path "$CH"
Prose-unit contract (two lanes): lego-prose-units / book_check_lego_prose_units.py
flags domain glyphs (FLOP/byte, TFLOP/s, g/kWh, GB, …) immediately after a
closed `{python} *_str` output. fmt_prose_contract.py flags %, $, scale,
and × duplication. Open fmt() outputs intentionally keep units in prose.
See book/tools/audit/artifacts/lego_closed_prose_audit.md.
Phase 2 — Naming contract
Every *_str output must match its formatter (see .claude/rules/lego-units.md):
- Closed-fixed:
*_w_str,*_gb_per_s_str→ typed fmt with pinnedunit= - Open: generic
*_str→fmt()and prose supplies unit - Scale:
*_b_str→fmt_params(..., scale="B");*_m_str→fmt_count(..., scale="M") - Avoid false tokens:
top_k_neighbors_strnottop_k_strwhen K is a dimension
Phase 3 — Prose preview (after each fix batch)
PYTHONPATH=mlsysim python3 book/tools/audit/fmt/audit_prose.py "$CH" --flagged-only
Precision guard triage (when exec fails with Formatting Precision Error):
| Error | Fix |
|---|---|
formatted as '0' |
Raise precision or change display unit |
not integer-like but precision=0 |
Use precision>=1 |
integer-like but precision=1 … '10.0' |
Use precision=0 or omit precision= |
For fmt_arithmetic_intensity, prefer no explicit precision= (auto:
integer-like → 0, fractional → 1). Pass explicit precision only for pinned
scenario literals (e.g. 300 FLOP/byte). 7.0 FLOP/byte is a smell on exact
integers; 153.0 FLOP/byte is normal for fractional ridge points at
precision 1. Agent copy: .claude/rules/lego-verify.md.
Corpus exec sweep (all chapter QMDs, shared namespace):
python3 book/tools/audit/chapter_html_verify.py --vol1 training # build + full lane
python3 book/tools/audit/chapter_html_verify.py --report
PYTHONPATH=mlsysim python3 book/tools/audit/fmt/audit_lego_html.py
Do not commit truncated book/quarto/config/_quarto-html-vol*.yml after binder
builds — restore from git if the render list shrinks.
Phase 4 — Per-chapter replayable verify (PASS bar)
One chapter at a time: binder HTML build, every LEGO cell, every inline ref in
rendered HTML prose, optional coherence review. Writes a certificate under
book/tools/audit/artifacts/lego_chapter_reports/.
./book/tools/audit/verify_lego_chapter.sh vol1 introduction
./book/tools/audit/verify_lego_chapter.sh vol2 network_fabrics
PASS requires: HTML build clean; cells N/N; rendered prose refs N/N (value
- HTML context); coherence review not
FAIL. Re-run the same command after fixes.
Corpus sweep (sequential, resumable):
./book/tools/audit/run_all_lego_chapters.sh
# failures → book/tools/audit/artifacts/lego_chapter_failures.txt
# progress → book/tools/audit/artifacts/lego_chapter_progress.md
Phase 5 — HTML spot checks
python3 book/tools/audit/fmt/audit_html.py book/quarto/_build/html-audit/vol1/introduction.html
Spot-check certificates for substituted QMD→HTML prose; no literal {python}.
Phase 6 — Capstone
./book/tools/audit/verify_lego_pipeline.sh # or /precheck before push
Sign-off template: book/tools/audit/artifacts/lego_audit_signoff.md.
Follow-up: distance / length outputs (P5)
Some cells still use open fmt() for meters while prose supplies the unit:
distance_str = fmt(distance_m, precision=2, commas=False)
# prose: ... `{python} EdgeLatencyDistance.distance_str` meters ...
This pass deferred a corpus-wide decision on:
- Comma rule — no commas for small decimals (2.8 m, 3.33 m); commas when displaying km or values ≥ 1,000 m.
- Closed vs open — compare
LightLatency.distance_str(fmt_qty, closed km) vsBrakingDistance/EdgeLatencyDistance(open m + prose “meters”). fmt_lengthhelper — optional typed formatter (pinnedunit=or auto m/km) if the pattern repeats; see P5 inlego_audit_signoff.md.