Measured, not assumed: 9 blocks x 2000 trials at n=30 took 8.16s
before this (pure-Python nested loops, GIL-held throughout -- moving
it off the event loop via asyncio.to_thread does NOT mean it stops
costing real wall-clock time, it just stops blocking OTHER requests
while it costs that time). The O(n^2) between/within split is now a
single tensor broadcast per call, and the biggest remaining cost
(permutation_test recomputing the full _unit()+matmul on every one of
2000 trials, when only the shuffled labels change) is hoisted out of
the loop. Same 9x2000 sweep: 0.76s, 10.7x faster.
All 48 existing trajectory_probe tests pass unchanged -- same exact
p-values, same edge cases -- confirming this is a pure speed fix, not
a behavior change. Benefits both trajectory_probe's own "Read" button
and h3_repr_steering's block_sweep, which share this code.
Settings ▸ Learning ▸ "Start a fresh measurement". Deleting the refinement key
was not enough: the probe log lives outside the <key>.* sidecar convention on
purpose, so it survives a key clear, and the per-bucket heads are REBUILT from
it on every rating — the next rating restored them from runs still on disk.
So the clear takes both, and leaves the switch alone: "throw away what was
measured" and "stop measuring" are different requests.
Verified against a running ComfyUI: 12 runs and the heads file removed, count
back to 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs of one prompt share most of their content: on a real 21-run session, 86% of
each descriptor was the part every run had. Comparing raw vectors measured that
shared part, so every distance came out near zero and near equal — mean cosine
0.97, and no separation at ANY bucket including the late ones where steering
demonstrably works.
Centring first flips the same data from -0.002 (backwards) to +0.03 and p 0.83
to 0.11, consistent across all four buckets. Not significant at 5 good / 16 bad,
but pointing the right way and now limited by sample size rather than by the
statistic.
The descriptor was not at fault. adaptive_avg_pool1d over the flat latent keeps
512 REGIONAL means, not one global mean, and it distinguishes clips fine.
Also: the prompt id is quantised before hashing. Raw bytes answered "are these
bit-identical", so any value crossing a rounding boundary changed the id — one
prompt produced 20 distinct ids across 21 runs. Stable now for jitter of the
size a GPU produces; no exact hash of floats can do better.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Settings ▸ Learning gains Download and Load. A rental is replaced whenever
something breaks, refinements/ is not in git, and the reading needs ~16 rated
runs — so without a way off the box the count restarts on every instance and
the answer is never reached. 16 runs is ~85 KB.
Importing the same export twice adds nothing: the permutation test reads n
directly, so duplicated runs would report a separation as far more significant
than the evidence supports.
An uploaded .pt is a pickle, so the restore parses it with weights_only — every
field in a run is a tensor or a primitive, and anything else is refused rather
than executed. Nothing but numbers, a rating key, a timestamp and a one-way
prompt hash is stored; no prompt text has ever been written to these files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Settings ▸ Learning ▸ Trajectory probe: a switch to record, the run count,
and a Read button that gives the answer in plain language. The switch is the
same environment variable the sampler reads and the editor sets it in the
running process, so it takes effect on the next generation with no restart —
and it is written to disk, so a restarted box comes back recording instead of
having quietly stopped.
tools/trajectory_separation.py stays as the terminal door for a headless box.
Verified against a running ComfyUI: toggle both ways, persistence across a
real restart, and the reading recovering a planted early signal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every rating-driven mechanism shares one gate (_make_steer_ramp) that opens
only over the last half of the schedule, so a "Missing action" rating has no
window in which it could act. Banking x0_hat per schedule position would give
it one — if the early half carries any signal. This measures that.
Off unless FUNPACK_TRAJECTORY_PROBE=1. Records, never steers. Built for H3:
buckets clamp to few-step runs, batched predictions are refused rather than
averaged with their unconditional row, and each pass binds its own schedule.
Read the result with tools/trajectory_separation.py.
fix: reference numbering counts within its own kind
A reference slot resolves per kind, so marking an image then a video labelled
the video R2 while the only slot reaching it was "Reference video 1" — there
was no way to have an R1 video and an R1 image at once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>