DFLASH2 vs NATIVE MTP — CONTEXT AND BATCH ABLATIONS — 2026-09-10 (PARTIAL)
==========================================================================

STATUS: ONE ARM OF TWO. The DFlash2 arm ran end to end; the native-MTP arm was
never booted, and there is no A-vs-A floor. The run was cut short by an
operator stop, not by a result. Nothing here compares the two drafters. What it
does carry is a within-arm context shape, an honest account of what the cells
actually were, and two harness defects that would have cost the next drive a
session.

THE QUESTION
  DFlash2's five drafter layers are ALL `sliding_attention` at window 2048
  (checkpoint config.json; ring `_ring_len = min(eff, 2048) + block`), so past
  2048 committed tokens it speculates off a trailing window while the verifier
  sees everything — `dflash_kv_slots.py` calls that "a truncated draft context
  (lower accept, never wrong output)". Native MTP is ONE decoder block doing
  REAL paged attention over the request's own KV (`mtp_head.py`, and its
  `_mtp_real_attention` marker), sized as one extra entry on the per-layer
  slab. It has no window to outgrow. The prediction is that accept falls with
  depth for DFlash2 and is flat for native MTP.

RIG
  box 81 (this dev box) GPU 1, clocks LOCKED at 2700 MHz for the run and reset
        after it. GPU 0 was a sibling agent's and was never touched.
  image registry.arbi.work/arbi-serve:test-latest,
        digest sha256:105732eb1c3e8bcd753253cd63ad0b047e847a3fa1ecb3e429c0e80c5bf4e0ad,
        org.opencontainers.image.revision 3211c8d86657 — THIRTY-EIGHT commits
        behind the main this was run from. NO bind mount: the arm is the
        SHIPPED code, which is what a drafter-selection decision wants, and it
        is NOT the ring code changed on 2026-09-10. An accept decay measured
        here is the shipped one.
  recipe compose.dflash.yaml + dflash-local.env, engine only (--no-deps), with
        the console port dropped by an overlay because 8899 is a literal in the
        recipe and a sibling already held it.
  verifier /models/Qwen3.8-27B-exl3-4.0bpw, drafter
        /models/Qwen3.8-27B-DFlash2-exl3-6.0bpw (block_size 8, so K<=7),
        k4v4 vq2 bundle, TKV_BITS=4.
  PINS, deliberate and stated because CLAUDE.md's default is auto:
        --max-context=131072  (identical across arms by design, so the KV pool
                               is not a hidden third variable — the two
                               drafters have different memory profiles)
        --max-batch=4         (the batch ablation needs c4 and nothing wider)
        --max-batched-tokens=2304, --chunk-prefill=2048  (2304 is
                               compose.serve.yaml's value, carried onto the
                               DFlash recipe so both arms would have had the
                               same prefill geometry)
        ARBI_SAVEPOINT_DECODE_WRITE=0  (confirmed in the boot's runtime-flags
                               line: savepoint_decode_write=False). A cache HIT
                               on a GDN hybrid can otherwise produce a
                               different continuation from a MISS.
  sampler the CHECKPOINT'S OWN generation_config.json — temperature 1.0,
        top_k 20, top_p 0.95, seed 20260910 — passed explicitly, `cache: false`
        (the spelling the request models accept; `cache_enabled` is the
        internal twin that is silently dropped), `enable_thinking: false` held
        constant so the thinking/non-thinking content mismatch cannot move
        accept between cells.
  corpus HELMET infbench longbook_sum_eng, English narrative prose, DISTINCT
        books only (the file carries several questions per book), ONE corpus
        truncated to every depth so content family is held constant and only
        depth moves.
  harness tools/bench/drafter_ablation.py, run
        docs/bench-measurement-footguns.md-first: decode-window timing, accept
        read PER REQUEST from usage.completion_tokens_details (one producer),
        /metrics read at the SINGLE label backend="paged_kv:tkv-k4v4" as an
        independent cross-check, generated text printed and read.

WHAT THE CELLS ACTUALLY WERE — READ THIS BEFORE THE TABLE
  The harness fired every prompt of a cell CONCURRENTLY, so a cell's
  concurrency was its SAMPLE SIZE, not the width it was named for. The three
  cells named c1 were served at c8, c8 and c4. The fix is committed
  (`cell(..., conc)` serves in groups of `conc`); the numbers below are
  relabelled by what was served, and they are therefore NOT the designed
  baseline and NOT a batch ablation — the "c4" cell and the "baseline" cell
  differ by 8-vs-4 concurrency rather than by 1-vs-4.

MEASURED — DFlash2, ONE BOOT, NO A-vs-A FLOOR
  Rates are per stream on the decode window; agg is the sum across live
  streams. tok/step is exact from `generated = steps + accepted`; ms/step
  prices the window's `steps - 1` steps. `specoff` is the load valve's
  disabled-step delta at the single label — 0 everywhere, so nothing here was
  a scheduler refusing to let the drafter draft.

  served    prompt    n   ok   tok/s    agg   accept  (metrics)  tok/step  ms/step   TTFT ms
  --------- --------- --- ---- -------- ------ ------- ---------- --------- -------- ---------
  c8          1,019    8  8/8    80.6   644.7  0.3270    0.3221     3.336    41.77     2,388
  c8         16,843    8  8/8    33.1   264.9  0.2590    0.2558     2.832   106.94    16,991
  c4        129,300    4  2/4    46.3    92.6  0.2493    0.2472     2.774   201.38    54,195
  c4            984    4  4/4    78.1   312.3  0.2910    0.2836     3.059    39.35       599
  c4          1,054    4  4/4    85.3   341.3  0.3432    0.3427     3.396    39.90       621

  The per-request accept and the single-label /metrics accept agree to within
  0.01 in every cell, which is the cross-check passing. Zero degenerate rows;
  the text was read at every depth and is clean prose (structured summaries of
  the right books), not a repetition loop.

THE ONE SHAPE THIS RUN DOES SUPPORT
  DFlash2's per-step yield FALLS with depth and then FLATTENS, at the depth the
  2048 window predicts:

    depth      tok/step   accept
    ~1k          3.34      0.327     below the window; ring has not wrapped
    ~17k         2.83      0.259     -15.2% tok/step
    ~129k        2.77      0.249     -16.9% tok/step, -0.9pp from 17k

  Almost the entire fall lands between 1k and 17k — i.e. at the wrap — and 17k
  to 129k costs another 2%. That is the SHAPE a fixed trailing window
  predicts: once the ring has wrapped, the drafter's context is 2048 tokens
  whether the request is at 17k or 129k, so there is nothing left to lose. It
  is NOT a decay that keeps compounding with depth.

  DO NOT READ THAT AS A MEASUREMENT OF THE WINDOW YET. Three things are
  unseparated in it: (a) the depths were served at different concurrencies
  (c8/c8/c4), (b) there is no A-vs-A floor, so the scale a -15% is read on is
  assumed rather than measured, and (c) with no native-MTP arm there is no
  control for "summarising 17k of a novel is simply harder to draft than
  summarising 1k of it", which is a content effect every drafter would pay.
  The native-MTP arm is exactly the control for (c) and it was not run.

  The 1k -> 17k step is nonetheless large and it is in the predicted
  direction, at the predicted depth. If the sibling hunting a ring-wrap defect
  wants a comparison point, this is a SHIPPED-image reading of the same
  quantity.

CELLS NOT RUN — ALL FOUR NATIVE-MTP CELLS, PLUS BOTH FLOORS
  designed cell                DFlash2            native MTP
  ---------------------------- ------------------ ------------------
  baseline  c1  ~1k            served at c8       NOT RUN
  ctx A     c1  16k            served at c8       NOT RUN
  ctx A     c1  128k           served at c4       NOT RUN
  batch B   c4  ~1k            served at c4 (x2)  NOT RUN
  A-vs-A floor at baseline     NOT RUN            NOT RUN

  So: no drafter comparison, no crossover, no cell winner. The 128k x c4 cell
  of the original matrix was correctly dropped from the design before any boot
  and was never attempted.

WHAT COST TIME, WRITTEN DOWN SO IT COSTS NOBODY ELSE ANY
  * A COLD BOOT OF THIS RECIPE AT THESE PINS IS 171.6 s TO SERVING. The pins
    (max-batch 4, max-context 131072) are not the shipped ones, so the boot
    found NO budget-cache record and no tkv verify-splits record: "graph_pool
    budget: cold-boot estimate 0.45 GiB (no cache yet)" and "[TKV
    verify-splits] no record for fp 06f073ed51989ac9 — measuring now". A second
    boot at the same pins reads both back. Plan the arm ORDER around that:
    two cold arms compared against each other is like-for-like; a cold arm
    against a warm one is footgun 36.
  * WHAT FITTED. At those pins the boot realized 1076 pages x 256 = 275,456
    servable tokens (KV 4.47 GiB, 19% of 23.52 GiB) with the DFlash drafter
    resident. So 128k x c1 fits with room; the compose header's ~199k figure is
    for `--max-context auto`, which captures wider.
  * A 128k PREFILL COSTS ~54 s OF TTFT on this card at these pins, and the
    engine's own comment budgets 48 s for 131k. Four of them is four minutes
    before a single decode number exists. Budget for that.
  * 8899 IS A LITERAL IN EVERY SERVING RECIPE, so a second engine on the same
    box fails to START with `Bind for 0.0.0.0:8899 failed`, after creating the
    container. `ports: !override` in a one-service overlay is the fix; nothing
    else in the recipe needs changing.
  * THE WORD->TOKEN CALIBRATION MUST NOT BE FITTED ON ONE DOCUMENT. The
    harness measured 0.7756 words/token on doc 0 and applied it to all eight;
    two of the four 128k prompts came out at 132,093 and 139,675 tokens and the
    server REFUSED them (`Prompt length ... >= max_context 131072`), taking the
    128k cell from n=4 to n=2. Calibrate per document, or aim at 0.95 x the
    pin. The refusal is loud and correct — it is the pin doing its job — but it
    silently halves a cell.
  * `/health` on this build answers `{"status":"ok"}` with NO `booting` field;
    the readiness gate is `/health/ready` -> `{"status":"ready"}`. A poll loop
    written for `"booting":false` never fires.
  * THE STEP IDENTITY IS EXACT AND NEEDS NO TIMING. A live request returned
    74 generated / 45 accepted / 158 rejected at K=7: 203 proposed = 29 x 7,
    and 74 = 29 + 45. So steps = generated - accepted, exactly, and tok/step
    can be read off usage alone. Only ms/step needs the window.
  * BOTH `add_mtp` PRODUCERS LAND ON THE SAME LABEL when one spec is active
    (`_counter_delta_backend` returns the sole active spec), so the single
    label's TOTALS are a double count while its RATIO is not — numerator and
    denominator are doubled together. Read the ratio, never the totals.

ARTIFACTS
  tools/bench/drafter_ablation.py — the harness, with the concurrency defect
    fixed. Per-run JSON (every row, every prompt, per-request accept) was
    written to /tmp and is NOT durable; re-running is cheaper than recovering
    it now that the pins and the boot cost are written down here.
