Repository navigation
KV offload: defect register, measurements and roadmap - #31
Closed
Piggidragon wants to merge 8 commits into
Closed
Piggidragon wants to merge 8 commits into
Piggidragon wants to merge 8 commits into
Conversation
Piggidragon
force-pushed
the
beellama/kv-host-decode-transport
branch
from
August 24, 2026 18:21
460bf8b to
bc549ee
Compare
This branch carries no code change. It is a measurement ground and a plan, built fresh from beellama/dev at 946c1e5. Every defect was re-confirmed on that base with a clean build tree, or is marked as not confirmed. Four of six reproduce from the scripts committed here; D5 needs a GGML_CUDA_KVARN=ON tree and its numbers are labelled as carried forward; D6 could not be reproduced at all and says so. docs/kv-offload-defects.md -- six entries, each with the exact command, the exact failing output, and the owning layer: D1 head dimension 320 routed to a vector kernel that has no D320 instance, aborting at fattn.cu:412. Reachable at nr23=[4,1], which is a lower ratio than PR #4 recorded, so the documented reproducer was narrower than the behaviour. Owner: BeeLlama. D2 divergent __syncthreads() localized to fattn-mma-f16.cuh:1732, 100 of 100 synccheck errors at one line, both lines blamed to upstream. Balancing the barrier count is necessary but not sufficient: the guard threadIdx.y % np == 0 is thread-dependent, so both barriers sit in divergent code. Owner: upstream. D3 MTP with a host-resident cache aborts with an illegal memory access, 1 in 3 runs, reproduced twice independently. D4 test-kvarn aborts on any default build with a CUDA device present, so the validation command CLAUDE.md documents cannot pass as written. Owner: BeeLlama. D5 host-resident KVarN falls to the CPU backend and reserves ~0.099 MiB of host workspace per context token, 25.9 GiB at 262,144. Not re-run here. D6 quantized K/V with logit_softcap: NOT reproduced. The suite carries exactly one quantized softcap case and it passes. docs/kv-offload-measurements.md -- the numbers and what they rule out. The decode token is copy + compute in series, confirmed two independent ways to within 4%. Removing the entire store side is worth 0.18%. Removing the ordering constraint reaches 96-99% of max(copy, compute): +21.3% / +77.0% / +60.0% at 4,096 / 16,384 / 32,768. Bytes, the store side and allocation properties are all ruled out as levers; ordering is the lever. docs/kv-offload-roadmap.md -- eight items with owner, size, blast radius, blocking relationships and acceptance gates, plus the two decisions that must be made before anyone writes code: where the staging ring lives, and who implements KVarN host-residency. docs/repro/ -- one script per defect and per measurement, all syntax-checked and four of them executed end to end for this commit. They serialize on a lock because concurrent GPU jobs produce failures that look like defects. docs/probes/ -- the five measurement patches, verified to apply to pristine 946c1e5 and to revert without residue. Three produce incorrect output by construction and say so. None is a candidate implementation. The KVarN fail-closed guard from the earlier branch is deliberately not here. It converts a slow configuration into an error message, which is not a fix and gets in the way of the real one; the integration is roadmap item R6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E8bBMKTLSzVbDBSUfdQc9N
Piggidragon
force-pushed
the
beellama/kv-host-decode-transport
branch
from
August 24, 2026 20:43
2a491ff to
c602c83
Compare
The synccheck run that localized the divergent barrier was taken on a -lineinfo tree carrying the transport-telemetry patch, which touches ggml-cuda.cu and not fattn-mma-f16.cuh and is inert unless its environment variable is set, so the kernel under test is bit-identical to a pristine build. Says so rather than implying the run came from a pristine tree, and names the re-run as the one verification step still outstanding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E8bBMKTLSzVbDBSUfdQc9N
Wiped build-li, reconfigured from scratch at 946c1e5 with -lineinfo only, and re-ran docs/repro/d2-barrier.sh. Identical result: 100 barrier errors, all at fattn-mma-f16.cuh:1732, the same two fixup instantiations of flash_attn_ext_f16_process_tile<256,256,4,8,4,...,F16,F16>, the same call frame at :2104. The request also aborted under the sanitizer, which is D3 firing. Removes the provenance caveat: the localization no longer rests on a tree that carried any patch, and every entry in the register except D5 is now reproduced from the committed scripts against a clean build of the current dev head. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E8bBMKTLSzVbDBSUfdQc9N
…em R9 Found while trying to re-measure D5: the KVarN configuration cannot be built on 946c1e5 at all. 82 compile errors, all "no instance of function template flash_attn_ext_f16_process_tile", at fattn-mma-kvarn-case.cuh(178). flash_attn_ext_f16_process_tile gained a parameter, causal_prefix_first_bound_smem, immediately after compact_causal_prefix in 0a85856. The non-KVarN caller at fattn-mma-f16.cuh:2105 passes it; both KVarN call sites, at fattn-mma-kvarn-case.cuh:178 and :245, still pass the old argument list. git blame attributes the signature change and both stale call sites to the same author, so this is BeeLlama's on both ends. The fix is two lines, one nullptr per call site. Included as probes/06-kvarn-build-fix.patch and verified: with it the build completes with exit 0 and zero errors, without it 82. It is a reference, not a proposal -- per the shape of this branch it should land as its own change, which is roadmap item R9. This also explains and strengthens D5's status. D5 was previously "not re-run"; the correct statement is that nobody can re-run it as the tree stands. With D7 patched, the kvarn5 arm was re-measured on a fresh build and reproduces the carried-forward figure exactly: 2,195.90 MiB of host compute buffer at -c 8192 against 122.01 MiB on the device. Also fixes docs/repro/d5-kvarn-workspace.sh, which emitted a 466 MB log because GGML_SCHED_DEBUG=2 and -v were on by default. The node breakdown is now behind LLAMA_KV_SCHED_DEBUG=1 with a warning about the size. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E8bBMKTLSzVbDBSUfdQc9N
The four measurement scripts defaulted their depth list with
`for D in "${@:-4096 16384 32768}"`. The quotes make the default a single
word, so the no-argument form -- the one the README documents -- passed all
three depths as one field. m1 and m4 interpolated it into `-d $D`, which
word-split into `-d 4096 16384 32768`; llama-bench rejected the trailing
values as unknown arguments and exited 1 before measuring anything. m2 and m3
passed the fields as separate positional arguments to `run`, so the second
depth landed in `-r`: 16384 (m3) and 32768 (m2) benchmark repetitions at a
single depth instead of 3 at three depths, and `[ "$D" -le 4096 ]` failed with
"integer expression expected". Use a real default array instead.
d5 reported `exit=$?` after a pipeline ending in `head`, so the non-sched
branch always printed 0 even when llama-cli failed to load the model -- which
is the failure the script exists to surface, since D5 needs a
GGML_CUDA_KVARN=ON build that does not compile without probe 06. Record
PIPESTATUS[0] instead. Also carries the pending -v and filter-width edits to
the same branch.
Verified with stubbed llama-bench/llama-cli: m1 now issues six runs over the
three depths, m2/m3 issue -r 5 at 4096 and -r 3 above it, m4 issues three runs
at -r 3, explicit-argument invocation is unaffected, and d5 reports the
child's status in both branches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vezsq8VRiPzENCmhCg8q41
With the D7 patch applied so the configuration compiles at all, both arms of D5 were measured at -c 8192 host-resident on a fresh GGML_CUDA_KVARN=ON build: kvarn5 CUDA0 122.01 MiB CUDA_Host 2,195.90 MiB q8_0 CUDA0 142.27 MiB CUDA_Host 20.28 MiB Both reproduce the carried-forward figures exactly -- 108x more host workspace for the cache that is supposed to be the smaller one. The device side moves the other way, kvarn5 reserving less on the GPU than q8_0, which is the tell that the work moved to the host rather than being avoided. Only the context-scaling table and the per-node breakdown remain from an earlier session, and both are now labelled as such at the point of use. Also fixes docs/repro/d5-kvarn-workspace.sh a second time: the previous fix dropped -v, but the sched_reserve lines that report the compute buffers are verbose-only, so the script filtered away exactly what it was supposed to capture. GGML_SCHED_DEBUG is what makes the output enormous, not -v. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E8bBMKTLSzVbDBSUfdQc9N
Two corrections, both about overstating validation. First, docs/repro/README.md listed all scripts as if equally verified. They were not: every d* script was executed end to end and the output in the defect register is what it printed, but the m* scripts were only syntax-checked before being committed, which is exactly how the argument-handling bug in bba4e10 survived -- `bash -n` does not catch `"${@:-a b c}"`. The table now carries a "verified how" column and says plainly that the m* scripts are tidied-up forms of the ad-hoc commands that produced the numbers, not the commands themselves. Second, m4 has now been run against build-clean rather than assumed: 4,096 39.5148 +- 0.1561 16,384 36.2507 +- 0.1053 32,768 32.7283 +- 0.0958 against the recorded 39.0152 / 36.1072 / 32.5832, which is 0.4-1.3% lower. The measurement table quotes the fresh figures, keeps the earlier ones in a note, and states the effect: the implied copy term moves by at most 0.33 ms and the agreement with the independently measured blocking H2D improves from 6.6/4.0/2.2% to 1.4/3.5/1.9%. Both sets support the same conclusion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E8bBMKTLSzVbDBSUfdQc9N
Piggidragon
marked this pull request as ready for review
August 24, 2026 21:21
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch contains no code change. It is a measurement ground and a plan,
rebuilt from
beellama/devat946c1e5b6with a clean tree and a clean build.It started as a search for new decode optimizations for a host-resident KV cache.
Current status update
beellama/dev; D7 remains historical evidence for this PR's base commit, not a current-devclaim after fix: restore CUDA KVarN build #34 lands.The optimizations were built, measured, and all but one rejected — and the
measurements falsified two conclusions this lane had already recorded. What is
worth keeping is the evidence and the plan, so that is what this PR is.
docs/kv-offload-roadmap.mddocs/kv-offload-defects.mddocs/kv-offload-measurements.mddocs/repro/docs/probes/946c1e5b6Defects, re-confirmed on this base
fattn.cu:412__syncthreads()in the MMA FA kerneltest-kvarnaborts on any default build with a CUDA devicelogit_softcapGGML_CUDA_KVARN=ONdoes not compileq2_0sFlashAttention returns wrong values athsk=64test-upstream-merge-keepers-staticfails on a clean checkoutD6 is listed precisely because it could not be confirmed. PR #4 records it; the
suite enumerates
logit_softcap != 0only athsk == 128and has exactly onequantized softcap case, which passes.
D1 is broader than recorded. PR #4 gave the confirmed reproducer as GQA 32;
on
946c1e5b6the suite aborts atnr23=[4,1], kv=512, nb=75.D7: historical KVarN-build failure; PR #34 supplies the repair
On this PR's base,
GGML_CUDA_KVARN=ONdid not compile at all — 82 errors, allno instance of function template flash_attn_ext_f16_process_tileatfattn-mma-kvarn-case.cuh(178).flash_attn_ext_f16_process_tilegained a parameter,causal_prefix_first_bound_smem, in0a85856ee. The non-KVarN caller passes it;both KVarN call sites, at
:178and:245, still pass the old argument list.git blameattributes the signature change and both stale call sites to thesame author.
The fix is two lines, one
nullptreach, and it is verified rather thanasserted: with
probes/06the buildcompletes with exit 0 and zero errors, without it 82. PR #34
carries that repair. It remains pending merge and validation on
beellama/dev.D2, localized
compute-sanitizer --tool synccheck --show-backtrace deviceon a pristine-lineinfobuild gives 100 barrier errors, all at one line:np == nwarps == 4, so onlythreadIdx.y == 0takes theif. Balancing thecount is necessary but not sufficient —
__syncthreads()is defined only whereits guard evaluates identically across the block, and
threadIdx.y % np == 0does not.
git blameputs both lines on upstream's CUDA maintainer(
0208355f42,95e18884fc); the fork owns neither.This corrects an earlier note in our own record, which said the balancer "is
present and correct" and concluded the divergence was elsewhere. The first half
was right and the conclusion was wrong.
The measurement that changes the plan
The decode token is
copy + computein series. Two independent routes agreeto within 4%: the telemetry's blocking H2D, and subtracting a device-resident arm.
Remove the ordering constraint — deliver on a copy stream nothing waits on,
incorrect output, upper bound only — and the copy engine hides 96–99% of it:
max(copy,compute)The penalty is the ordering, not the bandwidth. Transfers already run at the
practical PCIe 4.0 x16 rate.
Two things this lane had wrong
all 32 CPU
set_rowsnodes and 32 of 33 splits per token is worth +0.18%at 32,768 and nothing at 4,096. The store moves 34.8 KB per token, not the
2.2 MB an earlier revision recorded — wrong by ~60x, and that error is what
made the store look like a plausible culprit.
it could not. Both were argued from structure; both fell to measurement.
Rejected with evidence, each in
measurements §5:async H2D on the compute stream (+0.13%), cross-layer prefetch (+1.38% and not
exact), write-combined pinned memory (−0.85%), huge pages (null result, IOMMU
premise absent on this host), NUMA (untestable, one socket).
Roadmap
__syncthreads()-nkvotest-kvarnon default buildsGGML_CUDA_KVARN=ONbuildq2_0sFlashAttention athsk=64test-upstream-merge-keepers-staticOrder. R1 first: it gates the two largest numbers on record — MTP on a
host-resident cache is +75.9% on a 24,000-token prompt where it completes, and
PR #4's vector/MMA crossover is +11.59% on decode at 32,768 and cannot be taken
while lowering the threshold routes more work onto the diverging shapes. It is a
defect fix, not a feature, and it belongs upstream.
Then merge and validate R9 / PR #34. R7 is deliberately not planned because of default CUDA build time. Then the other small BeeLlama items. Then R4.
Two decisions are needed before anyone writes code. Where the staging ring
for R4 lives — fork-local or a generalization of upstream's
n_copiesmachinery. And who implements KVarN host-residency, which is a conversation
between us and BeeLlama rather than a technical question.
Supersedes PR #32
That PR added a fail-closed guard for KVarN +
--no-kv-offload. The guard works— verified at runtime, it refuses with a named error instead of degrading — but
it converts a slow configuration into an error message, which is not a fix and
gets in the way of the real one. The analysis is folded into D5 and the
integration is roadmap item R6. #32 is closed.
Validation
RTX 4070 (12 GB, sm_89, CUDA 13.3), i5-13400F, one socket, no IOMMU. All
processes under
taskset -c 0,2,4, one GPU job at a time behind a lock —concurrent jobs produced a CUDA OOM and an arm with
σ = 1.72against a normalσ ≈ 0.06; both were discarded and re-run. ModelQwen3.8-27B-UD-IQ2_M.gguf,q8_0/q8_0,-b 512 -ub 512 --parallel 1.llama-bencharms are--no-warmupwith reverse-order A/B/A/B pairs; server arms are greedy,
seed 1234.Every
d*script was executed end to end and the output in the defect registeris what it printed. The
m*scripts are tidied-up forms of the ad-hoc commandsthat produced the measurements;
m4has since been run againstbuild-clean,m1–m3are verified for argument handling but have not regenerated theirnumbers.
docs/repro/README.mdstates this per script.