[spec] Misc defensive guards for EAGLE draft KV indexing (#27486)
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
#include <dlpack/dlpack.h>
|
#include <dlpack/dlpack.h>
|
||||||
#include <tvm/ffi/container/tensor.h>
|
#include <tvm/ffi/container/tensor.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -23,6 +24,7 @@ struct StoreKVCacheParams {
|
|||||||
int64_t stride_cache_bytes;
|
int64_t stride_cache_bytes;
|
||||||
int64_t stride_indices;
|
int64_t stride_indices;
|
||||||
uint32_t batch_size;
|
uint32_t batch_size;
|
||||||
|
int64_t size_limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr uint32_t kNumWarps = 4;
|
constexpr uint32_t kNumWarps = 4;
|
||||||
@@ -94,7 +96,8 @@ __global__ void store_kvcache(const __grid_constant__ StoreKVCacheParams params)
|
|||||||
const uint32_t split_id = warp_id % kSplit;
|
const uint32_t split_id = warp_id % kSplit;
|
||||||
const auto& [
|
const auto& [
|
||||||
k_input, v_input, k_cache, v_cache, indices, // ptr
|
k_input, v_input, k_cache, v_cache, indices, // ptr
|
||||||
stride_k, stride_v, stride_cache, stride_indices, batch_size // size
|
stride_k, stride_v, stride_cache, stride_indices, batch_size, // size
|
||||||
|
size_limit // bound
|
||||||
] = params;
|
] = params;
|
||||||
if (item_id >= batch_size) return;
|
if (item_id >= batch_size) return;
|
||||||
|
|
||||||
@@ -102,6 +105,9 @@ __global__ void store_kvcache(const __grid_constant__ StoreKVCacheParams params)
|
|||||||
PDLWaitPrimary<kUsePDL>();
|
PDLWaitPrimary<kUsePDL>();
|
||||||
|
|
||||||
const auto index = *index_ptr;
|
const auto index = *index_ptr;
|
||||||
|
// A stale/OOB slot id would cause an illegal memory access in the store below;
|
||||||
|
// fail fast at the culprit instead. always-on (kvcache JIT compiles without NDEBUG).
|
||||||
|
assert(index >= 0 && index < size_limit);
|
||||||
const auto k_src = pointer::offset(k_input, item_id * stride_k, split_id * kSplitSize);
|
const auto k_src = pointer::offset(k_input, item_id * stride_k, split_id * kSplitSize);
|
||||||
const auto v_src = pointer::offset(v_input, item_id * stride_v, split_id * kSplitSize);
|
const auto v_src = pointer::offset(v_input, item_id * stride_v, split_id * kSplitSize);
|
||||||
const auto k_dst = pointer::offset(k_cache, index * stride_cache, split_id * kSplitSize);
|
const auto k_dst = pointer::offset(k_cache, index * stride_cache, split_id * kSplitSize);
|
||||||
@@ -138,7 +144,8 @@ struct StoreKVCacheKernel {
|
|||||||
const tvm::ffi::TensorView k_cache,
|
const tvm::ffi::TensorView k_cache,
|
||||||
const tvm::ffi::TensorView v_cache,
|
const tvm::ffi::TensorView v_cache,
|
||||||
const tvm::ffi::TensorView indices,
|
const tvm::ffi::TensorView indices,
|
||||||
const int num_split) {
|
const int num_split,
|
||||||
|
const int64_t size_limit) {
|
||||||
using namespace host;
|
using namespace host;
|
||||||
auto B = SymbolicSize{"batch_size"};
|
auto B = SymbolicSize{"batch_size"};
|
||||||
auto D = SymbolicSize{"element_size"};
|
auto D = SymbolicSize{"element_size"};
|
||||||
@@ -188,6 +195,7 @@ struct StoreKVCacheKernel {
|
|||||||
.stride_cache_bytes = S.unwrap() * dtype_size,
|
.stride_cache_bytes = S.unwrap() * dtype_size,
|
||||||
.stride_indices = I.unwrap(),
|
.stride_indices = I.unwrap(),
|
||||||
.batch_size = static_cast<uint32_t>(B.unwrap()),
|
.batch_size = static_cast<uint32_t>(B.unwrap()),
|
||||||
|
.size_limit = size_limit,
|
||||||
};
|
};
|
||||||
// select kernel and update num_split if needed
|
// select kernel and update num_split if needed
|
||||||
const auto use_int32 = indice_dtype.is_type<int32_t>();
|
const auto use_int32 = indice_dtype.is_type<int32_t>();
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ def store_cache(
|
|||||||
*,
|
*,
|
||||||
row_bytes: int = 0,
|
row_bytes: int = 0,
|
||||||
num_split: int = 0, # can be tuned for performance
|
num_split: int = 0, # can be tuned for performance
|
||||||
|
size_limit: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Store key and value tensors into KV cache at specified indices.
|
"""Store key and value tensors into KV cache at specified indices.
|
||||||
|
|
||||||
@@ -66,6 +67,10 @@ def store_cache(
|
|||||||
k_cache (torch.Tensor): Key cache tensor of shape (num_pages, H * D).
|
k_cache (torch.Tensor): Key cache tensor of shape (num_pages, H * D).
|
||||||
v_cache (torch.Tensor): Value cache tensor of shape (num_pages, H * D).
|
v_cache (torch.Tensor): Value cache tensor of shape (num_pages, H * D).
|
||||||
indices (torch.Tensor): Indices tensor of shape (batch_size,).
|
indices (torch.Tensor): Indices tensor of shape (batch_size,).
|
||||||
|
size_limit (int): Valid slot bound (cache row count = real slots + the
|
||||||
|
reserved padding slot); an index outside [0, size_limit) fails fast
|
||||||
|
(device assert) instead of an illegal memory access. Defaults to the
|
||||||
|
cache row count when 0.
|
||||||
"""
|
"""
|
||||||
row_bytes = row_bytes or k.shape[-1] * k.element_size()
|
row_bytes = row_bytes or k.shape[-1] * k.element_size()
|
||||||
module = _jit_kvcache_module(row_bytes)
|
module = _jit_kvcache_module(row_bytes)
|
||||||
@@ -76,6 +81,8 @@ def store_cache(
|
|||||||
num_split = 2
|
num_split = 2
|
||||||
else:
|
else:
|
||||||
num_split = 1
|
num_split = 1
|
||||||
|
if size_limit <= 0:
|
||||||
|
size_limit = k_cache.shape[0]
|
||||||
module.store_cache(
|
module.store_cache(
|
||||||
k,
|
k,
|
||||||
v,
|
v,
|
||||||
@@ -83,4 +90,5 @@ def store_cache(
|
|||||||
v_cache,
|
v_cache,
|
||||||
indices,
|
indices,
|
||||||
num_split,
|
num_split,
|
||||||
|
size_limit,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ def _set_kv_buffer_impl(
|
|||||||
row_dim: int, # head_num * head_dim
|
row_dim: int, # head_num * head_dim
|
||||||
store_dtype: torch.dtype,
|
store_dtype: torch.dtype,
|
||||||
device_module: Any,
|
device_module: Any,
|
||||||
|
size_limit: int,
|
||||||
alt_stream: Optional[torch.cuda.Stream] = None,
|
alt_stream: Optional[torch.cuda.Stream] = None,
|
||||||
same_kv_dim: bool = True,
|
same_kv_dim: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -115,6 +116,7 @@ def _set_kv_buffer_impl(
|
|||||||
v_cache.view(-1, row_dim),
|
v_cache.view(-1, row_dim),
|
||||||
indices,
|
indices,
|
||||||
row_bytes=row_bytes,
|
row_bytes=row_bytes,
|
||||||
|
size_limit=size_limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
if _is_cpu and _cpu_has_amx_support:
|
if _is_cpu and _cpu_has_amx_support:
|
||||||
@@ -1254,6 +1256,9 @@ class MHATokenToKVPool(KVCache):
|
|||||||
row_dim=self.row_dim,
|
row_dim=self.row_dim,
|
||||||
store_dtype=self.store_dtype,
|
store_dtype=self.store_dtype,
|
||||||
device_module=self.device_module,
|
device_module=self.device_module,
|
||||||
|
# size + page_size = real slots + the reserved padding slot (padded /
|
||||||
|
# dummy tokens write there); valid index range is [0, size + page_size).
|
||||||
|
size_limit=self.size + self.page_size,
|
||||||
alt_stream=self.alt_stream,
|
alt_stream=self.alt_stream,
|
||||||
same_kv_dim=self.same_kv_dim,
|
same_kv_dim=self.same_kv_dim,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ class MultiLayerEagleWorker(TpModelWorker):
|
|||||||
self.topk = server_args.speculative_eagle_topk
|
self.topk = server_args.speculative_eagle_topk
|
||||||
self.speculative_num_steps = server_args.speculative_num_steps
|
self.speculative_num_steps = server_args.speculative_num_steps
|
||||||
self.speculative_num_draft_tokens = server_args.speculative_num_draft_tokens
|
self.speculative_num_draft_tokens = server_args.speculative_num_draft_tokens
|
||||||
|
assert self.speculative_num_draft_tokens == self.speculative_num_steps + 1, (
|
||||||
|
"multi-layer EAGLE requires speculative_num_draft_tokens == "
|
||||||
|
"speculative_num_steps + 1, "
|
||||||
|
f"got {self.speculative_num_draft_tokens} and {self.speculative_num_steps}"
|
||||||
|
)
|
||||||
self.gpu_id = gpu_id
|
self.gpu_id = gpu_id
|
||||||
self.device = server_args.device
|
self.device = server_args.device
|
||||||
self.target_worker = target_worker
|
self.target_worker = target_worker
|
||||||
|
|||||||
@@ -106,6 +106,11 @@ class MultiLayerEagleDraftWorker(BaseDraftWorker):
|
|||||||
self.topk = server_args.speculative_eagle_topk
|
self.topk = server_args.speculative_eagle_topk
|
||||||
self.speculative_num_steps = server_args.speculative_num_steps
|
self.speculative_num_steps = server_args.speculative_num_steps
|
||||||
self.speculative_num_draft_tokens = server_args.speculative_num_draft_tokens
|
self.speculative_num_draft_tokens = server_args.speculative_num_draft_tokens
|
||||||
|
assert self.speculative_num_draft_tokens == self.speculative_num_steps + 1, (
|
||||||
|
"multi-layer EAGLE requires speculative_num_draft_tokens == "
|
||||||
|
"speculative_num_steps + 1, "
|
||||||
|
f"got {self.speculative_num_draft_tokens} and {self.speculative_num_steps}"
|
||||||
|
)
|
||||||
self.speculative_algorithm = SpeculativeAlgorithm.from_string(
|
self.speculative_algorithm = SpeculativeAlgorithm.from_string(
|
||||||
server_args.speculative_algorithm
|
server_args.speculative_algorithm
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ def draft_kv_indices_buffer_width(
|
|||||||
num_seqs * topk branches each attend up to max_context_len KV slots; the topk
|
num_seqs * topk branches each attend up to max_context_len KV slots; the topk
|
||||||
factor is mandatory -- dropping it under-allocates and overflows the row (#27338, #27460).
|
factor is mandatory -- dropping it under-allocates and overflows the row (#27338, #27460).
|
||||||
"""
|
"""
|
||||||
|
assert (
|
||||||
|
num_seqs * topk * max_context_len < 2**31
|
||||||
|
), "kv_indices flat offset would overflow int32; reduce batch/topk/context"
|
||||||
return num_seqs * topk * max_context_len
|
return num_seqs * topk * max_context_len
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user