205 lines
5.8 KiB
Python
205 lines
5.8 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Standard
|
|
from typing import Any, Callable, Dict, Optional
|
|
|
|
# Third Party
|
|
from vllm.model_executor.layers.rotary_embedding import get_rope as vllm_get_rope
|
|
import torch
|
|
|
|
# First Party
|
|
from lmcache import torch_device_type
|
|
from lmcache.logging import init_logger
|
|
import lmcache.c_ops as lmc_ops
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
# TODO(Jiayi): Add and test more types of rope
|
|
# (e.g., rope scaling, (non-)neox style, dtype, etc.)
|
|
|
|
|
|
class BasicReverseRope:
|
|
def __init__(self, rope, rotary_dim, is_neox_style):
|
|
self.rope = rope
|
|
self.rotary_dim = rotary_dim
|
|
self.is_neox_style = is_neox_style
|
|
|
|
def do_shuffle(self, t):
|
|
original_shape = t.shape
|
|
t = t.reshape(t.shape[0], -1, self.rotary_dim)
|
|
|
|
if self.is_neox_style:
|
|
o1, o2 = torch.chunk(t, 2, dim=-1)
|
|
else:
|
|
o1 = t[..., ::2]
|
|
o2 = t[..., 1::2]
|
|
|
|
if self.is_neox_style:
|
|
return torch.cat((o2, o1), dim=-1).reshape(original_shape)
|
|
else:
|
|
return torch.stack((o2, o1), dim=-1).reshape(original_shape)
|
|
|
|
def reverse_encode(self, positions, q, k):
|
|
sq = self.do_shuffle(q)
|
|
sk = self.do_shuffle(k)
|
|
nq, nk = self.rope(positions, sq, sk)
|
|
fq = self.do_shuffle(nq)
|
|
fk = self.do_shuffle(nk)
|
|
return fq, fk
|
|
|
|
def __call__(self, positions, q, k):
|
|
return self.reverse_encode(positions, q, k)
|
|
|
|
|
|
class FusedRope:
|
|
"""
|
|
Directly use the fused kernel to ratate K cache from
|
|
the old positions to the new positions.
|
|
"""
|
|
|
|
def __init__(self, rope, is_neox_style):
|
|
self.rope = rope
|
|
self.is_neox_style = is_neox_style
|
|
self.head_size = rope.head_size
|
|
self.cos_sin_cache = rope.cos_sin_cache
|
|
|
|
def fused_encode(self, old_positions, new_positions, k):
|
|
num_tokens = k.shape[0]
|
|
k = k.view(num_tokens, -1, self.head_size)
|
|
lmc_ops.rotary_embedding_k_fused(
|
|
old_positions,
|
|
new_positions,
|
|
k,
|
|
self.head_size,
|
|
self.cos_sin_cache.to(k.device),
|
|
self.is_neox_style,
|
|
)
|
|
k = k.view(num_tokens, -1)
|
|
return k
|
|
|
|
def __call__(self, old_positions, new_positions, k):
|
|
return self.fused_encode(old_positions, new_positions, k)
|
|
|
|
|
|
def validate_rope_params(
|
|
head_size: int,
|
|
rotary_dim: int,
|
|
max_position: int,
|
|
base: float,
|
|
is_neox_style: bool = True,
|
|
rope_scaling: Optional[Dict[str, Any]] = None,
|
|
dtype: Optional[torch.dtype] = None,
|
|
partial_rotary_factor: float = 1.0,
|
|
):
|
|
if rotary_dim != head_size:
|
|
logger.error("Currently KV blending only support rotary_dim == head_size.")
|
|
return False
|
|
|
|
if rope_scaling is not None:
|
|
logger.error("Currently KV blending do not support rope scaling.")
|
|
return False
|
|
|
|
if partial_rotary_factor != 1.0:
|
|
logger.error(
|
|
"Currently KV blending do not support rotary factor other than 1.0."
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def validate_reverse_correctness(rope, reverse_rope, fused_rope, head_size) -> bool:
|
|
hidden_dim = head_size * 8
|
|
num_tokens = 10
|
|
|
|
dumb_q = torch.rand(
|
|
(num_tokens, hidden_dim), device=torch_device_type, dtype=torch.bfloat16
|
|
)
|
|
dumb_k = torch.rand(
|
|
(num_tokens, hidden_dim), device=torch_device_type, dtype=torch.bfloat16
|
|
)
|
|
positions = torch.arange(num_tokens, device=torch_device_type)
|
|
|
|
q1 = dumb_q.clone()
|
|
k1 = dumb_k.clone()
|
|
q1, k1 = rope(positions, q1, k1)
|
|
q1, k1 = reverse_rope(positions, q1, k1)
|
|
|
|
max_q_error = (dumb_q - q1).abs().max()
|
|
max_k_error = (dumb_k - k1).abs().max()
|
|
|
|
logger.info(f"Max Q error: {max_q_error.item()}")
|
|
logger.info(f"Max K error: {max_k_error.item()}")
|
|
|
|
q_no_pos = dumb_q.clone()
|
|
k_no_pos = dumb_k.clone()
|
|
positions2 = torch.arange(100, 100 + num_tokens, device=torch_device_type)
|
|
_, k_pos2 = rope(positions2, q_no_pos, k_no_pos)
|
|
|
|
k_no_pos = dumb_k.clone()
|
|
_, k_pos1 = rope(positions, q_no_pos, k_no_pos)
|
|
k_pos2_fused = fused_rope(positions, positions2, k_pos1)
|
|
|
|
max_k_error_fused = (k_pos2 - k_pos2_fused).abs().max()
|
|
|
|
logger.info(f"Max K error (fused): {max_k_error.item()}")
|
|
|
|
return max_q_error < 0.1 and max_k_error < 0.1 and max_k_error_fused < 0.1
|
|
|
|
|
|
# Main interface
|
|
def get_fused_rope(
|
|
head_size: int,
|
|
rotary_dim: int,
|
|
max_position: int,
|
|
base: float,
|
|
is_neox_style: bool = True,
|
|
rope_scaling: Optional[Dict[str, Any]] = None,
|
|
dtype: Optional[torch.dtype] = None,
|
|
partial_rotary_factor: float = 1.0,
|
|
) -> Optional[Callable[..., Any]]:
|
|
# Validate the ROPE parameters
|
|
if not validate_rope_params(
|
|
head_size,
|
|
rotary_dim,
|
|
max_position,
|
|
base,
|
|
is_neox_style,
|
|
rope_scaling,
|
|
dtype,
|
|
partial_rotary_factor,
|
|
):
|
|
logger.warning(
|
|
"The rope parameters is not supported! Cannot use cacheblend in this case"
|
|
)
|
|
return None
|
|
|
|
new_rope_params = {
|
|
"rope_theta": base,
|
|
"partial_rotary_factor": partial_rotary_factor,
|
|
}
|
|
if rope_scaling is not None:
|
|
new_rope_params.update(rope_scaling)
|
|
if "type" in rope_scaling:
|
|
new_rope_params["rope_type"] = rope_scaling["type"]
|
|
|
|
rope = vllm_get_rope(
|
|
head_size=head_size,
|
|
max_position=max_position,
|
|
is_neox_style=is_neox_style,
|
|
rope_parameters=new_rope_params,
|
|
dtype=dtype,
|
|
dual_chunk_attention_config=None,
|
|
)
|
|
|
|
reverse_rope = BasicReverseRope(rope, rotary_dim, is_neox_style)
|
|
fused_rope = FusedRope(rope, is_neox_style)
|
|
|
|
correct = validate_reverse_correctness(rope, reverse_rope, fused_rope, head_size)
|
|
if not correct:
|
|
logger.error(
|
|
"Fused/reverse rotary encoding is not correct! Will disable blending!"
|
|
)
|
|
return None
|
|
|
|
return fused_rope
|