Skip to content

Commit f119954

Browse files
committed
issue/125 - cache interface
1 parent faa5d40 commit f119954

14 files changed

Lines changed: 579 additions & 393 deletions

csrc/cache/cache.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#pragma once
22

33
#include "cache_config.hpp"
4-
#include "kv_cache.hpp"
4+
#include "cache_interface.hpp"
5+
#include "dynamic_cache/dynamic_cache.hpp"

csrc/cache/cache_factory.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "cache_interface.hpp"
2+
#include "dynamic_cache/dynamic_cache.hpp"
3+
#include <spdlog/spdlog.h>
4+
5+
namespace infinilm::cache {
6+
7+
std::shared_ptr<CacheInterface> CacheInterface::create(const CacheConfig &config) {
8+
switch (config.type) {
9+
case CacheType::DYNAMIC:
10+
return std::make_shared<DynamicCache>(config);
11+
12+
case CacheType::PAGED:
13+
// Return PagedCache when implemented
14+
// return std::make_shared<PagedCache>(config);
15+
spdlog::warn("PagedCache not yet implemented, falling back to DynamicCache");
16+
return std::make_shared<DynamicCache>(config);
17+
18+
default:
19+
spdlog::error("Unknown cache type: {}", static_cast<int>(config.type));
20+
throw std::runtime_error("Unknown cache type");
21+
}
22+
}
23+
24+
} // namespace infinilm::cache

csrc/cache/cache_interface.hpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#pragma once
2+
3+
#include "cache_config.hpp"
4+
#include "infinicore/tensor.hpp"
5+
6+
#include <memory>
7+
8+
namespace infinilm::cache {
9+
10+
/**
11+
* @brief Abstract interface for KV cache implementations
12+
* This allows different cache types (Dynamic, Paged, etc.) to be used interchangeably
13+
*/
14+
class CacheInterface {
15+
public:
16+
virtual ~CacheInterface() = default;
17+
18+
/**
19+
* @brief Update cache with new key and value states
20+
* @param layer_idx Layer index for multi-layer models
21+
* @param k_new New key states [batch_size, n_kv_head, seq_len, head_dim]
22+
* @param v_new New value states [batch_size, n_kv_head, seq_len, head_dim]
23+
* @return Tuple of (k_total, v_total) with shape [batch_size, n_kv_head, total_seq_len, head_dim]
24+
*/
25+
virtual std::pair<infinicore::Tensor, infinicore::Tensor> update(
26+
size_t layer_idx,
27+
const infinicore::Tensor &k_new,
28+
const infinicore::Tensor &v_new)
29+
= 0;
30+
31+
/**
32+
* @brief Update cache (convenience method for single-layer or default layer)
33+
*/
34+
virtual std::pair<infinicore::Tensor, infinicore::Tensor> update(
35+
const infinicore::Tensor &k_new,
36+
const infinicore::Tensor &v_new) {
37+
return update(0, k_new, v_new);
38+
}
39+
40+
/**
41+
* @brief Reset cache for all layers to a specific position
42+
* @param pos Position to reset to (defaults to 0)
43+
*/
44+
virtual void reset(size_t pos = 0) = 0;
45+
46+
/**
47+
* @brief Update cache configuration
48+
* @param new_config New cache configuration
49+
*/
50+
virtual void update_config(const CacheConfig &new_config) = 0;
51+
52+
/**
53+
* @brief Get current cache configuration
54+
*/
55+
virtual const CacheConfig &get_config() const = 0;
56+
57+
/**
58+
* @brief Get the number of layers in this cache
59+
*/
60+
virtual size_t num_layers() const = 0;
61+
62+
/**
63+
* @brief Get cache position for a specific layer
64+
*/
65+
virtual size_t cache_position(size_t layer_idx) const = 0;
66+
67+
/**
68+
* @brief Check if cache is initialized
69+
*/
70+
virtual bool is_initialized() const = 0;
71+
72+
/**
73+
* @brief Factory method to create cache based on configuration
74+
*/
75+
static std::shared_ptr<CacheInterface> create(const CacheConfig &config);
76+
};
77+
78+
} // namespace infinilm::cache
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#include "dynamic_cache.hpp"
2+
3+
namespace infinilm::cache {
4+
5+
// KVCacheLayer Implementation
6+
7+
KVCacheLayer::KVCacheLayer()
8+
: max_capacity(0),
9+
initial_capacity(4096),
10+
initial_batch_size(1),
11+
growth_factor(2.0f),
12+
initialized(false) {}
13+
14+
void KVCacheLayer::ensure_capacity(size_t batch_size, size_t num_kv_heads, size_t head_dim,
15+
size_t seq_len, infinicore::DataType dtype,
16+
const infinicore::Device &device, const CacheConfig &cache_config) {
17+
size_t required_capacity = seq_len + std::accumulate(cache_positions.begin(), cache_positions.end(), size_t(0), [](size_t a, size_t b) { return std::max(a, b); });
18+
19+
// VALIDATION: Verify input parameters
20+
if (num_kv_heads == 0 || head_dim == 0 || seq_len == 0) {
21+
SPDLOG_ERROR("KVCacheLayer::ensure_capacity: Invalid parameters - num_kv_heads: {}, head_dim: {}, seq_len: {}",
22+
num_kv_heads, head_dim, seq_len);
23+
throw std::runtime_error("KV cache ensure_capacity: invalid parameters");
24+
}
25+
26+
// Store config parameters on first initialization
27+
if (!initialized) {
28+
initial_capacity = cache_config.initial_capacity;
29+
initial_batch_size = cache_config.initial_batch_size;
30+
growth_factor = cache_config.growth_factor;
31+
}
32+
33+
// Lazy initialization
34+
if (!initialized) {
35+
// Use max of required capacity and initial capacity from config
36+
max_capacity = std::max(required_capacity, initial_capacity);
37+
38+
// Use max of current batch size and initial batch size from config
39+
size_t alloc_batch_size = std::max(batch_size, initial_batch_size);
40+
41+
k_cache = infinicore::Tensor::empty({alloc_batch_size, num_kv_heads, max_capacity, head_dim},
42+
dtype, device);
43+
v_cache = infinicore::Tensor::empty({alloc_batch_size, num_kv_heads, max_capacity, head_dim},
44+
dtype, device);
45+
cache_positions = std::vector<size_t>(alloc_batch_size, 0);
46+
initialized = true;
47+
48+
spdlog::debug("Initialized KV cache with batch_size={}, capacity={} (config: initial_batch={}, initial_capacity={})",
49+
alloc_batch_size, max_capacity, initial_batch_size, initial_capacity);
50+
51+
// VALIDATION: Verify cache was created correctly
52+
if (k_cache->shape()[0] != alloc_batch_size || k_cache->shape()[1] != num_kv_heads || k_cache->shape()[2] != max_capacity || k_cache->shape()[3] != head_dim) {
53+
SPDLOG_ERROR("KVCacheLayer::ensure_capacity: Cache shape mismatch after initialization");
54+
throw std::runtime_error("KV cache initialization: shape mismatch");
55+
}
56+
}
57+
// Grow cache if needed using growth factor from config
58+
else if (required_capacity > max_capacity) {
59+
if (!cache_config.allow_expand) {
60+
SPDLOG_ERROR("KVCacheLayer::ensure_capacity: Cache expansion not allowed by config");
61+
throw std::runtime_error("KV cache expansion not allowed");
62+
}
63+
// Calculate new capacity using growth factor
64+
size_t new_capacity = static_cast<size_t>(
65+
std::max(static_cast<float>(max_capacity) * growth_factor,
66+
static_cast<float>(required_capacity + max_capacity)));
67+
68+
// Ensure we don't exceed max_position_embeddings if specified
69+
if (cache_config.max_kv_cache_length != 0) {
70+
new_capacity = std::min(new_capacity, cache_config.max_kv_cache_length);
71+
}
72+
73+
// Ensure we grow by at least some minimum amount
74+
size_t min_growth = 256;
75+
if (new_capacity - max_capacity < min_growth) {
76+
new_capacity = max_capacity + min_growth;
77+
}
78+
79+
size_t new_batch_size = std::max(batch_size, k_cache->shape()[0]);
80+
if (num_kv_heads != k_cache->shape()[1] || head_dim != k_cache->shape()[3]) {
81+
throw std::runtime_error("KVCache ensure_capacity: num_kv_heads or head_dim mismatch with existing cache.");
82+
}
83+
if (new_batch_size > cache_positions.size()) {
84+
cache_positions.resize(new_batch_size, 0);
85+
}
86+
87+
auto k_new = infinicore::Tensor::empty({new_batch_size, num_kv_heads, new_capacity, head_dim},
88+
dtype, device);
89+
auto v_new = infinicore::Tensor::empty({new_batch_size, num_kv_heads, new_capacity, head_dim},
90+
dtype, device);
91+
92+
spdlog::debug("Growing KV cache from capacity {} to {} (growth_factor={})",
93+
max_capacity, new_capacity, growth_factor);
94+
95+
// Copy existing cache data
96+
for (size_t b = 0; b < new_batch_size; ++b) {
97+
size_t cache_position = cache_positions[b];
98+
if (cache_position > 0) {
99+
auto k_slice = k_cache->narrow({{0, b, 1}, {2, 0, cache_position}});
100+
auto v_slice = v_cache->narrow({{0, b, 1}, {2, 0, cache_position}});
101+
k_new->narrow({{0, b, 1}, {2, 0, cache_position}})->copy_from(k_slice);
102+
v_new->narrow({{0, b, 1}, {2, 0, cache_position}})->copy_from(v_slice);
103+
}
104+
}
105+
106+
k_cache = k_new;
107+
v_cache = v_new;
108+
max_capacity = new_capacity;
109+
110+
// VALIDATION: Verify cache was grown correctly
111+
if (k_cache->shape()[2] != new_capacity) {
112+
SPDLOG_ERROR("KVCacheLayer::ensure_capacity: New cache capacity mismatch");
113+
throw std::runtime_error("KV cache growth: capacity mismatch");
114+
}
115+
}
116+
117+
// VALIDATION: Final check that capacity is sufficient
118+
if (required_capacity > max_capacity) {
119+
SPDLOG_ERROR("KVCacheLayer::ensure_capacity: Capacity still insufficient after growth");
120+
throw std::runtime_error("KV cache ensure_capacity: capacity insufficient");
121+
}
122+
}
123+
124+
std::pair<infinicore::Tensor, infinicore::Tensor> KVCacheLayer::update(
125+
const infinicore::Tensor &k_new,
126+
const infinicore::Tensor &v_new,
127+
const CacheConfig &cache_config) {
128+
if (k_new->ndim() != 4 || v_new->ndim() != 4) {
129+
throw std::runtime_error("KVCache update: k_new and v_new must be 4D tensors");
130+
}
131+
size_t batch_size = k_new->shape()[0];
132+
size_t num_kv_heads = k_new->shape()[1];
133+
size_t seq_len = k_new->shape()[2];
134+
size_t head_dim = k_new->shape()[3];
135+
136+
// Ensure capacity with cache config
137+
ensure_capacity(batch_size, num_kv_heads, head_dim, seq_len,
138+
k_new->dtype(), k_new->device(), cache_config);
139+
140+
// Copy new k/v into cache at current position
141+
bool all_equal = cache_positions.empty() || std::equal(cache_positions.begin() + 1, cache_positions.end(), cache_positions.begin());
142+
if (all_equal) {
143+
auto cache_position = cache_positions[0];
144+
145+
auto k_dst = k_cache->narrow({{2, cache_position, seq_len}});
146+
auto v_dst = v_cache->narrow({{2, cache_position, seq_len}});
147+
k_dst->copy_from(k_new);
148+
v_dst->copy_from(v_new);
149+
150+
// Update position
151+
cache_position += seq_len;
152+
for (size_t b = 0; b < batch_size; ++b) {
153+
cache_positions[b] = cache_position;
154+
}
155+
156+
// Return the total cache up to current position
157+
auto k_total = k_cache->narrow({{2, 0, cache_position}});
158+
auto v_total = v_cache->narrow({{2, 0, cache_position}});
159+
160+
return std::make_pair(k_total, v_total);
161+
} else {
162+
throw std::runtime_error("KVCache update: cache positions must be equal among a batch.");
163+
}
164+
}
165+
166+
// DynamicCache Implementation
167+
168+
DynamicCache::DynamicCache(const CacheConfig &cache_config)
169+
: cache_config_(cache_config), layers_(cache_config.num_layers) {
170+
if (cache_config.num_layers == 0) {
171+
throw std::runtime_error("DynamicCache: num_layers must be specified in CacheConfig");
172+
}
173+
}
174+
175+
DynamicCache::DynamicCache(size_t num_layers, size_t max_position_embeddings)
176+
: cache_config_(CacheConfig(CacheType::DYNAMIC, num_layers, max_position_embeddings)),
177+
layers_(num_layers) {
178+
if (num_layers == 0) {
179+
throw std::runtime_error("DynamicCache: num_layers must be greater than 0");
180+
}
181+
}
182+
183+
std::pair<infinicore::Tensor, infinicore::Tensor> DynamicCache::update(
184+
size_t layer_idx,
185+
const infinicore::Tensor &k_new,
186+
const infinicore::Tensor &v_new) {
187+
if (layer_idx >= layers_.size()) {
188+
SPDLOG_ERROR("DynamicCache::update: layer_idx {} out of range (num_layers: {})",
189+
layer_idx, layers_.size());
190+
throw std::runtime_error("DynamicCache: layer_idx out of range");
191+
}
192+
193+
// Update the cache for this layer with cache config
194+
return layers_[layer_idx].update(k_new, v_new, cache_config_);
195+
}
196+
197+
std::pair<infinicore::Tensor, infinicore::Tensor> DynamicCache::update(
198+
const infinicore::Tensor &k_new,
199+
const infinicore::Tensor &v_new) {
200+
return update(0, k_new, v_new);
201+
}
202+
203+
const CacheConfig &DynamicCache::get_config() const {
204+
return cache_config_;
205+
}
206+
207+
void DynamicCache::update_config(const CacheConfig &new_config) {
208+
// Check if we need to rebuild
209+
bool need_rebuild = false;
210+
211+
// Rebuild if number of layers changed
212+
if (new_config.num_layers != cache_config_.num_layers || new_config.initial_batch_size != cache_config_.initial_batch_size) {
213+
need_rebuild = true;
214+
layers_.resize(new_config.num_layers);
215+
}
216+
217+
// Rebuild if reset mode is RECREATE
218+
if (new_config.reset_mode == CacheResetMode::RECREATE) {
219+
need_rebuild = true;
220+
}
221+
222+
// Update configuration
223+
cache_config_ = new_config;
224+
225+
if (need_rebuild) {
226+
// Clear all layers to force reinitialization on next use
227+
for (auto &layer : layers_) {
228+
layer.initialized = false;
229+
layer.max_capacity = 0;
230+
// Tensors will be recreated when ensure_capacity is called
231+
}
232+
spdlog::info("DynamicCache configuration updated - cache will be rebuilt on next use");
233+
} else {
234+
spdlog::info("DynamicCache configuration updated: layers={}, initial_capacity={}, growth_factor={}",
235+
new_config.num_layers, new_config.initial_capacity, new_config.growth_factor);
236+
}
237+
}
238+
239+
size_t DynamicCache::num_layers() const {
240+
return layers_.size();
241+
}
242+
243+
size_t DynamicCache::cache_position(size_t layer_idx) const {
244+
if (layer_idx >= layers_.size()) {
245+
throw std::runtime_error("DynamicCache: layer_idx out of range");
246+
}
247+
if (layers_[layer_idx].cache_positions.empty()) {
248+
return 0;
249+
}
250+
return layers_[layer_idx].cache_positions[0];
251+
}
252+
253+
bool DynamicCache::is_initialized() const {
254+
return !layers_.empty() && layers_[0].initialized;
255+
}
256+
257+
size_t DynamicCache::max_kv_cache_length() const {
258+
return cache_config_.max_kv_cache_length;
259+
}
260+
261+
void DynamicCache::reset(size_t pos) {
262+
for (auto &layer : layers_) {
263+
std::fill(layer.cache_positions.begin(), layer.cache_positions.end(), pos);
264+
// Note: We don't reset initialized flag or clear the cache tensors
265+
// to avoid reallocation. The cache will be overwritten on next update.
266+
}
267+
}
268+
269+
KVCacheLayer &DynamicCache::layer(size_t layer_idx) {
270+
if (layer_idx >= layers_.size()) {
271+
throw std::runtime_error("DynamicCache: layer_idx out of range");
272+
}
273+
return layers_[layer_idx];
274+
}
275+
276+
const KVCacheLayer &DynamicCache::layer(size_t layer_idx) const {
277+
if (layer_idx >= layers_.size()) {
278+
throw std::runtime_error("DynamicCache: layer_idx out of range");
279+
}
280+
return layers_[layer_idx];
281+
}
282+
283+
} // namespace infinilm::cache

0 commit comments

Comments
 (0)