Skip to content

feat(phyai): radix prefix-cache bridge for AR attention#22

Open
Glorhop wants to merge 2 commits into
MEmbodied:mainfrom
Glorhop:feat/radix-cache-ar
Open

feat(phyai): radix prefix-cache bridge for AR attention#22
Glorhop wants to merge 2 commits into
MEmbodied:mainfrom
Glorhop:feat/radix-cache-ar

Conversation

@Glorhop

@Glorhop Glorhop commented Jun 10, 2026

Copy link
Copy Markdown

Summary

Wires the existing radix prefix cache (phyai_ext.radix_cache.PrefixCache) into the AR (LM-side) attention path so requests reuse the KV of a shared prompt prefix and only attend over the new (suffix) tokens — the standard RadixAttention speedup. Model- and encoding-agnostic; pi0.5's StaticCache path is untouched. Foundation for the upcoming cosmos adaptation.

What's in it

New components under phyai/layers/attention/ar/radix/:

  • RadixSequence: Per-request state (pre-encoded atoms in; prefix/suffix slot split + radix handles out).
  • RadixAttentionPlanner: The bridge over PrefixCache, with a 3-call lifecycle.
    • plan(seqs) -> ARAttnMetadata: Matches cached prefix → reuses those slots → allocates suffix-only → builds cu_seqlens_q / paged_kv_indptr / paged_kv_indices / write_indices / position_ids, with query = suffix tokens only.
    • commit(seqs): Seeds a fresh sequence into the tree for future reuse.
    • release(seqs): Drops the reused-prefix lock and frees the uncommitted suffix.
  • Purely additive — no attention backend modified: on current main the AR stack is flashinfer-only, and flashinfer's paged prefill natively consumes the non-contiguous paged_kv_indices radix produces, so the planner just feeds it.
  • Lazy re-exporting: The bridge is lazily re-exported from phyai.layers.attention.ar, keeping the optional phyai-ext extra off the base attention import.

Contract

Prefill-with-prefix: the caller feeds Q/K/V for the suffix tokens only; the flashinfer paged backend scatters the new K/V at write_indices and reads prefix_slots ++ suffix_slots. Causal masking for q_len < kv_len is handled by flashinfer's paged prefill — the suffix query is aligned to the tail of the KV via qo_indptr vs paged_kv_indptr.

Scope / non-goals

  • Device tier only and page_size == 1 (both enforced at construction).
  • Decode loop / incremental cache growth, multi-token pages, host/disk tiers: out of scope .

Tests

  • tests/layers/attention/ar/test_radix_planner.py — 21 CPU planner-logic tests (exact-tensor metadata, prefix reuse + non-contiguous indices, commit/release, plan() rollback, no-leak under nested reuse, capacity/validation errors, lazy/star-import safety).
  • test_radix_gather_e2e.py — CPU, backend-free: after seed → commit → decoy → reuse, the gathered [cached prefix ++ fresh suffix] equals the full sequence's KV (asserts the exact non-contiguous paged_kv_indices). Keeps CI numeric coverage without a kernel.
  • test_radix_flashinfer.py — CUDA paged e2e (auto-skips without CUDA + flashinfer).
  • tests/layers/attention/ suite green on CPU: 54 passed, 14 skipped (skips are the CUDA-gated flashinfer / vgpu / norm-backend tests).

Notes

Hardened across review rounds: device-only tier, lazy/star-import safety, plan() atomicity (rollback + validate-before-side-effects), order-independent commit, the KV-pool bounds check, and the NodeRef split-leak fix (commit doesn't pin; plan() pre-splits the tree before locking).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a radix-cache to AR attention bridge, adding RadixAttentionPlanner and RadixSequence to support prefix-reusing attention. It also updates the EagerARBackend to gather non-contiguous KV slots, removing the previous contiguous-slab constraint. The review feedback highlights two key improvement opportunities: optimizing the lazy import mechanism in ar/__init__.py to prevent recursive __getattr__ calls, and avoiding an expensive GPU-to-CPU synchronization in the planner by performing the slot ID boundary check on the host-side list rather than on the GPU tensor.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +69 to +74
if name in ("RadixAttentionPlanner", "RadixSequence"):
from phyai.layers.attention.ar import radix

value = getattr(radix, name)
globals()[name] = value
return value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Importing the classes directly from the submodule phyai.layers.attention.ar.radix is cleaner and avoids potentially triggering __getattr__ recursively for 'radix'. Additionally, populating both RadixAttentionPlanner and RadixSequence in globals() on the first access avoids subsequent __getattr__ calls for the other class.

Suggested change
if name in ("RadixAttentionPlanner", "RadixSequence"):
from phyai.layers.attention.ar import radix
value = getattr(radix, name)
globals()[name] = value
return value
if name in ("RadixAttentionPlanner", "RadixSequence"):
from phyai.layers.attention.ar.radix import RadixAttentionPlanner, RadixSequence
globals()["RadixAttentionPlanner"] = RadixAttentionPlanner
globals()["RadixSequence"] = RadixSequence
return globals()[name]

Comment thread phyai/src/phyai/layers/attention/ar/radix/planner.py Outdated
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a radix-cache-to-AR-attention bridge under phyai/src/phyai/layers/attention/ar/radix, comprising a RadixSequence dataclass, a RadixAttentionPlanner (plan/commit/release with rollback semantics), and lazy re-exports from the ar package. CPU and CUDA end-to-end tests validate planning, prefix reuse, and correctness.

Changes

Radix-cache AR attention bridge

Layer / File(s) Summary
Lazy re-export wiring
phyai/src/phyai/layers/attention/ar/__init__.py, phyai/src/phyai/layers/attention/ar/radix/__init__.py
Adds __getattr__ for lazy loading of RadixAttentionPlanner/RadixSequence and defines the radix package's public __all__ and docstring.
RadixSequence data model
phyai/src/phyai/layers/attention/ar/radix/sequence.py
Defines the dataclass holding prefix/suffix slot tensors, node references, lifecycle flags, and computed suffix_len/total_len properties.
RadixAttentionPlanner construction and planning
phyai/src/phyai/layers/attention/ar/radix/planner.py
Implements constructor validation, plan()/_plan() for prefix matching and suffix allocation with atomic rollback, prefix-matching helpers, and metadata assembly into ARAttnMetadata.
Commit and release lifecycle
phyai/src/phyai/layers/attention/ar/radix/planner.py
Adds commit() to insert fresh suffixes into the radix tree and release() to unlock nodes and free uncommitted allocations.
Planner unit and rollback tests
phyai/tests/layers/attention/ar/test_radix_planner.py
Covers constructor validation, plan/commit/release semantics, capacity errors, rollback atomicity, lazy import behavior, and leak/eviction regressions.
End-to-end gather and CUDA correctness tests
phyai/tests/layers/attention/ar/test_radix_gather_e2e.py, phyai/tests/layers/attention/ar/test_radix_flashinfer.py
Validates exact KV gather correctness with non-contiguous prefix reuse (CPU) and ARAttention output correctness against eager causal attention across prefill/suffix phases (CUDA/flashinfer).

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant RadixAttentionPlanner
  participant PrefixCache
  participant KVCachePool
  Caller->>RadixAttentionPlanner: plan(sequences)
  RadixAttentionPlanner->>PrefixCache: match_prefix, lock node
  RadixAttentionPlanner->>KVCachePool: allocate suffix units
  RadixAttentionPlanner-->>Caller: ARAttnMetadata
  Caller->>RadixAttentionPlanner: commit(sequences)
  RadixAttentionPlanner->>PrefixCache: insert fresh suffix units
  Caller->>RadixAttentionPlanner: release(sequences)
  RadixAttentionPlanner->>PrefixCache: unlock node_ref
  RadixAttentionPlanner->>KVCachePool: free uncommitted units
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a radix prefix-cache bridge for AR attention.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Glorhop added 2 commits July 7, 2026 06:26
RadixAttentionPlanner maps phyai_ext.radix_cache prefix matches into
ARAttnMetadata (reuse cached-prefix slots, allocate only the suffix) for the
flashinfer paged backend; the attention layer is untouched. Lazily re-exported
from phyai.layers.attention.ar so the base package needn't pull the phyai-ext extra.
@Glorhop Glorhop force-pushed the feat/radix-cache-ar branch from 7470c96 to e0e4f77 Compare July 9, 2026 03:55
@Glorhop Glorhop requested a review from chenghuaWang as a code owner July 9, 2026 03:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
phyai/tests/layers/attention/ar/test_radix_flashinfer.py (2)

110-119: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Call planner.release() after each scenario to match the planner lifecycle.

Same as the gather test: release() drops prefix-reuse locks and frees uncommitted suffix units. Adding planner.release([a]) after the seed phase and planner.release([c]) after the reuse phase makes the lifecycle explicit and avoids masking leak-detection regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/tests/layers/attention/ar/test_radix_flashinfer.py` around lines 110 -
119, The test is missing explicit planner lifecycle cleanup after each scenario.
Update the radix flashinfer test to call planner.release() for the seeded
sequence and the reuse sequence using the relevant RadixSequence symbols
(planner, plan, commit, release, a, and c), so prefix-reuse locks and
uncommitted suffix units are released and the lifecycle matches the other
planner tests.

98-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use meta.mode instead of hardcoding AttnMode.PREFILL in the run helper.

The run() function hardcodes mode=AttnMode.PREFILL in ARAttnCtx, but meta already carries the mode from planner.plan(). If plan() is ever called with a different mode argument, the context would be inconsistent with the metadata. Use mode=meta.mode for robustness.

♻️ Proposed fix
         ctx = ARAttnCtx(
             backend=backend,
             plan=plan,
-            mode=AttnMode.PREFILL,
+            mode=meta.mode,
             layout=AttnLayout.RAGGED_3D,
             kv_pool=pool,
             write_indices=meta.write_indices,
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/tests/layers/attention/ar/test_radix_flashinfer.py` around lines 98 -
108, The run helper hardcodes the attention mode in ARAttnCtx, which can make
the context inconsistent with the metadata returned by planner.plan(). Update
the run(meta, qsub, ksub, vsub) helper to pass meta.mode into ARAttnCtx instead
of AttnMode.PREFILL, keeping the mode aligned with the metadata produced by
init_forward_metadata and planner.plan().
phyai/tests/layers/attention/ar/test_radix_gather_e2e.py (1)

29-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Call planner.release() after each scenario to match the planner lifecycle.

The planner docs describe release() as the step that drops prefix-reuse locks and frees uncommitted suffix units. While the test objects are garbage-collected, explicitly calling release([a]), release([d]), and release([c]) at the end of each phase makes the lifecycle explicit and avoids masking any leak-detection regressions in future test runs within the same process.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/tests/layers/attention/ar/test_radix_gather_e2e.py` around lines 29 -
76, The radix attention test is missing explicit planner cleanup, so add
planner.release() for each RadixSequence scenario to mirror the intended
lifecycle. Use the RadixAttentionPlanner release path after the seed sequence a,
the decoy sequence d, and the reuse sequence c, so prefix-reuse locks and
uncommitted suffix units are dropped deterministically. Keep the calls near the
end of each phase in test_radix_reuse_gathers_exact_prefix_plus_suffix to make
the test resilient to future leak-detection checks.
phyai/src/phyai/layers/attention/ar/radix/planner.py (1)

312-314: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add type annotations to _assemble (and consider _match_prefix's return type).

_assemble is fully untyped while the rest of the module is typed. Annotating the signature keeps the module typed and documents the -> ARAttnMetadata contract; B also reads better as a lowercase batch_size.

♻️ Suggested signature
-    def _assemble(
-        self, B, suffix_lens, total_lens, indices_parts, write_parts, pos_parts, mode
-    ):
+    def _assemble(
+        self,
+        batch_size: int,
+        suffix_lens: list[int],
+        total_lens: list[int],
+        indices_parts: list[torch.Tensor],
+        write_parts: list[torch.Tensor],
+        pos_parts: list[torch.Tensor],
+        mode: AttnMode,
+    ) -> ARAttnMetadata:

Update the internal B references accordingly.

As per coding guidelines: "keep modules typed where practical".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/src/phyai/layers/attention/ar/radix/planner.py` around lines 312 - 314,
_add type annotations to the untyped planner helpers in the radix attention
planner: update `_assemble` in `ARPlanner` to use typed parameters (including
renaming `B` to `batch_size` for clarity) and an explicit `-> ARAttnMetadata`
return type, and make sure any internal references to `B` are updated
accordingly. Also review `_match_prefix` in the same class/module and add its
return type if it is still implicit so the module remains consistently typed.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@phyai/src/phyai/layers/attention/ar/radix/planner.py`:
- Around line 312-314: _add type annotations to the untyped planner helpers in
the radix attention planner: update `_assemble` in `ARPlanner` to use typed
parameters (including renaming `B` to `batch_size` for clarity) and an explicit
`-> ARAttnMetadata` return type, and make sure any internal references to `B`
are updated accordingly. Also review `_match_prefix` in the same class/module
and add its return type if it is still implicit so the module remains
consistently typed.

In `@phyai/tests/layers/attention/ar/test_radix_flashinfer.py`:
- Around line 110-119: The test is missing explicit planner lifecycle cleanup
after each scenario. Update the radix flashinfer test to call planner.release()
for the seeded sequence and the reuse sequence using the relevant RadixSequence
symbols (planner, plan, commit, release, a, and c), so prefix-reuse locks and
uncommitted suffix units are released and the lifecycle matches the other
planner tests.
- Around line 98-108: The run helper hardcodes the attention mode in ARAttnCtx,
which can make the context inconsistent with the metadata returned by
planner.plan(). Update the run(meta, qsub, ksub, vsub) helper to pass meta.mode
into ARAttnCtx instead of AttnMode.PREFILL, keeping the mode aligned with the
metadata produced by init_forward_metadata and planner.plan().

In `@phyai/tests/layers/attention/ar/test_radix_gather_e2e.py`:
- Around line 29-76: The radix attention test is missing explicit planner
cleanup, so add planner.release() for each RadixSequence scenario to mirror the
intended lifecycle. Use the RadixAttentionPlanner release path after the seed
sequence a, the decoy sequence d, and the reuse sequence c, so prefix-reuse
locks and uncommitted suffix units are dropped deterministically. Keep the calls
near the end of each phase in test_radix_reuse_gathers_exact_prefix_plus_suffix
to make the test resilient to future leak-detection checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c7e2225-3b17-4c75-8088-b3d0ba74c21f

📥 Commits

Reviewing files that changed from the base of the PR and between ac1a230 and e0e4f77.

📒 Files selected for processing (8)
  • phyai/src/phyai/layers/attention/ar/__init__.py
  • phyai/src/phyai/layers/attention/ar/radix/__init__.py
  • phyai/src/phyai/layers/attention/ar/radix/planner.py
  • phyai/src/phyai/layers/attention/ar/radix/sequence.py
  • phyai/tests/layers/attention/ar/__init__.py
  • phyai/tests/layers/attention/ar/test_radix_flashinfer.py
  • phyai/tests/layers/attention/ar/test_radix_gather_e2e.py
  • phyai/tests/layers/attention/ar/test_radix_planner.py

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.

1 participant