zero2robot · Phase 3 · Advancedch3.8-frontier · probe.py

Chapter 3.8

Reading the Frontier

By the end you can

  1. Read a released checkpoint the way you would read any frontier model, via the four moves this file makes explicit — LOAD a saved state_dict into an architecture skeleton you hold, INSPECT the modules / shapes / parameter counts, HOOK a hidden layer to capture its activations on a forward pass (you cannot edit a released forward()), and PROBE those activations with a linear map — on an accessible tiny checkpoint that stands in for pi0 / GR00T.
  2. Fit a LINEAR PROBE of the fused layer against a KNOWN factor and read its score, then guard the read honestly against the "the probe just recovered an input" trap — task-id is decodable from EVEN a random-init checkpoint (the instruction token is right there in the sequence), so the readout that MEANS something is the COMPUTED quantity (the routed coordinate's value), where training shows up as R^2 ~0.90 trained vs ~0.16 random.
  3. Read the real frontier as a STUDY-tier guided reading (the "you can now read any robot-learning paper" chapter) — openpi / pi0's flow-matching VLA (the ch1.5 action head + the ch1.8 fusion, at scale and pretrained) and GR00T N1's dual-system split (a slow System-2 VLM feeding a fast System-1 action head) — recognizing in each the from-scratch pieces you built across Phases 1 and 3.

See it work

live · P2
read the probe as
a probe recovering an INPUT proves nothing
linear probe of the fused layer · trained vs random-initheld-out split
task-id accuracyrecovers an INPUT · proves nothing
routed-coordinate R²the real read · a COMPUTED value
the four movesload · inspect · hook · probe
  1. 1loadpour the saved state_dict into an architecture skeleton you hold
  2. 2inspectenumerate the modules, their shapes, and their parameter counts
  3. 3hookcapture a hidden layer's activations — you can't edit a released forward()
  4. 4probefit a LINEAR map from those activations to a KNOWN factor, read its score

Identical whether the file on disk is 20 KB or 14 GB — the mechanics are size-invariant.

what's inside the file7,266 params
the fused CLS token attends to
CLSstateBOStaskEOSPAD
reading the real frontierpi0 · GR00T

A released VLA — Physical Intelligence's pi0 (openpi), NVIDIA's GR00T N1 — is these same pieces, scaled and pretrained: a fused VLM token conditioning a flow-matching action head. You read it with the exact four moves above, and you carry the exact caveat: when someone claims a probe shows pi0's fused token "knows the task," ask first — did the probe just recover an input? The read that survives that question is a value the model had to compute.

The task-id probe hitting ≈ 1.0 on both checkpoints is the lesson, not a bug. The honest evidence of learning is the routed-coordinate R² gap (0.90 vs 0.16). Real numbers from probe.py (seed 0, cpu); wall-clock not yet measured; this panel reads with JS off.

Open in Colabsoon

Free-tier notebook — the button goes live when the course repository is published.

Where you are

This is the last chapter of Phase 3, and it asks you to do something you could not have done when you started this course: open a frontier robot policy — the kind a well-funded lab released last month — and read it. Not run it, not fine-tune it. Read it, the way you read a paper whose every idea you have already implemented.

Because you have. The action head inside Physical Intelligence's pi0 is flow matching — you built that in 1.5: learn the velocity of the straight noise→action line, then integrate an ODE to sample. Its conditioning is a vision-language fusion — you built that in 1.7 and 1.8: lay the instruction tokens, the image feature, and the state out as one sequence and let attention fuse them into a single vector. NVIDIA's GR00T N1 splits its policy into two systems running at two rates — a slow one that understands, a fast one that acts — and the interface between them is exactly a fused representation conditioning an action head. There is no piece of these systems you have not written by hand at small scale.

So the skill of this chapter is not building. It is the four mechanical moves that "reading a checkpoint" always comes down to, and they are the same whether the file on disk is twenty kilobytes or fourteen gigabytes:

  1. Load a saved state_dict into an architecture skeleton you hold.
  2. Inspect the modules, their shapes, their parameter counts.
  3. Hook a forward pass to capture a hidden layer's activations — you cannot edit a released model's forward(), so you attach a hook.
  4. Probe those activations with a linear map, and ask: what has this layer actually learned to represent?

The honest part: which checkpoint we probe, and why

We are going to run those four moves, end to end, on a real checkpoint file — but not pi0's, and the reason is worth stating plainly. A real pi0 or GR00T checkpoint is several gigabytes, and it is not something you can pull down and probe offline on the free tier; the moment this chapter required a multi-gigabyte download it would stop being free-tier and stop being reproducible on a plane. Nor do we probe the tiny VLA you trained in 1.8: that chapter saved its metrics but not its weights, so there is nothing on disk to open. (An honest gap. If 1.8 later serializes a checkpoint, this probe reads it unchanged — the code does not care whose checkpoint it is.)

So probe.py trains one tiny language-conditioned policy itself — once, deterministically, in a few hundred steps — saves it, and then reloads it from disk as if someone else had released it. It is a stand-in for a frontier VLA, and it is honest about being one: we do not probe pi0, we probe a checkpoint built to behave like a miniature of one. The task it learns is a miniature of the real thing too — an instruction token selects which coordinate of the state should drive the action, a toy "which skill" router. What transfers from this to pi0 is not the scale. It is the four moves, which are size-invariant.

Move 1 — Load: pour weights into a skeleton

probe.py#checkpointsha256:a67a1d6ac6…
# The checkpoint we will READ. It is a tiny language-conditioned policy: a fused# transformer token (a CLS that reads instruction + state, exactly the 1.8 fusion) into# a small action head. This is the ONE model definition in the file; everything after# treats it as an opaque released checkpoint loaded from disk.class TinyPolicy(nn.Module):    def __init__(self, dim: int, heads: int, hidden: int) -> None:        super().__init__()        self.heads = heads        self.tok_embed = nn.Embedding(VOCAB, dim, padding_idx=PAD)        self.state_proj = nn.Linear(STATE_DIM, dim)        self.cls = nn.Parameter(torch.zeros(1, 1, dim))        self.pos = nn.Parameter(0.02 * torch.randn(1, 2 + MAX_TOK, dim))        self.ln = nn.LayerNorm(dim)        self.qkv = nn.Linear(dim, 3 * dim)        self.proj = nn.Linear(dim, dim)        self.norm = nn.LayerNorm(dim)          # the FUSED layer we will probe        self.head = nn.Sequential(nn.Linear(dim, hidden), nn.SiLU(), nn.Linear(hidden, ACT_DIM))        self.last_attn = None                  # CLS attention over the sequence, kept for inspection     def forward(self, tokens: torch.Tensor, state: torch.Tensor) -> torch.Tensor:        B = tokens.shape[0]        seq = torch.cat([self.cls.expand(B, -1, -1),                         self.state_proj(state)[:, None],                         self.tok_embed(tokens)], dim=1) + self.pos  # [CLS, state, tok_0..3]        h, hd = self.heads, seq.shape[-1] // self.heads        qkv = self.qkv(self.ln(seq)).reshape(B, 2 + MAX_TOK, 3, h, hd).permute(2, 0, 3, 1, 4)        q, k, v = qkv[0], qkv[1], qkv[2]        attn = ((q @ k.transpose(-2, -1)) / math.sqrt(hd)).softmax(dim=-1)        self.last_attn = attn[:, :, 0, :].mean(1).detach()          # (B, L): CLS -> each input token        seq = seq + self.proj((attn @ v).transpose(1, 2).reshape(B, 2 + MAX_TOK, seq.shape[-1]))        return self.head(self.norm(seq[:, 0]))                      # fused CLS -> action  def make_batch(gen: torch.Generator, n: int):    """Synthetic routing task: task t is drawn uniformly; the instruction encodes t; the    action is a FIXED linear function of the state SELECTED by t. To predict it the fused    token must encode which task is active (that is what the probe will later recover)."""    task = torch.randint(0, NUM_TASKS, (n,), generator=gen)    state = torch.randn(n, STATE_DIM, generator=gen)    tokens = torch.full((n, MAX_TOK), PAD, dtype=torch.long)    tokens[:, 0], tokens[:, 1], tokens[:, 2] = BOS, TASK_BASE + task, EOS    routed = state[torch.arange(n), task]                           # the task-selected coordinate    other = state[torch.arange(n), (task + 3) % STATE_DIM]    action = torch.stack([routed, -other], dim=1)                  # deterministic rule; no noise    return tokens, state, action, task, routed  ckpt_path = args.out / "tiny_policy.pt"data_gen = torch.Generator().manual_seed(args.seed)                 # one stream for the training piletokens, state, action, _, _ = make_batch(data_gen, args.train_examples)  def train_and_save() -> None:    """Stand-in for "someone trained and released a checkpoint": train the tiny policy,    then serialize its state_dict + the config that identifies its shape."""    torch.manual_seed(args.seed)                                   # reproducible init    model = TinyPolicy(args.model_dim, args.heads, args.hidden).to(device)    opt = torch.optim.Adam(model.parameters(), lr=args.lr)    tok_d, st_d, act_d = tokens.to(device), state.to(device), action.to(device)    for step in range(args.steps):        loss = ((model(tok_d, st_d) - act_d) ** 2).mean()        opt.zero_grad()        loss.backward()        opt.step()        if args.rerun:            rr.set_time("step", sequence=step)            rr.log("checkpoint/train_mse", rr.Scalars([loss.item()]))        if step % 100 == 0 or step == args.steps - 1:            print(f"train step {step:4d}  mse {loss.item():.5f}")    torch.save({"state_dict": model.state_dict(), "config": CONFIG}, ckpt_path)  # Rebuild the checkpoint only when it is absent or its shape no longer matches this# config (so a smoke run never loads a full run's weights). Then always RE-LOAD from# disk — move (1), the one you make with any released checkpoint you did not train.if not ckpt_path.is_file() or torch.load(ckpt_path, weights_only=False)["config"] != CONFIG:    train_and_save()blob = torch.load(ckpt_path, weights_only=False)policy = TinyPolicy(args.model_dim, args.heads, args.hidden).to(device)   # a fresh, weightless skeletonpolicy.load_state_dict(blob["state_dict"], strict=True)                   # strict=True: every key must matchpolicy.eval()print(f"loaded checkpoint {ckpt_path} ({sum(p.numel() for p in policy.parameters()):,} params); "      f"config {blob['config']}")

A checkpoint on disk is a state_dict: a flat dictionary of named tensors, no code. To read it you supply the code — the TinyPolicy class, the architecture skeleton — and pour the weights in with load_state_dict(..., strict=True). strict earns its keep here: it demands that every parameter name in the file match a slot in your skeleton, which is exactly the check that tells you whether you have the architecture right. This is the daily reality of reading a released model: find its model definition, instantiate it empty, load. A missing or extra key means you have the architecture wrong — and the error tells you which key.

Move 2 — Inspect: read the table of contents

probe.py#inspectsha256:8896960f23…
# Move (2): before you run a released model you read its shape. named_parameters() is the# table of contents — every weight, where it lives, how big. (For a real VLA this is how# you spot the vision tower vs the LM vs the action expert, and where the parameters sit.)total_params = sum(p.numel() for p in policy.parameters())module_params = {name: sum(p.numel() for p in mod.parameters())                 for name, mod in policy.named_children()}print("\narchitecture (module: params):")for name, count in module_params.items():    shapes = ", ".join(f"{n.split('.')[-1]}{tuple(p.shape)}" for n, p in policy.named_parameters()                       if n.startswith(name + "."))    print(f"  {name:12s} {count:7,d}  [{shapes}]")print(f"  {'TOTAL':12s} {total_params:7,d}")

Before you run a model you read its shape. named_parameters() is the table of contents — every weight, where it lives, how big it is. On our stand-in it prints a handful of modules totalling about 7,300 parameters. On a real VLA the very same enumeration is how you find the vision tower, the language model, and the action expert, and how you see where the parameters actually sit — almost always mostly in the pretrained backbone, a sliver in the freshly-trained action head. You cannot read a paper's architecture figure honestly until you have matched it against this table.

Move 3 — Hook: capture what you cannot edit

probe.py#forwardsha256:f412ea13d8…
# Move (3): capture a hidden layer's activations on a FRESH batch. You cannot edit a# released forward(), so you HOOK the layer — here `norm`, whose output is the fused CLS# vector every action is read from. We also keep the CLS attention the block recorded.probe_gen = torch.Generator().manual_seed(args.seed + 100)          # held-out from the training pilep_tokens, p_state, _, p_task, p_routed = make_batch(probe_gen, args.probe_examples) captured: dict[str, torch.Tensor] = {}handle = policy.norm.register_forward_hook(lambda _m, _i, out: captured.__setitem__("fused", out.detach()))with torch.no_grad():    policy(p_tokens.to(device), p_state.to(device))handle.remove()features = captured["fused"].cpu().numpy()                          # (probe_examples, model_dim)cls_attention = policy.last_attn[0].cpu().numpy()                   # how CLS attends to [CLS, state, tok_0..3]print(f"\nhooked policy.norm -> fused features {features.shape}; "      f"CLS attends most to input index {int(cls_attention.argmax())} "      f"(0=CLS, 1=state, 2..5=instruction tokens)")

Here is the constraint that shapes everything about reading someone else's model: you cannot edit its forward(). You did not write it, and even with the source in front of you the clean way to observe an internal activation is not to fork the code — it is to hook it. register_forward_hook attaches a listener to a layer and captures its output the next time the model runs, without touching a line of the model. We hook policy.norm, the fused token every action is read from, and run one forward pass on a fresh batch the checkpoint never trained on. That captured tensor is the object of the whole investigation: whatever the policy "knows" about this input, it knows it here.

Move 4 — Probe, and the trap in probing

probe.py#probesha256:fb7b434635…
# Move (4): a LINEAR probe. Fit a linear map from the frozen features to a KNOWN factor# on a train split, score it on a held-out split. A HIGH score means the layer already# represents that factor linearly — the model "knows" it. We ask two questions: does the# fused token encode WHICH TASK is active (classification accuracy), and does it encode# the ROUTED coordinate's value (regression R^2)? A RANDOM-init checkpoint of the same# shape is the control: any score above it is what TRAINING put there.def linear_probe(feats: np.ndarray, target: np.ndarray, ridge: float, classify: bool) -> float:    n, cut = len(feats), len(feats) // 2                            # deterministic 50/50 split    x = np.concatenate([feats, np.ones((n, 1), np.float64)], axis=1)  # append a bias column    xtr, xte = x[:cut], x[cut:]    ytr_raw, yte_raw = target[:cut], target[cut:]    y = np.eye(NUM_TASKS)[ytr_raw] if classify else ytr_raw.reshape(-1, 1).astype(np.float64)    w = np.linalg.solve(xtr.T @ xtr + ridge * np.eye(x.shape[1]), xtr.T @ y)  # ridge normal equations    pred = xte @ w    if classify:        return float((pred.argmax(1) == yte_raw).mean())           # held-out accuracy    resid = ((yte_raw.reshape(-1, 1) - pred) ** 2).sum()    total = ((yte_raw - yte_raw.mean()) ** 2).sum()    return float(1.0 - resid / total) if total > 0 else 0.0        # held-out R^2  # The random-init control: same architecture, never trained (seed offset so it is a# genuinely different init, the 1.5/1.8 baseline pattern).torch.manual_seed(args.seed + 7)control = TinyPolicy(args.model_dim, args.heads, args.hidden).to(device)control.eval()c_captured: dict[str, torch.Tensor] = {}c_handle = control.norm.register_forward_hook(lambda _m, _i, out: c_captured.__setitem__("fused", out.detach()))with torch.no_grad():    control(p_tokens.to(device), p_state.to(device))c_handle.remove()control_features = c_captured["fused"].cpu().numpy() task_np, routed_np = p_task.numpy(), p_routed.numpy().astype(np.float64)trained_task_acc = linear_probe(features, task_np, args.probe_ridge, classify=True)trained_coord_r2 = linear_probe(features, routed_np, args.probe_ridge, classify=False)control_task_acc = linear_probe(control_features, task_np, args.probe_ridge, classify=True)control_coord_r2 = linear_probe(control_features, routed_np, args.probe_ridge, classify=False)chance_task_acc = 1.0 / NUM_TASKSprint("\nlinear probe of the fused layer (held-out split):")print(f"  task-id accuracy   trained {trained_task_acc:.3f}  vs random {control_task_acc:.3f}  "      f"(chance {chance_task_acc:.3f}) <- trivially decodable from EITHER: the token is an input")print(f"  routed-coord R^2   trained {trained_coord_r2:.3f}  vs random {control_coord_r2:.3f}  "      f"<- the REAL readout: only training makes the fused token encode the routed VALUE")if args.rerun:    rr.log("probe/task_id_accuracy", rr.BarChart(np.array([trained_task_acc, control_task_acc, chance_task_acc])))    rr.log("probe/routed_coord_r2", rr.BarChart(np.array([trained_coord_r2, control_coord_r2])))    rr.log("forward/cls_attention", rr.BarChart(cls_attention.astype(np.float64)))

A linear probe is the simplest honest question you can ask an activation: is some known factor linearly readable from it? Fit a linear map from the frozen features to the factor on one split of the data, score it on a held-out split. A high score means the layer already represents that factor in a linearly accessible way; the model, in that sense, "knows" it. We fit the map in closed form — ridge normal equations, no optimizer, no seed to chase — so the read itself is deterministic and nothing about it can be blamed on training luck.

We ask two questions of the fused token. First: which task did the instruction select? The probe recovers it with about 1.0 accuracy. That looks like a triumph — until you run the exact same probe on a random-init checkpoint of the same shape and it also scores about 1.0. Nothing was trained, and the probe still "found" the task. This is the trap at the center of the chapter: the instruction token is a literal input, sitting right there in the sequence, and a linear read of almost any projection of the input recovers it. A probe that recovers an input has told you nothing about what the model learned.

So we ask a second question, the one that means something: does the fused token encode the value of the state coordinate the task routes to — a number the policy had to compute by combining the instruction with the state? Here training finally shows up. The trained checkpoint probes at R² ≈ 0.90; the random-init control at ≈ 0.16. That gap is the readout. It is the difference between a representation that merely carries an input and one that carries a computed quantity — and it is the single most important habit to bring to any paper that claims a layer of some real VLA "encodes" a concept: ask first whether the probe merely recovered an input.

(The attention tells a small, interpretable story of its own: the fused token attends most to the task token in the instruction — the fusion looks at the word that decides the routing, which is exactly what you would hope to see.)

Read the real thing

Now take the four moves to the real thing. This is a STUDY-tier segment — you read, you do not run. The checkpoints are too large for the free tier, which is precisely why you built the pieces yourself: the reading is the payoff, not the download. Three beats — our stand-in, the production version, and what the labs add.

What we built. The checkpoint region of probe.py is the whole subject: one TinyPolicy whose fused CLS — the output of self.norm, the single vector every action is read from — is the boundary the forward region hooks. That fused-token → action-head seam is where the probe sits, and it is the first place you would probe anything. Hold it in mind; everything below is that seam, scaled.

The production version — pi0. Physical Intelligence's pi0 lives at src/openpi/models/pi0.py (class Pi0, at the pinned commit 15a9616). The same two mechanisms, at scale. The conditioning is split across two methods: embed_prefix() lays the image tokens and the language tokens out as a prefix — these are a pretrained PaliGemma VLM's tokens, not our random-init embedding — and embed_suffix() lays the state, the noisy action, and a sine-cosine timestep out as a suffix, the action expert's inputs. Our fused CLS is that prefix→suffix attention boundary with a real VLM on one side. The flow-matching head is two methods you can read against flow.py from 1.5 line for line: sample_actions() integrates the inference ODE (dt = -1/num_steps, the update x_t + dt · v_t, t=1 noise → t=0 action), and compute_loss() builds the training target exactly as you did — x_t = t·noise + (1−t)·action, regress the velocity onto noise − action.

What they add, and why. Two things we could not. First, a real pretrained VLM: pi0 runs two Gemma experts — a full PaliGemma for vision-language and a smaller action expert (action_expert_variant) — so the conditioning tokens carry web-scale semantics, where ours carried a four-word vocabulary. Second, in NVIDIA's GR00T N1.7 (gr00t/model/gr00t_n1d7/gr00t_n1d7.py, class Gr00tN1d7, tag n1.7-release) the slow/fast split is made structural: self.backbone — System 2, a Qwen3Backbone VLM — produces backbone_outputs that condition self.action_head — System 1, the flow head running at control rate. The forward pass is literally action_outputs = self.action_head(backbone_outputs, action_inputs). That one line is the System-2 → System-1 interface, and it is our fused-token → head boundary with a name on it: if you had GR00T's checkpoint, the first hook you would place is on backbone_outputs, for the same reason we hooked policy.norm.

Honest: we probed a 20-KB stand-in, not pi0 — but the four moves and the one caveat (did the probe merely recover an input?) are size-invariant, and the frontier is now something you can read. Read next, in order: (1) embed_prefix / embed_suffix in src/openpi/models/pi0.py, to see the conditioning sequence assembled; (2) sample_actions and compute_loss in the same file, your 1.5 head at scale; (3) the self.action_head(backbone_outputs, ...) line in gr00t/model/gr00t_n1d7/gr00t_n1d7.py, the dual-system seam you would probe first.

Exercises

  • ex1 (predict-then-run): of the two probes, which one separates the trained checkpoint from the random-init control? Run it and see why task-id does not.
  • ex2 (bug-hunt): the leaking probe — score it on the held-out split instead of the rows it was fit on, and watch a signal-free layer's R² fall back to zero.
  • ex3 (code-completion): write the closed-form ridge linear probe from scratch — bias column, normal equations on the train split, R² on the held-out split.
  • ex4 (reading): in GR00T N1's dual system, what is the relationship between System 2 and System 1 — and which tensor boundary would you hook to probe it?

Practice

practice · candidate exercisesdrafted by the exercise generator, pending human promotion. Answers reveal only after you predict — honor system.

  1. predict-then-run

    Exercise 1

    SUGGESTED exercise candidate (humans promote) — predict-then-run, ch3.8.

    Objective tested: the chapter's central caveat about reading a checkpoint with a linear probe. probe.py fits TWO linear probes of the fused layer and compares the TRAINED checkpoint to a RANDOM-INIT control of the same shape:

    • task-id accuracy — can a linear map recover WHICH task the instruction selects?
    • routed-coord R^2 — can a linear map recover the VALUE of the state coordinate that task routes to (a quantity the model must COMPUTE)?

    The task token is a literal INPUT sitting in the sequence; the routed coordinate's value is not — the fused token only carries it if the policy learned to combine the instruction with the state.

    PREDICT before you run (default config, seed 0): comparing the trained checkpoint to the random-init control, which probe SEPARATES them?

    • A) BOTH separate — training lifts task-id accuracy AND routed-coord R^2 far above the random control.

    • B) ONLY the routed-coord R^2 separates them — task-id is ~1.0 for BOTH (a linear read of almost any projection recovers a distinct input token), while the routed-coord R^2 is high only after training (~0.90 vs ~0.16).

    • C) NEITHER separates them — a random-init checkpoint probes the same as a trained one; probing tells you nothing.

    NOTE: this runs probe.py once at the default config on CPU (trains a ~7K-param policy for 400 steps + fits the probes) — under a minute. Estimated learner time: 15 minutes.

    Predict, then commit

    Pick the outcome you expect from the options above. The answer and the local run command reveal only after you commit — predicting after you know teaches nothing.

  2. bug-hunt

    Exercise 2

    SUGGESTED exercise candidate (humans promote) — bug-hunt (fast), ch3.8.

    THE probing bug, isolated: evaluating a linear probe on the SAME rows it was fit on. A linear probe has enough capacity to partly memorize its training rows, so scoring it on those rows inflates the number — and, worst of all, makes even a RANDOM, uninformative layer look like it "encodes" the target. The whole point of a probe is a HELD-OUT read: fit on one split, score on another. probe.py splits 50/50; this isolated version has the split bug.

    Everything here is a self-contained numpy fixture (random features that carry NO signal about the target), so this gate is fast and needs no torch or checkpoint. On leakage- free code a random layer must score near 0; the buggy version scores far higher.

    FIND THE BUG in probe_r2 below (it scores on the training rows), then fix it to score on the held-out rows. checks.py gates on the signature (still leaking) and then verifies your fix drives the random-feature R^2 back down.

    Before you fix it, write one sentence: why does scoring a probe on the very rows it was fit on make even a random, signal-free layer look like it "encodes" the target — and why does moving to a held-out split make that false signal collapse to ~0?

    Estimated learner time: 15 minutes.

    Run it locally:

    pytest curriculum/phase3_advanced/ch3.8_frontier/exercises/suggested/checks.py -k ex2
  3. code-completion

    Exercise 3

    SUGGESTED exercise candidate (humans promote) — code-completion (fast), ch3.8.

    The core move of "reading a checkpoint" is the linear probe: fit a linear map from a frozen layer's activations to a KNOWN factor on a train split, then score it on a held-out split. probe.py runs this closed-form (ridge normal equations) so it is deterministic — no optimizer, no seed to chase. This exercise asks you to write it.

    The contract (the same math probe.py's linear_probe runs, regression variant):

    x   = [feats | 1]                       # append a bias column
    w   = solve(Xtr^T Xtr + ridge*I,  Xtr^T ytr)   # ridge normal equations, TRAIN split
    yte_pred = Xte @ w                       # predict on the HELD-OUT split
    R^2 = 1 - SS_res/SS_tot                  # on the held-out split
    

    Split the rows 50/50 (cut = len(feats)//2; train = first half, held-out = second), fit on train, score R^2 on held-out. Complete linear_probe_r2 so the check passes (it compares against a reference on features that DO linearly carry the target).

    pytest curriculum/phase3_advanced/ch3.8_frontier/exercises/suggested/checks.py -k ex3
    

    Run it locally:

    pytest curriculum/phase3_advanced/ch3.8_frontier/exercises/suggested/checks.py -k ex3
  4. reading-investigation

    Exercise 4

    SUGGESTED exercise candidate (humans promote) — reading/investigation, ch3.8.

    This is the "you can now read any robot-learning paper" exercise: a guided READING of the real frontier, not a code run. Nothing to execute — the deliverable is that you can open a released VLA and recognize the pieces you built.

    THE READING (STUDY tier; the chapter's read-the-real-thing block points at the pinned sources): read the architecture description of NVIDIA's GR00T N1 (its "dual-system" design) alongside Physical Intelligence's pi0. As you read, map each part onto what you built: pi0's action head is the ch1.5 flow-matching head; its conditioning is the ch1.7/1.8 VLM fusion. GR00T splits the policy into two systems running at two rates.

    THE QUESTION. In GR00T N1's dual-system architecture, what is the relationship between "System 2" and "System 1"?

    • A) System 1 is a slow, high-level planner and System 2 is a fast reflex; System 1 overrides System 2's actions frame by frame.

    • B) They are two independent policies trained separately and averaged at inference, like an ensemble.

    • C) System 2 is a slow vision-language model that produces a latent/representation which CONDITIONS System 1, a fast action head running at control rate — the same "fused representation -> action head" boundary probe.py hooks and probes.

    Record your answer in PREDICTION. checks.py verifies your recorded choice (like every predict-then-run gate, it only checks that you committed a reading, not your reasoning).

    Then, in a sentence for yourself: if you had GR00T's real checkpoint, WHICH tensor boundary would you hook to probe "what does System 2 tell System 1?" — and why is that the same hook probe.py places on policy.norm? Estimated learner time: 30 minutes (mostly reading).

    Run it locally:

    pytest curriculum/phase3_advanced/ch3.8_frontier/exercises/suggested/checks.py -k ex4
wall-clock · rendered from wallclock.csvone source · every tier
cpu-laptopexpected wall-clock on cpu-laptop: ~0.14 min (measured)measured
mpswall-clock on mps: not yet measuredpending
t4wall-clock on t4: not yet measuredpending
4090wall-clock on 4090: not yet measuredpending

Colophon · provenance

The code on this page is not pasted — each panel is included by region straight fromprobe.py, and these fingerprints are its sha256, the same ones check_prose_code_drift re-checks on every PR. Edit a shown region without re-rendering and CI turns red.

#setup
ce2a4727013ae16431158148bc77141878c0002a932c79bea2e3eb5796365a9f
#checkpoint
a67a1d6ac65e2c6146897bd4c5432d6d008d82aae60227a3bdabda9e644ebf87
#inspect
8896960f23c0f4368591ffe7f02f083ab3b19df983f0feb3fa252fe51569ed27
#forward
f412ea13d80708fafcac2ca06a6c216c8e010795930fd5f0bf4e653fe65a7710
#probe
fb7b43463513cab36e0542251c2cc91a1b1537eaf9f0bf482f4348f87198c98f
#report
64e52b6c0749a3f7019559ae75aefb91db48c73bd7acb563df77f4f0b1869b85