blob: 64c2a872d66d56168db26deb0ec7d07049fee4b8 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file src/runtime/vm/attn_backend.h
* \brief The attention backend classes used by KV cache.
*/
#ifndef TVM_RUNTIME_VM_ATTN_BACKEND_H_
#define TVM_RUNTIME_VM_ATTN_BACKEND_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/shape.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/function.h>
#include <tvm/runtime/device_api.h>
#include <tvm/runtime/logging.h>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "attn_utils.h"
namespace tvm {
namespace runtime {
namespace vm {
/*! \brief The attention backend kinds. */
enum class AttnBackendKind : int {
kTIR = 0,
kFlashInfer = 1,
};
/*!
* \brief Return a zero-copy alias of \p t whose `byte_offset` is folded into the
* data pointer, so the resulting tensor has `byte_offset == 0`.
*
* FlashInfer 0.6.3 kernels read tensors from `data` directly and do NOT honor
* the DLPack `byte_offset` field. mlc's auxiliary index tensors (qo_indptr,
* kv_indptr, page_indptr, page_indices, length_info, ...) are views packed into
* a shared workspace and therefore carry a non-zero `byte_offset`. Passing them
* as-is makes FlashInfer read the wrong addresses; this helper rebases them.
*/
inline ffi::Tensor ZeroByteOffsetView(const Tensor& t) {
if (t->byte_offset == 0) return t;
auto* holder = new Tensor(t); // keep the underlying storage alive
auto* managed = new DLManagedTensor();
managed->manager_ctx = holder;
managed->deleter = [](DLManagedTensor* self) {
delete[] self->dl_tensor.shape;
delete[] self->dl_tensor.strides;
delete static_cast<Tensor*>(self->manager_ctx);
delete self;
};
DLTensor& dl = managed->dl_tensor;
dl.data = static_cast<void*>(static_cast<char*>(t->data) + t->byte_offset);
dl.device = t->device;
dl.ndim = t->ndim;
dl.dtype = t->dtype;
dl.shape = new int64_t[t->ndim];
dl.strides = nullptr;
for (int i = 0; i < t->ndim; ++i) dl.shape[i] = t->shape[i];
if (t->strides != nullptr) {
dl.strides = new int64_t[t->ndim];
for (int i = 0; i < t->ndim; ++i) dl.strides[i] = t->strides[i];
}
dl.byte_offset = 0;
return tvm::ffi::Tensor::FromDLPack(managed, /*require_alignment=*/0,
/*require_contiguous=*/false);
}
/*!
* \brief Build a strided, zero-copy view selecting the key (which=0) or value
* (which=1) sub-tensor from a combined paged KV tensor of shape
* (num_pages, 2, num_heads, page_size, head_dim), yielding a
* (num_pages, num_heads, page_size, head_dim) tensor that shares storage with
* `pages`. FlashInfer 0.6.3 takes separate key/value paged caches and reads the
* tensor strides, so a strided view avoids an explicit split/copy.
*/
inline ffi::Tensor PagedKVCacheView(const Tensor& pages, int64_t which) {
TVM_FFI_ICHECK_EQ(pages->ndim, 5);
TVM_FFI_ICHECK_EQ(pages->shape[1], 2);
int64_t num_pages = pages->shape[0];
int64_t num_heads = pages->shape[2];
int64_t page_size = pages->shape[3];
int64_t head_dim = pages->shape[4];
int64_t inner = num_heads * page_size * head_dim;
int64_t elem_bytes = (pages->dtype.bits * pages->dtype.lanes + 7) / 8;
auto* holder = new Tensor(pages); // keep the underlying storage alive
auto* managed = new DLManagedTensor();
managed->manager_ctx = holder;
managed->deleter = [](DLManagedTensor* self) {
delete[] self->dl_tensor.shape;
delete[] self->dl_tensor.strides;
delete static_cast<Tensor*>(self->manager_ctx);
delete self;
};
DLTensor& dl = managed->dl_tensor;
dl.data = static_cast<void*>(static_cast<char*>(pages->data) + pages->byte_offset +
which * inner * elem_bytes);
dl.device = pages->device;
dl.ndim = 4;
dl.dtype = pages->dtype;
dl.shape = new int64_t[4]{num_pages, num_heads, page_size, head_dim};
dl.strides = new int64_t[4]{2 * inner, page_size * head_dim, head_dim, 1};
dl.byte_offset = 0;
return tvm::ffi::Tensor::FromDLPack(managed, /*require_alignment=*/0,
/*require_contiguous=*/false);
}
/*!
* \brief Return a strided, zero-copy view selecting the `[start, start+length)`
* slice along the LAST dimension of \p t, preserving all other strides and
* folding the slice offset into the data pointer (so `byte_offset == 0`).
*
* Used to split MLA tensors that store two head components concatenated along
* the last dim: the query into `q_nope`/`q_pe` and the paged cache into
* `ckv_cache`/`kpe_cache`. FlashInfer reads tensor strides and ignores
* `byte_offset`, so a strided slice avoids a copy.
*/
inline ffi::Tensor SliceLastDimView(const Tensor& t, int64_t start, int64_t length) {
int ndim = t->ndim;
int64_t elem_bytes = (t->dtype.bits * t->dtype.lanes + 7) / 8;
std::vector<int64_t> in_strides(ndim);
if (t->strides != nullptr) {
for (int i = 0; i < ndim; ++i) in_strides[i] = t->strides[i];
} else {
int64_t s = 1;
for (int i = ndim - 1; i >= 0; --i) {
in_strides[i] = s;
s *= t->shape[i];
}
}
auto* holder = new Tensor(t); // keep the underlying storage alive
auto* managed = new DLManagedTensor();
managed->manager_ctx = holder;
managed->deleter = [](DLManagedTensor* self) {
delete[] self->dl_tensor.shape;
delete[] self->dl_tensor.strides;
delete static_cast<Tensor*>(self->manager_ctx);
delete self;
};
DLTensor& dl = managed->dl_tensor;
dl.data = static_cast<void*>(static_cast<char*>(t->data) + t->byte_offset +
start * in_strides[ndim - 1] * elem_bytes);
dl.device = t->device;
dl.ndim = ndim;
dl.dtype = t->dtype;
dl.shape = new int64_t[ndim];
dl.strides = new int64_t[ndim];
for (int i = 0; i < ndim; ++i) {
dl.shape[i] = t->shape[i];
dl.strides[i] = in_strides[i];
}
dl.shape[ndim - 1] = length;
dl.byte_offset = 0;
return tvm::ffi::Tensor::FromDLPack(managed, /*require_alignment=*/0,
/*require_contiguous=*/false);
}
/*! \brief The base class of attention backends. */
class AttnBackendFunc {
public:
explicit AttnBackendFunc(ffi::Function attn_func, AttnKind attn_kind,
AttnBackendKind backend_kind)
: attn_func_(std::move(attn_func)), attn_kind(attn_kind), backend_kind(backend_kind) {}
virtual ~AttnBackendFunc() = default;
protected:
ffi::Function attn_func_;
public:
AttnKind attn_kind;
AttnBackendKind backend_kind;
};
/*! \brief The paged prefill attention function base class. */
class PagedPrefillFunc : public AttnBackendFunc {
public:
explicit PagedPrefillFunc(ffi::Function attn_func, AttnKind attn_kind,
AttnBackendKind backend_kind)
: AttnBackendFunc(std::move(attn_func), attn_kind, backend_kind) {}
virtual void MHA(int depth, Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, Tensor q_rope_position,
Tensor k_rope_pos_offset, bool causal, RoPEMode rope_mode, double rotary_scale,
double rotary_theta, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MHA computation is not supported by the current backend";
}
virtual void MLA(int depth, Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, bool causal, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MLA computation is not supported by the current backend";
}
virtual void BeginForward(int depth, Tensor float_workspace_buffer, Tensor int_workspace_buffer,
Tensor page_locked_int_workspace_buffer, HostMemoryVector* qo_indptr,
HostMemoryVector* page_indptr, HostMemoryVector* last_page_len,
int64_t batch_size, int64_t total_qo_len, int64_t page_size,
int64_t num_qo_heads, int64_t num_kv_heads, int64_t qk_head_dim,
int64_t v_head_dim, bool causal, TVMStreamHandle copy_stream) {
// Do nothing. Subclasses can override to customize behavior.
}
};
/*! \brief The TIR-based paged prefill attention function class. */
class TIRPagedPrefillFunc : public PagedPrefillFunc {
public:
explicit TIRPagedPrefillFunc(ffi::Function attn_func, AttnKind attn_kind)
: PagedPrefillFunc(std::move(attn_func), attn_kind, AttnBackendKind::kTIR) {}
void MHA(int depth, Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, Tensor q_rope_position,
Tensor k_rope_pos_offset, bool causal, RoPEMode rope_mode, double rotary_scale,
double rotary_theta, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) final {
attn_func_(q, qo_indptr, pages, page_indptr, page_indices, length_info, k_rope_pos_offset,
q_rope_position, attn_output, attn_lse, static_cast<int64_t>(causal),
/*rotary_mode=*/static_cast<int64_t>(rope_mode == RoPEMode::kInline), rotary_scale,
rotary_theta, sm_scale);
}
void MLA(int depth, Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, bool causal, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) final {
attn_func_(q, qo_indptr, pages, page_indptr, page_indices, length_info, attn_output, attn_lse,
static_cast<int64_t>(causal), sm_scale);
}
};
/*! \brief The FlashInfer-based paged prefill attention function class. */
class FlashInferPagedPrefillFunc : public PagedPrefillFunc {
public:
explicit FlashInferPagedPrefillFunc(ffi::Function attn_func, ffi::Function plan_func,
AttnKind attn_kind)
: PagedPrefillFunc(std::move(attn_func), attn_kind, AttnBackendKind::kFlashInfer),
plan_func_(std::move(plan_func)) {}
void MHA(int depth, Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, Tensor q_rope_position,
Tensor k_rope_pos_offset, bool causal, RoPEMode rope_mode, double rotary_scale,
double rotary_theta, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) final {
auto [float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
plan_info_vec] = cached_buffers_[depth];
double rope_rcp_scale = 1 / rotary_scale;
double rope_rcp_theta = 1 / rotary_theta;
attn_func_(
float_workspace_buffer, int_workspace_buffer, plan_info_vec, q, PagedKVCacheView(pages, 0),
PagedKVCacheView(pages, 1), ZeroByteOffsetView(qo_indptr), ZeroByteOffsetView(page_indptr),
ZeroByteOffsetView(page_indices), ZeroByteOffsetView(length_info), attn_output, attn_lse,
/*mask_mode_code=*/static_cast<int64_t>(causal),
/*layout(HND)=*/1, /*window_left=*/-1, /*enable_pdl=*/false, sm_scale,
/*rope_rcp_scale=*/rope_rcp_scale, /*rope_rcp_theta=*/rope_rcp_theta);
}
void MLA(int depth, Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, bool causal, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) final {
auto [float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
plan_info_vec] = cached_buffers_[depth];
// FlashInfer's MLA run takes the query split into its compressed (nope) and
// positional-embedding (pe) parts, and the paged cache split into the
// compressed-kv cache (ckv) and key-positional-embedding cache (kpe). Both
// q ([n, num_heads, ckv+kpe]) and pages ([num_pages, page_size, ckv+kpe])
// store the two components concatenated along the last dimension.
int64_t head_dim_ckv = mla_head_dim_ckv_;
int64_t head_dim_kpe = mla_head_dim_kpe_;
TVM_FFI_ICHECK_GE(head_dim_ckv, 0)
<< "MLA head dims are unset; BeginForward must run before MLA.";
attn_func_(float_workspace_buffer, int_workspace_buffer, plan_info_vec,
SliceLastDimView(q, 0, head_dim_ckv),
SliceLastDimView(q, head_dim_ckv, head_dim_kpe),
SliceLastDimView(pages, 0, head_dim_ckv),
SliceLastDimView(pages, head_dim_ckv, head_dim_kpe),
ZeroByteOffsetView(page_indices), attn_output, attn_lse,
/*mask_mode_code=*/static_cast<int64_t>(causal), /*num_heads=*/q->shape[1],
/*page_size=*/pages->shape[1], sm_scale, /*return_lse_base_on_e=*/false);
}
void BeginForward(int depth, Tensor float_workspace_buffer, Tensor int_workspace_buffer,
Tensor page_locked_int_workspace_buffer, HostMemoryVector* qo_indptr,
HostMemoryVector* page_indptr, HostMemoryVector* last_page_len,
int64_t batch_size, int64_t total_qo_len, int64_t page_size,
int64_t num_qo_heads, int64_t num_kv_heads, int64_t qk_head_dim,
int64_t v_head_dim, bool causal, TVMStreamHandle copy_stream) final {
// FlashInfer expects kv_len as an (int32) tensor rather than a shape tuple.
HostMemoryVector kv_len_arr(batch_size, DLDataType{kDLInt, 32, 1},
qo_indptr->as_tensor()->device);
for (int i = 0; i < static_cast<int>(batch_size); ++i) {
kv_len_arr.push_back(static_cast<int32_t>(
(*page_indptr)[i + 1] != (*page_indptr)[i]
? ((*page_indptr)[i + 1] - (*page_indptr)[i] - 1) * page_size + (*last_page_len)[i]
: 0));
}
ffi::Array<int64_t> plan_info_vec;
if (attn_kind == AttnKind::kMHA) {
// Todo(tvm-team): enable cuda graph
plan_info_vec =
plan_func_(float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
qo_indptr->as_tensor(), page_indptr->as_tensor(), kv_len_arr.as_tensor(),
total_qo_len, batch_size, num_qo_heads, num_kv_heads, page_size,
/*enable_cuda_graph=*/false, qk_head_dim, v_head_dim, causal,
/*window_left=*/-1, /*fixed_split_size=*/-1, /*disable_split_kv=*/false,
/*num_colocated_ctas=*/0)
.cast<ffi::Array<int64_t>>();
} else if (attn_kind == AttnKind::kMLA) {
// For MLA the compressed-kv head dim equals the output (v) head dim, and
// the remaining part of qk_head_dim is the key positional embedding. Cache
// them for the run, which must split q/pages into ckv and kpe components.
mla_head_dim_ckv_ = v_head_dim;
mla_head_dim_kpe_ = qk_head_dim - v_head_dim;
plan_info_vec =
plan_func_(float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
qo_indptr->as_tensor(), page_indptr->as_tensor(), kv_len_arr.as_tensor(),
num_qo_heads, v_head_dim, causal)
.cast<ffi::Array<int64_t>>();
}
if (cached_buffers_.size() <= static_cast<size_t>(depth)) {
cached_buffers_.resize(depth + 1);
}
cached_buffers_[depth] =
std::make_tuple(float_workspace_buffer, int_workspace_buffer,
page_locked_int_workspace_buffer, std::move(plan_info_vec));
}
private:
ffi::Function plan_func_;
std::vector<std::tuple<Tensor, Tensor, Tensor, ffi::Array<int64_t>>> cached_buffers_;
// MLA-only: the compressed-kv and key-positional-embedding head dims, used to
// split q/pages in the run. Set during BeginForward for the kMLA attn kind.
int64_t mla_head_dim_ckv_ = -1;
int64_t mla_head_dim_kpe_ = -1;
};
/*! \brief The ragged prefill attention function base class. */
class RaggedPrefillFunc : public AttnBackendFunc {
public:
explicit RaggedPrefillFunc(ffi::Function attn_func, AttnKind attn_kind,
AttnBackendKind backend_kind)
: AttnBackendFunc(std::move(attn_func), attn_kind, backend_kind) {}
virtual void MHA(Tensor q, Tensor k, Tensor v, Tensor qo_indptr, Tensor kv_indptr,
Tensor q_rope_position, Tensor k_rope_pos_offset, bool causal,
RoPEMode rope_mode, double rotary_scale, double rotary_theta, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MHA computation is not supported by the current backend";
}
virtual void BeginForward(Tensor float_workspace_buffer, Tensor int_workspace_buffer,
Tensor page_locked_int_workspace_buffer, HostMemoryVector* qo_indptr,
HostMemoryVector* kv_indptr, int64_t batch_size, int64_t total_qo_len,
int64_t num_qo_heads, int64_t num_kv_heads, int64_t qk_head_dim,
int64_t v_head_dim, bool causal, TVMStreamHandle copy_stream) {
// Do nothing. Subclasses can override to customize behavior.
}
};
/*! \brief The TIR-based ragged prefill attention function class. */
class TIRRaggedPrefillFunc : public RaggedPrefillFunc {
public:
explicit TIRRaggedPrefillFunc(ffi::Function attn_func, AttnKind attn_kind)
: RaggedPrefillFunc(std::move(attn_func), attn_kind, AttnBackendKind::kTIR) {}
void MHA(Tensor q, Tensor k, Tensor v, Tensor qo_indptr, Tensor kv_indptr, Tensor q_rope_position,
Tensor k_rope_pos_offset, bool causal, RoPEMode rope_mode, double rotary_scale,
double rotary_theta, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) final {
attn_func_(q, qo_indptr, k, v, kv_indptr, q_rope_position, k_rope_pos_offset, attn_output,
attn_lse, static_cast<int64_t>(causal),
/*rotary_mode=*/static_cast<int64_t>(rope_mode == RoPEMode::kInline), rotary_scale,
rotary_theta, sm_scale);
}
};
/*! \brief The FlashInfer-based ragged prefill attention function class. */
class FlashInferRaggedPrefillFunc : public RaggedPrefillFunc {
public:
explicit FlashInferRaggedPrefillFunc(ffi::Function attn_func, ffi::Function plan_func,
AttnKind attn_kind, int64_t qk_head_dim_override = -1,
int64_t v_head_dim_override = -1)
: RaggedPrefillFunc(std::move(attn_func), attn_kind, AttnBackendKind::kFlashInfer),
plan_func_(std::move(plan_func)),
qk_head_dim_override_(qk_head_dim_override),
v_head_dim_override_(v_head_dim_override) {}
void MHA(Tensor q, Tensor k, Tensor v, Tensor qo_indptr, Tensor kv_indptr, Tensor q_rope_position,
Tensor k_rope_pos_offset, bool causal, RoPEMode rope_mode, double rotary_scale,
double rotary_theta, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) final {
double rope_rcp_scale = 1 / rotary_scale;
double rope_rcp_theta = 1 / rotary_theta;
attn_func_(float_workspace_buffer_, int_workspace_buffer_, plan_info_vec_, q, k, v,
ZeroByteOffsetView(qo_indptr), ZeroByteOffsetView(kv_indptr), attn_output, attn_lse,
/*mask_mode_code=*/static_cast<int64_t>(causal),
/*layout(NHD)=*/0, /*window_left=*/-1, /*enable_pdl=*/false, sm_scale,
/*rope_rcp_scale=*/rope_rcp_scale,
/*rope_rcp_theta=*/rope_rcp_theta);
}
void BeginForward(Tensor float_workspace_buffer, Tensor int_workspace_buffer,
Tensor page_locked_int_workspace_buffer, HostMemoryVector* qo_indptr,
HostMemoryVector* kv_indptr, int64_t batch_size, int64_t total_qo_len,
int64_t num_qo_heads, int64_t num_kv_heads, int64_t qk_head_dim,
int64_t v_head_dim, bool causal, TVMStreamHandle copy_stream) final {
// For MLA self-attention the ragged kernel operates on different head dims
// than the (compressed) MLA cache, so they are supplied per-function via the
// backend spec and override the cache-derived dims passed by the caller. MLA
// self-attention is full multi-head (one kv head per query head), unlike the
// single-head compressed cache, so the kv head count is overridden too.
if (qk_head_dim_override_ >= 0) qk_head_dim = qk_head_dim_override_;
if (v_head_dim_override_ >= 0) {
v_head_dim = v_head_dim_override_;
num_kv_heads = num_qo_heads;
}
// FlashInfer expects kv_len as an (int32) tensor rather than a shape tuple.
HostMemoryVector kv_len_arr(batch_size, DLDataType{kDLInt, 32, 1},
qo_indptr->as_tensor()->device);
for (int i = 0; i < static_cast<int>(batch_size); ++i) {
kv_len_arr.push_back(static_cast<int32_t>((*kv_indptr)[i + 1] - (*kv_indptr)[i]));
}
// Todo(tvm-team): enable cuda graph
float_workspace_buffer_ = float_workspace_buffer;
int_workspace_buffer_ = int_workspace_buffer;
page_locked_int_workspace_buffer_ = page_locked_int_workspace_buffer;
plan_info_vec_ =
plan_func_(float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
qo_indptr->as_tensor(), kv_indptr->as_tensor(), kv_len_arr.as_tensor(),
total_qo_len, batch_size, num_qo_heads, num_kv_heads, /*page_size=*/1,
/*enable_cuda_graph=*/false, qk_head_dim, v_head_dim, causal,
/*window_left=*/-1, /*fixed_split_size=*/-1, /*disable_split_kv=*/false,
/*num_colocated_ctas=*/0)
.cast<ffi::Array<int64_t>>();
}
private:
ffi::Function plan_func_;
Tensor float_workspace_buffer_;
Tensor int_workspace_buffer_;
Tensor page_locked_int_workspace_buffer_;
ffi::Array<int64_t> plan_info_vec_;
// MLA self-attention head dims supplied via the backend spec; -1 means use the
// dims passed by the caller (the regular MHA case).
int64_t qk_head_dim_override_ = -1;
int64_t v_head_dim_override_ = -1;
};
/*! \brief The paged decode attention function base class. */
class PagedDecodeFunc : public AttnBackendFunc {
public:
explicit PagedDecodeFunc(ffi::Function attn_func, AttnKind attn_kind,
AttnBackendKind backend_kind)
: AttnBackendFunc(std::move(attn_func), attn_kind, backend_kind) {}
virtual void MHA(int depth, Tensor q, Tensor pages, Tensor page_indptr, Tensor page_indices,
Tensor length_info, Tensor k_rope_pos_offset, Tensor q_rope_position,
RoPEMode rope_mode, double rotary_scale, double rotary_theta, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MHA computation is not supported by the current backend";
}
virtual void MLA(int depth, Tensor q, Tensor pages, Tensor page_indptr, Tensor page_indices,
Tensor length_info, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MLA computation is not supported by the current backend";
}
virtual void BeginForward(int depth, Tensor float_workspace_buffer, Tensor int_workspace_buffer,
Tensor page_locked_int_workspace_buffer, HostMemoryVector* page_indptr,
int64_t batch_size, int64_t page_size, int64_t num_qo_heads,
int64_t num_kv_heads, int64_t qk_head_dim, int64_t v_head_dim,
RoPEMode rope_mode, DLDataType q_dtype, DLDataType kv_dtype,
TVMStreamHandle copy_stream) {
// Do nothing. Subclasses can override to customize behavior.
}
};
/*! \brief The TIR-based paged decode attention function class. */
class TIRPagedDecodeFunc : public PagedDecodeFunc {
public:
explicit TIRPagedDecodeFunc(ffi::Function attn_func, AttnKind attn_kind)
: PagedDecodeFunc(std::move(attn_func), attn_kind, AttnBackendKind::kTIR) {}
void MHA(int depth, Tensor q, Tensor pages, Tensor page_indptr, Tensor page_indices,
Tensor length_info, Tensor k_rope_pos_offset, Tensor q_rope_position, RoPEMode rope_mode,
double rotary_scale, double rotary_theta, double sm_scale, Tensor attn_output,
Tensor attn_lse, TVMStreamHandle compute_stream) final {
attn_func_(q, pages, page_indptr, page_indices, length_info, k_rope_pos_offset, q_rope_position,
attn_output, attn_lse,
/*rotary_mode=*/static_cast<int64_t>(rope_mode == RoPEMode::kInline), rotary_scale,
rotary_theta, sm_scale);
}
void MLA(int depth, Tensor q, Tensor pages, Tensor page_indptr, Tensor page_indices,
Tensor length_info, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) final {
attn_func_(q, pages, page_indptr, page_indices, length_info, attn_output, attn_lse, sm_scale);
}
};
/*! \brief The FlashInfer-based paged decode attention function class. */
class FlashInferPagedDecodeFunc : public PagedDecodeFunc {
public:
explicit FlashInferPagedDecodeFunc(ffi::Function attn_func, ffi::Function plan_func,
AttnKind attn_kind)
: PagedDecodeFunc(std::move(attn_func), attn_kind, AttnBackendKind::kFlashInfer),
plan_func_(std::move(plan_func)) {}
void MHA(int depth, Tensor q, Tensor pages, Tensor page_indptr, Tensor page_indices,
Tensor length_info, Tensor k_rope_pos_offset, Tensor q_rope_position, RoPEMode rope_mode,
double rotary_scale, double rotary_theta, double sm_scale, Tensor attn_output,
Tensor attn_lse, TVMStreamHandle compute_stream) final {
auto [float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
plan_info_vec] = cached_buffers_[depth];
double rope_rcp_scale = 1 / rotary_scale;
double rope_rcp_theta = 1 / rotary_theta;
attn_func_(float_workspace_buffer, int_workspace_buffer, plan_info_vec, q,
PagedKVCacheView(pages, 0), PagedKVCacheView(pages, 1),
ZeroByteOffsetView(page_indptr), ZeroByteOffsetView(page_indices),
ZeroByteOffsetView(length_info), attn_output, attn_lse, /*kv_layout_code(HND)=*/1,
/*window_left=*/-1, /*enable_pdl=*/false, sm_scale,
/*rope_rcp_scale=*/rope_rcp_scale, /*rope_rcp_theta=*/rope_rcp_theta);
}
void BeginForward(int depth, Tensor float_workspace_buffer, Tensor int_workspace_buffer,
Tensor page_locked_int_workspace_buffer, HostMemoryVector* page_indptr,
int64_t batch_size, int64_t page_size, int64_t num_qo_heads,
int64_t num_kv_heads, int64_t qk_head_dim, int64_t v_head_dim,
RoPEMode rope_mode, DLDataType q_dtype, DLDataType kv_dtype,
TVMStreamHandle copy_stream) final {
// Todo(tvm-team): enable cuda graph
// FlashInfer's decode plan takes empty q/kv tensors (used only for dtype
// dispatch) instead of dtype scalars, adds a logits_soft_cap argument, and
// no longer takes the pos-encoding mode or an explicit stream.
DLDevice device = float_workspace_buffer->device;
Tensor empty_q_data = Tensor::Empty(ffi::Shape({0}), q_dtype, device);
Tensor empty_kv_data = Tensor::Empty(ffi::Shape({0}), kv_dtype, device);
ffi::Array<int64_t> plan_info_vec =
plan_func_(float_workspace_buffer, int_workspace_buffer, page_locked_int_workspace_buffer,
page_indptr->as_tensor(), batch_size, num_qo_heads, num_kv_heads, page_size,
/*enable_cuda_graph=*/false, /*window_left=*/-1, /*logits_soft_cap=*/0.0,
qk_head_dim, v_head_dim, empty_q_data, empty_kv_data)
.cast<ffi::Array<int64_t>>();
if (cached_buffers_.size() <= static_cast<size_t>(depth)) {
cached_buffers_.resize(depth + 1);
}
cached_buffers_[depth] =
std::make_tuple(float_workspace_buffer, int_workspace_buffer,
page_locked_int_workspace_buffer, std::move(plan_info_vec));
}
private:
ffi::Function plan_func_;
std::vector<std::tuple<Tensor, Tensor, Tensor, ffi::Array<int64_t>>> cached_buffers_;
};
/*! \brief The paged prefill with tree mask attention function base class. */
class PagedPrefillTreeMaskFunc : public AttnBackendFunc {
public:
explicit PagedPrefillTreeMaskFunc(ffi::Function attn_func, AttnKind attn_kind,
AttnBackendKind backend_kind)
: AttnBackendFunc(std::move(attn_func), attn_kind, backend_kind) {}
virtual void MHA(Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, Tensor k_rope_pos_offset,
Tensor q_rope_position, Tensor tree_attn_mn_indptr, Tensor tree_attn_mask,
RoPEMode rope_mode, double rotary_scale, double rotary_theta, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MHA computation is not supported by the current backend";
}
virtual void MLA(Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr,
Tensor page_indices, Tensor length_info, Tensor tree_attn_mn_indptr,
Tensor tree_attn_mask, double sm_scale, Tensor attn_output, Tensor attn_lse,
TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MLA computation is not supported by the current backend";
}
virtual void BeginForward(Tensor temp_float_attn_workspace, Tensor temp_int_attn_workspace,
HostMemoryVector* page_indptr, HostMemoryVector* last_page_len,
HostMemoryVector* qo_indptr, int64_t batch_size, int64_t page_size,
int64_t num_qo_heads, int64_t num_kv_heads, int64_t qk_head_dim,
int64_t v_head_dim, RoPEMode rope_mode, TVMStreamHandle copy_stream) {
// Do nothing. Subclasses can override to customize behavior.
}
};
/*! \brief The TIR-based paged prefill with tree mask attention function class. */
class TIRPagedPrefillTreeMaskFunc : public PagedPrefillTreeMaskFunc {
public:
explicit TIRPagedPrefillTreeMaskFunc(ffi::Function attn_func, AttnKind attn_kind)
: PagedPrefillTreeMaskFunc(std::move(attn_func), attn_kind, AttnBackendKind::kTIR) {}
void MHA(Tensor q, Tensor qo_indptr, Tensor pages, Tensor page_indptr, Tensor page_indices,
Tensor length_info, Tensor k_rope_pos_offset, Tensor q_rope_position,
Tensor tree_attn_mn_indptr, Tensor tree_attn_mask, RoPEMode rope_mode,
double rotary_scale, double rotary_theta, double sm_scale, Tensor attn_output,
Tensor attn_lse, TVMStreamHandle compute_stream) final {
attn_func_(q, qo_indptr, pages, page_indptr, page_indices, length_info, k_rope_pos_offset,
q_rope_position, attn_output, attn_lse,
/*rotary_mode=*/static_cast<int64_t>(rope_mode == RoPEMode::kInline), rotary_scale,
rotary_theta, sm_scale, tree_attn_mn_indptr, tree_attn_mask);
}
};
/*! \brief The ragged prefill with tree mask function base class. */
class RaggedPrefillTreeMaskFunc : public AttnBackendFunc {
public:
explicit RaggedPrefillTreeMaskFunc(ffi::Function attn_func, AttnKind attn_kind,
AttnBackendKind backend_kind)
: AttnBackendFunc(std::move(attn_func), attn_kind, backend_kind) {}
virtual void MHA(Tensor q, Tensor k, Tensor v, Tensor qo_indptr, Tensor kv_indptr,
Tensor q_rope_position, Tensor tree_attn_mn_indptr, Tensor tree_attn_mask,
RoPEMode rope_mode, double rotary_scale, double rotary_theta, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MHA computation is not supported by the current backend";
}
virtual void MLA(Tensor q, Tensor compressed_kv, Tensor k_pe, Tensor qo_indptr, Tensor kv_indptr,
Tensor tree_attn_mn_indptr, Tensor tree_attn_mask, double sm_scale,
Tensor attn_output, Tensor attn_lse, TVMStreamHandle compute_stream) {
TVM_FFI_THROW(InternalError) << "MLA computation is not supported by the current backend";
}
virtual void BeginForward(Tensor temp_float_attn_workspace, Tensor temp_int_attn_workspace,
HostMemoryVector* page_indptr, HostMemoryVector* last_page_len,
HostMemoryVector* qo_indptr, int64_t batch_size, int64_t page_size,
int64_t num_qo_heads, int64_t num_kv_heads, int64_t qk_head_dim,
int64_t v_head_dim, RoPEMode rope_mode, TVMStreamHandle copy_stream) {
// Do nothing. Subclasses can override to customize behavior.
}
};
/*! \brief The TIR-based ragged prefill with tree mask attention function class. */
class TIRRaggedPrefillTreeMaskFunc : public RaggedPrefillTreeMaskFunc {
public:
explicit TIRRaggedPrefillTreeMaskFunc(ffi::Function attn_func, AttnKind attn_kind)
: RaggedPrefillTreeMaskFunc(std::move(attn_func), attn_kind, AttnBackendKind::kTIR) {}
void MHA(Tensor q, Tensor k, Tensor v, Tensor qo_indptr, Tensor kv_indptr, Tensor q_rope_position,
Tensor tree_attn_mn_indptr, Tensor tree_attn_mask, RoPEMode rope_mode,
double rotary_scale, double rotary_theta, double sm_scale, Tensor attn_output,
Tensor attn_lse, TVMStreamHandle compute_stream) final {
attn_func_(q, qo_indptr, k, v, kv_indptr, q_rope_position, tree_attn_mn_indptr, tree_attn_mask,
attn_output, attn_lse,
/*rotary_mode=*/static_cast<int64_t>(rope_mode == RoPEMode::kInline), rotary_scale,
rotary_theta, sm_scale);
}
};
/*!
* \brief Create a PagedPrefillFunc from the given arguments and the attention kind.
* \param args The arguments that contains the backend kind and the runtime attention
* ffi::Functions. \param attn_kind The attention kind of the function. \return The created
* PagedPrefillFunc pointer.
*/
std::unique_ptr<PagedPrefillFunc> ConvertPagedPrefillFunc(ffi::Array<ffi::Any> args,
AttnKind attn_kind);
/*!
* \brief Create a PagedDecodeFunc from the given arguments and the attention kind.
* \param args The arguments that contains the backend kind and the runtime attention
* ffi::Functions. \param attn_kind The attention kind of the function. \return The created
* PagedDecodeFunc pointer.
*/
std::unique_ptr<PagedDecodeFunc> ConvertPagedDecodeFunc(ffi::Array<ffi::Any> args,
AttnKind attn_kind);
/*!
* \brief Create a RaggedPrefillFunc from the given arguments and the attention kind.
* \param args The arguments that contains the backend kind and the runtime attention
* ffi::Functions. \param attn_kind The attention kind of the function. \return The created
* RaggedPrefillFunc pointer.
*/
std::unique_ptr<RaggedPrefillFunc> ConvertRaggedPrefillFunc(ffi::Array<ffi::Any> args,
AttnKind attn_kind);
/*!
* \brief Create a PagedPrefillTreeMaskFunc from the given arguments and the attention kind.
* \param args The arguments that contains the backend kind and the runtime attention
* ffi::Functions. \param attn_kind The attention kind of the function. \return The created
* PagedPrefillTreeMaskFunc pointer.
*/
std::unique_ptr<PagedPrefillTreeMaskFunc> ConvertPagedPrefillTreeMaskFunc(ffi::Array<ffi::Any> args,
AttnKind attn_kind);
/*!
* \brief Create a RaggedPrefillTreeMaskFunc from the given arguments and the attention kind.
* \param args The arguments that contains the backend kind and the runtime attention
* ffi::Functions. \param attn_kind The attention kind of the function. \return The created
* RaggedPrefillTreeMaskFunc pointer.
*/
std::unique_ptr<RaggedPrefillTreeMaskFunc> ConvertRaggedPrefillTreeMaskFunc(
ffi::Array<ffi::Any> args, AttnKind attn_kind);
} // namespace vm
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_VM_ATTN_BACKEND_H_