Skip to content

feat(KV-FP8): W6 — the ROCm fp8-e4m3 KV cache store and read - #2080

Merged
localai-bot merged 4 commits into
mudler:mainfrom
ghazni101:row/KV-FP8-ROCM
Aug 28, 2026
Merged

feat(KV-FP8): W6 — the ROCm fp8-e4m3 KV cache store and read#2080
localai-bot merged 4 commits into
mudler:mainfrom
ghazni101:row/KV-FP8-ROCM

Conversation

@ghazni101

@ghazni101 ghazni101 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

What changed

The ROCm backend gains the fp8-e4m3 KV cache store kernel
(ReshapeAndCacheFp8KernelRocm in src/vt/rocm/rocm_dense_basic.hip) and
the fp8 dequant on the paged-attention read (LoadKv in
src/vt/rocm/rocm_paged_attn.hip). This closes the last non-Metal gap in
the fp8 KV cache surface.

OpId::kReshapeAndCacheFp8 is registered for DeviceType::kROCM in
src/vt/rocm/rocm_ops.hip. The fp8 read refusal in src/vt/ops.cpp
widens from kCPU || kCUDA to kCPU || kCUDA || kROCM. Metal remains
refused by name.

ROCm HIP has no __nv_cvt_float_to_fp8 intrinsic, so the store uses a
software codec (F32ToF8E4M3Dev) with frexpf/nearbyintf/ldexpf
arithmetic that mirrors the CPU vt::F32ToF8E4M3. The codec is
bit-identical and portable across all ROCm targets, unlike hardware fp8
conversion intrinsics that exist only on CDNA2+ (gfx940/941/942).

Why

The fp8 KV cache halves the KV footprint. The CPU arm (W1) and CUDA arm
(W2) already landed; the ROCm arm was the last refused backend. Issue
#2065 tracks this work.

How to verify

Build with HIP and run the test:

cmake -B /tmp/build -DVLLM_CPP_HIP=ON
make -j4 vllm test_rocm_fp8_kv_cache
ctest -R test_rocm_fp8_kv_cache

Measured on gfx1100 (RX 7900 XTX), hipcc 7.2.4:

  • test_rocm_fp8_kv_cache: 7/7 cases, 28/28 assertions. G3 store
    byte-identical to the W1 CPU oracle; G4 f32 read NMSE < 1e-6; G4b bf16
    read NMSE < 1e-4; G5 e5m2 refused with the named message.
  • test_ops_fp8_kv_cache: 8/8, 511 assertions (CPU regression).
  • test_rocm_backend: 9/9, 1065 assertions (no regression).
  • test_ops_paged_attn: 14/14, 1646 assertions (no regression).

RED-first proven: dropping *scale in the read dequant fails G4/G4b at
NMSE ~34000x; dropping /scale in the store fails G3 at the byte level.

Out of scope

fp8_e5m2 compute, per-attention-head scales, the Metal arm, fast-path
(tensor-core/rocWMMA) fp8 attention kernels, and the memory-halving e2e
measurement on a ROCm gate model.

Relationship with #1936 and landing order

#1936 (the GFX1100-TG200 campaign) cherry-picked this PR's W6 store
and read work (commit 87c6f2518516) so its fp8 decode attention arm
can be tested end-to-end. The decode read side in #1936 (commit
e43a39d45803) depends on this store side -- both are needed for fp8
KV cache to work.

Landing order:

  1. This PR lands first -- the standalone W6 store kernel, closes
    KV-FP8 W6: the ROCm fp8-e4m3 KV cache store and read #2065.
  2. perf(GFX1100-TG200): the 200 tok/s gfx1100 decode campaign and fp8 KV cache decode attention #1936 rebases onto the new main and drops the cherry-pick commit
    87c6f2518516 since the store work is then in main.

All 13 files in this PR are currently also in #1936. After this PR
lands and #1936 rebases, the overlap is zero.

Closes #2065

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:glm-5-2 [OMP]

Append the W6 section to the fp8-kv-cache spec: scope, upstream chain,
port map, gates, and the wave-table row. The ROCm arm mirrors the W2
CUDA arm elementwise, with the CPU kernels (W1) as the oracle.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:glm-5-2 [OMP]
The ROCm backend gains the fp8-e4m3 KV cache store kernel and the fp8
dequant on the paged-attention read, closing the last non-Metal gap in
the fp8 KV cache surface. The store kernel mirrors the W1 CPU codec and
the W2 CUDA arm elementwise; the read dequant multiplies back by the
per-tensor k_scale/v_scale.

ROCm HIP has no __nv_cvt_float_to_fp8 intrinsic, so the store uses a
software codec (F32ToF8E4M3Dev) with frexpf/nearbyintf/ldexpf arithmetic
that mirrors the CPU vt::F32ToF8E4M3. The codec is bit-identical and
portable across all ROCm targets, unlike hardware fp8 conversion
intrinsics that exist only on CDNA2+ (gfx940/941/942).

OpId::kReshapeAndCacheFp8 is registered for DeviceType::kROCM in
rocm_ops.hip. The fp8 read refusal in ops.cpp widens from kCPU||kCUDA
to kCPU||kCUDA||kROCM. Metal remains refused by name.

Gated on gfx1100 (RX 7900 XTX): test_rocm_fp8_kv_cache 7/7 cases, 28/28
assertions. G3 store is byte-identical to the W1 CPU oracle; G4 f32
read NMSE < 1e-6; G4b bf16 read NMSE < 1e-4; G5 e5m2 refused with the
named message. RED-first proven: dropping *scale in the read dequant
fails G4/G4b at NMSE ~34000x; dropping /scale in the store fails G3 at
the byte level. Existing suites unaffected: test_rocm_backend 9/9,
test_ops_paged_attn 14/14, test_ops_fp8_kv_cache 8/8.

Closes mudler#2065

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:glm-5-2 [OMP]
@ghazni101
ghazni101 marked this pull request as draft August 27, 2026 18:08
@ghazni101
ghazni101 marked this pull request as ready for review August 28, 2026 00:07
GitHub reported mudler#2080 DIRTY against mudler/vllm.cpp main. This merges
upstream/main 5e9d81d (127 commits past the branch base 3e4cd6d)
into the row branch so the pull request is mergeable again.

The overlap since the base is confined to seven files, and none of them
needed a hand resolve: the four record matrices are row-disjoint (this
row edits BACKEND-ROCM, KV-FP8, and QUANT-KV-FP8; upstream touched other
rows plus the count table), the issue index is an append-only union
(846 ids = 714 base + 1 here + 131 upstream, nothing missing from either
side, mudler#2065 present exactly once), the ops.h change here is a comment
reword only (the enum values all came from base and upstream, so no op
id shifted), the ops.cpp fp8 read refusal still admits kROCM at the
widened guard, and tests/CMakeLists.txt keeps exactly one
test_rocm_fp8_kv_cache registration beside upstream's additions. The
.hip files and the spec were untouched by upstream since the base, so
the ROCm kernels carry over byte-identical.

Re-gated on the local gfx1100 (RX 7900 XTX) in a ROCm 10.0.0 container,
file mutex held: test_rocm_fp8_kv_cache 7/7 cases 28/28 assertions,
test_rocm_backend 9/9 1065, test_ops_paged_attn 15/15 1838 (upstream
added one case since the branch point), test_ops_fp8_kv_cache 8/8 511,
test_ops_paged_attn_dtype 3/3 172, and
test_ops_paged_attn_sharedk_wmma_p1 7/7 32813. The assertion counts
match the pull request's recorded gate. check-agent-record.py and
check-conflict-markers.py pass on the merged tree.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:glm-5.3 [OMP]
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator

@mudler This ROCm fp8-KV arm is ready for CI review, but the current reconciled head has no checks because its workflows still need approval. The branch includes focused byte-identity, dequantization, refusal, and backend regression tests with gfx1100 evidence. Please approve the current-head workflows before a merge decision.

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.

KV-FP8 W6: the ROCm fp8-e4m3 KV cache store and read

3 participants