Skip to content

Commit e345029

Browse files
authored
[Fix][Runtime] Keep sliding-window page metadata separate from global attention (#20699)
`PlainPagedKVCacheAuxDataManager` reuses the same device arrays for global and per-layer sliding attention. Preparing the sliding page table therefore overwrites the global one before either layer uses it. In Gemma 4, a global layer then attends to only the sliding window and loses the earlier context. Give the plain manager separate storage for the sliding page indptr, page indices and length info, and select it at the per-layer sliding copy calls. Allocate it only when the cache has sliding layers. The cached manager already assigns each copy its own region.
1 parent 18ec45e commit e345029

3 files changed

Lines changed: 127 additions & 20 deletions

File tree

‎src/runtime/vm/attn_utils.h‎

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -497,9 +497,11 @@ class PagedKVCacheAuxDataManager {
497497
/*! \brief Copy the indptr array of append lengths after coalescing. (see GetChunkedBlockIds) */
498498
virtual ffi::Tensor CopyQOIndptrOnDepthAsync(HostMemoryVector* data, int depth) = 0;
499499
/*! \brief Copy the indptr array of page table. */
500-
virtual ffi::Tensor CopyPageIndptrOnDepthAsync(HostMemoryVector* data, int depth) = 0;
500+
virtual ffi::Tensor CopyPageIndptrOnDepthAsync(HostMemoryVector* data, int depth,
501+
bool layer_sliding_window = false) = 0;
501502
/*! \brief Copy the indices array of page table. */
502-
virtual ffi::Tensor CopyPageIndicesOnDepthAsync(HostMemoryVector* data, int depth) = 0;
503+
virtual ffi::Tensor CopyPageIndicesOnDepthAsync(HostMemoryVector* data, int depth,
504+
bool layer_sliding_window = false) = 0;
503505
/*! \brief Copy the array of KV slot number used in the last page of the seq. */
504506
virtual ffi::Tensor CopyLastPageLenOnDepthAsync(HostMemoryVector* data, int depth) = 0;
505507
/*!
@@ -514,7 +516,8 @@ class PagedKVCacheAuxDataManager {
514516
*/
515517
virtual ffi::Tensor CopyLengthInfoOnDepthAsync(HostMemoryVector* last_page_len,
516518
HostMemoryVector* sliding_window_offset,
517-
HostMemoryVector* sink_size, int depth) = 0;
519+
HostMemoryVector* sink_size, int depth,
520+
bool layer_sliding_window = false) = 0;
518521
/*! \brief Copy the k position offset of applying RoPE for each sequence. */
519522
virtual ffi::Tensor CopyKRoPEPosOffsetOnDepthAsync(HostMemoryVector* data, int depth) = 0;
520523
/*!
@@ -580,7 +583,8 @@ class PlainPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
580583
explicit PlainPagedKVCacheAuxDataManager(int64_t reserved_num_seqs, int64_t num_total_pages,
581584
int64_t prefill_chunk_size, DLDataType dtype_aux,
582585
Device device, Device preferred_host_device,
583-
TVMStreamHandle copy_stream)
586+
TVMStreamHandle copy_stream,
587+
bool layer_sliding_window = false)
584588
: PagedKVCacheAuxDataManager(dtype_aux, device, preferred_host_device, copy_stream),
585589
cur_append_length_indptr_device_(
586590
tvm::runtime::Empty({reserved_num_seqs + 1}, dtype_aux_, device)),
@@ -611,6 +615,14 @@ class PlainPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
611615
tvm::runtime::Empty({num_total_pages}, dtype_aux_, device));
612616
length_info_on_depths_device_.push_back(
613617
tvm::runtime::Empty({3, reserved_num_seqs}, dtype_aux_, device));
618+
if (layer_sliding_window) {
619+
page_indptr_sliding_on_depths_device_.push_back(
620+
tvm::runtime::Empty({reserved_num_seqs + 1}, dtype_aux_, device));
621+
page_indices_sliding_on_depths_device_.push_back(
622+
tvm::runtime::Empty({num_total_pages}, dtype_aux_, device));
623+
length_info_sliding_on_depths_device_.push_back(
624+
tvm::runtime::Empty({3, reserved_num_seqs}, dtype_aux_, device));
625+
}
614626
k_rope_pos_offset_on_depths_device_.push_back(
615627
tvm::runtime::Empty({reserved_num_seqs}, dtype_aux_, device));
616628
tree_attn_mask_device_.push_back(tvm::runtime::Empty(
@@ -628,15 +640,21 @@ class PlainPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
628640
CopyVecDataToArray(view, data->data());
629641
return view;
630642
}
631-
ffi::Tensor CopyPageIndptrOnDepthAsync(HostMemoryVector* data, int depth) final {
632-
ffi::Tensor view = tvm::runtime::CreateView(page_indptr_on_depths_device_[depth],
633-
{static_cast<int64_t>(data->size())}, dtype_aux_);
643+
ffi::Tensor CopyPageIndptrOnDepthAsync(HostMemoryVector* data, int depth,
644+
bool layer_sliding_window = false) final {
645+
const auto& storage = layer_sliding_window ? page_indptr_sliding_on_depths_device_
646+
: page_indptr_on_depths_device_;
647+
ffi::Tensor view =
648+
tvm::runtime::CreateView(storage[depth], {static_cast<int64_t>(data->size())}, dtype_aux_);
634649
CopyVecDataToArray(view, data->data());
635650
return view;
636651
}
637-
ffi::Tensor CopyPageIndicesOnDepthAsync(HostMemoryVector* data, int depth) final {
638-
ffi::Tensor view = tvm::runtime::CreateView(page_indices_on_depths_device_[depth],
639-
{static_cast<int64_t>(data->size())}, dtype_aux_);
652+
ffi::Tensor CopyPageIndicesOnDepthAsync(HostMemoryVector* data, int depth,
653+
bool layer_sliding_window = false) final {
654+
const auto& storage = layer_sliding_window ? page_indices_sliding_on_depths_device_
655+
: page_indices_on_depths_device_;
656+
ffi::Tensor view =
657+
tvm::runtime::CreateView(storage[depth], {static_cast<int64_t>(data->size())}, dtype_aux_);
640658
CopyVecDataToArray(view, data->data());
641659
return view;
642660
}
@@ -722,11 +740,13 @@ class PlainPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
722740

723741
ffi::Tensor CopyLengthInfoOnDepthAsync(HostMemoryVector* last_page_len,
724742
HostMemoryVector* sliding_window_offset,
725-
HostMemoryVector* sink_size, int depth) final {
743+
HostMemoryVector* sink_size, int depth,
744+
bool layer_sliding_window = false) final {
726745
int n_elem = last_page_len->size();
727746
TVM_FFI_ICHECK_GT(n_elem, 0);
728-
ffi::Tensor view =
729-
tvm::runtime::CreateView(length_info_on_depths_device_[depth], {3, n_elem}, dtype_aux_);
747+
const auto& storage = layer_sliding_window ? length_info_sliding_on_depths_device_
748+
: length_info_on_depths_device_;
749+
ffi::Tensor view = tvm::runtime::CreateView(storage[depth], {3, n_elem}, dtype_aux_);
730750
ffi::Shape copy_shape{n_elem};
731751
CopyVecDataToArray(view, last_page_len->data(), copy_shape);
732752
CopyVecDataToArray(view, sliding_window_offset->data(), copy_shape,
@@ -804,6 +824,10 @@ class PlainPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
804824
tvm::runtime::CopyFromTo(&copy_src, &copy_dst, copy_stream_);
805825
}
806826

827+
// Both page tables stay live until all global and sliding layers finish.
828+
std::vector<ffi::Tensor> page_indptr_sliding_on_depths_device_;
829+
std::vector<ffi::Tensor> page_indices_sliding_on_depths_device_;
830+
std::vector<ffi::Tensor> length_info_sliding_on_depths_device_;
807831
std::vector<ffi::Tensor> qo_indptr_on_depths_device_;
808832
std::vector<ffi::Tensor> page_indptr_on_depths_device_;
809833
std::vector<ffi::Tensor> page_indices_on_depths_device_;
@@ -865,10 +889,12 @@ class CachedPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
865889
ffi::Tensor CopyQOIndptrOnDepthAsync(HostMemoryVector* data, int depth) final {
866890
return CopyAttnAuxVecToCache(data);
867891
}
868-
ffi::Tensor CopyPageIndptrOnDepthAsync(HostMemoryVector* data, int depth) final {
892+
ffi::Tensor CopyPageIndptrOnDepthAsync(HostMemoryVector* data, int depth,
893+
bool layer_sliding_window = false) final {
869894
return CopyAttnAuxVecToCache(data);
870895
}
871-
ffi::Tensor CopyPageIndicesOnDepthAsync(HostMemoryVector* data, int depth) final {
896+
ffi::Tensor CopyPageIndicesOnDepthAsync(HostMemoryVector* data, int depth,
897+
bool layer_sliding_window = false) final {
872898
return CopyAttnAuxVecToCache(data);
873899
}
874900
ffi::Tensor CopyLastPageLenOnDepthAsync(HostMemoryVector* data, int depth) final {
@@ -914,7 +940,8 @@ class CachedPagedKVCacheAuxDataManager : public PagedKVCacheAuxDataManager {
914940
}
915941
ffi::Tensor CopyLengthInfoOnDepthAsync(HostMemoryVector* last_page_len,
916942
HostMemoryVector* sliding_window_offset,
917-
HostMemoryVector* sink_size, int depth) final {
943+
HostMemoryVector* sink_size, int depth,
944+
bool layer_sliding_window = false) final {
918945
int64_t n_elem = last_page_len->size();
919946
std::memcpy(merged_attn_aux_data_host_.data() + attn_aux_data_copy_offset_,
920947
last_page_len->data(), n_elem * elem_byte_size_);

‎src/runtime/vm/paged_kv_cache.cc‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ class PagedAttentionKVCacheObj : public AttentionKVCacheObj {
600600
} else {
601601
aux_data_manager_ = std::make_unique<PlainPagedKVCacheAuxDataManager>(
602602
reserved_num_seqs, num_total_pages, prefill_chunk_size, dtype_aux_, device,
603-
preferred_host_device, copy_stream_);
603+
preferred_host_device, copy_stream_, support_layer_sliding_window_);
604604
}
605605

606606
// Right now only the "normal" RoPE mode supports the RoPE extention factors.
@@ -3171,15 +3171,15 @@ class PagedAttentionKVCacheObj : public AttentionKVCacheObj {
31713171
qo_indptr_on_depths_host_[d].size());
31723172
page_indptr_sliding_window_on_depths_view_[d] =
31733173
aux_data_manager_->CopyPageIndptrOnDepthAsync(
3174-
&page_indptr_sliding_window_on_depths_host_[d], d);
3174+
&page_indptr_sliding_window_on_depths_host_[d], d, /*layer_sliding_window=*/true);
31753175
}
31763176
// 6. page_indices_sliding_window_on_depths
31773177
for (int d = 0; d < num_depths_; ++d) {
31783178
TVM_FFI_ICHECK_EQ(page_indices_sliding_window_on_depths_host_[d].size(),
31793179
page_indptr_sliding_window_on_depths_host_[d].back());
31803180
page_indices_sliding_window_on_depths_view_[d] =
31813181
aux_data_manager_->CopyPageIndicesOnDepthAsync(
3182-
&page_indices_sliding_window_on_depths_host_[d], d);
3182+
&page_indices_sliding_window_on_depths_host_[d], d, /*layer_sliding_window=*/true);
31833183
}
31843184
}
31853185
// 7. length_info_on_depths
@@ -3206,7 +3206,8 @@ class PagedAttentionKVCacheObj : public AttentionKVCacheObj {
32063206
layer_sliding_window_length_info_on_depths_view_[d] =
32073207
aux_data_manager_->CopyLengthInfoOnDepthAsync(&last_page_len_on_depths_host_[d],
32083208
&sliding_window_offset_on_depths_host_[d],
3209-
&sink_size_on_depths_host_[d], d);
3209+
&sink_size_on_depths_host_[d], d,
3210+
/*layer_sliding_window=*/true);
32103211
}
32113212
}
32123213
// 6. k_rope_pos_offset_on_depths
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <gtest/gtest.h>
21+
22+
#include "../../../src/runtime/vm/attn_utils.h"
23+
24+
namespace tvm {
25+
namespace runtime {
26+
namespace vm {
27+
namespace {
28+
29+
HostMemoryVector IntVector(std::initializer_list<int32_t> values) {
30+
HostMemoryVector result(values.size(), DLDataType{kDLInt, 32, 1}, Device{kDLCPU, 0});
31+
for (int32_t value : values) result.push_back(value);
32+
return result;
33+
}
34+
35+
std::vector<int32_t> ReadInts(const ffi::Tensor& tensor) {
36+
std::vector<int32_t> result(tensor.shape().Product());
37+
tvm::runtime::CopyToBytes(tensor, result.data(), result.size() * sizeof(int32_t));
38+
return result;
39+
}
40+
41+
TEST(PagedKVCacheAuxData, GlobalAndSlidingMetadataRemainIndependent) {
42+
// Exercise the manager used by WebGPU and Metal on CPU. The normal CPU
43+
// cache uses CachedPagedKVCacheAuxDataManager and would miss this aliasing.
44+
Device cpu{kDLCPU, 0};
45+
PlainPagedKVCacheAuxDataManager manager(2, 128, 512, DLDataType{kDLInt, 32, 1}, cpu, cpu, nullptr,
46+
true);
47+
auto full_indptr = IntVector({0, 4});
48+
auto sliding_indptr = IntVector({0, 2});
49+
auto full_indices = IntVector({10, 11, 12, 13});
50+
auto sliding_indices = IntVector({12, 13});
51+
auto last_page = IntVector({7});
52+
auto full_offset = IntVector({0});
53+
auto sliding_offset = IntVector({5});
54+
auto sink = IntVector({0});
55+
for (int iteration = 0; iteration < 2; ++iteration) {
56+
manager.ResetAttnAuxDataCopy();
57+
for (int depth = 0; depth < kPagedKVCacheMaxBlockDepth; ++depth) {
58+
auto full_ip = manager.CopyPageIndptrOnDepthAsync(&full_indptr, depth);
59+
auto full_ix = manager.CopyPageIndicesOnDepthAsync(&full_indices, depth);
60+
auto full_len = manager.CopyLengthInfoOnDepthAsync(&last_page, &full_offset, &sink, depth);
61+
auto sliding_ip = manager.CopyPageIndptrOnDepthAsync(&sliding_indptr, depth, true);
62+
auto sliding_ix = manager.CopyPageIndicesOnDepthAsync(&sliding_indices, depth, true);
63+
auto sliding_len =
64+
manager.CopyLengthInfoOnDepthAsync(&last_page, &sliding_offset, &sink, depth, true);
65+
manager.CommitAttnAuxDataCopy();
66+
EXPECT_EQ(ReadInts(full_ip), (std::vector<int32_t>{0, 4}));
67+
EXPECT_EQ(ReadInts(full_ix), (std::vector<int32_t>{10, 11, 12, 13}));
68+
EXPECT_EQ(ReadInts(full_len), (std::vector<int32_t>{7, 0, 0}));
69+
EXPECT_EQ(ReadInts(sliding_ip), (std::vector<int32_t>{0, 2}));
70+
EXPECT_EQ(ReadInts(sliding_ix), (std::vector<int32_t>{12, 13}));
71+
EXPECT_EQ(ReadInts(sliding_len), (std::vector<int32_t>{7, 5, 0}));
72+
}
73+
}
74+
}
75+
76+
} // namespace
77+
} // namespace vm
78+
} // namespace runtime
79+
} // namespace tvm

0 commit comments

Comments
 (0)