187 lines
5.4 KiB
Python
187 lines
5.4 KiB
Python
"""Operators enabled by external modules."""
|
|
|
|
from typing import Optional
|
|
|
|
import tvm
|
|
from tvm.relax.frontend import nn
|
|
from tvm.relax.frontend.nn import Tensor, op
|
|
|
|
from mlc_llm.support import logging
|
|
|
|
from . import extern as _extern
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
WARN_FLASHINFER_GROUP_SIZE = False
|
|
WARN_FLASHINFER_HEAD_DIM = False
|
|
|
|
|
|
def attention(
|
|
q: nn.Tensor,
|
|
k: nn.Tensor,
|
|
v: nn.Tensor,
|
|
casual_mask: nn.Tensor,
|
|
attn_score_scaling_factor: float = 1.0,
|
|
qk_dtype: Optional[str] = None,
|
|
) -> nn.Tensor:
|
|
"""Attention with casual mask.
|
|
|
|
--- Variables ---
|
|
s: sequence length of the current query
|
|
t: total sequence length
|
|
d: head dimension
|
|
h, h_q: number of heads in query
|
|
h_kv: number of heads in key and value
|
|
b: batch size = 1
|
|
|
|
--- Shapes ---
|
|
q: [b, s, h_q, d]
|
|
k: [t, h_kv, d]
|
|
v: [t, h_kv, d]
|
|
o: [1, s, hidden = h_q * d]
|
|
|
|
--- Computation ---
|
|
|
|
.. code-block:: python
|
|
|
|
if h_kv != h_q:
|
|
k = k.repeat(h_q // h_kv, axis=1)
|
|
v = v.repeat(h_q // h_kv, axis=1)
|
|
q -> [b, h, s, d]
|
|
k, v -> [b, h, t, d]
|
|
attn = q @ k^T / sqrt(d) * attn_score_scaling_factor # [b, h, s, t]
|
|
attn = softmax_with_mask(attn, casual_mask, axis=-1)
|
|
o = attn @ v # [b, h, s, d]
|
|
o -> [b, s, h * d]
|
|
|
|
--- Other params ---
|
|
qk_dtype: if set, `matmul(Q, K, out_dtype=qk_dtype)`, (otherwise use `q.dtype` as `out_dtype`).
|
|
For FlashInfer, if "float32", sets `allow_fp16_qk_reduction` to False; otherwise no effect.
|
|
"""
|
|
assert q.ndim == 4 and k.ndim in [3, 4] and v.ndim in [3, 4]
|
|
b, s, h_q, d = q.shape
|
|
t, h_kv, _ = k.shape[-3:]
|
|
group_size = h_q // h_kv
|
|
|
|
def _fallback():
|
|
from tvm.relax.frontend.nn.llm.kv_cache import (
|
|
_attention_sequence_prefill,
|
|
)
|
|
|
|
nonlocal q, k, v, qk_dtype
|
|
if k.ndim == 3:
|
|
k = op.reshape(k, [b, t, h_kv, d])
|
|
if v.ndim == 3:
|
|
v = op.reshape(v, [b, t, h_kv, d])
|
|
if h_kv != h_q:
|
|
k = k.repeat(h_q // h_kv, axis=2)
|
|
v = v.repeat(h_q // h_kv, axis=2)
|
|
|
|
target = tvm.target.Target("cuda")
|
|
attn_output, _ = op.tensor_ir_op(
|
|
_attention_sequence_prefill(
|
|
h_kv=h_kv,
|
|
h_q=h_q,
|
|
d=d,
|
|
dtype=q.dtype,
|
|
target=target,
|
|
sm_scale=attn_score_scaling_factor / (d**0.5),
|
|
),
|
|
"sequence_prefill",
|
|
[q, k, v],
|
|
[
|
|
Tensor.placeholder([b, s, h_q, d], q.dtype),
|
|
Tensor.placeholder([b, s, h_q], q.dtype),
|
|
],
|
|
)
|
|
|
|
output = op.reshape(attn_output, shape=(b, s, h_q * d))
|
|
return output
|
|
|
|
# FlashInfer Implementation
|
|
if (
|
|
_extern.get_store().flashinfer
|
|
and attn_score_scaling_factor == 1.0
|
|
and q.dtype == "float16"
|
|
and k.dtype == "float16"
|
|
and v.dtype == "float16"
|
|
):
|
|
if group_size not in [1, 4, 6, 8]:
|
|
global WARN_FLASHINFER_GROUP_SIZE
|
|
if not WARN_FLASHINFER_GROUP_SIZE:
|
|
WARN_FLASHINFER_GROUP_SIZE = True
|
|
logger.warning(
|
|
"FlashInfer only supports group size in [1, 4, 6, 8], but got %d. Skip and "
|
|
"fallback to default implementation.",
|
|
group_size,
|
|
)
|
|
return _fallback()
|
|
if d not in [128]:
|
|
global WARN_FLASHINFER_HEAD_DIM
|
|
if not WARN_FLASHINFER_HEAD_DIM:
|
|
WARN_FLASHINFER_HEAD_DIM = True
|
|
logger.warning(
|
|
"FlashInfer only supports head_dim in [128], but got %d. Skip and fallback to "
|
|
"default implementation.",
|
|
d,
|
|
)
|
|
return _fallback()
|
|
rope_theta = 0.0
|
|
rope_scale = 1.0
|
|
qkv_layout = 0 # "NHD", N for seq_len, H for num_heads, D for head_dim
|
|
rotary_mode = 0 # "kNone"
|
|
casual = 1 # True
|
|
fp16_qk = 1 # True
|
|
if qk_dtype == "float32":
|
|
fp16_qk = 0 # False
|
|
|
|
# 32MB scratchpad
|
|
scratch = op.empty([8192 * 1024], dtype="float32")
|
|
|
|
def _decode():
|
|
return op.extern(
|
|
name="flashinfer.single_decode",
|
|
args=[
|
|
q,
|
|
k,
|
|
v,
|
|
scratch,
|
|
qkv_layout,
|
|
rotary_mode,
|
|
rope_scale,
|
|
rope_theta,
|
|
],
|
|
out=nn.Tensor.placeholder((b, s, h_q * d), dtype="float16"),
|
|
)
|
|
|
|
def _prefill():
|
|
return op.extern(
|
|
name="flashinfer.single_prefill",
|
|
args=[
|
|
q,
|
|
k,
|
|
v,
|
|
scratch,
|
|
casual,
|
|
qkv_layout,
|
|
rotary_mode,
|
|
fp16_qk,
|
|
rope_scale,
|
|
rope_theta,
|
|
],
|
|
out=nn.Tensor.placeholder((b, s, h_q * d), dtype="float16"),
|
|
)
|
|
|
|
if isinstance(s, int) and s == 1:
|
|
func = "decode"
|
|
else:
|
|
func = "prefill"
|
|
return {
|
|
"decode": _decode,
|
|
"prefill": _prefill,
|
|
}[func]()
|
|
|
|
# Fallback Implementation
|
|
return _fallback()
|