项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

867 行
28 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 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 fusion kernels (AOT-compiled).
Drop-in replacement for `flashinfer.comm` used by TokenSpeed.
Loads the pre-compiled trtllm_comm.so via tvm_ffi instead of JIT.
Usage:
import tokenspeed_kernel.comm as comm
# Then use comm.trtllm_allreduce_fusion(...), comm.AllReduceFusionPattern, etc.
"""
import functools
import logging
from ctypes import c_void_p, cast
from pathlib import Path
from typing import List, Optional, Tuple, Union
import torch
import torch.distributed as dist
from tokenspeed_kernel.thirdparty.cuda.cuda_ipc import (
create_shared_buffer,
cudart,
free_shared_buffer,
)
from torch.distributed import ProcessGroup
# ---------------------------------------------------------------------------
# Utility
# ---------------------------------------------------------------------------
def _round_up(x: int, y: int) -> int:
return ((x + y - 1) // y) * y
BarrierFlagCount = 256
MAX_COMM_SIZE = 2147483647 & ~((1 << 21) - 1) # MAX_INT32 rounded down to 2MB
# ---------------------------------------------------------------------------
# AOT module loader (replaces JIT gen_trtllm_comm_module().build_and_load())
# ---------------------------------------------------------------------------
@functools.cache
def _load_trtllm_comm_module():
import tvm_ffi
so_path = (
Path(__file__).resolve().parent / "objs" / "trtllm_comm" / "trtllm_comm.so"
)
if not so_path.exists():
raise RuntimeError(
f"trtllm_comm.so not found at {so_path}. "
"Run `python tokenspeed_kernel/setup.py build_ext` to compile."
)
return tvm_ffi.load_module(str(so_path))
# ---------------------------------------------------------------------------
# Pattern enums (pure Python, identical to flashinfer)
# ---------------------------------------------------------------------------
class AllReduceStrategyType:
NCCL = 0
MIN_LATENCY = 1
UB = 2
AUTO = 3
ONESHOT = 4
TWOSHOT = 5
LOWPRECISION = 6
class AllReduceStrategyConfig:
USE_MEMCPY = 1 << 0
PUSH_MODE = 1 << 1
class AllReduceFusionOp:
NONE = 0
RESIDUAL_RMS_NORM = 1
LAST_PROCESS_FOR_UB = 2
RESIDUAL_RMS_PREPOST_NORM = 3
RESIDUAL_RMS_NORM_QUANT_FP8 = 4
RESIDUAL_RMS_NORM_QUANT_NVFP4 = 5
RESIDUAL_RMS_NORM_OUT_QUANT_FP8 = 6
RESIDUAL_RMS_NORM_OUT_QUANT_NVFP4 = 7
MOE_ALLREDUCE_RESIDUAL_RMS_NORM = 8
MOE_FINALIZE_ALLREDUCE_RESIDUAL_RMS_NORM = 9
class AllReduceFusionPattern:
kAllReduce = 0
kARResidualRMSNorm = 1
kARResidualRMSNormFP8Quant = 2
kARResidualRMSNormFP4Quant = 3
kARResidualRMSNormOutFP8Quant = 4
kARResidualRMSNormOutFP4Quant = 5
kARResidualRMSNormFP8BlockWiseQuant = 6
kARResidualRMSNormPartialOutFP8BlockWiseQuant = 7
kARResidualRMSNormPartialOut = 8
class AllGatherFusionPattern:
kAllGather = 0
kAllGatherfusedRMS = 1
kAllGatherfusedRMSFP8BlockWiseQuant = 2
class ReduceScatterFusionPattern:
kReduceScatter = 0
kRSResidualRMSNorm = 1
kRSResidualRMSNormFP8Quant = 2
kRSResidualRMSNormFP4Quant = 3
kRSResidualRMSNormOutFP8Quant = 4
kRSResidualRMSNormOutFP4Quant = 5
kRSResidualRMSNormFP8BlockWiseQuant = 6
kRSAddResidualRMSNormFP8BlockWiseQuant = 7
kRSAddResidualRMSNorm = 8
class QuantizationSFLayout:
SWIZZLED_128x4 = 0
SWIZZLED_8x4 = 1
LINEAR = 2
# ---------------------------------------------------------------------------
# Lamport initialization
# ---------------------------------------------------------------------------
def trtllm_lamport_initialize(buffer_ptr: int, size: int, dtype: torch.dtype) -> None:
_load_trtllm_comm_module().trtllm_lamport_initialize(buffer_ptr, size, dtype)
def trtllm_lamport_initialize_all(
buffer_0_ptr: int,
buffer_1_ptr: int,
buffer_2_ptr: int,
size: int,
dtype: torch.dtype,
) -> None:
_load_trtllm_comm_module().trtllm_lamport_initialize_all(
buffer_0_ptr, buffer_1_ptr, buffer_2_ptr, size, dtype
)
# ---------------------------------------------------------------------------
# IPC workspace helpers (shared pattern for allreduce/allgather/reducescatter)
# ---------------------------------------------------------------------------
def _create_ipc_workspace(
tp_rank: int,
tp_size: int,
buffer_size: int,
flag_size: int,
lamport_comm_size: int,
use_fp32_lamport: bool,
group: Optional[ProcessGroup],
) -> Tuple[List[List[int]], torch.Tensor]:
"""Common IPC workspace creation logic."""
if lamport_comm_size > MAX_COMM_SIZE:
logging.warning(
f"lamport_comm_size {lamport_comm_size} > MAX_COMM_SIZE {MAX_COMM_SIZE}, clamping"
)
lamport_comm_size = MAX_COMM_SIZE
lamport_buffer_size = lamport_comm_size * 3
ipc_handles: List[List[int]] = []
for size in [buffer_size, flag_size, lamport_buffer_size]:
aligned_size = _round_up(size, 1 << 21)
ipc_handles.append(create_shared_buffer(aligned_size, group))
# Initialize lamport buffer
aligned_lamport_buffer_size = _round_up(lamport_buffer_size, 1 << 21)
if use_fp32_lamport:
trtllm_lamport_initialize(
ipc_handles[2][tp_rank], aligned_lamport_buffer_size // 4, torch.float32
)
else:
trtllm_lamport_initialize(
ipc_handles[2][tp_rank], aligned_lamport_buffer_size // 2, torch.float16
)
# Build workspace pointer list
workspace = []
for ipc_handle in ipc_handles:
for rank in range(tp_size):
workspace.append(ipc_handle[rank])
# Allocate and initialize flags: [0, 0, 0, lamport_comm_size, 0]
flag_ptr = cudart.cudaMalloc(5 * 4)
cudart.cudaMemset(flag_ptr, 0, 5 * 4)
lamport_comm_size_bytes = lamport_comm_size.to_bytes(4, byteorder="little")
cudart.cudaMemcpy(
c_void_p(flag_ptr.value + 3 * 4), cast(lamport_comm_size_bytes, c_void_p), 4
)
workspace.append(flag_ptr.value)
workspace_tensor = torch.tensor(
workspace, dtype=torch.int64, device=torch.device("cuda")
)
dist.barrier(group=group)
return ipc_handles, workspace_tensor
def _destroy_ipc_workspace(
workspace: List[List[int]], group: Optional[ProcessGroup] = None
) -> None:
for ipc_handle in workspace:
free_shared_buffer(ipc_handle, group)
# ---------------------------------------------------------------------------
# AllReduce fusion
# ---------------------------------------------------------------------------
_ar_oneshot_heuristics: dict = {2: 512, 4: 64, 8: 42}
def _ar_should_use_oneshot(
token_num: int, hidden_dim: int, dtype: torch.dtype, world_size: int
) -> bool:
comm_size_mb = (
token_num * hidden_dim * 2 * world_size * dtype.itemsize / 1024 / 1024
)
return comm_size_mb <= _ar_oneshot_heuristics.get(world_size, 0)
def trtllm_create_ipc_workspace_for_all_reduce_fusion(
tp_rank: int,
tp_size: int,
max_token_num: int,
hidden_dim,
use_fp32_lamport: bool = False,
group: Optional[ProcessGroup] = None,
create_metadata: bool = False,
) -> Union[
Tuple[List[List[int]], torch.Tensor],
Tuple[List[List[int]], torch.Tensor, dict],
]:
buffer_size = tp_size * max_token_num * hidden_dim * 2
flag_size = tp_size * BarrierFlagCount * 4
lamport_comm_size = (
tp_size * max_token_num * hidden_dim * 2
if not use_fp32_lamport
else tp_size * max_token_num * hidden_dim * 4
)
ipc_handles, workspace_tensor = _create_ipc_workspace(
tp_rank,
tp_size,
buffer_size,
flag_size,
lamport_comm_size,
use_fp32_lamport,
group,
)
if create_metadata:
metadata = {
"tp_rank": tp_rank,
"tp_size": tp_size,
"max_token_num": max_token_num,
"hidden_dim": hidden_dim,
"use_fp32_lamport": use_fp32_lamport,
"buffer_size": buffer_size,
"flag_size": flag_size,
"lamport_comm_size": min(lamport_comm_size, MAX_COMM_SIZE),
}
return ipc_handles, workspace_tensor, metadata
return ipc_handles, workspace_tensor
def trtllm_destroy_ipc_workspace_for_all_reduce_fusion(
workspace: List[List[int]], group: Optional[ProcessGroup] = None
) -> None:
_destroy_ipc_workspace(workspace, group)
def trtllm_allreduce_fusion(
allreduce_in: torch.Tensor,
world_size: int,
world_rank: int,
token_num: int,
hidden_dim: int,
workspace_ptrs: torch.Tensor,
launch_with_pdl: bool,
trigger_completion_at_end: bool,
fp32_acc: bool,
pattern_code: int,
use_oneshot: Optional[bool] = None,
allreduce_out: Optional[torch.Tensor] = None,
residual_in: Optional[torch.Tensor] = None,
residual_out: Optional[torch.Tensor] = None,
norm_out: Optional[torch.Tensor] = None,
partial_norm_out: Optional[torch.Tensor] = None,
quant_out: Optional[torch.Tensor] = None,
scale_out: Optional[torch.Tensor] = None,
rms_gamma: Optional[torch.Tensor] = None,
rms_eps: Optional[float] = None,
scale_factor: Optional[Union[torch.Tensor, float]] = None,
layout_code: Optional[int] = None,
metadata: Optional[dict] = None,
residual_reduce_scattered: bool = False,
max_sm_to_use: Optional[int] = None,
) -> None:
if use_oneshot is None:
use_oneshot = _ar_should_use_oneshot(
token_num, hidden_dim, allreduce_in.dtype, world_size
)
if not use_oneshot:
assert not residual_reduce_scattered, "Currently not supported!"
assert token_num > world_size, "sequence length should be larger than tp_size"
required_lamport_comm_size = (
token_num * hidden_dim * 2 * world_size
if allreduce_in.dtype != torch.float32
else token_num * hidden_dim * 4 * world_size
)
if required_lamport_comm_size > MAX_COMM_SIZE and use_oneshot:
logging.warning(
f"required_lamport_comm_size {required_lamport_comm_size} > MAX_COMM_SIZE. Falling back to twoshot."
)
use_oneshot = False
if scale_factor is not None:
if isinstance(scale_factor, torch.Tensor):
scale_factor = scale_factor.to(torch.float32)
else:
scale_factor = torch.tensor(
[scale_factor], dtype=torch.float32, device=allreduce_in.device
)
_load_trtllm_comm_module().trtllm_allreduce_fusion(
allreduce_in,
world_size,
world_rank,
token_num,
hidden_dim,
workspace_ptrs,
launch_with_pdl,
use_oneshot,
trigger_completion_at_end,
fp32_acc,
residual_reduce_scattered,
pattern_code,
allreduce_out,
residual_in,
residual_out,
norm_out,
partial_norm_out,
quant_out,
scale_out,
rms_gamma,
rms_eps,
scale_factor,
layout_code,
max_sm_to_use,
)
# ---------------------------------------------------------------------------
# AllGather fusion
# ---------------------------------------------------------------------------
_ag_oneshot_heuristics: dict = {2: 256, 4: 128, 8: 64, 16: 32}
def _ag_should_use_oneshot(
token_num: int, hidden_dim: int, dtype: torch.dtype, world_size: int
) -> bool:
comm_size_mb = (
token_num * hidden_dim * 2 * world_size * dtype.itemsize / 1024 / 1024
)
return comm_size_mb <= _ag_oneshot_heuristics.get(world_size, 0)
def trtllm_create_ipc_workspace_for_allgather_fusion(
tp_rank: int,
tp_size: int,
max_token_num: int,
hidden_dim,
use_fp32_lamport: bool = False,
group: Optional[ProcessGroup] = None,
create_metadata: bool = False,
) -> Union[
Tuple[List[List[int]], torch.Tensor],
Tuple[List[List[int]], torch.Tensor, dict],
]:
# AllGather: buffer_size is NOT multiplied by tp_size
buffer_size = max_token_num * hidden_dim * 2
flag_size = tp_size * BarrierFlagCount * 4
lamport_comm_size = (
max_token_num * hidden_dim * 2
if not use_fp32_lamport
else max_token_num * hidden_dim * 4
)
ipc_handles, workspace_tensor = _create_ipc_workspace(
tp_rank,
tp_size,
buffer_size,
flag_size,
lamport_comm_size,
use_fp32_lamport,
group,
)
if create_metadata:
metadata = {
"tp_rank": tp_rank,
"tp_size": tp_size,
"max_token_num": max_token_num,
"hidden_dim": hidden_dim,
"use_fp32_lamport": use_fp32_lamport,
"buffer_size": buffer_size,
"flag_size": flag_size,
"lamport_comm_size": min(lamport_comm_size, MAX_COMM_SIZE),
}
return ipc_handles, workspace_tensor, metadata
return ipc_handles, workspace_tensor
def trtllm_destroy_ipc_workspace_for_allgather_fusion(
workspace: List[List[int]], group: Optional[ProcessGroup] = None
) -> None:
_destroy_ipc_workspace(workspace, group)
def trtllm_allgather_fusion(
allgather_in: torch.Tensor,
world_size: int,
world_rank: int,
hidden_dim: int,
workspace_ptrs: torch.Tensor,
launch_with_pdl: bool,
trigger_completion_at_end: bool,
num_token_current_rank: int,
allgather_out: torch.Tensor,
num_token_all_group: int,
pattern_code: int = AllGatherFusionPattern.kAllGather,
use_oneshot: Optional[bool] = None,
fp32_acc: bool = False,
x_norm_out: Optional[torch.Tensor] = None,
y_norm_out: Optional[torch.Tensor] = None,
quant_out: Optional[torch.Tensor] = None,
scale_out: Optional[torch.Tensor] = None,
x_rms_gamma: Optional[torch.Tensor] = None,
y_rms_gamma: Optional[torch.Tensor] = None,
x_rms_eps: Optional[float] = 1e-6,
y_rms_eps: Optional[float] = 1e-6,
q_lora_rank: int = 0,
kv_lora_rank: int = 0,
qk_rope_head_dim: int = 0,
) -> None:
assert (
q_lora_rank % 128 == 0
), f"q_lora_rank ({q_lora_rank}) must be divisible by block_size (128)"
assert hidden_dim <= 2112, f"hidden_dim ({hidden_dim}) must be <= 2112"
total_rank = q_lora_rank + kv_lora_rank + qk_rope_head_dim
assert total_rank == hidden_dim, (
f"q_lora_rank + kv_lora_rank + qk_rope_head_dim must equal hidden_dim, "
f"got {total_rank} != {hidden_dim}"
)
if use_oneshot is None:
use_oneshot = _ag_should_use_oneshot(
num_token_all_group, hidden_dim, allgather_in.dtype, world_size
)
required_lamport_comm_size = (
num_token_all_group * hidden_dim * 2
if allgather_in.dtype != torch.float32
else num_token_all_group * hidden_dim * 4
)
if required_lamport_comm_size > MAX_COMM_SIZE and use_oneshot:
logging.warning(
f"required_lamport_comm_size {required_lamport_comm_size} > MAX_COMM_SIZE. Falling back."
)
use_oneshot = False
_load_trtllm_comm_module().trtllm_allgather_fusion(
allgather_in,
world_size,
world_rank,
hidden_dim,
workspace_ptrs,
launch_with_pdl,
use_oneshot,
trigger_completion_at_end,
fp32_acc,
pattern_code,
num_token_current_rank,
num_token_all_group,
allgather_out,
x_norm_out,
y_norm_out,
quant_out,
scale_out,
x_rms_gamma,
y_rms_gamma,
x_rms_eps,
y_rms_eps,
q_lora_rank,
kv_lora_rank,
qk_rope_head_dim,
)
# ---------------------------------------------------------------------------
# ReduceScatter fusion
# ---------------------------------------------------------------------------
_rs_oneshot_heuristics: dict = {2: 256, 4: 128, 8: 64, 16: 32}
def _rs_should_use_oneshot(
token_num: int, hidden_dim: int, dtype: torch.dtype, world_size: int
) -> bool:
comm_size_mb = (
token_num * hidden_dim * 2 * world_size * dtype.itemsize / 1024 / 1024
)
return comm_size_mb <= _rs_oneshot_heuristics.get(world_size, 0)
def trtllm_create_ipc_workspace_for_reduce_scatter_fusion(
tp_rank: int,
tp_size: int,
max_token_num: int,
hidden_dim,
use_fp32_lamport: bool = False,
group: Optional[ProcessGroup] = None,
create_metadata: bool = False,
) -> Union[
Tuple[List[List[int]], torch.Tensor],
Tuple[List[List[int]], torch.Tensor, dict],
]:
buffer_size = tp_size * max_token_num * hidden_dim * 2
flag_size = tp_size * BarrierFlagCount * 4
lamport_comm_size = (
tp_size * max_token_num * hidden_dim * 2
if not use_fp32_lamport
else tp_size * max_token_num * hidden_dim * 4
)
ipc_handles, workspace_tensor = _create_ipc_workspace(
tp_rank,
tp_size,
buffer_size,
flag_size,
lamport_comm_size,
use_fp32_lamport,
group,
)
if create_metadata:
metadata = {
"tp_rank": tp_rank,
"tp_size": tp_size,
"max_token_num": max_token_num,
"hidden_dim": hidden_dim,
"use_fp32_lamport": use_fp32_lamport,
"buffer_size": buffer_size,
"flag_size": flag_size,
"lamport_comm_size": min(lamport_comm_size, MAX_COMM_SIZE),
}
return ipc_handles, workspace_tensor, metadata
return ipc_handles, workspace_tensor
def trtllm_destroy_ipc_workspace_for_reduce_scatter_fusion(
workspace: List[List[int]], group: Optional[ProcessGroup] = None
) -> None:
_destroy_ipc_workspace(workspace, group)
def trtllm_reducescatter_fusion(
reducescatter_in: torch.Tensor,
world_size: int,
world_rank: int,
token_num: int,
hidden_dim: int,
workspace_ptrs: torch.Tensor,
launch_with_pdl: bool,
trigger_completion_at_end: bool,
fp32_acc: bool,
num_token_current_rank: int,
pattern_code: int,
use_oneshot: Optional[bool] = None,
reducescatter_out: Optional[torch.Tensor] = None,
add_in: Optional[torch.Tensor] = None,
residual_in: Optional[torch.Tensor] = None,
residual_out: Optional[torch.Tensor] = None,
norm_out: Optional[torch.Tensor] = None,
quant_out: Optional[torch.Tensor] = None,
scale_out: Optional[torch.Tensor] = None,
rms_gamma: Optional[torch.Tensor] = None,
rms_eps: Optional[float] = None,
scale_factor: Optional[Union[torch.Tensor, float]] = None,
layout_code: Optional[int] = None,
metadata: Optional[dict] = None,
) -> None:
if use_oneshot is None:
use_oneshot = _rs_should_use_oneshot(
token_num, hidden_dim, reducescatter_in.dtype, world_size
)
if not use_oneshot:
assert token_num > world_size, "sequence length should be larger than tp_size"
if pattern_code == ReduceScatterFusionPattern.kRSResidualRMSNormFP8BlockWiseQuant:
assert use_oneshot, "FP8 blockwise quant requires oneshot!"
required_lamport_comm_size = (
token_num * hidden_dim * 2 * world_size
if reducescatter_in.dtype != torch.float32
else token_num * hidden_dim * 4 * world_size
)
if required_lamport_comm_size > MAX_COMM_SIZE and use_oneshot:
logging.warning(
f"required_lamport_comm_size {required_lamport_comm_size} > MAX_COMM_SIZE. Falling back."
)
use_oneshot = False
if scale_factor is not None:
if isinstance(scale_factor, torch.Tensor):
scale_factor = scale_factor.to(torch.float32)
else:
scale_factor = torch.tensor(
[scale_factor], dtype=torch.float32, device=reducescatter_in.device
)
_load_trtllm_comm_module().trtllm_reducescatter_fusion(
reducescatter_in,
world_size,
world_rank,
token_num,
hidden_dim,
workspace_ptrs,
launch_with_pdl,
use_oneshot,
trigger_completion_at_end,
fp32_acc,
pattern_code,
num_token_current_rank,
reducescatter_out,
add_in,
residual_in,
residual_out,
norm_out,
quant_out,
scale_out,
rms_gamma,
rms_eps,
scale_factor,
layout_code,
)
# ---------------------------------------------------------------------------
# MiniMax QK fused AR + RMSNorm
# ---------------------------------------------------------------------------
def _minimax_lamport_comm_size_bytes(tp_size: int, max_token_num: int) -> int:
"""Conservative upper bound (in bytes) of a single rotation of the MiniMax
lamport comm buffer.
QK-fused path (TokenPerBlock=4) writes `2*tot_groups*sizeof(float4) = 32*tot_groups`
bytes per rank; the next-iter clear writes the same amount. Worst case:
`32 * ceil(max_token/4) * NRanks` bytes = `8 * max_token * NRanks`, with
2x headroom and rounded up to 2MB for the shared-memory allocator.
"""
raw = max(8 * max_token_num * tp_size, 1 << 16)
return _round_up(raw * 2, 1 << 21)
def trtllm_create_ipc_workspace_for_minimax(
tp_rank: int,
tp_size: int,
max_token_num: int,
group: Optional[ProcessGroup] = None,
dtype_elem_size: int = 2,
) -> Tuple[List[List[int]], torch.Tensor]:
"""Create an IPC workspace dedicated to the MiniMax QK fused AR+RMSNorm kernel.
Layout of the returned `workspace_tensor` (each slot is an int64 device-ptr):
[0, 2*tp_size) : unused placeholders (kept to match the indexing the
kernel uses: `workspace[2*NRanks + r]` for lamport)
[2*tp_size, 3*tp_size): per-rank lamport buffer pointers
[3*tp_size] : pointer to a 20-byte int32 scratch with
[0]=counter, [2]=flag (rotation in 0/1/2)
[3*tp_size + 1] : pointer to a 16-byte int64 scratch with
[0]=clear_size, [1]=comm_size_bytes
This layout is NOT interchangeable with the regular trtllm_allreduce_fusion
workspace; MiniMax must have its own because the two kernels read/write
different sizes and increment the rotation flag independently.
"""
# `dtype_elem_size` is accepted for API continuity but the lamport buffer
# always stores fp32 variance sums regardless of input dtype, so sizing
# and init are dtype-independent.
del dtype_elem_size
lamport_comm_size = _minimax_lamport_comm_size_bytes(tp_size, max_token_num)
if lamport_comm_size > MAX_COMM_SIZE:
lamport_comm_size = MAX_COMM_SIZE
lamport_buffer_size = lamport_comm_size * 3
# 3 × per-rank lamport buffers. We use the IPC allocator so each rank sees
# peer pointers.
lamport_handles = create_shared_buffer(
_round_up(lamport_buffer_size, 1 << 21), group
)
# Placeholder IPC allocation for the two unused slot groups. Using zero-sized
# allocations is not portable, so we allocate small (2MB) dummy buffers that
# the kernel never touches.
dummy_a = create_shared_buffer(1 << 21, group)
dummy_b = create_shared_buffer(1 << 21, group)
# Lamport sentinel: ALWAYS fp32 -0 (0x80000000). The MiniMax kernel stores
# per-token variance sums (fp32) in the lamport buffer regardless of the
# input/gamma dtype, so we must init with the fp32 sentinel pattern.
# Initialising with fp16 -0 (0x8000) would set the bytes to 0x80008000
# repeating, which an fp32 read would see as non-negative-zero and
# immediately consume as "already written", producing garbage.
trtllm_lamport_initialize(
lamport_handles[tp_rank],
lamport_buffer_size // 4,
torch.float32,
)
# Scratch #0: 5 × int32 at workspace[3*tp_size]
flag_ptr = cudart.cudaMalloc(5 * 4)
cudart.cudaMemset(flag_ptr, 0, 5 * 4)
# Scratch #1: 2 × int64 at workspace[3*tp_size + 1]: {clear_size=0, comm_size}
clear_scalar = cudart.cudaMalloc(2 * 8)
cudart.cudaMemset(clear_scalar, 0, 2 * 8)
comm_size_bytes = int(lamport_comm_size).to_bytes(8, byteorder="little")
cudart.cudaMemcpy(
c_void_p(clear_scalar.value + 8), cast(comm_size_bytes, c_void_p), 8
)
workspace: List[int] = []
# Slots [0, 2*tp_size): dummies. The kernel indexes [2*tp_size + r] for lamport.
for r in range(tp_size):
workspace.append(dummy_a[r])
for r in range(tp_size):
workspace.append(dummy_b[r])
for r in range(tp_size):
workspace.append(lamport_handles[r])
workspace.append(flag_ptr.value)
workspace.append(clear_scalar.value)
workspace_tensor = torch.tensor(
workspace, dtype=torch.int64, device=torch.device("cuda")
)
if dist.is_initialized() and group is not None:
dist.barrier(group=group)
ipc_handles = [dummy_a, dummy_b, lamport_handles]
return ipc_handles, workspace_tensor
def trtllm_destroy_ipc_workspace_for_minimax(
ipc_handles: List[List[int]], group: Optional[ProcessGroup] = None
) -> None:
for handle in ipc_handles:
free_shared_buffer(handle, group)
def minimax_allreduce_rms(
input: torch.Tensor,
norm_weight: torch.Tensor,
workspace_ptrs: torch.Tensor,
rank: int,
nranks: int,
eps: float,
trigger_completion_at_end: bool = True,
launch_with_pdl: bool = False,
rms_norm_out: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""Single-matrix Lamport AR + RMSNorm over sharded hidden dim.
`input` is [token_num, local_hidden_dim] (= global / nranks). `norm_weight`
must be bf16 of shape [local_hidden_dim]. Reuses the same workspace layout
as `trtllm_create_ipc_workspace_for_all_reduce_fusion`.
"""
if rms_norm_out is None:
rms_norm_out = torch.empty_like(input)
_load_trtllm_comm_module().minimax_allreduce_rms(
input,
norm_weight,
rms_norm_out,
workspace_ptrs,
rank,
nranks,
eps,
trigger_completion_at_end,
launch_with_pdl,
)
return rms_norm_out
def minimax_allreduce_rms_qk(
q: torch.Tensor,
k: torch.Tensor,
norm_weight_q: torch.Tensor,
norm_weight_k: torch.Tensor,
workspace_ptrs: torch.Tensor,
rank: int,
nranks: int,
eps: float,
trigger_completion_at_end: bool = True,
launch_with_pdl: bool = False,
rms_norm_out_q: Optional[torch.Tensor] = None,
rms_norm_out_k: Optional[torch.Tensor] = None,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""Fused Q+K Lamport AR + RMSNorm. Requires global head_dim_q==6144 and
global head_dim_k==1024 (i.e. MiniMax M2 attention)."""
# Outputs must be tightly packed (kernel writes them at head_dim stride);
# `q`/`k` may be strided slices, so don't preserve their layout via
# empty_like default (preserve_format) — force contiguous.
if rms_norm_out_q is None:
rms_norm_out_q = torch.empty_like(q, memory_format=torch.contiguous_format)
if rms_norm_out_k is None:
rms_norm_out_k = torch.empty_like(k, memory_format=torch.contiguous_format)
_load_trtllm_comm_module().minimax_allreduce_rms_qk(
q,
k,
norm_weight_q,
norm_weight_k,
rms_norm_out_q,
rms_norm_out_k,
workspace_ptrs,
rank,
nranks,
eps,
trigger_completion_at_end,
launch_with_pdl,
)
return rms_norm_out_q, rms_norm_out_k