Skip to content

support kvcache dynamic shape - #115

Merged
superxf merged 5 commits into
hw-native-sys:mainfrom
superxf:dynamic_kvcache
Jul 28, 2026
Merged

support kvcache dynamic shape#115
superxf merged 5 commits into
hw-native-sys:mainfrom
superxf:dynamic_kvcache

Conversation

@superxf

@superxf superxf commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator
  1. support dynamic kvcache in deepseek v3
  2. solve the serving ci bug

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

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

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c32632ed-ac01-4037-893d-0bcff6b99a5b

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
📝 Walkthrough

Walkthrough

DeepSeek V4 cache handling now derives grouped physical capacities at runtime. Runner allocation, device cache clearing, executor tensor contracts, and serving metadata synchronization use these capacities, with tests covering sizing, allocation, retry, lifecycle, and shape behavior.

Changes

DeepSeek runtime cache sizing

Layer / File(s) Summary
Cache group contracts and capacity helpers
pypto_serving/model/deepseek/npu_runner.py
Cache group specifications now include heterogeneous row footprints, and helpers derive scheduler-visible and physical block counts with scratch capacity.
Runtime cache allocation and device clearing
pypto_serving/model/deepseek/npu_runner.py, tests/test_deepseek_v4.py
DeepSeekV4ModelRunner computes runtime capacity, retries allocation with reduced capacity, builds group-specific decode metadata, clears device pages, and manages MTP cache lifecycle.
Runtime-derived executor tensor contracts
pypto_serving/model/deepseek/npu_executor.py, tests/test_deepseek_v4.py
MTP, prefill, and decode dummy tensor shapes and kernel contract checks use compiled physical cache block counts.
Serving metadata synchronization
pypto_serving/serving/engine/async_engine.py, pypto_serving/serving/memory/kv_cache.py, tests/test_batching.py, pypto-lib
Serving startup passes worker-reported capacity into grouped or single cache initialization, and unspecified groups scale from the primary pool.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReplicaEngineCore
  participant DeepSeekV4ModelRunner
  participant KvCacheManager
  participant DeepSeekV4NPUExecutor
  ReplicaEngineCore->>DeepSeekV4ModelRunner: receive reported cache capacity
  DeepSeekV4ModelRunner->>KvCacheManager: provide grouped physical capacity
  DeepSeekV4NPUExecutor->>DeepSeekV4ModelRunner: derive runtime cache block shapes
  DeepSeekV4NPUExecutor-->>DeepSeekV4ModelRunner: validate and use cache-shaped tensors
Loading

Possibly related PRs

Poem

A rabbit hops through cache rows bright,
Grouped pages fit the runtime right.
Scratch blocks clear, tensors align,
MTP paths bloom in orderly design.
“Hop!” says the bunny—“the pools now scale!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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.
Title check ✅ Passed The title matches the main change: dynamic KV cache shape support for DeepSeek V3.
Description check ✅ Passed The description is related to the changeset and mentions dynamic KV cache support and a serving CI fix.

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.

@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.

Actionable comments posted: 3

🧹 Nitpick comments (6)
tests/test_batching.py (1)

351-373: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a negative case for the divisibility rule.

The new validation rejects a primary_num_blocks that is not a multiple of the first group's max_blocks_per_seq; that branch is currently untested, and it is exactly the path a mis-sized runner report would hit at startup.

💚 Suggested additional test
def test_grouped_cache_rejects_unaligned_primary_pool():
    manager = KvCacheManager(block_size=1, enable_prefix_cache=False)
    specs = (
        KVCacheGroupSpec(
            name="primary",
            layer_indices=(0,),
            spec=KVCacheSpec(block_size=1, page_size_bytes=4),
            max_blocks_per_seq=3,
        ),
    )
    with pytest.raises(ValueError, match="positive multiple"):
        manager.init_groups(specs, max_batch_size=8, primary_num_blocks=7)
🤖 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 `@tests/test_batching.py` around lines 351 - 373, Add a negative test alongside
test_grouped_cache_capacity_scales_from_device_reported_primary_pool that
constructs a primary group with max_blocks_per_seq=3 and calls init_groups with
primary_num_blocks=7. Assert that init_groups raises ValueError and matches the
“positive multiple” validation message.
pypto_serving/model/deepseek/npu_runner.py (4)

3846-3851: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant bound check in the MTP branch.

ids were already validated against blocks_per_layer a few lines above, and this branch reuses the same bound. The MTP pool has exactly _physical_cache_num_blocks("ori") pages, so the check can be dropped.

🤖 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 `@pypto_serving/model/deepseek/npu_runner.py` around lines 3846 - 3851, Remove
the redundant block-ID range validation from the MTP branch near the page size
calculation, including its ValueError path. Keep the earlier validation against
blocks_per_layer and the existing page allocation logic unchanged.

182-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify decode_batch vs DEEPSEEK_V4_DECODE_BATCH in the ring divisor.

decode_batch (the caller-provided, layout-specific value) gates the minimum pool, but blocks_per_request divides by the module constant DEEPSEEK_V4_DECODE_BATCH. With the MTP layout (decode_batch=4) the two disagree, so the per-request ring is derived from a batch width the runtime is not using. If that is intentional (fixed kernel table depth), a one-line comment would prevent a future "fix" from changing capacity math.

🤖 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 `@pypto_serving/model/deepseek/npu_runner.py` around lines 182 - 186, Clarify
the intentional use of DEEPSEEK_V4_DECODE_BATCH as the divisor in
blocks_per_request, noting that decode_batch is caller-provided and
layout-specific while the module constant represents the fixed kernel table
depth. Add a concise comment directly above the calculation without changing the
existing capacity math.

1444-1474: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: fail fast on an exhausted budget and drop the defensive getattr.

RuntimeConfig always defines npu_memory_utilization, so getattr(..., 0.90) only hides typos. More usefully, when peak_non_kv already exceeds total * utilization the budget goes negative and capacity_slots silently clamps to 1, so the failure surfaces later as a generic "allocation failed at the one-slot minimum". A direct error naming the utilization shortfall would be much easier to diagnose in the field.

🤖 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 `@pypto_serving/model/deepseek/npu_runner.py` around lines 1444 - 1474, Update
the capacity calculation around npu_memory_utilization to access
runtime.npu_memory_utilization directly instead of using getattr, and fail
immediately when any device’s peak_non_kv usage exceeds its configured
total-memory budget. Raise a clear error identifying the affected device and
utilization shortfall before capacity_slots applies the one-slot minimum.

3822-3840: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider coalescing contiguous pages into a single copy_to.

The inner loop issues one host→device transfer per (layer, block). For the ori group that is 43 transfers per newly assigned block per rank, and this runs on the decode path via _seed_decode_work_cache_from_group_ids for every step. Sorting ids and merging consecutive block runs (pages within a layer are contiguous) would collapse most of these into a single larger copy, with a correspondingly larger zero page. Also hoist page_numel/page_nbytes out of the layer loop — they are invariant per tensor.

🤖 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 `@pypto_serving/model/deepseek/npu_runner.py` around lines 3822 - 3840,
Optimize the zero-page initialization loop in the tensor-group copy flow by
hoisting the invariant page-size calculations before iterating layers, sorting
ids, and coalescing consecutive block ids into contiguous runs. Issue one
copy_to call per run using a sufficiently large zero buffer, while preserving
the existing layer offsets, worker_id, bounds validation, and zeroing behavior
in _seed_decode_work_cache_from_group_ids.
pypto_serving/serving/engine/async_engine.py (1)

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

Prefer a public initializer over reaching into _init_blocks.

The engine calls a private KvCacheManager method across module boundaries. A thin public method (e.g. init_single_pool(num_blocks, block_size)) would keep the manager's API surface honest and match the grouped path's init_groups.

🤖 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 `@pypto_serving/serving/engine/async_engine.py` at line 255, Replace the
cross-module call to the private KvCacheManager._init_blocks in the async engine
with a public initializer, such as init_single_pool(num_blocks, block_size), and
implement or reuse that method on KvCacheManager to delegate to the existing
single-pool initialization behavior. Keep the grouped initialization path using
init_groups.
🤖 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.

Inline comments:
In `@pypto_serving/model/deepseek/npu_executor.py`:
- Around line 585-591: The docstring for the shape-producing method should stop
describing the returned dimensions as “worst-case.” Reword it to identify them
as minimum-sized or lower-bound JIT dummy shapes, while preserving the
explanation that runtime HBM-backed allocations determine actual dimensions and
generated kernels must derive strides from runtime tensor shapes.

In `@pypto_serving/model/deepseek/npu_runner.py`:
- Around line 4244-4258: Update _ensure_cache_zero_page to explicitly validate
that self._cache_group_specs is non-empty before computing max_page_bytes. Raise
a clear, named error describing that cache group specs must be resolved before
creating the zero page, while preserving the existing calculation for valid
specs.
- Around line 1416-1430: Update _fallback_cache_group_num_blocks to derive one
shared capacity_slots value from the layout’s physical limits and decode_batch,
rounded down to a whole number of group_specs[0].max_blocks_per_seq slots.
Return each cache group’s capacity as capacity_slots multiplied by its
corresponding max_blocks_per_seq, ensuring all fallback values are slot-aligned
and use consistent engine-side scaling.

---

Nitpick comments:
In `@pypto_serving/model/deepseek/npu_runner.py`:
- Around line 3846-3851: Remove the redundant block-ID range validation from the
MTP branch near the page size calculation, including its ValueError path. Keep
the earlier validation against blocks_per_layer and the existing page allocation
logic unchanged.
- Around line 182-186: Clarify the intentional use of DEEPSEEK_V4_DECODE_BATCH
as the divisor in blocks_per_request, noting that decode_batch is
caller-provided and layout-specific while the module constant represents the
fixed kernel table depth. Add a concise comment directly above the calculation
without changing the existing capacity math.
- Around line 1444-1474: Update the capacity calculation around
npu_memory_utilization to access runtime.npu_memory_utilization directly instead
of using getattr, and fail immediately when any device’s peak_non_kv usage
exceeds its configured total-memory budget. Raise a clear error identifying the
affected device and utilization shortfall before capacity_slots applies the
one-slot minimum.
- Around line 3822-3840: Optimize the zero-page initialization loop in the
tensor-group copy flow by hoisting the invariant page-size calculations before
iterating layers, sorting ids, and coalescing consecutive block ids into
contiguous runs. Issue one copy_to call per run using a sufficiently large zero
buffer, while preserving the existing layer offsets, worker_id, bounds
validation, and zeroing behavior in _seed_decode_work_cache_from_group_ids.

In `@pypto_serving/serving/engine/async_engine.py`:
- Line 255: Replace the cross-module call to the private
KvCacheManager._init_blocks in the async engine with a public initializer, such
as init_single_pool(num_blocks, block_size), and implement or reuse that method
on KvCacheManager to delegate to the existing single-pool initialization
behavior. Keep the grouped initialization path using init_groups.

In `@tests/test_batching.py`:
- Around line 351-373: Add a negative test alongside
test_grouped_cache_capacity_scales_from_device_reported_primary_pool that
constructs a primary group with max_blocks_per_seq=3 and calls init_groups with
primary_num_blocks=7. Assert that init_groups raises ValueError and matches the
“positive multiple” validation message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f9864683-4eba-422e-8a2f-c11390662d45

📥 Commits

Reviewing files that changed from the base of the PR and between 07e6706 and a805f25.

📒 Files selected for processing (7)
  • pypto-lib
  • pypto_serving/model/deepseek/npu_executor.py
  • pypto_serving/model/deepseek/npu_runner.py
  • pypto_serving/serving/engine/async_engine.py
  • pypto_serving/serving/memory/kv_cache.py
  • tests/test_batching.py
  • tests/test_deepseek_v4.py

Comment on lines +585 to +591
"""Return worst-case runtime pool shapes used for JIT dummy arguments.

Actual worker allocations are HBM-sized after compilation. Like Qwen's
paged kernels, DeepSeek kernels must derive their layer/page stride from
the runtime tensor shape rather than baking these dummy dimensions into
generated code.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Docstring says "worst-case" but this is a minimum-sized shape.

local_capacity_slots is ceil(max_batch_size / ranks), while the runtime pool is HBM-derived and normally far larger. These dummy dimensions are a lower bound, not a worst case; the surrounding note about deriving strides from the runtime shape is the real invariant. Reword so nobody treats this value as an upper bound on the pool.

🤖 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 `@pypto_serving/model/deepseek/npu_executor.py` around lines 585 - 591, The
docstring for the shape-producing method should stop describing the returned
dimensions as “worst-case.” Reword it to identify them as minimum-sized or
lower-bound JIT dummy shapes, while preserving the explanation that runtime
HBM-backed allocations determine actual dimensions and generated kernels must
derive strides from runtime tensor shapes.

Comment on lines +1416 to +1430
def _fallback_cache_group_num_blocks(self) -> dict[str, int]:
"""Return the old fixed capacities for shape-only/non-runtime callers."""
layout = self._compiled.layout
physical_limits = {
"ori": layout.decode_ori_max_blocks,
"cmp": layout.cmp_max_blocks,
"idx": layout.idx_max_blocks,
"hca_state": layout.hca_state_max_blocks,
"csa_state": layout.csa_state_max_blocks,
"csa_inner_state": layout.csa_inner_state_max_blocks,
}
return {
name: max(1, physical_limits[name] - layout.decode_batch)
for name in DEEPSEEK_V4_CACHE_GROUP_NAMES
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fallback capacities are not slot-aligned, which breaks the engine-side contract.

init_kv_cache returns _cache_group_num_blocks["ori"] and ReplicaEngineCore.start feeds it to KvCacheManager.init_groups(primary_num_blocks=...), which rejects any value that is not a multiple of group_specs[0].max_blocks_per_seq. The fallback computes physical_limit - decode_batch per group, e.g. 128 - 8 = 120 against max_blocks_per_seq = 128 // 8 = 16 → not a multiple, so startup raises. Note this path is reachable outside unit tests: the executor always sets runtime_model, but l3_callables() is empty whenever compile_kernels=False, so a real worker can land here. Additionally the per-group fallback values are not derived from one shared capacity_slots, so the engine's slots * max_blocks_per_seq scaling would not match the runner's pools even when divisibility happens to hold.

🛠️ Derive the fallback from a whole number of capacity slots
     def _fallback_cache_group_num_blocks(self) -> dict[str, int]:
         """Return the old fixed capacities for shape-only/non-runtime callers."""
         layout = self._compiled.layout
         physical_limits = {
             "ori": layout.decode_ori_max_blocks,
             "cmp": layout.cmp_max_blocks,
             "idx": layout.idx_max_blocks,
             "hca_state": layout.hca_state_max_blocks,
             "csa_state": layout.csa_state_max_blocks,
             "csa_inner_state": layout.csa_inner_state_max_blocks,
         }
-        return {
-            name: max(1, physical_limits[name] - layout.decode_batch)
-            for name in DEEPSEEK_V4_CACHE_GROUP_NAMES
-        }
+        specs = {spec.name: spec for spec in self._cache_group_specs}
+        slots = max(
+            1,
+            min(
+                max(1, physical_limits[name] - layout.decode_batch)
+                // specs[name].max_blocks_per_seq
+                for name in DEEPSEEK_V4_CACHE_GROUP_NAMES
+            ),
+        )
+        return deepseek_v4_cache_blocks_for_slots(self._cache_group_specs, slots)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _fallback_cache_group_num_blocks(self) -> dict[str, int]:
"""Return the old fixed capacities for shape-only/non-runtime callers."""
layout = self._compiled.layout
physical_limits = {
"ori": layout.decode_ori_max_blocks,
"cmp": layout.cmp_max_blocks,
"idx": layout.idx_max_blocks,
"hca_state": layout.hca_state_max_blocks,
"csa_state": layout.csa_state_max_blocks,
"csa_inner_state": layout.csa_inner_state_max_blocks,
}
return {
name: max(1, physical_limits[name] - layout.decode_batch)
for name in DEEPSEEK_V4_CACHE_GROUP_NAMES
}
def _fallback_cache_group_num_blocks(self) -> dict[str, int]:
"""Return the old fixed capacities for shape-only/non-runtime callers."""
layout = self._compiled.layout
physical_limits = {
"ori": layout.decode_ori_max_blocks,
"cmp": layout.cmp_max_blocks,
"idx": layout.idx_max_blocks,
"hca_state": layout.hca_state_max_blocks,
"csa_state": layout.csa_state_max_blocks,
"csa_inner_state": layout.csa_inner_state_max_blocks,
}
specs = {spec.name: spec for spec in self._cache_group_specs}
slots = max(
1,
min(
max(1, physical_limits[name] - layout.decode_batch)
// specs[name].max_blocks_per_seq
for name in DEEPSEEK_V4_CACHE_GROUP_NAMES
),
)
return deepseek_v4_cache_blocks_for_slots(self._cache_group_specs, slots)
🤖 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 `@pypto_serving/model/deepseek/npu_runner.py` around lines 1416 - 1430, Update
_fallback_cache_group_num_blocks to derive one shared capacity_slots value from
the layout’s physical limits and decode_batch, rounded down to a whole number of
group_specs[0].max_blocks_per_seq slots. Return each cache group’s capacity as
capacity_slots multiplied by its corresponding max_blocks_per_seq, ensuring all
fallback values are slot-aligned and use consistent engine-side scaling.

Comment on lines +4244 to +4258
def _ensure_cache_zero_page(self) -> torch.Tensor:
"""Create one pre-fork zero source reused for device-page clearing."""
if self._cache_zero_page is not None:
return self._cache_zero_page
self._ensure_shared_host_allocation_before_worker("cache zero page")
max_page_bytes = max(
spec.spec.page_size_bytes // max(len(spec.layer_indices), 1)
for spec in self._cache_group_specs
)
for tensor in (
cache.kv_cache,
cache.cmp_kv,
cache.idx_kv_cache,
cache.idx_kv_scale,
cache.hca_compress_state,
cache.csa_compress_state,
cache.csa_inner_compress_state,
):
tensor.zero_()
self._decode_work_cache = cache
return cache
max_page_bytes = max(
max_page_bytes,
self._compiled.layout.block_size
* DEEPSEEK_V4_HEAD_DIM
* torch.bfloat16.itemsize,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the empty-spec case with an explicit error.

max(...) over self._cache_group_specs raises ValueError: max() arg is an empty sequence when specs have not been resolved yet. That ordering is reachable: preflight() calls _ensure_l3_shared_buffers directly, which now stages the zero page, and _cache_group_specs is only populated by init_kv_cache. A named error makes the ordering requirement obvious.

🛡️ Proposed guard
         self._ensure_shared_host_allocation_before_worker("cache zero page")
+        if not self._cache_group_specs:
+            raise RuntimeError(
+                "DeepSeekV4 cache group specs must be resolved before staging the zero page"
+            )
         max_page_bytes = max(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _ensure_cache_zero_page(self) -> torch.Tensor:
"""Create one pre-fork zero source reused for device-page clearing."""
if self._cache_zero_page is not None:
return self._cache_zero_page
self._ensure_shared_host_allocation_before_worker("cache zero page")
max_page_bytes = max(
spec.spec.page_size_bytes // max(len(spec.layer_indices), 1)
for spec in self._cache_group_specs
)
for tensor in (
cache.kv_cache,
cache.cmp_kv,
cache.idx_kv_cache,
cache.idx_kv_scale,
cache.hca_compress_state,
cache.csa_compress_state,
cache.csa_inner_compress_state,
):
tensor.zero_()
self._decode_work_cache = cache
return cache
max_page_bytes = max(
max_page_bytes,
self._compiled.layout.block_size
* DEEPSEEK_V4_HEAD_DIM
* torch.bfloat16.itemsize,
)
def _ensure_cache_zero_page(self) -> torch.Tensor:
"""Create one pre-fork zero source reused for device-page clearing."""
if self._cache_zero_page is not None:
return self._cache_zero_page
self._ensure_shared_host_allocation_before_worker("cache zero page")
if not self._cache_group_specs:
raise RuntimeError(
"DeepSeekV4 cache group specs must be resolved before staging the zero page"
)
max_page_bytes = max(
spec.spec.page_size_bytes // max(len(spec.layer_indices), 1)
for spec in self._cache_group_specs
)
max_page_bytes = max(
max_page_bytes,
self._compiled.layout.block_size
* DEEPSEEK_V4_HEAD_DIM
* torch.bfloat16.itemsize,
)
🤖 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 `@pypto_serving/model/deepseek/npu_runner.py` around lines 4244 - 4258, Update
_ensure_cache_zero_page to explicitly validate that self._cache_group_specs is
non-empty before computing max_page_bytes. Raise a clear, named error describing
that cache group specs must be resolved before creating the zero page, while
preserving the existing calculation for valid specs.

@superxf
superxf force-pushed the dynamic_kvcache branch 3 times, most recently from be7d3ec to 68e288c Compare July 27, 2026 09:56
if primary_num_blocks is not None:
primary_num_blocks = int(primary_num_blocks)
primary_stride = group_specs[0].max_blocks_per_seq
if primary_num_blocks <= 0 or primary_num_blocks % primary_stride:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_fallback_cache_group_num_blocks 里计算的大小不符合整除要求,是否要一起修改

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已修改,先会进行取整
capacity_slots = min( (physical_limit - decode_batch) // max_blocks_per_seq )

)
else:
self.kv_cache_manager._init_blocks(actual_num_pages, self._runtime.page_size)
self.kv_cache_manager._init_blocks(reported_num_blocks, self._runtime.page_size)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

KvCacheManager 能否提供统一、public的初始化,自己内部区分 init_groups 或 _init_blocks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

之后在 KvCacheManager 增加统一 public 初始化接口,由 manager 根据 RuntimeConfig.kv_cache_groups 选择对应路径

@superxf
superxf merged commit e57e14f into hw-native-sys:main Jul 28, 2026
4 checks passed
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.

2 participants