59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
531 lines
19 KiB
Python
Executable File
531 lines
19 KiB
Python
Executable File
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
"""Fused operators for normalization layers."""
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
from tokenspeed_kernel.ops.communication.triton import (
|
|
allreduce_residual_rmsnorm as triton_allreduce_residual_rmsnorm,
|
|
)
|
|
from tokenspeed_kernel.ops.communication.trtllm import (
|
|
allgather_dual_rmsnorm,
|
|
)
|
|
from tokenspeed_kernel.ops.communication.trtllm import (
|
|
allreduce_residual_rmsnorm as trtllm_allreduce_residual_rmsnorm,
|
|
)
|
|
from tokenspeed_kernel.ops.communication.trtllm import (
|
|
reducescatter_residual_rmsnorm,
|
|
)
|
|
from tokenspeed_kernel.platform import current_platform
|
|
|
|
from tokenspeed.runtime.distributed.process_group_manager import (
|
|
process_group_manager as pg_manager,
|
|
)
|
|
from tokenspeed.runtime.utils import (
|
|
get_colorful_logger,
|
|
)
|
|
from tokenspeed.runtime.utils.env import global_server_args_dict
|
|
from tokenspeed.runtime.utils.pdl import pdl_enabled
|
|
|
|
_is_amd = current_platform().is_amd
|
|
|
|
if _is_amd:
|
|
from tokenspeed_kernel.ops.layernorm.triton import rmsnorm as triton_rmsnorm
|
|
from tokenspeed_kernel.ops.layernorm.triton import (
|
|
rmsnorm_fused_parallel as triton_rmsnorm_fused_parallel,
|
|
)
|
|
else:
|
|
from tokenspeed_kernel.ops.layernorm.cuda import rmsnorm_fused_parallel
|
|
from tokenspeed_kernel.ops.layernorm.flashinfer import (
|
|
fused_add_rmsnorm,
|
|
gemma_fused_add_rmsnorm,
|
|
gemma_rmsnorm,
|
|
layernorm,
|
|
rmsnorm,
|
|
)
|
|
|
|
|
|
logger = get_colorful_logger(__name__)
|
|
|
|
|
|
def _get_process_group(group: tuple[int, ...]):
|
|
return pg_manager.get_process_group("nccl", group)
|
|
|
|
|
|
class LayerNorm(nn.Module):
|
|
def __init__(self, hidden_size: int, eps: float = 1e-6) -> None:
|
|
super().__init__()
|
|
self.weight = nn.Parameter(torch.ones(hidden_size, dtype=torch.float32))
|
|
self.bias = nn.Parameter(torch.zeros(hidden_size, dtype=torch.float32))
|
|
self.variance_epsilon = eps
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
# There might be no tokens here (e.g. idle/padded graph rows).
|
|
if x.shape[0] == 0:
|
|
return x
|
|
if current_platform().is_nvidia:
|
|
return layernorm(x, self.weight, self.bias, self.variance_epsilon)
|
|
return nn.functional.layer_norm(
|
|
x.float(),
|
|
(x.shape[-1],),
|
|
self.weight,
|
|
self.bias,
|
|
self.variance_epsilon,
|
|
).to(x.dtype)
|
|
|
|
|
|
class RMSNorm(torch.nn.Module):
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
eps: float = 1e-6,
|
|
) -> None:
|
|
super().__init__()
|
|
self.weight = nn.Parameter(torch.ones(hidden_size))
|
|
self.variance_epsilon = eps
|
|
|
|
def forward(
|
|
self,
|
|
x: torch.Tensor,
|
|
residual: torch.Tensor | None = None,
|
|
inplace: bool = False,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
# There might be no tokens here
|
|
if x.shape[0] == 0:
|
|
if residual is not None:
|
|
return x, residual
|
|
else:
|
|
return x
|
|
|
|
if _is_amd:
|
|
if residual is not None:
|
|
if inplace:
|
|
raise ValueError(
|
|
"fused add rmsnorm does not support inplace operation"
|
|
)
|
|
return triton_rmsnorm(
|
|
x,
|
|
self.weight.data,
|
|
self.variance_epsilon,
|
|
residual=residual,
|
|
)
|
|
return triton_rmsnorm(
|
|
x,
|
|
self.weight.data,
|
|
self.variance_epsilon,
|
|
out=x if inplace else None,
|
|
)
|
|
else:
|
|
if residual is not None:
|
|
if inplace:
|
|
raise ValueError(
|
|
"fused_add_rmsnorm does not support inplace operation"
|
|
)
|
|
fused_add_rmsnorm(
|
|
x,
|
|
residual,
|
|
self.weight.data,
|
|
self.variance_epsilon,
|
|
enable_pdl=pdl_enabled(),
|
|
)
|
|
return x, residual
|
|
out = rmsnorm(
|
|
x,
|
|
self.weight.data,
|
|
self.variance_epsilon,
|
|
out=x if inplace else None,
|
|
enable_pdl=pdl_enabled(),
|
|
)
|
|
return out
|
|
|
|
def forward_with_allreduce_fusion(
|
|
self,
|
|
rank: int,
|
|
group: tuple[int, ...],
|
|
x: torch.Tensor,
|
|
residual: torch.Tensor | None = None,
|
|
fuse_block_quant_fp8: bool = False,
|
|
residual_reduce_scattered: bool = False,
|
|
max_sm_to_use: int | None = None,
|
|
trigger_completion_at_end: bool = False,
|
|
has_partial_norm_out: bool = False,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
"""
|
|
Forward method with allreduce fusion, prioritizing flashinfer fused operations
|
|
"""
|
|
|
|
if residual is not None:
|
|
|
|
if len(group) > 1:
|
|
if _is_amd:
|
|
allreduce_residual_rmsnorm = triton_allreduce_residual_rmsnorm
|
|
else:
|
|
if not current_platform().is_nvidia:
|
|
raise RuntimeError("Allreduce RMSNorm requires NVIDIA or AMD.")
|
|
allreduce_residual_rmsnorm = trtllm_allreduce_residual_rmsnorm
|
|
fused_result = allreduce_residual_rmsnorm(
|
|
input_tensor=x,
|
|
residual=residual,
|
|
weight=self.weight,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
eps=self.variance_epsilon,
|
|
max_token_num=global_server_args_dict["comm_fusion_max_num_tokens"],
|
|
block_quant_fp8=fuse_block_quant_fp8,
|
|
residual_reduce_scattered=residual_reduce_scattered,
|
|
max_sm_to_use=max_sm_to_use,
|
|
trigger_completion_at_end=trigger_completion_at_end,
|
|
has_partial_norm_out=has_partial_norm_out,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
if fused_result[0] is not None:
|
|
return fused_result
|
|
|
|
result = self.forward(x, residual)
|
|
if isinstance(result, tuple):
|
|
return result[0], result[1], None
|
|
return result, None, None
|
|
|
|
def forward_with_reducescatter_fusion(
|
|
self,
|
|
rank: int,
|
|
group: tuple[int, ...],
|
|
x: torch.Tensor,
|
|
residual: torch.Tensor | None = None,
|
|
fuse_block_quant_fp8: bool = False,
|
|
add_in: torch.Tensor | None = None,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
"""
|
|
Forward method with reducescatter fusion, prioritizing flashinfer fused operations
|
|
"""
|
|
|
|
if residual is not None:
|
|
|
|
if len(group) > 1:
|
|
fused_result = reducescatter_residual_rmsnorm(
|
|
input_tensor=x,
|
|
residual=residual,
|
|
weight=self.weight,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
eps=self.variance_epsilon,
|
|
max_token_num=global_server_args_dict["comm_fusion_max_num_tokens"],
|
|
use_oneshot=True,
|
|
block_quant_fp8=fuse_block_quant_fp8,
|
|
add_in=add_in,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
if fused_result[0] is not None:
|
|
return fused_result
|
|
|
|
result = self.forward(x, residual)
|
|
if isinstance(result, tuple):
|
|
return result[0], result[1], None
|
|
return result, None, None
|
|
|
|
|
|
class GemmaRMSNorm(torch.nn.Module):
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
eps: float = 1e-6,
|
|
) -> None:
|
|
super().__init__()
|
|
self.weight = nn.Parameter(torch.zeros(hidden_size))
|
|
self.variance_epsilon = eps
|
|
self.register_buffer("gemma_weight", self.weight.data + 1.0, persistent=False)
|
|
# (Chen-0210) Gemma weight = standard_weight + 1. Precompute once.
|
|
self.weight.weight_loader = self._weight_loader
|
|
|
|
def _weight_loader(self, param: torch.Tensor, loaded_weight: torch.Tensor) -> None:
|
|
if param.size() != loaded_weight.size():
|
|
raise ValueError(
|
|
f"Shape mismatch: {param.size()} != {loaded_weight.size()}."
|
|
)
|
|
param.data.copy_(loaded_weight)
|
|
self.gemma_weight = param.data + 1.0
|
|
|
|
def forward(
|
|
self,
|
|
x: torch.Tensor,
|
|
residual: torch.Tensor | None = None,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
if x.shape[0] == 0:
|
|
if residual is not None:
|
|
return x, residual
|
|
else:
|
|
return x
|
|
|
|
if _is_amd:
|
|
if x.shape[0] == 0:
|
|
if residual is not None:
|
|
return x, residual
|
|
else:
|
|
return x
|
|
orig_dtype = x.dtype
|
|
if residual is not None:
|
|
x = x + residual
|
|
residual = x
|
|
|
|
x = x.float()
|
|
variance = x.pow(2).mean(dim=-1, keepdim=True)
|
|
x = x * torch.rsqrt(variance + self.variance_epsilon)
|
|
x = x * (1.0 + self.weight.float())
|
|
x = x.to(orig_dtype)
|
|
return x if residual is None else (x, residual)
|
|
else:
|
|
if residual is not None:
|
|
gemma_fused_add_rmsnorm(
|
|
x,
|
|
residual,
|
|
self.weight.data,
|
|
self.variance_epsilon,
|
|
enable_pdl=pdl_enabled(),
|
|
)
|
|
return x, residual
|
|
out = gemma_rmsnorm(
|
|
x,
|
|
self.weight.data,
|
|
self.variance_epsilon,
|
|
enable_pdl=pdl_enabled(),
|
|
)
|
|
return out
|
|
|
|
def forward_with_allreduce_fusion(
|
|
self,
|
|
rank: int,
|
|
group: tuple[int, ...],
|
|
x: torch.Tensor,
|
|
residual: torch.Tensor | None = None,
|
|
fuse_block_quant_fp8: bool = False,
|
|
residual_reduce_scattered: bool = False,
|
|
max_sm_to_use: int | None = None,
|
|
trigger_completion_at_end: bool = False,
|
|
has_partial_norm_out: bool = False,
|
|
) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
|
|
"""
|
|
Forward method with allreduce fusion for GemmaRMSNorm.
|
|
Uses gemma_weight (= weight + 1.0) as gamma so that the standard
|
|
fused kernel computes x * (1 + weight) matching GemmaRMSNorm semantics.
|
|
"""
|
|
|
|
if residual is not None:
|
|
|
|
if len(group) > 1:
|
|
if _is_amd:
|
|
allreduce_residual_rmsnorm = triton_allreduce_residual_rmsnorm
|
|
else:
|
|
if not current_platform().is_nvidia:
|
|
raise RuntimeError("Allreduce RMSNorm requires NVIDIA or AMD.")
|
|
allreduce_residual_rmsnorm = trtllm_allreduce_residual_rmsnorm
|
|
fused_result = allreduce_residual_rmsnorm(
|
|
input_tensor=x,
|
|
residual=residual,
|
|
weight=self.gemma_weight,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
eps=self.variance_epsilon,
|
|
max_token_num=global_server_args_dict["comm_fusion_max_num_tokens"],
|
|
block_quant_fp8=fuse_block_quant_fp8,
|
|
residual_reduce_scattered=residual_reduce_scattered,
|
|
max_sm_to_use=max_sm_to_use,
|
|
trigger_completion_at_end=trigger_completion_at_end,
|
|
has_partial_norm_out=has_partial_norm_out,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
if fused_result[0] is not None:
|
|
return fused_result
|
|
|
|
result = self.forward(x, residual)
|
|
if isinstance(result, tuple):
|
|
return result[0], result[1], None
|
|
return result, None, None
|
|
|
|
def forward_with_reducescatter_fusion(
|
|
self,
|
|
rank: int,
|
|
group: tuple[int, ...],
|
|
x: torch.Tensor,
|
|
residual: torch.Tensor | None = None,
|
|
fuse_block_quant_fp8: bool = False,
|
|
add_in: torch.Tensor | None = None,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
"""
|
|
Forward method with reducescatter fusion for GemmaRMSNorm.
|
|
Uses gemma_weight (= weight + 1.0) as gamma so that the standard
|
|
fused kernel computes x * (1 + weight) matching GemmaRMSNorm semantics.
|
|
"""
|
|
|
|
if residual is not None:
|
|
|
|
if len(group) > 1:
|
|
fused_result = reducescatter_residual_rmsnorm(
|
|
input_tensor=x,
|
|
residual=residual,
|
|
weight=self.gemma_weight,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
eps=self.variance_epsilon,
|
|
max_token_num=global_server_args_dict["comm_fusion_max_num_tokens"],
|
|
use_oneshot=True,
|
|
block_quant_fp8=fuse_block_quant_fp8,
|
|
add_in=add_in,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
if fused_result[0] is not None:
|
|
return fused_result
|
|
|
|
result = self.forward(x, residual)
|
|
if isinstance(result, tuple):
|
|
return result[0], result[1], None
|
|
return result, None, None
|
|
|
|
|
|
class FusedRMSNorm(nn.Module):
|
|
"""Fused RMSNorm layer for normalizing two tensors simultaneously.
|
|
|
|
This layer wraps two independent RMSNorm layers (q_a and kv_a) and performs
|
|
fused normalization during forward pass. The RMSNorm layers are passed in as
|
|
parameters, allowing reuse of existing normalization layers.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
q_a_norm: RMSNorm,
|
|
kv_a_norm: RMSNorm,
|
|
) -> None:
|
|
super().__init__()
|
|
self.q_a_norm = q_a_norm
|
|
self.kv_a_norm = kv_a_norm
|
|
|
|
@property
|
|
def weight_q_a(self) -> nn.Parameter:
|
|
"""Expose weight_q_a from q_a_norm for backward compatibility."""
|
|
return self.q_a_norm.weight
|
|
|
|
@property
|
|
def weight_kv_a(self) -> nn.Parameter:
|
|
"""Expose weight_kv_a from kv_a_norm for backward compatibility."""
|
|
return self.kv_a_norm.weight
|
|
|
|
def forward(
|
|
self,
|
|
input_q_a: torch.Tensor,
|
|
input_kv_a: torch.Tensor,
|
|
output_q_a: torch.Tensor | None = None,
|
|
output_kv_a: torch.Tensor | None = None,
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""
|
|
Normalize two tensors in parallel using fused computation.
|
|
|
|
Args:
|
|
input_q_a: Q tensor to normalize
|
|
input_kv_a: KV tensor to normalize
|
|
|
|
Returns:
|
|
Tuple of (normalized_q_a, normalized_kv_a)
|
|
"""
|
|
if _is_amd:
|
|
triton_rmsnorm_fused_parallel(
|
|
input1=input_q_a,
|
|
weight1=self.weight_q_a,
|
|
output1=output_q_a if output_q_a is not None else input_q_a,
|
|
input2=input_kv_a,
|
|
weight2=self.weight_kv_a,
|
|
output2=output_kv_a if output_kv_a is not None else input_kv_a,
|
|
eps=self.q_a_norm.variance_epsilon,
|
|
enable_pdl=pdl_enabled(),
|
|
)
|
|
else:
|
|
rmsnorm_fused_parallel(
|
|
input1=input_q_a,
|
|
weight1=self.weight_q_a,
|
|
output1=output_q_a if output_q_a is not None else input_q_a,
|
|
input2=input_kv_a,
|
|
weight2=self.weight_kv_a,
|
|
output2=output_kv_a if output_kv_a is not None else input_kv_a,
|
|
eps=self.q_a_norm.variance_epsilon,
|
|
enable_pdl=pdl_enabled(),
|
|
)
|
|
return input_q_a, input_kv_a
|
|
|
|
def forward_with_allgather_fusion(
|
|
self,
|
|
rank: int,
|
|
group: tuple[int, ...],
|
|
qkv: torch.Tensor,
|
|
total_num_tokens: int,
|
|
fuse_block_quant_fp8: bool = False,
|
|
trigger_completion_at_end: bool = False,
|
|
) -> tuple[
|
|
torch.Tensor,
|
|
torch.Tensor | None,
|
|
torch.Tensor | None,
|
|
torch.Tensor | None,
|
|
]:
|
|
"""
|
|
Forward method with allgather fusion, performing allgather + dual RMSNorm + optional FP8 block quantization.
|
|
|
|
This method fuses allgather communication with dual RMSNorm computation
|
|
and optional FP8 block-wise quantization in a single kernel launch.
|
|
|
|
Args:
|
|
qkv: Input tensor to allgather, shape [num_token_current_rank, q_lora_rank + kv_lora_rank + qk_rope_head_dim]
|
|
fuse_block_quant_fp8: Whether to perform FP8 block-wise quantization on the first norm output
|
|
trigger_completion_at_end: Whether to trigger completion event at the end of kernel
|
|
|
|
Returns:
|
|
Tuple of (allgather_out, quant_out, k_nope, block_scale):
|
|
- allgather_out: Gathered tensor, shape [num_token_all_group, hidden_dim]
|
|
- quant_out: FP8 quantized first norm output (q_contiguous), None if fuse_block_quant_fp8=False
|
|
- k_nope: Second norm output
|
|
- block_scale: Quantization scales, None if fuse_block_quant_fp8=False
|
|
"""
|
|
|
|
if len(group) > 1:
|
|
fused_result = allgather_dual_rmsnorm(
|
|
qkv=qkv,
|
|
total_num_tokens=total_num_tokens,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
weight_q_a=self.weight_q_a,
|
|
weight_kv_a=self.weight_kv_a,
|
|
eps_q=self.q_a_norm.variance_epsilon,
|
|
eps_kv=self.kv_a_norm.variance_epsilon,
|
|
max_token_num=global_server_args_dict["comm_fusion_max_num_tokens"],
|
|
block_quant_fp8=fuse_block_quant_fp8,
|
|
trigger_completion_at_end=trigger_completion_at_end,
|
|
fp32_acc=False,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
if fused_result[0] is not None:
|
|
return fused_result
|
|
|
|
q_lora_rank = self.weight_q_a.shape[0]
|
|
kv_lora_rank = self.weight_kv_a.shape[0]
|
|
q = qkv[..., :q_lora_rank]
|
|
k_nope = qkv[..., q_lora_rank : q_lora_rank + kv_lora_rank]
|
|
q_contiguous = torch.empty_like(q)
|
|
if q.shape[0] > 0:
|
|
self.forward(input_q_a=q, input_kv_a=k_nope, output_q_a=q_contiguous)
|
|
|
|
return qkv, q_contiguous, k_nope, None
|