687 lines
32 KiB
Python
687 lines
32 KiB
Python
# 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.
|
|
# ruff: noqa: E501, RUF012
|
|
# fmt: off
|
|
|
|
"""Attention KV cache modeling.
|
|
|
|
This module exposes the public ``PagedKVCache`` classes (``FlashInferPagedKVCache``
|
|
and ``TIRPagedKVCache``). The kernel factories that build the underlying TIR
|
|
functions are split across sibling private modules:
|
|
|
|
- ``_kernel_common``: shared helpers (enums, RoPE, mask, tile allocators,
|
|
``@T.macro`` bundle, tiling config, scheduling).
|
|
- ``_page_kernels``: page management (append, debug, copy, compact).
|
|
- ``_prefill_kernels``: prefill attention kernels (paged/ragged/MLA/dense).
|
|
- ``_decode_kernels``: decode attention kernels and state-merge helpers.
|
|
|
|
The private-named kernel factories are re-exported from this module so the
|
|
test suite can continue to import them via ``tvm.relax.frontend.nn.llm.kv_cache``.
|
|
"""
|
|
|
|
# pylint: disable=too-many-statements,too-many-arguments,invalid-name,line-too-long
|
|
import math
|
|
from typing import Any, Literal
|
|
|
|
import tvm
|
|
from tvm import relax as rx
|
|
from tvm import tirx
|
|
from tvm.relax.frontend.nn import Object, Tensor
|
|
from tvm.target import Target
|
|
|
|
# Re-export enums + kernel factories so existing ``from kv_cache import ...``
|
|
# users (test suite, tree_attn.py, mlc-llm, etc.) continue to work after the
|
|
# split. These names are referenced in ``__all__`` below to signal to linters
|
|
# that the imports are intentional public API (not dead code).
|
|
from ._decode_kernels import (
|
|
_attention_decode,
|
|
_attention_decode_cpu,
|
|
_merge_state_inplace,
|
|
_merge_state_inplace_cpu,
|
|
)
|
|
from ._kernel_common import AttnKind, RopeMode
|
|
from ._page_kernels import (
|
|
_compact_kv_copy,
|
|
_compact_kv_copy_cpu,
|
|
_copy_single_page,
|
|
_copy_single_page_cpu,
|
|
_copy_single_page_mla,
|
|
_kv_cache_debug_get_kv,
|
|
_kv_cache_debug_get_kv_mla,
|
|
_kv_cache_transpose_append,
|
|
_kv_cache_transpose_append_mla,
|
|
)
|
|
from ._prefill_kernels import (
|
|
_attention_prefill,
|
|
_attention_prefill_cpu,
|
|
_attention_prefill_mla,
|
|
_attention_prefill_ragged,
|
|
_attention_prefill_ragged_cpu,
|
|
_attention_sequence_prefill,
|
|
_attention_sequence_prefill_with_mask,
|
|
)
|
|
from .position_embedding import llama_rope_with_position_map
|
|
from .tree_attn import (
|
|
tree_attn,
|
|
tree_attn_cpu,
|
|
tree_attn_with_paged_kv_cache,
|
|
tree_attn_with_paged_kv_cache_cpu,
|
|
)
|
|
|
|
__all__ = [
|
|
"AttnKind",
|
|
"FlashInferPagedKVCache",
|
|
"PagedKVCache",
|
|
"RopeMode",
|
|
"TIRPagedKVCache",
|
|
"_attention_decode",
|
|
"_attention_decode_cpu",
|
|
"_attention_prefill",
|
|
"_attention_prefill_cpu",
|
|
"_attention_prefill_mla",
|
|
"_attention_prefill_ragged",
|
|
"_attention_prefill_ragged_cpu",
|
|
"_attention_sequence_prefill",
|
|
"_attention_sequence_prefill_with_mask",
|
|
"_compact_kv_copy",
|
|
"_compact_kv_copy_cpu",
|
|
"_copy_single_page",
|
|
"_copy_single_page_cpu",
|
|
"_copy_single_page_mla",
|
|
"_kv_cache_debug_get_kv",
|
|
"_kv_cache_debug_get_kv_mla",
|
|
"_kv_cache_transpose_append",
|
|
"_kv_cache_transpose_append_mla",
|
|
"_merge_state_inplace",
|
|
"_merge_state_inplace_cpu",
|
|
"llama_rope_with_position_map",
|
|
"tree_attn",
|
|
"tree_attn_cpu",
|
|
"tree_attn_with_paged_kv_cache",
|
|
"tree_attn_with_paged_kv_cache_cpu",
|
|
]
|
|
|
|
|
|
class PagedKVCache(Object): # pylint: disable=too-few-public-methods
|
|
"""The Paged KV Cache used in LLM batching for efficient attention computation."""
|
|
|
|
extern_mods: list[tvm.runtime.Module] = []
|
|
|
|
def attention_with_fused_qkv(
|
|
self,
|
|
layer_id: int,
|
|
qkv: Tensor,
|
|
num_qo_heads: int,
|
|
sm_scale: float,
|
|
) -> Tensor:
|
|
"""Compute attention with the given fused q/k/v data and in-cache k/v data
|
|
on the specified layer. Rotary position embeddings are applied to k/v
|
|
within this function.
|
|
|
|
- For prefill, the input qkv and output tensor have shape
|
|
(1, total_seq_len) for the first two dimensions.
|
|
- For decode, the input qkv and output tensor have shape
|
|
(batch_size, 1) for the first two dimensions.
|
|
- The input qkv have `2 * num_qo_heads + num_kv_heads` at the third dim.
|
|
- The output tensor have `num_qo_heads` at the third dim.
|
|
- The input qkv and output tensor have `head_dim` at the last dim.
|
|
"""
|
|
# pylint: disable=protected-access
|
|
b, s, _, d = qkv._expr.ty.shape
|
|
qkv = qkv.reshape(b * s, qkv.shape[2], d)
|
|
return Tensor(
|
|
_expr=rx.BlockBuilder.current().emit(
|
|
rx.call_dps_packed(
|
|
"vm.builtin.attention_kv_cache_attention_with_fused_qkv",
|
|
[
|
|
self._expr,
|
|
rx.prim_value(layer_id), # type: ignore[arg-type]
|
|
rx.prim_value(sm_scale),
|
|
qkv._expr,
|
|
],
|
|
out_ty=rx.TensorType((b * s, num_qo_heads, d), qkv.dtype),
|
|
)
|
|
)
|
|
).reshape(b, s, num_qo_heads, d)
|
|
|
|
def self_attention( # pylint: disable=too-many-locals
|
|
self,
|
|
layer_id: int,
|
|
q: Tensor,
|
|
k: Tensor,
|
|
v: Tensor,
|
|
sm_scale: float,
|
|
) -> tuple[Tensor, Tensor]:
|
|
"""Fine-grained API that computes ragged self attention with Q/K/V data."""
|
|
# pylint: disable=protected-access
|
|
b, s, h_qo, d_qk = q._expr.ty.shape
|
|
_, _, h_kv, d_v = v._expr.ty.shape
|
|
q = q.reshape(b * s, h_qo, d_qk)
|
|
k = k.reshape(b * s, h_kv, d_qk)
|
|
v = v.reshape(b * s, h_kv, d_v)
|
|
bb = rx.BlockBuilder.current()
|
|
attn_results = bb.emit(
|
|
rx.call_dps_packed(
|
|
"vm.builtin.attention_kv_cache_self_attention",
|
|
[
|
|
self._expr,
|
|
rx.prim_value(layer_id), # type: ignore[arg-type]
|
|
rx.prim_value(sm_scale),
|
|
q._expr,
|
|
k._expr,
|
|
v._expr,
|
|
],
|
|
out_ty=[
|
|
rx.TensorType((b * s, h_qo, d_v), q.dtype),
|
|
rx.TensorType((b * s, h_qo), "float32"),
|
|
],
|
|
)
|
|
)
|
|
assert isinstance(attn_results.ty, rx.TupleType)
|
|
assert len(attn_results.ty.fields) == 2
|
|
o = Tensor(_expr=bb.emit(rx.TupleGetItem(attn_results, 0))).reshape(b, s, h_qo, d_v)
|
|
lse = Tensor(_expr=bb.emit(rx.TupleGetItem(attn_results, 1))).reshape(b, s, h_qo)
|
|
return o, lse
|
|
|
|
def cross_attention(
|
|
self,
|
|
layer_id: int,
|
|
q: Tensor,
|
|
v_head_dim: int,
|
|
sm_scale: float,
|
|
) -> tuple[Tensor, Tensor]:
|
|
"""Fine-grained API that computes paged cross attention with Q and in-cache KV data."""
|
|
# pylint: disable=protected-access
|
|
b, s, h_qo, d_qk = q._expr.ty.shape
|
|
q = q.reshape(b * s, h_qo, d_qk)
|
|
bb = rx.BlockBuilder.current()
|
|
attn_results = bb.emit(
|
|
rx.call_dps_packed(
|
|
"vm.builtin.attention_kv_cache_cross_attention",
|
|
[
|
|
self._expr,
|
|
rx.prim_value(layer_id), # type: ignore[arg-type]
|
|
rx.prim_value(sm_scale),
|
|
q._expr,
|
|
],
|
|
out_ty=[
|
|
rx.TensorType((b * s, h_qo, v_head_dim), q.dtype),
|
|
rx.TensorType((b * s, h_qo), "float32"),
|
|
],
|
|
)
|
|
)
|
|
assert isinstance(attn_results.ty, rx.TupleType)
|
|
assert len(attn_results.ty.fields) == 2
|
|
o = Tensor(_expr=bb.emit(rx.TupleGetItem(attn_results, 0))).reshape(b, s, h_qo, v_head_dim)
|
|
lse = Tensor(_expr=bb.emit(rx.TupleGetItem(attn_results, 1))).reshape(b, s, h_qo)
|
|
return o, lse
|
|
|
|
def append_mla_kv(self, layer_id: int, kv: Tensor) -> "PagedKVCache":
|
|
"""Fine-grained API that appends the MLA K/V data to KV cache."""
|
|
# pylint: disable=protected-access
|
|
b, s, _, d_qk = kv._expr.ty.shape
|
|
kv = kv.reshape(b * s, d_qk)
|
|
return PagedKVCache(
|
|
_expr=rx.call_pure_packed(
|
|
"vm.builtin.attention_kv_cache_append_mla_kv",
|
|
self._expr,
|
|
rx.prim_value(layer_id), # type: ignore[arg-type]
|
|
kv._expr,
|
|
ty_args=rx.AnyType(),
|
|
),
|
|
_name="paged_kv_cache",
|
|
)
|
|
|
|
def merge_attn_output_inplace(
|
|
self,
|
|
o_self_attn: Tensor,
|
|
lse_self_attn: Tensor,
|
|
o_cross_attn: Tensor,
|
|
lse_cross_attn: Tensor,
|
|
) -> tuple[Tensor, Tensor]:
|
|
"""Fine-grained API that merges the attention output from two sources.
|
|
The first two tensors will be inplace updated.
|
|
"""
|
|
# pylint: disable=protected-access
|
|
b, s, h_qo, d_v = o_self_attn._expr.ty.shape
|
|
o_self_attn = o_self_attn.reshape(b * s, h_qo, d_v)
|
|
lse_self_attn = lse_self_attn.reshape(b * s, h_qo)
|
|
o_cross_attn = o_cross_attn.reshape(b * s, h_qo, d_v)
|
|
lse_cross_attn = lse_cross_attn.reshape(b * s, h_qo)
|
|
bb = rx.BlockBuilder.current()
|
|
merge_results = bb.emit(
|
|
rx.call_pure_packed(
|
|
"vm.builtin.attention_kv_cache_merge_attn_output_inplace",
|
|
self._expr,
|
|
o_self_attn._expr,
|
|
lse_self_attn._expr,
|
|
o_cross_attn._expr,
|
|
lse_cross_attn._expr,
|
|
ty_args=rx.TupleType(
|
|
[o_self_attn._expr.ty, lse_self_attn._expr.ty]
|
|
),
|
|
)
|
|
)
|
|
assert isinstance(merge_results.ty, rx.TupleType)
|
|
assert len(merge_results.ty.fields) == 2
|
|
o_self_attn = Tensor(_expr=bb.emit(rx.TupleGetItem(merge_results, 0))).reshape(
|
|
b, s, h_qo, d_v
|
|
)
|
|
lse_self_attn = Tensor(_expr=bb.emit(rx.TupleGetItem(merge_results, 1))).reshape(b, s, h_qo)
|
|
return o_self_attn, lse_self_attn
|
|
|
|
def get_query_positions(self, total_length: tirx.Expr) -> Tensor:
|
|
"""Get the in-sequence positions of each slot in the query,
|
|
which are needed for applying positional embeddings in some models.
|
|
|
|
Parameters
|
|
----------
|
|
total_length : tirx.Expr
|
|
The summed-up total sequence length of queries in
|
|
the batch being forwarded.
|
|
|
|
Returns
|
|
-------
|
|
q_positions : Tensor
|
|
The in-sequence query positions, in shape `(total_length,)`
|
|
"""
|
|
return Tensor(
|
|
_expr=rx.BlockBuilder.current().emit(
|
|
rx.call_pure_packed(
|
|
"vm.builtin.attention_kv_cache_get_query_positions",
|
|
self._expr,
|
|
ty_args=rx.TensorType((total_length,), "int32"),
|
|
)
|
|
)
|
|
)
|
|
|
|
# pylint: enable=protected-access
|
|
|
|
|
|
def _prepare_yarn_rope_scaling(rope_scaling: dict[str, Any] | None, rope_theta: float | None) -> dict[str, Any] | None:
|
|
"""Ensure Yarn-specific scaling configs include the theta metadata."""
|
|
if rope_scaling is None:
|
|
return None
|
|
if rope_scaling.get("rope_type") != "yarn":
|
|
return rope_scaling
|
|
|
|
rope_scaling_updated = dict(rope_scaling)
|
|
if "inv_theta_log_scale" not in rope_scaling_updated and rope_theta is not None:
|
|
theta_value = float(rope_theta)
|
|
rope_scaling_updated["inv_theta_log_scale"] = 1.0 / (2 * math.log(theta_value))
|
|
return rope_scaling_updated
|
|
|
|
|
|
class FlashInferPagedKVCache(PagedKVCache): # pylint: disable=too-few-public-methods
|
|
"""Paged KV cache using FlashInfer (CUDA) kernels."""
|
|
|
|
def __init__( # pylint: disable=too-many-locals
|
|
self,
|
|
attn_kind: Literal["mha", "mla"] | list[Literal["mha", "mla", "mha_sliding"]],
|
|
max_batch_size: tirx.Var,
|
|
max_total_seq_len: tirx.Var,
|
|
prefill_chunk_size: tirx.Var,
|
|
page_size: tirx.Var,
|
|
support_sliding_window: tirx.Var,
|
|
layer_partition: rx.ShapeExpr,
|
|
num_hidden_layers: int,
|
|
num_attention_heads: int,
|
|
num_key_value_heads: int,
|
|
qk_head_dim: int,
|
|
v_head_dim: int,
|
|
mla_original_qk_head_dim: int,
|
|
mla_original_v_head_dim: int,
|
|
rope_mode: RopeMode,
|
|
rope_scale: int,
|
|
rope_theta: int,
|
|
rope_scaling: dict[str, Any],
|
|
rope_ext_factors: rx.Expr,
|
|
rotary_dim: int,
|
|
enable_disaggregation: bool,
|
|
dtype: str,
|
|
target: Target,
|
|
name: str = "paged_kv_cache",
|
|
) -> None:
|
|
"""Create a paged KV cache object with FlashInfer kernels.
|
|
|
|
Parameters
|
|
----------
|
|
max_batch_size : tirx.Var
|
|
The maximum allowed batch size of the KV cache.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
max_total_seq_len : tirx.Var
|
|
The maximum allowed total sequence length of the KV cache.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
prefill_chunk_size : tirx.Var
|
|
The maximum total sequence length in a prefill.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
page_size : tirx.Var
|
|
The size (a.k.a. number of tokens) of each page.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
support_sliding_window : tirx.Var
|
|
0 or 1, denoting whether the KV cache supports sliding window.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
layer_partition : rx.ShapeExpr
|
|
The KV cache layer partition for pipeline stages.
|
|
It is an indptr array, denoting the starting layer of each pipeline stage.
|
|
rope_mode : RopeMode
|
|
The RoPE mode of the Paged KV cache.
|
|
If it is normal, RoPE will be applied to k before adding k to cache.
|
|
Otherwise, RoPE will be applied to q/k in attention kernel on-the-fly.
|
|
rope_scale : int
|
|
The scale of rotary position embedding.
|
|
rope_theta : int
|
|
The base of rotary position embedding.
|
|
rope_scaling: Dict[str, Any]
|
|
The RoPE scaling information dict.
|
|
rope_ext_factors: rx.Expr
|
|
The RoPE extension factors when "longrope" mode RoPE scaling is enabled.
|
|
rotary_dim : int
|
|
The number of dimensions in the embedding that RoPE is applied to.
|
|
enable_disaggregation : bool
|
|
Whether to enable disaggregation in the KV cache.
|
|
"""
|
|
assert rope_mode != RopeMode.INLINE, "FlashInfer RoPE does not support inline mode."
|
|
rope_scaling = _prepare_yarn_rope_scaling(rope_scaling, rope_theta)
|
|
|
|
attn_kind_single = attn_kind[0] if isinstance(attn_kind, list) else attn_kind
|
|
if attn_kind_single == "mha_sliding":
|
|
attn_kind_single = "mha"
|
|
flashinfer_prefill_mods = rx.backend.cuda.flashinfer.gen_flashinfer_prefill_module(
|
|
dtype_q=dtype,
|
|
dtype_kv=dtype,
|
|
dtype_o=dtype,
|
|
qk_head_dim=(qk_head_dim if attn_kind_single == "mha" else mla_original_qk_head_dim),
|
|
v_head_dim=(v_head_dim if attn_kind_single == "mha" else mla_original_v_head_dim),
|
|
enable_inline_rope=False,
|
|
return_static_libs=True,
|
|
)
|
|
flashinfer_decode_mods = (
|
|
rx.backend.cuda.flashinfer.gen_flashinfer_decode_module(
|
|
dtype_q=dtype,
|
|
dtype_kv=dtype,
|
|
dtype_o=dtype,
|
|
qk_head_dim=qk_head_dim,
|
|
v_head_dim=v_head_dim,
|
|
enable_inline_rope=False,
|
|
return_static_libs=True,
|
|
)
|
|
if attn_kind_single == "mha"
|
|
else []
|
|
)
|
|
flashinfer_mla_mods = (
|
|
rx.backend.cuda.flashinfer.gen_flashinfer_mla_module(
|
|
dtype_q=dtype,
|
|
dtype_kv=dtype,
|
|
dtype_o=dtype,
|
|
head_dim_ckv=v_head_dim,
|
|
head_dim_kpe=qk_head_dim - v_head_dim,
|
|
return_static_libs=True,
|
|
)
|
|
if attn_kind_single == "mla"
|
|
else []
|
|
)
|
|
self.extern_mods = flashinfer_prefill_mods + flashinfer_decode_mods + flashinfer_mla_mods
|
|
|
|
bb = rx.BlockBuilder.current()
|
|
mha_functions = (
|
|
[
|
|
rx.Tuple([rx.StringImm("flashinfer"), rx.ExternFunc("batch_prefill_paged_run"), rx.ExternFunc("batch_prefill_plan")]),
|
|
rx.Tuple([rx.StringImm("flashinfer"), rx.ExternFunc("batch_decode_run"), rx.ExternFunc("batch_decode_plan")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, True, rope_scaling, target), "tir_attention_prefill_sliding_window")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_decode(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, True, rope_scaling, target), "tir_attention_decode_sliding_window")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(tree_attn_with_paged_kv_cache(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, rope_scaling, target), "tir_attention_prefill_with_tree_mask_with_paged_kv_cache")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(tree_attn(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, rope_scaling, target), "tir_attention_prefill_with_tree_mask")]),
|
|
]
|
|
if attn_kind_single == "mha"
|
|
else [rx.Tuple([]) for _ in range(6)]
|
|
)
|
|
ragged_prefill_function = rx.Tuple([rx.StringImm("flashinfer"), rx.ExternFunc("batch_prefill_ragged_run"), rx.ExternFunc("batch_prefill_plan")]) if attn_kind_single == "mha" else rx.Tuple([rx.StringImm("flashinfer"), rx.ExternFunc("batch_prefill_ragged_run"), rx.ExternFunc("batch_prefill_plan"), rx.prim_value(mla_original_qk_head_dim), rx.prim_value(mla_original_v_head_dim)])
|
|
mla_function = rx.Tuple([rx.StringImm("flashinfer"), rx.ExternFunc("batch_mla_run"), rx.ExternFunc("batch_mla_plan")] if attn_kind_single == "mla" else [])
|
|
attn_merge_functions = [
|
|
bb.add_func(_merge_state_inplace(num_attention_heads, v_head_dim, dtype, target, "tir_attention_merge_state"), "tir_attention_merge_state"),
|
|
]
|
|
if attn_kind_single == "mla":
|
|
attn_merge_functions.append(bb.add_func(_merge_state_inplace(num_attention_heads, mla_original_v_head_dim, dtype, target, "tir_attention_merge_state_mla"), "tir_attention_merge_state_mla"))
|
|
|
|
if isinstance(attn_kind, list):
|
|
attn_kind = [int(getattr(AttnKind, layer_kind.upper())) for layer_kind in attn_kind]
|
|
else:
|
|
attn_kind = [int(getattr(AttnKind, attn_kind.upper())) for _ in range(num_hidden_layers)]
|
|
|
|
args = [
|
|
rx.ShapeExpr(
|
|
[
|
|
max_batch_size,
|
|
max_total_seq_len,
|
|
prefill_chunk_size,
|
|
page_size,
|
|
support_sliding_window,
|
|
]
|
|
),
|
|
layer_partition,
|
|
rx.prim_value(num_attention_heads),
|
|
rx.prim_value(num_key_value_heads),
|
|
rx.prim_value(qk_head_dim),
|
|
rx.prim_value(v_head_dim),
|
|
rx.ShapeExpr(attn_kind),
|
|
rx.prim_value(enable_disaggregation),
|
|
rx.prim_value(rope_mode),
|
|
rx.prim_value(rope_scale),
|
|
rx.prim_value(rope_theta),
|
|
rope_ext_factors,
|
|
rx.op.zeros((), dtype),
|
|
bb.add_func(_kv_cache_transpose_append(num_key_value_heads, qk_head_dim, dtype), "kv_cache_transpose_append"),
|
|
bb.add_func(_kv_cache_transpose_append_mla(qk_head_dim, dtype), "kv_cache_transpose_append_mla"),
|
|
ragged_prefill_function,
|
|
*mha_functions,
|
|
mla_function,
|
|
rx.Tuple(attn_merge_functions),
|
|
bb.add_func(llama_rope_with_position_map(rope_theta, rope_scale, qk_head_dim, num_attention_heads, num_key_value_heads, dtype, rope_scaling, rotary_dim), "tir_split_rotary"),
|
|
bb.add_func(_copy_single_page(num_key_value_heads, page_size, qk_head_dim, dtype, target) if attn_kind_single == "mha" else _copy_single_page_mla(page_size, qk_head_dim, dtype, target), "kv_cache_copy_single_page"),
|
|
bb.add_func(_kv_cache_debug_get_kv(num_hidden_layers, num_key_value_heads, qk_head_dim, dtype), "kv_cache_debug_get_kv"),
|
|
bb.add_func(_compact_kv_copy(num_key_value_heads, qk_head_dim, dtype, target), "kv_cache_compact_kv_copy"),
|
|
]
|
|
super().__init__(
|
|
_expr=rx.call_pure_packed(
|
|
"vm.builtin.paged_attention_kv_cache_create",
|
|
*args,
|
|
ty_args=rx.AnyType(),
|
|
),
|
|
_name=name,
|
|
)
|
|
|
|
|
|
class TIRPagedKVCache(PagedKVCache): # pylint: disable=too-few-public-methods
|
|
"""Paged KV cache using TIR kernels."""
|
|
|
|
def __init__( # pylint: disable=too-many-locals
|
|
self,
|
|
attn_kind: Literal["mha", "mla"] | list[Literal["mha", "mla", "mha_sliding"]],
|
|
max_batch_size: tirx.Var,
|
|
max_total_seq_len: tirx.Var,
|
|
prefill_chunk_size: tirx.Var,
|
|
page_size: tirx.Var,
|
|
support_sliding_window: tirx.Var,
|
|
layer_partition: rx.ShapeExpr,
|
|
num_hidden_layers: int,
|
|
num_attention_heads: int,
|
|
num_key_value_heads: int,
|
|
qk_head_dim: int,
|
|
v_head_dim: int,
|
|
mla_original_qk_head_dim: int,
|
|
mla_original_v_head_dim: int,
|
|
rope_mode: RopeMode,
|
|
rope_scale: int,
|
|
rope_theta: int,
|
|
rope_scaling: dict[str, Any],
|
|
rope_ext_factors: rx.Expr,
|
|
rotary_dim: int,
|
|
enable_disaggregation: bool,
|
|
dtype: str,
|
|
target: Target,
|
|
name: str = "paged_kv_cache",
|
|
) -> None:
|
|
"""Create a paged KV cache object with TIR kernels.
|
|
|
|
Parameters
|
|
----------
|
|
max_batch_size : tirx.Var
|
|
The maximum allowed batch size of the KV cache.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
max_total_seq_len : tirx.Var
|
|
The maximum allowed total sequence length of the KV cache.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
prefill_chunk_size : tirx.Var
|
|
The maximum total sequence length in a prefill.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
page_size : tirx.Var
|
|
The size (a.k.a. number of tokens) of each page.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
support_sliding_window : tirx.Var
|
|
0 or 1, denoting whether the KV cache supports sliding window.
|
|
It is a symbolic variable whose concrete value is specified
|
|
at runtime.
|
|
layer_partition : rx.ShapeExpr
|
|
The KV cache layer partition for pipeline stages.
|
|
It is an indptr array, denoting the starting layer of each pipeline stage.
|
|
rope_mode : RopeMode
|
|
The RoPE mode of the Paged KV cache.
|
|
If it is normal, RoPE will be applied to k before adding k to cache.
|
|
Otherwise, RoPE will be applied to q/k in attention kernel on-the-fly.
|
|
rope_scale : int
|
|
The scale of rotary position embedding.
|
|
rope_theta : int
|
|
The base of rotary position embedding.
|
|
rope_scaling: Dict[str, Any]
|
|
The RoPE scaling information dict.
|
|
rope_ext_factors: rx.Expr
|
|
The RoPE extension factors when "longrope" mode RoPE scaling is enabled.
|
|
rotary_dim : int
|
|
The number of dimensions in the embedding that RoPE is applied to.
|
|
enable_disaggregation : bool
|
|
Whether to enable disaggregation in the KV cache.
|
|
target : Target
|
|
The target to build the model to.
|
|
"""
|
|
rope_scaling = _prepare_yarn_rope_scaling(rope_scaling, rope_theta)
|
|
attn_kind_single = attn_kind[0] if isinstance(attn_kind, list) else attn_kind
|
|
if attn_kind_single == "mha_sliding":
|
|
attn_kind_single = "mha"
|
|
if isinstance(attn_kind, list):
|
|
attn_kind = [int(getattr(AttnKind, layer_kind.upper())) for layer_kind in attn_kind]
|
|
else:
|
|
attn_kind = [int(getattr(AttnKind, attn_kind.upper())) for _ in range(num_hidden_layers)]
|
|
bb = rx.BlockBuilder.current()
|
|
args = [
|
|
rx.ShapeExpr(
|
|
[
|
|
max_batch_size,
|
|
max_total_seq_len,
|
|
prefill_chunk_size,
|
|
page_size,
|
|
support_sliding_window,
|
|
]
|
|
),
|
|
layer_partition,
|
|
rx.prim_value(num_attention_heads),
|
|
rx.prim_value(num_key_value_heads),
|
|
rx.prim_value(qk_head_dim),
|
|
rx.prim_value(v_head_dim),
|
|
rx.ShapeExpr(attn_kind),
|
|
rx.prim_value(enable_disaggregation),
|
|
rx.prim_value(rope_mode),
|
|
rx.prim_value(rope_scale),
|
|
rx.prim_value(rope_theta),
|
|
rope_ext_factors,
|
|
rx.op.zeros((), dtype),
|
|
bb.add_func(_kv_cache_transpose_append(num_key_value_heads, qk_head_dim, dtype), "kv_cache_transpose_append"),
|
|
bb.add_func(_kv_cache_transpose_append_mla(qk_head_dim, dtype), "kv_cache_transpose_append_mla"),
|
|
]
|
|
|
|
if target.kind.name == "llvm":
|
|
if attn_kind_single == "mla":
|
|
raise ValueError("MLA is not supported in TIR kernels for now.")
|
|
args.extend(
|
|
[
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill_ragged_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, v_head_dim, dtype, rope_scaling), "tir_attention_prefill_ragged_cpu")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, False, rope_scaling), "tir_attention_prefill_cpu")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_decode_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, False, rope_scaling), "tir_attention_decode_cpu")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, True, rope_scaling), "tir_attention_prefill_cpu_sliding_window")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_decode_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, True, rope_scaling), "tir_attention_decode_cpu_sliding_window")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(tree_attn_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, rope_scaling), "tir_attention_prefill_with_tree_mask_cpu")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(tree_attn_with_paged_kv_cache_cpu(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, rope_scaling), "tir_attention_prefill_with_tree_mask_with_paged_kv_cache_cpu")]),
|
|
rx.Tuple([]), # f_mla_prefill
|
|
rx.Tuple([bb.add_func(_merge_state_inplace_cpu(dtype), "tir_attention_merge_state_cpu")]),
|
|
bb.add_func(llama_rope_with_position_map(rope_theta, rope_scale, qk_head_dim, num_attention_heads, num_key_value_heads, dtype, rope_scaling, rotary_dim), "tir_split_rotary"),
|
|
bb.add_func(_copy_single_page_cpu(num_key_value_heads, page_size, qk_head_dim, dtype), "kv_cache_copy_single_page_cpu"),
|
|
bb.add_func(_kv_cache_debug_get_kv(num_hidden_layers, num_key_value_heads, qk_head_dim, dtype), "kv_cache_debug_get_kv"),
|
|
bb.add_func(_compact_kv_copy_cpu(num_key_value_heads, qk_head_dim, dtype), "kv_cache_compact_kv_copy_cpu"),
|
|
]
|
|
)
|
|
else:
|
|
ragged_qk_head_dim = qk_head_dim if attn_kind_single == "mha" else mla_original_qk_head_dim
|
|
ragged_v_head_dim = v_head_dim if attn_kind_single == "mha" else mla_original_v_head_dim
|
|
args.append(rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill_ragged(num_key_value_heads if attn_kind_single == "mha" else num_attention_heads, num_attention_heads, ragged_qk_head_dim, ragged_v_head_dim, dtype, rope_scaling, target), "tir_attention_prefill_ragged")]))
|
|
mha_functions = (
|
|
[
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, False, rope_scaling, target), "tir_attention_prefill")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_decode(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, False, rope_scaling, target), "tir_attention_decode")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, True, rope_scaling, target), "tir_attention_prefill_sliding_window")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_decode(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, True, rope_scaling, target), "tir_attention_decode_sliding_window")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(tree_attn_with_paged_kv_cache(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, rope_scaling, target), "tir_attention_prefill_with_tree_mask_with_paged_kv_cache")]),
|
|
rx.Tuple([rx.StringImm("tirx"), bb.add_func(tree_attn(num_key_value_heads, num_attention_heads, qk_head_dim, dtype, rope_scaling, target), "tir_attention_prefill_with_tree_mask")]),
|
|
]
|
|
if attn_kind_single == "mha"
|
|
else [rx.Tuple([]) for _ in range(6)]
|
|
)
|
|
mla_function = rx.Tuple([rx.StringImm("tirx"), bb.add_func(_attention_prefill_mla(num_attention_heads, v_head_dim, qk_head_dim - v_head_dim, dtype, False, target), "tir_attention_prefill_mla")] if attn_kind_single == "mla" else [])
|
|
attn_merge_functions = [
|
|
bb.add_func(_merge_state_inplace(num_attention_heads, v_head_dim, dtype, target, "tir_attention_merge_state"), "tir_attention_merge_state"),
|
|
]
|
|
if attn_kind_single == "mla":
|
|
attn_merge_functions.append(bb.add_func(_merge_state_inplace(num_attention_heads, mla_original_v_head_dim, dtype, target, "tir_attention_merge_state_mla"), "tir_attention_merge_state_mla"))
|
|
args.extend(mha_functions)
|
|
args.append(mla_function)
|
|
args.extend(
|
|
[
|
|
rx.Tuple(attn_merge_functions),
|
|
bb.add_func(llama_rope_with_position_map(rope_theta, rope_scale, qk_head_dim, num_attention_heads, num_key_value_heads, dtype, rope_scaling, rotary_dim), "tir_split_rotary"),
|
|
bb.add_func(_copy_single_page(num_key_value_heads, page_size, qk_head_dim, dtype, target) if attn_kind_single == "mha" else _copy_single_page_mla(page_size, qk_head_dim, dtype, target), "kv_cache_copy_single_page"),
|
|
bb.add_func(_kv_cache_debug_get_kv(num_hidden_layers, num_key_value_heads, qk_head_dim, dtype), "kv_cache_debug_get_kv"),
|
|
bb.add_func(_compact_kv_copy(num_key_value_heads, qk_head_dim, dtype, target), "kv_cache_compact_kv_copy"),
|
|
]
|
|
)
|
|
|
|
super().__init__(
|
|
_expr=rx.call_pure_packed(
|
|
"vm.builtin.paged_attention_kv_cache_create",
|
|
*args,
|
|
ty_args=rx.AnyType(),
|
|
),
|
|
_name=name,
|
|
)
|