Skip to content

CPU-7: multi-position CPU execution — a 42% batching defect, its 1.46x fix, and a falsified cost model - #316

Open
chrishayuk wants to merge 2 commits into
cpu5-q4q8-qualityfrom
cpu7-parallel-execution
Open

CPU-7: multi-position CPU execution — a 42% batching defect, its 1.46x fix, and a falsified cost model#316
chrishayuk wants to merge 2 commits into
cpu5-q4q8-qualityfrom
cpu7-parallel-execution

Conversation

@chrishayuk

Copy link
Copy Markdown
Owner

Stacked on #315integer.rs and arithmetic.rs do not exist on main, and stationary.rs is a direct extension of CPU-5's K5 row, so this cannot be based off main and compile.

CPU-7 asked whether the CPU can serve more than one token per weight traversal. The kernel says yes. The layer says no, and finding out why produced a production defect, its fix, and a falsified cost model.

CPU-7A   concurrency is not a lever          CLOSED, negative
CPU-7B   N=2 costs 1.02x one traversal       PASSED
CPU-7B2  and it transfers across geometry    PASSED
CPU-7C1  batching is 42% SLOWER than serial  DEFECT FOUND
CPU-7C2  raising the FFN fixes it, 1.46x     LANDED
         stationarity then buys nothing      FALSIFIED

The production defect, and its fix

execute_layer ran the FFN through h.par_iter_mut(), so every projection inside saw caller_owns_the_machine and collapsed to ONE worker. Positions in parallel, each re-entering the executor — this module's own ownership rule, broken from the other end.

arm              slabs/call   K=2 time   /(2A)
A serial               5.03    536.6 ms  1.000
B legacy batched       2.81    745.4 ms  1.389
C FFN multi-position   5.14    510.7 ms  0.952

slabs/call is the row partition the executor actually used; it is what attributes the loss rather than leaving it an argument. Two supplied continuation positions now evaluate for less than two ordinary serial decode steps, with bit-exact parity. That is target-side substrate for speculative verification.

What is falsified

CPU-7C1 banked T_K ~= K(1-g)T_1 + m_K·g·T_1, with g measured independently and m_K imported from CPU-7B, predicting the recurrent tranche to 0.4%. Arm E existed to ask whether that survived a change of substrate. It does not:

logical weight traffic   53.55 -> 29.48 GB   -45%, as designed
wall time                510.7 -> 517.5 ms   unmoved

CPU-7B2 then ruled out geometry — m_2 is 0.976-1.005 across every production shape, including the deep 5120x17408 FFN down projection, with the original square shape reproducing 1.005 as its own in-run control. So the isolated kernel amortises on exactly these shapes at exactly this worker count and the whole layer does not.

Not claimed: that the workload became compute-bound. The ledger counts LOGICAL weight-load demand, not DRAM transactions, and nothing here separates the two. An earlier draft of the write-up did claim it; it is retracted in docs/cpu7b2-geometry-transfer.md.

Also fixed: the crate did not build off aarch64

SDOT_LANES was #[cfg(target_arch = "aarch64")] while Q8xQ8::project_rows compared against it un-gated, so larql-vindex has not compiled on any non-aarch64 target since df36ca9 (#315). Found by the cross-target check, not by review. Note for future cfg-gated work: x86_64-unknown-linux-gnu is unusable locally (ring's build script needs a cross C toolchain) and its failure looks like a toolchain problem; x86_64-apple-darwin catches the same class.

Invariants added

  • every projection site belongs to a declared operator class, and the class shares must sum to p. A new site makes other go nonzero rather than silently contaminating a derived g. This caught a real instrumentation hole: inferring the FFN share from p - g would have given 0.699 against the true 0.593.
  • the ledger charges what was READ — one traversal only when the kernel says it grouped, n otherwise.
  • the stationary kernel REFUSES rather than falling back silently; the caller must consult supports first.
  • each position keeps its own activation quantisation and accumulator. A joint scale would be a new representation wearing a schedule's name, and a test plants a 400x neighbour to prove it is not.

step_many

A verification primitive: it consumes supplied token ids and never samples position t+1 from position t's logits, which would make the traversal autoregressive again and destroy the parallelism it exists to expose. Its gates compare the token AFTER the batch, because correct logits for the batch itself do not catch a wrong recurrent state. Causal isolation is checked by comparing step_many([a,b,c,d]) against step_many([a,b,c]) then step(d).

Gates

fmt                          clean
clippy aarch64               larql-vindex 0   larql-compute 0
check x86_64-apple-darwin    larql-vindex 0   larql-compute 0
tests                        3058 + 1061 pass
coverage                     stationary.rs clears the 90% floor;
                             ledger.rs 79.50 -> 83.85

The crate's per-file coverage policy still fails on 15 files, all pre-existing and byte-identical before and after this branch; four of them arrive with #315.

Not done

CPU-7C3 (attention, a 6.6% tranche) is deliberately NOT built: it is not worth building until we know why a 59.3% FFN tranche produced no speedup. The next instrument is per-class projection wall time inside arms C/E/D, which is readable at K>1 only because C2 removed the position-parallel nesting — the fix under test is what makes its own diagnostic possible.

Protocols and full run logs, every gate frozen before the run that adjudicated it: docs/cpu7-parallelism-protocol.md, docs/cpu7c-two-position-layer.md, docs/cpu7c2-multi-position-surfaces.md, docs/cpu7b2-geometry-transfer.md.

Written so CPU work can be picked up cold. Covers what is frozen and
where, what was learned independently of whether the candidate ships,
the exact commands to resume CPU-6, the productionisation order if it
passes, and the CPU-7 programme with its ranked opportunity list.

The short version: there is a CPU integer projection path 32% faster
than production (264 ms/token, 3.79 tok/s, 81% of attainable bandwidth)
and it is NOT validated. CPU-6 is frozen and parked to test exactly that,
unchanged, under a repaired gate.
…PU-7)

    CPU-7A   concurrency is not a lever          CLOSED, negative
    CPU-7B   N=2 costs 1.02x one traversal       PASSED
    CPU-7B2  and it transfers across geometry    PASSED
    CPU-7C1  batching is 42% SLOWER than serial  DEFECT FOUND
    CPU-7C2  raising the FFN fixes it, 1.46x     LANDED
             stationarity then buys nothing      FALSIFIED

Protocols and full run logs in `docs/cpu7-*.md`, every gate frozen before
the run that adjudicated it.

## The batching defect, and its fix

`execute_layer` ran the FFN through `h.par_iter_mut()`, so every
projection inside saw `caller_owns_the_machine` and collapsed to ONE
worker. Positions in parallel, each re-entering the executor — the
ownership rule this module already states, broken from the other end.

    arm              slabs/call   K=2 time   /(2A)
    A serial               5.03    536.6 ms  1.000
    B legacy batched       2.81    745.4 ms  1.389
    C FFN multi-position   5.14    510.7 ms  0.952

`slabs/call` is the row partition the executor actually used, and it is
what attributes the loss rather than leaving it an argument. Two supplied
continuation positions now evaluate for LESS than two ordinary serial
steps.

## What is falsified

CPU-7C1 banked a cost model: `T_K ~= K(1-g)T_1 + m_K·g·T_1`, with `g`
measured independently and `m_K` imported from CPU-7B, predicting the
recurrent tranche to 0.4%. Arm E was built to ask whether that survived a
change of substrate. It does not.

    logical weight traffic   53.55 -> 29.48 GB    -45%, as designed
    wall time                510.7 -> 517.5 ms    unmoved

CPU-7B2 then ruled out geometry: `m_2` is 0.976-1.005 across every
production shape including the deep `5120x17408` FFN down projection,
with the original square shape reproducing 1.005 as its own control. So
the isolated kernel amortises on exactly these shapes at exactly this
worker count, and the whole layer does not.

NOT established, and deliberately not claimed: that the workload became
compute-bound. The ledger counts LOGICAL weight-load demand, not DRAM
transactions, and no instrument here separates the two.

## Also fixed: the crate did not build off aarch64

`SDOT_LANES` was `#[cfg(target_arch = "aarch64")]` while
`Q8xQ8::project_rows` compared against it un-gated, so `larql-vindex` has
not compiled on any non-aarch64 target since df36ca9. Found by the
cross-target check, not by review — `x86_64-unknown-linux-gnu` cannot be
used here (`ring`'s build script needs a cross C toolchain), but
`x86_64-apple-darwin` catches the same class.

## Invariants added

  - every projection site belongs to a declared operator class, and the
    class shares must sum to `p`. A new site makes `other` go nonzero
    rather than silently contaminating a derived `g`.
  - the ledger charges what was READ: one traversal only when the kernel
    says it grouped, `n` otherwise.
  - the stationary kernel REFUSES rather than falling back silently; the
    caller must consult `supports` first.
  - each position keeps its own activation quantisation and accumulator.
    A joint scale would be a new representation wearing a schedule's name,
    and a test plants a 400x neighbour to prove it is not.

`step_many` is a verification primitive: it consumes supplied token ids
and never samples position t+1 from position t's logits. Its gates
compare the token AFTER the batch, because correct logits for the batch
itself do not catch a wrong recurrent state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant