Skip to content
Draft
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
35 changes: 35 additions & 0 deletions lib/kv-router/src/zmq_wire/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ impl std::fmt::Display for ConvertError {

impl std::error::Error for ConvertError {}

/// True when a BlockStored's `block_size` is smaller than the configured cache
/// block size: a partial-prefix (hash-boundary) entry -- vLLM hashes prefixes
/// every `prefix_match_unit` tokens, so the prompt tail inside a cache block is
/// published with block_size = k * prefix_match_unit (32/64/96 for a 128-token
/// block). A *larger* event block size can only be a --block-size misconfig.
pub fn is_partial_prefix_entry(event_block_size: usize, kv_block_size: u32) -> bool {
event_block_size > 0 && event_block_size < kv_block_size as usize
}

/// Convert a raw event coming from the ZMQ channel into a placement-aware worker event.
pub fn convert_event(
raw: RawKvEvent,
Expand Down Expand Up @@ -73,6 +82,32 @@ pub fn convert_event(
kv_cache_spec_kind: _,
kv_cache_spec_sliding_window: _,
} => {
if !block_hashes.is_empty()
&& is_partial_prefix_entry(block_size, kv_block_size)
{
// A partial-prefix entry: vLLM hashes prefixes every
// `prefix_match_unit` (hash_block_size) tokens, which can be
// finer than the cache block size, and publishes the prompt
// tail that ends inside a cache block as a BlockStored whose
// `block_size` is that sub-block length (e.g. 32/64/96 for a
// 128-token block). The indexer keys on whole cache blocks, so
// these carry no routable information: drop them instead of
// treating them as a --block-size misconfiguration.
if warning_count.fetch_add(1, Ordering::Relaxed) < 3 {
tracing::warn!(
event_id,
worker_id = worker.worker_id,
dp_rank = worker.dp_rank,
event_block_size = block_size,
configured_block_size = kv_block_size,
"Skipping sub-block BlockStored: a partial-prefix entry \
(prefix_match_unit < block_size), unless the indexer's \
--block-size is larger than the engine's -- then the \
index stays empty; check configured vs event size"
);
}
return Ok(None);
}
if !block_hashes.is_empty() && block_size != kv_block_size as usize {
tracing::error!(
event_id,
Expand Down
5 changes: 4 additions & 1 deletion lib/kv-router/src/zmq_wire/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ mod filter;
mod tests;
mod types;

pub use convert::{ConvertError, convert_event, create_stored_block_from_parts, create_stored_blocks};
pub use convert::{
ConvertError, convert_event, create_stored_block_from_parts, create_stored_blocks,
is_partial_prefix_entry,
};
pub use extra_keys::{extra_keys_to_block_mm_infos, parse_mm_hash_from_extra_key};
pub use filter::KvCacheSpecKind;
pub use types::{BlockHashValue, ExtraKeyItem, KvEventBatch, KvTokenIds, RawKvEvent};
Expand Down
33 changes: 33 additions & 0 deletions lib/kv-router/src/zmq_wire/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,39 @@ fn test_convert_event_block_size_mismatch_is_fatal() {
);
}

#[test]
fn test_convert_event_partial_prefix_entry_is_skipped() {
// vLLM with prefix_match_unit (hash_block_size) < block_size publishes the
// prompt tail that ends inside a cache block as a BlockStored whose
// block_size is the sub-block length (32/64/96 for a 128-token block).
// That is not a --block-size misconfiguration: drop it, don't exit.
for partial in [32usize, 64, 96] {
let raw_event = RawKvEvent::BlockStored {
block_hashes: vec![BlockHashValue::Unsigned(21)],
parent_block_hash: Some(BlockHashValue::Unsigned(9)),
token_ids: vec![10; partial],
block_size: partial,
medium: None,
lora_name: None,
block_mm_infos: None,
is_eagle: None,
group_idx: Some(4),
kv_cache_spec_kind: Some(KvCacheSpecKind::MlaAttention),
kv_cache_spec_sliding_window: None,
};
let warning_count = Arc::new(AtomicU32::new(0));
let result =
convert_event(raw_event, 7, 128, WorkerWithDpRank::new(3, 0), &warning_count)
.expect("partial-prefix entry is not a config error");
assert!(result.is_none(), "partial entry of {partial} tokens must be dropped");
}
// Anything smaller is a partial entry; equal/larger is not.
assert!(is_partial_prefix_entry(96, 128));
assert!(!is_partial_prefix_entry(256, 128));
assert!(!is_partial_prefix_entry(128, 128));
assert!(is_partial_prefix_entry(32, 128));
}

#[test]
fn test_convert_event_empty_store_is_not_fatal() {
// No blocks to publish -> nothing can mismatch; must not error.
Expand Down
Loading