NOCAP // AGENT ARCHITECTURE
DOC 05 · FULL SYSTEM MAP 2026-07-09 github.com/rainbowpuffpuff/nocap
Autonomous experiment agent · BottleCapAI NoCap-Test

An agent that finds a faster path to val loss ≤ 3.3821

Not a chatbot. A closed-loop research agent: a flag-instrumented GPT-2 trainer, a two-stage data-curation pipeline, a Modal serverless DAG orchestrator, and a methodology of pre-registered kill gates. The job is to beat the official FineWeb baseline on one GPU — measured against our own re-benchmark — then ship the evidence.

GPT-2 124M · d12 F1 done · 3.82 h to target F2 pending · stack recipe Modal A100-40GB Private repo until submission
≤3.3821
Target val loss
3.82h
F1 time-to-target
−5.4%
Proxy stack wall-clock
1.92B
Curated tokens (scale)
~$25
Total cloud budget
F2
Next: record run
00

Map of this document

01

Mission & environment constraints

The environment is the BottleCapAI NoCap-Test hiring challenge: train GPT-2 124M on FineWeb to the baseline’s final validation loss, faster.

OBJECTIVE
Beat baseline wall-clock

Reach val loss ≤ 3.3821 on the official disjoint val set, in less training wall-clock than the stock recipe on the same GPU class. Clock pauses during validation. One GPU only.

DENOMINATOR
F1 re-benchmark, not the 4090 board

Rules: re-benchmark baseline on your hardware. F1 on A100-40 crossed 3.3821 at step 4736 = 229.1 min (3.82 h), final 3.3795, step_avg 2903 ms. That is the speedup denominator.

AxisAllowedForbidden / trap
DataAny filter/reorder of the given FineWeb tokensNew datasets outside the kit’s FineWeb stream
Model / lossArchitecture, loss, schedule changes (with valid prob. model at eval)Copying Modded-NanoGPT / leaderboard primary ideas
EvalAny seq length that scores the full val set; sliding reported alongside officialChanging val_tokens on real runs (it IS the metric)
HardwareAny GPU; report relative speedup vs self-baselineTreating absolute hours as comparable to the 4090 board
HyperparamsSchedule changes justified by underfit regime / matched stepsPure hyperparameter search as the “idea”
Three “baselines” — never conflate them

(1) Official repo baseline — 5.401 h on RTX 4090, defines target loss 3.3821.

(2) F1 — stock recipe on our A100-40; speedup denominator.

(3) m0_anchor — 800-step proxy reference for cheap A/B only; never appears in the official claim.

02

What the agent is (and is not)

We call it an “agent” because it is a goal-directed, multi-phase system with memory, tools, and a control loop — not because it chatters. Humans set kill gates and recipes; Modal executes the DAG; the volume is durable memory; the kit is the tool surface.

PERCEPTION
Metrics & artifacts

log.csv (train/val/sliding/EMA), config.json, triage_stats.json, billing probes, volume listings. Every keep/kill is trajectory-based, not a single snapshot.

MEMORY
Modal Volume nocap v2

Data shards, features, scores, triage bins, priors, run dirs, LAUNCH markers, runs_logs.zip. Survives container death. Concurrent-writer safe (v2).

ACTUATION
Kit + GPU/CPU functions

Train, score, triage, prior-build, summarize — each a Modal function with hard timeouts, scaledown windows, and idempotent skip rules.

CONTROL
orchestrate()

Remote driver: phase DAG, spawn/fan-out, join. modal run --detach decouples local client; panic stop is one CLI command.

POLICY
Pre-registered gates

Playbook ideas behind flags (defaults = stock). Negative results are first-class outputs. Time-scale hparams re-derived at proxy length.

NOT THIS
Not an LLM agent

No tool-calling LLM in the loop. Humans write gates and interpret curves. The “agent” is the experiment runtime + method, not a conversational planner.

03

Layered architecture

L5 · CLAIM
Submission surface
IDEA.md (method + negatives) · RESULTS.md (timing, configs, repro) · git bundle · email subject = measured speedup. Docs 01–04 + this architecture map are the narrative spine.
L4 · CONTROL
Modal app nocap-wave2
Phases: smoke → data → prep ∥ anchors → score → wave2 → report; plus wave25 / wave3 / scale / f1 / f2. Peak 5 GPUs (Starter plan). Orchestrator spawns arms; local entrypoint only fires-and-forgets.
L3 · TOOLS
kit/ — instrumented trainer + pipeline
train_gpt2_t4.py (all ideas as flags) · get_data · triage_features / score_docs / triage_select · build_unigram_prior · eval_sliding · summarize_runs · modal_nocap.py · RUNBOOK.md
L2 · MEMORY
Volume layout under /vol
data/ · features/ · scores/ · triage_{a,b,c,bord,full}/ · unigram_prior.pt / prior_full.pt · runs/<name>/{config,log,main,final,LAUNCH} · runs_logs.zip
L1 · COMPUTE
Serverless containers
Debian slim + PyTorch 3.12 image · A100-40GB (explicit; avoid silent 80GB upgrade) · bf16 + compile on finals · Colab T4 fp16 for Wave-1 proxies · CPU workers for triage/prior
L0 · VENUES
Three Modal accounts + free Colab
Acct1: scale + F2 (curated data lives here) · Acct2: F1 notebook (raw HF only) · Acct3: optional Wave-3 architecture race · Colab A/B: Wave-1 discovery at $0
04

GitHub layout — rainbowpuffpuff/nocap

Private working repo until email submission. Mirrors the agent’s brain (docs), body (kit), hands (notebooks), and memory dumps (results).

nocap/ ├── docs/ # HTML reports — open in browser │ ├── 01-preflight-briefing.html │ ├── 02-idea-playbook.html # 30 ranked ideas │ ├── 03-wave1-retrospective.html │ └── 04-campaign-report.html # ledger + FAQ + next phase ├── kit/ # working code (defaults = stock semantics) │ ├── train_gpt2_t4.py # instrumented trainer │ ├── get_data.py │ ├── triage_features.py │ ├── score_docs.py │ ├── triage_select.py │ ├── build_unigram_prior.py │ ├── eval_sliding.py │ ├── summarize_runs.py │ ├── modal_nocap.py # serverless DAG orchestrator │ └── RUNBOOK.md ├── notebooks/ │ ├── colab/ # Wave-1 T4 era │ ├── modal/ # Modal-era notebooks │ ├── wave3/ │ ├── finals/ # F1 / F2 launch notebooks │ └── executed/ # F1 evidence (executed) ├── results/ │ ├── colab-accountA/ │ ├── modal-wave2/ # m0, m1, w2_* │ └── modal-wave3/ # swiglu / canon / parallel ├── README.md └── FUTURE.md # ranked post-finals design leads

Companion workspace (this machine): Modal CLI scripts, smoke writeup, scale-verify bundle, local archive of runs/logs under software_i_built/modal/.

05

Kit modules — tool surface

Design contract of the trainer: with all new flags at defaults, semantics match the stock baseline (model, loss, schedule, timing protocol). Every experiment is opt-in.

ModuleRoleKey I/O
train_gpt2_t4.pyInstrumented GPT-2 trainer (d12 / d2 smoke). RoPE, RMSNorm, optional SwiGLU, Canon, parallel block, MTP, loss shaping, prior, EMA, z-loss, sliding val.bins → runs/<name>/
get_data.pyFetch FineWeb pre-tokenized chunks from HF into volume/local.data/*.bin
triage_features.pyStage-A doc features (len, unique, top, rep8, digit, punct).features/*.npz
score_docs.pyStage-B: per-doc mean CE under a trained ckpt; per-shard skip = resumable.ckpt + bins → scores/*.npz
triage_select.pyJunk rules + optional score band + 10% random rescue + optional curriculum order.triage_*/fineweb_triage_*.bin
build_unigram_prior.pyCorpus unigram log-prior for fixed logit bias.unigram_prior.pt / prior_full.pt
eval_sliding.pyBoundary-aware sliding-window val (report alongside official disjoint).ckpt → Δ vs disjoint
summarize_runs.pyTable over all run dirs; report zips logs without final.pt.→ console + zip
modal_nocap.pyFull experiment DAG as Modal App; phases idempotent; detach-safe orchestrator.volume as bus

Trainer flag groups (opt-in ideas)

SHIPPED IN STACK
Prior · EMA · warmdown · data path

--logit_prior · --ema_beta · --ema_start_frac · --warmdown_iters · --input_bin (triage_*) · --val_stride · --precision bf16 · --compile 1

INSTRUMENTED / KILLED OR PARKED
Architecture & loss experiments

--mtp_* · --loss_shape / --ls_* · --mlp swiglu · --canon · --parallel_block · --zloss

06

Data pipeline architecture

FineWeb10B pre-tokenized GPT-2 BPE: up to 50×100M tokens. Competition budget cap 5B; baseline consumes 2.5B. Our F2 path uses a curated subset scored and banded from 28 chunks.

Two-stage triage (idea #4) + prior (idea #3)
Stage-A is cheap heuristics · Stage-B is model-scored perplexity band · Prior is zero-cost logit bias
FETCH
FineWeb bins
get_data · HF chunks
STAGE A
Features
triage_features
SCORER
m0_anchor ckpt
800-step reference
STAGE B
score_docs
per-doc mean CE
↓ band pct 10–80 · junk rules · 10% random rescue
SELECT
triage_full
~1.92B kept tokens · 20 shards
PRIOR
prior_full.pt
CE 7.67 nats vs ln V 10.82
TRAIN
F2 record
stack recipe · 4768 steps
Artifact (scale verify, 2026-07-09)Value
docs_total / docs_kept4,045,481 → 2,927,881
tokens_total / tokens_kept2.796B → 1.922B (kept_frac ≈ 0.687)
Score band (mean-loss)[4.1887, 5.1831] = pct 10–80 of 4,045,472 docs
scores/*.npz28 shards (expect 28)
triage_full bins20 shards · ≈3.58 GiB
prior_full.pt~198 KiB · unigram CE 7.6690 nats
Dominant rule_hitscore_band 1,213,639 · unique 33,465 · len 9,532
Why curation is on-clock-legal prep

Scoring is offline like the baseline’s own pre-tokenization cost narrative: GPU prep is honest and disclosed in IDEA.md. Training then sees a higher-signal stream (~1.22 epochs of 1.92B curated tokens cycled toward ~2.5B consumed). Proxy isolation: w2_databand alone was −0.051 nats @400 vs raw stream.

07

Modal orchestrator — control loop

App name: nocap-wave2. Image: debian slim + torch/numpy/huggingface_hub/tiktoken. Volume: nocap version=2. GPU string: A100-40GB (never bare A100).

Phase DAG — --phase all
data → [prep CPU ∥ 4 anchor GPUs] → score → [2 wave2 GPUs] → report · peak ≤ 5 GPUs
DATA
fetch_data
chunks=3 (proxy) / 28 (scale)
↓ parallel fan-out
PREP
prior + features + triage_a
CPU 8 / 16GB
m0
m0_anchor
stock · scorer ckpt
m1
m1_ema
β=0.99 · short WD
A
w2_mtp
killed
B
w2_shape
killed
↓ uses m0 final.pt
SCORE
score_stage_b
scores + triage_b band 10–80
↓ parallel
DATA ARM
w2_databand
400 steps · triage_b
STACK
w2_stack
prior+EMA+curated · 800
REPORT
summarize + zip logs
runs_logs.zip on volume

Phase catalog

smoke
Lifecycle proof
One A100 ~60–90s: bf16, synthetic shards, 10 trainer steps, volume marker, auto-shutdown. Validates billing lifecycle before real spend (~$0.01–0.04).
data
Fetch FineWeb
CPU function; commits bins to volume.
prep
Prior + stage-A
Idempotent if prior/features/triage_a already exist.
anchors
4-way GPU race
m0 / m1 / mtp / shape in parallel via spawn().
score
Stage-B band
score_docs + triage_select → triage_b.
wave2
Data isolation + stack
w2_databand ∥ w2_stack.
wave25
Data variants
Harder band / curriculum / stack seed 2029 (optional; skipped in rev 2 plan).
wave3
Architecture race
swiglu ∥ canon ∥ parallel_block vs m0 curve.
scale
Full corpus prep for F2
fetch N · score_all · finalize_data(band, order) → prior_full + triage_full.
f1 / f2
Finals via train_long
4768 steps, timeout 8h. F1 stock (zloss 0). F2 stack on triage_full + prior_full + EMA 0.999 + warmdown 256.

Invocation shape

# local entrypoint only spawns the remote orchestrator — safe to close the terminal
modal run --detach kit/modal_nocap.py --phase all
modal run --detach kit/modal_nocap.py --phase scale --chunks 28 --band 0.10,0.80
modal run --detach kit/modal_nocap.py --phase f2

# panic / observe
modal app stop nocap-wave2
modal app list
modal volume get nocap runs_logs.zip ./runs_logs_modal.zip
08

Confirmed recipe brain

Every component proxy-validated at matched steps / hardware. Stack additivity ~70–100% at proxy scale.

LeverMechanismProxy effectStatus
Perplexity-band curationKeep docs in mean-loss pct 10–80 + junk rules + 10% anti-drift rescue−0.051 @400SHIP
Unigram logit priorFixed corpus log-bias on logits; start ~7.7 not 10.8−0.047 @400SHIP
Shortened warmdownEarly LR decay wasteful while still underfit−0.013 @800SHIP
Sliding-window evalBoundary-aware val; reported with official disjointΔ 0.019@800 → 0.060 full (F1)SHIP (dual-report)
EMA weightsβ scaled to horizon; dual-eval at endlag artifact at proxyMAINTAIN free
Stack (data+prior+WD)Composition for F2−0.056 raw / −0.074 w/ eval @800F2 RECIPE

Killed with evidence (submission material)

KILL
MTP module

+11% step time and worse loss @800. Consistent with small-model NLL cautions. Configs + curves archived.

KILL
Token-loss shaping

~0 cost, never ahead (+0.007 @800). Val includes every token — down-weighting is a mid-run tax.

KILL (FINALS)
SwiGLU

Big early lead evaporates by step 800; ends ≈ baseline (4.78).

PARK
Canon layers

−0.078 nats (largest training-side effect) but +30% step time naive → net loss. #1 FUTURE lead if fused ≤2% tax.

BANK
Parallel block

~1.6% faster, loss-neutral-or-better (−0.005). Out of F2 for composition risk only.

TRAP
Model resizing

Baseline sits at exactly 20 tok/param (Chinchilla isoline). Resizing unstable sign, ≤2%.

09

Method — how the agent decides

  • Pre-registered kill gates before each arm (RUNBOOK + playbook).
  • Matched steps on identical GPU type when step times equal; matched wall-clock when they don’t.
  • Paired within-run measurements where possible (immune to run noise).
  • Re-derive every time-scale hyperparameter at proxy length (EMA-β lesson: 0.999 full-run ≠ 0.99 proxy).
  • Gates on trajectories, not snapshots (sliding Δ grows through training).
  • Negatives as first-class outputs — README invites them; we archive configs + CSVs.
  • Effects < 0.01 CE need two seeds before keep/kill.
  • Pin environment across accounts (torch version drift caused 802 vs 753 ms/step).
PROXY SCALE
Standard A100 proxy config

model d12 · batch 8 · grad_accum 16 · seq 1024 → 131,072 tok/step (¼ of full 524,288) · LR 4.5e-4 · warmup 128 · warmdown 160 (or 40 with EMA) · val every 100 · bf16 · compile 1 · zloss 1e-4 (F1 drops zloss).

Full finals: 4768 steps · batch 16 · accum 32 · LR 0.0018 · warmup 256 · warmdown 1024 stock / 256 stack · val every 128.

10

Cost safety, idempotency, serverless semantics

AUTO-SHUTOFF
Containers die with the call

Serverless: no long-lived GPU. timeouts 1h (train) / 8h (finals). scaledown_window=10s. Worst hung arm ~$2.10; absolute multi-hang cap ~$15.

IDEMPOTENT SKIP
final.pt / scores exist → skip

Safe re-run of any phase. score_docs skips per-shard .npz. prep skips prior/features/triage if present.

DUPLICATE GUARD
At-least-once spawn

LAUNCH + log.csv freshness <15 min → fail loud instead of racing twin trainers (learned after a silent double score ~$2 tax).

Operational lessons paid for in blood

Volume v1 vs v2: scripts request version=2; creating with default v1 breaks concurrent writers — recreate with --version=2.

Cloudflare 403 on kit download: default Python-urllib UA blocked inside Modal; send curl UA from ensure_kit().

Image build free, cold start billed: smoke in-container 14s ≈ $0.008 GPU; actual bill ~$0.009 with pull.

11

Live state — 2026-07-09

MilestoneResultStatus
Wave-1 (Colab T4)fp16 parity, prior, sliding Δ growth, warmdown waste foundDONE
Smoke (Modal)A100 lifecycle, volume write, ~$0.009DONE
Wave-2 (Modal)m0 4.7788 · stack 4.7232 · databand −0.051 · MTP/shape killDONE
Wave-3 (Modal)Canon −0.078 but +30% time · parallel banked · SwiGLU killDONE
Scale (acct 1)28 scores · prior_full · triage_full 1.92B tokensDONE
F1 (acct 2)Crosses 3.3821 @ step 4736 = 229.1 min · final 3.3795DONE
F2 record (acct 1)Stack on triage_full + prior_full + short WD + EMA dual-evalPENDING
Official speedup(13748s − F2_time) / 13748sAFTER F2
F2 launch surface

Notebook: notebooks/finals/nocap_f2_account1.ipynb (or modal run … --phase f2). Asserts 20 curated shards + prior_full present. Insurance ckpt at step 4352 before final warmdown. Success = first official val ≤ 3.3821 before step 4768; earlier crossing = larger speedup.

12

Future architecture (post-finals leads)

From FUTURE.md — ranked design work that does not block F2.

#LeadWhy it matters
1Fused Canon layersValidated −0.078 nats; kill the +30% transpose/conv tax (elementwise k=4 / channels-last / careful Triton). ≤2% step cost → headline win.
2Parallel attn+FFN (+ fused projections)Already 1.6% faster, loss-neutral; fused [QKV|FFN] GEMMs toward 3–6%.
3Top-K head + prior tail-bucket + late unlockOriginal idea; ~5–6× head FLOPs cut for most of training; extends prior narrative.
4Adaptive two-tier softmaxConservative cousin of #3; −16–18% step time niche.
5Harder band / curriculum orderAlready implemented knobs; never raced at finals scale (~$0.35 A/B).
6EMA re-test in flat endgameF2 dual-eval may claim free early cross on EMA weights.
7–8Sizing race · 4090 replicationWrite-up / board-comparable absolute hours.
13

How to run the agent

# once per workspace
pip install modal
modal setup
modal volume create nocap --version=2

# cheap proof (~$0.01)
modal run kit/modal_nocap.py --phase smoke

# proxy DAG (~$4–6, 60–80 min wall)
modal run --detach kit/modal_nocap.py --phase all

# full curated dataset for F2
modal run --detach kit/modal_nocap.py --phase scale --chunks 28

# finals
modal run --detach kit/modal_nocap.py --phase f1   # stock baseline
modal run --detach kit/modal_nocap.py --phase f2   # record stack

# fetch evidence
modal volume get nocap runs_logs.zip .
python kit/summarize_runs.py runs
COMPANION DOCS
Read order

01 Briefing → 02 Playbook → 03 Wave-1 retro → 04 Campaign report → 05 this architecture map → FUTURE.md → RUNBOOK.md

LOCAL WORKSPACE
software_i_built/modal/

modal_nocap.py · smoke-test-writeup · scale_verify_bundle · monitor.py · nocap_archive · modal-reference.md

One-line summary

The agent, compressed

A serverless, volume-backed experiment runtime that runs pre-registered keep/kill races on an instrumented GPT-2 trainer and a two-stage FineWeb triage pipeline — now holding a validated stack (curated data + unigram prior + short warmdown + dual eval) and a measured F1 denominator (3.82 h to 3.3821), waiting only on F2 to mint the official speedup claim.