feat(vault): add first visual-question exemplar + authoring guide

Seeds the visuals/ directory with a reference pattern so future
authors have a concrete template to clone.

Exemplar: Ring AllReduce on 4 ranks (cloud track, L3, apply/analyze).
- SVG follows .claude/rules/svg-style.md: 680×460 viewBox, Helvetica
  Neue, compute-blue ranks, orthogonal ring arrows, 10-px grid.
- YAML wires the visual block (kind=svg, path=cloud-visual-001.svg,
  alt + caption) and pairs it with a matching question: 'Using the
  diagram, calculate the total time to complete the full AllReduce.'
- The realistic_solution walks through 2(N−1)/N × data / bw and
  explains the common failure mode (forgetting the all-gather phase).
  Napkin math shows the step-time decomposition.

AUTHORING.md: the when/how/why guide for future visual questions.
- When a visual earns its place — three criteria (ask requires the
  diagram, encodes info text cannot, static suffices).
- High-value candidate topics — ring/tree AllReduce, roofline, KV
  cache, pipeline bubbles, memory hierarchy, MCU memory maps,
  systolic arrays, attention, MoE.
- Step-by-step authoring workflow pointing at the book's SVG style
  guide for the visual system — readers already know the visual
  vocabulary from the book, so consistency transfers.
- Accessibility requirements (non-negotiable): alt is enforced by
  the Pydantic schema, colour never the sole semantic channel, text
  in <text> elements not paths, WCAG AA contrast.
- Explicit anti-patterns: no inline SVG in YAML, no mermaid for
  non-graph content, no decorative effects, no label duplication of
  scenario prose.
This commit is contained in:
Vijay Janapa Reddi
2026-04-24 16:10:54 -04:00
parent cb0b7ea305
commit 1898fe8c9a
3 changed files with 235 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
schema_version: '1.0'
id: cloud-visual-001
track: cloud
level: L3
zone: analyze
topic: data-parallelism
competency_area: parallelism
bloom_level: apply
phase: training
title: Ring AllReduce Latency on 4 Ranks
scenario: >-
Four GPU ranks are participating in a Ring AllReduce to aggregate gradients
after a training step. The gradient tensor is 400 MB in total, split evenly
into 4 chunks of 100 MB each. Every inter-rank link runs at 200 GB/s. The
ring passes chunks clockwise: each rank sends one chunk to the next rank
while receiving a chunk from the previous rank.
question: "Using the diagram, calculate the total time to complete the full AllReduce across all 4 ranks, and justify the formula you used."
visual:
kind: svg
path: cloud-visual-001.svg
alt: >-
Four rank boxes arranged in a square: Rank 0 top-left, Rank 1 top-right,
Rank 2 bottom-right, Rank 3 bottom-left. Clockwise arrows between adjacent
ranks are labelled chunk 0 through chunk 3, each carrying 100 MB at
200 GB/s. A legend below notes total gradient 400 MB, N equals 4 ranks,
and that the full AllReduce comprises reduce-scatter plus all-gather.
caption: Ring AllReduce on 4 ranks passing 100 MB chunks clockwise at 200 GB/s.
details:
realistic_solution: >-
Total AllReduce time = 2(N1)/N × data / bw = 2(3)/4 × 400 MB / 200 GB/s
= 1.5 × 0.002 s = 3 ms. The Ring AllReduce has two phases: a reduce-scatter
(each rank ends up owning a reduced shard) and an all-gather (each rank
collects the full reduced tensor). Each phase completes in (N1)/N × data
/ bw = 1.5 ms, yielding 3 ms total. The key insight is that only one chunk
(data/N = 100 MB) traverses each link per step, so wall-time is set by
the number of steps (2(N1) = 6) times step-time (100 MB / 200 GB/s = 0.5 ms).
common_mistake: >-
Dividing the full 400 MB gradient by 200 GB/s to get 2 ms misses that
Ring AllReduce requires 2(N1) = 6 sequential steps, not one. Alternatively,
some learners compute (N1)/N × data / bw = 1.5 ms and forget to double
it for the all-gather phase, giving 1.5 ms instead of 3 ms. The 2(N1)/N
factor captures both the parallelism benefit (only 1/N of data per step)
and the phase count.
napkin_math: >-
data = 400 MB, N = 4, bw = 200 GB/s. Step time = (data/N)/bw =
100 MB / 200 GB/s = 0.5 ms. Reduce-scatter takes N1 = 3 steps = 1.5 ms.
All-gather also takes N1 = 3 steps = 1.5 ms. Total = 3 ms.
status: draft
provenance: llm-draft
expected_time_minutes: 5
requires_explanation: false
validated: false

View File

@@ -0,0 +1,115 @@
# Authoring visual questions
StaffML questions can optionally attach a diagram. The practice page renders
it between the scenario prose and the answer textarea, and the `question`
field always stays sticky at the top — reading flow is **scenario →
diagram → answer**.
Use visuals sparingly. A good visual earns its place; a bad one is
noise that slows the reader down.
## When a visual earns its place
A visual earns its place when **all three** hold:
1. **The ask requires reading the diagram.** If the question can be
answered from the scenario alone, the visual is decorative — omit it.
2. **The visual encodes information that text cannot.** Topology,
memory layouts, roofline curves, pipeline timelines, dataflow —
things where spatial structure *is* the payload.
3. **A static image suffices.** Animation, interactivity, and
multi-step reveals are out of scope.
### High-value candidate topics
Target these first; each one repeats across many chain positions, so
one good diagram earns its keep across dozens of questions.
- Ring / tree AllReduce topologies (show the ring, ask for latency)
- Parameter-server vs. AllReduce dataflow
- Roofline diagrams with workload points plotted
- KV-cache growth vs. sequence length
- Pipeline parallelism bubble / Gantt charts
- Memory hierarchy: HBM + SRAM + host DRAM + disk
- TinyML MCU memory map (flash + SRAM + model footprint)
- Systolic array dataflow (weight-stationary vs. output-stationary)
- Attention computation graph (Q·Kᵀ then softmax then ·V)
- MoE all-to-all shuffle topology
## Authoring workflow
1. **Draft the SVG** following `.claude/rules/svg-style.md` (the book's
SVG system). Non-negotiables:
- `viewBox="0 0 680 460"` default (widen only when content demands).
- `font-family="Helvetica Neue, Helvetica, Arial, sans-serif"` on
the root `<svg>`.
- Semantic palette — compute blue `#cfe2f3`/`#4a90c4`, data green
`#d4edda`/`#3d9e5a`, routing orange `#fdebd0`/`#c87b2a`, error
red `#f9d6d5`/`#c44`, MIT red accent `#a31f34`.
- Orthogonal routing (no diagonals except genuine topology diagrams).
- Arrows anchor at box edges, route around obstacles with ≥10px
clearance.
- Integer coordinates on a 10-px grid.
- Text in `<text>` elements (not paths) — selectable + accessible.
2. **Save** to `interviews/vault/visuals/<track>/<id>.svg` where
`<track>` matches the question's track (cloud, edge, mobile, tinyml,
global) and `<id>` matches the question's YAML id. Bare filename
only — no subdirectories, no path traversal.
3. **Add the `visual:` block** to the matching YAML:
```yaml
visual:
kind: svg
path: <id>.svg
alt: >-
Full accessibility description — objective, concrete, ≤400
chars. Describe what the diagram SHOWS, not why it matters.
caption: "Short caption rendered below the figure. ≤120 chars. Optional."
```
4. **Build** — `vault build --legacy-json`. This copies the SVG to
`interviews/staffml/public/question-visuals/<track>/` and surfaces
the `visual` metadata in the corpus bundle.
5. **Preview** at `/practice?q=<id>` on the dev server.
## Reference exemplar
`cloud/cloud-visual-001.yaml` + `cloud/cloud-visual-001.svg` — Ring
AllReduce on 4 ranks. Diagram shows the ring topology + chunk labels +
bandwidth annotations; scenario gives concrete numbers; question asks
for the total AllReduce time. Solution walks through the
2(N1)/N × data / bw formula. Copy this pattern.
## Accessibility requirements (non-negotiable)
- **`alt` is required** and is enforced by the schema. A visual
without alt fails Pydantic validation; `vault build` will reject it.
- Colour is never the sole semantic channel — pair colour with label
text, line style, or shape. A colour-blind reader must still be able
to parse the diagram.
- Text in SVG `<text>` elements (not baked into paths) so it's
selectable and screen-reader friendly.
- WCAG AA contrast on any label over a coloured fill: `#333` text on
`#cfe2f3` compute-blue passes; `#999` text on the same fill fails.
## Anti-patterns to reject
- Inline `<svg>` markup inside YAML. The schema's `path` field is a
bare filename — path traversal is rejected. Use the file-reference
pattern.
- Mermaid source for anything other than simple node-edge graphs. The
renderer MVP supports SVG only; `kind: mermaid` is reserved for a
future inline-text path and is currently a no-op.
- Label text that duplicates the scenario prose. The diagram should
encode *additional* information, not restate the scenario.
- Decorative gradients, fake 3-D, beveled edges, drop shadows. Every
mark earns its place (Tufte's data-ink principle — see the SVG
style guide for the full chart-junk policy).
- Icons or emoji in the SVG. Use neutral shapes + labels.
- Watermarks, signatures, tool branding. The caption handles attribution.
## Build artifacts
The directory `interviews/staffml/public/question-visuals/` is a
build output (written by `copy_visual_assets` during `vault build
--legacy-json`). Source files live under `interviews/vault/visuals/`.
Do not edit the build output directly — changes will be overwritten
on the next build.

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 680 460"
font-family="Helvetica Neue, Helvetica, Arial, sans-serif">
<rect width="680" height="460" fill="#fff" rx="4"/>
<defs>
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0,0 L8,3 L0,6 Z" fill="#555"/>
</marker>
</defs>
<!-- ===== Title ===== -->
<text x="340" y="32" text-anchor="middle" font-size="13" font-weight="700" fill="#333">
Ring AllReduce — 4 ranks, one reduce-scatter step
</text>
<!-- ===== Ranks (compute blue) ===== -->
<!-- Rank 0: top-left -->
<rect x="110" y="110" width="140" height="80" rx="4"
fill="#cfe2f3" stroke="#4a90c4" stroke-width="1.5"/>
<text x="180" y="156" text-anchor="middle" font-size="12" font-weight="700" fill="#333">Rank 0</text>
<!-- Rank 1: top-right -->
<rect x="430" y="110" width="140" height="80" rx="4"
fill="#cfe2f3" stroke="#4a90c4" stroke-width="1.5"/>
<text x="500" y="156" text-anchor="middle" font-size="12" font-weight="700" fill="#333">Rank 1</text>
<!-- Rank 2: bottom-right -->
<rect x="430" y="290" width="140" height="80" rx="4"
fill="#cfe2f3" stroke="#4a90c4" stroke-width="1.5"/>
<text x="500" y="336" text-anchor="middle" font-size="12" font-weight="700" fill="#333">Rank 2</text>
<!-- Rank 3: bottom-left -->
<rect x="110" y="290" width="140" height="80" rx="4"
fill="#cfe2f3" stroke="#4a90c4" stroke-width="1.5"/>
<text x="180" y="336" text-anchor="middle" font-size="12" font-weight="700" fill="#333">Rank 3</text>
<!-- ===== Ring arrows (clockwise) ===== -->
<!-- R0 → R1 (top edge) -->
<line x1="252" y1="150" x2="428" y2="150" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="340" y="140" text-anchor="middle" font-size="10" font-weight="700" fill="#333">chunk 0</text>
<text x="340" y="172" text-anchor="middle" font-size="9" fill="#555">100 MB @ 200 GB/s</text>
<!-- R1 → R2 (right edge) -->
<line x1="500" y1="192" x2="500" y2="288" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="514" y="234" text-anchor="start" font-size="10" font-weight="700" fill="#333">chunk 1</text>
<text x="514" y="250" text-anchor="start" font-size="9" fill="#555">100 MB @ 200 GB/s</text>
<!-- R2 → R3 (bottom edge) -->
<line x1="428" y1="330" x2="252" y2="330" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="340" y="322" text-anchor="middle" font-size="10" font-weight="700" fill="#333">chunk 2</text>
<text x="340" y="352" text-anchor="middle" font-size="9" fill="#555">100 MB @ 200 GB/s</text>
<!-- R3 → R0 (left edge) -->
<line x1="180" y1="288" x2="180" y2="192" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="166" y="234" text-anchor="end" font-size="10" font-weight="700" fill="#333">chunk 3</text>
<text x="166" y="250" text-anchor="end" font-size="9" fill="#555">100 MB @ 200 GB/s</text>
<!-- ===== Context legend ===== -->
<rect x="190" y="395" width="300" height="40" rx="5"
fill="#f7f7f7" stroke="#bbb" stroke-width="1"/>
<text x="340" y="412" text-anchor="middle" font-size="10" font-weight="700" fill="#333">
Total gradient: 400 MB · N = 4 ranks · 200 GB/s links
</text>
<text x="340" y="426" text-anchor="middle" font-size="9" fill="#555">
Full AllReduce = reduce-scatter (N1 steps) + all-gather (N1 steps)
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB