feat(CRITICAL): 从 GitHub 扫描搬运 ixformer SDK + xllm 完整 GDN/MoE 代码

来源:
  1. Chranos/ixformer (GitHub) → ixformer_sdk/ (230 files, 70K lines)
     - inference/functions/vllm.py: vllm_moe_topk_softmax 完整实现 (2033 lines)
     - inference/functions/moe.py: MoE ops 完整实现 (1380 lines)
     - contrib/vllm_flash_attn/: FA2 Python 接口 (1018 lines)
     - contrib/tgi/fused_moe.py: TGI fused MoE (429 lines)
     - csrc/include/ixformer/: C++ kernel headers + cmake

  2. Deep-Spark/xllm (GitHub) → upstream_ref/xllm_latest/ (+15 files)
     - npu_torch/qwen3_5_decoder_layer_impl.cpp/.h
     - npu_torch/qwen3_5_gated_delta_net.cpp/.h
     - npu_torch/qwen3_next_*.cpp/.h (6 files)
     - npu_torch/attention.cpp/.h + fused_moe.cpp/.h + CMakeLists.txt
     - models/llm/qwen3_5.h + qwen3_5_mtp.h + qwen3_next.h
     - models/vlm/qwen3_5.h

调用链完整性:
  ixformer_sdk/inference/functions/vllm.py
    → ops.infer.moe_topk_softmax() (C++ 层)
    → 这就是 base 镜像 libixformer.so 里的实现

  upstream_ref/xllm_latest/core/layers/ilu/fused_moe.cpp
    → ixformer::infer::topk_softmax() (直接 C++ 调用)
    → ixformer::infer::group_gemm() → 完整 7-step MoE pipeline
This commit is contained in:
project6-dev
2026-08-11 02:31:56 +00:00
parent a8b16da5da
commit 87a19d2d00
250 changed files with 76690 additions and 0 deletions

View File

@@ -0,0 +1 @@
from .llama_decoder_layer_overlap import LlamaDecoderLayerOverlapProtocol, LlamaDecoderLayerOverlapDefault, create_vllm_llama_decoder_layer

View File

@@ -0,0 +1,333 @@
import dataclasses
from typing import Optional, Tuple
import ixformer.distributed as ixfd
import ixformer.functions as F
import torch
import torch.distributed as dist
from ixformer.contrib.vllm_flash_attn import flash_attn_varlen_func
from ixformer.distributed.overlap_comm import SplitOverlapComm
from ixformer.core import config as ixff_config
@dataclasses.dataclass
class FmhaOProjAllReduceLnGatingParams:
# ==============================
# attention
# ==============================
# shape: [Batch * SeqLen, NumHeads / TP, HeadDim]
q: torch.Tensor
# shape: [Batch * SeqLen, NumHeads / TP, HeadDim]
k: torch.Tensor
# shape: [Batch * SeqLen, NumHeads / TP, HeadDim]
v: torch.Tensor
# shape [Batch + 1], dtype torch.int32. The cumulative sequence lengths
# of the sequences in the batch, used to index into q.
cu_seqlens_q: torch.Tensor
# shape: [Batch + 1], dtype torch.int32. The cumulative sequence lengths
# of the sequences in the batch, used to index into kv.
cu_seqlens_k: torch.Tensor
# Maximum query sequence length in the batch.
max_seqlen_q: int
# Maximum key sequence length in the batch.
max_seqlen_k: int
# ==============================
# o_proj
# ==============================
# shape: [HiddenSize, NumHeads * HeadDim / TP], dtype: int8
o_proj_weight: torch.Tensor
# shape: [HiddenSize], dtype: float32
o_proj_weight_scale: torch.Tensor
# shape: [HiddenSize]
o_proj_bias: torch.Tensor
# shape: [NumHeads * HeadDim / TP], dtype: float16 or bfloat16
o_proj_smooth_scale: torch.Tensor
# ==============================
# ln
# ==============================
# shape: [Batch * SeqLen, HiddenSize], dtype: float16 or bfloat16
residual: torch.Tensor
# shape: [HiddenSize], dtype: float16 or bfloat16
ln_weight: torch.Tensor
# shape: [HiddenSize], dtype: float16 or bfloat16
ln_bias: torch.Tensor
# ==============================
# gating linear
# ==============================
# shape: [TopK, HiddenSize], dtype: float16 or bfloat16
gating_weight: torch.Tensor
# shape: [SeqLen, TopK], dtype: float16 or bfloat16
out: Optional[torch.Tensor] = None
# ==============================
# default parameters
# ==============================
# the seqlens of q for per chunk when using overlap,
# the parameter can be initiated by params.prepare_overlap_params(),
# and only need to initialize once during the model's forward.
cu_seqlens_q_chunks = None
cu_seqlens_k_chunks = None
softmax_scale: Optional[float] = None
ln_eps: float = 1e-5
@property
def batch(self):
return len(self.cu_seqlens_q) - 1
@property
def seqlen(self):
return self.q.shape[0]
@property
def topk(self):
return self.gating_weight.shape[0]
def prepare_overlap_params(self):
"""compute the cu_seqlens qk of chunk when using overlap"""
first_chunk_size = int(self.q.shape[0] // 2)
if not hasattr(self.cu_seqlens_q, "q_chunks"):
first_q_chunks_cu_seqlens = self.cu_seqlens_q.clone()
first_q_chunks_cu_seqlens[-1] = first_chunk_size
self.cu_seqlens_q.q_chunks = [
first_q_chunks_cu_seqlens,
first_q_chunks_cu_seqlens,
]
self.cu_seqlens_q_chunks = self.cu_seqlens_q.q_chunks
if not hasattr(self.cu_seqlens_k, "k_chunks"):
first_chunk = self.cu_seqlens_k.clone()
last_chunk = self.cu_seqlens_k
first_chunk[-1] = first_chunk_size
self.cu_seqlens_k.k_chunks = [first_chunk, last_chunk]
self.cu_seqlens_k_chunks = self.cu_seqlens_k.k_chunks
return self
class FmhaOProjAllreduceLnGatingOverlap(SplitOverlapComm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.num_chunks != 2:
raise RuntimeError(
f"Overlap only support num_chunks == 2, but got {self.num_chunks}."
)
self.allreduce_end_events = [torch.cuda.Event() for _ in range(self.num_chunks)]
def start_ln_gating(self, chunk_idx):
compute_stream = self._compute_streams[chunk_idx % self.num_compute_streams]
compute_stream.wait_event(self.allreduce_end_events[chunk_idx])
def compute(self, params: FmhaOProjAllReduceLnGatingParams):
if params.out is None:
params.out = torch.empty(
[params.seqlen, params.topk], device="cuda", dtype=torch.float
)
seqlen_chunks = [int(params.q.shape[0] // 2)]
seqlen_chunks.append(params.q.shape[0] - seqlen_chunks[0])
q_chunks = torch.split_with_sizes(params.q, seqlen_chunks, dim=0)
ar_out_chunks = []
for chunk_idx in range(len(seqlen_chunks)):
with self.compute_stream_context(chunk_idx):
hidden_states = flash_attn_varlen_func(
q=q_chunks[chunk_idx],
k=params.k,
v=params.v,
cu_seqlens_q=params.cu_seqlens_q_chunks[chunk_idx],
cu_seqlens_k=params.cu_seqlens_k_chunks[chunk_idx],
max_seqlen_q=seqlen_chunks[chunk_idx],
max_seqlen_k=seqlen_chunks[0]
if chunk_idx == 0
else params.max_seqlen_k,
softmax_scale=params.softmax_scale,
causal=True,
window_size=(-1, -1),
alibi_slopes=None,
softcap=0,
)
hidden_states = hidden_states.view(hidden_states.shape[0], -1)
hidden_states, i_scales = F.dynamic_scaled_quant_dynamic_int8(
hidden_states, params.o_proj_smooth_scale
)
out_chunk = F.w8a8(
hidden_states,
params.o_proj_weight,
i_scales,
params.o_proj_weight_scale,
bias=params.o_proj_bias,
out_dtype=params.residual.dtype,
output=None,
persistent=True,
)
self.start_comm(chunk_idx)
ixfd.all_reduce(
out_chunk, async_op=True, group=self.comm_group, use_comm_stream=True
)
ar_out_chunks.append(out_chunk)
self.allreduce_end_events[chunk_idx].record(self._comm_stream)
ln_out = torch.empty_like(params.residual)
ln_out_chunks = ln_out.chunk(2, dim=0)
residual_chunks = torch.split_with_sizes(params.residual, seqlen_chunks, dim=0)
if params.out is None:
params.out = torch.empty(
[params.seqlen, params.topk], dtype=params.residual.dtype, device="cuda"
)
out_chunks = list(torch.split_with_sizes(params.out, seqlen_chunks, dim=0))
for chunk_idx in range(len(seqlen_chunks)):
self.start_ln_gating(chunk_idx)
with self.compute_stream_context(chunk_idx):
ln_out_chunk, residual_chunk = F.residual_layer_norm(
input=ar_out_chunks[chunk_idx],
weight=params.ln_weight,
bias=params.ln_bias,
residual=residual_chunks[chunk_idx].reshape(
ar_out_chunks[chunk_idx].shape
),
eps=params.ln_eps,
output=ln_out_chunks[chunk_idx],
)
if ln_out_chunk.dtype == params.gating_weight.dtype:
F.linear(
ln_out_chunk, params.gating_weight, output=out_chunks[chunk_idx]
)
else:
F.mixed_type_linear(
ln_out_chunk, params.gating_weight, output=out_chunks[chunk_idx]
)
return (
params.residual.reshape(params.batch, params.seqlen, -1),
ln_out.reshape(params.batch, params.seqlen, -1),
params.out,
)
_fa_o_proj_allreduce_ln_gating_overlap = None
def fmha_oproj_allreduce_ln_gating(
params: FmhaOProjAllReduceLnGatingParams,
enable_overlap: bool = False,
comm_group=None,
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
FMHA + OProjLinear + AllReduce + LayerNorm + GatingLinear
Args:
params: fused operator params
enable_overlap: whether enable overlap
comm_group: communication group
Returns:
Residual: shape: [Batch * SeqLen, HiddenSize], dtype: float16 or bfloat16
HiddenStates: shape: [Batch * SeqLen, HiddenSize], dtype: float16 or bfloat16
GatingLinearOutput: shape: [SeqLen, TopK], dtype: float16 or bfloat16
"""
global _fa_o_proj_allreduce_ln_gating_overlap
if _fa_o_proj_allreduce_ln_gating_overlap is None:
_fa_o_proj_allreduce_ln_gating_overlap = (
FmhaOProjAllreduceLnGatingOverlap.dispatcher(
num_chunks=2, comm_group=comm_group
).forward
)
if (
enable_overlap
and ixff_config.IXFORMER_ENABLE_OVERLAP_COMM
and dist.is_initialized()
and dist.get_world_size(comm_group) > 1
and params.batch == 1
and params.seqlen > 1
):
if params.cu_seqlens_q_chunks is None:
params = params.prepare_overlap_params()
return _fa_o_proj_allreduce_ln_gating_overlap(params)
hidden_states = flash_attn_varlen_func(
q=params.q,
k=params.k,
v=params.v,
cu_seqlens_q=params.cu_seqlens_q,
cu_seqlens_k=params.cu_seqlens_k,
max_seqlen_q=params.max_seqlen_q,
max_seqlen_k=params.max_seqlen_k,
softmax_scale=params.softmax_scale,
causal=True,
window_size=(-1, -1),
alibi_slopes=None,
softcap=0,
)
input = hidden_states.view(hidden_states.shape[0], -1)
input, i_scales = F.dynamic_scaled_quant_smoothquant(
input, params.o_proj_smooth_scale
)
hidden_states = F.w8a8(
input,
params.o_proj_weight,
i_scales,
params.o_proj_weight_scale,
bias=params.o_proj_bias,
out_dtype=params.residual.dtype,
output=None,
)
ixfd.all_reduce(hidden_states, async_op=True, group=comm_group)
hidden_states, residual = F.residual_layer_norm(
input=hidden_states,
weight=params.ln_weight,
bias=params.ln_bias,
residual=params.residual.reshape(hidden_states.shape),
eps=params.ln_eps,
)
if hidden_states.dtype == params.gating_weight.dtype:
out = F.linear(hidden_states, params.gating_weight, output=params.out)
else:
out = F.mixed_type_linear(
hidden_states, params.gating_weight, output=params.out
)
return (
residual.reshape(params.batch, params.seqlen, -1),
hidden_states.reshape(params.batch, params.seqlen, -1),
out,
)

View File

@@ -0,0 +1,219 @@
import dataclasses
import math
from contextlib import contextmanager
from typing import List, Optional
import ixformer.distributed as ixfd
import ixformer.functions as F
import torch
import torch.distributed as dist
from ixformer.distributed.overlap_comm import SplitOverlapComm
from ixformer.core import config as ixff_config
@dataclasses.dataclass
class GroupGemmMoeReduceSumAllReduceParams:
# M: NumTokens * TopK
# K: InnerSize // TP
# N: HiddenSize
# NumTokens: M // TopK
# =================================
# group gemm
# =================================
# the top k of experts
topk: int
# shape: [M, K] if format[1]=="N" else [K, M], dtype: int8
input: torch.Tensor
# shape: [NumExperts, N, K] if format[0]=="T" else [NumExperts, K, N], dtype: int8
weight: torch.Tensor
# shape: [M], dtype: float32
i_scales: torch.Tensor
# shape: [NumExperts, N], dtype: float32
w_scales: torch.Tensor
# shape: [NumExperts], dtype: int32
tokens_per_experts: torch.Tensor
# the dtype of output, support float16 and bfloat16
out_dtype: torch.dtype = None
# index of dst to src, shape: [M], dtype: int32
dst_to_src: torch.Tensor = None
# only support TN now
format: str = "TN"
# =================================
# moe reduce sum
# =================================
# shape: [M // TopK, TopK], dtype: torch.float16 or torch.bfloat16
topk_weight: torch.Tensor = None
# shape: [M // TopK, N], dtype: torch.float16 or torch.bfloat16
output: torch.Tensor = None
# overlap
output_chunks: Optional[List[torch.Tensor]] = None
@property
def M(self):
return self.input.shape[0]
@property
def N(self):
if torch.is_tensor(self.weight):
return self.weight.shape[1]
return sum(t.shape[1] for t in self.weight)
@property
def K(self):
return self.input.shape[-1]
def prepare_overlap_params(
self, num_chunks: int, split_ratio: Optional[float] = None
):
if num_chunks == 2 and split_ratio not in [0, None]:
return self.prepare_overla_params_with_ratio(split_ratio)
return self.prepare_overlap_params_with_chunks(num_chunks)
def prepare_overlap_params_with_chunks(self, num_chunks: int):
if torch.is_tensor(self.weight):
weight_chunks = torch.chunk(self.weight, num_chunks, dim=1)
self.weight = list(weight_chunks)
if torch.is_tensor(self.w_scales):
weight_scale_chunks = torch.chunk(self.w_scales, num_chunks, dim=1)
self.w_scales = list(weight_scale_chunks)
if self.output is None:
self.output = torch.empty(
self.M // self.topk, self.N, dtype=self.out_dtype, device="cuda"
)
if torch.is_tensor(self.output):
output_chunks = torch.chunk(self.output, num_chunks, dim=1)
self.output_chunks = list(output_chunks)
def prepare_overla_params_with_ratio(self, split_ratio: float):
N = self.N
n_chunks = [int(math.ceil(N * split_ratio))]
n_chunks.append(N - n_chunks[0])
if torch.is_tensor(self.weight):
weight_chunks = torch.split(self.weight, n_chunks, dim=1)
self.weight = list(weight_chunks)
if torch.is_tensor(self.w_scales):
weight_scale_chunks = torch.split(self.w_scales, n_chunks, dim=1)
self.w_scales = list(weight_scale_chunks)
if self.output is None:
self.output = torch.empty(
self.M // self.topk, self.N, dtype=self.out_dtype, device="cuda"
)
if torch.is_tensor(self.output):
output_chunks = torch.split(self.output, n_chunks, dim=1)
self.output_chunks = list(output_chunks)
class GroupGemmMoeReduceSumAllReduceSplitNOverlap(SplitOverlapComm):
def compute(self, params: GroupGemmMoeReduceSumAllReduceParams):
for chunk_idx, (weight, weight_scale) in enumerate(
zip(params.weight, params.w_scales)
):
with self.compute_stream_context(chunk_idx):
out = F.moe_w8a8_group_gemm(
input=params.input,
weight=weight,
i_scales=params.i_scales,
w_scales=weight_scale,
output_dtype=params.out_dtype,
tokens_per_experts=params.tokens_per_experts,
dst_to_src=params.dst_to_src,
format=params.format,
)
out = out.reshape(-1, params.topk, out.shape[-1])
out = F.moe_output_reduce_sum(
input=out,
topk_weight=params.topk_weight,
output=params.output_chunks[chunk_idx],
)
self.start_comm(chunk_idx)
ixfd.all_reduce(
out,
async_op=True,
group=self.comm_group,
use_comm_stream=True,
algo=ixfd.AllReduceAlgo.Stride,
)
# if chunk_idx == 0: torch.cuda.synchronize()
return params.output
_group_gemm_moe_reduce_sum_all_reduce_overlap = None
def group_gemm_moe_reduce_sum_allreduce(
params: GroupGemmMoeReduceSumAllReduceParams,
enable_overlap: bool = False,
comm_group=None,
num_chunks=2,
split_ratio: Optional[float] = None,
):
if params.output is None and params.out_dtype is None:
raise RuntimeError(
"group_gemm_moe_reduce_sum_all_reduce need out_dtype argument when output is none."
)
if params.out_dtype is None:
params.out_dtype = params.output.dtype
if (
enable_overlap
and ixff_config.IXFORMER_ENABLE_OVERLAP_COMM
and dist.is_initialized()
and dist.get_world_size(comm_group) > 1
):
global _group_gemm_moe_reduce_sum_all_reduce_overlap
if _group_gemm_moe_reduce_sum_all_reduce_overlap is None:
_group_gemm_moe_reduce_sum_all_reduce_overlap = (
GroupGemmMoeReduceSumAllReduceSplitNOverlap.dispatcher(
num_chunks=num_chunks, comm_group=comm_group
).forward
)
params.prepare_overlap_params(num_chunks=num_chunks, split_ratio=split_ratio)
return _group_gemm_moe_reduce_sum_all_reduce_overlap(params)
out = F.moe_w8a8_group_gemm(
input=params.input,
weight=params.weight,
i_scales=params.i_scales,
w_scales=params.w_scales,
output_dtype=params.out_dtype,
tokens_per_experts=params.tokens_per_experts,
dst_to_src=params.dst_to_src,
format=params.format,
)
out = out.reshape(-1, params.topk, out.shape[-1])
out = F.moe_output_reduce_sum(
input=out, topk_weight=params.topk_weight, output=params.output
)
if dist.is_initialized() and dist.get_world_size(comm_group) > 1:
ixfd.all_reduce(out, group=comm_group, async_op=True)
return out

View File

@@ -0,0 +1,305 @@
from contextlib import nullcontext
from typing import List
import torch.cuda
from ...distributed import _distributed as ixfd
from ...distributed import overlap_comm as base_overlap_comm
from ...distributed.overlap_comm import GemmAllReduceSplitOverlapComm
from .. import overlap as overlap_base
class LinearMLPOverlapCommHook:
def on_mlp_linear2_finished(
self,
overlap_comm: "LinearMLPOverlapComm",
num_chunks,
chunk_idx,
hidden_states_chunk,
residual_chunk,
):
pass
def on_mlp_finished(
self,
overlap_comm: "LinearMLPOverlapComm",
hidden_states_chunks,
residual_chunks,
):
pass
class LinearMLPOverlapComm(GemmAllReduceSplitOverlapComm):
def __init__(self, *args, **kwargs):
super().__init__(num_compute_streams=1, *args, **kwargs)
self._mlp_linear1_start_events: List[torch.cuda.Event] = [
torch.cuda.Event() for _ in range(self.num_chunks)
]
self._mlp_linear1_end_events: List[torch.cuda.Event] = [
torch.cuda.Event() for _ in range(self.num_chunks)
]
self._mlp_linear1_stream: torch.cuda.Stream = torch.cuda.Stream()
def stop_linear_comm(self, chunk_idx):
event = self._mlp_linear1_start_events[chunk_idx]
event.record(self._comm_stream)
def start_mlp_linear1(self, chunk_idx):
self._mlp_linear1_stream.wait_event(self._mlp_linear1_start_events[chunk_idx])
def stop_mlp_linear1(self, chunk_idx):
event = self._mlp_linear1_end_events[chunk_idx]
event.record(self._mlp_linear1_stream)
def start_mlp_linear2(self, chunk_idx):
compute_stream = self._compute_streams[chunk_idx % self.num_compute_streams]
compute_stream.wait_event(self._mlp_linear1_end_events[chunk_idx])
def compute(
self,
protocol: "overlap_base.LlamaDecoderLayerOverlapDefault",
attn_output,
residual,
*,
mlp_linear2_finished_callback=None,
mlp_finished_callback=None,
):
""" """
attn_output_shape = attn_output.shape
residual_shape = None if residual is None else residual.shape
is_update_shape = attn_output.ndim > 2
batch = 1
if attn_output.ndim == 2:
seqlen = attn_output_shape[0]
else:
batch = attn_output_shape[0]
seqlen = attn_output_shape[1]
parallel_dims = batch * seqlen
if is_update_shape:
attn_output = attn_output.reshape(parallel_dims, -1)
if residual is not None:
residual = residual.reshape(-1, residual_shape[-1])
attn_output_chunks, residual_chunks = protocol.split_mlp_inputs(
attn_output, residual, self.num_chunks
)
out = protocol.create_mlp_output()
out_chunks = protocol.split_mlp_output(out, self.num_chunks)
res_chunks = []
# 1. output project linear
for chunk_idx, (attn_output_chunk, residual_chunk) in enumerate(
zip(attn_output_chunks, residual_chunks)
):
with self.compute_stream_context(chunk_idx):
hidden_states = protocol.attn_output_proj_linear(
self.num_chunks,
chunk_idx,
attn_output_chunk,
use_limited_gemm=chunk_idx != 0,
)
self.start_comm(chunk_idx)
ixfd.all_reduce(
hidden_states,
async_op=True,
group=self.comm_group,
use_comm_stream=True,
)
self.stop_linear_comm(chunk_idx)
attn_output_chunks[chunk_idx] = hidden_states
# 2. ln, mlp_linear1 and act
for chunk_idx, (hidden_states, residual_chunk) in enumerate(
zip(attn_output_chunks, residual_chunks)
):
self.start_mlp_linear1(chunk_idx)
with self.stream_context(self._mlp_linear1_stream):
(
hidden_states,
residual_chunk,
) = protocol.attn_output_proj_linear_layer_norm(
self.num_chunks, chunk_idx, hidden_states, residual_chunk
)
hidden_states = protocol.mlp_linear1(
self.num_chunks, chunk_idx, hidden_states, use_limited_gemm=True
)
hidden_states = protocol.mlp_activation(hidden_states)
attn_output_chunks[chunk_idx] = hidden_states
res_chunks.append(residual_chunk)
self.stop_mlp_linear1(chunk_idx)
# 3. mlp_linear2
for chunk_idx, hidden_states in enumerate(attn_output_chunks):
self.start_mlp_linear2(chunk_idx)
with self.compute_stream_context(chunk_idx):
hidden_states = protocol.mlp_linear2(
self.num_chunks,
chunk_idx,
hidden_states,
out=out_chunks[chunk_idx],
use_limited_gemm=True,
)
self.start_comm(chunk_idx)
ixfd.all_reduce(
hidden_states,
async_op=True,
group=self.comm_group,
use_comm_stream=True,
)
if mlp_linear2_finished_callback is not None:
mlp_linear2_finished_callback(
self,
self.num_chunks,
chunk_idx,
hidden_states,
res_chunks[chunk_idx],
)
if mlp_finished_callback is not None:
mlp_finished_callback(self, out_chunks, res_chunks)
if is_update_shape:
out = out.reshape(attn_output_shape)
if residual is not None:
residual = residual.reshape(residual_shape)
return out, residual
def gemm_dispatcher(
self,
chunk_idx,
chunk_input,
weight,
chunk_out,
use_limited_gemm=False,
user_gemm_method=None,
*args,
**kwargs,
):
if user_gemm_method is not None and callable(user_gemm_method):
ctx = self.ixf_limited_gemm_ctx if use_limited_gemm else nullcontext()
with ctx:
return user_gemm_method(
chunk_input, weight, out=chunk_out, *args, **kwargs
)
ctx = self.limited_gemm_ctx if use_limited_gemm else nullcontext()
with ctx:
return torch.matmul(chunk_input, weight.T, out=chunk_out)
@classmethod
def is_supported(cls, input, num_chunks, comm_group):
if not cls.enable():
return False
ndim = input.ndim
shape = input.shape
if ndim == 1:
m, k = 1, shape[0]
elif ndim == 2:
m, k = shape
else:
m, k = sum(shape[:-1]), shape[-1]
return m >= 512
@classmethod
def native_forward(
cls,
attn_output,
residual,
linear_weight,
ln_layer,
mlp_weight1,
mlp_weight2,
mlp_activation,
linear_method=None,
mlp_linear1_method=None,
mlp_linear2_method=None,
group=None,
*args,
**kwargs,
):
import ixformer.functions as ixff
linear_method = linear_method or ixff.linear
mlp_linear1_method = mlp_linear1_method or ixff.linear
mlp_linear2_method = mlp_linear2_method or ixff.linear
hidden_states = linear_method(attn_output, linear_weight)
ixfd.all_reduce(hidden_states, async_op=True, group=group)
if ln_layer is not None:
hidden_states, residual = ln_layer(hidden_states, residual)
hidden_states = mlp_linear1_method(hidden_states, mlp_weight1)
hidden_states = mlp_activation(hidden_states)
hidden_states = mlp_linear2_method(hidden_states, mlp_weight2)
ixfd.all_reduce(hidden_states, async_op=True, group=group)
return hidden_states, residual
_DEFAULT_OVERLAP_GROUP = None
_DEFAULT_OVERLAP_COMM_N2 = None
_DEFAULT_OVERLAP_COMM_N4 = None
_DEFAULT_OVERLAP_CHUNKS = base_overlap_comm._DEFAULT_OVERLAP_CHUNKS
def linear_mlp_overlap(
protocol: "overlap_base.LlamaDecoderLayerOverlapProtocol",
attn_output,
residual,
num_chunks=None,
group=None,
*,
mlp_linear2_finished_callback=None,
mlp_finished_callback=None,
):
num_chunks = num_chunks or _DEFAULT_OVERLAP_CHUNKS
global _DEFAULT_OVERLAP_GROUP
global _DEFAULT_OVERLAP_COMM_N2
global _DEFAULT_OVERLAP_COMM_N4
if _DEFAULT_OVERLAP_GROUP is None:
_DEFAULT_OVERLAP_GROUP = group
if num_chunks == 2 and group == _DEFAULT_OVERLAP_GROUP:
if _DEFAULT_OVERLAP_COMM_N2 is None:
_DEFAULT_OVERLAP_COMM_N2 = LinearMLPOverlapComm.dispatcher(
num_chunks=num_chunks, comm_group=group
)
overlap_comm = _DEFAULT_OVERLAP_COMM_N2
elif num_chunks == 4 and group == _DEFAULT_OVERLAP_GROUP:
if _DEFAULT_OVERLAP_COMM_N4 is None:
_DEFAULT_OVERLAP_COMM_N4 = LinearMLPOverlapComm.dispatcher(
num_chunks=num_chunks, comm_group=group
)
overlap_comm = _DEFAULT_OVERLAP_COMM_N4
else:
overlap_comm = LinearMLPOverlapComm.dispatcher(
num_chunks=num_chunks, comm_group=group
)
return overlap_comm.forward(
protocol,
attn_output,
residual,
mlp_linear2_finished_callback=mlp_linear2_finished_callback,
mlp_finished_callback=mlp_finished_callback,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
import dataclasses
import math
from typing import Optional, Tuple
import ixformer.distributed as ixfd
import ixformer.functions as F
import torch
import torch.distributed as dist
from ixformer.distributed.overlap_comm import SplitOverlapComm
from ixformer.core import config as ixff_config
@dataclasses.dataclass
class MoeReduceAllReduceLnQkvLinearParams:
# ==============================
# MOE Reduce Sum
# ==============================
# shape: [Batch * SeqLen, TopK, HiddenSize], dtype: float16 or bfloat16
input: torch.Tensor
# shape: [Batch * SeqLen, TopK], dtype: float32
topk_weight: Optional[torch.Tensor]
# ==============================
# Ln
# ==============================
# shape: [Batch * SeqLen, HiddenSize], dtype: float16 or bfloat16
residual: torch.Tensor
# shape: [HiddenSize], dtype: float16 or bfloat16
ln_weight: torch.Tensor
# shape: [HiddenSize], dtype: float16 or bfloat16
ln_bias: torch.Tensor
ln_eps: float
# ==============================
# QkvLinear
# ==============================
# shape: [(NumHeads + 2 * NumKvHeads) * HeadDim / TP, HiddenSize], dtype: float16 or bfloat16
qkv_weight: torch.Tensor
# shape: [(NumHeads + 2 * NumKvHeads) * HeadDim / TP]
qkv_weight_scale: torch.Tensor
# shape: [Batch * SeqLen, (NumHeads + 2 * NumKvHeads) * HeadDim / TP], dtype: float16 or bfloat16
qkv_out: torch.Tensor
class MoeReduceSumAllReduceLnQkvLinearOverlap(SplitOverlapComm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.allreduce_end_events = [torch.cuda.Event() for _ in range(self.num_chunks)]
def start_qkv_linear(self, chunk_idx):
compute_stream = self._compute_streams[chunk_idx % self.num_compute_streams]
compute_stream.wait_event(self.allreduce_end_events[chunk_idx])
def compute(self, params: MoeReduceAllReduceLnQkvLinearParams, split_ratio=0.5):
input_chunk_sizes = [int(math.ceil(params.input.shape[0] * split_ratio))]
input_chunk_sizes.append(params.input.shape[0] - input_chunk_sizes[0])
input_chunks = list(
torch.split_with_sizes(params.input, input_chunk_sizes, dim=0)
)
topk_weight_chunks = list(
torch.split_with_sizes(params.topk_weight, input_chunk_sizes, dim=0)
)
out_chunks = []
for chunk_idx in range(len(input_chunks)):
with self.compute_stream_context(chunk_idx):
out_chunks.append(
F.moe_output_reduce_sum(
input_chunks[chunk_idx],
topk_weight=topk_weight_chunks[chunk_idx],
)
)
self.start_comm(chunk_idx)
ixfd.all_reduce(
out_chunks[chunk_idx],
async_op=True,
group=self.comm_group,
use_comm_stream=True,
)
self.allreduce_end_events[chunk_idx].record(self._comm_stream)
residual_chunk_sizes = [int(params.residual.shape[0] * split_ratio)]
residual_chunk_sizes.append(params.residual.shape[0] - residual_chunk_sizes[0])
residual_chunks = torch.split_with_sizes(
params.residual, residual_chunk_sizes, dim=0
)
qkv_out_chunk_sizes = [int(params.qkv_out.shape[0] * split_ratio)]
qkv_out_chunk_sizes.append(params.qkv_out.shape[0] - qkv_out_chunk_sizes[0])
qkv_out_chunks = torch.split_with_sizes(
params.qkv_out, qkv_out_chunk_sizes, dim=0
)
for chunk_idx in range(len(input_chunks)):
self.start_qkv_linear(chunk_idx)
with self.compute_stream_context(chunk_idx):
(
i8_hidden_states,
residual,
i_scales,
) = F.residual_layer_norm_dynamic_int8(
input=out_chunks[chunk_idx],
residual=residual_chunks[chunk_idx],
weight=params.ln_weight,
bias=params.ln_bias,
eps=params.ln_eps,
)
F.w8a8(
i8_hidden_states,
params.qkv_weight,
i_scales,
params.qkv_weight_scale,
output=qkv_out_chunks[chunk_idx],
)
return params.qkv_out, params.residual
_moe_reduce_with_allreduce_overlap = None
def moe_reduce_sum_allreduce_ln_qkv_linear(
params: MoeReduceAllReduceLnQkvLinearParams,
enable_overlap=False,
comm_group=None,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
MOE Reduce Sum + AllReduce + LayerNorm + QkvLinear
Args:
params: fused operator params
enable_overlap: whether enable overlap
comm_group: communication group
Returns:
QkvLinearOutput: [Batch * SeqLen, (NumHeads + 2 * NumKvHeads) * HeadDim / TP], dtype: float16 or bfloat16
Residual: [Batch * SeqLen, HiddenSize], dtype: float16 or bfloat16
"""
global _moe_reduce_with_allreduce_overlap
if _moe_reduce_with_allreduce_overlap is None:
_moe_reduce_with_allreduce_overlap = (
MoeReduceSumAllReduceLnQkvLinearOverlap.dispatcher(
num_chunks=2, comm_group=comm_group
).forward
)
if (
enable_overlap
and ixff_config.IXFORMER_ENABLE_OVERLAP_COMM
and dist.is_initialized()
and dist.get_world_size(comm_group) > 1
and params.input.shape[0] > 1
):
return _moe_reduce_with_allreduce_overlap(params, split_ratio=0.5)
out = F.moe_output_reduce_sum(params.input, topk_weight=params.topk_weight)
ixfd.all_reduce(out, async_op=True, group=comm_group)
i8_hidden_states, residual, i_scales = F.residual_layer_norm_dynamic_int8(
input=out,
residual=params.residual,
weight=params.ln_weight,
bias=params.ln_bias,
eps=params.ln_eps,
)
out = F.w8a8(
i8_hidden_states,
params.qkv_weight,
i_scales,
params.qkv_weight_scale,
output=params.qkv_out,
)
return out, residual

View File

@@ -0,0 +1,250 @@
import enum
from typing import Optional
import ixformer.functions as ixff
import torch
from ixformer.inference.overlap.linear_mlp_overlap_comm import (
LinearMLPOverlapComm,
LinearMLPOverlapCommHook,
)
def get_overlap_linear_method(layer):
if hasattr(layer, "_overlap_comm_gemm_fn"):
return layer._overlap_comm_gemm_fn
if layer.linear_weights["weight"].itemsize == 2:
layer._overlap_comm_gemm_fn = None
return None
def overlap_linear_fn(input, weight, bias=None, out: torch.Tensor = None, **kwargs):
return layer.linear_method.apply_weights(
layer.linear_weights, input, output=out
)
layer._overlap_comm_gemm_fn = overlap_linear_fn
return overlap_linear_fn
class DecoderLayerOverlapComm(LinearMLPOverlapCommHook):
class HookStage(enum.IntEnum):
kExited = 0
kTracing = 1
class HookState:
def __init__(self, max_num_chunks):
self.max_num_chunks = max_num_chunks
self.stage = DecoderLayerOverlapComm.HookStage.kExited
self.mlp_linaer2_end_events = [
torch.cuda.Event() for _ in range(max_num_chunks)
]
self.ln_attn_end_event = torch.cuda.Event()
self.overlap_comm: Optional[LinearMLPOverlapComm] = None
def is_tracing_stage(self):
return self.stage == DecoderLayerOverlapComm.HookStage.kTracing
def enter(self, overlap_comm, chunk_idx):
self.stage = DecoderLayerOverlapComm.HookStage.kTracing
self.overlap_comm = overlap_comm
self.mlp_linaer2_end_events[chunk_idx].record(overlap_comm._comm_stream)
def exit(self):
self.overlap_comm = None
self.stage = DecoderLayerOverlapComm.HookStage.kExited
def __str__(self):
return f"HookState(overlap_comm={self.overlap_comm}, stage={self.stage})"
def __repr__(self):
return self.__str__()
_overlap_comm_hook_state = dict()
def __init__(self, model_id, layer_idx, max_num_chunks: int = 4):
"""
DecoderLayer 的流程:
ln_qkv: InputLayerNorm(hidden_states, [residual]) -> qkv_proj(hidden_states) -> q, k, v = split(hidden_states) -> Attention(q, k, v)
linear_mlp: AttentionOutputProj(hidden_states) -> PostLayerNorm(hidden_states) -> MLPLinear1 -> MLPActivation -> MLPLinear2
其中AttentionOutputProj 和 MLPLinear2 之后如果使用 TP那么需要进行 AllReduce
通过上述流程,该类的目的是将 MLPLinear2 后的 AllReduce 和 DecoderLayer 最开始的 ln_qkv 进行 Overlap。
其中,第一层 DecoderLayer 不进行 ln_qkv 的 Overlap因为在第一层之前没有通讯。
我们需要将第 i 层 MLPLinear2 后的通讯 和 第 i + 1 层的 ln_qkv 进行 Overlap。
为了管理当前的状态和获取前一层的状态,从而设计了 DecoderLayerOverlapComm 类。
该类需要 model_id 来推断当前正在运行的模型,用 layer_idx 来标记每一层的开始和结束,
以及通过 layer_idx 去获取前一层的状态。
注:
- 在 call_ln_qkv_overlap 中对 Tensor 进行切分时,
需要保持和 linear_mlp 切分的大小是一致的,否则会出现 Tensor 的数据不对应;
- 如果需要使用 ln_qkv 进行 Overlap那么必须使用该类的 linear_mlp 去替换 linear_mlp_overlap
:param model_id: 模型的 id可以使用 id(model) 去设置
:param layer_idx: layer 的索引,注意,需要从 0 到 NumLayers 的顺序去完成构造
:param max_num_chunks: 最大能进行切分的次数
"""
self._model_id = model_id
self._layer_idx = layer_idx
self._max_num_chunks = max_num_chunks
self._state = self.HookState(max_num_chunks)
self._overlap_comm_hook_state[(model_id, layer_idx)] = self._state
self._prev_layer_state = (
None
if layer_idx == 0
else self._overlap_comm_hook_state[(model_id, layer_idx - 1)]
)
@property
def model_id(self):
return self._model_id
@property
def layer_idx(self):
return self._layer_idx
@property
def max_num_chunks(self):
return self._max_num_chunks
@property
def state(self) -> "DecoderLayerOverlapComm.HookState":
return self._state
@property
def prev_layer_state(self) -> "DecoderLayerOverlapComm.HookState":
return self._prev_layer_state
def is_ln_qkv_overlap(self):
return not (
self.layer_idx == 0
or not self.prev_layer_state.is_tracing_stage()
or self.prev_layer_state.overlap_comm is None
)
def ln_qkv(self, hidden_states, residual, ln_layer, qkv_layer, out_last_dim):
"""
:param hidden_state: shape[Batch * SeqLen, HiddenSize]
:param residual: shape[Batch * SeqLen, HiddenSize]
:param ln_layer: torch.nn.Module or Function(hidden_state, residual=None)
:param qkv_layer: vllm.QKVParallelLinear
:param out_last_dim: qkv_layer 输出 Tensor 的最后一个维度
:return: qkv, residual
"""
if self.is_ln_qkv_overlap():
qkv, residual = self.call_ln_qkv_overlap(
hidden_states, residual, ln_layer, qkv_layer, out_last_dim
)
else:
qkv, residual = self.call_ln_qkv(
hidden_states, residual, ln_layer, qkv_layer
)
return qkv, residual
def call_ln_qkv_overlap(
self, hidden_states, residual, ln_layer, qkv_layer, out_last_dim
):
if hidden_states.ndim != 2:
raise RuntimeError(
f"Expected 2-dim for hidden state, but got {hidden_states.ndim}."
)
num_chunks = self.prev_layer_state.overlap_comm.num_chunks
overlap_comm: LinearMLPOverlapComm = self.prev_layer_state.overlap_comm
if num_chunks > self.max_num_chunks:
raise RuntimeError(
f"The layer is not support more than {self.max_num_chunks}, got {num_chunks}."
)
hidden_state_chunks = list(torch.chunk(hidden_states, num_chunks, dim=0))
if residual is None:
residual = hidden_states
residual_chunks = [None] * num_chunks
else:
residual_chunks = torch.chunk(residual, num_chunks, dim=0)
out = torch.empty(
(hidden_states.shape[0], out_last_dim),
device=hidden_states.device,
dtype=hidden_states.dtype,
)
out_chunks = list(torch.chunk(out, num_chunks, dim=0))
for chunk_idx, (hidden_state_chunk, residual_chunk, out_chunk) in enumerate(
zip(hidden_state_chunks, residual_chunks, out_chunks)
):
overlap_comm._compute_streams[
chunk_idx % overlap_comm.num_compute_streams
].wait_event(self.prev_layer_state.mlp_linaer2_end_events[chunk_idx])
with overlap_comm.compute_stream_context(chunk_idx):
self.call_ln_qkv(
hidden_state_chunk,
residual_chunk,
ln_layer,
qkv_layer,
chunk_idx,
use_limited_gemm=chunk_idx != (num_chunks - 1),
out=out_chunk,
overlap_comm=overlap_comm,
)
self.prev_layer_state.exit()
overlap_comm.stop_overlap()
return out, residual
def call_ln_qkv(
self,
hidden_state,
residual,
ln_layer,
qkv_layer,
chunk_idx=0,
use_limited_gemm=False,
out=None,
overlap_comm: LinearMLPOverlapComm = None,
):
if residual is None:
residual = hidden_state
if ln_layer is not None:
hidden_state = ln_layer(hidden_state)
else:
hidden_state, residual = ln_layer(hidden_state, residual)
if out is None:
qkv, _ = qkv_layer(hidden_state)
else:
gemm_method = get_overlap_linear_method(qkv_layer)
qkv = overlap_comm.gemm_dispatcher(
chunk_idx=chunk_idx,
chunk_input=hidden_state,
weight=qkv_layer.linear_weights["weight"],
chunk_out=out,
user_gemm_method=gemm_method,
use_limited_gemm=use_limited_gemm,
)
return qkv, residual
def linear_mlp(self, *args, **kwargs):
"""ref: linear_mlp_overlap"""
return ixff.linear_mlp_overlap(
*args, **kwargs, mlp_linear2_finished_callback=self.on_mlp_linear2_finished
)
def on_mlp_linear2_finished(
self,
overlap_comm: LinearMLPOverlapComm,
num_chunks,
chunk_idx,
hidden_states_chunk,
residual_chunk,
):
self.state.enter(overlap_comm, chunk_idx)

View File

@@ -0,0 +1,154 @@
import math
from typing import Optional
import ixformer.distributed as ixfd
import ixformer.functions as F
import torch
import torch.distributed as dist
from ixformer.distributed.overlap_comm import SplitOverlapComm
from ixformer.core import config as ixff_config
__all__ = ["w8a8_allreduce"]
class W8A8AllReduceOverlap(SplitOverlapComm):
def compute(
self,
input: torch.Tensor,
weight: torch.Tensor,
input_scale: torch.Tensor,
weight_scale: torch.Tensor,
bias: Optional[torch.Tensor] = None,
output: Optional[torch.Tensor] = None,
format: str = "TN",
out_dtype: torch.dtype = None,
comm_group=None,
split_ratio=0.5,
):
# compute the chunk size of input
input_chunk_sizes = [int(math.ceil(input.shape[0] * split_ratio))]
input_chunk_sizes.append(input.shape[0] - input_chunk_sizes[0])
# split input and input_scale
input_chunks = list(torch.split_with_sizes(input, input_chunk_sizes, dim=0))
input_scale_chunks = torch.split(
input_scale,
input_chunk_sizes,
)
# create output and split it
if output is None:
if out_dtype is None:
raise RuntimeError(
"w8a8 gemm need out_dtype argument when output is none."
)
output = torch.empty(
(input.shape[:-1] + (weight.shape[0],)),
dtype=out_dtype,
device=input.device,
)
out_chunks = torch.split(output, input_chunk_sizes)
# overlap gemm and allreduce
for chunk_idx in range(len(input_chunks)):
# submit gemm kernel into compute stream
with self.compute_stream_context(chunk_idx):
F.w8a8(
input=input_chunks[chunk_idx],
weight=weight,
i_scales=input_scale_chunks[chunk_idx],
w_scales=weight_scale,
bias=bias,
output=out_chunks[chunk_idx],
format=format,
persistent=chunk_idx != 0,
)
# recode compute stream and wait gemm
self.start_comm(chunk_idx)
# submit allreduce kernel into communication stream by set use_comm_stream to true
ixfd.all_reduce(
out_chunks[chunk_idx],
async_op=True,
group=self.comm_group,
use_comm_stream=True,
)
return output
_w8a8_allreduce_overlap = None
def w8a8_allreduce(
enable_overlap: bool,
input: torch.Tensor,
weight: torch.Tensor,
input_scale: torch.Tensor,
weight_scale: torch.Tensor,
bias: Optional[torch.Tensor] = None,
output: Optional[torch.Tensor] = None,
format: str = "TN",
out_dtype: torch.dtype = None,
comm_group=None,
split_ratio=0.5,
) -> torch.Tensor:
"""
Gemm(w8a8) + AllReduce
Args:
enable_overlap: whether enable gemm and allreduce overlap
input: shape: [M, K], dtype: int8, linear input
weight: shape: [N, K], dtype: int8, linear weight
input_scale: shape: [M], dtype: float32, quantized scale of input
weight_scale: shape: [N], dtype: float32, quantized scale of weight
bias: shape: [N], dtype: float16 or bfloat16, linear bias
output: shape: [M, N], dtype: float16 or bfloat16, allreduce output
format: options include TN, NN and NT
out_dtype: use the argument to decide to the dtype of output when output is None
comm_group: communication group
split_ratio: split the ratio of input.shape[0] when using overlap, range: (0, 1),
it will affect area of the overlap for gemm and allreduce.
Returns: output
"""
if (
enable_overlap
and ixff_config.IXFORMER_ENABLE_OVERLAP_COMM
and dist.is_initialized()
and dist.get_world_size(comm_group) > 1
and input.shape[0] > 1
):
global _w8a8_allreduce_overlap
if _w8a8_allreduce_overlap is None:
_w8a8_allreduce_overlap = W8A8AllReduceOverlap.dispatcher(
num_chunks=2, comm_group=comm_group
).forward
return _w8a8_allreduce_overlap(
input=input,
weight=weight,
input_scale=input_scale,
weight_scale=weight_scale,
bias=bias,
output=output,
format=format,
out_dtype=out_dtype,
split_ratio=split_ratio,
)
out = F.w8a8(
input=input,
weight=weight,
i_scales=input_scale,
w_scales=weight_scale,
bias=bias,
output=output,
format=format,
out_dtype=out_dtype,
)
if dist.get_world_size() > 1:
ixfd.all_reduce(out, op=ixfd.ReduceOp.SUM, async_op=True, group=comm_group)
return out