# 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. """Operators for positional embeddings, e.g. RoPE.""" import math from collections.abc import Callable from functools import partial from typing import Any from tvm import tirx from tvm.relax.frontend.nn import Tensor, op from tvm.script import tirx as T # pylint: disable=invalid-name def rope_freq_default(s: tirx.Var, d: tirx.Var, d_range: int, theta: float, dtype: str): """Compute the inverse frequency of RoPE and then return the cosine and sine of it. Parameters ---------- s : tirx.Var The position index. d : tirx.Var The dimension index. d_range : int The maximum dimension index. theta : float The theta value in RoPE, which controls the frequency. dtype : str The data type of the output. Returns ------- cos_freq : Tensor The cosine of the inverse frequency. sin_freq : Tensor The sine of the inverse frequency. var_map: Dict[tirx.Var, tirx.Expr] The common expression map. """ freq = s / tirx.power(theta, d * 2 % d_range / tirx.const(d_range, "float32")) freq_var = tirx.Var("freq", "float32") cos_freq = tirx.cos(freq_var).astype(dtype) sin_freq = tirx.sin(freq_var).astype(dtype) return cos_freq, sin_freq, {freq_var: freq} def rope_freq_gptj(s: tirx.Var, d: tirx.Var, d_range: int, theta: float, dtype: str): """Compute the inverse frequency of RoPE for gptj RoPE scaling.""" freq = s / tirx.power(theta, 2 * (d // 2) % d_range / tirx.const(d_range, "float32")) freq_var = tirx.Var("freq", "float32") cos_freq = tirx.cos(freq_var).astype(dtype) sin_freq = tirx.sin(freq_var).astype(dtype) return cos_freq, sin_freq, {freq_var: freq} def rope_freq_llama4( # pylint: disable=too-many-arguments,too-many-locals s: tirx.Var, d: tirx.Var, d_range: int, theta: float, dtype: str, factor: float, low_freq_factor: float, high_freq_factor: float, original_max_position_embeddings: float, ): """Compute the inverse frequency of RoPE for llama4 RoPE scaling.""" orig_freq = tirx.const(1, "float32") / tirx.power( theta, 2 * (d // 2) / tirx.const(d_range, "float32") ) orig_freq_var = tirx.Var("orig_freq", "float32") llama4_inv_scaling_factor = 1.0 / factor if high_freq_factor == low_freq_factor: wavelength = tirx.const(2 * math.pi, "float32") / orig_freq_var threshold_wavelen = tirx.const( original_max_position_embeddings / low_freq_factor, "float32" ) scaled_freq = tirx.if_then_else( wavelength > threshold_wavelen, orig_freq_var / factor, orig_freq_var ) smoothed_freq = s * scaled_freq else: # Original smooth interpolation logic inv_diff_freq_factor = 1.0 / (high_freq_factor - low_freq_factor) llama4_alpha = original_max_position_embeddings / (2 * math.pi) * inv_diff_freq_factor llama4_beta = low_freq_factor * inv_diff_freq_factor smooth = tirx.max(0.0, tirx.min(1.0, llama4_alpha * orig_freq_var - llama4_beta)) smoothed_freq = s * ( (1.0 - smooth) * orig_freq_var * llama4_inv_scaling_factor + smooth * orig_freq_var ) smoothed_freq_var = tirx.Var("smoothed_freq", "float32") cos_freq = tirx.cos(smoothed_freq_var).astype(dtype) sin_freq = tirx.sin(smoothed_freq_var).astype(dtype) return ( cos_freq, sin_freq, {smoothed_freq_var: smoothed_freq, orig_freq_var: orig_freq}, ) def rope_freq_llama3( # pylint: disable=too-many-arguments,too-many-locals s: tirx.Var, d: tirx.Var, d_range: int, theta: float, dtype: str, factor: float, low_freq_factor: float, high_freq_factor: float, original_max_position_embeddings: float, ): """Compute the inverse frequency of RoPE for llama3 RoPE scaling.""" orig_freq = tirx.const(1, "float32") / tirx.power( theta, d * 2 % d_range / tirx.const(d_range, "float32") ) orig_freq_var = tirx.Var("orig_freq", "float32") inv_diff_freq_factor = 1.0 / (high_freq_factor - low_freq_factor) llama3_inv_scaling_factor = 1.0 / factor llama3_alpha = original_max_position_embeddings / (2 * math.pi) * inv_diff_freq_factor llama3_beta = low_freq_factor * inv_diff_freq_factor smooth = tirx.max(0.0, tirx.min(1.0, llama3_alpha * orig_freq_var - llama3_beta)) smoothed_freq = s * ( (1.0 - smooth) * orig_freq_var * llama3_inv_scaling_factor + smooth * orig_freq_var ) smoothed_freq_var = tirx.Var("smoothed_freq", "float32") cos_freq = tirx.cos(smoothed_freq_var).astype(dtype) sin_freq = tirx.sin(smoothed_freq_var).astype(dtype) return ( cos_freq, sin_freq, {smoothed_freq_var: smoothed_freq, orig_freq_var: orig_freq}, ) def rope_freq_longrope( # pylint: disable=too-many-arguments s: tirx.Var, d: tirx.Var, d_range: int, theta: float, dtype: str, max_position_embeddings: int, original_max_position_embeddings: int, ext_factors: T.Buffer | None = None, ): """Compute the inverse frequency of RoPE for longrope scaling.""" scale = max_position_embeddings / original_max_position_embeddings scaling_factor = ( math.sqrt(1 + math.log(scale) / math.log(original_max_position_embeddings)) if scale > 1.0 else 1.0 ) divisor = tirx.power(theta, d * 2 % d_range / tirx.const(d_range, "float32")) if ext_factors is not None: divisor = ext_factors[d % (d_range // 2)] * divisor freq = s / divisor freq_var = tirx.Var("freq", "float32") cos_freq = (tirx.cos(freq_var) * scaling_factor).astype(dtype) sin_freq = (tirx.sin(freq_var) * scaling_factor).astype(dtype) return cos_freq, sin_freq, {freq_var: freq} def yarn_find_correction_dim( num_rotations: int, d: tirx.Var, max_position_embeddings: int, inv_theta_log_scale: float | tirx.Expr | None = None, ): """Inverse dim formula to find dim based on number of rotations""" return ( d * math.log(max_position_embeddings / (num_rotations * 2 * math.pi)) * inv_theta_log_scale ) def yarn_find_correction_range( low_rot: int, high_rot: int, d: tirx.Var, max_position_embeddings: int, inv_theta_log_scale: float | tirx.Expr | None = None, ): """Find the correction range based on the number of rotations""" low = yarn_find_correction_dim( low_rot, d, max_position_embeddings, inv_theta_log_scale=inv_theta_log_scale ) high = yarn_find_correction_dim( high_rot, d, max_position_embeddings, inv_theta_log_scale=inv_theta_log_scale ) return tirx.max(low, 0), tirx.min(high, d - 1) def rope_freq_yarn( s: tirx.Var, d: tirx.Var, d_range: int, theta: float | tirx.Expr, dtype: str, original_max_position_embeddings: int, scaling_factor: float, beta_fast: int, beta_slow: int, inv_theta_log_scale: float | tirx.Expr | None = None, ): # pylint: disable=too-many-arguments, too-many-locals """Compute the inverse frequency of RoPE for yarn RoPE scaling.""" exponent = d * 2 % d_range / tirx.const(d_range, "float32") freq_power = tirx.power(theta, exponent) freq_extra = tirx.const(1, "float32") / freq_power freq_inter = tirx.const(1, "float32") / (scaling_factor * freq_power) low, high = yarn_find_correction_range( beta_fast, beta_slow, d_range, original_max_position_embeddings, inv_theta_log_scale=inv_theta_log_scale, ) high = tirx.if_then_else(low == high, high + 0.001, high) inv_freq_mask = tirx.const(1, "float32") - tirx.max( tirx.min((d - low) / (high - low), 1.0), 0.0 ).astype("float32") inv_freq = freq_inter * (1 - inv_freq_mask) + freq_extra * inv_freq_mask freq = s * inv_freq freq_var = tirx.Var("freq", "float32") cos_freq = tirx.cos(freq_var).astype(dtype) sin_freq = tirx.sin(freq_var).astype(dtype) return cos_freq, sin_freq, {freq_var: freq} def switch_rope_freq_func(rope_scaling: dict[str, Any]) -> Callable: """Return the RoPE inverse frequency computation function based on the given RoPE scaling. """ if "rope_type" not in rope_scaling: return rope_freq_default if rope_scaling["rope_type"] == "gptj": return rope_freq_gptj if rope_scaling["rope_type"] == "llama3": return partial( rope_freq_llama3, factor=rope_scaling["factor"], low_freq_factor=rope_scaling["low_freq_factor"], high_freq_factor=rope_scaling["high_freq_factor"], original_max_position_embeddings=rope_scaling["original_max_position_embeddings"], ) if rope_scaling["rope_type"] == "llama4": return partial( rope_freq_llama4, factor=rope_scaling["factor"], low_freq_factor=rope_scaling["low_freq_factor"], high_freq_factor=rope_scaling["high_freq_factor"], original_max_position_embeddings=rope_scaling["original_max_position_embeddings"], ) if rope_scaling["rope_type"] == "longrope": return partial( rope_freq_longrope, max_position_embeddings=rope_scaling["max_position_embeddings"], original_max_position_embeddings=rope_scaling["original_max_position_embeddings"], ) if rope_scaling["rope_type"] == "yarn": inv_theta_log_scale = rope_scaling.get("inv_theta_log_scale") assert inv_theta_log_scale is not None, "inv_theta_log_scale must be precomputed for YaRN" return partial( rope_freq_yarn, original_max_position_embeddings=rope_scaling["original_max_position_embeddings"], scaling_factor=rope_scaling["factor"], beta_fast=rope_scaling["beta_fast"], beta_slow=rope_scaling["beta_slow"], inv_theta_log_scale=inv_theta_log_scale, ) raise ValueError(f"Unsupported RoPE scaling type: {rope_scaling['rope_type']}") # mypy: disable-error-code="attr-defined" def llama_rope( # pylint: disable=too-many-arguments qkv: Tensor, total_seq_len: tirx.Var, theta: float, scale: float, num_q_heads: int, num_kv_heads: int, rope_scaling: dict[str, Any], rotary_dim: int | None = None, ) -> tuple[Tensor, Tensor, Tensor]: """Llama-style RoPE. Given a fused QKV tensor, it returns three tensors, Q, K, and V, where Q and K are rotated by RoPE while V remains unchanged. Parameters ---------- qkv : Tensor The fused QKV tensor of shape: [batch_size, seq_len, #q_heads + #kv_heads * 2, head_dim] total_seq_len : tirx.Var The total sequence length after being concatenated with KVCache. It is used to compute the offset of RoPE. theta : float The theta value, or "base" in RoPE, which controls the frequency. scale : float The RoPE scaling factor. num_q_heads : int The number of query heads. num_kv_heads : int The number of key/value heads. It differs from `num_q_heads` in group-query attention. rope_scaling : Dict The configuration of RoPE scaling. rotary_dim : Optional[int] The number of dimensions in the embedding that RoPE is applied to. By default, the rotary_dim is the same as head_dim. Returns ------- q : Tensor The query tensor of shape [batch_size, seq_len, #q_heads, head_dim] w/ RoPE applied k : Tensor The key tensor of shape [batch_size, seq_len, #kv_heads, head_dim] w/ RoPE applied v : Tensor The value tensor of shape [batch_size, seq_len, #kv_heads, head_dim] w/o RoPE applied """ _, _, fused_heads, head_dim = qkv.shape assert fused_heads == num_q_heads + num_kv_heads * 2 if rotary_dim is None: rotary_dim = head_dim dtype = qkv.dtype scale = tirx.const(scale, dtype) def _rope( # pylint: disable=too-many-arguments x: T.Buffer, b: tirx.Var, s: tirx.Var, h: tirx.Var, d: tirx.Var, offset: tirx.Var, ): cos_freq, sin_freq, var_map = switch_rope_freq_func(rope_scaling)( (s + offset) * scale, d, rotary_dim, theta, dtype ) cos = cos_freq * x[b, s, h, d] if rope_scaling["rope_type"] == "gptj": sin = sin_freq * tirx.if_then_else( d % 2 == 0, -x[b, s, h, d + 1], x[b, s, h, d - 1], ) else: sin = sin_freq * tirx.if_then_else( d < rotary_dim // 2, -x[b, s, h, d + rotary_dim // 2], x[b, s, h, d - rotary_dim // 2], ) expr = cos + sin for var, value in var_map.items(): expr = tirx.Let(var, value, expr) return expr @T.prim_func(private=True, s_tir=True) def fused_rope( # pylint: disable=too-many-locals var_qkv: T.handle, var_q: T.handle, var_k: T.handle, var_v: T.handle, total_seq_len: T.int64, ): T.func_attr( { "op_pattern": 8, # 2 means injective, 8 means opaque "tirx.noalias": True, } ) batch_size = T.int64() seq_len = T.int64() qkv = T.match_buffer(var_qkv, (batch_size, seq_len, fused_heads, head_dim), dtype) q = T.match_buffer(var_q, (batch_size, seq_len, num_q_heads, head_dim), dtype) k = T.match_buffer(var_k, (batch_size, seq_len, num_kv_heads, head_dim), dtype) v = T.match_buffer(var_v, (batch_size, seq_len, num_kv_heads, head_dim), dtype) for iters in T.grid(batch_size, seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): b, s, h, d = T.axis.remap("SSSS", iters) if h < num_q_heads: q[b, s, h, d] = T.if_then_else( d < rotary_dim, _rope(qkv, b, s, h, d, total_seq_len - seq_len), qkv[b, s, h, d], ) elif h < num_q_heads + num_kv_heads: k[b, s, h - num_q_heads, d] = T.if_then_else( d < rotary_dim, _rope(qkv, b, s, h, d, total_seq_len - seq_len), qkv[b, s, h, d], ) else: v[b, s, h - (num_q_heads + num_kv_heads), d] = qkv[b, s, h, d] b, s, _, _ = qkv.shape return op.tensor_ir_op( # pylint: disable=no-member fused_rope, "llama_rope", args=[qkv, total_seq_len], out=( Tensor.placeholder((b, s, num_q_heads, head_dim), dtype), Tensor.placeholder((b, s, num_kv_heads, head_dim), dtype), Tensor.placeholder((b, s, num_kv_heads, head_dim), dtype), ), ) def llama_rope_with_position_map( # pylint: disable=too-many-arguments theta: float, scale: float, head_dim: int, num_q_heads: int, num_kv_heads: int, dtype: str, rope_scaling: dict[str, Any], rotary_dim: int | None = None, ): """Return the TIR function that computes Llama-style RoPE with q position map. Parameters ---------- theta : float The theta value, or "base" in RoPE, which controls the frequency. scale : float The RoPE scaling factor. head_dim : int The number of features on each head. num_q_heads : int The number of query heads. num_kv_heads : int The number of key/value heads. It differs from `num_q_heads` in group-query attention. dtype : str The dtype of qkv data. rope_scaling : Dict The configuration of RoPE scaling. rotary_dim : int The number of dimensions in the embedding that RoPE is applied to. By default, the rotary_dim is the same as head_dim. """ fused_heads = num_q_heads + num_kv_heads * 2 if rotary_dim is None: rotary_dim = head_dim scale = tirx.const(scale, "float32") is_longrope_scaling = rope_scaling.get("rope_type") == "longrope" if is_longrope_scaling and "original_max_position_embeddings" in rope_scaling: original_max_position_embeddings = rope_scaling["original_max_position_embeddings"] else: original_max_position_embeddings = 0 def _rope( # pylint: disable=too-many-arguments x: T.Buffer, s: tirx.Var, h: tirx.Var, d: tirx.Var, pos: tirx.Var, ext_factors: T.Buffer | None = None, ): kwargs = {} if ext_factors: kwargs["ext_factors"] = ext_factors cos_freq, sin_freq, var_map = switch_rope_freq_func(rope_scaling)( pos * scale, d, rotary_dim, theta, "float32", **kwargs ) cos = cos_freq * x[s, h, d].astype("float32") if "rope_type" in rope_scaling and rope_scaling["rope_type"] == "gptj": sin = sin_freq * tirx.if_then_else( d % 2 == 0, -x[s, h, d + 1], x[s, h, d - 1], ).astype("float32") else: sin = sin_freq * tirx.if_then_else( d < rotary_dim // 2, -x[s, h, d + rotary_dim // 2], x[s, h, d - rotary_dim // 2], ).astype("float32") expr = (cos + sin).astype(dtype) for var, value in var_map.items(): expr = tirx.Let(var, value, expr) return expr @T.prim_func(s_tir=True) def fused_rope( # pylint: disable=too-many-locals var_qkv: T.handle, var_position_map: T.handle, var_q: T.handle, var_k: T.handle, var_v: T.handle, apply_rope: T.int64, ): T.func_attr( { "op_pattern": 8, # 2 means injective, 8 means opaque "tirx.noalias": True, } ) seq_len = T.int32() position_map_elem_offset = T.int32() qkv = T.match_buffer(var_qkv, (seq_len, fused_heads, head_dim), dtype) q = T.match_buffer(var_q, (seq_len, num_q_heads, head_dim), dtype) k = T.match_buffer(var_k, (seq_len, num_kv_heads, head_dim), dtype) v = T.match_buffer(var_v, (seq_len, num_kv_heads, head_dim), dtype) position_map = T.match_buffer( var_position_map, (seq_len,), "int32", elem_offset=position_map_elem_offset ) for iters in T.grid(seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): s, h, d = T.axis.remap("SSS", iters) if h < num_q_heads: q[s, h, d] = T.if_then_else( apply_rope > 0 and d < rotary_dim, _rope(qkv, s, h, d, position_map[s]), qkv[s, h, d], ) elif h < num_q_heads + num_kv_heads: k[s, h - num_q_heads, d] = T.if_then_else( apply_rope > 0 and d < rotary_dim, _rope(qkv, s, h, d, position_map[s]), qkv[s, h, d], ) else: v[s, h - (num_q_heads + num_kv_heads), d] = qkv[s, h, d] @T.prim_func(s_tir=True) def fused_rope_longrope_scaling( # pylint: disable=too-many-locals var_qkv: T.handle, var_position_map: T.handle, var_q: T.handle, var_k: T.handle, var_v: T.handle, ext_factors: T.Buffer((rotary_dim,), "float32"), # type: ignore ): T.func_attr( { "op_pattern": 8, # 2 means injective, 8 means opaque "tirx.noalias": True, } ) seq_len = T.int64() position_map_elem_offset = T.int64() qkv = T.match_buffer(var_qkv, (seq_len, fused_heads, head_dim), dtype) q = T.match_buffer(var_q, (seq_len, num_q_heads, head_dim), dtype) k = T.match_buffer(var_k, (seq_len, num_kv_heads, head_dim), dtype) v = T.match_buffer(var_v, (seq_len, num_kv_heads, head_dim), dtype) position_map = T.match_buffer( var_position_map, (seq_len,), "int32", elem_offset=position_map_elem_offset ) # long factors is the first half, short factors is the second half long_factors = T.decl_buffer((rotary_dim // 2,), "float32", data=ext_factors.data) short_factors = T.decl_buffer( (rotary_dim // 2,), "float32", data=ext_factors.data, elem_offset=(rotary_dim // 2), ) if seq_len > original_max_position_embeddings: for iters in T.grid(seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): s, h, d = T.axis.remap("SSS", iters) if h < num_q_heads: q[s, h, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], long_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) elif h < num_q_heads + num_kv_heads: k[s, h - num_q_heads, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], long_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) else: v[s, h - (num_q_heads + num_kv_heads), d] = qkv[s, h, d] else: for iters in T.grid(seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): s, h, d = T.axis.remap("SSS", iters) if h < num_q_heads: q[s, h, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], short_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) elif h < num_q_heads + num_kv_heads: k[s, h - num_q_heads, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], short_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) else: v[s, h - (num_q_heads + num_kv_heads), d] = qkv[s, h, d] if is_longrope_scaling: return fused_rope_longrope_scaling return fused_rope def llama4_rope_with_position_map( # pylint: disable=too-many-arguments theta: float, scale: float, head_dim: int, num_q_heads: int, num_kv_heads: int, dtype: str, rope_scaling: dict[str, Any], rotary_dim: int | None = None, ): """Return the TIR function that computes Llama-style RoPE with q position map. Parameters ---------- theta : float The theta value, or "base" in RoPE, which controls the frequency. scale : float The RoPE scaling factor. head_dim : int The number of features on each head. num_q_heads : int The number of query heads. num_kv_heads : int The number of key/value heads. It differs from `num_q_heads` in group-query attention. dtype : str The dtype of qkv data. rope_scaling : Dict The configuration of RoPE scaling. rotary_dim : int The number of dimensions in the embedding that RoPE is applied to. By default, the rotary_dim is the same as head_dim. """ fused_heads = num_q_heads + num_kv_heads * 2 if rotary_dim is None: rotary_dim = head_dim scale = tirx.const(scale, "float32") is_longrope_scaling = rope_scaling.get("rope_type") == "longrope" if is_longrope_scaling and "original_max_position_embeddings" in rope_scaling: original_max_position_embeddings = rope_scaling["original_max_position_embeddings"] else: original_max_position_embeddings = 0 def _rope( # pylint: disable=too-many-arguments x: T.Buffer, s: tirx.Var, h: tirx.Var, d: tirx.Var, pos: tirx.Var, ext_factors: T.Buffer | None = None, ): kwargs = {} if ext_factors: kwargs["ext_factors"] = ext_factors cos_freq, sin_freq, var_map = switch_rope_freq_func(rope_scaling)( pos * scale, d, rotary_dim, theta, "float32", **kwargs ) cos = cos_freq * x[s, h, d].astype("float32") if "rope_type" in rope_scaling and rope_scaling["rope_type"] == "gptj": sin = sin_freq * tirx.if_then_else( d % 2 == 0, -x[s, h, d + 1], x[s, h, d - 1], ).astype("float32") else: # Data layout is different for llama4 vs llama3 sin = sin_freq * tirx.if_then_else( d % 2 == 0, -x[s, h, d + 1], x[s, h, d - 1], ).astype("float32") expr = (cos + sin).astype(dtype) for var, value in var_map.items(): expr = tirx.Let(var, value, expr) return expr @T.prim_func(private=True, s_tir=True) def fused_rope( # pylint: disable=too-many-locals var_qkv: T.handle, var_position_map: T.handle, var_q: T.handle, var_k: T.handle, var_v: T.handle, apply_rope: T.int64, ): T.func_attr( { "op_pattern": 8, # 2 means injective, 8 means opaque "tirx.noalias": True, } ) seq_len = T.int32() position_map_elem_offset = T.int32() qkv = T.match_buffer(var_qkv, (seq_len, fused_heads, head_dim), dtype) q = T.match_buffer(var_q, (seq_len, num_q_heads, head_dim), dtype) k = T.match_buffer(var_k, (seq_len, num_kv_heads, head_dim), dtype) v = T.match_buffer(var_v, (seq_len, num_kv_heads, head_dim), dtype) position_map = T.match_buffer( var_position_map, (seq_len,), "int32", elem_offset=position_map_elem_offset ) for iters in T.grid(seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): s, h, d = T.axis.remap("SSS", iters) if h < num_q_heads: q[s, h, d] = T.if_then_else( apply_rope > 0 and d < rotary_dim, _rope(qkv, s, h, d, position_map[s]), qkv[s, h, d], ) elif h < num_q_heads + num_kv_heads: k[s, h - num_q_heads, d] = T.if_then_else( apply_rope > 0 and d < rotary_dim, _rope(qkv, s, h, d, position_map[s]), qkv[s, h, d], ) else: v[s, h - (num_q_heads + num_kv_heads), d] = qkv[s, h, d] @T.prim_func(s_tir=True) def fused_rope_longrope_scaling( # pylint: disable=too-many-locals var_qkv: T.handle, var_position_map: T.handle, var_q: T.handle, var_k: T.handle, var_v: T.handle, ext_factors: T.Buffer((rotary_dim,), "float32"), # type: ignore ): T.func_attr( { "op_pattern": 8, # 2 means injective, 8 means opaque "tirx.noalias": True, } ) seq_len = T.int64() position_map_elem_offset = T.int64() qkv = T.match_buffer(var_qkv, (seq_len, fused_heads, head_dim), dtype) q = T.match_buffer(var_q, (seq_len, num_q_heads, head_dim), dtype) k = T.match_buffer(var_k, (seq_len, num_kv_heads, head_dim), dtype) v = T.match_buffer(var_v, (seq_len, num_kv_heads, head_dim), dtype) position_map = T.match_buffer( var_position_map, (seq_len,), "int32", elem_offset=position_map_elem_offset ) # long factors is the first half, short factors is the second half long_factors = T.decl_buffer((rotary_dim // 2,), "float32", data=ext_factors.data) short_factors = T.decl_buffer( (rotary_dim // 2,), "float32", data=ext_factors.data, elem_offset=(rotary_dim // 2), ) if seq_len > original_max_position_embeddings: for iters in T.grid(seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): s, h, d = T.axis.remap("SSS", iters) if h < num_q_heads: q[s, h, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], long_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) elif h < num_q_heads + num_kv_heads: k[s, h - num_q_heads, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], long_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) else: v[s, h - (num_q_heads + num_kv_heads), d] = qkv[s, h, d] else: for iters in T.grid(seq_len, fused_heads, head_dim): with T.sblock("llama_fused_rope"): s, h, d = T.axis.remap("SSS", iters) if h < num_q_heads: q[s, h, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], short_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) elif h < num_q_heads + num_kv_heads: k[s, h - num_q_heads, d] = T.if_then_else( d < rotary_dim, _rope( qkv, s, h, d, position_map[s], short_factors if is_longrope_scaling else None, ), qkv[s, h, d], ) else: v[s, h - (num_q_heads + num_kv_heads), d] = qkv[s, h, d] if is_longrope_scaling: return fused_rope_longrope_scaling return fused_rope