@@ -19,7 +19,11 @@ limitations under the License.
1919
2020#include < vector>
2121
22+ #include " framework/block/block.h"
23+ #include " framework/kv_cache/deepseek_v4_cache_policy.h"
24+ #include " framework/kv_cache/kv_cache_utils.h"
2225#include " kv_cache_shape.h"
26+ #include " platform/device.h"
2327
2428namespace xllm {
2529
@@ -42,6 +46,17 @@ std::vector<int64_t> dsv4_block_shape(int64_t block_count,
4246
4347} // namespace
4448
49+ // Host prefix-cache allocation registers page-aligned host memory with the NPU
50+ // via aclrtHostRegister, which requires a live device context. Set one up once.
51+ class HostKVCacheTest : public ::testing::Test {
52+ protected:
53+ void SetUp () override {
54+ Device device (/* device_index=*/ 0 );
55+ device.set_device ();
56+ device.init_device_context ();
57+ }
58+ };
59+
4560TEST (KVCacheTest, DeepSeekV4FourDimCachesUseDeviceLayout) {
4661 constexpr int64_t kSwaCount = 10 ;
4762 constexpr int64_t kC4Count = 32 ;
@@ -109,4 +124,111 @@ TEST(KVCacheTest, DeepSeekV4FourDimCachesUseDeviceLayout) {
109124 (std::vector<int64_t >{kSwaCount , kBlockSize , kHeadDim }));
110125}
111126
127+ TEST_F (HostKVCacheTest, HostKVCacheNormalLayoutAddsLayerDim) {
128+ constexpr int64_t kNumBlocks = 16 ;
129+ constexpr int64_t kBlockSize = 128 ;
130+ constexpr int64_t kHeadDim = 64 ;
131+ constexpr int64_t kNumHeads = 4 ;
132+ constexpr int64_t kLayerCount = 5 ;
133+ constexpr double kHostFactor = 2.0 ;
134+
135+ KVCacheCapacity capacity;
136+ capacity.n_blocks (kNumBlocks ).block_size (kBlockSize );
137+
138+ ModelArgs model_args;
139+ model_args.model_type (" qwen" );
140+ model_args.n_kv_heads (kNumHeads );
141+ model_args.head_dim (kHeadDim );
142+ KVCacheShape shape (capacity, model_args, /* world_size=*/ 1 );
143+ ASSERT_TRUE (shape.has_key_cache_shape ());
144+
145+ KVCacheCreateOptions options;
146+ options.device (torch::Device (torch::kCPU ))
147+ .dtype (torch::kFloat32 )
148+ .num_layers (kLayerCount )
149+ .model_type (" qwen" )
150+ .host_blocks_factor (kHostFactor );
151+
152+ KVCache host_cache (shape, options, BlockType::KV , kLayerCount );
153+
154+ const BlockTypeTensorMap tensors =
155+ host_cache.get_block_type_tensors (BlockType::KV );
156+ ASSERT_TRUE (tensors.count (KVCacheTensorRole::KEY ) > 0 );
157+
158+ const std::vector<int64_t > base_key_shape = shape.key_cache_shape ();
159+ const torch::Tensor& host_key = tensors.at (KVCacheTensorRole::KEY );
160+ EXPECT_TRUE (host_key.is_contiguous ());
161+ EXPECT_EQ (host_key.device ().type (), torch::kCPU );
162+ // host shape == [scaled_blocks, layer_count, ...per_block_dims]
163+ ASSERT_EQ (host_key.dim (), static_cast <int64_t >(base_key_shape.size ()) + 1 );
164+ EXPECT_EQ (host_key.size (0 ),
165+ scale_host_block_count (base_key_shape[0 ], kHostFactor ));
166+ EXPECT_EQ (host_key.size (1 ), kLayerCount );
167+ for (size_t i = 1 ; i < base_key_shape.size (); ++i) {
168+ EXPECT_EQ (host_key.size (static_cast <int64_t >(i) + 1 ), base_key_shape[i]);
169+ }
170+ }
171+
172+ TEST_F (HostKVCacheTest, HostKVCacheDeepSeekV4PerBlockType) {
173+ constexpr int64_t kSwaCount = 10 ;
174+ constexpr int64_t kC4Count = 32 ;
175+ constexpr int64_t kC128Count = 4 ;
176+ constexpr int64_t kBlockSize = 128 ;
177+ constexpr int64_t kHeadDim = 16 ;
178+ constexpr int64_t kIndexHeadDim = 8 ;
179+ constexpr double kHostFactor = 3.0 ;
180+
181+ KVCacheCapacity capacity;
182+ capacity.block_size (kBlockSize )
183+ .swa_count (kSwaCount )
184+ .c4_count (kC4Count )
185+ .c128_count (kC128Count );
186+
187+ ModelArgs model_args;
188+ model_args.model_type (" deepseek_v4" );
189+ KVCacheShape shape (capacity, model_args, /* world_size=*/ 1 );
190+
191+ KVCacheCreateOptions options;
192+ options.device (torch::Device (torch::kCPU ))
193+ .dtype (torch::kFloat32 )
194+ .num_layers (3 )
195+ .model_type (" deepseek_v4" )
196+ .block_size (kBlockSize )
197+ .head_dim (kHeadDim )
198+ .index_head_dim (kIndexHeadDim )
199+ .window_size (/* window_size=*/ 512 )
200+ .compress_ratios ({1 , 4 , 128 })
201+ .host_blocks_factor (kHostFactor );
202+
203+ // SWA host cache: 1 layer in this 3-layer config (compress_ratio == 1).
204+ KVCache swa_host (shape, options, BlockType::SWA , /* layer_count=*/ 1 );
205+ const BlockTypeTensorMap swa_tensors =
206+ swa_host.get_block_type_tensors (BlockType::SWA );
207+ ASSERT_TRUE (swa_tensors.count (KVCacheTensorRole::SWA ) > 0 );
208+ const torch::Tensor& swa = swa_tensors.at (KVCacheTensorRole::SWA );
209+ EXPECT_TRUE (swa.is_contiguous ());
210+ EXPECT_EQ (swa.size (0 ), scale_host_block_count (kSwaCount , kHostFactor ));
211+ EXPECT_EQ (swa.size (1 ), 1 );
212+
213+ // C4 host cache: key + index, index uses the DSV4 index dtype.
214+ KVCache c4_host (shape, options, BlockType::C4 , /* layer_count=*/ 1 );
215+ const BlockTypeTensorMap c4_tensors =
216+ c4_host.get_block_type_tensors (BlockType::C4 );
217+ ASSERT_TRUE (c4_tensors.count (KVCacheTensorRole::KEY ) > 0 );
218+ ASSERT_TRUE (c4_tensors.count (KVCacheTensorRole::INDEX ) > 0 );
219+ EXPECT_EQ (c4_tensors.at (KVCacheTensorRole::KEY ).size (0 ),
220+ scale_host_block_count (kC4Count , kHostFactor ));
221+ EXPECT_EQ (c4_tensors.at (KVCacheTensorRole::INDEX ).scalar_type (),
222+ get_dsv4_cache_policy (options.dtype ()).index_dtype );
223+
224+ // C128 host cache: key only (no index).
225+ KVCache c128_host (shape, options, BlockType::C128 , /* layer_count=*/ 1 );
226+ const BlockTypeTensorMap c128_tensors =
227+ c128_host.get_block_type_tensors (BlockType::C128 );
228+ ASSERT_TRUE (c128_tensors.count (KVCacheTensorRole::KEY ) > 0 );
229+ EXPECT_TRUE (c128_tensors.count (KVCacheTensorRole::INDEX ) == 0 );
230+ EXPECT_EQ (c128_tensors.at (KVCacheTensorRole::KEY ).size (0 ),
231+ scale_host_block_count (kC128Count , kHostFactor ));
232+ }
233+
112234} // namespace xllm
0 commit comments