Skip to content

perf(KV-FP8): serve fp8 KV prefill through the bf16 dense dequant - #3360

Open
iiLaurens wants to merge 3 commits into
mudler:mainfrom
iiLaurens:row/KV-FP8
Open

iiLaurens wants to merge 3 commits into
mudler:mainfrom
iiLaurens:row/KV-FP8

Conversation

@iiLaurens

@iiLaurens iiLaurens commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Row

KV-FP8 — one row per PR. Spec: .agents/specs/fp8-kv-prefill-dense-dequant.md.

Before starting

  • Issue/PR search: KV-FP8 W2 (#1593)
    landed the fp8 store and read-dequant for correctness; no issue covered the
    PREFILL performance half. Filed ISSUE-LOCAL-01M3QG4WWC3X0C84M9PWQZAJ10.
  • Pull request shape selected at row claim: one PR for the spec and the
    implementation.
  • Anchors inspected: cuda_paged_attn.cu:148-197 (the W2 fp8 read and its
    per-element dequant), :2909, :2954 (the dispatch split that names the
    scalar arm), kv_cache_route.h:40,73-78, and the 33-case suite.

What changed

The fp8 KV-cache read sent every prefill to the scalar CUDA-core flash kernel at
f32 q/out, because the model presents f32 for a non-bf16 store and the vendored
FA-2 admission requires bf16 q, bf16 KV and bf16 out. nsys attributes the whole
gap: 25.1 s of 36 s in PagedFlashKernel<float, unsigned char, …> at 8k, against
2.9 ms/layer for the bf16 store on the FA-2 split-KV kernel.

The fp8 cache is now dequantized ONCE per layer into a dense bf16 scratch, and
the normal bf16 dispatch runs on it. The scratch is one block per request with
block_size = max_seq and an identity block table, so the paged address IS the
dense address and no attention kernel changes. A new shared
KvCachePresentsBf16 presents bf16 for an fp8 store, which admits FA-2 with zero
cast kernels; the CUDA path is model-agnostic, so any model that gains an fp8
store inherits it. VT_ATTN_FP8_DENSE=0 restores the per-read dequant for a
same-binary A/B. benchmarks/paged_attn_prefill_ab.cpp (new target) is the
isolated sweep: one vt::PagedAttention call per arm, so kernel cost is
separated from engine cost.

Two earlier shapes of the same lever are recorded as rejected rather than
deleted, because both are plausible and neither survives the numbers:

  1. routing fp8 through the bf16 WMMA ladder with the cast inside the K/V staging
    (8k 28.0 -> 9.1 s) — the per-element dequant stays on every re-stream;
  2. the hardware __nv_cvt_fp8_to_halfraw staging conversion — bit-identical and
    cheap, but still per re-stream.

Evidence

$ ./build-cuda/tests/test_ops_paged_attn
test cases:     33 |     33 passed | 0 failed | 0 skipped
assertions: 455286 | 455286 passed | 0 failed

$ ./build-cuda/tests/test_ops_paged_attn \
    -tc='paged_attention CUDA fp8-KV prefill (one-time dense dequant) matches f32 ref at head_dim 256'
MESSAGE: fp8-KV dense prefill max_abs_err vs f32 ref = 1.87755e-06
test cases: 1 | 1 passed | 0 failed | 32 skipped

Build: -DVLLM_CPP_CUDA=ON -DVLLM_CPP_CUTLASS_DIR=<local CUTLASS 4.x> -DVLLM_CPP_CUDA_ARCHITECTURES=120a on the local sm_120a card (RTX PRO 4000
Blackwell, 24 GiB). The 1.87755e-06 is the 1.9e-6 the commit body records — the
exact dequantized values, tighter than the bf16-compute envelope.

The new case pins the fp8 cache against the f32 reference at 1.9e-6 max abs err —
the exact dequantized values, tighter than the bf16-compute envelope — with the
other 33 cases unchanged.

The e2e numbers in the commit body are the AUTHOR's run on the local sm_120a
card, 27B NVFP4 arm, 2048-token chunks, no spec, VT_HOST_EMBEDDING=1, with the
bf16 store and llama.cpp's q8_0 KV as denominators on the same box:

32k prefill  34.9 s / 938 tok/s   (bf16 store 36.4 s / 901; llama.cpp q8_0 39.9 s / 822)
             peak 20.5 GiB        (bf16 store 22.5 GiB)
8k prefill   7.6 s / 1000 tok/s
decode MTP n=3  38.6 tok/s        (37.4 before)

They are recorded as the shape's shape evidence, NOT as a gate: the operator's
same-binary A/B under lease is owed.

  • commit style + trailer contracts OK; full scripts/agent-preflight.sh is
    RED AT BASE on this box (35 gates) and each failing gate was re-run on a
    clean origin/main worktree and fails there too
  • tests that cover this change: tests/vt/test_ops_paged_attn.cpp
  • no public doc changes: VT_ATTN_FP8_DENSE is a kernel-internal A/B knob,
    not a user-facing surface

Integration run on the official checkpoint

This branch alone cannot run the 27B artifact: without #3355's NVFP4 ownership
fix the first forward dies in marlin_repack: malloc bqt: out of memory (the
leak is characterized in PR #3355). The runs below therefore use a scratch tree
with #3355, #3356 and this change merged (not pushed), which is how the arm was
exercised end to end on the local sm_120a 24 GiB card:

$ ./build-cuda/examples/vllm-cli --model <Swift-1.5-Qwen3.8-27b-NVFP4 shards> --device cuda \
    --max-num-seqs 1 --kv-cache-memory 2000000000 --max-tokens 8 \
    --prompt "The capital of France is" --temperature 0 [--kv-cache-dtype fp8|bfloat16]
  • fp8 KV and bf16 KV produce the same 8 tokens ("The capital of Germany is")
    with the same prompt, both exit 0.
  • A 1440-token prefill with --max-tokens 2: fp8 5.063 s vs bf16 4.996 s
    (284.4 vs 288.2 prefill tok/s) — equal within noise at this length. The
    commit body's 938-vs-901 tok/s is a 32k-chunked-prefill measurement; this short
    run does not reproduce it and is NOT offered as a speed result.

This is a smoke test of the arm, not this PR's gate; the gate is the op parity
above.

Review follow-up, verified on Linux CUDA

The review's findings were fixed in b2f195e19 and re-verified here. Linux,
-DVLLM_CPP_CUDA=ON -DVLLM_CPP_CUTLASS_DIR=<local> -DVLLM_CPP_CUDA_ARCHITECTURES=120a:

$ ./build-cuda/tests/test_ops_paged_attn
test cases:     34 |     34 passed | 0 failed | 0 skipped
assertions: 455290 | 455290 passed | 0 failed

$ ./build-cuda/tests/test_ops_paged_attn \
    -tc='paged_attention CUDA fp8-KV prefill returns both scratch buffers to the pool when a later step throws'
test cases: 1 | 1 passed | 0 failed | 33 skipped
assertions: 4 | 4 passed | 0 failed

MUTATION of the guarantee the new case exists for — disable the scope guard's
free and rebuild cuda_paged_attn.cu:

ERROR: CHECK( Fp8DensePoolUsedBytesForTesting() == used_before ) is NOT correct!
  values: CHECK( 24584 == 12288 )      # 12,296 bytes retained
test cases: 1 | 0 passed | 1 failed;  assertions: 4 | 2 passed | 2 failed

Both injected failures (the identity allocation, and the identity copy after
both allocations succeeded) are covered, the original exception propagates, and
the pool-used-bytes assertion is what catches the leak when the guards are
removed.

The happy path is confirmed end to end on the official checkpoint in the
integration run above: fp8 KV generates the same 8 tokens as bf16 with host
embedding on (device_upload=9.676 GiB, exit 0).

Speed claims

  • This PR makes NO speed claim — checked as a helper PR: the numbers above
    are the author's, on a local card. The operator A/B under ${GPU_LOCK} and
    the docs/BENCHMARKS.md entry are owed and named in the spec.

Honest gaps

  • No operator-gated A/B, no docs/BENCHMARKS.md entry, and no dgx:gpu0
    reproduction. The numbers are one card, one artifact.
  • The dense scratch is allocated per layer with cudaMallocAsync under the
    stream; its own cost is inside the measured prefill time, not separated from
    it (the bench exists so a reviewer can separate them).
  • scripts/agent-preflight.sh is red at base (record drift, the
    e126687a9a vs a7c23ac96d oracle pin mismatch, check-test-registration's
    CMake probe, the missing file tool); every failing gate reproduces on clean
    main.

Refs ISSUE-LOCAL-01M3QG4WWC3X0C84M9PWQZAJ10

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]

dev added 2 commits September 29, 2026 21:12
The fp8 KV read routed every prefill to the scalar CUDA-core flash kernel, 3.7x
slower than the same kernel on a bf16 cache, because FA-2 admits only bf16
q/KV/out while the fp8 store presents f32. Record the dense per-layer dequant
shape, the two ladder levers it replaces and why, and the operator gate that
stays owed.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]
The fp8 KV-cache read sent every prefill to the scalar CUDA-core flash
kernel at f32 q/out, because the model presents f32 for a non-bf16 store
and the vendored FA-2 admission requires bf16 q, bf16 KV and bf16 out.
That is the whole 3.5x: nsys showed 25.1s of 36s in
PagedFlashKernel<float, unsigned char, ...> at 8k, while the bf16 store
ran the FA-2 split-KV kernel at 2.9ms/layer.

Dequantize the fp8 cache ONCE per layer into a dense bf16 scratch and run
the normal bf16 dispatch on it. The scratch is one block per request with
block_size = max_seq and an identity block table, so the kernel's paged
address IS the dense address and no attention kernel changes. The model
presents bf16 for an fp8 store through the new shared
`KvCachePresentsBf16` (the scratch is bf16), which admits FA-2 with zero
cast kernels; the CUDA path is model-agnostic, so any model that gains an
fp8 store inherits it. VT_ATTN_FP8_DENSE=0 restores the per-read dequant
for a same-binary A/B.

32k prefill (no spec, 2048-token chunks, host embedding): 34.9s / 938
tok/s, against 36.4s / 901 for the bf16 store and 39.9s / 822 for
llama.cpp's q8_0 KV, at 20.5 GiB against 22.5. 8k: 7.6s / 1000 tok/s.
Decode with MTP n=3: 38.6 tok/s against 37.4 before.

The op-level parity case pins the fp8 cache against the f32 reference at
1.9e-6 max abs err (the exact dequantized values); all 33 paged-attention
cases pass. benchmarks/paged_attn_prefill_ab.cpp and its CMake target land
with it: one vt::PagedAttention call per arm, so the kernel cost is
separated from the engine.

These numbers are the author's run on the local sm_120a card with host
embedding on; the operator's same-binary A/B under lease stays owed and is
recorded in the spec.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]
@iiLaurens
iiLaurens marked this pull request as ready for review September 29, 2026 22:12

@localai-org-maint-bot localai-org-maint-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new dense-scratch path leaks device memory on exceptions. In LaunchPagedFp8Out, the first cudaMallocAsync allocates dense, then the identity-table allocation, copy, launch checks, and LaunchPaged can all throw. Both frees occur only at the end of the success path. For example, if the identity-table allocation fails after the much larger dense allocation succeeds, Check throws and no owner frees dense. A failed request can therefore retain its scratch for the process lifetime and leave less memory for later requests.

Please use stream-aware scope guards/RAII for both allocations immediately after each successful allocation. Cover an injected failure after the first allocation, plus one after both allocations, and verify both buffers are released without masking the original exception.

The new benchmark also says every arm is verified against a bf16 reference with maximum absolute error, but it runs only the selected arm, prints a checksum, and returns zero without a comparison. Please implement the stated comparison or correct that claim; a checksum alone does not establish attention parity.

I read the complete diff at a40ba50. These are static findings; CUDA parity, failure injection, and the model speed/memory gate remain unexecuted here. @mudler @richiejp

The upstream review found that `LaunchPagedFp8Out` freed the dense bf16
scratch and the identity table only at the end of the success path. The
identity allocation, the identity copy, the dequant launch, the bf16
dispatch it feeds, and the vectors between them can all throw, so a
failed request held both buffers for the process lifetime. Each
allocation is now owned by a stream-aware scope guard from the moment it
succeeds; the guard frees on the same stream, so the free stays ordered
behind the work that reads the buffer, and it never throws, so the
original exception is not replaced. The success path keeps its explicit
checked frees through `release()`.

A new case in `test_ops_paged_attn` injects the two failures a healthy
device cannot produce on demand (the identity allocation, and the
identity copy after both allocations succeeded) through a test-only
seam, and checks that the CUDA memory pool's used bytes return to their
pre-call value after the unwind and that the propagated message is the
failing `Check`.

The same review found the A/B benchmark claimed a bf16 parity comparison
it did not perform. The header now states what the binary does (one arm,
a checksum, no parity gate) and points at `test_ops_paged_attn.cpp`,
which holds the f32-reference gate.

Verified in an MSVC 17.14 x64 Release CPU-only build: `test_ops_paged_attn`
compiles (the new case links through its CPU no-op stubs and skips at
runtime without a CUDA backend) and `vllm_paged_attn_prefill_ab` builds.
Execution of the injected case needs a CUDA device; the CUDA lane is its
gate.

Refs ISSUE-LOCAL-01M3QG4WWC3X0C84M9PWQZAJ10

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: opencode-go:deepseek-v4.1-flash [pi]
@iiLaurens

Copy link
Copy Markdown
Contributor Author

Repairs pushed at b2f195e19.

Scratch ownership. The dense bf16 scratch and the identity table are now each
owned by a stream-aware scope guard from the moment the allocation succeeds. The
identity allocation and copy, the dequant launch, the bf16 dispatch it feeds, and
the vectors between them all unwind through the guards, which free on the same
stream (ordered behind the work that reads the buffer) and never throw, so the
original exception is not replaced. The success path keeps its explicit checked
frees through release().

Coverage. A new case in test_ops_paged_attn injects both failures the
review asked for — the identity allocation (after the dense scratch exists) and
the identity copy (after both allocations succeeded) — through a test-only seam,
then asserts the CUDA memory pool's used bytes return to their pre-call value and
that the propagated message is the failing Check. The case skips when no CUDA
device is registered; this host has none, so its execution is owed to the CUDA
lane.

Benchmark claim. The header no longer claims a bf16 parity comparison it does
not perform. It now states that the binary runs one arm and prints a checksum,
and points at test_ops_paged_attn.cpp for the f32-reference parity gate.

Evidence: MSVC 17.14 x64 Release CPU-only — test_ops_paged_attn and
vllm_paged_attn_prefill_ab compile and link.

@iiLaurens

Copy link
Copy Markdown
Contributor Author

Addressed in b2f195e19.

  • Both allocations are now owned from the moment each succeeds. LaunchPagedFp8Out wraps the dense scratch and the identity table in a stream-aware scope guard: the free runs on the same stream, so it stays ordered behind the dequant and the bf16 dispatch that read the buffer, and the destructor never throws, so the original Check exception is not replaced. The success path keeps its explicit checked frees through release().
  • The benchmark claim is corrected. The header now states what the binary does — one selected arm, a checksum, no comparison — and points at test_ops_paged_attn.cpp, which holds the f32-reference parity gate.

Both injected failure points you asked for are covered by paged_attention CUDA fp8-KV prefill returns both scratch buffers to the pool when a later step throws: the identity allocation failure, and the identity copy failure after both allocations succeeded. It asserts the pool's used bytes return to the pre-call value and that the propagated message is the failing Check.

Verified on Linux with CUDA + CUTLASS at arch 120a on the local sm_120a card:

$ ./build-cuda/tests/test_ops_paged_attn
test cases:     34 |     34 passed | 0 failed | 0 skipped
assertions: 455290 | 455290 passed | 0 failed

$ ./build-cuda/tests/test_ops_paged_attn \
    -tc='paged_attention CUDA fp8-KV prefill returns both scratch buffers to the pool when a later step throws'
test cases: 1 | 1 passed | 0 failed | 33 skipped
assertions: 4 | 4 passed | 0 failed

MUTATION of the guarantee that case exists for — disable the scope guard's free and rebuild cuda_paged_attn.cu:

ERROR: CHECK( Fp8DensePoolUsedBytesForTesting() == used_before ) is NOT correct!
  values: CHECK( 24584 == 12288 )      # 12,296 bytes retained
test cases: 1 | 0 passed | 1 failed;  assertions: 4 | 2 passed | 2 failed

Restored, the case is 4/4 green again. The happy path is confirmed end to end too: with the scratch guards in place, fp8 KV generates the same 8 tokens as bf16 on the official ukisai/Swift-1.5-Qwen3.8-27b-NVFP4 checkpoint with host embedding on (device_upload=9.676 GiB, exit 0).

mudler-agent pushed a commit that referenced this pull request Oct 2, 2026
## Row

`LOAD-MODELOPT-NVFP4-BORROW` — one row per PR. Spec:
`.agents/specs/nvfp4-one-owner-per-resident.md`.

## Before starting

- Issue/PR search: no open issue described this leak. Issue #1647
(ModelOpt
borrow) is a different defect in the same row and closed in flow. Filed
  `ISSUE-LOCAL-01M3QDT6A8JEM8WNJTPR16MXHR`.
- Pull request shape selected at row claim: one PR for the spec and the
  implementation.
- Row: `LOAD-MODELOPT-NVFP4-BORROW` in `.agents/engine-matrix.md`; the
row spec
`load-modelopt-nvfp4-borrow.md` owns `ResidentNvfp4`'s upload and
adoption path.
- Anchors inspected: `Nvfp4Weight` (`qwen3_5_weights.h:672-712`),
`OwnedTensor::d_dev`
(`:195`), both `ResidentNvfp4` copies (`dense_nvfp4_gemm.h:319`,
`qwen3_5.cpp:1449`),
and every release site (`dense_nvfp4_gemm.h:448,667`;
`qwen3_5.cpp:2952,3132,6890`;
  `laguna.cpp:650`).

## What changed

`Nvfp4Weight` published one device allocation on two owning handles: the
type-specific `d_packed`/`d_scale` and the generic
`packed.d_dev`/`scale.d_dev`
slot that `AdoptDeviceBytesAsHost` keys on. Every Marlin repack builder
reset only
the type-specific pair, so the `d_dev` alias kept a full packed+scale
device copy
alive for the process lifetime — once per repacked weight, and once per
expert per
projection on an MoE model. That undoes the repack's whole point ("then
FREE the
fp4 originals") on exactly the 24 GiB cards the NVFP4 arms serve from.

The change makes `d_dev` the one owner. The type-specific members are
deleted (a
remaining reader fails to compile), both upload copies publish into
`d_dev`
directly, and `Nvfp4Weight::ReleaseResident()` is the single release: it
drops an
adopted host twin view first (that view holds the same block on a
host-addressable
device), then resets both slots.
`scripts/check-fp4-resident-consistency.py`, which
pinned the old two-handle contract, is updated in the same change: its
publication
clause now requires a backend-owned allocation and a new clause requires
the
release to reset BOTH slots with the twin drop first.

## Evidence

RED, before the fix (the new case, against unmodified `main`):

```
$ ./build/tests/test_load_direct_upload -tc="fp4 resident: releasing the resident frees both device buffers"
CHECK( b.frees == 2 ) is NOT correct!
  values: CHECK( 0 == 2 )
test cases: 1 | 0 passed | 1 failed | 16 skipped
```

The invariant is that a release through the weight's own resident state
frees both
buffers. Pre-fix it freed zero: the surviving `packed.d_dev` alias owned
the block.

GREEN, after the fix:

```
$ ./build/tests/test_load_direct_upload
test cases:  17 |  17 passed | 0 failed | 0 skipped
assertions: 206 | 206 passed | 0 failed
```

Checker, including its mutation suite:

```
$ python3 scripts/check-fp4-resident-consistency.py
OK: 2 ResidentNvfp4 definition(s) across 2 file(s) count the upload, copy,
publish d_dev, and adopt — per buffer, for both packed and scale — and
ReleaseResident() resets both d_dev slots.

$ python3 -m unittest tests.scripts.test_check_fp4_resident_consistency
Ran 53 tests ... OK
```

The new release clause goes red under each live-tree mutation of
`qwen3_5_weights.h`: drop `packed.d_dev.reset()`, drop
`scale.d_dev.reset()`, drop
either adopted-twin drop, move a twin drop after its reset; and the
publication
clause under `= nullptr` or a bare alias of another object's `d_dev`.

- [x] `python3 scripts/check-agent-record.py` OK; commit style and
trailer
      contracts OK for both commits
- [x] tests that cover this change:
`tests/vllm/test_load_direct_upload.cpp`,
      `tests/scripts/test_check_fp4_resident_consistency.py`
- [x] public docs unchanged (no user-facing fact changed)
- [x] full `ctest --test-dir build`: **795 of 809 pass**, and every one
of the 14
      failures reproduces on a clean `origin/main` (`b45a94273`) build:
`test_model_loader_gguf`, `test_gliner2_e2e`, `test_gdn_v_head_permute`,
      `test_deepseek_v4_exl3_loader`, `test_qwen4_exp_layer_loop`,
      `test_glm4_moe_lite_paged_engine`, `test_bench`,
`test_bench_kv_cache_dtype`, `test_bench_eos_chat_template`,
`test_capi`,
`test_qwen3_dense_async_serving`, `test_qwen3_paged_engine` (each run as
a
base binary, exit 1), plus `test_cpu_threadpool` (segfault/abort 3/3 on
base) and `test_serve_low_tools` (fails on base).
`test_serve_deepseek_v4_mm`
hangs in `accept()` on this box and is `Not Run` in the base ctest.
- [ ] full `scripts/agent-preflight.sh` — RED ON THIS BOX AT BASE. Each
failing
checker was re-run on a clean `origin/main` worktree and fails there
too;
      none of the 35 red gates touches a file this PR changes.

## End-to-end A/B on the official NVFP4 safetensors checkpoint

The unit gate proves the release frees; this proves the release MATTERS
on a real
artifact. `ukisai/Swift-1.5-Qwen3.8-27b-NVFP4` (21.94 GB, 5 shards,
public and
ungated), the ModelOpt NVFP4 + FP8 checkpoint the local GGUF is
converted from,
run on the local `sm_120a` card (RTX PRO 4000 Blackwell, 24 GiB) under
the file
mutex, same command, same binary build recipe, only the branch differs:

```
$ VT_LOAD_STATS=1 ./build-cuda/examples/vllm-cli --model /tmp/models/Swift-1.5-Qwen3.8-27b-NVFP4 \
    --device cuda --kv-cache-dtype fp8 --max-num-seqs 1 --kv-cache-memory 2000000000 \
    --max-tokens 8 --prompt "The capital of France is" --temperature 0
```

| Tree | Result |
|---|---|
| **This branch (fix)** | exit 0: generated 8 tokens (`" Paris.\nThe
capital of Germany is"`), 1.064 tok/s, `device_upload=12.044 GiB` |
| `main` + the unrelated PermuteVHeads CUDA kernel (PR #3359) | **exit
1: `engine-fatal: EngineCore busy loop threw: vt cuda: marlin_repack:
malloc bqt: out of memory`** at `device_upload=8.619 GiB` |
| `main` + the fp8-KV prefill change (PR #3360) | **the same OOM**, same
`device_upload=8.619 GiB` |

Two independent trees without the fix die in the NVFP4 Marlin repack;
the fix
reaches the same repack and finishes. That is the leak:
`BuildMarlinDenseResident`
repacks a weight and frees the fp4 originals only through the
type-specific handle,
and the surviving `d_dev` alias keeps a full packed+scale copy alive per
repacked
weight — so the first forward runs out of card. The repack's own comment
says the
free is the point ("we do this repack lazily on first forward and then
FREE the
fp4 originals"), and on a 24 GiB card a second copy is the difference
between
serving and OOM.

## Speed claims

- [x] This PR makes NO speed claim. It frees a leaked allocation; no
timing was
      taken and none is implied.

## Honest gaps

- No end-to-end GiB/RSS number. The gate proves the release frees both
buffers
through the fake backend; it does not measure a repacking serve's
process RSS.
- The mutation evidence is author-run. A fresh reviewer over the
immutable head
  still owes its own mutation pass.
- `scripts/agent-preflight.sh` on this box is red at base (35 gates):
record
drift in `.agents/completed/` and `docs/benchmarks/`, supported-models
drift,
the oracle pin mismatch (`e126687a9a` vs `a7c23ac96d` in
`upstream-sync.md`),
and one environment FAIL (`file` is not installed for the
release-archive
test). Every one reproduces on clean `main`, and none touches a changed
file.

Refs ISSUE-LOCAL-01M3QDT6A8JEM8WNJTPR16MXHR

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]

---------

Co-authored-by: dev <dev@local>

This branch has not been deployed

No deployments
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.

2 participants