Grail Computer · research record
Dated experiment record. Statements describe this run, not current submission readiness. Historical H. pylori category names do not establish infection; copy screens are bounded tests. Claims that a source does not exist mean none was identified in that recorded search, not proof of absence. Licensing interpretations in the notes remain unconfirmed. Local access details have been omitted. Current limitations and remaining work.

F2-TRAIN — the structure adapter, trained on an A100 and measured against the two-pass recipe

Work package F2-TRAIN (Phase 3) of the SGH programme, executed 2026-09-08 on spot A100 slot d (sgh-a100-slot-d, us-central1-f). Engineering evidence only: everything below is a measurement of geometry, pixel statistics and throughput. No clinical claim is made or implied, no pathologist saw any of these images, and nothing here says a synthetic field is diagnostically usable.

The VM was stopped at the end of the run and is TERMINATED. Nothing was committed to git.


1. Headline

The adapter does control layout, and by a wide margin — that was the open question and the rotation test answers it unambiguously. It does not clear the success bar in F2_RESULT.md section 8, which required both an in-range ring fraction and a smaller morphometric envelope distance than the two-pass recipe on the same cartoons. It clears the second condition only for intestinal metaplasia.

question answer
Does a from-noise generation follow the cartoon's label map? Yes. IoU(output ring mask, cartoon lumen mask) is 0.26–0.56 against a 0.03–0.06 chance level, and 2.6–5.4x the two-pass recipe's on the same cartoons
Does rotating the label map rotate the output? Yes. 5–15x separation between the matched and mismatched overlaps, all four categories
Is the ring fraction inside the real held-out range? Yes, all four — but the held-out range is wide (n=4–6 fields); against the held-out mean the adapter overshoots everywhere, from +0.016 (IM) to +0.243 (gastritis)
Is the morphometric envelope distance better than two-pass? Only for intestinal metaplasia (9/14 cartoons, median -0.23). Worse for normal, gastritis and mixed by +1.3 to +1.9
Did validation loss separate from the zero-init base model? Yes, but barely: 0.1641 -> 0.1594, a 2.9% relative drop, and flat after step 1500
Verdict against section 8 Not a success; not a failure either. No failure criterion is tripped. Partial success on intestinal metaplasia only

2. Run timeline (all times UTC, 2026-09-08)

time event
14:11:39 fleet.sh up d — spot A100 acquired first attempt, guest deadline +32400 s
14:12:20 ssh ready, NVIDIA A100-SXM4-40GB, 40960 MiB, driver 580.173.02 (62 s from start to ready)
14:12:33–14:15:31 fleet.sh stage d ../packages/f2-adapter — 8 items, 20 MB (19 MB of it the 60 a1 label maps), 178 s
14:16:17 launch #1
14:16:34 FAILURE 1: pair build FAILEDModuleNotFoundError: No module named 'scipy'
14:17–14:18 fixed on the VM: pip install "numpy==1.26.4" scipy scikit-image into the asset-root venv
14:18:35 launch #2
14:18:45–14:26:38 segmentation of the 96 donor fields into label maps, on the VM, 472 s, 96 ok / 0 failed
14:26:38–14:26:56 CPU smoke test on the VM: 27 passed, 0 failed in 15 s
14:26:56 training starts; DiT cast bf16 straight from the fp32 checkpoint, VAE fp16, UNI2-h fp32
14:28:10 validation loss @ step 0: 0.1641 (48 batches, 28 s) [zero-init adapter == base model]
14:28:15 step 1
16:44:40 step 3000
16:45:07 validation loss @ step 3000: 0.1594; checkpoint step_003000 saved
16:45:09–18:17:22 64 from-noise generations at 4096x2048 (60 a1 cartoons + 4 --rotate180)
18:17:22 WORKER_DONE, exit_code.txt = 0, 64 images, out/SHA256SUMS written
18:18 the worker's 20-minute self-stop timer was stopped by hand so the 1.5 GB retrieve could not race it (the guest-deadline metadata and --max-run-duration, both 32400 s, remained armed as the real backstop)
18:19:03–18:28:09 fleet.sh retrieve d …/retrieved — 1.5 GB, 546 s (2.8 MB/s), SHA256SUMS OK (73 files)
18:28:21–18:29:32 fleet.sh down d; fleet.sh status confirms TERMINATED

Preemptions: none. The spot instance ran 4 h 18 m uninterrupted, so the auto-resume path (resuming training from … in steps.log) was never exercised in this run. It was exercised by FLEET's own preemption tests, not here — this package cannot claim to have re-verified it.

fleet/slots.json change (as instructed)

defaults.deadline_seconds and defaults.max_run_duration_seconds were both raised from 10800 to 32400 before up. Both are still 32400 in the file. This lengthens only the backstop; the sweep slots call down explicitly, so a longer backstop cannot leave them running.


3. What failed, and the fix

FAILURE 1 — no scipy on the VM (17 s into the first launch). segment_real.py imports scipy.ndimage and, deeper in, skimage.feature.peak_local_max / skimage.segmentation.watershed. The PixCell asset-root venv is a generation environment: numpy 1.26.4, pillow, torch 2.9.1+cu129, diffusers, transformers, timm, peft — and no scipy, no scikit-image. Neither does the system python. This was invisible to the CPU smoke test, which reads an already-built manifest and never runs the segmenter, and invisible to F2-PAIRS, which ran the segmenter on the Mac where both are installed.

Fixed on the VM with pip install --no-input "numpy==1.26.4" scipy scikit-image, pinning numpy so the resolver could not upgrade it out from under torch. Result: scipy 1.17.1, scikit-image 0.26.0, imageio 2.37.4, lazy-loader 0.5, tifffile 2026.3.3; numpy stayed 1.26.4 and torch.cuda.is_available() stayed True. segment_real.py --selftest then passed. The change lives on slot d's boot disk only, not in the golden image sgh-pixcell-a100-20260908 — any other slot that runs this package will hit the same error. Either add the two wheels to the image or ship a pip install line in worker.sh.

BUG FOUND AND FIXED BEFORE LAUNCH — the inference donor path. worker.sh's inference planner read donors.json (which holds bare file names such as hiesd-28f9b423__hpylori_gastritis__34688_2112__mpp0.25.png) and passed the bare name straight to infer_adapter.py --donor. Image.open would have resolved it against the run root and raised FileNotFoundError on every one of the 64 generations — and because the planner's loop uses subprocess.run(..., check=False), all 64 would have failed silently and the worker would still have written WORKER_DONE with exit 0 and zero PNGs. Patched in packages/f2-adapter/worker.sh to resolve REFS_ROOT/<category>/<file>, to log MISSING_DONORS if a path does not exist, and to match the category longest-key-first. Verified on the VM before launch that all four donor-0 files exist. This is the same class of bug as FLEET gotcha 8: a step that "succeeds" having done nothing.

Not fixed (deliberately): the per-image model reload. The planner spawns one infer_adapter.py process per image, so PixCell + VAE + UNI2-h are loaded 64 times. Each image cost 79 s wall of which 46.3 s was generation — roughly 35 minutes of the run, about USD 1.2, spent re-loading weights. Rewriting the loop mid-run risked the whole 2.5 h of training, so it was left alone. A single-process driver that loads once and loops is the obvious fix for the next run.


4. Training

Config as shipped (adapter-train/config.json): cond=labels (9 soft one-hot channels at 128x128), crop 1024, batch 4, grad-accum 1, lr 1e-4 cosine with 100 warmup steps, weight decay 0.01, grad-norm clip 1.0, CFG dropout 0.10, conditioning dropout 0.10, bf16, gradient checkpointing on, 3000 steps, checkpoint every 500, validate every 250, seed 0.

Loss curve (packages/f2-adapter/retrieved/loss.png)

step train loss (mean of 25 around) val loss grad norm max VRAM GB s/step (median of 25)
0 0.1387 0.1641 0.014 6.8 2.60
1 0.1387 0.014 6.8 2.60
250 0.1228 0.1611 0.034 6.9 2.61
500 0.0883 0.1604 0.022 6.9 2.61
750 0.1253 0.1601 0.019 6.9 2.61
1000 0.1322 0.1598 0.022 6.9 2.61
1250 0.1372 0.1598 0.022 6.9 2.61
1500 0.1084 0.1596 0.023 6.9 2.61
1750 0.0917 0.1595 0.021 6.9 2.61
2000 0.0648 0.1595 0.010 6.9 2.61
2250 0.0844 0.1595 0.018 6.9 2.61
2500 0.1040 0.1595 0.008 6.9 2.61
2750 0.1018 0.1594 0.011 6.9 2.61
3000 0.0734 0.1594 0.007 6.9 2.61

Per-step training loss is dominated by the random diffusion timestep, so it scatters between 0.0002 and 0.42 throughout and its trend is only visible in the smoothed curve; the validation loss (fixed crops, fixed noise, fixed timesteps linspace(50, 950)) is the number that matters. It fell 0.1641 -> 0.1594 (-2.9% relative) and was essentially flat from step 1500 onward (0.1596 -> 0.1594 over the last half of the run). The cosine schedule reached lr 0 exactly at 3000.

Read honestly: the adapter learned something the frozen model did not already know, monotonically and without instability, but the epsilon-MSE it improves is dominated by the frozen DiT's already-good prediction, so a 2.9% drop is a weak signal on its own. The layout measurements in section 6 are the evidence that it learned the right thing; the loss alone would not carry that claim.


5. Generation

64 images at 4096x2048, 21 overlapping 1024 windows on one shared latent, 20 DPMSolver++ steps, CFG 1.5, --init noise, seed 51001, adapter scale 1.0, checkpoint step_003000, donor 0 of each category from donors.json:

category token donor at inference in the adapter's training set?
normal hiesd-40f3c0cf__normal__23744_3072 yes
hpylori_gastritis hiesd-28f9b423__hpylori_gastritis__34688_2112 yes
intestinal_metaplasia hiesd-324885ef__intestinal_metaplasia__15616_54528 no — held-out slide
mixed hiesd-324885ef__mixed__11584_55488 no — held-out slide

Worth flagging, because hiesd-324885ef is one of the three slides the by-slide split removed: the two categories generated from a slide the adapter never saw are IM (its best category) and mixed (its worst). Two categories is not enough to say anything about donor generalisation, but the pairing should not be read as evidence either way.

Cost per image: 46.3 s of generation (120 denoiser calls), remarkably constant — sd across the 64 images was 0.023 s, min 46.27 s, max 46.38 s — plus ~33 s of process startup and model loading. 64 images in 92 minutes.


6. Evaluation

All local, all on the retrieved PNGs, with research/sgh-synthetic-histopathology/.venv/bin/python. Ring topology is code/ring_topology.py with config/sydney_qa_v1.json (md5 aa8cdf2b…, byte-identical to code/sydney_qa_v1.json). Morphometrics is morphometrics/morphometrics.py scored against the stored morphometrics/envelope.json through calibrate.score_rows, i.e. the same instrument and the same fitted envelope C1 calibrated — nothing was re-fitted here.

The two-pass comparison uses packages/sweep-v1/retrieved/*/out/a1/pass2/<cat>/<stem>.png — the SWEEP package's own base arm (start_index 6, seed 51002) on the same 60 a1 cartoons, so this is a like-for-like comparison rather than the cross-cartoon fullset_pass2 numbers. Two caveats: 59 of 60 were usable (intestinal_metaplasia_s14_oblique.png was zero bytes in that package's local copy — a truncated retrieval, not a generation failure; the sweep's own steps.log reports failed=0), and the donor matches only for the s11 cartoons: the A1 sweep rotates donors by seed (s11->donor 0 … s15->donor 4) while this run used donor 0 throughout. Table T5 is the strictly donor-matched subset. The fullset_pass2 medians from morphometrics/scores.csv are quoted in section 7 for continuity with C1, but they are a different cartoon set.

T1 ring_with_lumen_fraction

category adapter, from noise (n=15) two-pass on the same cartoons a1 cartoon itself real held-out mean (min-max) adapter gap to real two-pass gap to real
normal 0.9218 +- 0.0886 0.3385 +- 0.2346 (n=15) 0.4420 0.8538 (0.671-0.976) +0.0680 -0.5153
hpylori_gastritis 0.9856 +- 0.0158 0.0618 +- 0.0716 (n=15) 0.7430 0.7428 (0.297-1.000) +0.2428 -0.6810
intestinal_metaplasia 0.7265 +- 0.1509 0.2511 +- 0.1927 (n=14) 0.6589 0.7101 (0.128-0.953) +0.0164 -0.4590
mixed 0.8155 +- 0.1028 0.2360 +- 0.1616 (n=15) 0.6323 0.7261 (0.268-1.000) +0.0894 -0.4901

T2 morphometric envelope distance (Mahalanobis, per-category real-train envelope)

category adapter median (min-max) two-pass median real held-out median real held-out max adapter inside band paired: adapter closer on median paired delta
normal 8.620 (5.309-10.931) 7.435 3.330 4.339 0% 3/15 +1.371
hpylori_gastritis 8.280 (7.527-10.646) 7.760 4.193 7.974 13% 3/15 +1.260
intestinal_metaplasia 5.112 (4.525-5.604) 5.557 2.575 4.502 0% 9/14 -0.228
mixed 9.188 (7.989-10.457) 7.355 3.660 4.377 0% 0/15 +1.939

T3 layout adherence: IoU(output ring mask, cartoon lumen mask)

category adapter, true label map adapter, label map rotated (chance) adapter, other cartoon (chance) two-pass, true label map two-pass, chance (rotated) adapter / two-pass
normal 0.5020 0.0358 0.0489 0.1267 0.0408 4.0x
hpylori_gastritis 0.5599 0.0282 0.0432 0.1028 0.0399 5.4x
intestinal_metaplasia 0.2586 0.0565 0.0504 0.1002 0.0439 2.6x
mixed 0.3194 0.0509 0.0501 0.1050 0.0406 3.0x

T4 rotation test (one cartoon per category, both renders)

category cartoon (a) rotated output vs rotated label (b) rotated output vs unrotated label (a) unrotated output vs unrotated label (b) unrotated output vs rotated label a/b (rotated) a/b (unrotated)
hpylori_gastritis hpylori_gastritis_s11_across 0.4599 0.0417 0.5170 0.0345 11.0x 15.0x
intestinal_metaplasia intestinal_metaplasia_s11_across 0.2173 0.0397 0.1982 0.0410 5.5x 4.8x
mixed mixed_s11_across 0.2634 0.0389 0.2566 0.0412 6.8x 6.2x
normal normal_s11_across 0.3494 0.0283 0.3519 0.0328 12.3x 10.7x

T5 donor-matched subset (s11 cartoons only; adapter and sweep both used donor 0)

category cartoon adapter ring two-pass ring adapter D two-pass D adapter layout IoU two-pass layout IoU
normal s11_across 0.9398 0.0000 8.509 7.523 0.3519 0.0450
normal s11_along 0.9688 0.0164 9.592 7.435 0.5611 0.0875
normal s11_oblique 0.9670 0.0455 8.975 7.605 0.6755 0.0883
hpylori_gastritis s11_across 0.9882 0.0000 10.214 7.831 0.5170 0.0614
hpylori_gastritis s11_along 0.9630 0.0208 8.025 9.195 0.5788 0.1324
hpylori_gastritis s11_oblique 1.0000 0.1905 8.192 6.932 0.5843 0.0709
intestinal_metaplasia s11_across 0.7481 0.2017 5.284 5.587 0.1982 0.0639
intestinal_metaplasia s11_along 0.4444 0.1742 5.604 5.757 0.1832 0.1057
intestinal_metaplasia s11_oblique 0.8000 0.3622 5.134 5.535 0.3485 0.1368
mixed s11_across 0.8485 0.2333 8.917 5.872 0.2566 0.0807
mixed s11_along 0.5556 0.0575 10.457 6.380 0.2426 0.0482
mixed s11_oblique 0.8800 0.4583 8.609 5.733 0.4296 0.1718

T6 per-image correlation with the cartoon

T7 what the envelope distance is actually made of

Median per category, adapter vs the two-pass base arm vs the real held-out fields:

category ring area um2 (A / 2P / real) ring density per mm2 (A / 2P / real) nuclear density per mm2 (A / 2P / real) median nucleus area um2 (A / real)
normal 439 / 425 / 442 148 / 141 / 101 9067 / 4893 / 6350 17.3 / 25.1
hpylori_gastritis 416 / 414 / 365 124 / 107 / 89 12322 / 5912 / 5996 14.2 / 23.1
intestinal_metaplasia 594 / 432 / 438 191 / 170 / 120 8835 / 5784 / 6326 20.4 / 23.3
mixed 604 / 463 / 441 187 / 143 / 101 10499 / 5524 / 6762 18.2 / 21.8

The three features that dominate the worst_features column of adapter-scores.csv are the same in every category and in the same direction: nuc_density_per_mm2 = +, epi_nuc_density_per_mm2 = +, and (for IM) median_ring_area_um2 = +. The adapter's single systematic defect is nuclei: it makes 1.4x to 2.1x too many of them and each is 20–40% too small. Ring size is close to real; ring density is 25–70% high, which is a knock-on of the same thing. The two-pass recipe, which starts from a real image's latent, gets nuclear density almost exactly right and loses on geometry instead.


7. Reading the tables

Ring topology (T1). The a1 two-pass base arm collapses on this cartoon set — 0.06 to 0.34 lumen-bearing ring fraction against a real held-out 0.71–0.85. The adapter is dramatically closer on every category: gaps of +0.016 (IM), +0.068 (normal), +0.089 (mixed), +0.243 (gastritis) against the two-pass's -0.46 to -0.68. Note this is not the fullset_pass2 picture that C1 recorded (normal 0.726, gastritis 0.475, IM 0.917, mixed 0.726 on the fullset cartoons); the a1 cartoons behave very differently under the two-pass recipe, which is SWEEP's finding to explain, not this package's. Against fullset_pass2 the adapter is still closer to the real mean for normal, gastritis and IM, and further for mixed.

The adapter's failure direction is a consistent overshoot: it makes tissue whose rings almost always contain a pale lumen (gastritis 0.986, i.e. 62.6 of 63.3 detected rings on the average field). Real tissue is messier. Section 8's condition 1 is satisfied on the letter — every category's mean lies inside the real held-out min–max — but that range spans 0.13–1.00 for IM and 0.30–1.00 for gastritis on 4–6 fields, so "inside the range" is a weak test and should not be read as "matches real".

Morphometrics (T2). Paired on the same cartoon, the adapter is further from the real envelope than the two-pass base arm for normal (3/15 wins, median +1.37), gastritis (3/15, +1.26) and mixed (0/15, +1.94), and closer for intestinal metaplasia (9/14, -0.23). The donor-matched s11 subset (T5) tells the same story: IM 3/3 to the adapter, mixed 0/3, normal 0/3, gastritis 1/3. Neither method sits inside the real held-out band often: the adapter is inside for 0% of fields in three categories and 13% in gastritis; the two-pass base arm is inside for 0% (normal), 0% (mixed), 21% (IM) and 60% (gastritis) — the one place the two-pass is clearly the better of the two.

Layout (T3, T4, T6). This is where the adapter wins and the win is not marginal:

The --scale 0 ablation of failure criterion 3 was not run — it would have cost a second VM boot after down, and the evidence already on hand is strictly stronger: non-zero grad_norm at every one of 3000 steps, a validation loss that moved off the zero-init value, and outputs whose geometry tracks which label map was supplied (rotating it moves the tissue). An adapter doing nothing cannot produce T3 or T4.


8. Contact sheets

packages/f2-adapter/retrieved/adapter-sheet.png (3790x1950) — one row per category, columns cartoon label map | adapter from noise | adapter with the label map rotated 180 | two-pass on the same cartoon, each cell the whole 4096x2048 field downscaled so that layout is comparable. packages/f2-adapter/retrieved/detail-sheet.png (3096x1082) — the same IM and gastritis cartoons at native 0.25 um/px, 256x128 um windows, adapter | two-pass | a real held-out field.

What I see, honestly.

At field scale the adapter columns clearly carry the label map. In the gastritis row the label map's dense, gland-free patch at lower-centre-right appears as a correspondingly dense pale region in the from-noise output, and in the rotated column that region has moved to the upper left; in the IM and mixed rows the vertical crypt columns of the label map appear as vertical epithelial columns in the output, and rotate with it. The normal row is the hardest to judge by eye because the label map is a near-uniform carpet of round gland profiles — but that is also where the IoU is highest (0.50). The two-pass column is not obviously related to the label map in any row.

At native resolution the ranking reverses. The two-pass crops have convincing one-cell-thick epithelial bands with elongated, basally-oriented nuclei, visible goblet-type vacuoles in the IM crop and stromal collagen strands; the real held-out crops have all of that plus crisply defined chromatin and, in the gastritis field, red blood cells in a vessel. The adapter crops are grainier and flatter: nuclei are small, round, soft-edged and scattered fairly evenly rather than being organised into a polarised band at the edge of a gland, the cytoplasm is a washed pink with little internal texture, and there are no red cells. The pale holes are the right size but their walls are made of a crowd of small nuclei rather than a palisade. This is the same defect the morphometrics found — too many, too small nuclei — and it is visible without any measurement.

So: the adapter has learned where to put tissue and has not yet learned what the tissue looks like up close, while the two-pass recipe is the other way round.


9. Verdict against F2_RESULT.md section 8

Success required both conditions. It is met for intestinal metaplasia only, and even there condition 2 is a small margin (median -0.23 Mahalanobis, 9 of 14 cartoons).

condition normal gastritis IM mixed
1. ring fraction inside the real held-out range yes (0.922 in 0.671–0.976) yes (0.986 in 0.297–1.000) yes (0.727 in 0.128–0.953) yes (0.816 in 0.268–1.000)
2. morphometric distance below the two-pass on the same cartoon no (+1.37) no (+1.26) yes (-0.23) no (+1.94)
combined no no yes no

The "weaker but still reportable" outcome named in section 8 — the adapter fixes gastritis, where the two-pass recipe is worst — is half true and should be stated carefully. On ring topology the adapter moves gastritis from 0.06 (a1 two-pass) or 0.475 (fullset two-pass) to 0.986 against a real 0.743: it stops undershooting and starts overshooting, by a comparable margin. On morphometrics it makes gastritis worse. Calling that "fixed" would be wrong.

Failure criteria — none is tripped:

criterion result
1. Layout ignored (ring density / lumen fraction uncorrelated with the cartoon's) not tripped. r = 0.513 over 60 cartoons; layout IoU 4–10x chance
2. Rotation test — output is not the rotation of the unrotated output not tripped. 5.5x–12.3x separation on the rotated renders, 4.8x–15.0x on the unrotated ones, all four categories
3. Adapter does nothing (zero-init heads never left zero) not tripped. grad_norm non-zero at all 3000 steps (median 0.0150, min 0.0032); conditioning demonstrably changes the output. The literal --scale 0 vs --scale 1 ablation was not run — see section 7
4. Validation loss does not separate from step 0 not tripped, but weakly. 0.1641 -> 0.1594, -2.9% relative, flat after step 1500

Overall. Phase 3's question was whether a small zero-init control branch on a frozen PixCell DiT can make a from-noise generation obey a procedural label map. The answer measured here is yes for geometry, no for texture: the layout signal is strong, unambiguous and reproducible across all four categories and 60 cartoons, and the resulting fields are further from the real morphometric envelope than the two-pass recipe in three categories out of four because the adapter over-produces small nuclei. This is a promising negative: the hard part (control) works, and the part that failed is a distribution-matching problem with obvious next moves.

The honest caveat from F2_RESULT section 8 still stands and is now sharper. The adapter was trained on segmenter output, not on ground truth, so it learned "epithelium wherever segment_real.py said epithelium". The programme cartoons' label maps are cleaner than that training conditioning, and the adapter's response to a clean map is to produce a lumen in nearly every ring — gastritis 0.986. Some of the overshoot is the adapter, and some is a train/inference distribution shift the segmenter created. This run cannot separate the two. The mucin channel specifically was not measured separately here (no goblet-vacuole detector exists yet); the detail sheet shows the adapter's IM crops lack convincing goblet vacuoles, but that is a visual impression, not a measurement.


10. Exact commands

cd [local]/sgh-program-20260908
P=[local]/python

# 0. before anything: raise both fleet backstops from 10800 to 32400 in fleet/slots.json
#    (deadline_seconds and max_run_duration_seconds), and fix the donor path in the package worker.

# 1. local pre-flight, free: the package's own 27-check CPU smoke against F2-PAIRS' manifest
$P packages/f2-adapter/adapter-train/run_cpu_smoke.py --manifest pairs/data/manifest.json --out-dir /tmp/f2-smoke

# 2. the run (fleet.sh run == up + stage + launch + watch; done as four calls here so that a
#    staging failure could be seen before committing to a multi-hour watch)
cd fleet
./fleet.sh up d
./fleet.sh stage  d ../packages/f2-adapter
./fleet.sh launch d ../packages/f2-adapter
./fleet.sh watch  d                       # backgrounded; polls 60 s, recovers preemptions

# the scipy repair, on the VM, after launch #1 failed
./fleet.sh ssh d '[local]/pip \
    install --no-input "numpy==1.26.4" scipy scikit-image'

# supervision, every ~2 minutes
./fleet.sh ssh d 'R=[local]/f2-adapter; tail -3 $R/evidence/train.log; nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv,noheader'

# 3. retrieve, verify, stop
./fleet.sh ssh d 'sudo -n systemctl stop sgh-fleet-stop.timer'   # so the 20-min backstop cannot race a 1.5 GB copy
./fleet.sh retrieve d [local]/retrieved
./fleet.sh down d && ./fleet.sh status

# 4. evaluation
cd ..
$P packages/f2-adapter/eval/plot_loss.py --log packages/f2-adapter/retrieved/out/adapter/loss_log.jsonl \
   --val packages/f2-adapter/retrieved/out/adapter/val_loss.json \
   --out packages/f2-adapter/retrieved/loss.png --table packages/f2-adapter/eval/loss-table.md
$P packages/f2-adapter/eval/cartoon_rings.py                      # ring metrics of the 60 a1 cartoons
$P packages/f2-adapter/eval/f2_eval.py --images packages/f2-adapter/retrieved/out/adapter-infer \
   --labels packages/f2-adapter/cartoons/a1 --out packages/f2-adapter/eval/results --jobs 10
$P packages/f2-adapter/eval/layout_iou.py packages/f2-adapter/retrieved/out/adapter-infer \
   packages/f2-adapter/cartoons/a1 packages/f2-adapter/eval/results/layout-iou-adapter.csv
# the same three on the sweep's two-pass a1 outputs, collected into eval/twopass-a1/ as symlinks
$P packages/f2-adapter/eval/f2_eval.py   --images packages/f2-adapter/eval/twopass-a1 \
   --labels packages/f2-adapter/cartoons/a1 --out packages/f2-adapter/eval/results-twopass --jobs 10
$P packages/f2-adapter/eval/layout_iou.py packages/f2-adapter/eval/twopass-a1 \
   packages/f2-adapter/cartoons/a1 packages/f2-adapter/eval/results-twopass/layout-iou-twopass.csv
$P packages/f2-adapter/eval/final_tables.py > packages/f2-adapter/eval/final-tables.md
$P packages/f2-adapter/eval/make_sheet.py --images packages/f2-adapter/retrieved/out/adapter-infer \
   --labels packages/f2-adapter/cartoons/a1 --twopass packages/f2-adapter/eval/twopass-a1 \
   --out packages/f2-adapter/retrieved/adapter-sheet.png

11. Cost and powered time

Reconstructed from fleet/state/d/powered.log (UP 1788876699 -> DOWN 1788892172) and cross-checked against the orchestrator log.

powered window 2026-09-08 14:11:39Z -> 18:29:32Z
powered minutes 257.9 min (4.298 h), one session, zero preemptions
spot A100 (a2-highgpu-1g) @ USD 2.12/h USD 9.11
egress, 1.5 GB retrieved to the Mac @ ~USD 0.12/GB ~USD 0.18
total attributable to this package ~USD 9.29

Where the 258 minutes went: 5 boot/stage, 8 segmentation, 0.3 smoke, 137 training, 92 generation, 9 retrieve, 1 shutdown, ~5 of orchestration slack. Two obvious savings for a rerun: ~35 min of the generation phase is the 64 redundant model loads (one process per image), and the run used 6.9 GB of 40 GB VRAM at batch 4, so batch 12–16 would cut the 137 training minutes substantially at the same step count. The other four slots were untouched by this package; a, b and c were RUNNING under the concurrent SWEEP package when this one finished and are not this package's to stop.


12. Files

packages/f2-adapter/worker.sh                          patched: donor paths resolved under REFS_ROOT
packages/f2-adapter/retrieved/out/adapter-infer/       64 PNGs at 4096x2048 + 64 .json provenance (944 MB)
packages/f2-adapter/retrieved/out/adapter/             loss_log.jsonl, val_loss.json, summary.json,
                                                       train.log, train_config.json,
                                                       checkpoints/step_{000500..003000}/ (6 x 95 MB:
                                                       structure_adapter.safetensors 33 MB +
                                                       training_state.pt 66 MB), latest.txt
packages/f2-adapter/retrieved/out/smoke/               the on-VM smoke result
packages/f2-adapter/retrieved/out/SHA256SUMS           73 files, all verified locally after transfer
packages/f2-adapter/retrieved/evidence/                steps.log, exit_code.txt (0), runtime.txt,
                                                       build_pairs.log, smoke.log, train.log,
                                                       infer.log, infer_plan.json, WORKER_DONE
packages/f2-adapter/retrieved/loss.png                 loss / validation / VRAM+throughput curves
packages/f2-adapter/retrieved/adapter-sheet.png        label map | adapter | adapter rot180 | two-pass
packages/f2-adapter/retrieved/detail-sheet.png         native-resolution adapter | two-pass | real
packages/f2-adapter/eval/f2_eval.py                    ring + morphometrics + rotation test
packages/f2-adapter/eval/layout_iou.py                 layout adherence with two chance levels
packages/f2-adapter/eval/cartoon_rings.py              ring metrics of the a1 cartoons themselves
packages/f2-adapter/eval/plot_loss.py                  loss.png + loss-table.md
packages/f2-adapter/eval/make_sheet.py                 the contact sheet
packages/f2-adapter/eval/final_tables.py               every table in section 6
packages/f2-adapter/eval/results/                      adapter-ring.csv, adapter-features.csv,
                                                       adapter-scores.csv, rotation-test.csv,
                                                       layout-iou-adapter.csv, summary.json
packages/f2-adapter/eval/results-twopass/              the same instruments on sweep-v1's a1 pass-2 base arm
packages/f2-adapter/eval/twopass-a1/                   59 symlinks into packages/sweep-v1/retrieved/
packages/f2-adapter/eval/a1-cartoon-ring.csv           the 60 a1 cartoons' own ring metrics
packages/f2-adapter/eval/final-tables.md               sections 6's tables as generated
fleet/logs/d.log, fleet/logs/f2-train-poll.log         orchestrator and supervision logs
fleet/slots.json                                       deadline_seconds / max_run_duration_seconds 10800 -> 32400

13. What this package did not do

Download public Markdown export