Research Stage
Previous existing literature and research
Hi,
I have been chasing a scaling oddity in the KVarN store path on my box (7900 XTX,
gfx1100) and I am out of ideas for what to measure next, so I figured I would ask.
Setup: 27B dense model (D256, GQA 6, 24/4 heads), kvarn5 for K and V, prefill with
ub 512. Timing comes from the built in GGML_KVARN_PROFILE event pairs with
GGML_CUDA_DISABLE_GRAPHS=1.
For reference, q8_0 on the same binary and box does 912 / 835 / 712 t/s at
8k / 16k / 32k, i.e. minus 22% over the range where kvarn5 loses 32%
(550 / 475 / 376 t/s). The gap widens with context (0.60x down to 0.53x of q8_0),
so this looks KVarN specific rather than general long context scaling.
| pp |
q8_0 |
q5_0 |
kvarn5 |
kvarn5 / q8_0 |
| 8192 |
911.95 t/s |
907.39 t/s |
549.58 t/s |
0.60x |
| 16384 |
834.50 t/s |
832.27 t/s |
475.26 t/s |
0.57x |
| 32768 |
711.97 t/s |
710.26 t/s |
376.10 t/s |
0.53x |
What I see: V store_low per-call mean climbs all the way from 8k to 65k context
at a fixed launch grid (4 blocks). K store over the same range is flat.
| ctx |
8k |
16k |
24.6k |
32k |
49k |
65k |
| V mean/call |
36.5 ms |
44.1 |
51.6 |
59.1 |
74.3 |
90.0 ms |
| K mean/call |
~7.7 ms flat throughout |
|
|
|
|
|
The V curve is smooth (about +0.94 ms per 1k tokens, exponent around 1.6), not
stepwise. At 32k, V totals are 121 s vs 15.9 s for K, so about 7.6x apart with
the same grid shape.
Things I ruled out on my side:
- Route is stable
prompt-generic-mma / compact-tail from 512 to 65536 (route debug env).
--flash-attn off gives byte identical V totals (121.1 vs 121.0 s at 32k), so it is not queued FA work on the stream.
- Host side
live_stage_groups planning is 11.8 ms total over an 87 s prefill, so not the host loop.
- kvarn2/5/8 all show the same exponent despite 4x record bytes, so not payload size.
Two more discriminators since:
- Ub sweep at fixed 32k. K behaves normally (totals fall monotonically as ub grows,
mean linear in ub). V totals are U shaped with the minimum at the default ub 512,
and the same U shape shows at 8k. So V per-chunk cost is superlinear in ub at any
fixed position, while K is linear.
| ub |
128 |
256 |
512 |
1024 |
2048 |
| K total @32k |
17.51 s |
16.55 |
15.76 |
14.34 |
12.99 s |
| V total @32k |
137.73 s |
123.04 |
121.20 |
129.12 |
141.42 s |
| V total @8k |
23.10 s |
(not run) |
18.74 |
(not run) |
21.58 s |
- Asymmetric bit widths at 32k/ub512. The gap follows the side, not the bits. Each
side only cares about its own bits, and V moves all of 0.7% going from 2 to 8 bits.
| cfg |
K mean |
V mean |
V/K gap |
| kvarn5 (sym) |
7.70 ms |
59.18 ms |
7.7x |
| kvarn2 (sym) |
7.27 ms |
57.36 ms |
7.9x |
| kvarn8 (sym) |
8.43 ms |
57.09 ms |
6.8x |
| K8V2 |
8.30 ms |
57.33 ms |
6.9x |
| K2V8 |
7.29 ms |
57.73 ms |
7.9x |
I also checked the store call site (llama-kv-cache-kvarn.cpp, store()): same
sinkhorn iters (16), same stage/tail groups, same slices (2), same grid for K and V.
The only difference is the value flag. So as far as I can tell, the level gap, the
position slope, and the ub superlinearity are all properties of the value=true path,
i.e. the transposed stage reads in kvarn_quantize_stage_lowshmem via
kvarn_stage_rotated_value (token = value ? row : col).
My question: what in that path could grow with cache position and with ub at fixed
per-flush work? The flush kernel reads O(128) stage data and writes one fixed size
record, so I would expect constant cost per flush, but per-flush cost goes from
9.2 ms at 8k to 14.8 ms at 32k (ub512). Is there something striding over a structure
that grows with n_kv that I am not seeing, or an interaction with the record sweep /
L2 at large ub? Is there a debug counter worth adding, or a knob I can use to test
this without rebuilding?
Happy to run anything you suggest on this box and report back. Raw numbers and logs
available on request.
Hypothesis
No response
Implementation
No response
Analysis
No response
Relevant log output
Research Stage
Previous existing literature and research
Hi,
I have been chasing a scaling oddity in the KVarN store path on my box (7900 XTX,
gfx1100) and I am out of ideas for what to measure next, so I figured I would ask.
Setup: 27B dense model (D256, GQA 6, 24/4 heads), kvarn5 for K and V, prefill with
ub 512. Timing comes from the built in
GGML_KVARN_PROFILEevent pairs withGGML_CUDA_DISABLE_GRAPHS=1.For reference, q8_0 on the same binary and box does 912 / 835 / 712 t/s at
8k / 16k / 32k, i.e. minus 22% over the range where kvarn5 loses 32%
(550 / 475 / 376 t/s). The gap widens with context (0.60x down to 0.53x of q8_0),
so this looks KVarN specific rather than general long context scaling.
What I see: V
store_lowper-call mean climbs all the way from 8k to 65k contextat a fixed launch grid (4 blocks). K store over the same range is flat.
The V curve is smooth (about +0.94 ms per 1k tokens, exponent around 1.6), not
stepwise. At 32k, V totals are 121 s vs 15.9 s for K, so about 7.6x apart with
the same grid shape.
Things I ruled out on my side:
prompt-generic-mma / compact-tailfrom 512 to 65536 (route debug env).--flash-attn offgives byte identical V totals (121.1 vs 121.0 s at 32k), so it is not queued FA work on the stream.live_stage_groupsplanning is 11.8 ms total over an 87 s prefill, so not the host loop.Two more discriminators since:
mean linear in ub). V totals are U shaped with the minimum at the default ub 512,
and the same U shape shows at 8k. So V per-chunk cost is superlinear in ub at any
fixed position, while K is linear.
side only cares about its own bits, and V moves all of 0.7% going from 2 to 8 bits.
I also checked the store call site (
llama-kv-cache-kvarn.cpp,store()): samesinkhorn iters (16), same stage/tail groups, same slices (2), same grid for K and V.
The only difference is the
valueflag. So as far as I can tell, the level gap, theposition slope, and the ub superlinearity are all properties of the
value=truepath,i.e. the transposed stage reads in
kvarn_quantize_stage_lowshmemviakvarn_stage_rotated_value(token = value ? row : col).My question: what in that path could grow with cache position and with ub at fixed
per-flush work? The flush kernel reads O(128) stage data and writes one fixed size
record, so I would expect constant cost per flush, but per-flush cost goes from
9.2 ms at 8k to 14.8 ms at 32k (ub512). Is there something striding over a structure
that grows with n_kv that I am not seeing, or an interaction with the record sweep /
L2 at large ub? Is there a debug counter worth adding, or a knob I can use to test
this without rebuilding?
Happy to run anything you suggest on this box and report back. Raw numbers and logs
available on request.
Hypothesis
No response
Implementation
No response
Analysis
No response
Relevant log output