Files
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 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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