Update Wall-X to 1.1.0 (#104)
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
"""Operator proxy layer with runtime fallback backends."""
|
||||
|
||||
from wall_x.model.core.ops.index import get_rope_index, get_window_index
|
||||
from wall_x.model.core.ops.moe import permute, unpermute
|
||||
from wall_x.model.core.ops.norm import rmsnorm
|
||||
from wall_x.model.core.ops.rope import m_rope, rope, rot_pos_emb
|
||||
|
||||
__all__ = [
|
||||
"rmsnorm",
|
||||
"rope",
|
||||
"m_rope",
|
||||
"rot_pos_emb",
|
||||
"permute",
|
||||
"unpermute",
|
||||
"get_rope_index",
|
||||
"get_window_index",
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Install-time compiled CUDA kernels for wall_x ops.
|
||||
|
||||
Auto-generated by scripts/extract_cuda_kernels.py.
|
||||
Do not edit manually.
|
||||
"""
|
||||
|
||||
_module = None
|
||||
|
||||
|
||||
def load():
|
||||
"""Load the CUDA extension module built by setup.py."""
|
||||
global _module
|
||||
if _module is not None:
|
||||
return _module
|
||||
|
||||
try:
|
||||
from wall_x.model.core.ops import _cuda_ext_bin
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Wall-X CUDA operators were not built. Install requirements first, "
|
||||
"then reinstall Wall-X with: "
|
||||
"MAX_JOBS=8 pip install --no-build-isolation -e ."
|
||||
) from exc
|
||||
|
||||
_module = _cuda_ext_bin
|
||||
return _module
|
||||
|
||||
|
||||
def is_available() -> bool:
|
||||
"""Check whether the install-time CUDA extension can be imported."""
|
||||
try:
|
||||
load()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -0,0 +1,750 @@
|
||||
"""Auto-generated CUDA kernel wrappers with autograd support.
|
||||
|
||||
Generated by scripts/extract_cuda_kernels.py from internal CUDA wrappers.
|
||||
Do not edit manually.
|
||||
"""
|
||||
|
||||
import torch
|
||||
from torch.autograd import Function
|
||||
|
||||
|
||||
def _m():
|
||||
"""Get the compiled CUDA extension module."""
|
||||
from wall_x.model.core.ops._cuda_ext import load
|
||||
return load()
|
||||
|
||||
|
||||
|
||||
class _RopeFunction(Function):
|
||||
@staticmethod
|
||||
def forward(
|
||||
ctx,
|
||||
q,
|
||||
k,
|
||||
cos,
|
||||
sin,
|
||||
interleave,
|
||||
fwd_kernel=None,
|
||||
bwd_kernel=None,
|
||||
):
|
||||
q_embed = q
|
||||
k_embed = k
|
||||
ctx.save_for_backward(q, k, cos, sin)
|
||||
ctx.interleave = interleave
|
||||
ctx.bwd_kernel = bwd_kernel
|
||||
fwd_kernel(q, k, q_embed, k_embed, cos, sin, interleave)
|
||||
return q_embed, k_embed
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_q_embed, grad_k_embed):
|
||||
q, k, cos, sin = ctx.saved_tensors
|
||||
interleave = ctx.interleave
|
||||
bwd_kernel = ctx.bwd_kernel
|
||||
grad_q = grad_q_embed
|
||||
grad_k = grad_k_embed
|
||||
|
||||
bwd_kernel(
|
||||
grad_q_embed,
|
||||
grad_k_embed,
|
||||
grad_q, # output
|
||||
grad_k, # output
|
||||
cos,
|
||||
sin,
|
||||
interleave,
|
||||
)
|
||||
|
||||
return grad_q, grad_k, None, None, None, None, None
|
||||
|
||||
|
||||
class _RopePackFunction(Function):
|
||||
@staticmethod
|
||||
def forward(
|
||||
ctx,
|
||||
qkv,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
cos,
|
||||
sin,
|
||||
interleave,
|
||||
fwd_kernel=None,
|
||||
bwd_kernel=None,
|
||||
):
|
||||
ctx.save_for_backward(cos, sin)
|
||||
ctx.q_num_heads = q_num_heads
|
||||
ctx.kv_num_heads = kv_num_heads
|
||||
ctx.interleave = interleave
|
||||
ctx.bwd_kernel = bwd_kernel
|
||||
|
||||
fwd_kernel(qkv, cos, sin, q_num_heads, kv_num_heads, interleave)
|
||||
return qkv
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, dqkv):
|
||||
if dqkv is None:
|
||||
return None, None, None, None, None, None, None, None
|
||||
|
||||
cos, sin = ctx.saved_tensors
|
||||
if dqkv.stride(-1) != 1:
|
||||
dqkv = dqkv.contiguous()
|
||||
|
||||
ctx.bwd_kernel(
|
||||
dqkv,
|
||||
cos,
|
||||
sin,
|
||||
ctx.q_num_heads,
|
||||
ctx.kv_num_heads,
|
||||
ctx.interleave,
|
||||
)
|
||||
return dqkv, None, None, None, None, None, None, None
|
||||
|
||||
|
||||
class Rope:
|
||||
def __init__(self):
|
||||
pass
|
||||
self.fwd_kernel = _m().rope
|
||||
self.inplace_kernel = _m().rope_inplace
|
||||
self.pack_kernel = _m().rope_inplace_pack
|
||||
self.pack_bwd_kernel = _m().rope_inplace_pack_bwd
|
||||
self.bwd_kernel = _m().rope_bwd
|
||||
|
||||
def pack(
|
||||
self,
|
||||
qkv,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
cos,
|
||||
sin,
|
||||
interleave=False,
|
||||
inference=False,
|
||||
):
|
||||
"""packed qkvq/krope,qkv.
|
||||
|
||||
Split/view/offset calculations are done on the C++ side. Callers consume qkv through the paired
|
||||
flash_attn pack interface without splitting q/k/v in Python.
|
||||
autograd,backwarddqkvdq/dk.
|
||||
|
||||
Args:
|
||||
qkv: [seq_len, q_dim + 2*kv_dim]
|
||||
q_num_heads: number of query heads
|
||||
kv_num_heads: number of key/value heads
|
||||
cos, sin: rotary position encoding [1, seq_len, head_dim/2]
|
||||
interleave: whether to use interleaved RoPE
|
||||
inference: kernel,autograd
|
||||
"""
|
||||
if inference:
|
||||
self.pack_kernel(qkv, cos, sin, q_num_heads, kv_num_heads, interleave)
|
||||
return qkv
|
||||
|
||||
return _RopePackFunction.apply(
|
||||
qkv,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
cos,
|
||||
sin,
|
||||
interleave,
|
||||
self.pack_kernel,
|
||||
self.pack_bwd_kernel,
|
||||
)
|
||||
|
||||
def pack_backward(
|
||||
self, dqkv, q_num_heads, kv_num_heads, cos, sin, interleave=False
|
||||
):
|
||||
"""packed dqkvdq/dkrope backward,dqkv."""
|
||||
self.pack_bwd_kernel(dqkv, cos, sin, q_num_heads, kv_num_heads, interleave)
|
||||
return dqkv
|
||||
|
||||
def __call__(self, q, k, cos, sin, interleave=False, inference=False):
|
||||
if inference:
|
||||
self.inplace_kernel(q, k, cos, sin, interleave)
|
||||
return q, k
|
||||
|
||||
return _RopeFunction.apply(
|
||||
q,
|
||||
k,
|
||||
cos,
|
||||
sin,
|
||||
interleave,
|
||||
self.fwd_kernel,
|
||||
self.bwd_kernel,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class _MRopeFunction(Function):
|
||||
@staticmethod
|
||||
def forward(
|
||||
ctx,
|
||||
q,
|
||||
k,
|
||||
cos,
|
||||
sin,
|
||||
mrope_section,
|
||||
fwd_kernel=None,
|
||||
bwd_kernel=None,
|
||||
):
|
||||
first = mrope_section[0]
|
||||
second = mrope_section[1]
|
||||
ctx.save_for_backward(q, k, cos, sin)
|
||||
ctx.mrope_section = mrope_section
|
||||
ctx.bwd_kernel = bwd_kernel
|
||||
q_embed = q
|
||||
k_embed = k
|
||||
fwd_kernel(q, k, q_embed, k_embed, cos, sin, first, second)
|
||||
return q_embed, k_embed
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_q_embed, grad_k_embed):
|
||||
if grad_q_embed.stride(-1) != 1:
|
||||
grad_q_embed = grad_q_embed.contiguous()
|
||||
if grad_k_embed.stride(-1) != 1:
|
||||
grad_k_embed = grad_k_embed.contiguous()
|
||||
q, k, cos, sin = ctx.saved_tensors
|
||||
mrope_section = ctx.mrope_section
|
||||
bwd_kernel = ctx.bwd_kernel
|
||||
grad_q = grad_q_embed
|
||||
grad_k = grad_k_embed
|
||||
first = mrope_section[0]
|
||||
second = mrope_section[1]
|
||||
|
||||
bwd_kernel(
|
||||
grad_q_embed,
|
||||
grad_k_embed,
|
||||
grad_q, # output
|
||||
grad_k, # output
|
||||
cos,
|
||||
sin,
|
||||
first,
|
||||
second,
|
||||
)
|
||||
|
||||
return grad_q, grad_k, None, None, None, None, None
|
||||
|
||||
|
||||
class _MRopePackFunction(Function):
|
||||
@staticmethod
|
||||
def forward(
|
||||
ctx,
|
||||
qkv,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
cos,
|
||||
sin,
|
||||
mrope_section,
|
||||
fwd_kernel=None,
|
||||
bwd_kernel=None,
|
||||
):
|
||||
first = mrope_section[0]
|
||||
second = mrope_section[1]
|
||||
ctx.save_for_backward(cos, sin)
|
||||
ctx.q_num_heads = q_num_heads
|
||||
ctx.kv_num_heads = kv_num_heads
|
||||
ctx.mrope_section = mrope_section
|
||||
ctx.bwd_kernel = bwd_kernel
|
||||
|
||||
fwd_kernel(qkv, cos, sin, q_num_heads, kv_num_heads, first, second)
|
||||
return qkv
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, dqkv):
|
||||
if dqkv is None:
|
||||
return None, None, None, None, None, None, None, None
|
||||
|
||||
cos, sin = ctx.saved_tensors
|
||||
if dqkv.stride(-1) != 1:
|
||||
dqkv = dqkv.contiguous()
|
||||
first = ctx.mrope_section[0]
|
||||
second = ctx.mrope_section[1]
|
||||
|
||||
ctx.bwd_kernel(
|
||||
dqkv,
|
||||
cos,
|
||||
sin,
|
||||
ctx.q_num_heads,
|
||||
ctx.kv_num_heads,
|
||||
first,
|
||||
second,
|
||||
)
|
||||
return dqkv, None, None, None, None, None, None, None
|
||||
|
||||
|
||||
class MRope:
|
||||
def __init__(self):
|
||||
pass
|
||||
self.fwd_kernel = _m().m_rope
|
||||
self.inplace_kernel = _m().m_rope_inplace
|
||||
self.pack_kernel = _m().m_rope_inplace_pack
|
||||
self.pack_bwd_kernel = _m().m_rope_inplace_pack_bwd
|
||||
self.bwd_kernel = _m().m_rope_bwd
|
||||
|
||||
def pack(
|
||||
self,
|
||||
qkv,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
cos,
|
||||
sin,
|
||||
mrope_section,
|
||||
inference=False,
|
||||
):
|
||||
"""packed qkvq/kmrope,qkv.GQA.
|
||||
|
||||
Split/view/offset calculations are done on the C++ side. qkv must be 3D: [bz, seq_len, q_dim+2*kv_dim].
|
||||
|
||||
Args:
|
||||
qkv: [bz, seq_len, q_dim + 2*kv_dim]
|
||||
q_num_heads: number of query heads
|
||||
kv_num_heads: number of key/value heads
|
||||
cos, sin: rotary position encoding [3, bz, seq_len, head_dim/2]
|
||||
mrope_section: (first, second) M-RoPE section parameters
|
||||
inference: kernel,autograd
|
||||
"""
|
||||
if inference:
|
||||
self.pack_kernel(
|
||||
qkv,
|
||||
cos,
|
||||
sin,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
mrope_section[0],
|
||||
mrope_section[1],
|
||||
)
|
||||
return qkv
|
||||
|
||||
return _MRopePackFunction.apply(
|
||||
qkv,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
cos,
|
||||
sin,
|
||||
mrope_section,
|
||||
self.pack_kernel,
|
||||
self.pack_bwd_kernel,
|
||||
)
|
||||
|
||||
def pack_backward(self, dqkv, q_num_heads, kv_num_heads, cos, sin, mrope_section):
|
||||
"""packed dqkvdq/dkmrope backward,dqkv."""
|
||||
self.pack_bwd_kernel(
|
||||
dqkv,
|
||||
cos,
|
||||
sin,
|
||||
q_num_heads,
|
||||
kv_num_heads,
|
||||
mrope_section[0],
|
||||
mrope_section[1],
|
||||
)
|
||||
return dqkv
|
||||
|
||||
def __call__(self, q, k, cos, sin, mrope_section, inference=False):
|
||||
if inference:
|
||||
first = mrope_section[0]
|
||||
second = mrope_section[1]
|
||||
self.inplace_kernel(q, k, cos, sin, first, second)
|
||||
return q, k
|
||||
|
||||
return _MRopeFunction.apply(
|
||||
q,
|
||||
k,
|
||||
cos,
|
||||
sin,
|
||||
mrope_section,
|
||||
self.fwd_kernel,
|
||||
self.bwd_kernel,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class RotPos:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, inv_freq, grid_thw, spatial_merge_size):
|
||||
assert (
|
||||
inv_freq.dtype == torch.float32
|
||||
), f"Expected float32, got {inv_freq.dtype}"
|
||||
get_token_counts_kernel = _m().get_token_counts
|
||||
rot_pos_kernel = _m().rot_pos
|
||||
|
||||
num_grids = grid_thw.size(0)
|
||||
token_counts = torch.zeros(
|
||||
(num_grids), dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
|
||||
get_token_counts_kernel(grid_thw, token_counts, spatial_merge_size)
|
||||
cumsum_tokens = torch.cat(
|
||||
[
|
||||
torch.zeros(1, dtype=token_counts.dtype, device=token_counts.device),
|
||||
token_counts.cumsum(dim=0),
|
||||
],
|
||||
dim=0,
|
||||
).to(grid_thw.dtype)
|
||||
|
||||
output = torch.empty(
|
||||
(cumsum_tokens[-1].item(), inv_freq.size(0) * 2),
|
||||
dtype=torch.float,
|
||||
device=inv_freq.device,
|
||||
)
|
||||
rot_pos_kernel(inv_freq, grid_thw, output, cumsum_tokens, spatial_merge_size)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
|
||||
|
||||
class GetRopeIndex:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
input_ids,
|
||||
image_grid_thw,
|
||||
video_grid_thw,
|
||||
second_per_grid_ts,
|
||||
attention_mask,
|
||||
spatial_merge_size,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
vision_start_token_id,
|
||||
tokens_per_second,
|
||||
):
|
||||
get_workspace = _m().get_rope_index_getworkspace
|
||||
get_rope_index_kernel = _m().get_rope_index
|
||||
work_space_size = get_workspace(input_ids, image_grid_thw, video_grid_thw)
|
||||
|
||||
workspace = torch.empty(
|
||||
work_space_size, dtype=torch.uint8, device=input_ids.device
|
||||
)
|
||||
batch_size = input_ids.size(0)
|
||||
seq_len = input_ids.size(1)
|
||||
position_ids = torch.empty(
|
||||
(3, batch_size, seq_len), dtype=torch.int64, device=input_ids.device
|
||||
)
|
||||
mrope_deltas = torch.empty(
|
||||
(batch_size, 1), dtype=torch.int64, device=input_ids.device
|
||||
)
|
||||
get_rope_index_kernel(
|
||||
input_ids,
|
||||
image_grid_thw,
|
||||
video_grid_thw,
|
||||
second_per_grid_ts,
|
||||
attention_mask,
|
||||
position_ids,
|
||||
mrope_deltas,
|
||||
workspace,
|
||||
spatial_merge_size,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
vision_start_token_id,
|
||||
tokens_per_second,
|
||||
)
|
||||
|
||||
return position_ids, mrope_deltas
|
||||
|
||||
|
||||
|
||||
get_window_index_kernel = _m().get_window_index
|
||||
get_totals_kernel = _m().get_totals
|
||||
|
||||
|
||||
def get_window_index_cuda(
|
||||
grid_thw, window_size, spatial_merge_size, patch_size, spatial_merge_unit=1
|
||||
):
|
||||
|
||||
if grid_thw.size(0) == 0:
|
||||
return (
|
||||
torch.empty(0, dtype=grid_thw.dtype, device=grid_thw.device),
|
||||
torch.zeros(1, dtype=grid_thw.dtype, device=grid_thw.device),
|
||||
)
|
||||
|
||||
vit_merger_window_size = window_size // spatial_merge_size // patch_size
|
||||
|
||||
grid_info_tensor = torch.empty(
|
||||
(grid_thw.size(0), 6), dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
global_totals_tensor = torch.zeros(
|
||||
(2), dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
get_totals_kernel(
|
||||
grid_thw,
|
||||
grid_info_tensor,
|
||||
global_totals_tensor,
|
||||
spatial_merge_size,
|
||||
vit_merger_window_size,
|
||||
)
|
||||
total_elements = global_totals_tensor[0].item()
|
||||
total_windows = global_totals_tensor[1].item()
|
||||
if total_elements == 0 or total_windows == 0:
|
||||
return (
|
||||
torch.empty(0, dtype=grid_thw.dtype, device=grid_thw.device),
|
||||
torch.zeros(1, dtype=grid_thw.dtype, device=grid_thw.device),
|
||||
)
|
||||
|
||||
window_indices = torch.empty(
|
||||
total_elements, dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
cu_window_seqlens = torch.empty(
|
||||
(total_windows + 1), dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
window_counts_tensor = torch.empty(
|
||||
(total_windows), dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
|
||||
max_grid_t = grid_thw[:, 0].max().item()
|
||||
get_window_index_kernel(
|
||||
grid_thw,
|
||||
grid_info_tensor,
|
||||
window_indices,
|
||||
cu_window_seqlens,
|
||||
window_counts_tensor,
|
||||
max_grid_t,
|
||||
spatial_merge_size,
|
||||
vit_merger_window_size,
|
||||
patch_size,
|
||||
spatial_merge_unit,
|
||||
)
|
||||
|
||||
return window_indices, cu_window_seqlens
|
||||
|
||||
|
||||
|
||||
################################################################################################
|
||||
##
|
||||
## PermuteMoE topK
|
||||
##
|
||||
################################################################################################
|
||||
|
||||
|
||||
|
||||
class PermuteMoE_topK(torch.autograd.Function):
|
||||
|
||||
dtype = None
|
||||
max_expanded_token_num = 0
|
||||
|
||||
@staticmethod
|
||||
def forward(
|
||||
ctx,
|
||||
input_act: torch.Tensor,
|
||||
indices: torch.Tensor,
|
||||
num_out_tokens: int,
|
||||
max_token_num: int,
|
||||
):
|
||||
"""
|
||||
indices: for topK=1, indices in a 1-d tensor of shape [num_tokens],
|
||||
otherwise, it's a 2-d tensor of shape [num_tokens, topK]
|
||||
"""
|
||||
if not input_act.numel():
|
||||
return input_act, None
|
||||
|
||||
# For top1 case, view the indices as 2D tensor to unify the shape for topk>=2 cases.
|
||||
if indices.dim() == 1:
|
||||
indices = indices.view(-1, 1)
|
||||
|
||||
# Device check
|
||||
if input_act.is_cpu:
|
||||
raise RuntimeError(
|
||||
"[Error] The input `input_act` of permute_topK op is on the device: CPU!"
|
||||
)
|
||||
|
||||
# Data type check
|
||||
if indices.dtype != torch.int32:
|
||||
indices = indices.to(torch.int32)
|
||||
|
||||
# Contiguous check
|
||||
if not input_act.is_contiguous():
|
||||
input_act = input_act.contiguous()
|
||||
if not indices.is_contiguous():
|
||||
indices = indices.contiguous()
|
||||
|
||||
num_topK = indices.size(1)
|
||||
|
||||
input_max_expanded_token_num = max(max_token_num, input_act.size(0)) * num_topK
|
||||
if PermuteMoE_topK.max_expanded_token_num < input_max_expanded_token_num:
|
||||
PermuteMoE_topK.max_expanded_token_num = input_max_expanded_token_num
|
||||
|
||||
permute_kernel = _m().permute
|
||||
sorted_indices = torch.empty(
|
||||
PermuteMoE_topK.max_expanded_token_num,
|
||||
dtype=torch.int32,
|
||||
device=input_act.device,
|
||||
)
|
||||
row_id = torch.arange(
|
||||
PermuteMoE_topK.max_expanded_token_num,
|
||||
dtype=torch.int32,
|
||||
device=input_act.device,
|
||||
)
|
||||
sorted_row_id = torch.empty(
|
||||
PermuteMoE_topK.max_expanded_token_num,
|
||||
dtype=torch.int32,
|
||||
device=input_act.device,
|
||||
)
|
||||
get_storage_bytes_kernel = _m().cub_sort_pair_get_storage_bytes
|
||||
temp_storage_bytes = get_storage_bytes_kernel(
|
||||
PermuteMoE_topK.max_expanded_token_num
|
||||
)
|
||||
temp_storage = torch.empty(
|
||||
temp_storage_bytes, dtype=torch.int8, device=input_act.device
|
||||
)
|
||||
num_out = (
|
||||
num_out_tokens if (num_out_tokens > 0) else (indices.size(0) * num_topK)
|
||||
)
|
||||
permuted_output = torch.empty(
|
||||
(num_out, input_act.size(1)), dtype=input_act.dtype, device=input_act.device
|
||||
)
|
||||
row_id_map = torch.empty(
|
||||
(indices.size(0) * num_topK), dtype=torch.int32, device=input_act.device
|
||||
)
|
||||
permute_kernel(
|
||||
input_act,
|
||||
indices,
|
||||
sorted_indices,
|
||||
row_id,
|
||||
sorted_row_id,
|
||||
temp_storage,
|
||||
permuted_output,
|
||||
row_id_map,
|
||||
num_out_tokens,
|
||||
PermuteMoE_topK.max_expanded_token_num,
|
||||
)
|
||||
|
||||
ctx.row_id_map = row_id_map
|
||||
ctx.num_tokens = indices.size(0)
|
||||
ctx.num_topK = num_topK
|
||||
|
||||
return permuted_output, row_id_map
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, permuted_act_grad, _):
|
||||
if not permuted_act_grad.numel():
|
||||
return permuted_act_grad, None, None, None
|
||||
|
||||
unpermute_kernel = _m().unpermute
|
||||
if not permuted_act_grad.is_contiguous():
|
||||
permuted_act_grad = permuted_act_grad.contiguous()
|
||||
|
||||
row_id_map = ctx.row_id_map
|
||||
num_tokens = ctx.num_tokens
|
||||
num_topK = ctx.num_topK
|
||||
num_cols = permuted_act_grad.size(1)
|
||||
unpermuted_output = torch.empty(
|
||||
(num_tokens, num_cols),
|
||||
dtype=permuted_act_grad.dtype,
|
||||
device=permuted_act_grad.device,
|
||||
)
|
||||
unpermute_kernel(
|
||||
permuted_act_grad, row_id_map, None, unpermuted_output, num_tokens, num_topK
|
||||
)
|
||||
|
||||
return unpermuted_output, None, None, None
|
||||
|
||||
|
||||
################################################################################################
|
||||
##
|
||||
## UnpermuteMoE topK
|
||||
##
|
||||
################################################################################################
|
||||
|
||||
|
||||
class UnpermuteMoE_topK(torch.autograd.Function):
|
||||
|
||||
@staticmethod
|
||||
def forward(
|
||||
ctx,
|
||||
input_act: torch.Tensor,
|
||||
row_id_map: torch.Tensor,
|
||||
probs: torch.Tensor = None,
|
||||
):
|
||||
|
||||
if not input_act.numel():
|
||||
ctx.probs = probs
|
||||
return input_act
|
||||
|
||||
# Device check
|
||||
if input_act.is_cpu:
|
||||
raise RuntimeError(
|
||||
"[Error] The input `input_act` of unpermute_topK op is on the device: CPU!"
|
||||
)
|
||||
if row_id_map.is_cpu:
|
||||
row_id_map = row_id_map.cuda()
|
||||
if probs is not None and probs.is_cpu:
|
||||
probs = probs.cuda()
|
||||
|
||||
# Shape check
|
||||
if probs is not None and row_id_map.size(0) != probs.size(0) * probs.size(1):
|
||||
raise RuntimeError(
|
||||
f"[Error] unpermute_topK op input `probs` shape mismatch! "
|
||||
f"Expect {row_id_map.size(0)}, but got {probs.size(0) * probs.size(1)}."
|
||||
)
|
||||
|
||||
# Data type check
|
||||
if row_id_map.dtype != torch.int32:
|
||||
row_id_map = row_id_map.to(torch.int32)
|
||||
if probs is not None and probs.dtype != torch.float32:
|
||||
probs = probs.to(torch.float32)
|
||||
|
||||
# Contiguous check
|
||||
if not input_act.is_contiguous():
|
||||
input_act = input_act.contiguous()
|
||||
if not row_id_map.is_contiguous():
|
||||
row_id_map = row_id_map.contiguous()
|
||||
if probs is not None and not probs.is_contiguous():
|
||||
probs = probs.contiguous()
|
||||
|
||||
num_tokens = probs.size(0) if probs is not None else input_act.size(0)
|
||||
num_topK = probs.size(1) if probs is not None else 1
|
||||
unpermute_kernel = _m().unpermute
|
||||
num_cols = input_act.size(1)
|
||||
unpermuted_output = torch.empty(
|
||||
(num_tokens, num_cols), dtype=input_act.dtype, device=input_act.device
|
||||
)
|
||||
unpermute_kernel(
|
||||
input_act, row_id_map, probs, unpermuted_output, num_tokens, num_topK
|
||||
)
|
||||
|
||||
ctx.save_for_backward(input_act, row_id_map, probs)
|
||||
|
||||
return unpermuted_output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, unpermuted_act_grad):
|
||||
|
||||
if not unpermuted_act_grad.numel():
|
||||
return unpermuted_act_grad, None, ctx.probs
|
||||
|
||||
if not unpermuted_act_grad.is_contiguous():
|
||||
unpermuted_act_grad = unpermuted_act_grad.contiguous()
|
||||
|
||||
input_act, row_id_map, probs = ctx.saved_tensors
|
||||
|
||||
act_grad = None
|
||||
unpermute_bwd_kernel = _m().unpermute_bwd
|
||||
if ctx.needs_input_grad[0]:
|
||||
num_cols = unpermuted_act_grad.size(1)
|
||||
act_grad = torch.empty(
|
||||
(input_act.size(0), num_cols),
|
||||
dtype=unpermuted_act_grad.dtype,
|
||||
device=unpermuted_act_grad.device,
|
||||
)
|
||||
prob_grad = torch.empty(
|
||||
(probs.size(0), probs.size(1)),
|
||||
dtype=torch.float32,
|
||||
device=unpermuted_act_grad.device,
|
||||
)
|
||||
unpermute_bwd_kernel(
|
||||
unpermuted_act_grad, input_act, row_id_map, probs, act_grad, prob_grad
|
||||
)
|
||||
|
||||
if not ctx.needs_input_grad[2]:
|
||||
prob_grad = None
|
||||
|
||||
return act_grad, None, prob_grad
|
||||
|
||||
|
||||
def permute_kernel(input_act, indices, num_out_tokens=None, max_token_num=0):
|
||||
num_out_tokens = 0 if num_out_tokens is None else num_out_tokens
|
||||
return PermuteMoE_topK.apply(input_act, indices, num_out_tokens, max_token_num)
|
||||
|
||||
|
||||
def unpermute_kernel(input_act, row_id_map, probs=None):
|
||||
return UnpermuteMoE_topK.apply(input_act, row_id_map, probs)
|
||||
@@ -0,0 +1,143 @@
|
||||
"""Base class for operator proxies with lazy backend resolution."""
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import threading
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpsProxy:
|
||||
"""Callable proxy that lazily resolves to the best available backend.
|
||||
|
||||
Three-level fallback:
|
||||
Level 1: external_accel (fastest, requires external_accel package)
|
||||
Level 2: CUDA inline kernel (JIT compiled, requires CUDA)
|
||||
Level 3: Pure PyTorch (always available)
|
||||
|
||||
Subclasses define _external_accel_name, _pytorch_fallback, and optionally _cuda_kernel.
|
||||
|
||||
Set _external_accel_name to None to skip external_accel resolution (e.g., when the
|
||||
external_accel API is incompatible).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._resolved_fn = None
|
||||
self._backend = None
|
||||
self._resolve_lock = threading.Lock()
|
||||
|
||||
def _resolve(self):
|
||||
"""Resolve the best available backend. Called once on first use (thread-safe)."""
|
||||
if self._resolved_fn is not None:
|
||||
return
|
||||
with self._resolve_lock:
|
||||
# Double-check after acquiring lock
|
||||
if self._resolved_fn is not None:
|
||||
return
|
||||
|
||||
# Level 1: try external_accel (skip if _external_accel_name is None)
|
||||
fn = self._probe_external_accel()
|
||||
if fn is not None:
|
||||
self._backend = "external_accel"
|
||||
self._resolved_fn = fn
|
||||
return
|
||||
|
||||
# Level 2: try CUDA inline kernel (subclass override)
|
||||
cuda_fn = self._get_cuda_kernel()
|
||||
if cuda_fn is not None:
|
||||
self._backend = "cuda_inline"
|
||||
self._resolved_fn = cuda_fn
|
||||
return
|
||||
|
||||
# Level 3: PyTorch fallback
|
||||
self._backend = "pytorch"
|
||||
self._resolved_fn = self._pytorch_fallback
|
||||
logger.info(f"{self.__class__.__name__}: using PyTorch fallback")
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
"""Override in subclass to provide Level 2 CUDA kernel. Returns None if unavailable."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
"""Return external_accel attribute name, or None to skip external_accel resolution.
|
||||
|
||||
Base returns None (skip external_accel). Subclasses override to return the
|
||||
attribute name when external_accel integration is desired.
|
||||
"""
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(self, *args, **kwargs):
|
||||
raise NotImplementedError(f"{self.__class__.__name__} has no PyTorch fallback")
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if self._resolved_fn is None:
|
||||
self._resolve()
|
||||
return self._resolved_fn(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def backend(self) -> str:
|
||||
if self._resolved_fn is None:
|
||||
self._resolve()
|
||||
return self._backend
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Multi-backend API (for testing / benchmarking)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _probe_external_accel(self):
|
||||
"""Return the external_accel function for this op, or None if unavailable."""
|
||||
if self._external_accel_name is None:
|
||||
return None
|
||||
try:
|
||||
mod = importlib.import_module("external_accel")
|
||||
return getattr(mod, self._external_accel_name)
|
||||
except (ImportError, AttributeError):
|
||||
return None
|
||||
|
||||
def available_backends(self):
|
||||
"""Return list of available backend names for this operator."""
|
||||
backends = ["pytorch"]
|
||||
if self._probe_external_accel() is not None:
|
||||
backends.append("external_accel")
|
||||
if self._get_cuda_kernel() is not None:
|
||||
backends.append("cuda_inline")
|
||||
return backends
|
||||
|
||||
def _get_backend_fn(self, backend):
|
||||
"""Return the callable for a specific backend, or None if unavailable."""
|
||||
if backend == "pytorch":
|
||||
return self._pytorch_fallback
|
||||
elif backend == "external_accel":
|
||||
return self._probe_external_accel()
|
||||
elif backend == "cuda_inline":
|
||||
return self._get_cuda_kernel()
|
||||
else:
|
||||
raise ValueError(f"Unknown backend: {backend}")
|
||||
|
||||
def call_with_backend(self, backend, *args, **kwargs):
|
||||
"""Call this operator using a specific backend.
|
||||
|
||||
This does NOT change the default backend used by ``__call__``.
|
||||
The ``backend`` property still reflects the auto-resolved default.
|
||||
|
||||
Args:
|
||||
backend: One of "external_accel", "cuda_inline", "pytorch".
|
||||
|
||||
Returns:
|
||||
Operator output from the specified backend.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the requested backend is not available.
|
||||
"""
|
||||
fn = self._get_backend_fn(backend)
|
||||
if fn is None:
|
||||
raise RuntimeError(
|
||||
f"{self.__class__.__name__}: backend '{backend}' not available. "
|
||||
f"Available: {self.available_backends()}"
|
||||
)
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
backend = self._backend or "unresolved"
|
||||
return f"<{self.__class__.__name__} backend={backend}>"
|
||||
@@ -0,0 +1,69 @@
|
||||
// Auto-generated by scripts/extract_cuda_kernels.py
|
||||
// This file registers CUDA kernel bindings via pybind11.
|
||||
//
|
||||
// Kernel .cu files are compiled separately so ninja can parallelize builds.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <torch/extension.h>
|
||||
|
||||
namespace wallx_cuda_get_rope_index {
|
||||
void GetRopeIndex(const c10::optional<at::Tensor>& input_ids, const c10::optional<at::Tensor>& image_grid_thw, const c10::optional<at::Tensor>& video_grid_thw, const c10::optional<at::Tensor>& second_per_grid_ts, const c10::optional<at::Tensor>& attention_mask, const at::Tensor& position_ids, const at::Tensor& mrope_deltas, const at::Tensor& workspace, int spatial_merge_size, int image_token_id, int video_token_id, int vision_start_token_id, float tokens_per_second);
|
||||
int64_t GetRopeIndexGetWorkSpace(const c10::optional<at::Tensor>& input_ids, const c10::optional<at::Tensor>& image_grid_thw, const c10::optional<at::Tensor>& video_grid_thw);
|
||||
} // namespace wallx_cuda_get_rope_index
|
||||
|
||||
namespace wallx_cuda_m_rope {
|
||||
void MRope(const at::Tensor& q, const at::Tensor& k, const at::Tensor& q_embed, const at::Tensor& k_embed, const at::Tensor& cos, const at::Tensor& sin, const int first, const int second );
|
||||
void MRopeInplace(const at::Tensor& q, const at::Tensor& k, const at::Tensor& cos, const at::Tensor& sin, const int first, const int second );
|
||||
void MRopeInplacePack(const at::Tensor& qkv, const at::Tensor& cos, const at::Tensor& sin, int64_t q_num_heads, int64_t kv_num_heads, const int first, const int second);
|
||||
void MRopeInplacePackBackward(const at::Tensor& dqkv, const at::Tensor& cos, const at::Tensor& sin, int64_t q_num_heads, int64_t kv_num_heads, const int first, const int second);
|
||||
void MRopeBackward( const at::Tensor& grad_q_embed, const at::Tensor& grad_k_embed, const at::Tensor& grad_q, const at::Tensor& grad_k, const at::Tensor& cos, const at::Tensor& sin, const int first, const int second );
|
||||
} // namespace wallx_cuda_m_rope
|
||||
|
||||
namespace wallx_cuda_permute {
|
||||
void MoePermuteTopKOp( const at::Tensor& input, const at::Tensor& indices, const at::Tensor& sorted_indices, const at::Tensor& row_id, const at::Tensor& sorted_row_id, const at::Tensor& temp_storage, const at::Tensor& permuted_output, const at::Tensor& row_id_map, int64_t num_out_tokens, int64_t max_expanded_token_num );
|
||||
void MoeRecoverTopKOp( const at::Tensor& input, const at::Tensor& row_id_map, const c10::optional<at::Tensor>& prob, const at::Tensor& unpermuted_output, int64_t num_tokens, int64_t num_topK );
|
||||
void MoeRecoverTopKBwdOp( const at::Tensor& input_bwd, const at::Tensor& input_fwd, const at::Tensor& row_id_map, const at::Tensor& prob, const at::Tensor& act_grad, const at::Tensor& prob_grad );
|
||||
size_t CubSortPairGetStorageBytes(int64_t num_items);
|
||||
} // namespace wallx_cuda_permute
|
||||
|
||||
namespace wallx_cuda_rope {
|
||||
void Rope(const at::Tensor& q, const at::Tensor& k, const at::Tensor& q_embed, const at::Tensor& k_embed, const at::Tensor& cos, const at::Tensor& sin, bool interleave );
|
||||
void RopeInplace(const at::Tensor& q, const at::Tensor& k, const at::Tensor& cos, const at::Tensor& sin, bool interleave );
|
||||
void RopeInplacePack(const at::Tensor& qkv, const at::Tensor& cos, const at::Tensor& sin, int64_t q_num_heads, int64_t kv_num_heads, bool interleave);
|
||||
void RopeInplacePackBackward(const at::Tensor& dqkv, const at::Tensor& cos, const at::Tensor& sin, int64_t q_num_heads, int64_t kv_num_heads, bool interleave);
|
||||
void RopeBackward( const at::Tensor& grad_q_embed, const at::Tensor& grad_k_embed, const at::Tensor& grad_q, const at::Tensor& grad_k, const at::Tensor& cos, const at::Tensor& sin, bool interleave );
|
||||
} // namespace wallx_cuda_rope
|
||||
|
||||
namespace wallx_cuda_rot_pos {
|
||||
void GetTokenCounts( const at::Tensor& grid_thw, const at::Tensor& token_counts, int spatial_merge_size );
|
||||
void RotPosEmb( const at::Tensor& inv_freq, const at::Tensor& grid_thw, const at::Tensor& output, const at::Tensor& cumsum_tokens, int spatial_merge_size );
|
||||
} // namespace wallx_cuda_rot_pos
|
||||
|
||||
namespace wallx_cuda_window_index {
|
||||
void GetWindowIndex( const at::Tensor& grid_thw, const at::Tensor& grid_info_tensor, const at::Tensor& window_indices, const at::Tensor& cu_window_seqlens, const at::Tensor& window_counts_tensor, int max_grid_t, int spatial_merge_size, int vit_merger_window_size, int patch_size, int spatial_merge_unit);
|
||||
void GetTotals( const at::Tensor& grid_thw, const at::Tensor& grid_info_tensor, const at::Tensor& global_totals_tensor, int spatial_merge_size, int vit_merger_window_size );
|
||||
} // namespace wallx_cuda_window_index
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("rope", &wallx_cuda_rope::Rope);
|
||||
m.def("rope_inplace", &wallx_cuda_rope::RopeInplace);
|
||||
m.def("rope_inplace_pack", &wallx_cuda_rope::RopeInplacePack);
|
||||
m.def("rope_inplace_pack_bwd", &wallx_cuda_rope::RopeInplacePackBackward);
|
||||
m.def("rope_bwd", &wallx_cuda_rope::RopeBackward);
|
||||
m.def("m_rope", &wallx_cuda_m_rope::MRope);
|
||||
m.def("m_rope_inplace", &wallx_cuda_m_rope::MRopeInplace);
|
||||
m.def("m_rope_inplace_pack", &wallx_cuda_m_rope::MRopeInplacePack);
|
||||
m.def("m_rope_inplace_pack_bwd", &wallx_cuda_m_rope::MRopeInplacePackBackward);
|
||||
m.def("m_rope_bwd", &wallx_cuda_m_rope::MRopeBackward);
|
||||
m.def("get_token_counts", &wallx_cuda_rot_pos::GetTokenCounts);
|
||||
m.def("rot_pos", &wallx_cuda_rot_pos::RotPosEmb);
|
||||
m.def("get_rope_index", &wallx_cuda_get_rope_index::GetRopeIndex);
|
||||
m.def("get_rope_index_getworkspace", &wallx_cuda_get_rope_index::GetRopeIndexGetWorkSpace);
|
||||
m.def("get_window_index", &wallx_cuda_window_index::GetWindowIndex);
|
||||
m.def("get_totals", &wallx_cuda_window_index::GetTotals);
|
||||
m.def("permute", &wallx_cuda_permute::MoePermuteTopKOp);
|
||||
m.def("unpermute", &wallx_cuda_permute::MoeRecoverTopKOp);
|
||||
m.def("unpermute_bwd", &wallx_cuda_permute::MoeRecoverTopKBwdOp);
|
||||
m.def("cub_sort_pair_get_storage_bytes", &wallx_cuda_permute::CubSortPairGetStorageBytes);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace wallx_cuda {
|
||||
|
||||
enum class ActivationType {
|
||||
RELU,
|
||||
SILU,
|
||||
GELU
|
||||
};
|
||||
|
||||
} // namespace wallx_cuda
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cuda_fp16.h>
|
||||
#include "activation_types.h"
|
||||
|
||||
namespace wallx_cuda {
|
||||
|
||||
template<typename T, ActivationType activation_type>
|
||||
__forceinline__ __device__ T applyActivation(const T &x) {
|
||||
if constexpr (activation_type == ActivationType::RELU) {
|
||||
return x > (T)0.0f ? x : (T)0.0f;
|
||||
}
|
||||
else if constexpr (activation_type == ActivationType::SILU) {
|
||||
return (T)((float)x / (1.0f + __expf((float)-x)));
|
||||
}
|
||||
else if constexpr (activation_type == ActivationType::GELU) {
|
||||
// GELU implementation from vllm (gelu_new_kernel)
|
||||
const float x_f = (float)x;
|
||||
const float x3 = x_f * x_f * x_f;
|
||||
const float t = tanhf(0.79788456f * (x_f + 0.044715f * x3));
|
||||
return (T)(0.5f * x_f * (1.0f + t));
|
||||
}
|
||||
else {
|
||||
// No activation matches
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wallx_cuda
|
||||
@@ -0,0 +1,111 @@
|
||||
// Re-enable CUDA half operators (PyTorch disables them)
|
||||
#undef __CUDA_NO_HALF_OPERATORS__
|
||||
#undef __CUDA_NO_HALF_CONVERSIONS__
|
||||
#undef __CUDA_NO_HALF2_OPERATORS__
|
||||
#undef __CUDA_NO_BFLOAT16_CONVERSIONS__
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
#define CUDA_CHECK(cmd) do { \
|
||||
cudaError_t result = cmd; \
|
||||
if (result != cudaSuccess) { \
|
||||
printf("[ERROR] CUDA error %s:%d '%s': (%d) %s\n", __FILE__, __LINE__, #cmd, (int)result, cudaGetErrorString(result)); \
|
||||
exit(-1); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
inline void syncAndCheck(const char* const file, int const line, bool force_check = false) {
|
||||
#ifdef DEBUG
|
||||
force_check = true;
|
||||
#endif
|
||||
if (force_check) {
|
||||
cudaDeviceSynchronize();
|
||||
cudaError_t result = cudaGetLastError();
|
||||
if (result) {
|
||||
throw std::runtime_error(std::string("[ST] CUDA runtime error: ") + cudaGetErrorString(result) + " "
|
||||
+ file + ":" + std::to_string(line) + " \n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define sync_check_cuda_error() syncAndCheck(__FILE__, __LINE__, false)
|
||||
#define sync_check_cuda_error_force() syncAndCheck(__FILE__, __LINE__, true)
|
||||
|
||||
// #ifdef DEBUG
|
||||
#define ASSERT_CHECK(__cond) \
|
||||
do { \
|
||||
const bool __cond_var = (__cond); \
|
||||
if (!__cond_var) { \
|
||||
::std::string __err_msg = \
|
||||
::std::string("`") + #__cond + \
|
||||
"` check failed at " + \
|
||||
__FILE__ + ":" + \
|
||||
::std::to_string(__LINE__); \
|
||||
throw std::runtime_error(__err_msg); \
|
||||
} \
|
||||
} while (0)
|
||||
// #else
|
||||
// #define ASSERT_CHECK(__cond) do { } while (0)
|
||||
// #endif
|
||||
|
||||
// Some stuff for indexing into an 1-D array
|
||||
#define INDEX_2D(dim1, dim2, index1, index2) \
|
||||
(((int64_t)index1) * (dim2) + (index2))
|
||||
#define INDEX_3D(dim1, dim2, dim3, index1, index2, index3) \
|
||||
(((int64_t)index1) * (dim2) * (dim3) + ((int64_t)index2) * (dim3) + (index3))
|
||||
#define INDEX_4D(dim1, dim2, dim3, dim4, index1, index2, index3, index4) \
|
||||
(((int64_t)index1) * (dim2) * (dim3) * (dim4) + ((int64_t)index2) * (dim3) * (dim4) + ((int64_t)index3) * (dim4) + (index4))
|
||||
#define INDEX_5D(dim1, dim2, dim3, dim4, dim5, index1, index2, index3, index4, index5) \
|
||||
(((int64_t)index1) * (dim2) * (dim3) * (dim4) * (dim5) + ((int64_t)index2) * (dim3) * (dim4) * (dim5) + ((int64_t)index3) * (dim4) * (dim5) + (index4) * (dim5) + (index5))
|
||||
|
||||
template<typename T>
|
||||
struct VecTraits;
|
||||
|
||||
template<>
|
||||
struct VecTraits<float> {
|
||||
using Type = float4;
|
||||
static constexpr int vec_size = 4;
|
||||
__device__ static inline Type load(const float* p) { return *reinterpret_cast<const float4*>(p); }
|
||||
__device__ static inline void store(float* p, const Type& v) { *reinterpret_cast<float4*>(p) = v; }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct VecTraits<__half> {
|
||||
using Type = __half2;
|
||||
static constexpr int vec_size = 2;
|
||||
__device__ static inline Type load(const __half* p) { return *reinterpret_cast<const __half2*>(p); }
|
||||
__device__ static inline void store(__half* p, const Type& v) { *reinterpret_cast<__half2*>(p) = v; }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct VecTraits<__nv_bfloat16> {
|
||||
using Type = __nv_bfloat162;
|
||||
static constexpr int vec_size = 2;
|
||||
__device__ static inline Type load(const __nv_bfloat16* p) { return *reinterpret_cast<const __nv_bfloat162*>(p); }
|
||||
__device__ static inline void store(__nv_bfloat16* p, const Type& v) { *reinterpret_cast<__nv_bfloat162*>(p) = v; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
cudaDataType_t getCudaDataType() {
|
||||
if (std::is_same<T, half>::value) {
|
||||
return CUDA_R_16F;
|
||||
}
|
||||
else if (std::is_same<T, __nv_bfloat16>::value) {
|
||||
return CUDA_R_16BF;
|
||||
}
|
||||
else if (std::is_same<T, float>::value) {
|
||||
return CUDA_R_32F;
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Cuda data type: Unsupported type");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
#undef __CUDA_NO_HALF_OPERATORS__
|
||||
#undef __CUDA_NO_HALF_CONVERSIONS__
|
||||
#undef __CUDA_NO_BFLOAT16_CONVERSIONS__
|
||||
#undef __CUDA_NO_HALF2_OPERATORS__
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda.h>
|
||||
#include <cub/cub.cuh>
|
||||
|
||||
#include "../common/cuda_utils.h"
|
||||
#include <torch/extension.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
namespace wallx_cuda_get_rope_index {
|
||||
|
||||
// Constants and macros
|
||||
#define MAX_SEQ_LEN 8192
|
||||
#define MAX_VISION_TOKENS 64
|
||||
#define WARP_SIZE 32
|
||||
#define MAX_THREADS_PER_BLOCK 1024
|
||||
|
||||
// Vision content descriptor
|
||||
struct VisionDescriptor
|
||||
{
|
||||
int64_t start_pos; // Starting position in the sequence
|
||||
int64_t token_pos; // Position of the vision token
|
||||
int64_t patch_count; // Number of patches
|
||||
int64_t grid_t, grid_h, grid_w; // Grid dimensions (T, H, W)
|
||||
float time_interval; // Time interval (for video)
|
||||
int64_t is_video; // 0 = image, 1 = video
|
||||
int64_t position_offset; // Positional encoding offset
|
||||
};
|
||||
|
||||
// Device function: fast integer division using reciprocal multiplication
|
||||
__device__ __forceinline__ int64_t fast_div(int64_t a, int64_t b)
|
||||
{
|
||||
return __float2ll_rd(__ll2float_rn(a) * __frcp_rn(__ll2float_rn(b)));
|
||||
}
|
||||
|
||||
// Device function: map 3D patch index to (t, h, w) coordinates
|
||||
__device__ __forceinline__ void get_3d_coords(int64_t patch_idx, int64_t H, int64_t W,
|
||||
int64_t &t, int64_t &h, int64_t &w)
|
||||
{
|
||||
int64_t hw = H * W;
|
||||
t = fast_div(patch_idx, hw);
|
||||
int64_t remaining = patch_idx - t * hw;
|
||||
h = fast_div(remaining, W);
|
||||
w = remaining - h * W;
|
||||
}
|
||||
|
||||
// Stage 1: Kernel to count image and video tokens per batch
|
||||
__global__ void compute_vision_counts(
|
||||
const int64_t *input_ids, // (batch_size, seq_len)
|
||||
const int64_t *attention_mask, // (batch_size, seq_len)
|
||||
int64_t *image_counts, // (batch_size,) - output: number of images per batch
|
||||
int64_t *video_counts, // (batch_size,) - output: number of videos per batch
|
||||
const int64_t batch_size,
|
||||
const int64_t seq_len,
|
||||
const int64_t image_token_id,
|
||||
const int64_t video_token_id,
|
||||
const int64_t vision_start_token_id)
|
||||
{
|
||||
int64_t batch_idx = blockIdx.x;
|
||||
int64_t thread_idx = threadIdx.x;
|
||||
|
||||
if (batch_idx >= batch_size)
|
||||
return;
|
||||
|
||||
__shared__ int64_t shared_image_counts[MAX_THREADS_PER_BLOCK];
|
||||
__shared__ int64_t shared_video_counts[MAX_THREADS_PER_BLOCK];
|
||||
|
||||
// Initialize per-thread counters
|
||||
int64_t thread_image_count = 0;
|
||||
int64_t thread_video_count = 0;
|
||||
|
||||
// Parallel scan over sequence tokens (each thread processes multiple tokens)
|
||||
for (int64_t i = thread_idx; i < seq_len - 1; i += blockDim.x)
|
||||
{
|
||||
// Skip if masked out
|
||||
if ((attention_mask != nullptr) && attention_mask[batch_idx * seq_len + i] == 0)
|
||||
continue;
|
||||
|
||||
int64_t token_id = input_ids[batch_idx * seq_len + i];
|
||||
|
||||
// Check if current token is a vision start token followed by image/video token
|
||||
if (token_id == vision_start_token_id && i + 1 < seq_len)
|
||||
{
|
||||
int64_t next_token = input_ids[batch_idx * seq_len + i + 1];
|
||||
|
||||
if (next_token == image_token_id)
|
||||
{
|
||||
thread_image_count++;
|
||||
}
|
||||
else if (next_token == video_token_id)
|
||||
{
|
||||
thread_video_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store per-thread counts in shared memory
|
||||
shared_image_counts[thread_idx] = thread_image_count;
|
||||
shared_video_counts[thread_idx] = thread_video_count;
|
||||
__syncthreads();
|
||||
|
||||
// Parallel reduction to sum counts across threads
|
||||
for (int64_t stride = blockDim.x / 2; stride > 0; stride /= 2)
|
||||
{
|
||||
if (thread_idx < stride)
|
||||
{
|
||||
shared_image_counts[thread_idx] += shared_image_counts[thread_idx + stride];
|
||||
shared_video_counts[thread_idx] += shared_video_counts[thread_idx + stride];
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
// Write final result for this batch to global memory
|
||||
if (thread_idx == 0)
|
||||
{
|
||||
image_counts[batch_idx] = shared_image_counts[0];
|
||||
video_counts[batch_idx] = shared_video_counts[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Stage 2: Preprocessing kernel - parse and analyze all vision content
|
||||
__global__ void preprocess_vision_tokens(
|
||||
const int64_t *input_ids, // (batch_size, seq_len)
|
||||
const int64_t *attention_mask, // (batch_size, seq_len)
|
||||
const int64_t *image_grid_thw, // (max_images, 3)
|
||||
const int64_t *video_grid_thw, // (max_videos, 3)
|
||||
const float *second_per_grid_ts, // (max_videos,)
|
||||
const int64_t *image_counts, // (batch_size,)
|
||||
const int64_t *video_counts, // (batch_size,)
|
||||
VisionDescriptor *vision_desc, // (batch_size, MAX_VISION_TOKENS)
|
||||
int64_t *vision_counts, // (batch_size,) - total vision tokens per batch
|
||||
int64_t *text_lengths, // (batch_size, MAX_VISION_TOKENS+1) - lengths of text segments
|
||||
int64_t *position_offsets, // (batch_size, MAX_VISION_TOKENS+1) - cumulative position offsets
|
||||
const int64_t batch_size,
|
||||
const int64_t seq_len,
|
||||
const int64_t spatial_merge_size,
|
||||
const int64_t image_token_id,
|
||||
const int64_t video_token_id,
|
||||
const int64_t vision_start_token_id,
|
||||
const float tokens_per_second)
|
||||
{
|
||||
// Each thread processes one batch sequence
|
||||
int64_t batch_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (batch_idx >= batch_size)
|
||||
return;
|
||||
|
||||
// Compute cumulative image/video indices before this batch
|
||||
int64_t image_idx = 0, video_idx = 0;
|
||||
for (int64_t i = 0; i < batch_idx; i++)
|
||||
{
|
||||
image_idx += image_counts[i];
|
||||
video_idx += video_counts[i];
|
||||
}
|
||||
|
||||
// Local state
|
||||
int64_t vision_count = 0;
|
||||
int64_t current_pos = 0;
|
||||
int64_t position_offset = 0;
|
||||
|
||||
// Sequential scan over the sequence (single-threaded per batch)
|
||||
for (int64_t i = 0; i < seq_len - 1; i++)
|
||||
{
|
||||
// Skip masked tokens
|
||||
if ((attention_mask != nullptr) && attention_mask[batch_idx * seq_len + i] == 0)
|
||||
continue;
|
||||
|
||||
int64_t token_id = input_ids[batch_idx * seq_len + i];
|
||||
|
||||
// Check for vision start token
|
||||
if (token_id == vision_start_token_id)
|
||||
{
|
||||
int64_t next_token = input_ids[batch_idx * seq_len + i + 1];
|
||||
|
||||
if (next_token == image_token_id || next_token == video_token_id)
|
||||
{
|
||||
// Record length of preceding text segment
|
||||
int64_t vision_pos = i + 1;
|
||||
text_lengths[batch_idx * (MAX_VISION_TOKENS + 1) + vision_count] = vision_pos - current_pos;
|
||||
position_offsets[batch_idx * (MAX_VISION_TOKENS + 1) + vision_count] = position_offset;
|
||||
position_offset += (vision_pos - current_pos);
|
||||
|
||||
// Fetch grid dimensions
|
||||
int64_t T, H, W;
|
||||
float time_interval = 0.0f;
|
||||
int64_t is_video = (next_token == video_token_id) ? 1 : 0;
|
||||
|
||||
if (is_video == 0)
|
||||
{
|
||||
// Image case
|
||||
T = image_grid_thw[image_idx * 3 + 0];
|
||||
H = image_grid_thw[image_idx * 3 + 1];
|
||||
W = image_grid_thw[image_idx * 3 + 2];
|
||||
image_idx++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Video case
|
||||
T = video_grid_thw[video_idx * 3 + 0];
|
||||
H = video_grid_thw[video_idx * 3 + 1];
|
||||
W = video_grid_thw[video_idx * 3 + 2];
|
||||
time_interval = second_per_grid_ts[video_idx];
|
||||
video_idx++;
|
||||
}
|
||||
|
||||
// Compute number of patches after spatial merging
|
||||
int64_t H_merged = H / spatial_merge_size;
|
||||
int64_t W_merged = W / spatial_merge_size;
|
||||
int64_t patch_count = T * H_merged * W_merged;
|
||||
|
||||
// Populate vision descriptor
|
||||
if (vision_count < MAX_VISION_TOKENS)
|
||||
{
|
||||
VisionDescriptor &desc = vision_desc[batch_idx * MAX_VISION_TOKENS + vision_count];
|
||||
desc.start_pos = current_pos;
|
||||
desc.token_pos = vision_pos;
|
||||
desc.patch_count = patch_count;
|
||||
desc.grid_t = T;
|
||||
desc.grid_h = H_merged;
|
||||
desc.grid_w = W_merged;
|
||||
desc.time_interval = time_interval;
|
||||
desc.is_video = is_video;
|
||||
desc.position_offset = position_offset;
|
||||
|
||||
// Update position offset based on content type
|
||||
if (is_video)
|
||||
{
|
||||
position_offset += max(static_cast<int64_t>((T - 1) * time_interval * tokens_per_second) + 1,
|
||||
static_cast<int64_t>(max(H_merged, W_merged)));
|
||||
}
|
||||
else
|
||||
{
|
||||
position_offset += max(H_merged, W_merged);
|
||||
}
|
||||
|
||||
current_pos = vision_pos + patch_count;
|
||||
vision_count++;
|
||||
}
|
||||
|
||||
// Skip over the vision patch tokens
|
||||
i = vision_pos + patch_count - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store total vision token count for this batch
|
||||
vision_counts[batch_idx] = vision_count;
|
||||
|
||||
// Handle final text segment after last vision token
|
||||
int64_t effective_len = seq_len;
|
||||
if (attention_mask != nullptr)
|
||||
{
|
||||
// Find last valid (unmasked) token
|
||||
for (int64_t i = seq_len - 1; i >= 0; i--)
|
||||
{
|
||||
if (attention_mask[batch_idx * seq_len + i] != 0)
|
||||
{
|
||||
effective_len = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text_lengths[batch_idx * (MAX_VISION_TOKENS + 1) + vision_count] = effective_len - current_pos;
|
||||
position_offsets[batch_idx * (MAX_VISION_TOKENS + 1) + vision_count] = position_offset;
|
||||
}
|
||||
|
||||
|
||||
// Stage 3: Main kernel - compute 3D position IDs for all tokens in parallel
|
||||
__global__ void compute_3d_positions(
|
||||
const int64_t *input_ids, // (batch_size, seq_len)
|
||||
const int64_t *attention_mask, // (batch_size, seq_len)
|
||||
const VisionDescriptor *vision_desc, // (batch_size, MAX_VISION_TOKENS)
|
||||
const int64_t *vision_counts, // (batch_size,)
|
||||
const int64_t *text_lengths, // (batch_size, MAX_VISION_TOKENS+1)
|
||||
const int64_t *position_offsets, // (batch_size, MAX_VISION_TOKENS+1)
|
||||
int64_t *position_ids, // (3, batch_size, seq_len) - output 3D position IDs
|
||||
int64_t *mrope_deltas, // (batch_size,) - RoPE length adjustment
|
||||
const int64_t batch_size,
|
||||
const int64_t seq_len,
|
||||
const float tokens_per_second)
|
||||
{
|
||||
// Grid: (batch_size), Block: (threads_per_block)
|
||||
int64_t batch_idx = blockIdx.x;
|
||||
int64_t thread_idx = threadIdx.x;
|
||||
|
||||
if (batch_idx >= batch_size)
|
||||
return;
|
||||
|
||||
__shared__ VisionDescriptor shared_visions[MAX_VISION_TOKENS];
|
||||
__shared__ int64_t shared_position_offsets[MAX_VISION_TOKENS + 1];
|
||||
__shared__ int64_t shared_max_positions[MAX_THREADS_PER_BLOCK]; // For block-wide reduction
|
||||
|
||||
int64_t shared_vision_count = vision_counts[batch_idx];
|
||||
|
||||
// Cooperative loading of vision descriptors into shared memory
|
||||
for (int64_t i = thread_idx; i < MAX_VISION_TOKENS; i += blockDim.x)
|
||||
{
|
||||
if (i < shared_vision_count)
|
||||
{
|
||||
shared_visions[i] = vision_desc[batch_idx * MAX_VISION_TOKENS + i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int64_t i = thread_idx; i < MAX_VISION_TOKENS + 1; i += blockDim.x)
|
||||
{
|
||||
shared_position_offsets[i] = position_offsets[batch_idx * (MAX_VISION_TOKENS + 1) + i];
|
||||
}
|
||||
|
||||
// Initialize per-thread max position
|
||||
int64_t thread_max_position = -1;
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Parallel processing: each thread handles multiple tokens
|
||||
for (int64_t token_idx = thread_idx; token_idx < seq_len; token_idx += blockDim.x)
|
||||
{
|
||||
// Check validity via attention mask
|
||||
int mask_offset = 0;
|
||||
bool is_valid_token = true;
|
||||
if (attention_mask != nullptr)
|
||||
{
|
||||
is_valid_token = attention_mask[batch_idx * seq_len + token_idx] != 0;
|
||||
for (int i = 0; i < token_idx; i++) {
|
||||
if (attention_mask[batch_idx * seq_len + i] == 0) {
|
||||
mask_offset += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid_token)
|
||||
{
|
||||
// Set masked tokens to default position (1)
|
||||
position_ids[0 * batch_size * seq_len + batch_idx * seq_len + token_idx] = 1;
|
||||
position_ids[1 * batch_size * seq_len + batch_idx * seq_len + token_idx] = 1;
|
||||
position_ids[2 * batch_size * seq_len + batch_idx * seq_len + token_idx] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine which segment this token belongs to
|
||||
int64_t segment_idx = -9999999;
|
||||
int64_t local_pos = token_idx;
|
||||
|
||||
// Linear search (vision count is small, so this is efficient)
|
||||
for (int64_t v = 0; v < shared_vision_count; v++)
|
||||
{
|
||||
if (token_idx < shared_visions[v].token_pos)
|
||||
{
|
||||
// Token belongs to text segment before vision v
|
||||
segment_idx = v;
|
||||
local_pos = token_idx - (v > 0 ? shared_visions[v - 1].token_pos + shared_visions[v - 1].patch_count : 0);
|
||||
break;
|
||||
}
|
||||
else if (token_idx < shared_visions[v].token_pos + shared_visions[v].patch_count)
|
||||
{
|
||||
// Token belongs to vision patch v
|
||||
segment_idx = -(v + 1); // Negative index indicates vision
|
||||
local_pos = token_idx - shared_visions[v].token_pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (segment_idx == -9999999)
|
||||
{
|
||||
// Token belongs to final text segment
|
||||
segment_idx = shared_vision_count;
|
||||
int64_t last_vision_end = 0;
|
||||
if (shared_vision_count > 0)
|
||||
{
|
||||
last_vision_end = shared_visions[shared_vision_count - 1].token_pos +
|
||||
shared_visions[shared_vision_count - 1].patch_count;
|
||||
}
|
||||
local_pos = token_idx - last_vision_end;
|
||||
}
|
||||
|
||||
int64_t pos_t, pos_h, pos_w;
|
||||
|
||||
if (segment_idx >= 0)
|
||||
{
|
||||
// Text token: use 1D position encoding
|
||||
int64_t offset = shared_position_offsets[segment_idx];
|
||||
pos_t = pos_h = pos_w = offset + local_pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vision patch: compute 3D position encoding
|
||||
int64_t vision_idx = -(segment_idx + 1);
|
||||
const VisionDescriptor &desc = shared_visions[vision_idx];
|
||||
|
||||
// Map linear patch index to (t, h, w)
|
||||
int64_t t, h, w;
|
||||
get_3d_coords(local_pos, desc.grid_h, desc.grid_w, t, h, w);
|
||||
|
||||
// Compute 3D positions with temporal scaling for video
|
||||
pos_t = static_cast<int64_t>(t * desc.time_interval * tokens_per_second) + desc.position_offset;
|
||||
pos_h = h + desc.position_offset;
|
||||
pos_w = w + desc.position_offset;
|
||||
}
|
||||
|
||||
// Write 3D position IDs
|
||||
position_ids[0 * batch_size * seq_len + batch_idx * seq_len + token_idx] = pos_t - mask_offset;
|
||||
position_ids[1 * batch_size * seq_len + batch_idx * seq_len + token_idx] = pos_h - mask_offset;
|
||||
position_ids[2 * batch_size * seq_len + batch_idx * seq_len + token_idx] = pos_w - mask_offset;
|
||||
|
||||
// Track per-thread maximum position
|
||||
int64_t max_pos = max(pos_t - mask_offset, max(pos_h - mask_offset, pos_w - mask_offset));
|
||||
thread_max_position = max(thread_max_position, max_pos);
|
||||
}
|
||||
|
||||
// Store per-thread max into shared memory
|
||||
shared_max_positions[thread_idx] = thread_max_position;
|
||||
__syncthreads();
|
||||
|
||||
// Block-wide reduction to find global max position
|
||||
for (int64_t stride = blockDim.x / 2; stride > 0; stride /= 2)
|
||||
{
|
||||
if (thread_idx < stride)
|
||||
{
|
||||
shared_max_positions[thread_idx] = max(shared_max_positions[thread_idx],
|
||||
shared_max_positions[thread_idx + stride]);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
// Compute mRoPE delta: (max_pos + 1) - seq_len
|
||||
if (thread_idx == 0)
|
||||
{
|
||||
int64_t global_max_position = shared_max_positions[0];
|
||||
mrope_deltas[batch_idx] = global_max_position + 1 - seq_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback kernel when no vision tokens exist: assign sequential positions based on attention mask
|
||||
__global__ void compute_3d_positions_mask_text(
|
||||
const int64_t* __restrict__ attention_mask, // (batch_size, seq_len)
|
||||
int64_t* __restrict__ position_ids, // (3, batch_size, seq_len) - flattened row-major
|
||||
int64_t* __restrict__ mrope_deltas, // (batch_size,)
|
||||
const int64_t batch_size,
|
||||
const int64_t seq_len
|
||||
) {
|
||||
// One block per batch
|
||||
int batch_idx = blockIdx.x;
|
||||
if (batch_idx >= batch_size) return;
|
||||
|
||||
extern __shared__ int64_t shared_mask[];
|
||||
int64_t* mask = shared_mask;
|
||||
|
||||
// Load attention mask for current batch into shared memory
|
||||
for (int i = threadIdx.x; i < seq_len; i += blockDim.x) {
|
||||
mask[i] = attention_mask[batch_idx * seq_len + i];
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
int tid = threadIdx.x;
|
||||
int64_t base_offset = batch_idx * seq_len;
|
||||
int64_t total_valid = 0;
|
||||
|
||||
// Compute prefix sum (cumulative count of valid tokens)
|
||||
// Simple O(seq_len^2) per-thread loop - acceptable for moderate seq_len
|
||||
for (int idx = tid; idx < seq_len; idx += blockDim.x) {
|
||||
int64_t sum = 0;
|
||||
for (int i = 0; i <= idx; ++i) {
|
||||
sum += mask[i];
|
||||
}
|
||||
int64_t pos_val = (mask[idx] ? (sum - 1) : 1); // Use 0-based if valid, else 1
|
||||
|
||||
// Write same position to all three dimensions
|
||||
int64_t flat_idx0 = 0 * batch_size * seq_len + base_offset + idx;
|
||||
int64_t flat_idx1 = 1 * batch_size * seq_len + base_offset + idx;
|
||||
int64_t flat_idx2 = 2 * batch_size * seq_len + base_offset + idx;
|
||||
|
||||
position_ids[flat_idx0] = pos_val;
|
||||
position_ids[flat_idx1] = pos_val;
|
||||
position_ids[flat_idx2] = pos_val;
|
||||
|
||||
// Only thread 0 tracks total valid tokens
|
||||
if (tid == 0) {
|
||||
total_valid = sum; // After last idx, sum = total number of 1s
|
||||
}
|
||||
}
|
||||
|
||||
// Compute mrope_deltas: (total_valid) - seq_len
|
||||
if (tid == 0) {
|
||||
// Re-compute total_valid robustly
|
||||
total_valid = 0;
|
||||
for (int i = 0; i < seq_len; ++i) {
|
||||
total_valid += mask[i];
|
||||
}
|
||||
mrope_deltas[batch_idx] = total_valid - seq_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Kernel to fill position_ids with arange(seq_len) in 3D layout when no vision or mask
|
||||
__global__ void arange_3d(int64_t seq_len,
|
||||
int64_t* out) {
|
||||
int i = blockIdx.x; // dimension index: 0, 1, 2
|
||||
int j = blockIdx.y; // batch index
|
||||
int64_t k = blockIdx.z * blockDim.x + threadIdx.x; // token index
|
||||
|
||||
if (k >= seq_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Flatten index: (dim, batch, token) -> linear
|
||||
int64_t idx = i * gridDim.y * seq_len + j * seq_len + k;
|
||||
out[idx] = k;
|
||||
}
|
||||
|
||||
// Kernel to zero-initialize a 1D tensor
|
||||
__global__ void fill_zeros_1d(int64_t numel, int64_t* data) {
|
||||
int64_t idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx < numel) {
|
||||
data[idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute required workspace size (in bytes)
|
||||
int64_t GetRopeIndexGetWorkSpace(const c10::optional<at::Tensor>& input_ids,
|
||||
const c10::optional<at::Tensor>& image_grid_thw,
|
||||
const c10::optional<at::Tensor>& video_grid_thw) {
|
||||
|
||||
if (!image_grid_thw.has_value() && !video_grid_thw.has_value()) {
|
||||
return 1; // minimal workspace
|
||||
} else {
|
||||
const at::Tensor& input_ids_tensor = input_ids.value();
|
||||
const int batch_size = input_ids_tensor.size(0);
|
||||
return (batch_size * MAX_VISION_TOKENS * static_cast<int64_t>(sizeof(VisionDescriptor)) +
|
||||
3 * batch_size * sizeof(int64_t) +
|
||||
2 * batch_size * (MAX_VISION_TOKENS + 1) * sizeof(int64_t));
|
||||
}
|
||||
}
|
||||
|
||||
// Main entry function: compute RoPE position IDs and mRoPE deltas
|
||||
void GetRopeIndex(const c10::optional<at::Tensor>& input_ids,
|
||||
const c10::optional<at::Tensor>& image_grid_thw,
|
||||
const c10::optional<at::Tensor>& video_grid_thw,
|
||||
const c10::optional<at::Tensor>& second_per_grid_ts,
|
||||
const c10::optional<at::Tensor>& attention_mask,
|
||||
const at::Tensor& position_ids,
|
||||
const at::Tensor& mrope_deltas,
|
||||
const at::Tensor& workspace,
|
||||
int spatial_merge_size,
|
||||
int image_token_id,
|
||||
int video_token_id,
|
||||
int vision_start_token_id,
|
||||
float tokens_per_second) {
|
||||
|
||||
ASSERT_CHECK(input_ids.has_value());
|
||||
|
||||
const at::Tensor& input_ids_tensor = input_ids.value();
|
||||
ASSERT_CHECK(input_ids_tensor.dim() == 2);
|
||||
ASSERT_CHECK(input_ids_tensor.is_contiguous());
|
||||
|
||||
const auto batch_size = input_ids_tensor.size(0);
|
||||
const auto seq_len = input_ids_tensor.size(1);
|
||||
const auto device = input_ids_tensor.device();
|
||||
|
||||
const int64_t *input_ids_ptr = static_cast<const int64_t*>(input_ids_tensor.data_ptr());
|
||||
|
||||
const int64_t *attention_mask_ptr = nullptr;
|
||||
|
||||
if (attention_mask.has_value()) {
|
||||
const at::Tensor& attention_mask_tensor = attention_mask.value();
|
||||
ASSERT_CHECK(attention_mask_tensor.is_contiguous());
|
||||
attention_mask_ptr = static_cast<const int64_t*>(attention_mask_tensor.data_ptr());
|
||||
}
|
||||
|
||||
const int64_t *image_grid_thw_ptr = nullptr;
|
||||
|
||||
if (image_grid_thw.has_value()) {
|
||||
const at::Tensor& image_grid_thw_tensor = image_grid_thw.value();
|
||||
ASSERT_CHECK(image_grid_thw_tensor.dim() == 2 && image_grid_thw_tensor.size(1) == 3);
|
||||
ASSERT_CHECK(image_grid_thw_tensor.is_contiguous());
|
||||
image_grid_thw_ptr = static_cast<const int64_t*>(image_grid_thw_tensor.data_ptr());
|
||||
}
|
||||
|
||||
const int64_t *video_grid_thw_ptr = nullptr;
|
||||
|
||||
if (video_grid_thw.has_value()) {
|
||||
const at::Tensor& video_grid_thw_tensor = video_grid_thw.value();
|
||||
ASSERT_CHECK(video_grid_thw_tensor.is_contiguous());
|
||||
ASSERT_CHECK(video_grid_thw_tensor.dim() == 2 && video_grid_thw_tensor.size(1) == 3);
|
||||
video_grid_thw_ptr = static_cast<const int64_t*>(video_grid_thw_tensor.data_ptr());
|
||||
}
|
||||
|
||||
const float *second_per_grid_ts_ptr = nullptr;
|
||||
|
||||
if (second_per_grid_ts.has_value()) {
|
||||
const at::Tensor& second_per_grid_ts_tensor = second_per_grid_ts.value();
|
||||
ASSERT_CHECK(second_per_grid_ts_tensor.is_contiguous());
|
||||
ASSERT_CHECK(second_per_grid_ts_tensor.dim() == 1);
|
||||
second_per_grid_ts_ptr = static_cast<const float*>(second_per_grid_ts_tensor.data_ptr());
|
||||
}
|
||||
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
// Case: no vision tokens -> use simple positional encoding
|
||||
if (!image_grid_thw.has_value() && !video_grid_thw.has_value()) {
|
||||
if (attention_mask.has_value()) {
|
||||
// Use attention mask to compute valid positions
|
||||
int block_size = min(1024, (int)seq_len);
|
||||
dim3 grid(batch_size);
|
||||
dim3 block(block_size);
|
||||
size_t shared_mem_size = seq_len * sizeof(int64_t);
|
||||
|
||||
compute_3d_positions_mask_text<<<grid, block, shared_mem_size>>>(
|
||||
attention_mask_ptr, static_cast<int64_t*>(position_ids.data_ptr()), static_cast<int64_t*>(mrope_deltas.data_ptr()), batch_size, seq_len
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
// No mask, no vision -> use simple arange
|
||||
dim3 thread_num(256);
|
||||
dim3 grid(3, batch_size, (seq_len + 255) / 256);
|
||||
arange_3d<<<grid, thread_num, 0, stream>>>(seq_len, static_cast<int64_t*>(position_ids.data_ptr()));
|
||||
|
||||
int block_size = 256;
|
||||
int grid_size = (batch_size + block_size - 1) / block_size;
|
||||
fill_zeros_1d<<<grid_size, block_size, 0, stream>>>(batch_size, static_cast<int64_t*>(mrope_deltas.data_ptr()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate workspace buffers
|
||||
void* work_ptr = workspace.data_ptr();
|
||||
VisionDescriptor *d_vision_desc = reinterpret_cast<VisionDescriptor*>(work_ptr);
|
||||
|
||||
int64_t *d_vision_counts = reinterpret_cast<int64_t*>(reinterpret_cast<VisionDescriptor*>(work_ptr) + batch_size * MAX_VISION_TOKENS);
|
||||
int64_t *d_text_lengths = d_vision_counts + batch_size;
|
||||
int64_t *d_position_offsets = d_text_lengths + batch_size * (MAX_VISION_TOKENS + 1);
|
||||
int64_t *d_image_counts = d_position_offsets + batch_size * (MAX_VISION_TOKENS + 1);
|
||||
int64_t *d_video_counts = d_image_counts + batch_size;
|
||||
|
||||
// Stage 1: count vision tokens
|
||||
dim3 index_grid(static_cast<unsigned int>(batch_size));
|
||||
dim3 index_block(256);
|
||||
|
||||
compute_vision_counts<<<index_grid, index_block, 0, stream>>>(
|
||||
input_ids_ptr, attention_mask_ptr,
|
||||
d_image_counts, d_video_counts,
|
||||
batch_size, seq_len, image_token_id, video_token_id, vision_start_token_id);
|
||||
|
||||
// Stage 2: preprocess vision tokens
|
||||
int64_t threads_per_block = std::min(static_cast<int64_t>(batch_size), static_cast<int64_t>(256));
|
||||
int64_t num_blocks = (batch_size + threads_per_block - 1) / threads_per_block;
|
||||
|
||||
dim3 preprocess_grid(static_cast<unsigned int>(num_blocks));
|
||||
dim3 preprocess_block(static_cast<unsigned int>(threads_per_block));
|
||||
|
||||
preprocess_vision_tokens<<<preprocess_grid, preprocess_block, 0, stream>>>(
|
||||
input_ids_ptr, attention_mask_ptr, image_grid_thw_ptr, video_grid_thw_ptr,
|
||||
second_per_grid_ts_ptr, d_image_counts, d_video_counts,
|
||||
d_vision_desc, d_vision_counts, d_text_lengths, d_position_offsets,
|
||||
batch_size, seq_len, spatial_merge_size,
|
||||
image_token_id, video_token_id, vision_start_token_id, tokens_per_second);
|
||||
|
||||
// Stage 3: compute 3D positions
|
||||
threads_per_block = std::min(static_cast<int64_t>(seq_len), static_cast<int64_t>(MAX_THREADS_PER_BLOCK));
|
||||
if (threads_per_block < 32) threads_per_block = 32;
|
||||
|
||||
int64_t power_of_2 = 1;
|
||||
while (power_of_2 < threads_per_block) power_of_2 *= 2;
|
||||
if (power_of_2 > MAX_THREADS_PER_BLOCK) power_of_2 = MAX_THREADS_PER_BLOCK;
|
||||
threads_per_block = power_of_2;
|
||||
|
||||
dim3 compute_grid(static_cast<unsigned int>(batch_size));
|
||||
dim3 compute_block(static_cast<unsigned int>(threads_per_block));
|
||||
|
||||
compute_3d_positions<<<compute_grid, compute_block, 0, stream>>>(
|
||||
input_ids_ptr, attention_mask_ptr, d_vision_desc, d_vision_counts,
|
||||
d_text_lengths, d_position_offsets,
|
||||
static_cast<int64_t*>(position_ids.data_ptr()), static_cast<int64_t*>(mrope_deltas.data_ptr()),
|
||||
batch_size, seq_len, tokens_per_second);
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
} // wallx_cuda_get_rope_index
|
||||
|
||||
// TVM FFI exports
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
@@ -0,0 +1,840 @@
|
||||
// Re-enable CUDA half operators (PyTorch disables them)
|
||||
#undef __CUDA_NO_HALF_OPERATORS__
|
||||
#undef __CUDA_NO_HALF_CONVERSIONS__
|
||||
#undef __CUDA_NO_HALF2_OPERATORS__
|
||||
#undef __CUDA_NO_BFLOAT16_CONVERSIONS__
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <cassert>
|
||||
#include <cuda_runtime.h>
|
||||
#include <cmath>
|
||||
#include <cfloat>
|
||||
#include "../common/cuda_utils.h"
|
||||
|
||||
namespace wallx_cuda_m_rope {
|
||||
|
||||
// Helper: convert vector of T to float4
|
||||
template<typename T>
|
||||
__device__ __forceinline__ float4 to_float4(const T* ptr) {
|
||||
if constexpr (std::is_same_v<T, half>) {
|
||||
half2 h2_0 = reinterpret_cast<const half2*>(ptr)[0];
|
||||
half2 h2_1 = reinterpret_cast<const half2*>(ptr)[1];
|
||||
float2 f2_0 = __half22float2(h2_0);
|
||||
float2 f2_1 = __half22float2(h2_1);
|
||||
return make_float4(f2_0.x, f2_0.y, f2_1.x, f2_1.y);
|
||||
} else if constexpr (std::is_same_v<T, __nv_bfloat16>) {
|
||||
__nv_bfloat162 b2_0 = reinterpret_cast<const __nv_bfloat162*>(ptr)[0];
|
||||
__nv_bfloat162 b2_1 = reinterpret_cast<const __nv_bfloat162*>(ptr)[1];
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
float2 f2_0 = __bfloat1622float2(b2_0);
|
||||
float2 f2_1 = __bfloat1622float2(b2_1);
|
||||
return make_float4(f2_0.x, f2_0.y, f2_1.x, f2_1.y);
|
||||
#else
|
||||
assert(false && "Unsupported on arch < 800");
|
||||
#endif
|
||||
} else if constexpr (std::is_same_v<T, float>) {
|
||||
return reinterpret_cast<const float4*>(ptr)[0];
|
||||
} else {
|
||||
static_assert(sizeof(T) == 0, "Unsupported type");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: convert float4 back to vector of T
|
||||
template<typename T>
|
||||
__device__ __forceinline__ void from_float4(T* ptr, const float4& f4) {
|
||||
if constexpr (std::is_same_v<T, half>) {
|
||||
half2 h2_0 = __float22half2_rn(make_float2(f4.x, f4.y));
|
||||
half2 h2_1 = __float22half2_rn(make_float2(f4.z, f4.w));
|
||||
reinterpret_cast<half2*>(ptr)[0] = h2_0;
|
||||
reinterpret_cast<half2*>(ptr)[1] = h2_1;
|
||||
} else if constexpr (std::is_same_v<T, __nv_bfloat16>) {
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
__nv_bfloat162 b2_0 = __float22bfloat162_rn(make_float2(f4.x, f4.y));
|
||||
__nv_bfloat162 b2_1 = __float22bfloat162_rn(make_float2(f4.z, f4.w));
|
||||
reinterpret_cast<__nv_bfloat162*>(ptr)[0] = b2_0;
|
||||
reinterpret_cast<__nv_bfloat162*>(ptr)[1] = b2_1;
|
||||
#else
|
||||
assert(false && "Unsupported on arch < 800");
|
||||
#endif
|
||||
} else if constexpr (std::is_same_v<T, float>) {
|
||||
reinterpret_cast<float4*>(ptr)[0] = f4;
|
||||
} else {
|
||||
static_assert(sizeof(T) == 0, "Unsupported type");
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
__global__ void MRopeKernel(const float* cos,
|
||||
const float* sin,
|
||||
const T* q,
|
||||
const int q_h,
|
||||
const T* k,
|
||||
const int k_h,
|
||||
T* q_embed,
|
||||
T* k_embed,
|
||||
const int first,
|
||||
const int second,
|
||||
const int d,
|
||||
const int qb_stride,
|
||||
const int qs_stride,
|
||||
const int qh_stride,
|
||||
const int kb_stride,
|
||||
const int ks_stride,
|
||||
const int kh_stride,
|
||||
const int qeb_stride,
|
||||
const int qes_stride,
|
||||
const int qeh_stride,
|
||||
const int keb_stride,
|
||||
const int kes_stride,
|
||||
const int keh_stride) {
|
||||
extern __shared__ char cos_sin[];
|
||||
const int half_dim = d / 2;
|
||||
float* cos_smem = reinterpret_cast<float*>(cos_sin);
|
||||
float* sin_smem = cos_smem + half_dim;
|
||||
int b = blockIdx.x;
|
||||
int s = blockIdx.y;
|
||||
|
||||
int64_t offset = gridDim.x * gridDim.y * half_dim;
|
||||
int64_t cos_sin_b_stride = gridDim.y * half_dim;
|
||||
int64_t cos_sin_s_stride = half_dim;
|
||||
#define SIN_GMEM(a, b, c, d) sin[(a) * offset + (b) * cos_sin_b_stride + (c) * cos_sin_s_stride + (d)]
|
||||
#define COS_GMEM(a, b, c, d) cos[(a) * offset + (b) * cos_sin_b_stride + (c) * cos_sin_s_stride + (d)]
|
||||
|
||||
for (int i = threadIdx.x; i < half_dim; i += blockDim.x) {
|
||||
if (i < first) {
|
||||
cos_smem[i] = COS_GMEM(0, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(0, b, s, i);
|
||||
} else if (i < (second + first)) {
|
||||
cos_smem[i] = COS_GMEM(1, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(1, b, s, i);
|
||||
} else {
|
||||
cos_smem[i] = COS_GMEM(2, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(2, b, s, i);
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
#define Q_GMEM(a, b, c, d) q[(a) * qb_stride + (b) * qs_stride + (c) * qh_stride + d]
|
||||
#define K_GMEM(a, b, c, d) k[(a) * kb_stride + (b) * ks_stride + (c) * kh_stride + d]
|
||||
#define Q_EMBED_GMEM(a, b, c, d) q_embed[(a) * qeb_stride + (b) * qes_stride + (c) * qeh_stride + d]
|
||||
#define K_EMBED_GMEM(a, b, c, d) k_embed[(a) * keb_stride + (b) * kes_stride + (c) * keh_stride + d]
|
||||
|
||||
for (int j = threadIdx.x * 4; j < q_h * half_dim; j += blockDim.x * 4) {
|
||||
int h_idx = j / half_dim;
|
||||
int base_d = j % half_dim;
|
||||
if (base_d + 3 >= half_dim) continue;
|
||||
|
||||
float4 q_x0 = to_float4<T>(&Q_GMEM(b, s, h_idx, base_d));
|
||||
float4 q_x1 = to_float4<T>(&Q_GMEM(b, s, h_idx, base_d + half_dim));
|
||||
|
||||
float4 cos_vec = to_float4<float>(&cos_smem[base_d]);
|
||||
float4 sin_vec = to_float4<float>(&sin_smem[base_d]);
|
||||
|
||||
float4 q0_rot = make_float4(
|
||||
q_x0.x * cos_vec.x - q_x1.x * sin_vec.x,
|
||||
q_x0.y * cos_vec.y - q_x1.y * sin_vec.y,
|
||||
q_x0.z * cos_vec.z - q_x1.z * sin_vec.z,
|
||||
q_x0.w * cos_vec.w - q_x1.w * sin_vec.w
|
||||
);
|
||||
|
||||
float4 q1_rot = make_float4(
|
||||
q_x1.x * cos_vec.x + q_x0.x * sin_vec.x,
|
||||
q_x1.y * cos_vec.y + q_x0.y * sin_vec.y,
|
||||
q_x1.z * cos_vec.z + q_x0.z * sin_vec.z,
|
||||
q_x1.w * cos_vec.w + q_x0.w * sin_vec.w
|
||||
);
|
||||
|
||||
from_float4<T>(&Q_EMBED_GMEM(b, s, h_idx, base_d), q0_rot);
|
||||
from_float4<T>(&Q_EMBED_GMEM(b, s, h_idx, base_d + half_dim), q1_rot);
|
||||
}
|
||||
|
||||
for (int j = threadIdx.x * 4; j < k_h * half_dim; j += blockDim.x * 4) {
|
||||
int h_idx = j / half_dim;
|
||||
int base_d = j % half_dim;
|
||||
if (base_d + 3 >= half_dim) continue;
|
||||
|
||||
float4 k_x0 = to_float4<T>(&K_GMEM(b, s, h_idx, base_d));
|
||||
float4 k_x1 = to_float4<T>(&K_GMEM(b, s, h_idx, base_d + half_dim));
|
||||
|
||||
float4 cos_vec = to_float4<float>(&cos_smem[base_d]);
|
||||
float4 sin_vec = to_float4<float>(&sin_smem[base_d]);
|
||||
|
||||
float4 k0_rot = make_float4(
|
||||
k_x0.x * cos_vec.x - k_x1.x * sin_vec.x,
|
||||
k_x0.y * cos_vec.y - k_x1.y * sin_vec.y,
|
||||
k_x0.z * cos_vec.z - k_x1.z * sin_vec.z,
|
||||
k_x0.w * cos_vec.w - k_x1.w * sin_vec.w
|
||||
);
|
||||
|
||||
float4 k1_rot = make_float4(
|
||||
k_x1.x * cos_vec.x + k_x0.x * sin_vec.x,
|
||||
k_x1.y * cos_vec.y + k_x0.y * sin_vec.y,
|
||||
k_x1.z * cos_vec.z + k_x0.z * sin_vec.z,
|
||||
k_x1.w * cos_vec.w + k_x0.w * sin_vec.w
|
||||
);
|
||||
|
||||
from_float4<T>(&K_EMBED_GMEM(b, s, h_idx, base_d), k0_rot);
|
||||
from_float4<T>(&K_EMBED_GMEM(b, s, h_idx, base_d + half_dim), k1_rot);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
__global__ void MRopeInplaceKernel(float* cos, float* sin,
|
||||
T* q, const int q_h, T* k, const int k_h,
|
||||
const int first, const int second,
|
||||
const int d, const int qb_stride, const int qs_stride, const int qh_stride,
|
||||
const int kb_stride, const int ks_stride, const int kh_stride) {
|
||||
extern __shared__ char cos_sin[];
|
||||
const int half_dim = d / 2;
|
||||
float* cos_smem = reinterpret_cast<float*>(cos_sin);
|
||||
float* sin_smem = cos_smem + half_dim;
|
||||
int b = blockIdx.x;
|
||||
int s = blockIdx.y;
|
||||
|
||||
int64_t offset = gridDim.x * gridDim.y * half_dim;
|
||||
int64_t cos_sin_b_stride = gridDim.y * half_dim;
|
||||
int64_t cos_sin_s_stride = half_dim;
|
||||
#define SIN_GMEM(a, b, c, d) sin[(a) * offset + (b) * cos_sin_b_stride + (c) * cos_sin_s_stride + (d)]
|
||||
#define COS_GMEM(a, b, c, d) cos[(a) * offset + (b) * cos_sin_b_stride + (c) * cos_sin_s_stride + (d)]
|
||||
|
||||
for (int i = threadIdx.x; i < half_dim; i += blockDim.x) {
|
||||
if (i < first) {
|
||||
cos_smem[i] = COS_GMEM(0, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(0, b, s, i);
|
||||
} else if (i < (second + first)) {
|
||||
cos_smem[i] = COS_GMEM(1, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(1, b, s, i);
|
||||
} else {
|
||||
cos_smem[i] = COS_GMEM(2, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(2, b, s, i);
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
#define Q_GMEM(a, b, c, d) q[(a) * qb_stride + (b) * qs_stride + (c) * qh_stride + d]
|
||||
#define K_GMEM(a, b, c, d) k[(a) * kb_stride + (b) * ks_stride + (c) * kh_stride + d]
|
||||
|
||||
for (int j = threadIdx.x * 4; j < q_h * half_dim; j += blockDim.x * 4) {
|
||||
int h_idx = j / half_dim;
|
||||
int base_d = j % half_dim;
|
||||
if (base_d + 3 >= half_dim) continue;
|
||||
|
||||
float4 q_x0 = to_float4<T>(&Q_GMEM(b, s, h_idx, base_d));
|
||||
float4 q_x1 = to_float4<T>(&Q_GMEM(b, s, h_idx, base_d + half_dim));
|
||||
|
||||
float4 cos_vec = to_float4<float>(&cos_smem[base_d]);
|
||||
float4 sin_vec = to_float4<float>(&sin_smem[base_d]);
|
||||
|
||||
float4 q0_rot = make_float4(
|
||||
q_x0.x * cos_vec.x - q_x1.x * sin_vec.x,
|
||||
q_x0.y * cos_vec.y - q_x1.y * sin_vec.y,
|
||||
q_x0.z * cos_vec.z - q_x1.z * sin_vec.z,
|
||||
q_x0.w * cos_vec.w - q_x1.w * sin_vec.w
|
||||
);
|
||||
|
||||
float4 q1_rot = make_float4(
|
||||
q_x1.x * cos_vec.x + q_x0.x * sin_vec.x,
|
||||
q_x1.y * cos_vec.y + q_x0.y * sin_vec.y,
|
||||
q_x1.z * cos_vec.z + q_x0.z * sin_vec.z,
|
||||
q_x1.w * cos_vec.w + q_x0.w * sin_vec.w
|
||||
);
|
||||
|
||||
from_float4<T>(&Q_GMEM(b, s, h_idx, base_d), q0_rot);
|
||||
from_float4<T>(&Q_GMEM(b, s, h_idx, base_d + half_dim), q1_rot);
|
||||
}
|
||||
|
||||
for (int j = threadIdx.x * 4; j < k_h * half_dim; j += blockDim.x * 4) {
|
||||
int h_idx = j / half_dim;
|
||||
int base_d = j % half_dim;
|
||||
if (base_d + 3 >= half_dim) continue;
|
||||
|
||||
float4 k_x0 = to_float4<T>(&K_GMEM(b, s, h_idx, base_d));
|
||||
float4 k_x1 = to_float4<T>(&K_GMEM(b, s, h_idx, base_d + half_dim));
|
||||
|
||||
float4 cos_vec = to_float4<float>(&cos_smem[base_d]);
|
||||
float4 sin_vec = to_float4<float>(&sin_smem[base_d]);
|
||||
|
||||
float4 k0_rot = make_float4(
|
||||
k_x0.x * cos_vec.x - k_x1.x * sin_vec.x,
|
||||
k_x0.y * cos_vec.y - k_x1.y * sin_vec.y,
|
||||
k_x0.z * cos_vec.z - k_x1.z * sin_vec.z,
|
||||
k_x0.w * cos_vec.w - k_x1.w * sin_vec.w
|
||||
);
|
||||
|
||||
float4 k1_rot = make_float4(
|
||||
k_x1.x * cos_vec.x + k_x0.x * sin_vec.x,
|
||||
k_x1.y * cos_vec.y + k_x0.y * sin_vec.y,
|
||||
k_x1.z * cos_vec.z + k_x0.z * sin_vec.z,
|
||||
k_x1.w * cos_vec.w + k_x0.w * sin_vec.w
|
||||
);
|
||||
|
||||
from_float4<T>(&K_GMEM(b, s, h_idx, base_d), k0_rot);
|
||||
from_float4<T>(&K_GMEM(b, s, h_idx, base_d + half_dim), k1_rot);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
__global__ void MRopeKernelBackward(
|
||||
const float* cos,
|
||||
const float* sin,
|
||||
const T* grad_q_embed,
|
||||
const T* grad_k_embed,
|
||||
T* grad_q,
|
||||
T* grad_k,
|
||||
const int first, const int second,
|
||||
const int q_h,
|
||||
const int k_h,
|
||||
const int d,
|
||||
const int qb_stride, const int qs_stride, const int qh_stride,
|
||||
const int kb_stride, const int ks_stride, const int kh_stride) {
|
||||
|
||||
extern __shared__ char cos_sin[];
|
||||
const int half_dim = d / 2;
|
||||
float* cos_smem = reinterpret_cast<float*>(cos_sin);
|
||||
float* sin_smem = cos_smem + half_dim;
|
||||
|
||||
int b = blockIdx.x;
|
||||
int s = blockIdx.y;
|
||||
|
||||
int64_t offset = gridDim.x * gridDim.y * half_dim;
|
||||
int64_t cos_sin_b_stride = gridDim.y * half_dim;
|
||||
int64_t cos_sin_s_stride = half_dim;
|
||||
|
||||
#define SIN_GMEM(a, b, c, d) sin[(a) * offset + (b) * cos_sin_b_stride + (c) * cos_sin_s_stride + (d)]
|
||||
#define COS_GMEM(a, b, c, d) cos[(a) * offset + (b) * cos_sin_b_stride + (c) * cos_sin_s_stride + (d)]
|
||||
|
||||
for (int i = threadIdx.x; i < half_dim; i += blockDim.x) {
|
||||
if (i < first) {
|
||||
cos_smem[i] = COS_GMEM(0, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(0, b, s, i);
|
||||
} else if (i < (second + first)) {
|
||||
cos_smem[i] = COS_GMEM(1, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(1, b, s, i);
|
||||
} else {
|
||||
cos_smem[i] = COS_GMEM(2, b, s, i);
|
||||
sin_smem[i] = SIN_GMEM(2, b, s, i);
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
#define GQ_EMBED(a, b, c, d) grad_q_embed[(a) * qb_stride + (b) * qs_stride + (c) * qh_stride + (d)]
|
||||
#define GK_EMBED(a, b, c, d) grad_k_embed[(a) * kb_stride + (b) * ks_stride + (c) * kh_stride + (d)]
|
||||
#define GQ(a, b, c, d) grad_q[(a) * qb_stride + (b) * qs_stride + (c) * qh_stride + (d)]
|
||||
#define GK(a, b, c, d) grad_k[(a) * kb_stride + (b) * ks_stride + (c) * kh_stride + (d)]
|
||||
|
||||
// Process grad_q
|
||||
for (int j = threadIdx.x * 4; j < q_h * half_dim; j += blockDim.x * 4) {
|
||||
int h_idx = j / half_dim;
|
||||
int base_d = j % half_dim;
|
||||
if (base_d + 3 >= half_dim) continue;
|
||||
|
||||
// float path
|
||||
float4 gq0_rot = to_float4<T>(&GQ_EMBED(b, s, h_idx, base_d));
|
||||
float4 gq1_rot = to_float4<T>(&GQ_EMBED(b, s, h_idx, base_d + half_dim));
|
||||
|
||||
float4 cos_vec = to_float4<float>(&cos_smem[base_d]);
|
||||
float4 sin_vec = to_float4<float>(&sin_smem[base_d]);
|
||||
|
||||
float4 gq0 = make_float4(
|
||||
gq0_rot.x * cos_vec.x + gq1_rot.x * sin_vec.x,
|
||||
gq0_rot.y * cos_vec.y + gq1_rot.y * sin_vec.y,
|
||||
gq0_rot.z * cos_vec.z + gq1_rot.z * sin_vec.z,
|
||||
gq0_rot.w * cos_vec.w + gq1_rot.w * sin_vec.w
|
||||
);
|
||||
float4 gq1 = make_float4(
|
||||
gq1_rot.x * cos_vec.x - gq0_rot.x * sin_vec.x,
|
||||
gq1_rot.y * cos_vec.y - gq0_rot.y * sin_vec.y,
|
||||
gq1_rot.z * cos_vec.z - gq0_rot.z * sin_vec.z,
|
||||
gq1_rot.w * cos_vec.w - gq0_rot.w * sin_vec.w
|
||||
);
|
||||
|
||||
from_float4<T>(&GQ(b, s, h_idx, base_d), gq0);
|
||||
from_float4<T>(&GQ(b, s, h_idx, base_d + half_dim), gq1);
|
||||
}
|
||||
|
||||
// Process grad_k
|
||||
for (int j = threadIdx.x * 4; j < k_h * half_dim; j += blockDim.x * 4) {
|
||||
int h_idx = j / half_dim;
|
||||
int base_d = j % half_dim;
|
||||
if (base_d + 3 >= half_dim) continue;
|
||||
|
||||
// float path
|
||||
float4 gk0_rot = to_float4<T>(&GK_EMBED(b, s, h_idx, base_d));
|
||||
float4 gk1_rot = to_float4<T>(&GK_EMBED(b, s, h_idx, base_d + half_dim));
|
||||
|
||||
float4 cos_vec = to_float4<float>(&cos_smem[base_d]);
|
||||
float4 sin_vec = to_float4<float>(&sin_smem[base_d]);
|
||||
|
||||
float4 gk0 = make_float4(
|
||||
gk0_rot.x * cos_vec.x + gk1_rot.x * sin_vec.x,
|
||||
gk0_rot.y * cos_vec.y + gk1_rot.y * sin_vec.y,
|
||||
gk0_rot.z * cos_vec.z + gk1_rot.z * sin_vec.z,
|
||||
gk0_rot.w * cos_vec.w + gk1_rot.w * sin_vec.w
|
||||
);
|
||||
float4 gk1 = make_float4(
|
||||
gk1_rot.x * cos_vec.x - gk0_rot.x * sin_vec.x,
|
||||
gk1_rot.y * cos_vec.y - gk0_rot.y * sin_vec.y,
|
||||
gk1_rot.z * cos_vec.z - gk0_rot.z * sin_vec.z,
|
||||
gk1_rot.w * cos_vec.w - gk0_rot.w * sin_vec.w
|
||||
);
|
||||
|
||||
from_float4<T>(&GK(b, s, h_idx, base_d), gk0);
|
||||
from_float4<T>(&GK(b, s, h_idx, base_d + half_dim), gk1);
|
||||
}
|
||||
}
|
||||
|
||||
void MRope(const at::Tensor& q, // [b, s, h, d]
|
||||
const at::Tensor& k, // [b, s, h_k, d]
|
||||
const at::Tensor& q_embed, // [b, s, h, d]
|
||||
const at::Tensor& k_embed, // [b, s, h_k, d]
|
||||
const at::Tensor& cos, // [3, b, s, d / 2]
|
||||
const at::Tensor& sin, // [3, b, s, d / 2]
|
||||
const int first,
|
||||
const int second
|
||||
) {
|
||||
int Nthreads = 256;
|
||||
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
ASSERT_CHECK(q.scalar_type() == k.scalar_type());
|
||||
ASSERT_CHECK(q.scalar_type() == q_embed.scalar_type());
|
||||
ASSERT_CHECK(k.scalar_type() == k_embed.scalar_type());
|
||||
ASSERT_CHECK(q_embed.size(0) == q.size(0));
|
||||
ASSERT_CHECK(q_embed.size(1) == q.size(1));
|
||||
ASSERT_CHECK(q_embed.size(2) == q.size(2));
|
||||
ASSERT_CHECK(q_embed.size(3) == q.size(3));
|
||||
ASSERT_CHECK(k_embed.size(0) == k.size(0));
|
||||
ASSERT_CHECK(k_embed.size(1) == k.size(1));
|
||||
ASSERT_CHECK(k_embed.size(2) == k.size(2));
|
||||
ASSERT_CHECK(k_embed.size(3) == k.size(3));
|
||||
ASSERT_CHECK(q.size(3) % 8 == 0);
|
||||
ASSERT_CHECK(cos.size(0) == 3);
|
||||
ASSERT_CHECK(cos.size(3) == q.size(3) / 2);
|
||||
ASSERT_CHECK(cos.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(sin.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(sin.size(0) == 3);
|
||||
ASSERT_CHECK(q.stride(3) == 1);
|
||||
ASSERT_CHECK(k.stride(3) == 1);
|
||||
ASSERT_CHECK(q_embed.stride(3) == 1);
|
||||
ASSERT_CHECK(k_embed.stride(3) == 1);
|
||||
ASSERT_CHECK(cos.stride(3) == 1);
|
||||
ASSERT_CHECK(sin.stride(3) == 1);
|
||||
ASSERT_CHECK(cos.is_contiguous());
|
||||
ASSERT_CHECK(sin.is_contiguous());
|
||||
|
||||
int64_t batch = q.size(0);
|
||||
int64_t q_head_num = q.size(2);
|
||||
int64_t k_head_num = k.size(2);
|
||||
int64_t seq_len = q.size(1);
|
||||
int64_t dim = q.size(3);
|
||||
int64_t qb_stride = q.stride(0);
|
||||
int64_t kb_stride = k.stride(0);
|
||||
int64_t qs_stride = q.stride(1);
|
||||
int64_t ks_stride = k.stride(1);
|
||||
int64_t qh_stride = q.stride(2);
|
||||
int64_t kh_stride = k.stride(2);
|
||||
int64_t qeb_stride = q_embed.stride(0);
|
||||
int64_t keb_stride = k_embed.stride(0);
|
||||
int64_t qes_stride = q_embed.stride(1);
|
||||
int64_t kes_stride = k_embed.stride(1);
|
||||
int64_t qeh_stride = q_embed.stride(2);
|
||||
int64_t keh_stride = k_embed.stride(2);
|
||||
|
||||
dim3 grid(batch, seq_len);
|
||||
|
||||
if (q.scalar_type() == at::kHalf) {
|
||||
const half* q_data = static_cast<const half*>(q.data_ptr());
|
||||
const half* k_data = static_cast<const half*>(k.data_ptr());
|
||||
half* q_embed_data = static_cast<half*>(q_embed.data_ptr());
|
||||
half* k_embed_data = static_cast<half*>(k_embed.data_ptr());
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernel<half><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_head_num,
|
||||
k_data, k_head_num,
|
||||
q_embed_data, k_embed_data,
|
||||
first, second, dim,
|
||||
qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride,
|
||||
qeb_stride, qes_stride, qeh_stride, keb_stride, kes_stride, keh_stride);
|
||||
} else if (q.scalar_type() == at::kBFloat16) {
|
||||
const __nv_bfloat16* q_data = static_cast<const __nv_bfloat16*>(q.data_ptr());
|
||||
const __nv_bfloat16* k_data = static_cast<const __nv_bfloat16*>(k.data_ptr());
|
||||
__nv_bfloat16* q_embed_data = static_cast<__nv_bfloat16*>(q_embed.data_ptr());
|
||||
__nv_bfloat16* k_embed_data = static_cast<__nv_bfloat16*>(k_embed.data_ptr());
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernel<__nv_bfloat16><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_head_num,
|
||||
k_data, k_head_num,
|
||||
q_embed_data, k_embed_data,
|
||||
first, second, dim,
|
||||
qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride,
|
||||
qeb_stride, qes_stride, qeh_stride, keb_stride, kes_stride, keh_stride);
|
||||
} else if (q.scalar_type() == at::kFloat) {
|
||||
const float* q_data = static_cast<const float*>(q.data_ptr());
|
||||
const float* k_data = static_cast<const float*>(k.data_ptr());
|
||||
float* q_embed_data = static_cast<float*>(q_embed.data_ptr());
|
||||
float* k_embed_data = static_cast<float*>(k_embed.data_ptr());
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernel<float><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_head_num,
|
||||
k_data, k_head_num,
|
||||
q_embed_data, k_embed_data,
|
||||
first, second, dim,
|
||||
qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride,
|
||||
qeb_stride, qes_stride, qeh_stride, keb_stride, kes_stride, keh_stride
|
||||
);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for m_rope");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
void MRopeInplace(const at::Tensor& q, // [b, s, h, d]
|
||||
const at::Tensor& k, // [b, s, h_k, d]
|
||||
const at::Tensor& cos, // [3, b, s, d / 2]
|
||||
const at::Tensor& sin, // [3, b, s, d / 2]
|
||||
const int first,
|
||||
const int second
|
||||
) {
|
||||
int Nthreads = 256;
|
||||
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
ASSERT_CHECK(q.scalar_type() == k.scalar_type());
|
||||
ASSERT_CHECK(q.size(3) % 8 == 0);
|
||||
ASSERT_CHECK(cos.size(0) == 3);
|
||||
ASSERT_CHECK(cos.size(3) == q.size(3) / 2);
|
||||
ASSERT_CHECK(cos.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(sin.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(sin.size(0) == 3);
|
||||
ASSERT_CHECK(q.stride(3) == 1);
|
||||
ASSERT_CHECK(k.stride(3) == 1);
|
||||
ASSERT_CHECK(cos.stride(3) == 1);
|
||||
ASSERT_CHECK(sin.stride(3) == 1);
|
||||
ASSERT_CHECK(cos.is_contiguous());
|
||||
ASSERT_CHECK(sin.is_contiguous());
|
||||
|
||||
int64_t batch = q.size(0);
|
||||
int64_t q_head_num = q.size(2);
|
||||
int64_t k_head_num = k.size(2);
|
||||
int64_t seq_len = q.size(1);
|
||||
int64_t dim = q.size(3);
|
||||
int64_t qb_stride = q.stride(0);
|
||||
int64_t kb_stride = k.stride(0);
|
||||
int64_t qs_stride = q.stride(1);
|
||||
int64_t ks_stride = k.stride(1);
|
||||
int64_t qh_stride = q.stride(2);
|
||||
int64_t kh_stride = k.stride(2);
|
||||
|
||||
dim3 grid(batch, seq_len);
|
||||
|
||||
if (q.scalar_type() == at::kHalf) {
|
||||
half* q_data = static_cast<half*>(q.data_ptr());
|
||||
half* k_data = static_cast<half*>(k.data_ptr());
|
||||
float* cos_data = static_cast<float*>(cos.data_ptr());
|
||||
float* sin_data = static_cast<float*>(sin.data_ptr());
|
||||
MRopeInplaceKernel<half><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_head_num,
|
||||
k_data, k_head_num,
|
||||
first, second, dim, qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride);
|
||||
} else if (q.scalar_type() == at::kBFloat16) {
|
||||
__nv_bfloat16* q_data = static_cast<__nv_bfloat16*>(q.data_ptr());
|
||||
__nv_bfloat16* k_data = static_cast<__nv_bfloat16*>(k.data_ptr());
|
||||
float* cos_data = static_cast<float*>(cos.data_ptr());
|
||||
float* sin_data = static_cast<float*>(sin.data_ptr());
|
||||
MRopeInplaceKernel<__nv_bfloat16><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_head_num,
|
||||
k_data, k_head_num,
|
||||
first, second, dim, qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride);
|
||||
} else if (q.scalar_type() == at::kFloat) {
|
||||
float* q_data = static_cast<float*>(q.data_ptr());
|
||||
float* k_data = static_cast<float*>(k.data_ptr());
|
||||
float* cos_data = static_cast<float*>(cos.data_ptr());
|
||||
float* sin_data = static_cast<float*>(sin.data_ptr());
|
||||
MRopeInplaceKernel<float><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_head_num,
|
||||
k_data, k_head_num,
|
||||
first, second, dim, qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride
|
||||
);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for m_rope");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
void MRopeBackward(
|
||||
const at::Tensor& grad_q_embed,
|
||||
const at::Tensor& grad_k_embed,
|
||||
const at::Tensor& grad_q, // output: [b, s, q_h, d]
|
||||
const at::Tensor& grad_k, // output: [b, s, k_h, d]
|
||||
const at::Tensor& cos, // [3, b, s, d/2]
|
||||
const at::Tensor& sin, // [3, b, s, d/2]
|
||||
const int first,
|
||||
const int second
|
||||
) {
|
||||
int Nthreads = 256;
|
||||
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
ASSERT_CHECK(grad_q_embed.scalar_type() == grad_k_embed.scalar_type());
|
||||
ASSERT_CHECK(grad_q.scalar_type() == grad_k.scalar_type());
|
||||
ASSERT_CHECK(grad_q_embed.size(3) == grad_q.size(3));
|
||||
ASSERT_CHECK(grad_k_embed.size(3) == grad_k.size(3));
|
||||
ASSERT_CHECK(grad_q_embed.size(3) % 8 == 0);
|
||||
ASSERT_CHECK(grad_q_embed.stride(3) == 1);
|
||||
ASSERT_CHECK(grad_k_embed.stride(3) == 1);
|
||||
ASSERT_CHECK(grad_q.stride(0) == grad_q_embed.stride(0));
|
||||
ASSERT_CHECK(grad_q.stride(1) == grad_q_embed.stride(1));
|
||||
ASSERT_CHECK(grad_q.stride(2) == grad_q_embed.stride(2));
|
||||
ASSERT_CHECK(grad_q.stride(3) == grad_q_embed.stride(3));
|
||||
ASSERT_CHECK(grad_k.stride(0) == grad_k_embed.stride(0));
|
||||
ASSERT_CHECK(grad_k.stride(1) == grad_k_embed.stride(1));
|
||||
ASSERT_CHECK(grad_k.stride(2) == grad_k_embed.stride(2));
|
||||
ASSERT_CHECK(grad_k.stride(3) == grad_k_embed.stride(3));
|
||||
|
||||
int64_t batch = grad_q_embed.size(0);
|
||||
int64_t seq_len = grad_q_embed.size(1);
|
||||
int64_t q_head_num = grad_q.size(2);
|
||||
int64_t k_head_num = grad_k.size(2);
|
||||
int64_t dim = grad_q_embed.size(3);
|
||||
int64_t qb_stride = grad_q_embed.stride(0);
|
||||
int64_t kb_stride = grad_k_embed.stride(0);
|
||||
int64_t qs_stride = grad_q_embed.stride(1);
|
||||
int64_t ks_stride = grad_k_embed.stride(1);
|
||||
int64_t qh_stride = grad_q_embed.stride(2);
|
||||
int64_t kh_stride = grad_k_embed.stride(2);
|
||||
|
||||
dim3 grid(batch, seq_len);
|
||||
|
||||
if (grad_q_embed.scalar_type() == at::kHalf) {
|
||||
const half* gq_embed = static_cast<const half*>(grad_q_embed.data_ptr());
|
||||
const half* gk_embed = static_cast<const half*>(grad_k_embed.data_ptr());
|
||||
half* gq = static_cast<half*>(grad_q.data_ptr());
|
||||
half* gk = static_cast<half*>(grad_k.data_ptr());
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernelBackward<half><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
gq_embed, gk_embed,
|
||||
gq, gk,
|
||||
first, second,
|
||||
q_head_num, k_head_num, dim, qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride
|
||||
);
|
||||
} else if (grad_q_embed.scalar_type() == at::kBFloat16) {
|
||||
const __nv_bfloat16* gq_embed = static_cast<const __nv_bfloat16*>(grad_q_embed.data_ptr());
|
||||
const __nv_bfloat16* gk_embed = static_cast<const __nv_bfloat16*>(grad_k_embed.data_ptr());
|
||||
__nv_bfloat16* gq = static_cast<__nv_bfloat16*>(grad_q.data_ptr());
|
||||
__nv_bfloat16* gk = static_cast<__nv_bfloat16*>(grad_k.data_ptr());
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernelBackward<__nv_bfloat16><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
gq_embed, gk_embed,
|
||||
gq, gk,
|
||||
first, second,
|
||||
q_head_num, k_head_num, dim, qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride
|
||||
);
|
||||
} else if (grad_q_embed.scalar_type() == at::kFloat) {
|
||||
const float* gq_embed = static_cast<const float*>(grad_q_embed.data_ptr());
|
||||
const float* gk_embed = static_cast<const float*>(grad_k_embed.data_ptr());
|
||||
float* gq = static_cast<float*>(grad_q.data_ptr());
|
||||
float* gk = static_cast<float*>(grad_k.data_ptr());
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernelBackward<float><<<grid, Nthreads, dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
gq_embed, gk_embed,
|
||||
gq, gk,
|
||||
first, second,
|
||||
q_head_num, k_head_num, dim, qb_stride, qs_stride, qh_stride, kb_stride, ks_stride, kh_stride
|
||||
);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for m_rope backward");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
// Pack interface: accepts qkv [bz, seq_len, q_dim + 2*kv_dim], applies mrope
|
||||
// inplace on q and k without Python-side split/view overhead.
|
||||
// Supports GQA (q_num_heads != kv_num_heads). CUDA kernels are not modified.
|
||||
void MRopeInplacePack(const at::Tensor& qkv, // [bz, seq_len, q_dim + 2*kv_dim]
|
||||
const at::Tensor& cos, // [3, bz, seq_len, head_dim/2]
|
||||
const at::Tensor& sin, // [3, bz, seq_len, head_dim/2]
|
||||
int64_t q_num_heads,
|
||||
int64_t kv_num_heads,
|
||||
const int first,
|
||||
const int second) {
|
||||
int Nthreads = 256;
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
ASSERT_CHECK(qkv.dim() == 3);
|
||||
ASSERT_CHECK(qkv.stride(2) == 1);
|
||||
ASSERT_CHECK(cos.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(sin.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(cos.size(0) == 3);
|
||||
ASSERT_CHECK(sin.size(0) == 3);
|
||||
ASSERT_CHECK(cos.is_contiguous());
|
||||
ASSERT_CHECK(sin.is_contiguous());
|
||||
|
||||
int64_t bz = qkv.size(0);
|
||||
int64_t seq_len = qkv.size(1);
|
||||
int64_t head_dim = cos.size(3) * 2; // cos: [3, bz, seq, half_dim]
|
||||
int64_t q_dim = q_num_heads * head_dim;
|
||||
int64_t kv_dim = kv_num_heads * head_dim;
|
||||
ASSERT_CHECK(qkv.size(2) == q_dim + 2 * kv_dim);
|
||||
ASSERT_CHECK(head_dim % 8 == 0);
|
||||
|
||||
// q at offset 0, k at offset q_dim; logical [bz, seq, *_num_heads, head_dim]
|
||||
// with strides [qkv.stride(0), qkv.stride(1), head_dim, 1]
|
||||
int64_t qkv_b_stride = qkv.stride(0);
|
||||
int64_t qkv_s_stride = qkv.stride(1);
|
||||
|
||||
dim3 grid(bz, seq_len);
|
||||
|
||||
if (qkv.scalar_type() == at::kHalf) {
|
||||
half* q_data = static_cast<half*>(qkv.data_ptr());
|
||||
half* k_data = q_data + q_dim;
|
||||
float* cos_data = static_cast<float*>(cos.data_ptr());
|
||||
float* sin_data = static_cast<float*>(sin.data_ptr());
|
||||
MRopeInplaceKernel<half><<<grid, Nthreads, head_dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_num_heads, k_data, kv_num_heads,
|
||||
first, second, head_dim,
|
||||
qkv_b_stride, qkv_s_stride, head_dim,
|
||||
qkv_b_stride, qkv_s_stride, head_dim);
|
||||
} else if (qkv.scalar_type() == at::kBFloat16) {
|
||||
__nv_bfloat16* q_data = static_cast<__nv_bfloat16*>(qkv.data_ptr());
|
||||
__nv_bfloat16* k_data = q_data + q_dim;
|
||||
float* cos_data = static_cast<float*>(cos.data_ptr());
|
||||
float* sin_data = static_cast<float*>(sin.data_ptr());
|
||||
MRopeInplaceKernel<__nv_bfloat16><<<grid, Nthreads, head_dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_num_heads, k_data, kv_num_heads,
|
||||
first, second, head_dim,
|
||||
qkv_b_stride, qkv_s_stride, head_dim,
|
||||
qkv_b_stride, qkv_s_stride, head_dim);
|
||||
} else if (qkv.scalar_type() == at::kFloat) {
|
||||
float* q_data = static_cast<float*>(qkv.data_ptr());
|
||||
float* k_data = q_data + q_dim;
|
||||
float* cos_data = static_cast<float*>(cos.data_ptr());
|
||||
float* sin_data = static_cast<float*>(sin.data_ptr());
|
||||
MRopeInplaceKernel<float><<<grid, Nthreads, head_dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
q_data, q_num_heads, k_data, kv_num_heads,
|
||||
first, second, head_dim,
|
||||
qkv_b_stride, qkv_s_stride, head_dim,
|
||||
qkv_b_stride, qkv_s_stride, head_dim);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for m_rope_inplace_pack");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
// Pack backward interface: accepts dqkv [bz, seq_len, q_dim + 2*kv_dim] and
|
||||
// applies the inverse mrope transform inplace on the dq and dk slices. The dv
|
||||
// slice is left unchanged.
|
||||
void MRopeInplacePackBackward(const at::Tensor& dqkv, // [bz, seq_len, q_dim + 2*kv_dim]
|
||||
const at::Tensor& cos, // [3, bz, seq_len, head_dim/2]
|
||||
const at::Tensor& sin, // [3, bz, seq_len, head_dim/2]
|
||||
int64_t q_num_heads,
|
||||
int64_t kv_num_heads,
|
||||
const int first,
|
||||
const int second) {
|
||||
int Nthreads = 256;
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
ASSERT_CHECK(dqkv.dim() == 3);
|
||||
ASSERT_CHECK(dqkv.stride(2) == 1);
|
||||
ASSERT_CHECK(cos.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(sin.scalar_type() == at::kFloat);
|
||||
ASSERT_CHECK(cos.size(0) == 3);
|
||||
ASSERT_CHECK(sin.size(0) == 3);
|
||||
ASSERT_CHECK(cos.is_contiguous());
|
||||
ASSERT_CHECK(sin.is_contiguous());
|
||||
|
||||
int64_t bz = dqkv.size(0);
|
||||
int64_t seq_len = dqkv.size(1);
|
||||
int64_t head_dim = cos.size(3) * 2; // cos: [3, bz, seq, half_dim]
|
||||
int64_t q_dim = q_num_heads * head_dim;
|
||||
int64_t kv_dim = kv_num_heads * head_dim;
|
||||
ASSERT_CHECK(dqkv.size(2) == q_dim + 2 * kv_dim);
|
||||
ASSERT_CHECK(head_dim % 8 == 0);
|
||||
|
||||
int64_t dqkv_b_stride = dqkv.stride(0);
|
||||
int64_t dqkv_s_stride = dqkv.stride(1);
|
||||
|
||||
dim3 grid(bz, seq_len);
|
||||
|
||||
if (dqkv.scalar_type() == at::kHalf) {
|
||||
half* dq_data = static_cast<half*>(dqkv.data_ptr());
|
||||
half* dk_data = dq_data + q_dim;
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernelBackward<half><<<grid, Nthreads, head_dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
dq_data, dk_data,
|
||||
dq_data, dk_data,
|
||||
first, second,
|
||||
q_num_heads, kv_num_heads, head_dim,
|
||||
dqkv_b_stride, dqkv_s_stride, head_dim,
|
||||
dqkv_b_stride, dqkv_s_stride, head_dim);
|
||||
} else if (dqkv.scalar_type() == at::kBFloat16) {
|
||||
__nv_bfloat16* dq_data = static_cast<__nv_bfloat16*>(dqkv.data_ptr());
|
||||
__nv_bfloat16* dk_data = dq_data + q_dim;
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernelBackward<__nv_bfloat16><<<grid, Nthreads, head_dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
dq_data, dk_data,
|
||||
dq_data, dk_data,
|
||||
first, second,
|
||||
q_num_heads, kv_num_heads, head_dim,
|
||||
dqkv_b_stride, dqkv_s_stride, head_dim,
|
||||
dqkv_b_stride, dqkv_s_stride, head_dim);
|
||||
} else if (dqkv.scalar_type() == at::kFloat) {
|
||||
float* dq_data = static_cast<float*>(dqkv.data_ptr());
|
||||
float* dk_data = dq_data + q_dim;
|
||||
const float* cos_data = static_cast<const float*>(cos.data_ptr());
|
||||
const float* sin_data = static_cast<const float*>(sin.data_ptr());
|
||||
MRopeKernelBackward<float><<<grid, Nthreads, head_dim * sizeof(float), stream>>>(
|
||||
cos_data, sin_data,
|
||||
dq_data, dk_data,
|
||||
dq_data, dk_data,
|
||||
first, second,
|
||||
q_num_heads, kv_num_heads, head_dim,
|
||||
dqkv_b_stride, dqkv_s_stride, head_dim,
|
||||
dqkv_b_stride, dqkv_s_stride, head_dim);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for m_rope_inplace_pack_bwd");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
|
||||
} // wallx_cuda_m_rope
|
||||
@@ -0,0 +1,581 @@
|
||||
// Re-enable CUDA half operators (PyTorch disables them)
|
||||
#undef __CUDA_NO_HALF_OPERATORS__
|
||||
#undef __CUDA_NO_HALF_CONVERSIONS__
|
||||
#undef __CUDA_NO_HALF2_OPERATORS__
|
||||
#undef __CUDA_NO_BFLOAT16_CONVERSIONS__
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_bf16.h>
|
||||
#include <cub/cub.cuh>
|
||||
#include "../common/cuda_utils.h"
|
||||
#include "cuda_runtime.h"
|
||||
#include "device_launch_parameters.h"
|
||||
|
||||
// Global FP8 guard: use CUDA_VERSION (defined in both host and device passes),
|
||||
// not __CUDA_ARCH__ (device-pass only - leaves host dispatchers unable to see
|
||||
// FP8 typedefs/branches). FP8 storage types arrived in CUDA 11.8.
|
||||
#if defined(CUDA_VERSION) && (CUDA_VERSION >= 11800)
|
||||
#include <cuda_fp8.h>
|
||||
#define XCOMPUTE_HAS_FP8 1
|
||||
#else
|
||||
#define XCOMPUTE_HAS_FP8 0
|
||||
#endif
|
||||
|
||||
namespace wallx_cuda_permute {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CUTLASS-free helpers: replace cutlass::Array, NumericArrayConverter,
|
||||
// arch::global_load, and type aliases with standard CUDA equivalents.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Vectorized array: replaces cutlass::Array<T, N>
|
||||
// __align__(16) ensures safe reinterpret_cast<float4*> in vec_load/store.
|
||||
template <typename T, int N>
|
||||
struct __align__(16) VecArray {
|
||||
T data[N];
|
||||
__device__ __forceinline__ T& at(int i) { return data[i]; }
|
||||
__device__ __forceinline__ const T& at(int i) const { return data[i]; }
|
||||
__device__ __forceinline__ T* raw() { return data; }
|
||||
__device__ __forceinline__ const T* raw() const { return data; }
|
||||
__device__ __forceinline__ void clear() {
|
||||
#pragma unroll
|
||||
for (int i = 0; i < N; i++) data[i] = T(0);
|
||||
}
|
||||
};
|
||||
|
||||
// Scalar multiply: VecArray * scalar
|
||||
template <typename T, int N>
|
||||
__device__ __forceinline__ VecArray<T, N> operator*(const VecArray<T, N>& a, T s) {
|
||||
VecArray<T, N> r;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < N; i++) r.data[i] = a.data[i] * s;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Element-wise convert: replaces cutlass::NumericArrayConverter
|
||||
template <typename To, typename From, int N>
|
||||
struct ArrayConverter {
|
||||
__device__ __forceinline__ VecArray<To, N> operator()(const VecArray<From, N>& src) const {
|
||||
VecArray<To, N> dst;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < N; i++) dst.data[i] = To(src.data[i]);
|
||||
return dst;
|
||||
}
|
||||
};
|
||||
|
||||
// Vectorized global load: replaces cutlass::arch::global_load
|
||||
template <typename Fragment>
|
||||
__device__ __forceinline__ void vec_load(Fragment& frag, const void* ptr) {
|
||||
static_assert(sizeof(Fragment) == sizeof(float4) || sizeof(Fragment) == 16,
|
||||
"vec_load expects 16-byte fragment");
|
||||
*reinterpret_cast<float4*>(&frag) = *reinterpret_cast<const float4*>(ptr);
|
||||
}
|
||||
|
||||
// Type aliases: replace cutlass types with standard CUDA types
|
||||
using half_t = __half;
|
||||
using bfloat16_t = __nv_bfloat16;
|
||||
#if XCOMPUTE_HAS_FP8
|
||||
using float_e5m2_t = __nv_fp8_e5m2;
|
||||
using float_e4m3_t = __nv_fp8_e4m3; // fn variant; CUDA's __nv_fp8_e4m3 IS the "fn" form
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kernels (unchanged logic, only CUTLASS types/calls replaced)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static __global__ void moe_permute_topK_row_map(
|
||||
const int *sorted_row_id,
|
||||
int *row_id_map,
|
||||
const int num_rows,
|
||||
const int num_topK,
|
||||
const int num_out_tokens)
|
||||
{
|
||||
const int bid = blockIdx.x;
|
||||
const int tid = threadIdx.x;
|
||||
const int idx = bid * blockDim.x + tid;
|
||||
|
||||
if (idx >= num_rows * num_topK)
|
||||
return;
|
||||
|
||||
int source_row = sorted_row_id[idx];
|
||||
int source_token_id = source_row / num_topK;
|
||||
int source_topK_id = source_row % num_topK;
|
||||
|
||||
if (idx >= num_out_tokens)
|
||||
{
|
||||
row_id_map[source_topK_id * num_rows + source_token_id] = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
row_id_map[source_topK_id * num_rows + source_token_id] = idx;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename TCompute, int kElementsPerAccess, bool hasProb>
|
||||
__global__ void moe_recover_topK_kernel(const T *input,
|
||||
T *unpermuted_output,
|
||||
const int *row_id_map,
|
||||
const float *prob,
|
||||
const int num_rows,
|
||||
const int num_topK,
|
||||
const int num_cols)
|
||||
{
|
||||
extern __shared__ int8_t s_mem[];
|
||||
TCompute *s_prob = reinterpret_cast<TCompute *>(s_mem);
|
||||
|
||||
using FragLS = VecArray<T, kElementsPerAccess>;
|
||||
using FragC = VecArray<TCompute, kElementsPerAccess>;
|
||||
|
||||
ArrayConverter<TCompute, T, kElementsPerAccess> src_converter;
|
||||
ArrayConverter<T, TCompute, kElementsPerAccess> dst_converter;
|
||||
|
||||
const int source_token = blockIdx.x;
|
||||
const int tid = threadIdx.x;
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
for (int i = tid; i < num_topK; i += blockDim.x * blockDim.y)
|
||||
{
|
||||
s_prob[i] = TCompute(prob[source_token * num_topK + i]);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
for (int i = tid * kElementsPerAccess; i < num_cols; i += blockDim.x * kElementsPerAccess)
|
||||
{
|
||||
FragLS frag_load_store;
|
||||
FragC frag_elem;
|
||||
FragC frag_sum;
|
||||
|
||||
int source_row = row_id_map[source_token];
|
||||
|
||||
if (source_row != -1)
|
||||
{
|
||||
const T *source_row_ptr = input + source_row * num_cols;
|
||||
|
||||
vec_load(frag_load_store, source_row_ptr + i);
|
||||
frag_sum = src_converter(frag_load_store);
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
frag_sum = frag_sum * s_prob[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frag_sum.clear();
|
||||
}
|
||||
|
||||
for (int k = 1; k < num_topK; k++)
|
||||
{
|
||||
source_row = row_id_map[k * num_rows + source_token];
|
||||
|
||||
if (source_row == -1)
|
||||
continue;
|
||||
|
||||
const T *source_row_ptr = input + source_row * num_cols;
|
||||
|
||||
vec_load(frag_load_store, source_row_ptr + i);
|
||||
frag_elem = src_converter(frag_load_store);
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
frag_elem = frag_elem * s_prob[k];
|
||||
}
|
||||
|
||||
for (int e = 0; e < kElementsPerAccess; e++)
|
||||
{
|
||||
frag_sum.at(e) = frag_sum.at(e) + frag_elem.at(e);
|
||||
}
|
||||
}
|
||||
|
||||
T *dest_row_ptr = unpermuted_output + source_token * num_cols;
|
||||
frag_load_store = dst_converter(frag_sum);
|
||||
*(float4 *)(dest_row_ptr + i) = *(float4 *)(frag_load_store.raw());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename TCompute,
|
||||
int kElementsPerAccess,
|
||||
int topKTile,
|
||||
bool hasProb>
|
||||
__global__ void moe_permute_topK_kernel(const T *input_bwd,
|
||||
const T *input_fwd,
|
||||
T *act_grad,
|
||||
const float *prob,
|
||||
float *prob_grad,
|
||||
const int *row_id_map,
|
||||
const int num_rows,
|
||||
const int num_topK,
|
||||
const int num_cols)
|
||||
{
|
||||
extern __shared__ int8_t s_mem[];
|
||||
TCompute *s_prob = reinterpret_cast<TCompute *>(s_mem);
|
||||
|
||||
using FragLS = VecArray<T, kElementsPerAccess>;
|
||||
using FragC = VecArray<TCompute, kElementsPerAccess>;
|
||||
|
||||
ArrayConverter<TCompute, T, kElementsPerAccess> src_converter;
|
||||
ArrayConverter<T, TCompute, kElementsPerAccess> dst_converter;
|
||||
|
||||
const int source_token = blockIdx.x;
|
||||
const int tid = threadIdx.x;
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
for (int i = tid; i < num_topK; i += blockDim.x)
|
||||
{
|
||||
s_prob[i] = TCompute(prob[source_token * num_topK + i]);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
float accum[topKTile] = {0.0f};
|
||||
FragLS frag_load_store;
|
||||
|
||||
const T *source_row_ptr = input_bwd + source_token * num_cols;
|
||||
for (int i = tid * kElementsPerAccess; i < num_cols; i += blockDim.x * kElementsPerAccess)
|
||||
{
|
||||
vec_load(frag_load_store, source_row_ptr + i);
|
||||
FragC frag_src = src_converter(frag_load_store);
|
||||
|
||||
int index = source_token;
|
||||
|
||||
for (int k = 0; k < topKTile; k++)
|
||||
{
|
||||
if (k == num_topK)
|
||||
break;
|
||||
|
||||
int dest_row = row_id_map[index];
|
||||
index += num_rows;
|
||||
|
||||
if (dest_row == -1)
|
||||
continue;
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
frag_load_store = dst_converter(frag_src * s_prob[k]);
|
||||
}
|
||||
else
|
||||
{
|
||||
frag_load_store = dst_converter(frag_src);
|
||||
}
|
||||
|
||||
T *dest_row_ptr = act_grad + dest_row * num_cols;
|
||||
*(float4 *)(dest_row_ptr + i) = *(float4 *)(frag_load_store.raw());
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
const T *input_fwd_ptr = input_fwd + dest_row * num_cols;
|
||||
vec_load(frag_load_store, input_fwd_ptr + i);
|
||||
FragC frag_input_fwd = src_converter(frag_load_store);
|
||||
|
||||
for (int e = 0; e < kElementsPerAccess; e++)
|
||||
{
|
||||
accum[k] += float(frag_src.at(e) * frag_input_fwd.at(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProb)
|
||||
{
|
||||
for (int k = 0; k < topKTile; k++)
|
||||
{
|
||||
if (k == num_topK)
|
||||
break;
|
||||
|
||||
for (int mask = 16; mask > 0; mask /= 2)
|
||||
{
|
||||
accum[k] = accum[k] + __shfl_xor_sync(0xffffffff, accum[k], mask, 32);
|
||||
}
|
||||
}
|
||||
|
||||
if (tid == 0)
|
||||
{
|
||||
for (int k = 0; k < topKTile; k++)
|
||||
{
|
||||
if (k == num_topK)
|
||||
break;
|
||||
prob_grad[source_token * num_topK + k] = accum[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Launcher (unchanged)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template <typename T, typename TCompute, bool FWD, int kElementsPerAccess>
|
||||
void moe_permute_topK_kernel_launcher(
|
||||
const T *input,
|
||||
T *output,
|
||||
const int *sorted_row_id,
|
||||
int *row_id_map,
|
||||
const float *prob,
|
||||
const int num_rows,
|
||||
const int num_topK,
|
||||
const int num_cols,
|
||||
const int num_out_tokens,
|
||||
cudaStream_t stream,
|
||||
float *prob_grad = nullptr,
|
||||
const T *input_fwd = nullptr)
|
||||
{
|
||||
if (FWD)
|
||||
{
|
||||
if (prob_grad == nullptr)
|
||||
{
|
||||
int threads = 64;
|
||||
int blocks = (num_rows * num_topK + threads - 1) / threads;
|
||||
moe_permute_topK_row_map<<<blocks, threads, 0, stream>>>(
|
||||
sorted_row_id, row_id_map, num_rows, num_topK, num_out_tokens);
|
||||
|
||||
blocks = num_rows;
|
||||
threads = std::min(num_cols / kElementsPerAccess, 1024);
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 128, false><<<blocks, threads, 0, stream>>>(
|
||||
input, nullptr, output, nullptr, nullptr, row_id_map,
|
||||
num_rows, num_topK, num_cols);
|
||||
}
|
||||
else
|
||||
{
|
||||
int blocks = num_rows;
|
||||
int threads = 32;
|
||||
size_t smem_bytes = num_topK * sizeof(TCompute);
|
||||
|
||||
if (num_topK == 1)
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 1, false><<<blocks, threads, 0, stream>>>(
|
||||
input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols);
|
||||
else if (num_topK <= 8)
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 8, true><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols);
|
||||
else if (num_topK <= 16)
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 16, true><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols);
|
||||
else if (num_topK <= 32)
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 32, true><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols);
|
||||
else if (num_topK <= 64)
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 64, true><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols);
|
||||
else if (num_topK <= 128)
|
||||
moe_permute_topK_kernel<T, TCompute, kElementsPerAccess, 128, true><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, input_fwd, output, prob, prob_grad, row_id_map, num_rows, num_topK, num_cols);
|
||||
else
|
||||
throw std::runtime_error("num_topK cannot exceed 128.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int blocks = num_rows;
|
||||
int threads = std::min(num_cols / kElementsPerAccess, 1024);
|
||||
size_t smem_bytes = num_topK * sizeof(TCompute);
|
||||
|
||||
if (num_topK == 1)
|
||||
moe_recover_topK_kernel<T, TCompute, kElementsPerAccess, false><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, output, row_id_map, prob, num_rows, num_topK, num_cols);
|
||||
else if (prob == nullptr)
|
||||
moe_recover_topK_kernel<T, TCompute, kElementsPerAccess, false><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, output, row_id_map, prob, num_rows, num_topK, num_cols);
|
||||
else
|
||||
moe_recover_topK_kernel<T, TCompute, kElementsPerAccess, true><<<blocks, threads, smem_bytes, stream>>>(
|
||||
input, output, row_id_map, prob, num_rows, num_topK, num_cols);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Host-side ops
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void MoePermuteTopKOp(
|
||||
const at::Tensor& input,
|
||||
const at::Tensor& indices,
|
||||
const at::Tensor& sorted_indices,
|
||||
const at::Tensor& row_id,
|
||||
const at::Tensor& sorted_row_id,
|
||||
const at::Tensor& temp_storage,
|
||||
const at::Tensor& permuted_output,
|
||||
const at::Tensor& row_id_map,
|
||||
int64_t num_out_tokens,
|
||||
int64_t max_expanded_token_num
|
||||
) {
|
||||
ASSERT_CHECK(input.size(0) == indices.size(0));
|
||||
const int num_tokens = input.size(0);
|
||||
const int num_cols = input.size(1);
|
||||
const int num_topK = indices.size(1);
|
||||
|
||||
int *indices_ptr = static_cast<int*>(indices.data_ptr());
|
||||
int *sorted_indices_ptr = static_cast<int*>(sorted_indices.data_ptr());
|
||||
int *row_id_ptr = static_cast<int*>(row_id.data_ptr());
|
||||
int *sorted_row_id_ptr = static_cast<int*>(sorted_row_id.data_ptr());
|
||||
|
||||
void *d_temp_storage = static_cast<void*>(temp_storage.data_ptr());
|
||||
size_t temp_storage_bytes = std::numeric_limits<size_t>::max();
|
||||
|
||||
cub::DeviceRadixSort::SortPairs(d_temp_storage, temp_storage_bytes,
|
||||
indices_ptr, sorted_indices_ptr,
|
||||
row_id_ptr, sorted_row_id_ptr, num_tokens * num_topK);
|
||||
|
||||
num_out_tokens = (num_out_tokens > 0) ? num_out_tokens : num_tokens * num_topK;
|
||||
int *row_id_map_ptr = static_cast<int*>(row_id_map.data_ptr());
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
if (input.scalar_type() == at::kFloat) {
|
||||
float *input_ptr = static_cast<float*>(input.data_ptr());
|
||||
float *out_ptr = static_cast<float*>(permuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float, float, true, 4>(
|
||||
input_ptr, out_ptr, sorted_row_id_ptr, row_id_map_ptr,
|
||||
nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream);
|
||||
} else if (input.scalar_type() == at::kHalf) {
|
||||
half_t *input_ptr = static_cast<half_t*>(input.data_ptr());
|
||||
half_t *out_ptr = static_cast<half_t*>(permuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<half_t, half_t, true, 8>(
|
||||
input_ptr, out_ptr, sorted_row_id_ptr, row_id_map_ptr,
|
||||
nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream);
|
||||
} else if (input.scalar_type() == at::kBFloat16) {
|
||||
bfloat16_t *input_ptr = static_cast<bfloat16_t*>(input.data_ptr());
|
||||
bfloat16_t *out_ptr = static_cast<bfloat16_t*>(permuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<bfloat16_t, bfloat16_t, true, 8>(
|
||||
input_ptr, out_ptr, sorted_row_id_ptr, row_id_map_ptr,
|
||||
nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream);
|
||||
#if XCOMPUTE_HAS_FP8
|
||||
} else if (input.scalar_type() == at::kFloat8_e5m2) {
|
||||
float_e5m2_t *input_ptr = static_cast<float_e5m2_t*>(input.data_ptr());
|
||||
float_e5m2_t *out_ptr = static_cast<float_e5m2_t*>(permuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float_e5m2_t, half_t, true, 16>(
|
||||
input_ptr, out_ptr, sorted_row_id_ptr, row_id_map_ptr,
|
||||
nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream);
|
||||
} else if (input.scalar_type() == at::kFloat8_e4m3fn) {
|
||||
float_e4m3_t *input_ptr = static_cast<float_e4m3_t*>(input.data_ptr());
|
||||
float_e4m3_t *out_ptr = static_cast<float_e4m3_t*>(permuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float_e4m3_t, half_t, true, 16>(
|
||||
input_ptr, out_ptr, sorted_row_id_ptr, row_id_map_ptr,
|
||||
nullptr, num_tokens, num_topK, num_cols, num_out_tokens, stream);
|
||||
#endif
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for MoePermuteTopKOp");
|
||||
}
|
||||
}
|
||||
|
||||
void MoeRecoverTopKOp(
|
||||
const at::Tensor& input,
|
||||
const at::Tensor& row_id_map,
|
||||
const c10::optional<at::Tensor>& prob,
|
||||
const at::Tensor& unpermuted_output,
|
||||
int64_t num_tokens,
|
||||
int64_t num_topK
|
||||
) {
|
||||
const int num_cols = input.size(1);
|
||||
int *row_id_map_ptr = static_cast<int*>(row_id_map.data_ptr());
|
||||
float *prob_ptr = (prob.has_value()) ? static_cast<float*>(prob.value().data_ptr()) : nullptr;
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
if (input.scalar_type() == at::kFloat) {
|
||||
float *in_ptr = static_cast<float*>(input.data_ptr());
|
||||
float *out_ptr = static_cast<float*>(unpermuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float, float, false, 4>(
|
||||
in_ptr, out_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream);
|
||||
} else if (input.scalar_type() == at::kHalf) {
|
||||
half_t *in_ptr = static_cast<half_t*>(input.data_ptr());
|
||||
half_t *out_ptr = static_cast<half_t*>(unpermuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<half_t, half_t, false, 8>(
|
||||
in_ptr, out_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream);
|
||||
} else if (input.scalar_type() == at::kBFloat16) {
|
||||
bfloat16_t *in_ptr = static_cast<bfloat16_t*>(input.data_ptr());
|
||||
bfloat16_t *out_ptr = static_cast<bfloat16_t*>(unpermuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<bfloat16_t, bfloat16_t, false, 8>(
|
||||
in_ptr, out_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream);
|
||||
#if XCOMPUTE_HAS_FP8
|
||||
} else if (input.scalar_type() == at::kFloat8_e5m2) {
|
||||
float_e5m2_t *in_ptr = static_cast<float_e5m2_t*>(input.data_ptr());
|
||||
float_e5m2_t *out_ptr = static_cast<float_e5m2_t*>(unpermuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float_e5m2_t, half_t, false, 16>(
|
||||
in_ptr, out_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream);
|
||||
} else if (input.scalar_type() == at::kFloat8_e4m3fn) {
|
||||
float_e4m3_t *in_ptr = static_cast<float_e4m3_t*>(input.data_ptr());
|
||||
float_e4m3_t *out_ptr = static_cast<float_e4m3_t*>(unpermuted_output.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float_e4m3_t, half_t, false, 16>(
|
||||
in_ptr, out_ptr, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream);
|
||||
#endif
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for MoeRecoverTopKOp");
|
||||
}
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
void MoeRecoverTopKBwdOp(
|
||||
const at::Tensor& input_bwd,
|
||||
const at::Tensor& input_fwd,
|
||||
const at::Tensor& row_id_map,
|
||||
const at::Tensor& prob,
|
||||
const at::Tensor& act_grad,
|
||||
const at::Tensor& prob_grad
|
||||
) {
|
||||
const int num_tokens = prob.size(0);
|
||||
const int num_topK = prob.size(1);
|
||||
const int num_cols = input_bwd.size(1);
|
||||
int *row_id_map_ptr = static_cast<int*>(row_id_map.data_ptr());
|
||||
float *prob_ptr = static_cast<float*>(prob.data_ptr());
|
||||
float *prob_grad_ptr = static_cast<float*>(prob_grad.data_ptr());
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
if (input_bwd.scalar_type() == at::kFloat) {
|
||||
float *bwd = static_cast<float*>(input_bwd.data_ptr());
|
||||
float *fwd = static_cast<float*>(input_fwd.data_ptr());
|
||||
float *grad = static_cast<float*>(act_grad.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float, float, true, 4>(
|
||||
bwd, grad, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, fwd);
|
||||
} else if (input_bwd.scalar_type() == at::kHalf) {
|
||||
half_t *bwd = static_cast<half_t*>(input_bwd.data_ptr());
|
||||
half_t *fwd = static_cast<half_t*>(input_fwd.data_ptr());
|
||||
half_t *grad = static_cast<half_t*>(act_grad.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<half_t, half_t, true, 8>(
|
||||
bwd, grad, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, fwd);
|
||||
} else if (input_bwd.scalar_type() == at::kBFloat16) {
|
||||
bfloat16_t *bwd = static_cast<bfloat16_t*>(input_bwd.data_ptr());
|
||||
bfloat16_t *fwd = static_cast<bfloat16_t*>(input_fwd.data_ptr());
|
||||
bfloat16_t *grad = static_cast<bfloat16_t*>(act_grad.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<bfloat16_t, bfloat16_t, true, 8>(
|
||||
bwd, grad, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, fwd);
|
||||
#if XCOMPUTE_HAS_FP8
|
||||
} else if (input_bwd.scalar_type() == at::kFloat8_e5m2) {
|
||||
float_e5m2_t *bwd = static_cast<float_e5m2_t*>(input_bwd.data_ptr());
|
||||
float_e5m2_t *fwd = static_cast<float_e5m2_t*>(input_fwd.data_ptr());
|
||||
float_e5m2_t *grad = static_cast<float_e5m2_t*>(act_grad.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float_e5m2_t, half_t, true, 16>(
|
||||
bwd, grad, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, fwd);
|
||||
} else if (input_bwd.scalar_type() == at::kFloat8_e4m3fn) {
|
||||
float_e4m3_t *bwd = static_cast<float_e4m3_t*>(input_bwd.data_ptr());
|
||||
float_e4m3_t *fwd = static_cast<float_e4m3_t*>(input_fwd.data_ptr());
|
||||
float_e4m3_t *grad = static_cast<float_e4m3_t*>(act_grad.data_ptr());
|
||||
moe_permute_topK_kernel_launcher<float_e4m3_t, half_t, true, 16>(
|
||||
bwd, grad, nullptr, row_id_map_ptr, prob_ptr, num_tokens, num_topK, num_cols, 0, stream, prob_grad_ptr, fwd);
|
||||
#endif
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for MoeRecoverTopKBwdOp");
|
||||
}
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
size_t CubSortPairGetStorageBytes(int64_t num_items){
|
||||
size_t temp_storage_bytes = 0;
|
||||
int *temp_ptr = nullptr;
|
||||
cub::DeviceRadixSort::SortPairs(nullptr, temp_storage_bytes,
|
||||
temp_ptr, temp_ptr,
|
||||
temp_ptr, temp_ptr, num_items);
|
||||
return temp_storage_bytes;
|
||||
}
|
||||
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
|
||||
} // wallx_cuda_permute
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,284 @@
|
||||
#include <torch/extension.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <cub/cub.cuh>
|
||||
#include <cassert>
|
||||
#include "../common/cuda_utils.h"
|
||||
|
||||
namespace wallx_cuda_rot_pos {
|
||||
|
||||
__global__ void fused_rot_pos_emb_kernel_int32(
|
||||
const float *__restrict__ inv_freq, // [dim/2] - precomputed inverse frequencies
|
||||
const int32_t *__restrict__ grid_thw, // [num_grids, 3] - (t, h, w) for each grid
|
||||
float *__restrict__ output, // [total_tokens, dim] - output rotary embeddings
|
||||
const int32_t *__restrict__ cumsum_tokens, // [num_grids+1] - cumulative sum of tokens per grid
|
||||
const int dim_half, // dim/2 (size of inv_freq)
|
||||
const int spatial_merge_size, // spatial merge size
|
||||
const int num_grids // number of grids
|
||||
)
|
||||
{
|
||||
const int32_t tid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const int32_t total_tokens = cumsum_tokens[num_grids];
|
||||
|
||||
extern __shared__ float freq_smem[];
|
||||
for (int i = threadIdx.x; i < dim_half; i += blockDim.x) {
|
||||
freq_smem[i] = inv_freq[i];
|
||||
}
|
||||
if (tid >= total_tokens * dim_half)
|
||||
return;
|
||||
|
||||
const int32_t token_idx = tid / dim_half;
|
||||
const int freq_idx = tid % dim_half;
|
||||
|
||||
// Find which grid this token belongs to
|
||||
int grid_idx = 0;
|
||||
int32_t local_token_idx = token_idx;
|
||||
for (int g = 0; g < num_grids; g++)
|
||||
{
|
||||
if (token_idx < cumsum_tokens[g + 1])
|
||||
{
|
||||
grid_idx = g;
|
||||
local_token_idx = token_idx - cumsum_tokens[g];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get grid dimensions
|
||||
const int32_t h = grid_thw[grid_idx * 3 + 1];
|
||||
const int32_t w = grid_thw[grid_idx * 3 + 2];
|
||||
|
||||
// Calculate spatial dimensions after merging
|
||||
const int32_t h_merged = h / spatial_merge_size;
|
||||
const int32_t w_merged = w / spatial_merge_size;
|
||||
const int32_t spatial_tokens = h_merged * w_merged * spatial_merge_size * spatial_merge_size;
|
||||
|
||||
// Get spatial index
|
||||
const int32_t spatial_idx = local_token_idx % spatial_tokens;
|
||||
|
||||
// Decompose spatial index to get merged block and position within block
|
||||
const int32_t tokens_per_block = spatial_merge_size * spatial_merge_size;
|
||||
const int32_t block_idx = spatial_idx / tokens_per_block;
|
||||
const int32_t within_block_idx = spatial_idx % tokens_per_block;
|
||||
|
||||
// Get block coordinates in merged grid
|
||||
const int32_t block_h = block_idx / w_merged;
|
||||
const int32_t block_w = block_idx % w_merged;
|
||||
|
||||
// Get position within block
|
||||
const int32_t within_h = within_block_idx / spatial_merge_size;
|
||||
const int32_t within_w = within_block_idx % spatial_merge_size;
|
||||
|
||||
// Calculate actual h and w positions
|
||||
const int32_t h_pos = block_h * spatial_merge_size + within_h;
|
||||
const int32_t w_pos = block_w * spatial_merge_size + within_w;
|
||||
|
||||
// Compute rotary embedding
|
||||
float freq_val = freq_smem[freq_idx];
|
||||
|
||||
// Output has shape [total_tokens, dim] where dim = 2 * dim_half
|
||||
int32_t out_idx = token_idx * dim_half * 2 + freq_idx;
|
||||
output[out_idx] = h_pos * freq_val; // h_pos frequencies
|
||||
output[out_idx + dim_half] = w_pos * freq_val; // w_pos frequencies
|
||||
}
|
||||
|
||||
__global__ void fused_rot_pos_emb_kernel_int64(
|
||||
const float *__restrict__ inv_freq, // [dim/2] - precomputed inverse frequencies
|
||||
const int64_t *__restrict__ grid_thw, // [num_grids, 3] - (t, h, w) for each grid
|
||||
float *__restrict__ output, // [total_tokens, dim] - output rotary embeddings
|
||||
const int64_t *__restrict__ cumsum_tokens, // [num_grids+1] - cumulative sum of tokens per grid
|
||||
const int dim_half, // dim/2 (size of inv_freq)
|
||||
const int spatial_merge_size, // spatial merge size
|
||||
const int num_grids // number of grids
|
||||
)
|
||||
{
|
||||
const int64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const int64_t total_tokens = cumsum_tokens[num_grids];
|
||||
|
||||
extern __shared__ float freq_smem[];
|
||||
for (int i = threadIdx.x; i < dim_half; i += blockDim.x) {
|
||||
freq_smem[i] = inv_freq[i];
|
||||
}
|
||||
|
||||
if (tid >= total_tokens * dim_half)
|
||||
return;
|
||||
|
||||
const int64_t token_idx = tid / dim_half;
|
||||
const int freq_idx = tid % dim_half;
|
||||
|
||||
// Find which grid this token belongs to
|
||||
int grid_idx = 0;
|
||||
int64_t local_token_idx = token_idx;
|
||||
for (int g = 0; g < num_grids; g++)
|
||||
{
|
||||
if (token_idx < cumsum_tokens[g + 1])
|
||||
{
|
||||
grid_idx = g;
|
||||
local_token_idx = token_idx - cumsum_tokens[g];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get grid dimensions
|
||||
const int64_t h = grid_thw[grid_idx * 3 + 1];
|
||||
const int64_t w = grid_thw[grid_idx * 3 + 2];
|
||||
|
||||
// Calculate spatial dimensions after merging
|
||||
const int64_t h_merged = h / spatial_merge_size;
|
||||
const int64_t w_merged = w / spatial_merge_size;
|
||||
const int64_t spatial_tokens = h_merged * w_merged * spatial_merge_size * spatial_merge_size;
|
||||
|
||||
// Get spatial index
|
||||
const int64_t spatial_idx = local_token_idx % spatial_tokens;
|
||||
|
||||
// Decompose spatial index to get merged block and position within block
|
||||
const int64_t tokens_per_block = spatial_merge_size * spatial_merge_size;
|
||||
const int64_t block_idx = spatial_idx / tokens_per_block;
|
||||
const int64_t within_block_idx = spatial_idx % tokens_per_block;
|
||||
|
||||
// Get block coordinates in merged grid
|
||||
const int64_t block_h = block_idx / w_merged;
|
||||
const int64_t block_w = block_idx % w_merged;
|
||||
|
||||
// Get position within block
|
||||
const int64_t within_h = within_block_idx / spatial_merge_size;
|
||||
const int64_t within_w = within_block_idx % spatial_merge_size;
|
||||
|
||||
// Calculate actual h and w positions
|
||||
const int64_t h_pos = block_h * spatial_merge_size + within_h;
|
||||
const int64_t w_pos = block_w * spatial_merge_size + within_w;
|
||||
|
||||
// Compute rotary embedding
|
||||
float freq_val = freq_smem[freq_idx];
|
||||
|
||||
// Output has shape [total_tokens, dim] where dim = 2 * dim_half
|
||||
int64_t out_idx = token_idx * dim_half * 2 + freq_idx;
|
||||
output[out_idx] = h_pos * freq_val; // h_pos frequencies
|
||||
output[out_idx + dim_half] = w_pos * freq_val; // w_pos frequencies
|
||||
}
|
||||
|
||||
__global__ void compute_token_counts_kernel_int32(
|
||||
const int32_t *__restrict__ grid_thw,
|
||||
int32_t *__restrict__ token_counts,
|
||||
const int spatial_merge_size,
|
||||
const int num_grids)
|
||||
{
|
||||
const int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx >= num_grids)
|
||||
return;
|
||||
|
||||
int32_t t = grid_thw[idx * 3 + 0];
|
||||
int32_t h = grid_thw[idx * 3 + 1];
|
||||
int32_t w = grid_thw[idx * 3 + 2];
|
||||
int32_t h_merged = h / spatial_merge_size;
|
||||
int32_t w_merged = w / spatial_merge_size;
|
||||
token_counts[idx] = t * h_merged * w_merged * spatial_merge_size * spatial_merge_size;
|
||||
}
|
||||
|
||||
__global__ void compute_token_counts_kernel_int64(
|
||||
const int64_t *__restrict__ grid_thw,
|
||||
int64_t *__restrict__ token_counts,
|
||||
const int spatial_merge_size,
|
||||
const int num_grids)
|
||||
{
|
||||
const int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx >= num_grids)
|
||||
return;
|
||||
|
||||
int64_t t = grid_thw[idx * 3 + 0];
|
||||
int64_t h = grid_thw[idx * 3 + 1];
|
||||
int64_t w = grid_thw[idx * 3 + 2];
|
||||
int64_t h_merged = h / spatial_merge_size;
|
||||
int64_t w_merged = w / spatial_merge_size;
|
||||
token_counts[idx] = t * h_merged * w_merged * spatial_merge_size * spatial_merge_size;
|
||||
}
|
||||
|
||||
void GetTokenCounts(
|
||||
const at::Tensor& grid_thw, // [num_grids, 3]
|
||||
const at::Tensor& token_counts, // [num_grids]
|
||||
int spatial_merge_size
|
||||
) {
|
||||
ASSERT_CHECK(grid_thw.dim() == 2);
|
||||
ASSERT_CHECK(grid_thw.size(1) == 3);
|
||||
ASSERT_CHECK(grid_thw.size(0) == token_counts.size(0));
|
||||
|
||||
ASSERT_CHECK(spatial_merge_size > 0);
|
||||
|
||||
const int num_grids = grid_thw.size(0);
|
||||
|
||||
const int threads = 256;
|
||||
const int blocks = (num_grids + threads - 1) / threads;
|
||||
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
if (grid_thw.scalar_type() == at::kInt) {
|
||||
compute_token_counts_kernel_int32<<<blocks, threads, 0, stream>>>(
|
||||
static_cast<int32_t*>(grid_thw.data_ptr()),
|
||||
static_cast<int32_t*>(token_counts.data_ptr()),
|
||||
spatial_merge_size,
|
||||
num_grids);
|
||||
} else if (grid_thw.scalar_type() == at::kLong) {
|
||||
compute_token_counts_kernel_int64<<<blocks, threads, 0, stream>>>(
|
||||
static_cast<int64_t*>(grid_thw.data_ptr()),
|
||||
static_cast<int64_t*>(token_counts.data_ptr()),
|
||||
spatial_merge_size,
|
||||
num_grids);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for GetTokenCounts");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
void RotPosEmb(
|
||||
const at::Tensor& inv_freq, // [dim/2]
|
||||
const at::Tensor& grid_thw, // [num_grids, 3]
|
||||
const at::Tensor& output,
|
||||
const at::Tensor& cumsum_tokens,
|
||||
int spatial_merge_size
|
||||
) {
|
||||
ASSERT_CHECK(output.size(0) > 0);
|
||||
ASSERT_CHECK(inv_freq.dim() == 1);
|
||||
ASSERT_CHECK(inv_freq.scalar_type() == at::kFloat);
|
||||
|
||||
const int dim_half = inv_freq.size(0);
|
||||
const int num_grids = grid_thw.size(0);
|
||||
const int total_tokens = output.size(0);
|
||||
|
||||
const int64_t num_elements = total_tokens * dim_half;
|
||||
|
||||
const int threads_per_block = 256;
|
||||
const int num_blocks = static_cast<int>((num_elements + threads_per_block - 1) / threads_per_block);
|
||||
cudaStream_t stream = static_cast<cudaStream_t>(
|
||||
at::cuda::getCurrentCUDAStream().stream());
|
||||
|
||||
if (grid_thw.scalar_type() == at::kInt) {
|
||||
fused_rot_pos_emb_kernel_int32<<<num_blocks, threads_per_block, dim_half * sizeof(float), stream>>>(
|
||||
static_cast<float*>(inv_freq.data_ptr()),
|
||||
static_cast<int32_t*>(grid_thw.data_ptr()),
|
||||
static_cast<float*>(output.data_ptr()),
|
||||
static_cast<int32_t*>(cumsum_tokens.data_ptr()),
|
||||
dim_half,
|
||||
spatial_merge_size,
|
||||
num_grids);
|
||||
} else if (grid_thw.scalar_type() == at::kLong) {
|
||||
fused_rot_pos_emb_kernel_int64<<<num_blocks, threads_per_block, dim_half * sizeof(float), stream>>>(
|
||||
static_cast<float*>(inv_freq.data_ptr()),
|
||||
static_cast<int64_t*>(grid_thw.data_ptr()),
|
||||
static_cast<float*>(output.data_ptr()),
|
||||
static_cast<int64_t*>(cumsum_tokens.data_ptr()),
|
||||
dim_half,
|
||||
spatial_merge_size,
|
||||
num_grids);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported data type for RotPosEmb");
|
||||
}
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
|
||||
} // wallx_cuda_rot_pos
|
||||
@@ -0,0 +1,284 @@
|
||||
#include <torch/extension.h>
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <cassert>
|
||||
#include "../common/cuda_utils.h"
|
||||
|
||||
namespace wallx_cuda_window_index {
|
||||
|
||||
__global__ void compute_metadata(
|
||||
const int *grid_thw, // [num_grids, 3]
|
||||
int *grid_info, // [num_grids, 6]: [grid_elements, grid_windows, llm_h, llm_w, num_windows_h, num_windows_w]
|
||||
int *global_totals, // [total_elements, total_windows]
|
||||
int num_grids,
|
||||
int spatial_merge_size,
|
||||
int vit_merger_window_size)
|
||||
{
|
||||
int grid_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (grid_idx >= num_grids)
|
||||
return;
|
||||
|
||||
int grid_t = grid_thw[grid_idx * 3 + 0];
|
||||
int grid_h = grid_thw[grid_idx * 3 + 1];
|
||||
int grid_w = grid_thw[grid_idx * 3 + 2];
|
||||
|
||||
int llm_h = grid_h / spatial_merge_size;
|
||||
int llm_w = grid_w / spatial_merge_size;
|
||||
|
||||
int pad_h = (vit_merger_window_size - llm_h % vit_merger_window_size) % vit_merger_window_size;
|
||||
int pad_w = (vit_merger_window_size - llm_w % vit_merger_window_size) % vit_merger_window_size;
|
||||
|
||||
int num_windows_h = (llm_h + pad_h) / vit_merger_window_size;
|
||||
int num_windows_w = (llm_w + pad_w) / vit_merger_window_size;
|
||||
|
||||
int grid_elements = grid_t * llm_h * llm_w;
|
||||
int grid_windows = grid_t * num_windows_h * num_windows_w;
|
||||
|
||||
grid_info[grid_idx * 6 + 0] = grid_elements;
|
||||
grid_info[grid_idx * 6 + 1] = grid_windows;
|
||||
grid_info[grid_idx * 6 + 2] = llm_h;
|
||||
grid_info[grid_idx * 6 + 3] = llm_w;
|
||||
grid_info[grid_idx * 6 + 4] = num_windows_h;
|
||||
grid_info[grid_idx * 6 + 5] = num_windows_w;
|
||||
|
||||
atomicAdd(&global_totals[0], grid_elements);
|
||||
atomicAdd(&global_totals[1], grid_windows);
|
||||
}
|
||||
|
||||
__global__ void compute_window_counts(
|
||||
const int *grid_thw,
|
||||
const int *grid_info,
|
||||
int *window_counts,
|
||||
int vit_merger_window_size,
|
||||
int spatial_merge_unit,
|
||||
int num_grids)
|
||||
{
|
||||
int grid_idx = blockIdx.y;
|
||||
int t_idx = blockIdx.x;
|
||||
|
||||
if (grid_idx >= num_grids)
|
||||
return;
|
||||
|
||||
int grid_t = grid_thw[grid_idx * 3 + 0];
|
||||
if (t_idx >= grid_t)
|
||||
return;
|
||||
|
||||
int llm_h = grid_info[grid_idx * 6 + 2];
|
||||
int llm_w = grid_info[grid_idx * 6 + 3];
|
||||
int num_windows_h = grid_info[grid_idx * 6 + 4];
|
||||
int num_windows_w = grid_info[grid_idx * 6 + 5];
|
||||
|
||||
int window_base = 0;
|
||||
for (int g = 0; g < grid_idx; g++)
|
||||
{
|
||||
window_base += grid_info[g * 6 + 1];
|
||||
}
|
||||
|
||||
int t_window_base = window_base + t_idx * num_windows_h * num_windows_w;
|
||||
|
||||
int thread_id = threadIdx.x;
|
||||
int warp_id = thread_id / 32;
|
||||
int lane_id = thread_id % 32;
|
||||
|
||||
int windows_per_t = num_windows_h * num_windows_w;
|
||||
int warps_per_block = blockDim.x / 32;
|
||||
|
||||
if (lane_id == 0)
|
||||
{
|
||||
for (int window_idx = warp_id; window_idx < windows_per_t; window_idx += warps_per_block)
|
||||
{
|
||||
int win_h = window_idx / num_windows_w;
|
||||
int win_w = window_idx % num_windows_w;
|
||||
|
||||
int start_h = win_h * vit_merger_window_size;
|
||||
int start_w = win_w * vit_merger_window_size;
|
||||
|
||||
int valid_h = min(vit_merger_window_size, llm_h - start_h);
|
||||
int valid_w = min(vit_merger_window_size, llm_w - start_w);
|
||||
|
||||
int valid_count = (valid_h > 0 && valid_w > 0) ? valid_h * valid_w : 0;
|
||||
|
||||
window_counts[t_window_base + window_idx] = valid_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void compute_cu_window_seqlens(
|
||||
const int *window_counts,
|
||||
int *cu_window_seqlens, // [total_windows + 1]
|
||||
int total_windows,
|
||||
int spatial_merge_unit
|
||||
)
|
||||
{
|
||||
int tid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
if (tid == 0)
|
||||
{
|
||||
cu_window_seqlens[0] = 0;
|
||||
}
|
||||
|
||||
if (tid < total_windows)
|
||||
{
|
||||
cu_window_seqlens[tid + 1] = window_counts[tid] * spatial_merge_unit;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
if (tid == 0)
|
||||
{
|
||||
for (int i = 1; i <= total_windows; i++)
|
||||
{
|
||||
cu_window_seqlens[i] += cu_window_seqlens[i - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void generate_window_indices(
|
||||
const int *grid_thw,
|
||||
const int *grid_info,
|
||||
const int *cu_window_seqlens,
|
||||
int *window_indices,
|
||||
int vit_merger_window_size,
|
||||
int spatial_merge_unit,
|
||||
int num_grids)
|
||||
{
|
||||
int grid_idx = blockIdx.y;
|
||||
int t_idx = blockIdx.x;
|
||||
|
||||
if (grid_idx >= num_grids)
|
||||
return;
|
||||
|
||||
int grid_t = grid_thw[grid_idx * 3 + 0];
|
||||
if (t_idx >= grid_t)
|
||||
return;
|
||||
|
||||
int llm_h = grid_info[grid_idx * 6 + 2];
|
||||
int llm_w = grid_info[grid_idx * 6 + 3];
|
||||
int num_windows_h = grid_info[grid_idx * 6 + 4];
|
||||
int num_windows_w = grid_info[grid_idx * 6 + 5];
|
||||
|
||||
int element_base = 0;
|
||||
for (int g = 0; g < grid_idx; g++)
|
||||
{
|
||||
element_base += grid_info[g * 6 + 0];
|
||||
}
|
||||
int t_element_base = element_base + t_idx * llm_h * llm_w;
|
||||
|
||||
int window_base = 0;
|
||||
for (int g = 0; g < grid_idx; g++)
|
||||
{
|
||||
window_base += grid_info[g * 6 + 1];
|
||||
}
|
||||
int t_window_base = window_base + t_idx * num_windows_h * num_windows_w;
|
||||
|
||||
int thread_id = threadIdx.x;
|
||||
int warp_id = thread_id / 32;
|
||||
int lane_id = thread_id % 32;
|
||||
|
||||
int windows_per_t = num_windows_h * num_windows_w;
|
||||
int warps_per_block = blockDim.x / 32;
|
||||
|
||||
for (int window_idx = warp_id; window_idx < windows_per_t; window_idx += warps_per_block)
|
||||
{
|
||||
int win_h = window_idx / num_windows_w;
|
||||
int win_w = window_idx % num_windows_w;
|
||||
|
||||
int global_window_idx = t_window_base + window_idx;
|
||||
int output_offset = cu_window_seqlens[global_window_idx] / spatial_merge_unit;
|
||||
|
||||
int start_h = win_h * vit_merger_window_size;
|
||||
int start_w = win_w * vit_merger_window_size;
|
||||
|
||||
int valid_h = min(vit_merger_window_size, llm_h - start_h);
|
||||
int valid_w = min(vit_merger_window_size, llm_w - start_w);
|
||||
|
||||
for (int elem_idx = lane_id; elem_idx < valid_h * valid_w; elem_idx += 32)
|
||||
{
|
||||
int local_h = elem_idx / valid_w;
|
||||
int local_w = elem_idx % valid_w;
|
||||
|
||||
int abs_h = start_h + local_h;
|
||||
int abs_w = start_w + local_w;
|
||||
|
||||
int value = t_element_base + abs_h * llm_w + abs_w;
|
||||
|
||||
int base_offset = output_offset + elem_idx;
|
||||
window_indices[base_offset] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetTotals(
|
||||
const at::Tensor& grid_thw,
|
||||
const at::Tensor& grid_info_tensor,
|
||||
const at::Tensor& global_totals_tensor,
|
||||
int spatial_merge_size,
|
||||
int vit_merger_window_size
|
||||
) {
|
||||
ASSERT_CHECK(grid_thw.dim() == 2 && grid_thw.size(1) == 3);
|
||||
ASSERT_CHECK(grid_thw.scalar_type() == at::kInt);
|
||||
|
||||
int num_grids = grid_thw.size(0);
|
||||
const int *d_grid_thw = static_cast<int32_t*>(grid_thw.data_ptr());
|
||||
|
||||
int *d_grid_info = static_cast<int32_t*>(grid_info_tensor.data_ptr());
|
||||
int *d_global_totals = static_cast<int32_t*>(global_totals_tensor.data_ptr());
|
||||
|
||||
int threads1 = 256;
|
||||
int blocks1 = (num_grids + threads1 - 1) / threads1;
|
||||
|
||||
compute_metadata<<<blocks1, threads1>>>(
|
||||
d_grid_thw, d_grid_info, d_global_totals,
|
||||
num_grids, spatial_merge_size, vit_merger_window_size);
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
void GetWindowIndex(
|
||||
const at::Tensor& grid_thw,
|
||||
const at::Tensor& grid_info_tensor,
|
||||
const at::Tensor& window_indices,
|
||||
const at::Tensor& cu_window_seqlens,
|
||||
const at::Tensor& window_counts_tensor,
|
||||
int max_grid_t,
|
||||
int spatial_merge_size,
|
||||
int vit_merger_window_size,
|
||||
int patch_size,
|
||||
int spatial_merge_unit) {
|
||||
|
||||
int num_grids = grid_thw.size(0);
|
||||
|
||||
dim3 blocks2(max_grid_t, num_grids);
|
||||
dim3 threads2(256);
|
||||
|
||||
const int *d_grid_thw = static_cast<int32_t*>(grid_thw.data_ptr());
|
||||
|
||||
int *d_grid_info = static_cast<int32_t*>(grid_info_tensor.data_ptr());
|
||||
|
||||
int *d_window_indices = static_cast<int32_t*>(window_indices.data_ptr());
|
||||
int *d_cu_window_seqlens = static_cast<int32_t*>(cu_window_seqlens.data_ptr());
|
||||
|
||||
int *d_window_counts = static_cast<int32_t*>(window_counts_tensor.data_ptr());
|
||||
|
||||
compute_window_counts<<<blocks2, threads2>>>(
|
||||
d_grid_thw, d_grid_info, d_window_counts,
|
||||
vit_merger_window_size, spatial_merge_unit, num_grids);
|
||||
|
||||
int total_windows = window_counts_tensor.size(0);
|
||||
|
||||
int threads4 = 256;
|
||||
int blocks4 = (total_windows + threads4 - 1) / threads4;
|
||||
compute_cu_window_seqlens<<<blocks4, threads4>>>(
|
||||
d_window_counts, d_cu_window_seqlens, total_windows, spatial_merge_unit);
|
||||
|
||||
generate_window_indices<<<blocks2, threads2>>>(
|
||||
d_grid_thw, d_grid_info, d_cu_window_seqlens, d_window_indices,
|
||||
vit_merger_window_size, spatial_merge_unit, num_grids);
|
||||
|
||||
sync_check_cuda_error();
|
||||
}
|
||||
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
// Removed TVM FFI export (see pybind11 registration)
|
||||
|
||||
} // wallx_cuda_window_index
|
||||
@@ -0,0 +1,340 @@
|
||||
"""Index computation operators for attention."""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from wall_x.model.core.ops.base import OpsProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Sentinel value for padding in window index computation.
|
||||
# Valid indices are always >= 0 (they are positional offsets within flattened
|
||||
# grid tensors), so any negative value is safe. -100 is chosen by convention
|
||||
# (same as HuggingFace's ignore_index for cross-entropy loss).
|
||||
_PAD_VALUE = -100
|
||||
|
||||
|
||||
def _compute_vision_position_ids(
|
||||
input_tokens,
|
||||
token_set,
|
||||
st,
|
||||
image_grid_thw,
|
||||
video_grid_thw,
|
||||
second_per_grid_ts,
|
||||
image_index,
|
||||
video_index,
|
||||
remain_images,
|
||||
remain_videos,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
spatial_merge_size,
|
||||
tokens_per_second,
|
||||
device,
|
||||
llm_pos_ids_list,
|
||||
):
|
||||
"""Compute position IDs for a single vision token (image or video).
|
||||
|
||||
Returns updated (st, image_index, video_index, remain_images, remain_videos).
|
||||
"""
|
||||
if image_token_id in token_set and remain_images > 0:
|
||||
ed_image = input_tokens.index(image_token_id, st)
|
||||
else:
|
||||
ed_image = len(input_tokens) + 1
|
||||
if video_token_id in token_set and remain_videos > 0:
|
||||
ed_video = input_tokens.index(video_token_id, st)
|
||||
else:
|
||||
ed_video = len(input_tokens) + 1
|
||||
|
||||
if ed_image < ed_video:
|
||||
if image_index >= len(image_grid_thw):
|
||||
raise IndexError(
|
||||
f"image_index {image_index} out of range (have {len(image_grid_thw)} image grids)"
|
||||
)
|
||||
t, h, w = (
|
||||
image_grid_thw[image_index][0],
|
||||
image_grid_thw[image_index][1],
|
||||
image_grid_thw[image_index][2],
|
||||
)
|
||||
# Images are single-frame: no temporal progression, so second_per_grid_t = 0.
|
||||
# This makes all image tokens share t_index = 0 (only spatial positions vary).
|
||||
second_per_grid_t = 0
|
||||
image_index += 1
|
||||
remain_images -= 1
|
||||
ed = ed_image
|
||||
else:
|
||||
if video_index >= len(video_grid_thw):
|
||||
raise IndexError(
|
||||
f"video_index {video_index} out of range (have {len(video_grid_thw)} video grids)"
|
||||
)
|
||||
t, h, w = (
|
||||
video_grid_thw[video_index][0],
|
||||
video_grid_thw[video_index][1],
|
||||
video_grid_thw[video_index][2],
|
||||
)
|
||||
if second_per_grid_ts is not None:
|
||||
second_per_grid_t = second_per_grid_ts[video_index]
|
||||
else:
|
||||
second_per_grid_t = 1.0
|
||||
video_index += 1
|
||||
remain_videos -= 1
|
||||
ed = ed_video
|
||||
|
||||
llm_grid_t, llm_grid_h, llm_grid_w = (
|
||||
int(t),
|
||||
int(h) // spatial_merge_size,
|
||||
int(w) // spatial_merge_size,
|
||||
)
|
||||
text_len = ed - st
|
||||
st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
|
||||
llm_pos_ids_list.append(
|
||||
torch.arange(text_len, device=device).view(1, -1).expand(3, -1) + st_idx
|
||||
)
|
||||
range_tensor = torch.arange(llm_grid_t, device=device).view(-1, 1)
|
||||
expanded_range = range_tensor.expand(-1, llm_grid_h * llm_grid_w)
|
||||
time_tensor = expanded_range * second_per_grid_t * tokens_per_second
|
||||
t_index = time_tensor.long().flatten()
|
||||
h_index = (
|
||||
torch.arange(llm_grid_h, device=device)
|
||||
.view(1, -1, 1)
|
||||
.expand(llm_grid_t, -1, llm_grid_w)
|
||||
.flatten()
|
||||
)
|
||||
w_index = (
|
||||
torch.arange(llm_grid_w, device=device)
|
||||
.view(1, 1, -1)
|
||||
.expand(llm_grid_t, llm_grid_h, -1)
|
||||
.flatten()
|
||||
)
|
||||
llm_pos_ids_list.append(
|
||||
torch.stack([t_index, h_index, w_index]) + text_len + st_idx
|
||||
)
|
||||
st = ed + llm_grid_t * llm_grid_h * llm_grid_w
|
||||
return st, image_index, video_index, remain_images, remain_videos
|
||||
|
||||
|
||||
def _compute_text_only_positions(input_ids, attention_mask):
|
||||
"""Compute position IDs for text-only inputs (no vision tokens)."""
|
||||
if attention_mask is not None:
|
||||
position_ids = attention_mask.long().cumsum(-1) - 1
|
||||
position_ids.masked_fill_(attention_mask == 0, 1)
|
||||
position_ids = (
|
||||
position_ids.unsqueeze(0).expand(3, -1, -1).to(attention_mask.device)
|
||||
)
|
||||
max_position_ids = position_ids.max(0, keepdim=False)[0].max(-1, keepdim=True)[
|
||||
0
|
||||
]
|
||||
mrope_position_deltas = max_position_ids + 1 - attention_mask.shape[-1]
|
||||
else:
|
||||
position_ids = (
|
||||
torch.arange(input_ids.shape[1], device=input_ids.device)
|
||||
.view(1, 1, -1)
|
||||
.expand(3, input_ids.shape[0], -1)
|
||||
)
|
||||
mrope_position_deltas = torch.zeros(
|
||||
[input_ids.shape[0], 1],
|
||||
device=input_ids.device,
|
||||
dtype=input_ids.dtype,
|
||||
)
|
||||
return position_ids, mrope_position_deltas
|
||||
|
||||
|
||||
class GetRopeIndexOp(OpsProxy):
|
||||
"""Compute 3D RoPE position indices for multimodal inputs.
|
||||
|
||||
Signature: get_rope_index(input_ids, image_grid_thw, video_grid_thw,
|
||||
second_per_grid_ts, attention_mask, spatial_merge_size,
|
||||
image_token_id, video_token_id, vision_start_token_id,
|
||||
tokens_per_second) -> (position_ids, mrope_position_deltas)
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "get_rope_index"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import GetRopeIndex
|
||||
|
||||
return GetRopeIndex()
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("GetRopeIndexOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(
|
||||
self,
|
||||
input_ids: torch.LongTensor,
|
||||
image_grid_thw: Optional[torch.LongTensor],
|
||||
video_grid_thw: Optional[torch.LongTensor],
|
||||
second_per_grid_ts: Optional[torch.Tensor],
|
||||
attention_mask: Optional[torch.Tensor],
|
||||
spatial_merge_size: int,
|
||||
image_token_id: int,
|
||||
video_token_id: int,
|
||||
vision_start_token_id: int,
|
||||
tokens_per_second: int,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
if input_ids is None or (image_grid_thw is None and video_grid_thw is None):
|
||||
return _compute_text_only_positions(input_ids, attention_mask)
|
||||
|
||||
mrope_position_deltas = []
|
||||
device = input_ids.device
|
||||
total_input_ids = input_ids
|
||||
if attention_mask is None:
|
||||
attention_mask = torch.ones_like(total_input_ids)
|
||||
position_ids = torch.ones(
|
||||
3,
|
||||
input_ids.shape[0],
|
||||
input_ids.shape[1],
|
||||
dtype=input_ids.dtype,
|
||||
device=input_ids.device,
|
||||
)
|
||||
image_index, video_index = 0, 0
|
||||
attention_mask = attention_mask.to(total_input_ids.device)
|
||||
for i, input_ids_i in enumerate(total_input_ids):
|
||||
input_ids_i = input_ids_i[attention_mask[i] == 1]
|
||||
vision_start_indices = torch.argwhere(
|
||||
input_ids_i == vision_start_token_id
|
||||
).squeeze(1)
|
||||
# Boundary check: ensure vision_start + 1 doesn't exceed sequence length
|
||||
valid_mask = (vision_start_indices + 1) < len(input_ids_i)
|
||||
vision_start_indices = vision_start_indices[valid_mask]
|
||||
vision_tokens = input_ids_i[vision_start_indices + 1]
|
||||
image_nums = int((vision_tokens == image_token_id).sum())
|
||||
video_nums = int((vision_tokens == video_token_id).sum())
|
||||
input_tokens = input_ids_i.tolist()
|
||||
token_set = set(input_tokens)
|
||||
llm_pos_ids_list: list = []
|
||||
st = 0
|
||||
remain_images, remain_videos = image_nums, video_nums
|
||||
for _ in range(image_nums + video_nums):
|
||||
st, image_index, video_index, remain_images, remain_videos = (
|
||||
_compute_vision_position_ids(
|
||||
input_tokens,
|
||||
token_set,
|
||||
st,
|
||||
image_grid_thw,
|
||||
video_grid_thw,
|
||||
second_per_grid_ts,
|
||||
image_index,
|
||||
video_index,
|
||||
remain_images,
|
||||
remain_videos,
|
||||
image_token_id,
|
||||
video_token_id,
|
||||
spatial_merge_size,
|
||||
tokens_per_second,
|
||||
device,
|
||||
llm_pos_ids_list,
|
||||
)
|
||||
)
|
||||
if st < len(input_tokens):
|
||||
st_idx = (
|
||||
llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
|
||||
)
|
||||
text_len = len(input_tokens) - st
|
||||
llm_pos_ids_list.append(
|
||||
torch.arange(text_len, device=device).view(1, -1).expand(3, -1)
|
||||
+ st_idx
|
||||
)
|
||||
llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1)
|
||||
position_ids[..., i, attention_mask[i] == 1] = llm_positions.to(
|
||||
position_ids.device
|
||||
)
|
||||
mrope_position_deltas.append(
|
||||
llm_positions.max() + 1 - len(total_input_ids[i])
|
||||
)
|
||||
mrope_position_deltas = torch.tensor(
|
||||
mrope_position_deltas, device=total_input_ids.device
|
||||
).unsqueeze(1)
|
||||
return position_ids, mrope_position_deltas
|
||||
|
||||
|
||||
class GetWindowIndexOp(OpsProxy):
|
||||
"""Compute window attention indices for ViT.
|
||||
|
||||
Signature: get_window_index(grid_thw, window_size, spatial_merge_size,
|
||||
patch_size, spatial_merge_unit=1) -> (window_index, cu_window_seqlens)
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "get_window_index"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import get_window_index_cuda
|
||||
|
||||
return get_window_index_cuda
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("GetWindowIndexOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(
|
||||
self,
|
||||
grid_thw,
|
||||
window_size,
|
||||
spatial_merge_size,
|
||||
patch_size,
|
||||
spatial_merge_unit=1,
|
||||
):
|
||||
device = grid_thw.device
|
||||
vit_merger_window_size = window_size // spatial_merge_size // patch_size
|
||||
window_index_list = []
|
||||
cu_window_seqlens = [0]
|
||||
window_index_id = 0
|
||||
for grid_t, grid_h, grid_w in grid_thw:
|
||||
llm_grid_h = grid_h // spatial_merge_size
|
||||
llm_grid_w = grid_w // spatial_merge_size
|
||||
index = torch.arange(
|
||||
grid_t * llm_grid_h * llm_grid_w, device=device
|
||||
).reshape(grid_t, llm_grid_h, llm_grid_w)
|
||||
pad_h = (
|
||||
vit_merger_window_size - llm_grid_h % vit_merger_window_size
|
||||
) % vit_merger_window_size
|
||||
pad_w = (
|
||||
vit_merger_window_size - llm_grid_w % vit_merger_window_size
|
||||
) % vit_merger_window_size
|
||||
num_windows_h = (llm_grid_h + pad_h) // vit_merger_window_size
|
||||
num_windows_w = (llm_grid_w + pad_w) // vit_merger_window_size
|
||||
index_padded = F.pad(index, (0, pad_w, 0, pad_h), "constant", _PAD_VALUE)
|
||||
index_padded = (
|
||||
index_padded.reshape(
|
||||
grid_t,
|
||||
num_windows_h,
|
||||
vit_merger_window_size,
|
||||
num_windows_w,
|
||||
vit_merger_window_size,
|
||||
)
|
||||
.permute(0, 1, 3, 2, 4)
|
||||
.reshape(
|
||||
grid_t,
|
||||
num_windows_h * num_windows_w,
|
||||
vit_merger_window_size,
|
||||
vit_merger_window_size,
|
||||
)
|
||||
)
|
||||
seqlens = (index_padded != _PAD_VALUE).sum([2, 3]).reshape(-1)
|
||||
index_padded = index_padded.reshape(-1)
|
||||
index_new = index_padded[index_padded != _PAD_VALUE]
|
||||
window_index_list.append(index_new + window_index_id)
|
||||
cu_seqlens_tmp = (
|
||||
seqlens.cumsum(0) * spatial_merge_unit + cu_window_seqlens[-1]
|
||||
)
|
||||
cu_window_seqlens.extend(cu_seqlens_tmp.tolist())
|
||||
window_index_id += int(grid_t) * int(llm_grid_h) * int(llm_grid_w)
|
||||
window_index = torch.cat(window_index_list, dim=0)
|
||||
cu_window_seqlens = torch.tensor(
|
||||
cu_window_seqlens, dtype=grid_thw.dtype, device=grid_thw.device
|
||||
)
|
||||
return window_index, cu_window_seqlens
|
||||
|
||||
|
||||
get_rope_index = GetRopeIndexOp()
|
||||
get_window_index = GetWindowIndexOp()
|
||||
@@ -0,0 +1,97 @@
|
||||
"""MoE routing operators (permute/unpermute).
|
||||
|
||||
These operators reorder tokens by expert assignment for efficient
|
||||
Mixture-of-Experts processing.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import torch
|
||||
|
||||
from wall_x.model.core.ops.base import OpsProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PermuteOp(OpsProxy):
|
||||
"""Reorder tokens by expert assignment for MoE processing.
|
||||
|
||||
Signature: permute(tokens, indices, num_out_tokens=None, max_token_num=0) -> (permuted_tokens, sorted_indices)
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "permute"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import permute_kernel
|
||||
|
||||
return permute_kernel
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("PermuteOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(self, tokens, indices, num_out_tokens=None, max_token_num=0):
|
||||
"""PyTorch fallback for permute.
|
||||
|
||||
Args:
|
||||
num_out_tokens: If set, truncate output to this many tokens (matches
|
||||
external_accel behavior of discarding padding expert assignments).
|
||||
max_token_num: Unused, kept for external_accel API compatibility.
|
||||
"""
|
||||
del max_token_num # unused, external_accel API compat
|
||||
if indices.dim() == 1:
|
||||
indices = indices.view(-1, 1)
|
||||
expand_factor = indices.size(1)
|
||||
flatten_indices = indices.view(-1)
|
||||
# Keep int64 throughout to avoid precision loss on large token counts
|
||||
sorted_indices = torch.argsort(flatten_indices, stable=True)
|
||||
permuted_tokens = tokens.index_select(0, sorted_indices // expand_factor)
|
||||
if num_out_tokens is not None:
|
||||
permuted_tokens = permuted_tokens[:num_out_tokens]
|
||||
sorted_indices = sorted_indices[:num_out_tokens]
|
||||
return permuted_tokens, sorted_indices
|
||||
|
||||
|
||||
class UnpermuteOp(OpsProxy):
|
||||
"""Restore tokens to original order after MoE processing.
|
||||
|
||||
Signature: unpermute(permuted_tokens, sorted_indices, probs=None) -> restored_tokens
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "unpermute"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import unpermute_kernel
|
||||
|
||||
return unpermute_kernel
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("UnpermuteOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(self, permuted_tokens, sorted_indices, probs=None):
|
||||
if probs is not None:
|
||||
merge_factor = probs.size(1)
|
||||
else:
|
||||
merge_factor = 1
|
||||
unpermuted_tokens = torch.zeros_like(permuted_tokens)
|
||||
unpermuted_tokens.index_copy_(0, sorted_indices.long(), permuted_tokens)
|
||||
unpermuted_tokens = unpermuted_tokens.reshape(
|
||||
-1, merge_factor, permuted_tokens.size(-1)
|
||||
)
|
||||
if probs is not None:
|
||||
unpermuted_tokens = unpermuted_tokens * probs.unsqueeze(-1)
|
||||
unpermuted_tokens = unpermuted_tokens.sum(dim=1)
|
||||
return unpermuted_tokens
|
||||
|
||||
|
||||
permute = PermuteOp()
|
||||
unpermute = UnpermuteOp()
|
||||
@@ -0,0 +1,42 @@
|
||||
"""RMS normalization operator."""
|
||||
|
||||
import logging
|
||||
|
||||
import torch
|
||||
|
||||
from wall_x.model.core.ops.base import OpsProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RMSNormOp(OpsProxy):
|
||||
"""RMS normalization: rmsnorm(x, weight, eps).
|
||||
|
||||
Accepts ``rmsnorm(x, weight, eps)``.
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "rmsnorm"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import Rmsnorm
|
||||
|
||||
return Rmsnorm()
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("RMSNormOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(self, hidden_states, weight, eps=1e-6):
|
||||
"""Pure PyTorch RMSNorm. eps default matches external_accel (1e-6, not PyTorch's 1e-5)."""
|
||||
input_dtype = hidden_states.dtype
|
||||
hidden_states = hidden_states.to(torch.float32)
|
||||
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
||||
hidden_states = hidden_states * torch.rsqrt(variance + eps)
|
||||
return (weight * hidden_states).to(input_dtype)
|
||||
|
||||
|
||||
rmsnorm = RMSNormOp()
|
||||
@@ -0,0 +1,234 @@
|
||||
"""Rotary position embedding operators."""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
|
||||
from wall_x.model.core.ops.base import OpsProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _rotate_half(x):
|
||||
"""Rotate half: split last dim in two halves, negate-swap, concat."""
|
||||
x1 = x[..., : x.shape[-1] // 2]
|
||||
x2 = x[..., x.shape[-1] // 2 :]
|
||||
return torch.cat((-x2, x1), dim=-1)
|
||||
|
||||
|
||||
def _rotate_interleave(x):
|
||||
"""Interleaved rotation: pairs of (even, odd) elements."""
|
||||
x_even = x[..., ::2]
|
||||
x_odd = x[..., 1::2]
|
||||
return torch.stack((-x_odd, x_even), dim=-1).flatten(-2)
|
||||
|
||||
|
||||
class RoPEOp(OpsProxy):
|
||||
"""Standard rotary position embedding.
|
||||
|
||||
Signature: rope(q, k, cos, sin, interleave=False) -> (q_embed, k_embed)
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "rope"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import Rope
|
||||
|
||||
return Rope()
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("RoPEOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(self, q, k, cos, sin, interleave=False, **kwargs):
|
||||
cos = cos.float()
|
||||
sin = sin.float()
|
||||
rotary_dim = cos.size(-1) * 2
|
||||
head_dim = q.size(-1)
|
||||
if rotary_dim > head_dim:
|
||||
raise ValueError(
|
||||
f"rotary_dim ({rotary_dim}) > head_dim ({head_dim}): "
|
||||
f"cos last dim ({cos.size(-1)}) is too large for q"
|
||||
)
|
||||
partial = rotary_dim < head_dim
|
||||
if partial:
|
||||
q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
|
||||
k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
|
||||
else:
|
||||
q_rot, k_rot = q, k
|
||||
cos = cos.unsqueeze(-2) # (..., 1, half_dim)
|
||||
sin = sin.unsqueeze(-2)
|
||||
if interleave:
|
||||
cos = cos.repeat_interleave(2, dim=-1)
|
||||
sin = sin.repeat_interleave(2, dim=-1)
|
||||
q_embed = q_rot.float() * cos + _rotate_interleave(q_rot.float()) * sin
|
||||
k_embed = k_rot.float() * cos + _rotate_interleave(k_rot.float()) * sin
|
||||
else:
|
||||
cos = torch.cat((cos, cos), dim=-1)
|
||||
sin = torch.cat((sin, sin), dim=-1)
|
||||
q_embed = q_rot.float() * cos + _rotate_half(q_rot.float()) * sin
|
||||
k_embed = k_rot.float() * cos + _rotate_half(k_rot.float()) * sin
|
||||
if partial:
|
||||
q_embed = torch.cat([q_embed.to(q.dtype), q_pass], dim=-1)
|
||||
k_embed = torch.cat([k_embed.to(k.dtype), k_pass], dim=-1)
|
||||
else:
|
||||
q_embed = q_embed.to(q.dtype)
|
||||
k_embed = k_embed.to(k.dtype)
|
||||
return q_embed, k_embed
|
||||
|
||||
def pack(self, *args, **kwargs):
|
||||
"""Delegate to resolved backend's pack method if available."""
|
||||
if self._resolved_fn is None:
|
||||
self._resolve()
|
||||
if hasattr(self._resolved_fn, "pack"):
|
||||
return self._resolved_fn.pack(*args, **kwargs)
|
||||
return args
|
||||
|
||||
|
||||
class MRoPEOp(OpsProxy):
|
||||
"""Multi-head rotary position embedding (used by Qwen2.5-VL models).
|
||||
|
||||
Signature: m_rope(query_states, key_states, cos, sin, mrope_section, interleaved=False) -> (q_embed, k_embed)
|
||||
|
||||
cos/sin shape: (3, B, S, D//2). mrope_section like [16, 24, 24] specifies
|
||||
how many head dims each of T/H/W occupies. After split by mrope_section * 2,
|
||||
uses i % 3 to select from corresponding temporal/height/width rows.
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "m_rope"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import MRope
|
||||
|
||||
return MRope()
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("MRoPEOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(
|
||||
self,
|
||||
query_states,
|
||||
key_states,
|
||||
cos,
|
||||
sin,
|
||||
mrope_section: List[int],
|
||||
interleaved=False,
|
||||
**kwargs,
|
||||
):
|
||||
cos = cos.float()
|
||||
sin = sin.float()
|
||||
# Double cos/sin along last dim (matching external_accel kernel's internal behavior)
|
||||
cos = torch.cat((cos, cos), dim=-1)
|
||||
sin = torch.cat((sin, sin), dim=-1)
|
||||
# List concatenation: [16,24,24] -> [16,24,24,16,24,24] (NOT element-wise multiply)
|
||||
mrope_section_doubled = mrope_section + mrope_section
|
||||
cos_split = torch.cat(
|
||||
[m[i % 3] for i, m in enumerate(cos.split(mrope_section_doubled, dim=-1))],
|
||||
dim=-1,
|
||||
).unsqueeze(
|
||||
2
|
||||
) # (B, S, 1, D)
|
||||
sin_split = torch.cat(
|
||||
[m[i % 3] for i, m in enumerate(sin.split(mrope_section_doubled, dim=-1))],
|
||||
dim=-1,
|
||||
).unsqueeze(2)
|
||||
q_embed = (query_states.float() * cos_split) + (
|
||||
_rotate_half(query_states.float()) * sin_split
|
||||
)
|
||||
k_embed = (key_states.float() * cos_split) + (
|
||||
_rotate_half(key_states.float()) * sin_split
|
||||
)
|
||||
return q_embed.to(query_states.dtype), k_embed.to(key_states.dtype)
|
||||
|
||||
def pack(self, *args, **kwargs):
|
||||
"""Delegate to resolved backend's pack method if available."""
|
||||
if self._resolved_fn is None:
|
||||
self._resolve()
|
||||
if hasattr(self._resolved_fn, "pack"):
|
||||
return self._resolved_fn.pack(*args, **kwargs)
|
||||
return args
|
||||
|
||||
|
||||
class RotPosEmbOp(OpsProxy):
|
||||
"""Rotary position embedding computation for ViT (used by Qwen2.5-VL vision encoder).
|
||||
|
||||
Signature: rot_pos_emb(inv_freq, grid_thw, spatial_merge_size) -> rotary_pos_emb
|
||||
"""
|
||||
|
||||
@property
|
||||
def _external_accel_name(self):
|
||||
return "rot_pos_emb"
|
||||
|
||||
def _get_cuda_kernel(self):
|
||||
try:
|
||||
from wall_x.model.core.ops._cuda_wrappers import RotPos
|
||||
|
||||
return RotPos()
|
||||
except ImportError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("RotPosEmbOp: CUDA kernel load failed: %s", e)
|
||||
return None
|
||||
|
||||
def _pytorch_fallback(self, inv_freq, grid_thw, spatial_merge_size):
|
||||
if inv_freq.dtype != torch.float32:
|
||||
inv_freq = inv_freq.to(torch.float32)
|
||||
pos_ids = []
|
||||
for t, h, w in grid_thw:
|
||||
t, h, w = int(t), int(h), int(w)
|
||||
if h % spatial_merge_size != 0 or w % spatial_merge_size != 0:
|
||||
raise ValueError(
|
||||
f"grid h={h}, w={w} must be divisible by spatial_merge_size={spatial_merge_size}"
|
||||
)
|
||||
hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
|
||||
hpos_ids = (
|
||||
hpos_ids.reshape(
|
||||
h // spatial_merge_size,
|
||||
spatial_merge_size,
|
||||
w // spatial_merge_size,
|
||||
spatial_merge_size,
|
||||
)
|
||||
.permute(0, 2, 1, 3)
|
||||
.flatten()
|
||||
)
|
||||
wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
|
||||
wpos_ids = (
|
||||
wpos_ids.reshape(
|
||||
h // spatial_merge_size,
|
||||
spatial_merge_size,
|
||||
w // spatial_merge_size,
|
||||
spatial_merge_size,
|
||||
)
|
||||
.permute(0, 2, 1, 3)
|
||||
.flatten()
|
||||
)
|
||||
pos_ids.append(torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
|
||||
pos_ids = torch.cat(pos_ids, dim=0).to(inv_freq.device)
|
||||
max_grid_size = grid_thw[:, 1:].max()
|
||||
seq = torch.arange(max_grid_size, device=inv_freq.device, dtype=inv_freq.dtype)
|
||||
rotary_pos_emb_full = torch.outer(seq, inv_freq)
|
||||
rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
|
||||
return rotary_pos_emb.to(torch.float)
|
||||
|
||||
def pack(self, *args, **kwargs):
|
||||
"""Delegate to resolved backend's pack method if available."""
|
||||
if self._resolved_fn is None:
|
||||
self._resolve()
|
||||
if hasattr(self._resolved_fn, "pack"):
|
||||
return self._resolved_fn.pack(*args, **kwargs)
|
||||
return args
|
||||
|
||||
|
||||
rope = RoPEOp()
|
||||
m_rope = MRoPEOp()
|
||||
rot_pos_emb = RotPosEmbOp()
|
||||
Reference in New Issue
Block a user