Skip to content

[DSV4][ROCm] Reuse native operators for Indexer state and MXFP4 cache (task 22) - #626

Open
inaniloquentee wants to merge 6 commits into
dsv4-rocmfrom
feat/dsv4-indexer-state-fp4-bi
Open

inaniloquentee wants to merge 6 commits into
dsv4-rocmfrom
feat/dsv4-indexer-state-fp4-bi

Conversation

@inaniloquentee

Copy link
Copy Markdown
Collaborator

Roadmap task 22 ??independent Indexer state and MXFP4 cache

Dependencies: task 20 / #624 and task 21 / #625 (and their #618 prerequisite). Target remains dsv4-rocm. Task-22-only commit: f011f5206255125e2d8009c73e599fe11d8d8728; the base diff includes prerequisites until they merge. Refs #512.

Reuse

  • Inherit task 21's request generations, model versions, writer checks, staging leases, exactly-once commit, cancellation/release/model replacement and fail-closed poisoning. No duplicate lifecycle state machine.
  • Reuse aiter C4 state reads/compression, task 20's native Hadamard and MXFP4 quantization, vLLM FP32 partial-state writes and native Torch masked scatter. No new numerical GPU kernel.
  • Add only independent 128-dimensional Indexer buffers, complete-row masks/gather metadata, payload/scale clearing and atomic-under-one-lock snapshots.

Cache contract

Unshuffled row-major uint8 E2M1 [pages,64,64], low nibble first, and uint8 E8M0 [pages,64,4] group-32 scales. Inactive/released payloads are zero and scales are 127; validity comes from the committed cursor, not the scale byte. Padded/partial rows never write persistent cache. A failure between payload/scale writes poisons the arena without publishing a cursor.

Real MI300X acceptance

  • 434 tests passed twice, zero failures/errors/skips (75.32s coverage run, 61.35s repeat).
  • 19 new CPU + 30 real GPU tests, plus tasks 20/21/main-C4 and existing operator-contract regressions.
  • Payload/scales match standalone task 20 byte-for-byte across prefill, decode, chunks, seeds, interleaved requests, cross-page reuse, model replacement and real Graph replay.
  • FP32 state checked separately; capture warmup non-mutation, invalid-row isolation, duplicate leases, stale handles, original-input aliases, private snapshots, threads and streams covered.
  • Fixed Graph buffers and 40 allocation-stable release/recreate cycles; injected payload-before-scale failure verifies poisoned-arena behavior.
  • Python statements 451/458 (98.47%), branches 29/30 (96.67%); no native instruction coverage claim. Lint/format pass.
  • Synchronized 30-append benchmark included; no kernel-only/full-model speedup claim.

Docs: docs/models/deepseek_v4/indexer-state.md.
Report: reports/indexer-state-mi300x-20261029.md.

This is an explicit unshuffled cache boundary, not an implicit replacement for swizzled engine layouts. No cache-write autograd, full quantized VJP, actual distributed CP/RCCL, multi-node, complete framework registration or full-model acceptance is claimed.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fec0dae8-80d9-4012-93f6-2c1e2901308d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

🔍 OpenCodeReview found 9 issue(s) in this PR.

  • ✅ Successfully posted inline: 5 comment(s)
  • 📋 Routed to summary by policy: 4 comment(s)

⚠️ 5 warning(s) occurred during review.


test · low

📄 tests/models/deepseek_v4/compressor_state_helpers.py (L70-L71)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category test)

The independent reference computes a plain softmax over scores, but for rows with absolute < 0 it appends scores.append(torch.full((512,), -torch.inf)). In the first-group case (position < state_rows - 1, i.e. historical > 0 and shorter inputs), the accumulation of -inf entries happens alongside valid entries so amax stays finite — fine — but if all window rows were padded this yields -inf - -inf = nan. More importantly, the -inf padding is only correct for a strict softmax; the native kernel's documented profile is "first group previous window is -inf padded", so the reference happens to match. The real problem is that this reference is used as the accuracy oracle in test_numerical_cache_vs_independent_fp64_and_exact_ring_state; any silent divergence in the padding convention between reference and kernel will surface as a tolerance-exceeded failure rather than a clear signal. Suggest asserting on the padded-lane (scores == -inf) case explicitly or documenting that all--inf windows cannot occur.


bug · low

📄 benchmarks/benchmark_indexer_state.py (L57-L60)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category bug)

Unlike the sibling benchmarks, the output directory is never created before write_text; passing a path under a non-existent directory fails with FileNotFoundError after all benchmark cases have already run. Create parents for robustness.

💡 Suggested Change

Before:

    p.add_argument("--repeats", type=int, default=30)
    p.add_argument("--output", type=Path, required=True)
    args = p.parse_args()
    backend = AiterIndexerStateBackend()

After:

    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(report, indent=2))

other · low

📄 benchmarks/benchmark_indexer_state.py (L42-L43)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity low · category other)

snapshot reports complete_rows from snapshot["packed"], which is taken before release(h) — fine. But the sizing tokens=max(4, rows * (5 * repeats + 3)) is consumed as 3*rows warmup + 5*repeats*rows measured, i.e. exactly rows*(5*repeats+3) only when max(4, ...) picks the product; for rows=1 it equals 5*repeats+3 so it is fine, but the trailing appends in snapshot/release path assume the capacity is never exceeded. Consider asserting start <= c.max_sequence_tokens (or checking complete_rows == initial_complete_rows + 5*repeats) so a truncated benchmark cannot silently under-report work.


bug · medium

📄 rl_engine/models/deepseek_v4/indexer_state.py (L34-L35)

⚠️ GitHub could not post this as an inline comment: Routed to summary (severity medium · category bug)

_prepare_extra resets _gather_rows/_commit_mask but not _commit_mask's freshness for the scatter payload: _execute builds payload from _gather_rows ordering. If a previous commit set _commit_mask True over a wider range and the new lease covers a narrower range, the current code does zero _commit_mask first, so this is OK — but _bf16_stage is only zeroed in _execute before _compress_to, so a failed commit that already wrote some rows leaves stale _bf16_stage values. Since _execute re-zeros it, that is covered; please confirm with the arena poisoning path that a retried _execute cannot observe partially-written _cache rows from a prior failed scatter (test asserts scales are reset, but _cache rows written by fail_after_payload are not asserted to be reset/ignored).


⚠️ Warnings:

  • benchmarks/benchmark_compressor_state.py,rl_engine/backends/rocm/attention/compressor_state.py,rl_engine/contracts/operators/compressor_state.py,rl_engine/integrations/common/compressor_state.py,rl_engine/integrations/engines/rollout/vllm/compressor_state.py,rl_engine/integrations/engines/train/megatron/compressor_state.py,rl_engine/models/deepseek_v4/compressor_state.py,tests/models/deepseek_v4/compressor_state_helpers.py,tests/models/deepseek_v4/test_compressor_state.py,tests/models/deepseek_v4/test_compressor_state_rocm.py (review_limit_reached): reached the aggregate token budget before finishing; the grace round did not complete the task
  • benchmarks/benchmark_compressor_state.py (token_budget_reached): stopped group "benchmarks/benchmark_compressor_state.py,rl_engine/backends/rocm/attention/compressor_state.py,rl_engine/contracts/operators/compressor_state.py,rl_engine/integrations/common/compressor_state.py,rl_engine/integrations/engines/rollout/vllm/compressor_state.py,rl_engine/integrations/engines/train/megatron/compressor_state.py,rl_engine/models/deepseek_v4/compressor_state.py,tests/models/deepseek_v4/compressor_state_helpers.py,tests/models/deepseek_v4/test_compressor_state.py,tests/models/deepseek_v4/test_compressor_state_rocm.py" mid-review: used 613386 tokens exceeds budget 500000
  • benchmarks/benchmark_kv_compressor_c4.py (token_budget_reached): stopped dispatch: used 613386 tokens + group estimate 306248 = projected 919634 exceeds budget 500000
  • benchmarks/benchmark_indexer_compressor_c4.py,rl_engine/backends/rocm/attention/indexer_compressor_c4.py,rl_engine/contracts/operators/indexer_compressor_c4.py,rl_engine/integrations/common/indexer_compressor_c4.py,rl_engine/integrations/engines/rollout/vllm/indexer_compressor_c4.py,rl_engine/integrations/engines/train/megatron/indexer_compressor_c4.py,rl_engine/models/deepseek_v4/indexer_compressor_c4.py,tests/models/deepseek_v4/indexer_c4_helpers.py,tests/models/deepseek_v4/test_indexer_compressor_c4.py,tests/models/deepseek_v4/test_indexer_compressor_c4_rocm.py (review_limit_reached): reached the aggregate token budget before finishing; the grace round did not complete the task
  • benchmarks/benchmark_indexer_state.py,rl_engine/backends/rocm/attention/indexer_state.py,rl_engine/contracts/operators/indexer_state.py,rl_engine/integrations/common/indexer_state.py,rl_engine/integrations/engines/rollout/vllm/indexer_state.py,rl_engine/integrations/engines/train/megatron/indexer_state.py,rl_engine/models/deepseek_v4/indexer_state.py,tests/models/deepseek_v4/indexer_state_helpers.py,tests/models/deepseek_v4/test_indexer_state.py,tests/models/deepseek_v4/test_indexer_state_rocm.py (review_limit_reached): reached the aggregate token budget before finishing; the grace round did not complete the task

Comment on lines +38 to +39
dummy = torch.zeros((1, 128), dtype=torch.bfloat16, device=self.device)
self._rotate(dummy)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Constructing the backend launches a native GPU kernel (self._rotate(dummy)) during __init__. C4Compressor.__init__ may be called from inside a CUDA graph capture region or before the runtime installed the intended device as current; the parent eager() graph-capture guard is only applied on bind/__call__, not here. A construction inside capture would pollute the captured graph with a JIT-bound kernel launch, and construction on a different device index than torch.cuda.current_device() at import time would warn/behave inconsistently. Consider guarding construction the same way eager() does, or deferring the priming launch to the first forward.

Comment on lines +106 to +107
unrotated_grad = self._rotate(grad.to(torch.bfloat16))
return super().vjp(bound, ape, norm, cos, sin, unrotated_grad, contract)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The incoming gradient is unconditionally truncated to BF16 before the rotation and before the FP32 mLite VJP. The parent AiterC4Backend.vjp fuses grad.float() into the FP32 reference arithmetic (grad[index:index+1].float() on the padded grad_out), so this cast happens earlier than in the main-C4 path and silently discards FP32 precision for any caller passing an FP32 dy. No validation exists for the FP32 case (ROCm tests only pass .bfloat16() grads), so numerical regressions in the FP32 path would pass unnoticed. Either enforce BF16 as a documented precondition of vjp or keep the accumulated gradient dtype and cast only where the native rotation requires it.

Comment on lines +40 to +48
self.implementation_fingerprint = hashlib.sha256(
json.dumps(
{
"shared": self.implementation_fingerprint,
"indexer_model": source_hash(__file__),
},
sort_keys=True,
).encode()
).hexdigest()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The Indexer fingerprint is re-derived from source_hash(__file__) of this bridge module, but the shared parent fingerprint already mixes source_hash(kv_compressor_c4.py) and the backend provenance. The Indexer backend's own provenance (indexer_sources, indexer_bridge, rotation) is stored on self.backend.provenance but is not included here beyond whatever the parent AiterC4Backend.__init__ provenance captured. Since AiterIndexerC4Backend.__init__ mutates self.provenance after super().__init__() (which builds the parent identity dict), any consumer that pins expected_implementation will not detect a change to the indexer provider sources (e.g. a different aiter.ops.quant revision) without also re-deriving via the backend provenance. Verify the parent C4Compressor.__init__ reads backend.provenance after the indexer subclass finishes mutating it; if it does not, the fingerprint can miss provider changes.

Comment on lines +55 to +56
def __call__(self, bound, ape, norm, cos, sin, *, version, parameter_id=""):
out = super().__call__(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The quantized columns are produced outside the recorded autograd path and outside the canonical backward budget: backend.quantize is invoked with torch.no_grad() on out.values.detach() after super().__call__ returns. If the provider returns a per-column-major or per-group scale layout (the FP8 path passes column_major_scales=False but nothing here verifies what the installed vLLM actually returns), the require(...) below raises C4Error only after the parent already performed a full forward and registered canonical backward slots, so the failure surfaces as a raising exception mid-session with dangling registered rows. More importantly, the empty-input branch of quantize returns 2-D (0, 1)/(0, 4) scales while the non-empty FP8 branch returns whatever per_token_group_quant_fp8 yields; if that is 1-D (rows,), the scales.shape == (rows, 1) check fails only for non-empty rows. The empty path is the only one exercised by the CPU tests, so this inconsistency is not covered. Normalize the returned scale to (rows, 1)/(rows, 4) explicitly at the backend boundary.

Comment on lines +155 to +161
assert train is rollout
op = operator()
assert train(
contract=op.contract,
backend=ReferenceIndexerC4Backend(),
expected_implementation=op.implementation_fingerprint,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected_implementation is validated against a freshly constructed ReferenceIndexerC4Backend(), which has no provenance attribute (the parent falls back to {"test_provider": type(backend).__name__}). This makes the fingerprint comparison trivially reproducible on CPU but does not exercise the case where the expected value is pinned from a real GPU backend artifact; a backend-provenance regression on the ROCm path would not be caught by this test. The negative case below (expected_implementation="old") only verifies the mismatch message. If the contract intends expected_implementation to pin a real deployment fingerprint, add a GPU-side check (or document the CPU-only scope).

@github-actions

Copy link
Copy Markdown

@inaniloquentee The automated OCR review has completed.

Please review and address all automated review bot findings, then push your updates. Maintainers will begin manual review after the bot findings have been addressed.

@Flink-ddd Flink-ddd added platform: rocm Specific tasks specific to AMD graphics cards (such as CK, bpreshuffle/FA) DSv4 deepseek-P2 labels Oct 29, 2026

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

deepseek-P2 DSv4 platform: rocm Specific tasks specific to AMD graphics cards (such as CK, bpreshuffle/FA)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants