perf: precompute ragged Q tile scheduling
This commit is contained in:
@@ -610,6 +610,8 @@ class CudaBackend(AttentionBackend):
|
|||||||
kv_cache.req_pool_indices,
|
kv_cache.req_pool_indices,
|
||||||
kv_cache.kv_indptr,
|
kv_cache.kv_indptr,
|
||||||
kv_cache.qo_indptr,
|
kv_cache.qo_indptr,
|
||||||
|
kv_cache.q_tile_to_batch,
|
||||||
|
kv_cache.q_tile_to_index,
|
||||||
attn_mask,
|
attn_mask,
|
||||||
is_causal=is_causal,
|
is_causal=is_causal,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ def attn_paged_prefill(
|
|||||||
req_pool_indices: torch.Tensor,
|
req_pool_indices: torch.Tensor,
|
||||||
kv_indptr: torch.Tensor,
|
kv_indptr: torch.Tensor,
|
||||||
qo_indptr: torch.Tensor,
|
qo_indptr: torch.Tensor,
|
||||||
|
q_tile_to_batch: torch.Tensor,
|
||||||
|
q_tile_to_index: torch.Tensor,
|
||||||
mask: Optional[torch.Tensor] = None,
|
mask: Optional[torch.Tensor] = None,
|
||||||
is_causal: bool = False,
|
is_causal: bool = False,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
@@ -167,6 +169,8 @@ def attn_paged_prefill(
|
|||||||
req_pool_indices: [batch] (int32)
|
req_pool_indices: [batch] (int32)
|
||||||
kv_indptr: [batch+1] (int32) — prefix sum of per-request kv_lens
|
kv_indptr: [batch+1] (int32) — prefix sum of per-request kv_lens
|
||||||
qo_indptr: [batch+1] (int32) — prefix sum of per-request q_lens
|
qo_indptr: [batch+1] (int32) — prefix sum of per-request q_lens
|
||||||
|
q_tile_to_batch: [num_q_tiles] (int32) — request index per Q tile
|
||||||
|
q_tile_to_index: [num_q_tiles] (int32) — local Q tile index per request
|
||||||
mask: 4D [batch, 1, q_len, kv_len] (bool, True=keep) or None
|
mask: 4D [batch, 1, q_len, kv_len] (bool, True=keep) or None
|
||||||
is_causal: apply causal mask
|
is_causal: apply causal mask
|
||||||
|
|
||||||
@@ -183,6 +187,8 @@ def attn_paged_prefill(
|
|||||||
req_pool_indices,
|
req_pool_indices,
|
||||||
kv_indptr,
|
kv_indptr,
|
||||||
qo_indptr,
|
qo_indptr,
|
||||||
|
q_tile_to_batch,
|
||||||
|
q_tile_to_index,
|
||||||
mask,
|
mask,
|
||||||
causal_offset=causal_offset,
|
causal_offset=causal_offset,
|
||||||
)
|
)
|
||||||
|
|||||||
Vendored
+2
@@ -99,6 +99,8 @@ class KVCache:
|
|||||||
max_len: int = 0
|
max_len: int = 0
|
||||||
kv_indptr: Optional[Tensor] = None
|
kv_indptr: Optional[Tensor] = None
|
||||||
qo_indptr: Optional[Tensor] = None
|
qo_indptr: Optional[Tensor] = None
|
||||||
|
q_tile_to_batch: Optional[Tensor] = None
|
||||||
|
q_tile_to_index: Optional[Tensor] = None
|
||||||
decode_o_part: Optional[Tensor] = None
|
decode_o_part: Optional[Tensor] = None
|
||||||
decode_ml_part: Optional[Tensor] = None
|
decode_ml_part: Optional[Tensor] = None
|
||||||
decode_out: Optional[Tensor] = None
|
decode_out: Optional[Tensor] = None
|
||||||
|
|||||||
Vendored
+19
-1
@@ -25,7 +25,7 @@ from astrai.inference.cache.strategy import (
|
|||||||
RadixCache,
|
RadixCache,
|
||||||
TaskCacheState,
|
TaskCacheState,
|
||||||
)
|
)
|
||||||
from astrai.inference.workspace import InferenceWorkspace
|
from astrai.inference.workspace import Q_TILE_ROWS, InferenceWorkspace
|
||||||
|
|
||||||
# Re-export everything so existing ``from astrai.inference.cache import ...``
|
# Re-export everything so existing ``from astrai.inference.cache import ...``
|
||||||
# continues to work unchanged after the file split.
|
# continues to work unchanged after the file split.
|
||||||
@@ -217,6 +217,21 @@ class PagePool:
|
|||||||
torch.tensor(q_lens, dtype=torch.int32, device=device).cumsum(0)
|
torch.tensor(q_lens, dtype=torch.int32, device=device).cumsum(0)
|
||||||
)
|
)
|
||||||
qo_indptr = workspace.qo_indptr[: b + 1]
|
qo_indptr = workspace.qo_indptr[: b + 1]
|
||||||
|
tile_batches = []
|
||||||
|
tile_indices = []
|
||||||
|
for batch, q_len in enumerate(q_lens):
|
||||||
|
n_tiles = (q_len + Q_TILE_ROWS - 1) // Q_TILE_ROWS
|
||||||
|
tile_batches.extend([batch] * n_tiles)
|
||||||
|
tile_indices.extend(range(n_tiles))
|
||||||
|
n_tiles = len(tile_batches)
|
||||||
|
workspace.q_tile_to_batch[:n_tiles].copy_(
|
||||||
|
torch.tensor(tile_batches, dtype=torch.int32, device=device)
|
||||||
|
)
|
||||||
|
workspace.q_tile_to_index[:n_tiles].copy_(
|
||||||
|
torch.tensor(tile_indices, dtype=torch.int32, device=device)
|
||||||
|
)
|
||||||
|
q_tile_to_batch = workspace.q_tile_to_batch[:n_tiles]
|
||||||
|
q_tile_to_index = workspace.q_tile_to_index[:n_tiles]
|
||||||
decode_o_part = decode_ml_part = decode_out = None
|
decode_o_part = decode_ml_part = decode_out = None
|
||||||
else:
|
else:
|
||||||
# ---- decode: out_cache_loc is a single column (last position) ----
|
# ---- decode: out_cache_loc is a single column (last position) ----
|
||||||
@@ -226,6 +241,7 @@ class PagePool:
|
|||||||
out_cache_loc = ocl_buf[:b].reshape(-1)
|
out_cache_loc = ocl_buf[:b].reshape(-1)
|
||||||
workspace.qo_indptr[: b + 1].copy_(inc_buf[: b + 1])
|
workspace.qo_indptr[: b + 1].copy_(inc_buf[: b + 1])
|
||||||
qo_indptr = workspace.qo_indptr[: b + 1]
|
qo_indptr = workspace.qo_indptr[: b + 1]
|
||||||
|
q_tile_to_batch = q_tile_to_index = None
|
||||||
decode_o_part = getattr(workspace, "decode_o_part", None)
|
decode_o_part = getattr(workspace, "decode_o_part", None)
|
||||||
decode_ml_part = getattr(workspace, "decode_ml_part", None)
|
decode_ml_part = getattr(workspace, "decode_ml_part", None)
|
||||||
decode_out = getattr(workspace, "decode_out", None)
|
decode_out = getattr(workspace, "decode_out", None)
|
||||||
@@ -240,6 +256,8 @@ class PagePool:
|
|||||||
max_len=max(seq_lens),
|
max_len=max(seq_lens),
|
||||||
kv_indptr=kv_indptr,
|
kv_indptr=kv_indptr,
|
||||||
qo_indptr=qo_indptr,
|
qo_indptr=qo_indptr,
|
||||||
|
q_tile_to_batch=q_tile_to_batch,
|
||||||
|
q_tile_to_index=q_tile_to_index,
|
||||||
decode_o_part=decode_o_part,
|
decode_o_part=decode_o_part,
|
||||||
decode_ml_part=decode_ml_part,
|
decode_ml_part=decode_ml_part,
|
||||||
decode_out=decode_out,
|
decode_out=decode_out,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import torch
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
_MAX_SPLITS = 32
|
_MAX_SPLITS = 32
|
||||||
|
Q_TILE_ROWS = 64
|
||||||
|
|
||||||
|
|
||||||
class InferenceWorkspace:
|
class InferenceWorkspace:
|
||||||
@@ -83,6 +84,13 @@ class InferenceWorkspace:
|
|||||||
self.qo_indptr = torch.empty(
|
self.qo_indptr = torch.empty(
|
||||||
(max_batch_size + 1,), dtype=torch.int32, device=device
|
(max_batch_size + 1,), dtype=torch.int32, device=device
|
||||||
)
|
)
|
||||||
|
max_q_tiles = max_batch_size * ((max_seq_len + Q_TILE_ROWS - 1) // Q_TILE_ROWS)
|
||||||
|
self.q_tile_to_batch = torch.empty(
|
||||||
|
(max_q_tiles,), dtype=torch.int32, device=device
|
||||||
|
)
|
||||||
|
self.q_tile_to_index = torch.empty(
|
||||||
|
(max_q_tiles,), dtype=torch.int32, device=device
|
||||||
|
)
|
||||||
self.inc = torch.arange(max_batch_size + 1, dtype=torch.int32, device=device)
|
self.inc = torch.arange(max_batch_size + 1, dtype=torch.int32, device=device)
|
||||||
self.out_cache_loc = torch.empty(
|
self.out_cache_loc = torch.empty(
|
||||||
(max_batch_size, 1), dtype=torch.int32, device=device
|
(max_batch_size, 1), dtype=torch.int32, device=device
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ enum TensorLayout : int {
|
|||||||
// - Contiguous K/V: dense [batch, kv_head, kv_len, head_dim] tensors (k/v).
|
// - Contiguous K/V: dense [batch, kv_head, kv_len, head_dim] tensors (k/v).
|
||||||
// - Paged (SGLang-style): flat pool [size, kv_head, head_dim] + req_to_token.
|
// - Paged (SGLang-style): flat pool [size, kv_head, head_dim] + req_to_token.
|
||||||
// Each kernel selects the addressing via a KVSource policy (see
|
// Each kernel selects the addressing via a KVSource policy (see
|
||||||
// attn_kv_source.cuh); a given call only touches the fields of one mode, so
|
// attn_layout_policies.cuh); a given call only touches the fields of one mode, so
|
||||||
// this is a POD shared by both paths rather than two parallel structs that
|
// this is a POD shared by both paths rather than two parallel structs that
|
||||||
// drift out of sync.
|
// drift out of sync.
|
||||||
template<typename T, typename AT = float>
|
template<typename T, typename AT = float>
|
||||||
@@ -59,6 +59,9 @@ struct AttentionParams {
|
|||||||
const int* __restrict__ req_pool_indices; // [batch]
|
const int* __restrict__ req_pool_indices; // [batch]
|
||||||
const int* __restrict__ kv_indptr; // [batch + 1]
|
const int* __restrict__ kv_indptr; // [batch + 1]
|
||||||
const int* __restrict__ qo_indptr; // [batch + 1] or nullptr for decode
|
const int* __restrict__ qo_indptr; // [batch + 1] or nullptr for decode
|
||||||
|
const int* __restrict__ q_tile_to_batch; // [num_q_tiles], prefill only
|
||||||
|
const int* __restrict__ q_tile_to_index; // [num_q_tiles], prefill only
|
||||||
|
int num_q_tiles;
|
||||||
int max_context_len; // req_to_token stride (dim 1)
|
int max_context_len; // req_to_token stride (dim 1)
|
||||||
|
|
||||||
// Decode split-KV workspace
|
// Decode split-KV workspace
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <cuda_bf16.h>
|
#include <cuda_bf16.h>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include "attn_common.h"
|
#include "attn_common.h"
|
||||||
#include "attn_kv_source.cuh"
|
#include "attn_layout_policies.cuh"
|
||||||
#include "attn_warp_utils.cuh"
|
#include "attn_warp_utils.cuh"
|
||||||
constexpr int DC_CHUNK = 64;
|
constexpr int DC_CHUNK = 64;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cuda_bf16.h>
|
#include <cuda_bf16.h>
|
||||||
#include "attn_common.h"
|
#include "attn_common.h"
|
||||||
#include "attn_kv_source.cuh"
|
#include "attn_layout_policies.cuh"
|
||||||
#include "attn_mma_utils.cuh"
|
#include "attn_mma_utils.cuh"
|
||||||
#include "attn_warp_utils.cuh"
|
#include "attn_warp_utils.cuh"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// No torch dependency; pure CUDA.
|
// No torch dependency; pure CUDA.
|
||||||
//
|
//
|
||||||
// The paged and contiguous kernels are unified by the KVSource policy
|
// The paged and contiguous kernels are unified by the KVSource policy
|
||||||
// (ContigKV / PagedKV from attn_kv_source.cuh), so each launcher struct
|
// (ContigKV / PagedKV from attn_layout_policies.cuh), so each launcher struct
|
||||||
// below is templated on KV and the paged dispatch is just the same launcher
|
// below is templated on KV and the paged dispatch is just the same launcher
|
||||||
// instantiated with PagedKV. Only the grid/split math differs, and that is
|
// instantiated with PagedKV. Only the grid/split math differs, and that is
|
||||||
// covered by KV::host_q_len / KV::host_kv_len.
|
// covered by KV::host_q_len / KV::host_kv_len.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
#include <cuda_runtime.h>
|
#include <cuda_runtime.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "attn_warp_utils.cuh"
|
#include "attn_warp_utils.cuh"
|
||||||
#include "attn_kv_source.cuh"
|
#include "attn_layout_policies.cuh"
|
||||||
#include "attn_prefill_split_q.cuh"
|
#include "attn_prefill_split_q.cuh"
|
||||||
#include "attn_decode_split_kv.cuh"
|
#include "attn_decode_split_kv.cuh"
|
||||||
#ifndef ASTRAI_NO_MMA
|
#ifndef ASTRAI_NO_MMA
|
||||||
@@ -80,31 +80,32 @@ template <> struct PrefillConfigMap<128, true> : PrefillKernelConfig<32> {};
|
|||||||
template <> struct PrefillConfigMap<256, false> : PrefillKernelConfig<16> {};
|
template <> struct PrefillConfigMap<256, false> : PrefillKernelConfig<16> {};
|
||||||
template <> struct PrefillConfigMap<256, true> : PrefillKernelConfig<16> {};
|
template <> struct PrefillConfigMap<256, true> : PrefillKernelConfig<16> {};
|
||||||
|
|
||||||
template <typename KV>
|
template <typename QSchedule, typename KV>
|
||||||
struct PrefillLauncherMMA {
|
struct PrefillLauncherMMA {
|
||||||
template <int HEAD_DIM, bool IsCausal, bool HasMask>
|
template <int HEAD_DIM, bool IsCausal, bool HasMask>
|
||||||
static void launch(AttentionParams<bf16>& p, cudaStream_t stream) {
|
static void launch(AttentionParams<bf16>& p, cudaStream_t stream) {
|
||||||
using Config = PrefillConfigMap<HEAD_DIM, IsCausal>;
|
using Config = PrefillConfigMap<HEAD_DIM, IsCausal>;
|
||||||
using Traits = KernelTraits<HEAD_DIM, Config::BC, Config::WARPS, Config::STAGES>;
|
using Traits = KernelTraits<HEAD_DIM, Config::BC, Config::WARPS, Config::STAGES>;
|
||||||
constexpr int ROWS = Traits::BR * Config::WARPS;
|
constexpr int ROWS = Traits::BR * Config::WARPS;
|
||||||
dim3 grid(KV::host_q_blocks(p, ROWS), p.q_head,
|
dim3 grid(QSchedule::host_q_blocks(p, ROWS), p.q_head,
|
||||||
KV::kPaged ? 1 : p.batch);
|
QSchedule::host_grid_batch(p));
|
||||||
dim3 block(Traits::NUM_THREADS);
|
dim3 block(Traits::NUM_THREADS);
|
||||||
attn_prefill_split_q_mma_kernel<Traits, KV, IsCausal, HasMask>
|
attn_prefill_split_q_mma_kernel<Traits, QSchedule, KV, IsCausal, HasMask>
|
||||||
<<<grid, block, 0, stream>>>(p);
|
<<<grid, block, 0, stream>>>(p);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename KV>
|
template <typename QSchedule, typename KV>
|
||||||
struct PrefillLauncherScalar {
|
struct PrefillLauncherScalar {
|
||||||
template <int HEAD_DIM, bool IsCausal, bool HasMask>
|
template <int HEAD_DIM, bool IsCausal, bool HasMask>
|
||||||
static void launch(AttentionParams<bf16>& p, cudaStream_t stream) {
|
static void launch(AttentionParams<bf16>& p, cudaStream_t stream) {
|
||||||
constexpr int G = (HEAD_DIM == 32) ? 4 : 8, ROWS = 32, P_BC = 32;
|
constexpr int G = (HEAD_DIM == 32) ? 4 : 8, ROWS = 64, P_BC = 32;
|
||||||
dim3 grid(KV::host_q_blocks(p, ROWS), p.q_head,
|
dim3 grid(QSchedule::host_q_blocks(p, ROWS), p.q_head,
|
||||||
KV::kPaged ? 1 : p.batch);
|
QSchedule::host_grid_batch(p));
|
||||||
dim3 block(G, ROWS);
|
dim3 block(G, ROWS);
|
||||||
attn_prefill_split_q_kernel_t<HEAD_DIM, KV, G, ROWS, P_BC, IsCausal, HasMask>
|
attn_prefill_split_q_kernel_t<HEAD_DIM, QSchedule, KV, G, ROWS, P_BC,
|
||||||
|
IsCausal, HasMask>
|
||||||
<<<grid, block, 0, stream>>>(p);
|
<<<grid, block, 0, stream>>>(p);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,12 +116,14 @@ static inline void dispatch_prefill(AttentionParams<bf16>& p, cudaStream_t strea
|
|||||||
bool has_mask = (p.use_mask && p.mask);
|
bool has_mask = (p.use_mask && p.mask);
|
||||||
|
|
||||||
#ifndef ASTRAI_NO_MMA
|
#ifndef ASTRAI_NO_MMA
|
||||||
|
using Launcher = PrefillLauncherMMA<DenseQSchedule, ContigKV>;
|
||||||
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
||||||
PrefillLauncherMMA<ContigKV>::template launch,
|
Launcher::template launch,
|
||||||
HEAD_DIM, p, stream);
|
HEAD_DIM, p, stream);
|
||||||
#else
|
#else
|
||||||
|
using Launcher = PrefillLauncherScalar<DenseQSchedule, ContigKV>;
|
||||||
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
||||||
PrefillLauncherScalar<ContigKV>::template launch,
|
Launcher::template launch,
|
||||||
HEAD_DIM, p, stream);
|
HEAD_DIM, p, stream);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -131,12 +134,14 @@ static inline void dispatch_paged_prefill(AttentionParams<bf16>& p, cudaStream_t
|
|||||||
bool has_mask = (p.use_mask && p.mask);
|
bool has_mask = (p.use_mask && p.mask);
|
||||||
|
|
||||||
#ifndef ASTRAI_NO_MMA
|
#ifndef ASTRAI_NO_MMA
|
||||||
|
using Launcher = PrefillLauncherMMA<PackedQSchedule, PagedKV>;
|
||||||
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
||||||
PrefillLauncherMMA<PagedKV>::template launch,
|
Launcher::template launch,
|
||||||
HEAD_DIM, p, stream);
|
HEAD_DIM, p, stream);
|
||||||
#else
|
#else
|
||||||
|
using Launcher = PrefillLauncherScalar<PackedQSchedule, PagedKV>;
|
||||||
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
DISPATCH_CAUSAL_MASK(is_causal, has_mask,
|
||||||
PrefillLauncherScalar<PagedKV>::template launch,
|
Launcher::template launch,
|
||||||
HEAD_DIM, p, stream);
|
HEAD_DIM, p, stream);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ inline void attn_pack_paged_prefill_params(
|
|||||||
torch::Tensor req_pool_indices,
|
torch::Tensor req_pool_indices,
|
||||||
torch::Tensor kv_indptr,
|
torch::Tensor kv_indptr,
|
||||||
torch::Tensor qo_indptr,
|
torch::Tensor qo_indptr,
|
||||||
|
torch::Tensor q_tile_to_batch,
|
||||||
|
torch::Tensor q_tile_to_index,
|
||||||
c10::optional<torch::Tensor> mask,
|
c10::optional<torch::Tensor> mask,
|
||||||
int64_t causal_offset,
|
int64_t causal_offset,
|
||||||
double scale,
|
double scale,
|
||||||
@@ -237,6 +239,7 @@ inline void attn_pack_paged_prefill_params(
|
|||||||
TORCH_CHECK(q.is_cuda() && k_cache.is_cuda() && v_cache.is_cuda());
|
TORCH_CHECK(q.is_cuda() && k_cache.is_cuda() && v_cache.is_cuda());
|
||||||
TORCH_CHECK(req_to_token.is_cuda() && req_pool_indices.is_cuda());
|
TORCH_CHECK(req_to_token.is_cuda() && req_pool_indices.is_cuda());
|
||||||
TORCH_CHECK(kv_indptr.is_cuda() && qo_indptr.is_cuda());
|
TORCH_CHECK(kv_indptr.is_cuda() && qo_indptr.is_cuda());
|
||||||
|
TORCH_CHECK(q_tile_to_batch.is_cuda() && q_tile_to_index.is_cuda());
|
||||||
TORCH_CHECK(q.dtype() == torch::kBFloat16, "q must be bf16");
|
TORCH_CHECK(q.dtype() == torch::kBFloat16, "q must be bf16");
|
||||||
TORCH_CHECK(k_cache.dtype() == torch::kBFloat16, "k_cache must be bf16");
|
TORCH_CHECK(k_cache.dtype() == torch::kBFloat16, "k_cache must be bf16");
|
||||||
TORCH_CHECK(v_cache.dtype() == torch::kBFloat16, "v_cache must be bf16");
|
TORCH_CHECK(v_cache.dtype() == torch::kBFloat16, "v_cache must be bf16");
|
||||||
@@ -245,6 +248,10 @@ inline void attn_pack_paged_prefill_params(
|
|||||||
"req_pool_indices must be int32");
|
"req_pool_indices must be int32");
|
||||||
TORCH_CHECK(kv_indptr.dtype() == torch::kInt32, "kv_indptr must be int32");
|
TORCH_CHECK(kv_indptr.dtype() == torch::kInt32, "kv_indptr must be int32");
|
||||||
TORCH_CHECK(qo_indptr.dtype() == torch::kInt32, "qo_indptr must be int32");
|
TORCH_CHECK(qo_indptr.dtype() == torch::kInt32, "qo_indptr must be int32");
|
||||||
|
TORCH_CHECK(q_tile_to_batch.dtype() == torch::kInt32,
|
||||||
|
"q_tile_to_batch must be int32");
|
||||||
|
TORCH_CHECK(q_tile_to_index.dtype() == torch::kInt32,
|
||||||
|
"q_tile_to_index must be int32");
|
||||||
TORCH_CHECK(k_cache.sizes() == v_cache.sizes(), "k_cache and v_cache must match");
|
TORCH_CHECK(k_cache.sizes() == v_cache.sizes(), "k_cache and v_cache must match");
|
||||||
TORCH_CHECK(k_cache.dim() == 3, "k_cache must be 3D [size, kv_head, head_dim]");
|
TORCH_CHECK(k_cache.dim() == 3, "k_cache must be 3D [size, kv_head, head_dim]");
|
||||||
TORCH_CHECK(q.dim() == 3, "q must be 3D [total_q, q_head, head_dim]");
|
TORCH_CHECK(q.dim() == 3, "q must be 3D [total_q, q_head, head_dim]");
|
||||||
@@ -261,6 +268,10 @@ inline void attn_pack_paged_prefill_params(
|
|||||||
TORCH_CHECK(p.q_head % p.kv_head == 0, "q_head must be divisible by kv_head");
|
TORCH_CHECK(p.q_head % p.kv_head == 0, "q_head must be divisible by kv_head");
|
||||||
TORCH_CHECK(kv_indptr.size(0) == p.batch + 1, "kv_indptr must be [batch+1]");
|
TORCH_CHECK(kv_indptr.size(0) == p.batch + 1, "kv_indptr must be [batch+1]");
|
||||||
TORCH_CHECK(qo_indptr.size(0) == p.batch + 1, "qo_indptr must be [batch+1]");
|
TORCH_CHECK(qo_indptr.size(0) == p.batch + 1, "qo_indptr must be [batch+1]");
|
||||||
|
TORCH_CHECK(q_tile_to_batch.dim() == 1 && q_tile_to_index.dim() == 1,
|
||||||
|
"Q tile mappings must be 1D");
|
||||||
|
TORCH_CHECK(q_tile_to_batch.size(0) == q_tile_to_index.size(0),
|
||||||
|
"Q tile mappings must have equal length");
|
||||||
|
|
||||||
p.q_l_stride = (int)q.stride(0);
|
p.q_l_stride = (int)q.stride(0);
|
||||||
p.q_h_stride = (int)q.stride(1);
|
p.q_h_stride = (int)q.stride(1);
|
||||||
@@ -273,6 +284,9 @@ inline void attn_pack_paged_prefill_params(
|
|||||||
p.req_pool_indices = req_pool_indices.data_ptr<int>();
|
p.req_pool_indices = req_pool_indices.data_ptr<int>();
|
||||||
p.kv_indptr = kv_indptr.data_ptr<int>();
|
p.kv_indptr = kv_indptr.data_ptr<int>();
|
||||||
p.qo_indptr = qo_indptr.data_ptr<int>();
|
p.qo_indptr = qo_indptr.data_ptr<int>();
|
||||||
|
p.q_tile_to_batch = q_tile_to_batch.data_ptr<int>();
|
||||||
|
p.q_tile_to_index = q_tile_to_index.data_ptr<int>();
|
||||||
|
p.num_q_tiles = (int)q_tile_to_batch.size(0);
|
||||||
p.max_context_len = (int)req_to_token.size(1);
|
p.max_context_len = (int)req_to_token.size(1);
|
||||||
|
|
||||||
p.causal_offset = (int)causal_offset;
|
p.causal_offset = (int)causal_offset;
|
||||||
|
|||||||
@@ -3,13 +3,10 @@
|
|||||||
#include "attn_common.h"
|
#include "attn_common.h"
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// KVSource policies — the single dimension along which the paged and
|
// Attention layout policies keep Q scheduling independent from K/V storage.
|
||||||
// non-paged attention kernels differ. Each kernel is templated on one of
|
// DenseQSchedule / PackedQSchedule map blocks to Q tiles; ContigKV / PagedKV
|
||||||
// these (ContigKV / PagedKV) and stays fully generic: the policy owns every
|
// resolve logical K/V positions to physical addresses. This lets the shared
|
||||||
// place where "where does K/V live" and "what is this request's seq_len"
|
// kernels compose Q layout and K/V storage without coupling the two concerns.
|
||||||
// are answered. All methods are __host__ __device__ so the same policy
|
|
||||||
// serves both the device kernels (addressing, seq_len) and the host-side
|
|
||||||
// launchers (grid / split computation).
|
|
||||||
//
|
//
|
||||||
// ContigKV: K/V are dense [batch, kv_head, kv_len, head_dim] tensors.
|
// ContigKV: K/V are dense [batch, kv_head, kv_len, head_dim] tensors.
|
||||||
// Params fields used: k, v, kv_stride_*, kv_len, q_len,
|
// Params fields used: k, v, kv_stride_*, kv_len, q_len,
|
||||||
@@ -25,11 +22,76 @@
|
|||||||
// (e.g. the req_pool_indices global read) element-by-element.
|
// (e.g. the req_pool_indices global read) element-by-element.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Every policy method is static + callable from both host and device code.
|
#define HOST_FORCEINLINE static __host__ __forceinline__
|
||||||
|
#define DEVICE_FORCEINLINE static __device__ __forceinline__
|
||||||
#define HOST_DEV_FORCEINLINE static __host__ __device__ __forceinline__
|
#define HOST_DEV_FORCEINLINE static __host__ __device__ __forceinline__
|
||||||
|
|
||||||
using bf16 = __nv_bfloat16;
|
using bf16 = __nv_bfloat16;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Q scheduling policies
|
||||||
|
//
|
||||||
|
// Map CUDA blocks to request-local Q tiles independently of K/V storage.
|
||||||
|
// Dense tensors encode the request in blockIdx.z; packed ragged tensors use
|
||||||
|
// a compact precomputed work map indexed by blockIdx.x.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
struct DenseQSchedule {
|
||||||
|
HOST_FORCEINLINE int host_q_blocks(
|
||||||
|
const AttentionParams<bf16>& p, int rows) {
|
||||||
|
return (p.q_len + rows - 1) / rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
HOST_FORCEINLINE int host_grid_batch(
|
||||||
|
const AttentionParams<bf16>& p) {
|
||||||
|
return p.batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_FORCEINLINE void map_block(
|
||||||
|
const AttentionParams<bf16>&, int& batch, int& q_tile) {
|
||||||
|
batch = blockIdx.z;
|
||||||
|
q_tile = blockIdx.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_FORCEINLINE int q_len(
|
||||||
|
const AttentionParams<bf16>& p, int) {
|
||||||
|
return p.q_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_FORCEINLINE int q_base(
|
||||||
|
const AttentionParams<bf16>& p, int batch, int q_head) {
|
||||||
|
return batch * p.q_b_stride + q_head * p.q_h_stride;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackedQSchedule {
|
||||||
|
HOST_FORCEINLINE int host_q_blocks(
|
||||||
|
const AttentionParams<bf16>& p, int) {
|
||||||
|
return p.num_q_tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
HOST_FORCEINLINE int host_grid_batch(
|
||||||
|
const AttentionParams<bf16>&) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_FORCEINLINE void map_block(
|
||||||
|
const AttentionParams<bf16>& p, int& batch, int& q_tile) {
|
||||||
|
batch = p.q_tile_to_batch[blockIdx.x];
|
||||||
|
q_tile = p.q_tile_to_index[blockIdx.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_FORCEINLINE int q_len(
|
||||||
|
const AttentionParams<bf16>& p, int batch) {
|
||||||
|
return p.qo_indptr[batch + 1] - p.qo_indptr[batch];
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_FORCEINLINE int q_base(
|
||||||
|
const AttentionParams<bf16>& p, int batch, int q_head) {
|
||||||
|
return p.qo_indptr[batch] * p.q_l_stride + q_head * p.q_h_stride;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Hoisted per-(batch, kv_head) addressing context.
|
// Hoisted per-(batch, kv_head) addressing context.
|
||||||
struct KVContext {
|
struct KVContext {
|
||||||
int kv_base; // contig: batch*kv_b_stride + kv_head*kv_h_stride
|
int kv_base; // contig: batch*kv_b_stride + kv_head*kv_h_stride
|
||||||
@@ -56,59 +118,40 @@ struct KVAddr {
|
|||||||
struct ContigKV {
|
struct ContigKV {
|
||||||
static constexpr bool kPaged = false;
|
static constexpr bool kPaged = false;
|
||||||
|
|
||||||
// host-side length hooks (grid + split computation in the launchers)
|
HOST_FORCEINLINE int host_kv_len(const AttentionParams<bf16>& p) {
|
||||||
HOST_DEV_FORCEINLINE int host_q_blocks(const AttentionParams<bf16>& p, int rows) {
|
|
||||||
return (p.q_len + rows - 1) / rows;
|
|
||||||
}
|
|
||||||
template <int ROWS>
|
|
||||||
HOST_DEV_FORCEINLINE bool map_q_tile(const AttentionParams<bf16>&,
|
|
||||||
int flat_tile, int grid_batch,
|
|
||||||
int& batch, int& q_tile) {
|
|
||||||
batch = grid_batch;
|
|
||||||
q_tile = flat_tile;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
HOST_DEV_FORCEINLINE int host_kv_len(const AttentionParams<bf16>& p) {
|
|
||||||
return p.kv_len;
|
return p.kv_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefill: element offset of the request's Q rows (kernel adds qrow*q_l_stride)
|
|
||||||
HOST_DEV_FORCEINLINE int q_base(
|
|
||||||
const AttentionParams<bf16>& p, int batch, int q_head) {
|
|
||||||
return batch * p.q_b_stride + q_head * p.q_h_stride;
|
|
||||||
}
|
|
||||||
// decode: same offset (q_len == 1, so there is no row stride component)
|
// decode: same offset (q_len == 1, so there is no row stride component)
|
||||||
HOST_DEV_FORCEINLINE int q_decode_base(
|
DEVICE_FORCEINLINE int q_decode_base(
|
||||||
const AttentionParams<bf16>& p, int batch, int q_head) {
|
const AttentionParams<bf16>& p, int batch, int q_head) {
|
||||||
return batch * p.q_b_stride + q_head * p.q_h_stride;
|
return batch * p.q_b_stride + q_head * p.q_h_stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
HOST_DEV_FORCEINLINE int kv_len(const AttentionParams<bf16>& p, int batch) {
|
DEVICE_FORCEINLINE int kv_len(const AttentionParams<bf16>& p, int) {
|
||||||
return p.kv_len;
|
return p.kv_len;
|
||||||
}
|
}
|
||||||
HOST_DEV_FORCEINLINE int q_len(const AttentionParams<bf16>& p, int batch) {
|
DEVICE_FORCEINLINE int causal_offset(
|
||||||
return p.q_len;
|
const AttentionParams<bf16>& p, int, int) {
|
||||||
}
|
|
||||||
HOST_DEV_FORCEINLINE int causal_offset(const AttentionParams<bf16>& p, int batch) {
|
|
||||||
return p.causal_offset;
|
return p.causal_offset;
|
||||||
}
|
}
|
||||||
// decode: exclusive bound of the single query's attend range
|
// decode: exclusive bound of the single query's attend range
|
||||||
HOST_DEV_FORCEINLINE int decode_attend_len(const AttentionParams<bf16>& p, int batch) {
|
DEVICE_FORCEINLINE int decode_attend_len(const AttentionParams<bf16>& p, int) {
|
||||||
return (p.kv_len < p.causal_offset + 1) ? p.kv_len : (p.causal_offset + 1);
|
return (p.kv_len < p.causal_offset + 1) ? p.kv_len : (p.causal_offset + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int HEAD_DIM>
|
template <int HEAD_DIM>
|
||||||
HOST_DEV_FORCEINLINE KVContext make_ctx(
|
DEVICE_FORCEINLINE KVContext make_ctx(
|
||||||
const AttentionParams<bf16>& p, int batch, int kv_head) {
|
const AttentionParams<bf16>& p, int batch, int kv_head) {
|
||||||
KVContext c = {};
|
KVContext c = {};
|
||||||
c.kv_base = batch * p.kv_b_stride + kv_head * p.kv_h_stride;
|
c.kv_base = batch * p.kv_b_stride + kv_head * p.kv_h_stride;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
HOST_DEV_FORCEINLINE int resolve_token(
|
DEVICE_FORCEINLINE int resolve_token(
|
||||||
const AttentionParams<bf16>& p, const KVContext& c, int kc, bool valid) {
|
const AttentionParams<bf16>& p, const KVContext& c, int kc, bool valid) {
|
||||||
return valid ? kc : -1;
|
return valid ? kc : -1;
|
||||||
}
|
}
|
||||||
HOST_DEV_FORCEINLINE KVAddr kv_addr_from_token(
|
DEVICE_FORCEINLINE KVAddr kv_addr_from_token(
|
||||||
const AttentionParams<bf16>& p, const KVContext& c, int token, int d) {
|
const AttentionParams<bf16>& p, const KVContext& c, int token, int d) {
|
||||||
const bool valid = token >= 0;
|
const bool valid = token >= 0;
|
||||||
const int safe_token = valid ? token : 0;
|
const int safe_token = valid ? token : 0;
|
||||||
@@ -123,58 +166,30 @@ struct ContigKV {
|
|||||||
struct PagedKV {
|
struct PagedKV {
|
||||||
static constexpr bool kPaged = true;
|
static constexpr bool kPaged = true;
|
||||||
|
|
||||||
HOST_DEV_FORCEINLINE int host_q_blocks(const AttentionParams<bf16>& p, int rows) {
|
HOST_FORCEINLINE int host_kv_len(const AttentionParams<bf16>& p) {
|
||||||
// sum(ceil(q_len[b] / rows)) <= ceil(total_q / rows) + batch - 1.
|
|
||||||
return (p.q_len + rows - 1) / rows + p.batch - 1;
|
|
||||||
}
|
|
||||||
template <int ROWS>
|
|
||||||
HOST_DEV_FORCEINLINE bool map_q_tile(const AttentionParams<bf16>& p,
|
|
||||||
int flat_tile, int,
|
|
||||||
int& batch, int& q_tile) {
|
|
||||||
int tile_base = 0;
|
|
||||||
for (int b = 0; b < p.batch; ++b) {
|
|
||||||
int len = p.qo_indptr[b + 1] - p.qo_indptr[b];
|
|
||||||
int tiles = (len + ROWS - 1) / ROWS;
|
|
||||||
if (flat_tile < tile_base + tiles) {
|
|
||||||
batch = b;
|
|
||||||
q_tile = flat_tile - tile_base;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
tile_base += tiles;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
HOST_DEV_FORCEINLINE int host_kv_len(const AttentionParams<bf16>& p) {
|
|
||||||
return p.max_context_len;
|
return p.max_context_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefill: Q rows start at qo_indptr[batch] (ragged batch base)
|
|
||||||
HOST_DEV_FORCEINLINE int q_base(
|
|
||||||
const AttentionParams<bf16>& p, int batch, int q_head) {
|
|
||||||
return p.qo_indptr[batch] * p.q_l_stride + q_head * p.q_h_stride;
|
|
||||||
}
|
|
||||||
// decode: Q is [batch, q_head, head_dim], so batch is the outer row
|
// decode: Q is [batch, q_head, head_dim], so batch is the outer row
|
||||||
HOST_DEV_FORCEINLINE int q_decode_base(
|
DEVICE_FORCEINLINE int q_decode_base(
|
||||||
const AttentionParams<bf16>& p, int batch, int q_head) {
|
const AttentionParams<bf16>& p, int batch, int q_head) {
|
||||||
return batch * p.q_l_stride + q_head * p.q_h_stride;
|
return batch * p.q_l_stride + q_head * p.q_h_stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
HOST_DEV_FORCEINLINE int kv_len(const AttentionParams<bf16>& p, int batch) {
|
DEVICE_FORCEINLINE int kv_len(const AttentionParams<bf16>& p, int batch) {
|
||||||
return p.kv_indptr[batch + 1] - p.kv_indptr[batch];
|
return p.kv_indptr[batch + 1] - p.kv_indptr[batch];
|
||||||
}
|
}
|
||||||
HOST_DEV_FORCEINLINE int q_len(const AttentionParams<bf16>& p, int batch) {
|
DEVICE_FORCEINLINE int causal_offset(
|
||||||
return p.qo_indptr[batch + 1] - p.qo_indptr[batch];
|
const AttentionParams<bf16>& p, int batch, int q_len) {
|
||||||
}
|
return kv_len(p, batch) - q_len;
|
||||||
HOST_DEV_FORCEINLINE int causal_offset(const AttentionParams<bf16>& p, int batch) {
|
|
||||||
return kv_len(p, batch) - q_len(p, batch);
|
|
||||||
}
|
}
|
||||||
// decode: the query is the last token, so [0, seq_len) IS its causal range
|
// decode: the query is the last token, so [0, seq_len) IS its causal range
|
||||||
HOST_DEV_FORCEINLINE int decode_attend_len(const AttentionParams<bf16>& p, int batch) {
|
DEVICE_FORCEINLINE int decode_attend_len(const AttentionParams<bf16>& p, int batch) {
|
||||||
return kv_len(p, batch);
|
return kv_len(p, batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int HEAD_DIM>
|
template <int HEAD_DIM>
|
||||||
HOST_DEV_FORCEINLINE KVContext make_ctx(
|
DEVICE_FORCEINLINE KVContext make_ctx(
|
||||||
const AttentionParams<bf16>& p, int batch, int kv_head) {
|
const AttentionParams<bf16>& p, int batch, int kv_head) {
|
||||||
KVContext c = {};
|
KVContext c = {};
|
||||||
c.req_idx = p.req_pool_indices[batch];
|
c.req_idx = p.req_pool_indices[batch];
|
||||||
@@ -183,11 +198,11 @@ struct PagedKV {
|
|||||||
c.head_off = (int64_t)kv_head * HEAD_DIM;
|
c.head_off = (int64_t)kv_head * HEAD_DIM;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
HOST_DEV_FORCEINLINE int resolve_token(
|
DEVICE_FORCEINLINE int resolve_token(
|
||||||
const AttentionParams<bf16>& p, const KVContext& c, int kc, bool valid) {
|
const AttentionParams<bf16>& p, const KVContext& c, int kc, bool valid) {
|
||||||
return valid ? p.req_to_token[c.req_idx * c.rtt_stride + kc] : -1;
|
return valid ? p.req_to_token[c.req_idx * c.rtt_stride + kc] : -1;
|
||||||
}
|
}
|
||||||
HOST_DEV_FORCEINLINE KVAddr kv_addr_from_token(
|
DEVICE_FORCEINLINE KVAddr kv_addr_from_token(
|
||||||
const AttentionParams<bf16>& p, const KVContext& c, int slot, int d) {
|
const AttentionParams<bf16>& p, const KVContext& c, int slot, int d) {
|
||||||
const bool valid = slot >= 0;
|
const bool valid = slot >= 0;
|
||||||
const int safe_slot = valid ? slot : 0;
|
const int safe_slot = valid ? slot : 0;
|
||||||
@@ -195,30 +210,3 @@ struct PagedKV {
|
|||||||
return {&p.k_ptr[gmem_off], &p.v_ptr[gmem_off], valid};
|
return {&p.k_ptr[gmem_off], &p.v_ptr[gmem_off], valid};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Q-block mapping ----
|
|
||||||
// Contiguous grids map directly to (batch, q_tile). Paged grids flatten the
|
|
||||||
// ragged Q tiles, so one thread resolves the request and broadcasts it.
|
|
||||||
template <int ROWS, typename KV>
|
|
||||||
__device__ __forceinline__ bool map_q_block(
|
|
||||||
const AttentionParams<bf16>& p, int& batch, int& q_tile) {
|
|
||||||
if constexpr (!KV::kPaged) {
|
|
||||||
batch = blockIdx.z;
|
|
||||||
q_tile = blockIdx.x;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
__shared__ int mapped_batch;
|
|
||||||
__shared__ int mapped_q_tile;
|
|
||||||
|
|
||||||
if ((threadIdx.x | threadIdx.y) == 0) {
|
|
||||||
mapped_batch = -1;
|
|
||||||
KV::template map_q_tile<ROWS>(
|
|
||||||
p, blockIdx.x, blockIdx.z, mapped_batch, mapped_q_tile);
|
|
||||||
}
|
|
||||||
__syncthreads();
|
|
||||||
|
|
||||||
batch = mapped_batch;
|
|
||||||
q_tile = mapped_q_tile;
|
|
||||||
return batch >= 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,8 @@ torch::Tensor attn_paged_prefill(
|
|||||||
torch::Tensor req_pool_indices,
|
torch::Tensor req_pool_indices,
|
||||||
torch::Tensor kv_indptr,
|
torch::Tensor kv_indptr,
|
||||||
torch::Tensor qo_indptr,
|
torch::Tensor qo_indptr,
|
||||||
|
torch::Tensor q_tile_to_batch,
|
||||||
|
torch::Tensor q_tile_to_index,
|
||||||
c10::optional<torch::Tensor> mask,
|
c10::optional<torch::Tensor> mask,
|
||||||
int64_t causal_offset,
|
int64_t causal_offset,
|
||||||
double scale
|
double scale
|
||||||
@@ -18,8 +20,9 @@ torch::Tensor attn_paged_prefill(
|
|||||||
|
|
||||||
AttentionParams<bf16> p;
|
AttentionParams<bf16> p;
|
||||||
attn_pack_paged_prefill_params(q, k_cache, v_cache,
|
attn_pack_paged_prefill_params(q, k_cache, v_cache,
|
||||||
req_to_token, req_pool_indices,
|
req_to_token, req_pool_indices,
|
||||||
kv_indptr, qo_indptr, mask,
|
kv_indptr, qo_indptr,
|
||||||
|
q_tile_to_batch, q_tile_to_index, mask,
|
||||||
causal_offset, scale, p);
|
causal_offset, scale, p);
|
||||||
|
|
||||||
auto O = torch::empty({q.size(0), q.size(1), q.size(2)}, q.options());
|
auto O = torch::empty({q.size(0), q.size(1), q.size(2)}, q.options());
|
||||||
@@ -39,6 +42,8 @@ PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
|||||||
py::arg("req_pool_indices"),
|
py::arg("req_pool_indices"),
|
||||||
py::arg("kv_indptr"),
|
py::arg("kv_indptr"),
|
||||||
py::arg("qo_indptr"),
|
py::arg("qo_indptr"),
|
||||||
|
py::arg("q_tile_to_batch"),
|
||||||
|
py::arg("q_tile_to_index"),
|
||||||
py::arg("mask") = py::none(),
|
py::arg("mask") = py::none(),
|
||||||
py::arg("causal_offset") = -1,
|
py::arg("causal_offset") = -1,
|
||||||
py::arg("scale") = 0.0,
|
py::arg("scale") = 0.0,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cuda_bf16.h>
|
#include <cuda_bf16.h>
|
||||||
#include "attn_common.h"
|
#include "attn_common.h"
|
||||||
#include "attn_kv_source.cuh"
|
#include "attn_layout_policies.cuh"
|
||||||
|
|
||||||
using bf16 = __nv_bfloat16;
|
using bf16 = __nv_bfloat16;
|
||||||
|
|
||||||
@@ -32,13 +32,13 @@ __device__ __forceinline__ void ld8(const bf16* p, float* o) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int HEAD_DIM, typename KV, int G, int ROWS, int P_BC, bool IsCausal, bool HasMask>
|
template <int HEAD_DIM, typename QSchedule, typename KV, int G, int ROWS, int P_BC,
|
||||||
|
bool IsCausal, bool HasMask>
|
||||||
__global__ void attn_prefill_split_q_kernel_t(AttentionParams<bf16> p) {
|
__global__ void attn_prefill_split_q_kernel_t(AttentionParams<bf16> p) {
|
||||||
constexpr int DPT = HEAD_DIM / G;
|
constexpr int DPT = HEAD_DIM / G;
|
||||||
|
|
||||||
int batch, q_tile;
|
int batch, q_tile;
|
||||||
if (!map_q_block<ROWS, KV>(p, batch, q_tile))
|
QSchedule::map_block(p, batch, q_tile);
|
||||||
return;
|
|
||||||
|
|
||||||
int q_head = blockIdx.y;
|
int q_head = blockIdx.y;
|
||||||
int gpos = threadIdx.x; // 0..G-1 (which d-chunk)
|
int gpos = threadIdx.x; // 0..G-1 (which d-chunk)
|
||||||
@@ -47,8 +47,8 @@ __global__ void attn_prefill_split_q_kernel_t(AttentionParams<bf16> p) {
|
|||||||
|
|
||||||
// Per-request dims (from KV policy — paged reads kv_indptr/qo_indptr).
|
// Per-request dims (from KV policy — paged reads kv_indptr/qo_indptr).
|
||||||
const int seq_len = KV::kv_len(p, batch);
|
const int seq_len = KV::kv_len(p, batch);
|
||||||
const int q_len = KV::q_len(p, batch);
|
const int q_len = QSchedule::q_len(p, batch);
|
||||||
const int causal_off = KV::causal_offset(p, batch);
|
const int causal_off = KV::causal_offset(p, batch, q_len);
|
||||||
const int kv_head = q_head / (p.q_head / p.kv_head);
|
const int kv_head = q_head / (p.q_head / p.kv_head);
|
||||||
const KVContext kctx = KV::template make_ctx<HEAD_DIM>(p, batch, kv_head);
|
const KVContext kctx = KV::template make_ctx<HEAD_DIM>(p, batch, kv_head);
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ __global__ void attn_prefill_split_q_kernel_t(AttentionParams<bf16> p) {
|
|||||||
__shared__ __align__(16) bf16 sV[P_BC * HEAD_DIM];
|
__shared__ __align__(16) bf16 sV[P_BC * HEAD_DIM];
|
||||||
|
|
||||||
// Q: stride-based load [batch, q_head, q_len, head_dim]
|
// Q: stride-based load [batch, q_head, q_len, head_dim]
|
||||||
const int q_base = KV::q_base(p, batch, q_head);
|
const int q_base = QSchedule::q_base(p, batch, q_head);
|
||||||
float qreg[DPT];
|
float qreg[DPT];
|
||||||
if (q_row < q_len) {
|
if (q_row < q_len) {
|
||||||
int q_off = q_base + q_row * p.q_l_stride + gpos * DPT * p.q_d_stride;
|
int q_off = q_base + q_row * p.q_l_stride + gpos * DPT * p.q_d_stride;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cuda_bf16.h>
|
#include <cuda_bf16.h>
|
||||||
#include "attn_common.h"
|
#include "attn_common.h"
|
||||||
#include "attn_kv_source.cuh"
|
#include "attn_layout_policies.cuh"
|
||||||
#include "attn_mma_utils.cuh"
|
#include "attn_mma_utils.cuh"
|
||||||
|
|
||||||
// Tensor-core prefill flash attention (raw mma.sync PTX), unified across
|
// Tensor-core prefill flash attention (raw mma.sync PTX), unified across
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
// dead branches in the inner compute loop (FA2-style).
|
// dead branches in the inner compute loop (FA2-style).
|
||||||
//
|
//
|
||||||
// Traits = KernelTraits<HEAD_DIM, BC, WARPS=4, STAGES=2>.
|
// Traits = KernelTraits<HEAD_DIM, BC, WARPS=4, STAGES=2>.
|
||||||
template <typename Traits, typename KV, bool IsCausal, bool HasMask>
|
template <typename Traits, typename QSchedule, typename KV, bool IsCausal, bool HasMask>
|
||||||
__global__ void attn_prefill_split_q_mma_kernel(AttentionParams<bf16> p) {
|
__global__ void attn_prefill_split_q_mma_kernel(AttentionParams<bf16> p) {
|
||||||
const int warp = threadIdx.x / 32;
|
const int warp = threadIdx.x / 32;
|
||||||
const int lane = threadIdx.x % 32;
|
const int lane = threadIdx.x % 32;
|
||||||
@@ -25,15 +25,14 @@ __global__ void attn_prefill_split_q_mma_kernel(AttentionParams<bf16> p) {
|
|||||||
|
|
||||||
const int q_head = blockIdx.y;
|
const int q_head = blockIdx.y;
|
||||||
int batch, q_tile;
|
int batch, q_tile;
|
||||||
if (!map_q_block<Traits::BR * Traits::WARPS, KV>(p, batch, q_tile))
|
QSchedule::map_block(p, batch, q_tile);
|
||||||
return;
|
|
||||||
const int kv_head = q_head / (p.q_head / p.kv_head);
|
const int kv_head = q_head / (p.q_head / p.kv_head);
|
||||||
const int qrow0 = (q_tile * Traits::WARPS + warp) * Traits::BR;
|
const int qrow0 = (q_tile * Traits::WARPS + warp) * Traits::BR;
|
||||||
|
|
||||||
// Per-request dims (from KV policy — paged reads kv_indptr/qo_indptr).
|
// Per-request dims (from KV policy — paged reads kv_indptr/qo_indptr).
|
||||||
const int seq_len = KV::kv_len(p, batch);
|
const int seq_len = KV::kv_len(p, batch);
|
||||||
const int q_len = KV::q_len(p, batch);
|
const int q_len = QSchedule::q_len(p, batch);
|
||||||
const int causal_off = KV::causal_offset(p, batch);
|
const int causal_off = KV::causal_offset(p, batch, q_len);
|
||||||
const KVContext kctx = KV::template make_ctx<Traits::HEAD_DIM>(p, batch, kv_head);
|
const KVContext kctx = KV::template make_ctx<Traits::HEAD_DIM>(p, batch, kv_head);
|
||||||
|
|
||||||
// Static shared memory: double-buffered K/V (no sQ — Q goes direct
|
// Static shared memory: double-buffered K/V (no sQ — Q goes direct
|
||||||
@@ -42,7 +41,7 @@ __global__ void attn_prefill_split_q_mma_kernel(AttentionParams<bf16> p) {
|
|||||||
__shared__ __align__(16) bf16 sV[Traits::STAGES * Traits::BC * Traits::LD];
|
__shared__ __align__(16) bf16 sV[Traits::STAGES * Traits::BC * Traits::LD];
|
||||||
|
|
||||||
// Load Q fragments straight from global into mma A-operand layout.
|
// Load Q fragments straight from global into mma A-operand layout.
|
||||||
const int q_base = KV::q_base(p, batch, q_head);
|
const int q_base = QSchedule::q_base(p, batch, q_head);
|
||||||
const int qra = qrow0 + gid;
|
const int qra = qrow0 + gid;
|
||||||
const int qrb = qrow0 + gid + 8;
|
const int qrb = qrow0 + gid + 8;
|
||||||
const bool va = qra < q_len, vb = qrb < q_len;
|
const bool va = qra < q_len, vb = qrb < q_len;
|
||||||
@@ -138,7 +137,7 @@ __global__ void attn_prefill_split_q_mma_kernel(AttentionParams<bf16> p) {
|
|||||||
// ---- write output: packed bf16x2 stores ----
|
// ---- write output: packed bf16x2 stores ----
|
||||||
float rl0 = (l0 > 1e-20f) ? (1.0f / l0) : 0.0f;
|
float rl0 = (l0 > 1e-20f) ? (1.0f / l0) : 0.0f;
|
||||||
float rl1 = (l1 > 1e-20f) ? (1.0f / l1) : 0.0f;
|
float rl1 = (l1 > 1e-20f) ? (1.0f / l1) : 0.0f;
|
||||||
const int o_base = KV::q_base(p, batch, q_head);
|
const int o_base = QSchedule::q_base(p, batch, q_head);
|
||||||
#pragma unroll
|
#pragma unroll
|
||||||
for (int dn8 = 0; dn8 < Traits::DN8; dn8++) {
|
for (int dn8 = 0; dn8 < Traits::DN8; dn8++) {
|
||||||
int d = dn8 * 8 + 2 * tid4;
|
int d = dn8 * 8 + 2 * tid4;
|
||||||
|
|||||||
@@ -12,6 +12,26 @@
|
|||||||
struct PagedDecodeDispatch { AttentionParams<bf16>& p; template<int H> void operator()() { dispatch_paged_decode<H>(p, 0); } };
|
struct PagedDecodeDispatch { AttentionParams<bf16>& p; template<int H> void operator()() { dispatch_paged_decode<H>(p, 0); } };
|
||||||
struct PagedPrefillDispatch { AttentionParams<bf16>& p; template<int H> void operator()() { dispatch_paged_prefill<H>(p, 0); } };
|
struct PagedPrefillDispatch { AttentionParams<bf16>& p; template<int H> void operator()() { dispatch_paged_prefill<H>(p, 0); } };
|
||||||
|
|
||||||
|
static int make_q_tile_mapping(const std::vector<int>& q_lens,
|
||||||
|
int** d_batch, int** d_tile) {
|
||||||
|
constexpr int ROWS = 64;
|
||||||
|
std::vector<int> h_batch;
|
||||||
|
std::vector<int> h_tile;
|
||||||
|
for (int b = 0; b < (int)q_lens.size(); ++b) {
|
||||||
|
int n_tiles = (q_lens[b] + ROWS - 1) / ROWS;
|
||||||
|
for (int tile = 0; tile < n_tiles; ++tile) {
|
||||||
|
h_batch.push_back(b);
|
||||||
|
h_tile.push_back(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t bytes = h_batch.size() * sizeof(int);
|
||||||
|
cudaMalloc(d_batch, bytes);
|
||||||
|
cudaMalloc(d_tile, bytes);
|
||||||
|
cudaMemcpy(*d_batch, h_batch.data(), bytes, cudaMemcpyHostToDevice);
|
||||||
|
cudaMemcpy(*d_tile, h_tile.data(), bytes, cudaMemcpyHostToDevice);
|
||||||
|
return (int)h_batch.size();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- CPU reference: paged decode with variable seq_lens ----
|
// ---- CPU reference: paged decode with variable seq_lens ----
|
||||||
// Q: [B, Hq, D], K/V pool: [pool_size, Hkv, D]
|
// Q: [B, Hq, D], K/V pool: [pool_size, Hkv, D]
|
||||||
// req_to_token: [num_reqs, max_ctx_len], req_pool_indices: [B]
|
// req_to_token: [num_reqs, max_ctx_len], req_pool_indices: [B]
|
||||||
@@ -483,6 +503,9 @@ static int run_prefill_test(int B, int Hq, int Hkv,
|
|||||||
nullptr, 0, 0,
|
nullptr, 0, 0,
|
||||||
B, Hq, Hkv, HEAD_DIM, max_ctx, causal, h_o_ref);
|
B, Hq, Hkv, HEAD_DIM, max_ctx, causal, h_o_ref);
|
||||||
|
|
||||||
|
int *d_qtb, *d_qti;
|
||||||
|
int num_q_tiles = make_q_tile_mapping(q_lens, &d_qtb, &d_qti);
|
||||||
|
|
||||||
// Kernel launch
|
// Kernel launch
|
||||||
AttentionParams<bf16> p;
|
AttentionParams<bf16> p;
|
||||||
p.batch = B; p.q_head = Hq; p.kv_head = Hkv;
|
p.batch = B; p.q_head = Hq; p.kv_head = Hkv;
|
||||||
@@ -497,6 +520,8 @@ static int run_prefill_test(int B, int Hq, int Hkv,
|
|||||||
p.q_ptr = d_q; p.k_ptr = d_k_pool; p.v_ptr = d_v_pool;
|
p.q_ptr = d_q; p.k_ptr = d_k_pool; p.v_ptr = d_v_pool;
|
||||||
p.req_to_token = d_rtt; p.req_pool_indices = d_rpi;
|
p.req_to_token = d_rtt; p.req_pool_indices = d_rpi;
|
||||||
p.kv_indptr = d_kvi; p.qo_indptr = d_qoi;
|
p.kv_indptr = d_kvi; p.qo_indptr = d_qoi;
|
||||||
|
p.q_tile_to_batch = d_qtb; p.q_tile_to_index = d_qti;
|
||||||
|
p.num_q_tiles = num_q_tiles;
|
||||||
p.o_ptr = d_o; p.o_part = nullptr; p.ml_part = nullptr;
|
p.o_ptr = d_o; p.o_part = nullptr; p.ml_part = nullptr;
|
||||||
|
|
||||||
dispatch_by_head_dim(HEAD_DIM, PagedPrefillDispatch{p});
|
dispatch_by_head_dim(HEAD_DIM, PagedPrefillDispatch{p});
|
||||||
@@ -523,6 +548,7 @@ static int run_prefill_test(int B, int Hq, int Hkv,
|
|||||||
free(h_o_ref); free(h_o_bf); free(h_o_got);
|
free(h_o_ref); free(h_o_bf); free(h_o_got);
|
||||||
cudaFree(d_q); cudaFree(d_o); cudaFree(d_k_pool); cudaFree(d_v_pool);
|
cudaFree(d_q); cudaFree(d_o); cudaFree(d_k_pool); cudaFree(d_v_pool);
|
||||||
cudaFree(d_rtt); cudaFree(d_rpi); cudaFree(d_kvi); cudaFree(d_qoi);
|
cudaFree(d_rtt); cudaFree(d_rpi); cudaFree(d_kvi); cudaFree(d_qoi);
|
||||||
|
cudaFree(d_qtb); cudaFree(d_qti);
|
||||||
return pass ? 0 : 1;
|
return pass ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,6 +645,10 @@ static int run_prefill_mask_test(int Hq, int Hkv, int q_len, int seed) {
|
|||||||
h_mask, q_len, q_len,
|
h_mask, q_len, q_len,
|
||||||
B, Hq, Hkv, HEAD_DIM, max_ctx, 0, h_o_ref);
|
B, Hq, Hkv, HEAD_DIM, max_ctx, 0, h_o_ref);
|
||||||
|
|
||||||
|
std::vector<int> q_lens(B, q_len);
|
||||||
|
int *d_qtb, *d_qti;
|
||||||
|
int num_q_tiles = make_q_tile_mapping(q_lens, &d_qtb, &d_qti);
|
||||||
|
|
||||||
AttentionParams<bf16> p;
|
AttentionParams<bf16> p;
|
||||||
p.batch = B; p.q_head = Hq; p.kv_head = Hkv;
|
p.batch = B; p.q_head = Hq; p.kv_head = Hkv;
|
||||||
p.head_dim = HEAD_DIM;
|
p.head_dim = HEAD_DIM;
|
||||||
@@ -632,6 +662,8 @@ static int run_prefill_mask_test(int Hq, int Hkv, int q_len, int seed) {
|
|||||||
p.q_ptr = d_q; p.k_ptr = d_k_pool; p.v_ptr = d_v_pool;
|
p.q_ptr = d_q; p.k_ptr = d_k_pool; p.v_ptr = d_v_pool;
|
||||||
p.req_to_token = d_rtt; p.req_pool_indices = d_rpi;
|
p.req_to_token = d_rtt; p.req_pool_indices = d_rpi;
|
||||||
p.kv_indptr = d_kvi; p.qo_indptr = d_qoi;
|
p.kv_indptr = d_kvi; p.qo_indptr = d_qoi;
|
||||||
|
p.q_tile_to_batch = d_qtb; p.q_tile_to_index = d_qti;
|
||||||
|
p.num_q_tiles = num_q_tiles;
|
||||||
p.o_ptr = d_o; p.o_part = nullptr; p.ml_part = nullptr;
|
p.o_ptr = d_o; p.o_part = nullptr; p.ml_part = nullptr;
|
||||||
|
|
||||||
dispatch_by_head_dim(HEAD_DIM, PagedPrefillDispatch{p});
|
dispatch_by_head_dim(HEAD_DIM, PagedPrefillDispatch{p});
|
||||||
@@ -659,6 +691,7 @@ static int run_prefill_mask_test(int Hq, int Hkv, int q_len, int seed) {
|
|||||||
cudaFree(d_q); cudaFree(d_o); cudaFree(d_k_pool); cudaFree(d_v_pool);
|
cudaFree(d_q); cudaFree(d_o); cudaFree(d_k_pool); cudaFree(d_v_pool);
|
||||||
cudaFree(d_rtt); cudaFree(d_rpi); cudaFree(d_kvi); cudaFree(d_qoi);
|
cudaFree(d_rtt); cudaFree(d_rpi); cudaFree(d_kvi); cudaFree(d_qoi);
|
||||||
cudaFree(d_mask);
|
cudaFree(d_mask);
|
||||||
|
cudaFree(d_qtb); cudaFree(d_qti);
|
||||||
return pass ? 0 : 1;
|
return pass ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,6 +819,10 @@ static void bench_prefill(int B, int Hq, int Hkv, int q_len, int kv_len, int cau
|
|||||||
for (int b = 0; b < B; b++) h_qoi[b + 1] = h_qoi[b] + q_len;
|
for (int b = 0; b < B; b++) h_qoi[b + 1] = h_qoi[b] + q_len;
|
||||||
cudaMemcpy(d_qoi, h_qoi, sz_qoi, cudaMemcpyHostToDevice);
|
cudaMemcpy(d_qoi, h_qoi, sz_qoi, cudaMemcpyHostToDevice);
|
||||||
|
|
||||||
|
std::vector<int> q_lens(B, q_len);
|
||||||
|
int *d_qtb, *d_qti;
|
||||||
|
int num_q_tiles = make_q_tile_mapping(q_lens, &d_qtb, &d_qti);
|
||||||
|
|
||||||
AttentionParams<bf16> p;
|
AttentionParams<bf16> p;
|
||||||
p.batch = B; p.q_head = Hq; p.kv_head = Hkv;
|
p.batch = B; p.q_head = Hq; p.kv_head = Hkv;
|
||||||
p.head_dim = HEAD_DIM;
|
p.head_dim = HEAD_DIM;
|
||||||
@@ -798,6 +835,8 @@ static void bench_prefill(int B, int Hq, int Hkv, int q_len, int kv_len, int cau
|
|||||||
p.q_ptr = d_q; p.k_ptr = d_k_pool; p.v_ptr = d_v_pool;
|
p.q_ptr = d_q; p.k_ptr = d_k_pool; p.v_ptr = d_v_pool;
|
||||||
p.req_to_token = d_rtt; p.req_pool_indices = d_rpi;
|
p.req_to_token = d_rtt; p.req_pool_indices = d_rpi;
|
||||||
p.kv_indptr = d_kvi; p.qo_indptr = d_qoi;
|
p.kv_indptr = d_kvi; p.qo_indptr = d_qoi;
|
||||||
|
p.q_tile_to_batch = d_qtb; p.q_tile_to_index = d_qti;
|
||||||
|
p.num_q_tiles = num_q_tiles;
|
||||||
p.o_ptr = d_o; p.o_part = nullptr; p.ml_part = nullptr;
|
p.o_ptr = d_o; p.o_part = nullptr; p.ml_part = nullptr;
|
||||||
|
|
||||||
auto launch = [&]() {
|
auto launch = [&]() {
|
||||||
@@ -827,6 +866,7 @@ static void bench_prefill(int B, int Hq, int Hkv, int q_len, int kv_len, int cau
|
|||||||
free(tmp); free(h_rtt); free(h_rpi); free(h_kvi); free(h_qoi);
|
free(tmp); free(h_rtt); free(h_rpi); free(h_kvi); free(h_qoi);
|
||||||
cudaFree(d_q); cudaFree(d_o); cudaFree(d_k_pool); cudaFree(d_v_pool);
|
cudaFree(d_q); cudaFree(d_o); cudaFree(d_k_pool); cudaFree(d_v_pool);
|
||||||
cudaFree(d_rtt); cudaFree(d_rpi); cudaFree(d_kvi); cudaFree(d_qoi);
|
cudaFree(d_rtt); cudaFree(d_rpi); cudaFree(d_kvi); cudaFree(d_qoi);
|
||||||
|
cudaFree(d_qtb); cudaFree(d_qti);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ Additionally, optimized `.cuh` variants with tensor-core MMA (Matrix Multiply-Ac
|
|||||||
| Split-KV MMA decode | `attn_decode_split_kv_mma.cuh` | Split KV across warps + MMA (sm_80+) |
|
| Split-KV MMA decode | `attn_decode_split_kv_mma.cuh` | Split KV across warps + MMA (sm_80+) |
|
||||||
| Split-Q MMA prefill | `attn_prefill_split_q_mma.cuh` | Split Q across warps + MMA (sm_80+) |
|
| Split-Q MMA prefill | `attn_prefill_split_q_mma.cuh` | Split Q across warps + MMA (sm_80+) |
|
||||||
|
|
||||||
> The paged and non-paged paths are ONE kernel templated on a `KVSource`
|
> The paged and non-paged paths share one kernel body. Prefill is templated on
|
||||||
> policy (`ContigKV` / `PagedKV` in `attn_kv_source.cuh`); there are no
|
> an independent Q schedule (`DenseQSchedule` / `PackedQSchedule`) and KV
|
||||||
> separate `attn_paged_*.cuh` files anymore.
|
> source (`ContigKV` / `PagedKV`); decode only needs the KV source. There are
|
||||||
|
> no separate `attn_paged_*.cuh` files.
|
||||||
|
|
||||||
### Rotary Embedding Kernel
|
### Rotary Embedding Kernel
|
||||||
|
|
||||||
@@ -238,6 +239,47 @@ mask: 2D [batch, kv_len] or 3D [batch, q_len, kv_len] (bool, True=keep)
|
|||||||
|
|
||||||
Layout convention: all q/k/v are `[batch, seq_len, n_heads, head_dim]` (blhd). Scale is always `1/sqrt(head_dim)`.
|
Layout convention: all q/k/v are `[batch, seq_len, n_heads, head_dim]` (blhd). Scale is always `1/sqrt(head_dim)`.
|
||||||
|
|
||||||
|
### Q Scheduling and KV Addressing
|
||||||
|
|
||||||
|
Prefill separates Q work scheduling from KV storage:
|
||||||
|
|
||||||
|
- `DenseQSchedule` maps a rectangular grid directly with
|
||||||
|
`batch = blockIdx.z` and `q_tile = blockIdx.x`.
|
||||||
|
- `PackedQSchedule` consumes a compact work map for a packed
|
||||||
|
`[total_q, q_heads, head_dim]` tensor.
|
||||||
|
- `ContigKV` and `PagedKV` only provide KV lengths and translate logical KV
|
||||||
|
positions into physical addresses. They do not schedule Q blocks.
|
||||||
|
|
||||||
|
For ragged Q lengths `[70, 10, 130]` and 64 rows per Q tile, cache binding
|
||||||
|
builds:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qo_indptr = [0, 70, 80, 210]
|
||||||
|
q_tile_to_batch = [0, 0, 1, 2, 2, 2]
|
||||||
|
q_tile_to_index = [0, 1, 0, 0, 1, 2]
|
||||||
|
```
|
||||||
|
|
||||||
|
Paged prefill launches:
|
||||||
|
|
||||||
|
```text
|
||||||
|
grid.x = num_q_tiles # 6, exactly the valid ragged work items
|
||||||
|
grid.y = q_heads
|
||||||
|
grid.z = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Each block resolves its request and request-local tile in O(1):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
batch = q_tile_to_batch[blockIdx.x];
|
||||||
|
q_tile = q_tile_to_index[blockIdx.x];
|
||||||
|
```
|
||||||
|
|
||||||
|
The kernel then uses `qo_indptr[batch]` for the packed Q base and adjacent
|
||||||
|
`qo_indptr` / `kv_indptr` entries for that request's Q and KV lengths. This
|
||||||
|
avoids the previous per-block linear scan over the batch, shared-memory
|
||||||
|
broadcast, mapping barrier, and upper-bound grid with potentially invalid
|
||||||
|
blocks.
|
||||||
|
|
||||||
## Standalone Testing
|
## Standalone Testing
|
||||||
|
|
||||||
Each `csrc/tests/*.cu` file has the `nvcc` compile command in its header comment. Example:
|
Each `csrc/tests/*.cu` file has the `nvcc` compile command in its header comment. Example:
|
||||||
@@ -285,7 +327,7 @@ csrc/
|
|||||||
│ ├── attn_decode_split_kv_mma.cuh # Split-KV + MMA variant (contig + paged)
|
│ ├── attn_decode_split_kv_mma.cuh # Split-KV + MMA variant (contig + paged)
|
||||||
│ ├── attn_prefill_split_q.cuh # Split-Q variant (contig + paged via KVSource)
|
│ ├── attn_prefill_split_q.cuh # Split-Q variant (contig + paged via KVSource)
|
||||||
│ ├── attn_prefill_split_q_mma.cuh # Split-Q + MMA variant (contig + paged)
|
│ ├── attn_prefill_split_q_mma.cuh # Split-Q + MMA variant (contig + paged)
|
||||||
│ ├── attn_kv_source.cuh # KVSource policies (ContigKV / PagedKV)
|
│ ├── attn_layout_policies.cuh # Q schedules and KVSource policies
|
||||||
│ ├── attn_dispatchers.cuh # Kernel dispatch macros + KV-templated launchers
|
│ ├── attn_dispatchers.cuh # Kernel dispatch macros + KV-templated launchers
|
||||||
│ ├── attn_entry_utils.cuh # Entry point helpers
|
│ ├── attn_entry_utils.cuh # Entry point helpers
|
||||||
│ ├── attn_mma_utils.cuh # MMA utilities
|
│ ├── attn_mma_utils.cuh # MMA utilities
|
||||||
|
|||||||
@@ -286,6 +286,16 @@ def test_page_pool_contiguous_bind_tasks_prefill():
|
|||||||
assert kv.req_pool_indices.dtype == torch.int32
|
assert kv.req_pool_indices.dtype == torch.int32
|
||||||
|
|
||||||
|
|
||||||
|
def test_page_pool_bind_tasks_builds_compact_q_tile_mapping():
|
||||||
|
pool = _make_contiguous_pool(max_batch_size=3, max_seq_len=256)
|
||||||
|
|
||||||
|
kv = pool.bind_tasks([0, 1, 2], [70, 10, 130], _ws(pool), start_pos=0)
|
||||||
|
|
||||||
|
assert kv.qo_indptr.tolist() == [0, 70, 80, 210]
|
||||||
|
assert kv.q_tile_to_batch.tolist() == [0, 0, 1, 2, 2, 2]
|
||||||
|
assert kv.q_tile_to_index.tolist() == [0, 1, 0, 0, 1, 2]
|
||||||
|
|
||||||
|
|
||||||
def test_page_pool_contiguous_bind_tasks_decode():
|
def test_page_pool_contiguous_bind_tasks_decode():
|
||||||
pool = _make_contiguous_pool()
|
pool = _make_contiguous_pool()
|
||||||
task_cache = _make_task_cache(pool)
|
task_cache = _make_task_cache(pool)
|
||||||
|
|||||||
Reference in New Issue
Block a user