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
320 lines
10 KiB
Python
320 lines
10 KiB
Python
# 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.
|
|
|
|
"""Communication ops for distributed communication.
|
|
|
|
All ops require explicit group (tuple of ranks) and rank parameters.
|
|
Groups are looked up from pg_manager internally via comm_backend.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from enum import IntEnum
|
|
|
|
import torch
|
|
import torch.distributed
|
|
from tokenspeed_kernel.ops.communication.trtllm import (
|
|
allgather_dual_rmsnorm,
|
|
allreduce_residual_rmsnorm,
|
|
reducescatter_residual_rmsnorm,
|
|
)
|
|
|
|
from tokenspeed.runtime.distributed.comm_backend import (
|
|
CommBackend,
|
|
Group,
|
|
get_global_backend,
|
|
)
|
|
from tokenspeed.runtime.distributed.process_group_manager import (
|
|
process_group_manager as pg_manager,
|
|
)
|
|
from tokenspeed.runtime.utils.pdl import pdl_enabled
|
|
|
|
|
|
def _get_process_group(group: Group):
|
|
return pg_manager.get_process_group("nccl", group)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fusion parameters
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FusionOp(IntEnum):
|
|
"""What post-communication fusion to apply."""
|
|
|
|
NONE = 0
|
|
# all_reduce + residual_add + RMSNorm
|
|
RESIDUAL_RMS_NORM = 1
|
|
# reduce_scatter + residual_add + RMSNorm
|
|
RS_RESIDUAL_RMS_NORM = 2
|
|
# all_gather + dual RMSNorm (for MLA)
|
|
AG_DUAL_RMS_NORM = 3
|
|
|
|
|
|
@dataclass
|
|
class FusionParams:
|
|
"""Optional fusion context passed to fused comm_ops functions.
|
|
|
|
Not all fields are used by every ``FusionOp``. Only the relevant
|
|
subset is accessed.
|
|
"""
|
|
|
|
fusion_op: FusionOp = FusionOp.NONE
|
|
|
|
# --- For RESIDUAL_RMS_NORM / RS_RESIDUAL_RMS_NORM ---
|
|
residual: torch.Tensor | None = None
|
|
norm_weight: torch.Tensor | None = None
|
|
eps: float = 1e-6
|
|
|
|
# --- For AG_DUAL_RMS_NORM ---
|
|
norm_weight_2: torch.Tensor | None = None
|
|
eps_2: float = 1e-6
|
|
|
|
# --- For reduce-scatter fusion ---
|
|
add_in: torch.Tensor | None = None
|
|
residual_reduce_scattered: bool = False
|
|
has_partial_norm_out: bool = False
|
|
|
|
# --- Shared by RESIDUAL_RMS_NORM / RS_RESIDUAL_RMS_NORM / AG_DUAL_RMS_NORM ---
|
|
max_token_num: int = 0
|
|
|
|
# --- For FP8 block quantization ---
|
|
block_quant_fp8: bool = False
|
|
|
|
# --- General ---
|
|
total_num_tokens: int = 0
|
|
trigger_completion_at_end: bool = False
|
|
fp32_acc: bool = False
|
|
max_sm_to_use: int | None = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Basic primitives
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def all_reduce(
|
|
tensor: torch.Tensor,
|
|
group: Group,
|
|
backend: CommBackend | None = None,
|
|
op: torch.distributed.ReduceOp = torch.distributed.ReduceOp.SUM,
|
|
) -> torch.Tensor:
|
|
"""All-reduce the tensor across the given communication group."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
return backend.all_reduce(tensor, group, op=op)
|
|
|
|
|
|
def all_gather(
|
|
tensor: torch.Tensor,
|
|
group: Group,
|
|
dim: int = -1,
|
|
backend: CommBackend | None = None,
|
|
) -> torch.Tensor:
|
|
"""All-gather the tensor across the given communication group."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
return backend.all_gather(tensor, group, dim)
|
|
|
|
|
|
def all_gather_into_tensor(
|
|
output: torch.Tensor,
|
|
input: torch.Tensor,
|
|
group: Group,
|
|
backend: CommBackend | None = None,
|
|
) -> None:
|
|
"""All-gather input into a pre-allocated output buffer."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
backend.all_gather_into_tensor(output, input, group)
|
|
|
|
|
|
def reduce_scatter(
|
|
tensor: torch.Tensor,
|
|
group: Group,
|
|
backend: CommBackend | None = None,
|
|
) -> torch.Tensor:
|
|
"""Reduce-scatter the tensor across the given communication group."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
return backend.reduce_scatter(tensor, group)
|
|
|
|
|
|
def all_to_all_single(
|
|
output: torch.Tensor,
|
|
input: torch.Tensor,
|
|
group: Group,
|
|
backend: CommBackend | None = None,
|
|
) -> None:
|
|
"""Even-split all_to_all into a pre-allocated output buffer."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
backend.all_to_all_single(output, input, group)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fused ops (comm + residual + norm)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def fused_all_reduce(
|
|
tensor: torch.Tensor,
|
|
rank: int,
|
|
group: Group,
|
|
backend: CommBackend | None = None,
|
|
fusion_params: FusionParams | None = None,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
"""All-reduce with optional fused residual + RMSNorm."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
|
|
if fusion_params is None or fusion_params.fusion_op == FusionOp.NONE:
|
|
return backend.all_reduce(tensor, group)
|
|
|
|
if fusion_params.fusion_op == FusionOp.RESIDUAL_RMS_NORM:
|
|
return allreduce_residual_rmsnorm(
|
|
input_tensor=tensor,
|
|
residual=fusion_params.residual,
|
|
weight=fusion_params.norm_weight,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
eps=fusion_params.eps,
|
|
fp32_acc=fusion_params.fp32_acc,
|
|
block_quant_fp8=fusion_params.block_quant_fp8,
|
|
residual_reduce_scattered=fusion_params.residual_reduce_scattered,
|
|
has_partial_norm_out=fusion_params.has_partial_norm_out,
|
|
trigger_completion_at_end=fusion_params.trigger_completion_at_end,
|
|
max_sm_to_use=fusion_params.max_sm_to_use,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
|
|
raise ValueError(
|
|
f"Unsupported fusion_op {fusion_params.fusion_op} for fused_all_reduce"
|
|
)
|
|
|
|
|
|
def fused_reduce_scatter(
|
|
tensor: torch.Tensor,
|
|
rank: int,
|
|
group: Group,
|
|
backend: CommBackend | None = None,
|
|
fusion_params: FusionParams | None = None,
|
|
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
"""Reduce-scatter with optional fused residual + RMSNorm."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
|
|
if fusion_params is None or fusion_params.fusion_op == FusionOp.NONE:
|
|
return backend.reduce_scatter(tensor, group)
|
|
|
|
if fusion_params.fusion_op == FusionOp.RS_RESIDUAL_RMS_NORM:
|
|
return reducescatter_residual_rmsnorm(
|
|
input_tensor=tensor,
|
|
weight=fusion_params.norm_weight,
|
|
residual=fusion_params.residual,
|
|
eps=fusion_params.eps,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
add_in=fusion_params.add_in,
|
|
fp32_acc=fusion_params.fp32_acc,
|
|
block_quant_fp8=fusion_params.block_quant_fp8,
|
|
max_token_num=fusion_params.max_token_num or tensor.shape[0],
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
|
|
raise ValueError(
|
|
f"Unsupported fusion_op {fusion_params.fusion_op} for fused_reduce_scatter"
|
|
)
|
|
|
|
|
|
def fused_all_gather(
|
|
tensor: torch.Tensor,
|
|
rank: int,
|
|
group: Group,
|
|
dim: int = -1,
|
|
backend: CommBackend | None = None,
|
|
fusion_params: FusionParams | None = None,
|
|
) -> torch.Tensor | tuple[torch.Tensor, ...]:
|
|
"""All-gather with optional fused dual-RMSNorm."""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
|
|
if fusion_params is None or fusion_params.fusion_op == FusionOp.NONE:
|
|
return backend.all_gather(tensor, group, dim)
|
|
|
|
if fusion_params.fusion_op == FusionOp.AG_DUAL_RMS_NORM:
|
|
return allgather_dual_rmsnorm(
|
|
qkv=tensor,
|
|
weight_q_a=fusion_params.norm_weight,
|
|
eps_q=fusion_params.eps,
|
|
weight_kv_a=fusion_params.norm_weight_2,
|
|
eps_kv=fusion_params.eps_2,
|
|
rank=rank,
|
|
group=_get_process_group(group),
|
|
total_num_tokens=fusion_params.total_num_tokens,
|
|
max_token_num=fusion_params.max_token_num
|
|
or max(tensor.shape[0], fusion_params.total_num_tokens),
|
|
fp32_acc=fusion_params.fp32_acc,
|
|
block_quant_fp8=fusion_params.block_quant_fp8,
|
|
launch_with_pdl=pdl_enabled(),
|
|
)
|
|
|
|
raise ValueError(
|
|
f"Unsupported fusion_op {fusion_params.fusion_op} for fused_all_gather"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Token-aware ops (uneven token distribution via TritonRSAG)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def token_all_gather(
|
|
tensor: torch.Tensor,
|
|
group: Group,
|
|
scattered_num_tokens: list[int],
|
|
backend=None,
|
|
) -> torch.Tensor:
|
|
"""All-gather with token-aware distribution (TritonRSAG).
|
|
|
|
Args:
|
|
scattered_num_tokens: Number of tokens on each rank in the group,
|
|
e.g. [50, 50, 51, 49] for 4 ranks with 200 total tokens.
|
|
"""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
return backend.token_all_gather(tensor, group, scattered_num_tokens)
|
|
|
|
|
|
def token_reduce_scatter(
|
|
tensor: torch.Tensor,
|
|
group: Group,
|
|
scattered_num_tokens: list[int],
|
|
backend=None,
|
|
) -> torch.Tensor:
|
|
"""Reduce-scatter with token-aware distribution (TritonRSAG).
|
|
|
|
Args:
|
|
scattered_num_tokens: Number of tokens on each rank in the group,
|
|
e.g. [50, 50, 51, 49] for 4 ranks with 200 total tokens.
|
|
"""
|
|
if backend is None:
|
|
backend = get_global_backend()
|
|
return backend.token_reduce_scatter(tensor, group, scattered_num_tokens)
|