mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-07-21 00:23:30 -05:00
260 lines
12 KiB
Markdown
260 lines
12 KiB
Markdown
# 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:
|
||
|
||
1. **Header comment** — Context (section/callout ID), Goal, Show, and How.
|
||
2. **Class name** — Scenario-specific (`Gpt4ClusterMtbf`), not generic (`Calc`).
|
||
3. **Four blocks** (when applicable): LOAD → EXECUTE → GUARD → OUTPUT.
|
||
4. **Output values** — Formatted strings (`*_str`) via typed helpers such as `fmt_qty()`,
|
||
`fmt_usd()`, `fmt_percent()`, and `fmt_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 semantic `mult` token, for example `speedup_mult_str` or
|
||
`speedup_range_mult_str`.
|
||
|
||
Example shape:
|
||
|
||
````markdown
|
||
```{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`, and
|
||
`mlsysim.physics.calc_*` for architecture formulas. Reserve `mlsysim.core.units`
|
||
for physics/units only (`HOURS_PER_DAY`, `BYTES_FP16`, latency stack).
|
||
- Put `#| echo: false` as the **first line** after ` ```{python}` (required by
|
||
`book-check-code`).
|
||
- Use **`fmt_int(round(x))`** for computed integers; **`precision=0`** only 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: `TrainingDimensions` spanning forward-pass
|
||
prose and a wave-quantization table hundreds of lines apart). **Exception:**
|
||
`# │ Scope: chapter-anchor` when 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_str` without
|
||
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.py`
|
||
symbols (`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 sources` enforces 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-literals` flags 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:
|
||
|
||
1. Split the mega-class at callout boundaries.
|
||
2. Rename classes to match the narrative beat.
|
||
3. Inline duplicated constants from `mlsysim` where it reduces drift.
|
||
4. Re-run `fmt/audit_prose.py` and `./book/binder check math --scope canonical` on 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 validation
|
||
- `book/tools/audit/book_check_registry_sources.py` — legacy alias / constant import gate
|
||
- `mlsysim/tests/test_constants_allowlist.py` — CI lock on `constants.py` surface
|
||
|
||
## Audit runbook (last-minute cleanup)
|
||
|
||
Use this pass before chapter sign-off or pre-push `/precheck`.
|
||
|
||
### Phase 0 — Inventory
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
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 pinned `unit=`
|
||
- **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_str` not `top_k_str` when K is a dimension
|
||
|
||
### Phase 3 — Prose preview (after each fix batch)
|
||
|
||
```bash
|
||
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):
|
||
|
||
```bash
|
||
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/`.
|
||
|
||
```bash
|
||
./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):
|
||
|
||
```bash
|
||
./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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
./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:
|
||
|
||
```python
|
||
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) vs `BrakingDistance` / `EdgeLatencyDistance` (open m + prose “meters”).
|
||
- **`fmt_length` helper** — optional typed formatter (pinned `unit=` or auto m/km) if the pattern repeats; see P5 in `lego_audit_signoff.md`.
|