"""
Generate every README diagram as a Mermaid .mmd file, then render to PNG with the hand-drawn
look. Reproducible: edit a graph below, re-run, re-render.
Render step (run from this folder, needs Node.js for npx):
for f in *.mmd; do
npx -y @mermaid-js/mermaid-cli -i "$f" -o "${f%.mmd}.png" -c mmdc.json -p puppeteer.json -b white -s 3
done
"""
import os
OUT = os.path.dirname(os.path.abspath(__file__))
# Shared palette: soft fill + strong border per role (data, store, proc, model, rl, loss, ckpt, eval, out, op).
PALETTE = """
classDef data fill:#cdeccd,stroke:#2e7d32,stroke-width:2px,color:#143d1a;
classDef store fill:#cfeee9,stroke:#1f9e8f,stroke-width:2px,color:#0c3a34;
classDef proc fill:#cfe3fb,stroke:#1565c0,stroke-width:2px,color:#0d2c52;
classDef model fill:#ffe8a3,stroke:#d48806,stroke-width:2px,color:#5a3d00;
classDef rl fill:#ffd9b3,stroke:#e67e22,stroke-width:2px,color:#6b3500;
classDef loss fill:#ffd4d4,stroke:#c0392b,stroke-width:2px,color:#5c1212;
classDef ckpt fill:#ececec,stroke:#666666,stroke-width:2px,color:#222222;
classDef eval fill:#e7d6fb,stroke:#8e44ad,stroke-width:2px,color:#3d1a5a;
classDef out fill:#e7d6fb,stroke:#7b3fbf,stroke-width:2px,color:#3d1a5a;
classDef op fill:#ffffff,stroke:#888888,stroke-width:2px,color:#333333;
"""
DIAGRAMS = {
"00_pipeline": """flowchart LR
PILE([The Pile
raw text]):::data --> PRE{{Pretrain
base ~400M}}:::model
PRE --> SFT{{SFT
instruct}}:::model
SFT --> RM{{Reward Model
Bradley-Terry}}:::rl
SFT --> DPO{{DPO / ORPO / KTO}}:::rl
SFT --> GRPO{{GRPO / RLVR}}:::rl
RM -->|reward signal| PPO{{PPO}}:::rl
PPO --> EVAL([GSM8K eval
+ chat]):::eval
DPO --> EVAL
GRPO --> EVAL
""",
"01_data": """flowchart LR
A1([Pretrain: The Pile]):::data --> A2[tiktoken r50k_base
stream + encode]:::proc --> A3[(pile_train.h5
flat int32 tokens)]:::store
B1([SFT: Alpaca · Dolly · GSM8K]):::data --> B2[chat template
+ mask the prompt]:::proc --> B3[(sft_packed.h5
tokens + loss_mask)]:::store
C1([Prefs: HH-RLHF · UltraFeedback]):::data --> C2[split prompt /
chosen / rejected]:::proc --> C3[(preferences.jsonl)]:::store
D1([RL: GSM8K · arithmetic]):::data --> D2[keep the gold answer]:::proc --> D3[(rl_prompts.jsonl)]:::store
""",
"02_tokenization": """flowchart LR
T1["raw text
'Once upon a time'"]:::data --> T2["tiktoken
r50k_base"]:::proc
T2 --> T3["token ids
[7454, 2402, 257, 640]"]:::store
T3 --> T4["append <|endoftext|>
(id 50256)"]:::proc
T4 --> T5["fixed windows of
context_length + 1"]:::store
T5 --> T6["x = window[:-1]
y = window[1:]"]:::out
""",
"03_mlp": """flowchart LR
M1["x (B, T, n_embed)"]:::data --> M2["Linear
n_embed -> 4 · n_embed"]:::proc
M2 --> M3["ReLU"]:::proc
M3 --> M4["Linear
4 · n_embed -> n_embed"]:::proc
M4 --> M5["out (B, T, n_embed)"]:::out
""",
"04_attention_head": """flowchart LR
X["x : token vectors
(B, T, C)"]:::data --> Q["Q = x Wq"]:::proc
X --> K["K = x Wk"]:::proc
X --> V["V = x Wv"]:::proc
Q --> S["scores = Q Kᵀ / √d"]:::rl
K --> S
S --> M["causal mask
+ softmax"]:::rl
M --> O["weights · V"]:::rl
V --> O
O --> H["head output
(B, T, head_size)"]:::out
""",
"05_multi_head_attention": """flowchart LR
X["x (B, T, C)"]:::data --> H1["head 1"]:::proc
X --> H2["head 2"]:::proc
X --> H3["... (n_head heads)"]:::proc
H1 --> CC["concat heads
(B, T, C)"]:::rl
H2 --> CC
H3 --> CC
CC --> PJ["output projection
Linear C -> C"]:::proc
PJ --> HO["multi-head output"]:::out
""",
"06_transformer_block": """flowchart LR
X["x"]:::data --> LN1["LayerNorm"]:::proc --> AT["Multi-Head Attention"]:::rl --> A1(("+")):::op
X --> A1
A1 --> LN2["LayerNorm"]:::proc --> ML["MLP"]:::proc --> A2(("+")):::op
A1 --> A2
A2 --> BO["block output"]:::out
""",
"07_transformer": """flowchart LR
I["token ids (B, T)"]:::data --> TE["token embedding"]:::proc
I --> PE["position embedding"]:::proc
TE --> SUM(("+")):::op
PE --> SUM
SUM --> BLK["N_BLOCKS ×
Transformer Block"]:::model
BLK --> LNF["final LayerNorm"]:::proc
LNF --> LMH["lm_head
Linear -> vocab_size"]:::proc
LMH --> LOG["logits (B, T, vocab)"]:::out
LOG -. targets present .-> CE["cross-entropy loss"]:::loss
""",
"08_training_loop": """flowchart LR
D[(pile_train.h5)]:::store --> IT["get_batch_iterator
random windows"]:::proc --> FW["forward
(bf16 autocast)"]:::model --> CE["cross-entropy"]:::loss --> BW["backward
× grad_accum"]:::proc --> CL["clip grad norm 1.0"]:::proc --> ST["AdamW step
cosine LR + warmup"]:::model --> CK[(checkpoint)]:::ckpt
ST -. next step .-> IT
""",
"09_sft": """flowchart LR
S0[(sft_packed.h5
tokens + loss_mask)]:::store --> S1["Transformer forward"]:::model --> S2["shift: predict t+1"]:::proc --> S3["per-token cross-entropy"]:::loss
S0 --> S4["loss_mask = 1 only on
assistant tokens"]:::proc --> S5["mean over masked tokens"]:::loss
S3 --> S5 --> S6["AdamW step"]:::model
""",
"10_reward_model": """flowchart LR
R0["prompt + chosen / rejected"]:::data --> R1["SFT backbone
forward_hidden"]:::model --> R2["take last real token"]:::proc --> R3["reward head
Linear -> 1"]:::proc
R3 --> R4["r_chosen , r_rejected"]:::rl --> R5["Bradley-Terry loss
-log σ(r_chosen - r_rejected)"]:::loss --> R6["AdamW step"]:::model
""",
"11_dpo": """flowchart LR
P0["chosen / rejected pair"]:::data --> P1["policy (trainable)
sequence log-probs"]:::model
P0 --> P2["reference (frozen SFT)
sequence log-probs"]:::ckpt
P1 --> P3["DPO loss
-log σ(β · Δ)"]:::loss
P2 --> P3 --> P4["AdamW step"]:::model
""",
"12_ppo": """flowchart LR
Q0([GSM8K prompts]):::data --> Q1["rollout
generate + log-probs"]:::model --> Q2["score
verifier or reward model"]:::rl --> Q3["+ per-token KL to ref"]:::rl --> Q4["GAE
advantages + returns"]:::proc --> Q5["clipped policy + value
(K epochs)"]:::model --> Q6[(ppo.pt)]:::ckpt
Q5 -. sync old policy .-> Q1
""",
"13_grpo": """flowchart LR
G0([prompt]):::data --> G1["sample a GROUP
of G answers"]:::model --> G2["verifier reward
per answer"]:::rl --> G3["group advantage
(r - mean) / std"]:::proc --> G4["clipped surrogate
+ k3 KL to ref"]:::loss --> G5["policy update"]:::model
G5 -. next prompt .-> G0
""",
"14_evaluation": """flowchart LR
E0[(stage checkpoint)]:::ckpt --> E1["greedy generate"]:::model --> E2["extract_answer
<answer>N</answer>"]:::proc --> E3{"== gold?"}:::rl --> E4["GSM8K accuracy
Base -> SFT -> ... -> GRPO"]:::eval
""",
"15_inference": """flowchart LR
C0[(any checkpoint)]:::ckpt --> C1["load dims from cfg"]:::proc --> C2{"chat or raw?"}:::rl
C2 -->|instruct| C3["wrap in chat template"]:::proc
C2 -->|base| C4["raw prefix"]:::proc
C3 --> C5["generate
temperature / top-p / greedy"]:::model
C4 --> C5 --> C6["decode -> reply"]:::out
""",
}
def main():
for name, body in DIAGRAMS.items():
path = os.path.join(OUT, name + ".mmd")
with open(path, "w", encoding="utf-8") as f:
f.write(body.rstrip() + "\n" + PALETTE)
print("wrote", name + ".mmd")
print("total:", len(DIAGRAMS), "diagrams")
if __name__ == "__main__":
main()