Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions benchmarks/benchmark_kv_compressor_c4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# SPDX-License-Identifier: Apache-2.0
"""MI300X benchmark for the fixed aiter C4 overlap forward boundary."""

import argparse
import json
from pathlib import Path
import sys
import time

import torch

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from rl_engine.backends.rocm.attention.kv_compressor_c4 import AiterC4Backend
from rl_engine.contracts.operators.kv_compressor_c4 import C4Contract
from rl_engine.models.deepseek_v4.kv_compressor_c4 import C4Compressor
from tests.models.deepseek_v4.c4_helpers import fixture


def timed(fn, repeats):
for _ in range(5):
fn()
torch.cuda.synchronize()
start = time.perf_counter()
for _ in range(repeats):
fn()
torch.cuda.synchronize()
return (time.perf_counter() - start) * 1e6 / repeats
Comment on lines +20 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · high
This times host wall clock around an async device loop only. Peers in this repo report GPU-event and wall time together (see benchmarks/backends/rocm/benchmark_rocm_singleton_lse.py::_elapsed_ms). Without the event pair, forward_us / provider_forward_us are dominated by Python-side work in the timed region: torch.empty + two zeros_like in AiterC4Backend.forward, the (blocks, 64, 512) BF16 cache allocation, the clone() of context_values, the Python loop over logical_keys, and (for bind_us) a blocking token_ids.cpu().tolist(). Recording kernel time alongside wall time makes it explicit which part is host/launch overhead, so the JSON is not read as kernel latency.

Also, this torch.cuda.synchronize() and the device="cuda" fixtures in main() are unconditional, so the script cannot run (or clearly skip) on a non-CUDA/ROCm host; the repo's guarded pattern is if device.type == "cuda" (benchmark_rocm_logp.py::_device_synchronize).

Suggestion:

Suggested change
def timed(fn, repeats):
for _ in range(5):
fn()
torch.cuda.synchronize()
start = time.perf_counter()
for _ in range(repeats):
fn()
torch.cuda.synchronize()
return (time.perf_counter() - start) * 1e6 / repeats
def timed(fn, repeats):
for _ in range(5):
fn()
torch.cuda.synchronize()
start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)
wall = time.perf_counter()
start.record()
for _ in range(repeats):
fn()
end.record()
end.synchronize()
return (
start.elapsed_time(end) * 1e3 / repeats,
(time.perf_counter() - wall) * 1e6 / repeats,
)



def main():
parser = argparse.ArgumentParser()
parser.add_argument("--repeats", type=int, default=30)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
backend = AiterC4Backend()
operator = C4Compressor(C4Contract(), backend=backend)
cases = []
for groups in (1, 8, 64, 128):
values, ids, ape, norm, cos, sin = fixture(groups, device="cuda")
bound = operator.bind(values, ids, version="v1")

def forward(bound=bound, ape=ape, norm=norm, cos=cos, sin=sin):
operator(bound, ape, norm, cos, sin, version="v1")

cases.append(
{
"groups": groups,
"tokens": groups * 4,
"input_bytes": values.numel() * values.element_size(),
"output_bytes": groups * 512 * 2,
"bind_us": timed(
lambda values=values, ids=ids: operator.bind(values, ids, version="v1"),
args.repeats,
),
"forward_us": timed(forward, args.repeats),
"provider_forward_us": timed(
lambda bound=bound, ape=ape, norm=norm, cos=cos, sin=sin: backend.forward(
bound, ape, norm, cos, sin, operator.contract
),
args.repeats,
),
}
)
result = {
"gpu": torch.cuda.get_device_name(),
"arch": torch.cuda.get_device_properties(0).gcnArchName,
"torch": torch.__version__,
"hip": torch.version.hip,
"triton": __import__("triton").__version__,
"backend": backend.provenance,
"contract": operator.contract.fingerprint,
"implementation": operator.implementation_fingerprint,
"metric": (
"synchronized average over warmup+repeated loops; Python validation and "
"paged-cache gather included"
),
"scope": (
"complete C4 forward; aiter overlap pool and norm/RoPE; no live cache "
"commit, no full model"
),
"cases": cases,
}
args.output.write_text(json.dumps(result, indent=2))
print(json.dumps(result, indent=2))


if __name__ == "__main__":
main()
81 changes: 81 additions & 0 deletions docs/models/deepseek_v4/kv-compressor-c4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# DSV4 item 19: C4 KV compressor

This item provides the main-KV C4 compressor with the fixed eight-token overlap
window. It is separate from:

- item 18 C128 main-KV compression;
- item 20 the independent 128-dimensional Indexer C4 compressor;
- item 21 state/cache lifecycle and exactly-once commit;
- item 27 prefill/decode/recompute/Graph runtime integration.

## Contract

```text
input: BF16 [tokens, 2048]
[content1024, score1024] where each 1024-wide stream has two 512 halves
int64 logical token IDs [request_id, global_token_position]
output: BF16 [complete_groups, 512]
logical IDs [request_id, global_group_id]
```

The compressor consumes four current tokens per output row. For group `g`, the
softmax window is:

```text
previous group content/gate first half (g-1), then
current group content/gate second half (g)
```

The first group has four masked predecessor rows. Incomplete groups are never
padded into a submitted output. Global position is `group_id * 4`; local rank
order is not substituted for it. The optional `emit_group_ids` binding argument
allows a microbatch to include one completed predecessor group as context while
emitting only its requested groups.

CP callers must provide complete current groups and their overlap predecessor
before binding. The rank owning the current group’s fourth token emits that row.
No collective is performed by this item.

## Reuse and paged output

The ROCm backend directly reuses aiter’s
`flydsl_fused_compress_attn` with `overlap=True`, `ratio=4`, `head_dim=512`,
BF16 output, and the fixed one-wave (`k_split_num_waves=1`) arithmetic path.
The private output uses the provider’s real compressed-cache layout:
`k_per_block=64`, a per-request block table, and cache rows selected by
`group_id % 64`. This is important for correctness; treating every row as a
one-row block is not an accepted shortcut.

The overlap input is materialized as eight direct input rows per plan row. This
reuses aiter’s optimized online-softmax/norm/RoPE kernel while leaving live
request state mutation and cache commit to item 21. The first group’s four
predecessor scores use a finite BF16 floor below the admitted score range,
avoiding the input-phase `-inf - (-inf)` case; the independent oracle retains
the exact masked semantics.

Backward reuses the pinned mLite/Megatron `compressed_compact_graph` ratio-4
functional graph. One output’s VJP is evaluated against its previous/current
context. Overlapping row contributions are applied in a fixed Python logical
order, not with unordered floating-point atomics. APE and RMS rows are
registered with RLK’s existing `CanonicalBackwardSession`.

Provider source hashes are checked before admission; missing or different
aiter/mLite sources fail closed. No PyTorch numerical fallback is exposed.

## Verification

The MI300X tests cover:

- complete and partial groups, first-group masking, overlap previous/current
halves, global positions, request permutation and padding;
- CP final-token ownership, context predecessor admission and block-table
mapping;
- softmax/content scale extremes, fixed overlap plan, `k_per_block=64`,
output/cache isolation, streams and fresh-process/JIT fingerprints;
- independent FP64 forward arithmetic;
- full/microbatch/reversed-order backward, overlap gradient accumulation and
independent autograd/finite-difference checks;
- malformed ABI/metadata, budget, mutation, version and Graph rejection.

This item does not claim RCCL/multi-GPU CP, live cache commit, Graph replay,
prefill/decode framework integration or full-model acceptance.
47 changes: 47 additions & 0 deletions reports/kv-compressor-c4-mi300x-20261028.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Item 19 C4 KV compressor — MI300X validation

Branch: `feat/dsv4-kv-c4-bi`
Target: `dsv4-rocm`
Hardware: AMD Instinct MI300X VF (`gfx942`)
Environment: ROCm/HIP 7.14.60850, PyTorch 2.12.0+rocm7.14.0a20260608,
Triton 3.7.0, FlyDSL 0.3.4.1, pinned aiter commit
`326cffa38c9eda16bef19375ba55334e4d3acd59`, pinned mLite commit
`327a238239448de6771beea185dc6902a8de3eaa`.

## Test evidence

- CPU-safe C4 tests: **53 passed**.
- Real MI300X C4 tests: **26 passed**, 0 skipped, 1 existing warning, 19.49s.
- Full targeted contract + DeepSeek-V4 suite: **221 passed**, 0 skipped, 1
existing warning, 20.33s without coverage and 26.54s with coverage.
- Scoped Python wrapper coverage: **336/371 statements (90%)** and **80/86
branches**. Coverage does not measure FlyDSL/native GPU instructions; the
unexecuted wrapper lines are provider-constructor and direct native VJP
plumbing branches exercised by the real GPU tests.
- Ruff format/lint passed for all ten Python files.
- Real GPU coverage includes aiter overlap compression, true `k_per_block=64`
paged output mapping, mLite ratio-4 functional VJP, fixed-order overlap
gradients, streams, fresh processes/JIT isolation and CP ownership.
- The forward oracle is independent FP64 softmax/pool/RMS/rotation arithmetic.
- The backward checks compare fixed-order microbatch/reverse-order gradients to
independent autograd and finite-difference results.

This item does not claim RCCL/multi-GPU CP, Graph replay, live cache commit,
prefill/decode runtime or full-model acceptance.

The benchmark records bind, provider forward and full operator timing with
Python validation, paged-cache gather and synchronization included. It is not a
kernel-only or throughput claim.

## Synchronized MI300X benchmark (30 repetitions)

| Complete groups | Tokens | Bind (us) | Provider (us) | Full forward (us) |
|---:|---:|---:|---:|---:|
| 1 | 4 | 429.67 | 95.44 | 440.68 |
| 8 | 32 | 944.07 | 221.16 | 781.28 |
| 64 | 256 | 2,805.11 | 533.89 | 1,096.21 |
| 128 | 512 | 5,045.03 | 380.60 | 912.69 |

These are synchronized average wall times with warmup, Python validation,
paged-cache gather and dispatch included. They are not kernel-only latency or
a speedup claim. The raw benchmark records the provider source fingerprints.
55 changes: 55 additions & 0 deletions reports/kv-compressor-c4-new-host-20261029.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# C4 follow-up: new MI300X environment, 2026-10-29

This is a corrective follow-up to task 19 / PR #618, not a new task PR.

## Corrections

- The first group's four missing predecessors now use the existing aiter
state-phase negative-position mask. The old finite `-2**30` gate floor was
not a valid universal mask over the admitted gate domain. No handwritten
numerical kernel or approximate masking workaround is used.
- Validate the predecessor context as well as the emitted current-group rows.
An `emit_group_ids` filter cannot hide non-finite predecessor data.
- The independent oracle now preserves FP64 throughout softmax, pooling,
normalization and rotation. The previous helper converted its inputs to
FP32 internally, so the earlier report's description of that comparison as
fully FP64 was incorrect. New regression tests verify FP64 perturbations
survive and gradients remain FP64.
- Backward passes host-known unique context indices instead of iterating GPU
index tensors and synchronizing each index.

## Dependency profile

The new image's existing aiter 0.1.16.post3 / FlyDSL 0.2.0 provider is admitted
by its exact source hash, alongside the previously pinned provider ABI.
The older ABI has a fixed single-wave path without a batch-size dispatch.
No numerical fallback is selected on a library error.

- PyTorch: `2.11.0+gitd0c8b1f`
- HIP: `7.2.53211`
- Triton: `3.6.0`
- GPU architecture: `gfx942`
- aiter C4 source SHA256:
`0c2019ca3a4a0d6dc7d5fdd20ebcce658ff5f504fdc880dffd61c9c398029451`
- mLite functional backward source SHA256:
`6a1e78ca89b83f56391b5e5ac409e765338f1e3a625251d0d4c877c1e80b4a68`

## Tests

- C4 CPU/GPU tests: **85 passed**, one warning, 23.66 seconds.
- Full targeted contract + DeepSeek-V4 suite: **227 passed**, zero skips,
one warning, 34.38 seconds with coverage.
- Includes four extreme first-group score biases, hidden non-finite context,
real FP64 oracle checks, real aiter launches, mLite VJP, streams and fresh
subprocess runs.
- Changed-file Ruff lint/format checks pass; the 30-repetition benchmark was
re-run on the new environment.
- Python wrapper coverage remains approximately 90%. Native instructions and
autograd-internal tracing gaps are not evidence of native coverage; earlier
statements describing all uncovered lines as constructor failures were too
broad.

Logs, JUnit XML, coverage and benchmark JSON are under the member's mounted
`/outputs/c4-new-host-20261029/`, not the shared Docker writable layer.
The task-19 limits remain: no RCCL/multi-GPU CP, Graph replay, live cache
commit, full-model or end-to-end framework acceptance.
Loading
Loading