Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4ea0189
workaround
vadiklyutiy Feb 24, 2026
152ccc2
zeroing kv-cache block after allocation
vadiklyutiy Feb 25, 2026
27ab3fa
Merge branch 'main' into vadim/issue35138
vadiklyutiy Feb 25, 2026
af817b7
optimize
vadiklyutiy Feb 25, 2026
053d305
make a bit better code
vadiklyutiy Feb 26, 2026
3fc05cd
Merge branch 'main' into vadim/issue35138
vadiklyutiy Feb 27, 2026
13271d9
fixes
vadiklyutiy Mar 2, 2026
3654a39
fixes
vadiklyutiy Mar 2, 2026
a507809
Merge branch 'main' into vadim/issue35138
vadiklyutiy Mar 2, 2026
59fcf43
zeroing only full attn
vadiklyutiy Mar 3, 2026
0dd399a
Merge branch 'main' into vadim/issue35138
vadiklyutiy Mar 3, 2026
888e947
fix
vadiklyutiy Mar 3, 2026
387cf77
fix
vadiklyutiy Mar 3, 2026
72d4aaf
fix bugs
vadiklyutiy Mar 4, 2026
3115077
fix pin memory
vadiklyutiy Mar 4, 2026
61e6b3c
fix cumem bug
vadiklyutiy Mar 4, 2026
56ae39a
code style
vadiklyutiy Mar 4, 2026
6e91a13
limit to hybrid model only
vadiklyutiy Mar 4, 2026
624fdea
Merge branch 'main' into vadim/issue35138
vadiklyutiy Mar 4, 2026
e26d486
pre-commit fix
vadiklyutiy Mar 4, 2026
a000a63
Merge branch 'main' into vadim/issue35138
vadiklyutiy Mar 6, 2026
2fea792
Merge branch 'main' into vadim/issue35138
vadiklyutiy Mar 9, 2026
267646a
revert workaround for pre-commit in hermes_tool_parser.py
vadiklyutiy Mar 9, 2026
61feb0c
move finding of block dim to attn backend
vadiklyutiy Mar 9, 2026
9c92ec2
resolve PR comment
vadiklyutiy Mar 9, 2026
145c7fa
move zeroing from vllm/utils/math_utils.py to vllm/v1/worker/utils.py
vadiklyutiy Mar 10, 2026
67fcd34
call _init_kv_zero_meta for model with mamba
vadiklyutiy Mar 10, 2026
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
11 changes: 11 additions & 0 deletions vllm/v1/core/block_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ def __init__(

self.metrics_collector = metrics_collector

# Block IDs allocated since the last drain. The worker uses these
# to zero the corresponding GPU memory before the blocks are used.
self.new_block_ids: list[int] = []

def get_cached_block(
self, block_hash: BlockHash, kv_cache_group_ids: list[int]
) -> list[KVCacheBlock] | None:
Expand Down Expand Up @@ -347,8 +351,15 @@ def get_new_blocks(self, num_blocks: int) -> list[KVCacheBlock]:
block.ref_cnt += 1
if self.metrics_collector:
self.metrics_collector.on_block_allocated(block)
self.new_block_ids.extend(block.block_id for block in ret)
return ret

def take_new_block_ids(self) -> list[int]:
"""Drain and return block IDs allocated since the last call."""
ids = self.new_block_ids
self.new_block_ids = []
return ids

def _maybe_evict_cached_block(self, block: KVCacheBlock) -> bool:
"""
If a block is cached in `cached_block_hash_to_block`, we reset its hash
Expand Down
4 changes: 4 additions & 0 deletions vllm/v1/core/kv_cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ def create_kv_cache_blocks(
# Only create new KVCacheBlocks for non-empty blocks
return KVCacheBlocks(blocks) if any(blocks) else self.empty_kv_cache_blocks

def take_new_block_ids(self) -> list[int]:
"""Drain and return block IDs allocated since the last call."""
return self.block_pool.take_new_block_ids()

def new_step_starts(self) -> None:
"""Called when a new step is started."""
self.coordinator.new_step_starts()
5 changes: 5 additions & 0 deletions vllm/v1/core/sched/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ class SchedulerOutput:
# EC Cache Connector metadata
ec_connector_metadata: ECConnectorMetadata | None = None

# Block IDs freshly allocated from the pool during this scheduling step.
# The worker zeros the corresponding GPU memory before the blocks are used,
# preventing stale NaN/data from corrupting attention or SSM computation.
new_block_ids_to_zero: list[int] | None = None

@classmethod
def make_empty(cls) -> "SchedulerOutput":
return cls(
Expand Down
3 changes: 3 additions & 0 deletions vllm/v1/core/sched/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,8 @@ def schedule(self) -> SchedulerOutput:
self.prev_step_scheduled_req_ids.clear()
self.prev_step_scheduled_req_ids.update(num_scheduled_tokens.keys())

new_block_ids_to_zero = self.kv_cache_manager.take_new_block_ids() or None

scheduler_output = SchedulerOutput(
scheduled_new_reqs=new_reqs_data,
scheduled_cached_reqs=cached_reqs_data,
Expand All @@ -886,6 +888,7 @@ def schedule(self) -> SchedulerOutput:
# the previous and the current steps.
finished_req_ids=self.finished_req_ids,
free_encoder_mm_hashes=self.encoder_cache_manager.get_freed_mm_hashes(),
new_block_ids_to_zero=new_block_ids_to_zero,
)

# NOTE(Kuntai): this function is designed for multiple purposes:
Expand Down
18 changes: 18 additions & 0 deletions vllm/v1/worker/gpu_model_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ def __init__(
# self.model: nn.Module # Set after load_model
# Initialize in initialize_kv_cache
self.kv_caches: list[torch.Tensor] = []
# (raw_tensor, page_size_bytes) pairs for zeroing newly allocated blocks.
# Populated by _allocate_kv_cache_tensors.
self.kv_cache_raw_buffers: list[tuple[torch.Tensor, int]] = []
# Initialize in initialize_kv_cache_tensors
self.cross_layers_kv_cache: torch.Tensor | None = None
self.cross_layers_attn_backend: type[AttentionBackend] | None = None
Expand Down Expand Up @@ -906,6 +909,13 @@ def _may_reorder_batch(self, scheduler_output: "SchedulerOutput") -> None:
decode_threshold=self.reorder_batch_threshold,
)

def _zero_block_ids(self, block_ids: list[int]) -> None:
"""Zero the raw KV cache memory for the given block IDs."""
for raw_tensor, page_size in self.kv_cache_raw_buffers:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it be more efficient to build an index tensor and have one op to zero at all the block id slots?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How many block ids would we normally see for a typical prefill/decode? Is it very few?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This zeroing takes small amount of time. We do it once per forward step and only for new.

@benchislett Can you say right away does it code works in sync or async part?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It happens always.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I notice that this is not specific to SSM blocks, and it clears all new KV blocks. Will this have a detrimental effect on prefills for non-mamba deployments where block_size=16?

In this case if we get a prefill of 8k tokens, that will be 512 new blocks, right? I think that would lead to 512 kernel invocations in this implementation. If that is indeed the case, this will not suffice.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

right,
I am optimizing it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

does it make sense to use torch.tensor for block ids and use a gpu operation to zero the indices in tensors?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See my comment below

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

does it make sense to use torch.tensor for block ids and use a gpu operation to zero the indices in tensors?

I implemented zeroing as a triton kernel

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Pls lets me know if there is a better way to do it

for bid in block_ids:
start = bid * page_size
raw_tensor[start : start + page_size] = 0

# Note: used for model runner override.
def _init_device_properties(self) -> None:
"""Initialize attributes from torch.cuda.get_device_properties"""
Expand Down Expand Up @@ -939,6 +949,11 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None:
for req_id in scheduler_output.finished_req_ids:
self.input_batch.remove_request(req_id)

# Zero GPU memory for freshly allocated cache blocks to prevent
# stale NaN/data from corrupting attention or SSM computation.
if scheduler_output.new_block_ids_to_zero:
self._zero_block_ids(scheduler_output.new_block_ids_to_zero)

Comment thread
vadiklyutiy marked this conversation as resolved.
# Free the cached encoder outputs.
for mm_hash in scheduler_output.free_encoder_mm_hashes:
self.encoder_cache.pop(mm_hash, None)
Expand Down Expand Up @@ -5815,12 +5830,15 @@ def _allocate_kv_cache_tensors(
corresponding memory buffer for KV cache.
"""
kv_cache_raw_tensors: dict[str, torch.Tensor] = {}
num_blocks = kv_cache_config.num_blocks
for kv_cache_tensor in kv_cache_config.kv_cache_tensors:
tensor = torch.zeros(
kv_cache_tensor.size, dtype=torch.int8, device=self.device
)
for layer_name in kv_cache_tensor.shared_by:
kv_cache_raw_tensors[layer_name] = tensor
page_size = kv_cache_tensor.size // num_blocks
Comment thread
vadiklyutiy marked this conversation as resolved.
Outdated
self.kv_cache_raw_buffers.append((tensor, page_size))

layer_names = set()
for group in kv_cache_config.kv_cache_groups:
Expand Down