Skip to content

Commit 5ee2301

Browse files
mudlerclaude
andcommitted
refactor(v1/core): re-port BlockPool + Request block-hashes to e24d1b24 API (was classic); fix free_blocks LRU split
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
1 parent 2bba0ad commit 5ee2301

8 files changed

Lines changed: 556 additions & 228 deletions

File tree

include/vllm/v1/core/block_pool.h

Lines changed: 89 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,42 @@
88
// (no CUDA, no model): the physical KV memory itself is not touched here, only
99
// the block bookkeeping.
1010
//
11-
// VERSION NOTE (which upstream is mirrored): the landed kv_cache_utils.{h,cpp}
12-
// mirror the CLASSIC vLLM V1 block-hashing API — free functions
13-
// hash_block_tokens / hash_request_tokens that take a pluggable hash_function,
14-
// BlockHash as raw hash bytes, and make_block_hash_with_group_id packing a
15-
// 4-byte group id. BlockPool is ported to match THOSE utils, i.e. the classic
16-
// BlockPool shape:
17-
// - cached_block_hash_to_block is a plain
18-
// {BlockHashWithGroupId -> {block_id -> KVCacheBlock*}} nesting (the classic
19-
// defaultdict(dict)); the e24d1b24 BlockHashToBlockMap single-vs-dict union
20-
// optimization is intentionally NOT ported — it is a GC micro-optimization
21-
// with identical observable semantics.
22-
// - cache_full_blocks carries the classic (block_hashes, hash_fn) parameters:
23-
// the caller passes the precomputed per-block hashes (from
24-
// hash_request_tokens) and a hash function used to fill in any trailing
25-
// blocks whose hashes were not precomputed. The e24d1b24 signature instead
26-
// reads request.block_hashes (a Request field not present in the T0 Request
27-
// port) and takes hash_block_size on the pool; that path is not portable
28-
// against the landed utils, so the classic signature is used.
29-
// The @ e24d1b24 pin marks the upstream tree tracked for the surrounding V1
30-
// core; the BlockPool logic mirrored is upstream's classic pre-BlockHashToBlockMap
31-
// form, consistent with the already-landed classic-hashing utils.
11+
// API FIDELITY: this mirrors the e24d1b24 BlockPool API 1:1 for the COMMON
12+
// prefix-caching path. cache_full_blocks takes the pinned signature
13+
// (request, blocks, num_cached_blocks, num_full_blocks, block_size,
14+
// kv_cache_group_id, block_mask) and reads request.block_hashes — the per-block
15+
// hashes the Request computes incrementally via its _block_hasher
16+
// (get_request_block_hasher). The pool carries hash_block_size (per the pin).
3217
//
33-
// DEFERRED, recorded (kept as stubs / no-ops so signatures stay 1:1 and the
34-
// later units fill them in without a call-site change):
18+
// DEFERRED behind 1:1 stubs (documented; the gate models — text-only GDN/MoE —
19+
// never exercise these, and later units fill them in without a call-site change):
20+
// - The align / multiple-block-size path in cache_full_blocks
21+
// (block_size != hash_block_size, upstream's BlockHashListWithBlockSize):
22+
// guarded with a throw. Only the block_size == hash_block_size common case
23+
// is ported.
24+
// - The partial->full promotion branch inside cache_full_blocks (a "new full
25+
// block" that already carries a hash): guarded with a throw. Requires the
26+
// deferred partial primitives.
27+
// - cache_partial_block / _get_partial_block_hash /
28+
// _get_partial_block_parent_hash_and_start: throw-if-called stubs (partial
29+
// prefix entries, not needed by the gate models).
30+
// - evict_blocks (KV-connector-driven cache eviction): throw-if-called stub.
3531
// - KV-cache events: enable_kv_cache_events, kv_event_queue, take_events(),
36-
// and the BlockStored / BlockRemoved emission inside cache_full_blocks /
37-
// _maybe_evict_cached_block. KVCacheEvent is a placeholder struct; the
38-
// emission branches are omitted (marked in the .cpp). This mirrors upstream
39-
// vllm/distributed/kv_events.py, deferred with the rest of the V1 event bus.
32+
// and the BlockStored / BlockRemoved / AllBlocksCleared emission inside
33+
// cache_full_blocks / _remove_cached_block_hashes / reset_prefix_cache.
34+
// KVCacheEvent is a placeholder struct; the emission branches are omitted
35+
// (marked in the .cpp). Mirrors upstream vllm/distributed/kv_events.py.
4036
// - metrics_collector (KVCacheMetricsCollector): the on_block_allocated /
41-
// _accessed / _evicted hooks are omitted. Not part of the correctness core.
42-
// - cache_partial_block / evict_blocks: not in the classic shape and not
43-
// needed by the gate models; omitted.
37+
// _accessed / _evicted / reset hooks are omitted. Not part of the
38+
// correctness core.
39+
// - block_mask: the parameter is present and honored (masked blocks skipped),
40+
// but only ever passed None by the ported (non-SWA/non-mamba) call sites.
41+
//
42+
// DEVIATION, recorded: the BlockHashToBlockMap single-block-vs-dict union
43+
// (upstream's GC micro-optimization) is NOT ported. cached_block_hash_to_block
44+
// is the plain {BlockHashWithGroupId -> {block_id -> KVCacheBlock*}} nesting.
45+
// Observable semantics are identical (get_one_block / contain / insert / pop
46+
// map onto the nested-map operations); only the inner GC cost differs.
4447
//
4548
// DEVIATIONS, recorded:
4649
// - The null block (block_id 0) is made un-allocatable exactly as upstream:
@@ -64,6 +67,7 @@
6467
#include <cstdint>
6568
#include <map>
6669
#include <optional>
70+
#include <set>
6771
#include <unordered_map>
6872
#include <vector>
6973

@@ -88,8 +92,11 @@ class BlockPool {
8892
// Args:
8993
// num_gpu_blocks: The number of blocks in the pool (must be > 0).
9094
// enable_caching: Whether to enable prefix caching.
95+
// hash_block_size: The block size at which block hashes are computed. The
96+
// actual block size usually equals it; with differently-sized KV cache
97+
// groups it can be a multiple (the align path, DEFERRED).
9198
// enable_kv_cache_events: Whether to enable kv cache events (DEFERRED).
92-
BlockPool(int64_t num_gpu_blocks, bool enable_caching,
99+
BlockPool(int64_t num_gpu_blocks, bool enable_caching, int hash_block_size,
93100
bool enable_kv_cache_events = false);
94101

95102
// Non-copyable / non-movable (see DEVIATIONS in the file header).
@@ -104,25 +111,39 @@ class BlockPool {
104111
std::optional<std::vector<KVCacheBlock*>> get_cached_block(
105112
const BlockHash& block_hash, const std::vector<int>& kv_cache_group_ids);
106113

107-
// Cache a list of full blocks for prefix caching. Updates each newly-full
108-
// block's hash metadata and inserts it into cached_block_hash_to_block.
114+
// Cache a list of full blocks for prefix caching. Reads request.block_hashes
115+
// (computed incrementally by the Request's _block_hasher), updates each
116+
// newly-full block's hash metadata and inserts it into
117+
// cached_block_hash_to_block.
109118
//
110-
// block_hashes: the request's per-block hashes; block_hashes[k] is the hash
111-
// of the k-th full block. May be extended in place for trailing blocks
112-
// whose hash was not precomputed (the else branch computes it via hash_fn
113-
// and appends it — matching upstream, which caches it on the request for a
114-
// possible future preemption).
119+
// request: the request whose block_hashes drive caching.
120+
// blocks: all blocks in the request (the range
121+
// [num_cached_blocks, num_full_blocks) is cached).
115122
// num_cached_blocks: number of blocks already cached.
116123
// num_full_blocks: number of blocks that are full and should be cached now.
117124
// block_size: number of tokens per block.
118125
// kv_cache_group_id: id of the KV cache group.
119-
// hash_fn: the pluggable block-hash function (see kv_cache_utils.h).
120-
void cache_full_blocks(const Request& request,
121-
const std::vector<KVCacheBlock*>& blocks,
122-
std::vector<BlockHash>& block_hashes,
123-
int num_cached_blocks, int num_full_blocks,
124-
int block_size, int kv_cache_group_id,
125-
const HashFn& hash_fn);
126+
// block_mask: optional mask aligned with blocks[num_cached_blocks:
127+
// num_full_blocks]; masked-off (false) blocks are skipped like null blocks.
128+
// Only ever None from the ported call sites (see header DEFERRED note).
129+
//
130+
// Common path only (block_size == hash_block_size); the align path and the
131+
// partial->full promotion branch are DEFERRED (throw). See the header.
132+
void cache_full_blocks(
133+
const Request& request, const std::vector<KVCacheBlock*>& blocks,
134+
int num_cached_blocks, int num_full_blocks, int block_size,
135+
int kv_cache_group_id,
136+
const std::optional<std::vector<bool>>& block_mask = std::nullopt);
137+
138+
// Register a partial prefix-cache entry for an existing block. DEFERRED 1:1
139+
// stub (partial primitives, not needed by the gate models): throws if called.
140+
std::optional<BlockHashWithGroupId> cache_partial_block(
141+
const Request& request, KVCacheBlock* block, int num_tokens,
142+
int kv_cache_group_id, int block_size);
143+
144+
// Evict blocks from the prefix cache by their block IDs (KV-connector-driven).
145+
// DEFERRED 1:1 stub: throws if called.
146+
void evict_blocks(const std::set<int>& block_ids);
126147

127148
// Get num_blocks new blocks from the free block pool. Does NOT check the
128149
// prefix cache. Throws std::runtime_error (upstream ValueError) if there are
@@ -133,6 +154,20 @@ class BlockPool {
133154
// and evict it from the cache. Returns true iff the block was evicted.
134155
bool _maybe_evict_cached_block(KVCacheBlock* block);
135156

157+
// Remove every hash key that points to `block` (its primary hash plus any
158+
// partial-alias hashes in cached_block_hashes_by_block), reset the block's
159+
// hash, and return the keys actually removed from the map. Mirrors upstream
160+
// _remove_cached_block_hashes.
161+
std::vector<BlockHashWithGroupId> _remove_cached_block_hashes(
162+
KVCacheBlock* block);
163+
164+
// Insert a hash key for `block` into cached_block_hash_to_block. If the block
165+
// has no primary hash yet, the key becomes its primary hash (with num_tokens);
166+
// otherwise the key is tracked as a partial alias in
167+
// cached_block_hashes_by_block. Mirrors upstream _insert_block_hash.
168+
void _insert_block_hash(const BlockHashWithGroupId& block_hash_with_group_id,
169+
KVCacheBlock* block, std::optional<int> num_tokens);
170+
136171
// Touch a block: increase its reference count by 1, and remove it from the
137172
// free queue if it was an eviction candidate (ref_cnt == 0). Used when a
138173
// block is hit by another request with the same prefix.
@@ -162,6 +197,7 @@ class BlockPool {
162197

163198
int64_t num_gpu_blocks;
164199
bool enable_caching;
200+
int hash_block_size;
165201

166202
// All kv-cache blocks, indexed by block_id (blocks[0] is the null block).
167203
std::vector<KVCacheBlock> blocks;
@@ -176,6 +212,14 @@ class BlockPool {
176212
std::unordered_map<BlockHashWithGroupId, std::map<int, KVCacheBlock*>>
177213
cached_block_hash_to_block;
178214

215+
// {block_id -> set of partial-alias hash keys}. Tracks the extra hash keys
216+
// (beyond a block's primary block_hash) that point to the block, so eviction /
217+
// reset / promotion can remove every key. Only populated by the deferred
218+
// partial primitives; empty on the common path. Mirrors upstream
219+
// cached_block_hashes_by_block.
220+
std::unordered_map<int, std::set<BlockHashWithGroupId>>
221+
cached_block_hashes_by_block;
222+
179223
// The null placeholder block (block_id 0). Its ref_cnt is NOT maintained;
180224
// it is popped out of the free queue so it can never be allocated.
181225
KVCacheBlock* null_block;

include/vllm/v1/core/kv_cache_utils.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,18 @@ class FreeKVCacheBlockQueue {
268268
KVCacheBlock fake_free_list_tail{-1};
269269
};
270270

271-
// Forward declaration for generate_block_hash_extra_keys (defined in
272-
// vllm/v1/request.h; only a const-ref is needed here).
271+
// Forward declaration for generate_block_hash_extra_keys / the request block
272+
// hasher (Request is defined in vllm/v1/request.h; only a const-ref is needed
273+
// here).
273274
struct Request;
274275

276+
// The request block hasher. Mirrors upstream's
277+
// `Callable[[Request], list[BlockHash]]` (Request._block_hasher): given a
278+
// request, returns the block hashes for its newly-complete full blocks (the
279+
// ones not yet in request.block_hashes). A null BlockHasher mirrors upstream
280+
// `block_hasher=None` => prefix caching off (update_block_hashes is a no-op).
281+
using BlockHasher = std::function<std::vector<BlockHash>(const Request&)>;
282+
275283
// generate_block_hash_extra_keys — DEFERRED derivation.
276284
//
277285
// Upstream derives a block's extra hash keys from a Request's multi-modal
@@ -310,6 +318,17 @@ std::vector<BlockHash> hash_request_tokens(
310318
const std::vector<int32_t>& token_ids,
311319
const std::vector<ExtraKeys>& per_block_extra_keys = {});
312320

321+
// Build the incremental request block hasher. Mirrors upstream
322+
// get_request_block_hasher: the returned closure computes ONLY the not-yet-hashed
323+
// full blocks of a request (starting at
324+
// request.block_hashes.size() * hash_block_size), chaining each block's hash
325+
// onto the previous one (request.block_hashes.back() as the parent). It hashes
326+
// full blocks only; a partial trailing block is left unhashed. Request stores
327+
// this closure as _block_hasher and calls it from update_block_hashes() at
328+
// construction and after each append.
329+
BlockHasher get_request_block_hasher(int hash_block_size,
330+
const HashFn& caching_hash_fn);
331+
313332
} // namespace vllm::v1
314333

315334
#endif // VLLM_V1_CORE_KV_CACHE_UTILS_H_

include/vllm/v1/request.h

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
// slot these in without reshaping the struct:
1515
// - prompt_embeds / prompt_is_token_ids / _prompt_embeds_per_block_hashes,
1616
// mm_features (multimodal), pooling_params, structured_output_request,
17-
// lora_request, cache_salt (prefix caching salt), block_hashes /
18-
// _block_hasher / update_block_hashes (M1.2 BlockPool), events /
17+
// lora_request, cache_salt (prefix caching salt), events /
1918
// stop_reason / kv_transfer_params, spec_token_ids, priority /
2019
// client_index / __lt__ (priority scheduling), streaming / resumable
2120
// state, prefill_stats, async-scheduling counters
@@ -48,6 +47,7 @@
4847
#include <vector>
4948

5049
#include "vllm/sampling_params.h"
50+
#include "vllm/v1/core/kv_cache_utils.h" // BlockHash, BlockHasher
5151

5252
namespace vllm::v1 {
5353

@@ -106,16 +106,22 @@ std::optional<FinishReason> GetFinishedReason(RequestStatus status);
106106
// A generation request tracked by the V1 engine (T0 field subset). The
107107
// scheduler / model runner mutate this in place exactly as upstream does.
108108
struct Request {
109+
// block_hasher mirrors upstream's `block_hasher=None` default: null => prefix
110+
// caching off (update_block_hashes() is a no-op, block_hashes stays empty).
109111
Request(std::string request_id, std::vector<int32_t> prompt_token_ids,
110-
SamplingParams sampling_params, double arrival_time);
112+
SamplingParams sampling_params, double arrival_time,
113+
BlockHasher block_hasher = nullptr);
111114

112115
// from_engine_core_request: build a Request from the frontend->core message.
113116
// Mirrors upstream Request.from_engine_core_request for the T0 fields
114117
// (request_id, prompt_token_ids, sampling_params, arrival_time); status
115118
// starts kWaiting, num_computed_tokens 0, output empty. The params arrived
116119
// already PostInit'd / validated by the frontend, so this does NOT
117-
// re-validate (upstream's factory doesn't either).
118-
static Request FromEngineCoreRequest(const EngineCoreRequest& request);
120+
// re-validate (upstream's factory doesn't either). block_hasher is injected
121+
// by the engine exactly as upstream from_engine_core_request(request,
122+
// block_hasher).
123+
static Request FromEngineCoreRequest(const EngineCoreRequest& request,
124+
BlockHasher block_hasher = nullptr);
119125

120126
std::string request_id;
121127
std::vector<int32_t> prompt_token_ids;
@@ -138,12 +144,28 @@ struct Request {
138144
// num_output_tokens: len(_output_token_ids).
139145
int NumOutputTokens() const;
140146

147+
// Per-block hashes of this request's full blocks, computed at
148+
// hash_block_size granularity and chained over the full prefix. Populated by
149+
// update_block_hashes() via _block_hasher (empty when caching is off).
150+
std::vector<BlockHash> block_hashes;
151+
152+
// The block hasher (upstream _block_hasher). Stored without binding a
153+
// back-reference to this Request (upstream avoids the Request->partial->Request
154+
// reference cycle); here it is a plain std::function taking the request by
155+
// const ref. Null => prefix caching off.
156+
BlockHasher block_hasher_;
157+
141158
// append_output_token_ids(int | list[int]): append the sampled token(s) to
142-
// output_token_ids. (Upstream also mirrors into _all_token_ids and updates
143-
// block hashes; NumTokens recomputes here and block hashing is deferred.)
159+
// output_token_ids, then update_block_hashes() (upstream mirrors into
160+
// _all_token_ids too; NumTokens recomputes prompt+output here).
144161
void AppendOutputToken(int32_t token_id);
145162
void AppendOutputToken(const std::vector<int32_t>& token_ids);
146163

164+
// update_block_hashes: compute block hashes for any newly-complete full
165+
// blocks and append them to block_hashes. No-op when _block_hasher is null.
166+
// Mirrors upstream Request.update_block_hashes.
167+
void update_block_hashes();
168+
147169
// is_finished / get_finished_reason: delegate to RequestStatus.
148170
bool IsFinished() const;
149171
std::optional<FinishReason> GetFinishedReason() const;

0 commit comments

Comments
 (0)