Init
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
High-performance C++ backend interface for optimized matrix operations.
|
||||
|
||||
This module provides Python bindings for custom CUDA kernels optimized for
|
||||
transformer and MoE (Mixture of Experts) operations, including:
|
||||
- Asymmetric dual expert operations
|
||||
- Token permutation/unpermutation for MoE routing
|
||||
- RoPE (Rotary Position Embedding) operations
|
||||
"""
|
||||
|
||||
import torch
|
||||
from typing import Tuple, Optional
|
||||
import wallx_csrc as backend
|
||||
|
||||
|
||||
|
||||
def _allocate_asymmetric_dual_outputs(input_expert0: torch.Tensor,
|
||||
input_expert1: torch.Tensor,
|
||||
weight_expert0: torch.Tensor,
|
||||
weight_expert1: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Allocate output tensors for asymmetric dual expert GEMM operations.
|
||||
|
||||
This function handles the case where two experts may have different output
|
||||
dimensions, which is common in heterogeneous MoE architectures.
|
||||
|
||||
Args:
|
||||
input_expert0 (torch.Tensor): Expert 0 input tensor of shape [m0, k]
|
||||
input_expert1 (torch.Tensor): Expert 1 input tensor of shape [m1, k]
|
||||
weight_expert0 (torch.Tensor): Expert 0 weight tensor of shape [k, n0]
|
||||
weight_expert1 (torch.Tensor): Expert 1 weight tensor of shape [k, n1]
|
||||
|
||||
Returns:
|
||||
Tuple[torch.Tensor, torch.Tensor]: Pre-allocated output tensors
|
||||
- output_expert0: Shape [m0, n0]
|
||||
- output_expert1: Shape [m1, n1]
|
||||
|
||||
Raises:
|
||||
AssertionError: If tensor dimensions are incompatible
|
||||
"""
|
||||
# Validate input tensor dimensions
|
||||
assert input_expert0.ndim == 2, "Expected 2D tensor for input_expert0"
|
||||
assert input_expert1.ndim == 2, "Expected 2D tensor for input_expert1"
|
||||
assert weight_expert0.ndim == 2, "Expected 2D tensor for weight_expert0"
|
||||
assert weight_expert1.ndim == 2, "Expected 2D tensor for weight_expert1"
|
||||
|
||||
# Verify dimension compatibility for matrix multiplication
|
||||
assert input_expert0.size(1) == weight_expert0.size(0), \
|
||||
f"Input expert0 K dimension {input_expert0.size(1)} != weight expert0 K dimension {weight_expert0.size(0)}"
|
||||
assert input_expert1.size(1) == weight_expert1.size(0), \
|
||||
f"Input expert1 K dimension {input_expert1.size(1)} != weight expert1 K dimension {weight_expert1.size(0)}"
|
||||
|
||||
# Calculate output shapes: [m, k] × [k, n] = [m, n]
|
||||
m0, n0 = input_expert0.size(0), weight_expert0.size(1)
|
||||
m1, n1 = input_expert1.size(0), weight_expert1.size(1)
|
||||
|
||||
# Allocate output tensors with matching device and dtype
|
||||
output_expert0 = torch.empty(m0, n0, device=input_expert0.device, dtype=input_expert0.dtype)
|
||||
output_expert1 = torch.empty(m1, n1, device=input_expert1.device, dtype=input_expert1.dtype)
|
||||
|
||||
return output_expert0, output_expert1
|
||||
|
||||
|
||||
def asym_dual_gmm_separated(input_expert0: torch.Tensor,
|
||||
input_expert1: torch.Tensor,
|
||||
weight_expert0: torch.Tensor,
|
||||
weight_expert1: torch.Tensor,
|
||||
output_expert0: Optional[torch.Tensor] = None,
|
||||
output_expert1: Optional[torch.Tensor] = None,
|
||||
trans_a: bool = False,
|
||||
trans_b: bool = False) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Asymmetric dual expert grouped GEMM with separated inputs and outputs.
|
||||
|
||||
This is the recommended interface for maximum flexibility and performance when
|
||||
dealing with two experts that may have different intermediate dimensions.
|
||||
The operation is equivalent to:
|
||||
output_expert0 = input_expert0 @ weight_expert0
|
||||
output_expert1 = input_expert1 @ weight_expert1
|
||||
But optimized as a single fused kernel call.
|
||||
|
||||
Args:
|
||||
input_expert0 (torch.Tensor): Expert 0 input tensor of shape [m0, k]
|
||||
input_expert1 (torch.Tensor): Expert 1 input tensor of shape [m1, k]
|
||||
weight_expert0 (torch.Tensor): Expert 0 weight tensor of shape [k, n0]
|
||||
weight_expert1 (torch.Tensor): Expert 1 weight tensor of shape [k, n1]
|
||||
Note: n0 can be different from n1
|
||||
output_expert0 (torch.Tensor, optional): Pre-allocated output for expert 0 [m0, n0]
|
||||
output_expert1 (torch.Tensor, optional): Pre-allocated output for expert 1 [m1, n1]
|
||||
trans_a (bool, optional): Whether to transpose input tensors. Defaults to False.
|
||||
trans_b (bool, optional): Whether to transpose weight tensors. Defaults to False.
|
||||
|
||||
Returns:
|
||||
Tuple[torch.Tensor, torch.Tensor]: Output tensors (output_expert0, output_expert1)
|
||||
|
||||
Example:
|
||||
>>> # Two experts with different output dimensions
|
||||
>>> input0 = torch.randn(512, 1024, device='cuda') # 512 tokens for expert 0
|
||||
>>> input1 = torch.randn(256, 1024, device='cuda') # 256 tokens for expert 1
|
||||
>>> weight0 = torch.randn(1024, 2048, device='cuda') # Expert 0: 1024->2048
|
||||
>>> weight1 = torch.randn(1024, 4096, device='cuda') # Expert 1: 1024->4096
|
||||
>>> out0, out1 = asym_dual_gmm_separated(input0, input1, weight0, weight1)
|
||||
"""
|
||||
# Allocate outputs if not provided
|
||||
if output_expert0 is None or output_expert1 is None:
|
||||
alloc_out0, alloc_out1 = _allocate_asymmetric_dual_outputs(
|
||||
input_expert0, input_expert1, weight_expert0, weight_expert1
|
||||
)
|
||||
if output_expert0 is None:
|
||||
output_expert0 = alloc_out0
|
||||
if output_expert1 is None:
|
||||
output_expert1 = alloc_out1
|
||||
|
||||
# Call optimized C++ backend kernel
|
||||
backend.asym_dual_gmm(
|
||||
input_expert0, input_expert1,
|
||||
weight_expert0, weight_expert1,
|
||||
output_expert0, output_expert1,
|
||||
trans_a, trans_b
|
||||
)
|
||||
|
||||
return output_expert0, output_expert1
|
||||
|
||||
|
||||
def permute(input: torch.Tensor,
|
||||
indices: torch.Tensor,
|
||||
num_out_tokens: int,
|
||||
workspace: torch.Tensor,
|
||||
max_expanded_token_num: int) -> torch.Tensor:
|
||||
"""
|
||||
Permute input tokens according to expert assignment indices for MoE routing.
|
||||
|
||||
This function reorders tokens based on their assigned experts to enable
|
||||
efficient grouped processing. Used in the forward pass of MoE layers.
|
||||
|
||||
Args:
|
||||
input (torch.Tensor): Input tokens to permute
|
||||
indices (torch.Tensor): Expert assignment indices for each token
|
||||
num_out_tokens (int): Number of output tokens after expansion
|
||||
workspace (torch.Tensor): Temporary workspace tensor for intermediate computations
|
||||
max_expanded_token_num (int): Maximum number of tokens after top-k expansion
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Permuted tokens grouped by expert assignment
|
||||
|
||||
Note:
|
||||
This is typically used with top-k expert selection where each token
|
||||
can be routed to multiple experts.
|
||||
"""
|
||||
return backend.permute(input, indices, num_out_tokens, workspace, max_expanded_token_num)
|
||||
|
||||
|
||||
def unpermute(input: torch.Tensor,
|
||||
row_id_map: torch.Tensor,
|
||||
prob: torch.Tensor,
|
||||
max_tokens: int,
|
||||
num_topK: int) -> torch.Tensor:
|
||||
"""
|
||||
Unpermute expert outputs back to original token order with probability weighting.
|
||||
|
||||
This function reverses the permutation applied in the forward pass and combines
|
||||
outputs from multiple experts using their routing probabilities.
|
||||
|
||||
Args:
|
||||
input (torch.Tensor): Permuted expert outputs to unpermute
|
||||
row_id_map (torch.Tensor): Mapping from permuted positions to original positions
|
||||
prob (torch.Tensor): Expert routing probabilities for weighted combination
|
||||
max_tokens (int): Maximum number of tokens in the sequence
|
||||
num_topK (int): Number of top experts selected per token
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Unpermuted tokens in original order with expert outputs combined
|
||||
|
||||
Note:
|
||||
The output combines multiple expert predictions for each token using
|
||||
the routing probabilities as weights.
|
||||
"""
|
||||
return backend.unpermute(input, row_id_map, prob, max_tokens, num_topK)
|
||||
|
||||
|
||||
def unpermute_bwd(input_bwd: torch.Tensor,
|
||||
input_fwd: torch.Tensor,
|
||||
row_id_map: torch.Tensor,
|
||||
prob: Optional[torch.Tensor]) -> torch.Tensor:
|
||||
"""
|
||||
Backward pass for unpermute operation with gradient flow.
|
||||
|
||||
This function handles the backward pass through the unpermute operation,
|
||||
ensuring proper gradient flow for training MoE models.
|
||||
|
||||
Args:
|
||||
input_bwd (torch.Tensor): Backward gradients from the next layer
|
||||
input_fwd (torch.Tensor): Forward pass inputs (for gradient computation)
|
||||
row_id_map (torch.Tensor): Row mapping used in forward unpermute
|
||||
prob (torch.Tensor, optional): Expert probabilities. If None, uniform weights are used.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Gradients with respect to the input of unpermute forward pass
|
||||
|
||||
Note:
|
||||
If prob is None, uniform probabilities are assumed for gradient computation.
|
||||
"""
|
||||
# Handle case where probabilities are not provided
|
||||
if prob is None:
|
||||
prob = torch.ones([input_bwd.size(0), 1], dtype=torch.float32, device=input_bwd.device)
|
||||
|
||||
return backend.unpermute_bwd(input_bwd, input_fwd, row_id_map, prob)
|
||||
|
||||
|
||||
def rope(q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
cos: torch.Tensor,
|
||||
sin: torch.Tensor,
|
||||
q_out: torch.Tensor,
|
||||
k_out: torch.Tensor,
|
||||
mrope_section_doubled: bool) -> None:
|
||||
"""
|
||||
Apply RoPE (Rotary Position Embedding) to query and key tensors.
|
||||
|
||||
Applies rotary position embeddings to query and key tensors using precomputed
|
||||
cosine and sine values. Supports both standard RoPE and multi-dimensional RoPE (mRoPE).
|
||||
|
||||
Args:
|
||||
q (torch.Tensor): Query tensor to apply RoPE to
|
||||
k (torch.Tensor): Key tensor to apply RoPE to
|
||||
cos (torch.Tensor): Precomputed cosine values for rotation
|
||||
sin (torch.Tensor): Precomputed sine values for rotation
|
||||
q_out (torch.Tensor): Output tensor for rotated queries (in-place operation supported)
|
||||
k_out (torch.Tensor): Output tensor for rotated keys (in-place operation supported)
|
||||
mrope_section_doubled (bool): Whether using multi-dimensional RoPE with doubled sections
|
||||
|
||||
Note:
|
||||
This function performs in-place operations if q_out and k_out point to the same
|
||||
memory as q and k respectively. The rotation is applied using the standard
|
||||
RoPE formulation with complex number rotation.
|
||||
"""
|
||||
return backend.rope(q, k, cos, sin, q_out, k_out, mrope_section_doubled)
|
||||
|
||||
|
||||
def rope_bwd(grad_q_out: torch.Tensor,
|
||||
grad_k_out: torch.Tensor,
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
cos: torch.Tensor,
|
||||
sin: torch.Tensor,
|
||||
grad_q: torch.Tensor,
|
||||
grad_k: torch.Tensor,
|
||||
mrope_section_doubled: bool) -> None:
|
||||
"""
|
||||
Backward pass for RoPE operation with gradient computation.
|
||||
|
||||
Computes gradients with respect to the input query and key tensors
|
||||
for the RoPE operation used in transformer attention mechanisms.
|
||||
|
||||
Args:
|
||||
grad_q_out (torch.Tensor): Gradient with respect to output queries
|
||||
grad_k_out (torch.Tensor): Gradient with respect to output keys
|
||||
q (torch.Tensor): Original query tensor from forward pass
|
||||
k (torch.Tensor): Original key tensor from forward pass
|
||||
cos (torch.Tensor): Cosine values used in forward pass
|
||||
sin (torch.Tensor): Sine values used in forward pass
|
||||
grad_q (torch.Tensor): Output tensor for query gradients
|
||||
grad_k (torch.Tensor): Output tensor for key gradients
|
||||
mrope_section_doubled (bool): Whether using multi-dimensional RoPE configuration
|
||||
|
||||
Note:
|
||||
This function computes the analytical gradient of the RoPE operation,
|
||||
which involves the inverse rotation compared to the forward pass.
|
||||
"""
|
||||
return backend.rope_bwd(grad_q_out, grad_k_out, q, k, cos, sin, grad_q, grad_k, mrope_section_doubled)
|
||||
@@ -0,0 +1,397 @@
|
||||
import torch
|
||||
import warnings
|
||||
from wall_x.fusions import backend
|
||||
|
||||
|
||||
class AsymmetricDualExpertGemm(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def forward(ctx, input_expert0, input_expert1, weight_expert0, weight_expert1, trans_b=False):
|
||||
"""
|
||||
Forward pass for asymmetric dual expert GEMM.
|
||||
|
||||
Args:
|
||||
input_expert0: Expert 0 input [m0, k]
|
||||
input_expert1: Expert 1 input [m1, k]
|
||||
weight_expert0: Expert 0 weight [k, n0] or [n0, k] if trans_b=True
|
||||
weight_expert1: Expert 1 weight [k, n1] or [n1, k] if trans_b=True
|
||||
trans_b: Whether to transpose the weight matrices
|
||||
|
||||
Returns:
|
||||
Tuple of (output_expert0, output_expert1)
|
||||
"""
|
||||
# Validate inputs
|
||||
assert input_expert0.dim() == 2, "input_expert0 must be 2D"
|
||||
assert input_expert1.dim() == 2, "input_expert1 must be 2D"
|
||||
assert weight_expert0.dim() == 2, "weight_expert0 must be 2D"
|
||||
assert weight_expert1.dim() == 2, "weight_expert1 must be 2D"
|
||||
|
||||
# Dimension validation depends on trans_b
|
||||
if trans_b:
|
||||
assert input_expert0.size(1) == weight_expert0.size(1), "Expert 0 dimension mismatch (trans_b=True)"
|
||||
assert input_expert1.size(1) == weight_expert1.size(1), "Expert 1 dimension mismatch (trans_b=True)"
|
||||
else:
|
||||
assert input_expert0.size(1) == weight_expert0.size(0), "Expert 0 dimension mismatch (trans_b=False)"
|
||||
assert input_expert1.size(1) == weight_expert1.size(0), "Expert 1 dimension mismatch (trans_b=False)"
|
||||
|
||||
# Save tensors and trans_b for backward pass
|
||||
ctx.save_for_backward(input_expert0, input_expert1, weight_expert0, weight_expert1)
|
||||
ctx.trans_b = trans_b
|
||||
|
||||
# Allocate output tensors
|
||||
m0 = input_expert0.size(0)
|
||||
m1 = input_expert1.size(0)
|
||||
n0 = weight_expert0.size(0) if trans_b else weight_expert0.size(1)
|
||||
n1 = weight_expert1.size(0) if trans_b else weight_expert1.size(1)
|
||||
|
||||
output_expert0 = torch.empty(m0, n0, device=input_expert0.device, dtype=input_expert0.dtype)
|
||||
output_expert1 = torch.empty(m1, n1, device=input_expert1.device, dtype=input_expert1.dtype)
|
||||
|
||||
# Call the backend C++ function
|
||||
backend.asym_dual_gmm_separated(input_expert0, input_expert1, weight_expert0, weight_expert1, output_expert0, output_expert1, trans_b=trans_b)
|
||||
|
||||
return output_expert0, output_expert1
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output_expert0, grad_output_expert1):
|
||||
"""
|
||||
Optimized backward pass using specialized kernels.
|
||||
Always computes all gradients to minimize kernel calls.
|
||||
"""
|
||||
grad_output_expert0 = grad_output_expert0.contiguous()
|
||||
grad_output_expert1 = grad_output_expert1.contiguous()
|
||||
|
||||
input_expert0, input_expert1, weight_expert0, weight_expert1 = ctx.saved_tensors
|
||||
trans_b = ctx.trans_b
|
||||
|
||||
# Always allocate all gradient tensors (no conditional computation)
|
||||
grad_input_expert0 = torch.empty_like(input_expert0)
|
||||
grad_input_expert1 = torch.empty_like(input_expert1)
|
||||
grad_weight_expert0 = torch.empty_like(weight_expert0)
|
||||
grad_weight_expert1 = torch.empty_like(weight_expert1)
|
||||
|
||||
# Compute input gradients: grad_input = grad_output @ weight^T (if trans_b=False)
|
||||
# = grad_output @ weight (if trans_b=True)
|
||||
backend.asym_dual_gmm_separated(
|
||||
grad_output_expert0,
|
||||
grad_output_expert1,
|
||||
weight_expert0,
|
||||
weight_expert1,
|
||||
grad_input_expert0,
|
||||
grad_input_expert1,
|
||||
trans_a=False,
|
||||
trans_b=not trans_b,
|
||||
)
|
||||
|
||||
# Compute weight gradients
|
||||
if trans_b:
|
||||
# When trans_b=True in forward: output = input @ weight^T
|
||||
# So grad_weight^T = input^T @ grad_output
|
||||
# Which means grad_weight = grad_output^T @ input
|
||||
backend.asym_dual_gmm_separated(
|
||||
grad_output_expert0,
|
||||
grad_output_expert1,
|
||||
input_expert0,
|
||||
input_expert1,
|
||||
grad_weight_expert0,
|
||||
grad_weight_expert1,
|
||||
trans_a=True,
|
||||
trans_b=False,
|
||||
)
|
||||
else:
|
||||
# When trans_b=False in forward: output = input @ weight
|
||||
# So grad_weight = input^T @ grad_output
|
||||
backend.asym_dual_gmm_separated(
|
||||
input_expert0,
|
||||
input_expert1,
|
||||
grad_output_expert0,
|
||||
grad_output_expert1,
|
||||
grad_weight_expert0,
|
||||
grad_weight_expert1,
|
||||
trans_a=True,
|
||||
trans_b=False,
|
||||
)
|
||||
|
||||
return grad_input_expert0, grad_input_expert1, grad_weight_expert0, grad_weight_expert1, None
|
||||
|
||||
|
||||
def asym_dual_gmm(input_expert0, input_expert1, weight_expert0, weight_expert1, trans_b=False):
|
||||
"""
|
||||
Convenience function for asymmetric dual expert GEMM.
|
||||
|
||||
Args:
|
||||
input_expert0: Expert 0 input [m0, k]
|
||||
input_expert1: Expert 1 input [m1, k]
|
||||
weight_expert0: Expert 0 weight [k, n0] or [n0, k] if trans_b=True
|
||||
weight_expert1: Expert 1 weight [k, n1] or [n1, k] if trans_b=True
|
||||
trans_b: Whether to transpose the weight matrices
|
||||
|
||||
Returns:
|
||||
Tuple of (output_expert0, output_expert1)
|
||||
"""
|
||||
return AsymmetricDualExpertGemm.apply(input_expert0, input_expert1, weight_expert0, weight_expert1, trans_b)
|
||||
|
||||
|
||||
################################################################################################
|
||||
##
|
||||
## PermuteMoE topK
|
||||
##
|
||||
################################################################################################
|
||||
|
||||
|
||||
class PermuteMoE_topK(torch.autograd.Function):
|
||||
|
||||
workspace_fw = None
|
||||
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]
|
||||
"""
|
||||
# Empty input check
|
||||
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!")
|
||||
if indices.is_cpu:
|
||||
warnings.warn("The input `indices` of permute_topK op is on the device: CPU!")
|
||||
expert_for_rows = expert_for_rows.cuda()
|
||||
|
||||
# Shape check
|
||||
if input_act.size(0) != indices.size(0):
|
||||
raise RuntimeError(f"[Error] permute_topK op input `indices` shape mismatch! " f"Expect {input_act.size(0)}, but got {indices.size(0)}.")
|
||||
|
||||
# Data type check
|
||||
if indices.dtype != torch.int32:
|
||||
warnings.warn(f"The data type of the input `indices` of permute_topK op is {indices.dtype}! " "The recommended type is torch.int32.")
|
||||
indices = indices.to(torch.int32)
|
||||
|
||||
# Contiguous check
|
||||
if not input_act.is_contiguous():
|
||||
warnings.warn("The input `input_act` of permute_topK op is discontiguous!")
|
||||
input_act = input_act.contiguous()
|
||||
if not indices.is_contiguous():
|
||||
warnings.warn("The input `indices` of permute_topK op is discontiguous!")
|
||||
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
|
||||
PermuteMoE_topK.workspace_fw = []
|
||||
|
||||
if PermuteMoE_topK.dtype != input_act.dtype:
|
||||
PermuteMoE_topK.dtype = input_act.dtype
|
||||
PermuteMoE_topK.workspace_fw = []
|
||||
|
||||
permuted_act, row_id_map, PermuteMoE_topK.workspace_fw = backend.permute(
|
||||
input_act, indices, num_out_tokens, PermuteMoE_topK.workspace_fw, 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_act, row_id_map
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, permuted_act_grad, _):
|
||||
# Empty input check
|
||||
if not permuted_act_grad.numel():
|
||||
return permuted_act_grad, None, None, None
|
||||
|
||||
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
|
||||
|
||||
unpermuted_act_grad = backend.unpermute(permuted_act_grad, row_id_map, torch.tensor([]), num_tokens, num_topK)
|
||||
return unpermuted_act_grad, 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):
|
||||
# Empty input check
|
||||
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:
|
||||
warnings.warn("The input `row_id_map` of unpermute_topK op is on the device: CPU!")
|
||||
row_id_map = row_id_map.cuda()
|
||||
if probs is not None and probs.is_cpu:
|
||||
warnings.warn("The input `probs` of unpermute_topK op is on the device: 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:
|
||||
warnings.warn(
|
||||
f"The data type of the input `row_id_map` of unpermute_topK op is {row_id_map.dtype}! " "The recommended type is torch.int32."
|
||||
)
|
||||
row_id_map = row_id_map.to(torch.int32)
|
||||
if probs is not None and probs.dtype != torch.float32:
|
||||
warnings.warn(f"The data type of the input `probs` of unpermute_topK op is {probs.dtype}! " "The recommended type is torch.float32.")
|
||||
probs = probs.to(torch.float32)
|
||||
|
||||
# Contiguous check
|
||||
if not input_act.is_contiguous():
|
||||
warnings.warn("The input `input_act` of unpermute_topK op is discontiguous!")
|
||||
input_act = input_act.contiguous()
|
||||
if not row_id_map.is_contiguous():
|
||||
warnings.warn("The input `row_id_map` of unpermute_topK op is discontiguous!")
|
||||
row_id_map = row_id_map.contiguous()
|
||||
if probs is not None and not probs.is_contiguous():
|
||||
warnings.warn("The input `probs` of unpermute_topK op is discontiguous!")
|
||||
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
|
||||
|
||||
unpermuted_output = backend.unpermute(input_act, row_id_map, probs if probs is not None else torch.tensor([]), num_tokens, num_topK)
|
||||
|
||||
ctx.save_for_backward(input_act, row_id_map, probs)
|
||||
return unpermuted_output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, unpermuted_act_grad):
|
||||
# Empty input check
|
||||
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
|
||||
if ctx.needs_input_grad[0]:
|
||||
act_grad, prob_grad = backend.unpermute_bwd(unpermuted_act_grad, input_act, row_id_map, probs)
|
||||
|
||||
if not ctx.needs_input_grad[2]:
|
||||
prob_grad = None
|
||||
return act_grad, None, prob_grad
|
||||
|
||||
|
||||
def permute(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(input_act, row_id_map, probs=None):
|
||||
return UnpermuteMoE_topK.apply(input_act, row_id_map, probs)
|
||||
|
||||
|
||||
################################################################################################
|
||||
##
|
||||
## mutlimodal RoPE
|
||||
##
|
||||
################################################################################################
|
||||
|
||||
|
||||
class MultimodalRoPE(torch.autograd.Function):
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, mrope_section: list):
|
||||
|
||||
# Device check
|
||||
if q.is_cpu:
|
||||
raise RuntimeError("[Error] The input `q` of multimodal_rope op is on the device: CPU!")
|
||||
if k.is_cpu:
|
||||
raise RuntimeError("[Error] The input `k` of multimodal_rope op is on the device: CPU!")
|
||||
if cos.is_cpu:
|
||||
raise RuntimeError("[Error] The input `cos` of multimodal_rope op is on the device: CPU!")
|
||||
if sin.is_cpu:
|
||||
raise RuntimeError("[Error] The input `sin` of multimodal_rope op is on the device: CPU!")
|
||||
if len(mrope_section) != 3:
|
||||
raise RuntimeError("[Error] The input `mrope_section` of multimodal_rope op must be a list of 3 integers!")
|
||||
|
||||
# Contiguous check
|
||||
if not q.is_contiguous():
|
||||
warnings.warn("The input `q` of multimodal_rope op is discontiguous!")
|
||||
q = q.contiguous()
|
||||
if not k.is_contiguous():
|
||||
warnings.warn("The input `k` of multimodal_rope op is discontiguous!")
|
||||
k = k.contiguous()
|
||||
if not cos.is_contiguous():
|
||||
warnings.warn("The input `cos` of multimodal_rope op is discontiguous!")
|
||||
cos = cos.contiguous()
|
||||
if not sin.is_contiguous():
|
||||
warnings.warn("The input `sin` of multimodal_rope op is discontiguous!")
|
||||
sin = sin.contiguous()
|
||||
|
||||
# Prepare mrope_section_doubled
|
||||
mrope_section_doubled = [x * 2 for x in mrope_section]
|
||||
|
||||
# Create output tensors
|
||||
q_out = torch.empty_like(q)
|
||||
k_out = torch.empty_like(k)
|
||||
backend.rope(q, k, cos, sin, q_out, k_out, mrope_section_doubled)
|
||||
|
||||
ctx.save_for_backward(q, k, cos, sin)
|
||||
ctx.mrope_section_doubled = mrope_section_doubled
|
||||
|
||||
return q_out, k_out
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_q_out, grad_k_out):
|
||||
|
||||
if not grad_q_out.is_contiguous():
|
||||
grad_q_out = grad_q_out.contiguous()
|
||||
if not grad_k_out.is_contiguous():
|
||||
grad_k_out = grad_k_out.contiguous()
|
||||
|
||||
q, k, cos, sin = ctx.saved_tensors
|
||||
|
||||
grad_q = None
|
||||
grad_k = None
|
||||
if ctx.needs_input_grad[0]:
|
||||
grad_q = torch.empty_like(q)
|
||||
if ctx.needs_input_grad[1]:
|
||||
grad_k = torch.empty_like(k)
|
||||
|
||||
if grad_q is not None or grad_k is not None:
|
||||
backend.rope_bwd(
|
||||
grad_q_out,
|
||||
grad_k_out,
|
||||
q,
|
||||
k,
|
||||
cos,
|
||||
sin,
|
||||
grad_q if grad_q is not None else torch.empty_like(q),
|
||||
grad_k if grad_k is not None else torch.empty_like(k),
|
||||
ctx.mrope_section_doubled,
|
||||
)
|
||||
|
||||
return grad_q, grad_k, None, None, None
|
||||
|
||||
|
||||
def multimodal_rope(q, k, cos, sin, mrope_section):
|
||||
return MultimodalRoPE.apply(q, k, cos, sin, mrope_section)
|
||||
Reference in New Issue
Block a user