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,44 @@
from .act_and_mul import *
from .act_bias_mm import *
from .add import *
from .bert import *
from .bnb_dequant import *
from .bnb_double_quant import *
from .bnb_mm_dequant import *
from .bnb_qgemm import *
from .bnb_quant import *
from .bnb_rowcol_absmax import *
from .conv2d import *
from .cross_entropy_loss import *
from .flash_attn import *
from .flash_attn_lib import *
from .fused_rope import *
from .gemv import *
from .groupnorm import *
from .i8w8o32 import *
from .layernorm import *
from .lightllm import *
from .linalg import *
from .linear import *
from .lmdeploy import *
from .marlin import *
from .matmul import *
from .mla_fused import *
from .mm import *
from .moe import *
from .overlap_comm import *
from .paged_attention import *
from .quantized_linear import *
from .residual_bias import *
from .rms_norm import *
from .scaled_dot_product_attention import *
from .smoothquant import *
from .softmax import *
from .store_kv_cache import *
from .t5 import *
from .tgi import *
from .vllm import *
from .w8a8 import *
from .w8a16 import *
from .wi4a16 import *
from .wui4a16 import *

View File

@@ -0,0 +1,88 @@
from typing import List, Union
import ixformer._C as ops
import torch
import torch.nn.functional as NNF
__all__ = ["ref_silu_and_mul", "ref_gelu_and_mul", "ref_gelu_tanh_and_mul",
"silu_and_mul", "gelu_and_mul", "gelu_tanh_and_mul"]
def ref_silu_and_mul(input: "torch.Tensor") -> torch.Tensor:
x1, x2 = input.chunk(chunks=2, dim=-1)
res = NNF.silu(x1) * x2
return res
def ref_gelu_and_mul(input: "torch.Tensor", gate_first=True) -> torch.Tensor:
x1, x2 = input.chunk(chunks=2, dim=-1)
if gate_first:
res = NNF.gelu(x1) * x2
else:
res = NNF.gelu(x2) * x1
return res
def ref_gelu_tanh_and_mul(input: "torch.Tensor") -> torch.Tensor:
x1, x2 = input.chunk(chunks=2, dim=-1)
res = NNF.gelu(x1) * x2
return res
def silu_and_mul(input: torch.Tensor, output: torch.Tensor = None):
"""
Args:
input: (..., 2*hidden_size) torch.float16, torch.bfloat16, torch.float32
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
"""
if output is None:
output_shape = list(input.shape)
output_shape[-1] = output_shape[-1] // 2
output = input.new_empty(output_shape)
ops.infer.silu_and_mul(input, output)
return output
def gelu_and_mul(input: "torch.Tensor", output: torch.Tensor = None, gate_first=True):
"""
Args:
input: (..., 2*hidden_size) torch.float16, torch.bfloat16, torch.float32
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
gate_first: bool
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
"""
if output is None:
output_shape = list(input.shape)
output_shape[-1] = output_shape[-1] // 2
output = input.new_empty(output_shape)
ops.infer.gelu_and_mul(input, output, gate_first)
return output
def gelu_tanh_and_mul(input: torch.Tensor, output: torch.Tensor = None):
"""
Args:
input: (..., 2*hidden_size) torch.float16, torch.bfloat16, torch.float32
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
"""
if output is None:
output_shape = list(input.shape)
output_shape[-1] = output_shape[-1] // 2
output = input.new_empty(output_shape)
ops.infer.gelu_tanh_and_mul(input, output)
return output

View File

@@ -0,0 +1,89 @@
import ixformer._C as ops
import torch
import torch.nn.functional as NNF
__all__ = ["act_bias_mm", "ref_act_bias_mm"]
def ref_act_bias_mm(
mat1: torch.Tensor,
mat2: torch.Tensor,
bias: torch.Tensor = None,
scale: float = 1,
act_type: str = "none",
trans_format: str = "NN",
):
assert len(mat1.shape) >= 2
assert len(mat2.shape) >= 2
if trans_format == "NN":
if bias is not None:
output = torch.matmul(mat1, mat2) * scale + bias
else:
output = torch.matmul(mat1, mat2) * scale
else:
if bias is not None:
output = torch.matmul(mat1, mat2.transpose(-1, -2)) * scale + bias
else:
output = torch.matmul(mat1, mat2.transpose(-1, -2)) * scale
if act_type == "gelu":
output = NNF.gelu(output)
elif act_type == "relu":
output = NNF.relu(output)
elif act_type == "silu":
output = NNF.silu(output)
elif act_type == "none":
output = output
else:
raise NotImplementedError()
return output
def act_bias_mm(
mat1: torch.Tensor,
mat2: torch.Tensor,
bias: torch.Tensor = None,
output: torch.Tensor = None,
scale: float = 1,
act_type: str = "none",
trans_format: str = "NN",
):
"""
Args:
mat1: [m,k] or [batch_count,m,k] torch.float16
mat2: [k,n] or [n,k] torch.float16
当trans_format为"NN"时[k,n], 当trans_format为"TN"时[n,k]
bias: [n] torch.float16
output: [m,n] torch.float16
scale: float
act_type: silu/gelu/relu/None str
如果act_type不为None,则bias也不可以为None
trans_format: NN or TN str
Returns:
output: [m,n] torch.float16
"""
assert len(mat1.shape) >= 2
assert len(mat2.shape) >= 2
if output is None:
output_shape = list(mat1.shape)
m = mat1.shape[-2]
if trans_format == "NN":
n = mat2.shape[-1]
else:
n = mat2.shape[-2]
output_shape[-2] = m
output_shape[-1] = n
output = mat1.new_empty(output_shape)
add_bias = False
if bias is not None:
add_bias = True
if add_bias:
ops.infer.act_bias_mm(
mat1, mat2, bias, output, add_bias, scale, act_type, trans_format
)
else:
ops.infer.act_bias_mm(
mat1, mat2, mat1, output, add_bias, scale, act_type, trans_format
)
return output

View File

@@ -0,0 +1,46 @@
import ixformer._C as ops
import torch
__all__ = [
"ref_add",
"add",
]
def ref_add(input: torch.Tensor, other: torch.Tensor, out: torch.Tensor = None):
return torch.add(input, other, out=out)
def add(input: torch.Tensor, other: torch.Tensor, out: torch.Tensor = None):
"""
out = input + other
Support elementwise addition, but broadcasting is not supported yet.
Note: The dtype of input and other needs to be the same.
Args:
input: (...) torch.float32, torch.float16, torch.bfloat16
other: (...) same as input
out: (...) same as input
Returns:
out: (...) same as input
"""
if input.dtype not in [torch.float16, torch.float32, torch.bfloat16]:
return torch.add(input, other, out=out)
if not input.is_contiguous() or not other.is_contiguous():
return torch.add(input, other, out=out)
if out is not None and not out.is_contiguous():
return torch.add(input, other, out=out)
if input.dtype != other.dtype:
return torch.add(input, other, out=out)
if out is not None and out.dtype != input.dtype:
return torch.add(input, other, out=out)
assert input.shape == other.shape, (f"broadcasting is not supported yet."
"input is {input.shape}, other is {other.shape}")
if out is None:
out = torch.empty_like(input)
ops.infer.add(input, other, out)
return out

View File

@@ -0,0 +1,199 @@
import ixformer._C as ops
import torch
__all__ = [
"ref_bert_embedding",
"bert_embedding",
"ref_bert_add_norm",
"bert_add_norm",
"ref_bert_unpack_start_end_logits",
"bert_unpack_start_end_logits",
"ref_bert_linear_residual",
"bert_linear_residual",
]
def ref_bert_embedding(
token_weight: torch.Tensor,
pos_weight: torch.Tensor,
type_weight: torch.Tensor,
ln_weight: torch.Tensor,
ln_bias: torch.Tensor,
token_ids: torch.Tensor,
pos_ids: torch.Tensor,
type_ids: torch.Tensor,
epsilon: float = 1e-5,
out: torch.Tensor = None,
):
assert out is None
emd1 = torch.nn.functional.embedding(token_ids, token_weight)
emd2 = torch.nn.functional.embedding(pos_ids, pos_weight)
emd3 = torch.nn.functional.embedding(type_ids, type_weight)
out = emd1 + emd2 + emd3
out = torch.nn.functional.layer_norm(out, [out.shape[-1]], ln_weight, ln_bias)
return out
def bert_embedding(
token_weight: torch.Tensor,
pos_weight: torch.Tensor,
type_weight: torch.Tensor,
ln_weight: torch.Tensor,
ln_bias: torch.Tensor,
token_ids: torch.Tensor,
pos_ids: torch.Tensor,
type_ids: torch.Tensor,
epsilon: float = 1e-5,
out: torch.Tensor = None,
):
"""
Args:
token_weight: (vocab_size, hidden_size) torch.float16, torch.bfloat16
pos_weight: (pos_size, hidden_size) same as token_weight
type_weight: (type_size, hidden_size) same as token_weight
ln_weight: (hidden_size) same as token_weight
ln_bias: (hidden_size) same as token_weight
token_ids: (num_tokens) torch.int32, torch.int64
pos_ids: (num_tokens) same as token_ids
type_ids: (num_tokens) same as token_ids
epsilon: float
out: (num_tokens, hidden_size) same as token_weight
Returns:
out: (num_tokens, hidden_size) same as token_weight
"""
if out is None:
out_shape = list(token_ids.shape)
hidden_size = token_weight.shape[-1]
out_shape.append(hidden_size)
out = token_weight.new_empty(out_shape)
ops.infer.bert_embedding(
token_weight,
pos_weight,
type_weight,
ln_weight,
ln_bias,
token_ids,
pos_ids,
type_ids,
out,
epsilon,
)
return out
def ref_bert_add_norm(
input: torch.Tensor,
residual: torch.Tensor,
ln_weight: torch.Tensor,
ln_bias: torch.Tensor,
epsilon: float = 1e-5,
out: torch.Tensor = None,
):
assert out is None
input = input + residual
return torch.nn.functional.layer_norm(
input, [input.shape[-1]], ln_weight, ln_bias, epsilon
)
def bert_add_norm(
input: torch.Tensor,
residual: torch.Tensor,
ln_weight: torch.Tensor,
ln_bias: torch.Tensor,
epsilon: float = 1e-5,
out: torch.Tensor = None,
):
"""
out = input + residual
out = add_norm(out, ln_weight, ln_bias, epsilon)
Args:
input: (num_tokens, hidden_size) torch.float16, torch.bfloat16
residual: (num_tokens, hidden_size) same as input
ln_weight: (hidden_size) same as input
ln_bias: (hidden_size) same as input
epsilon: float
out: (num_tokens, hidden_size) same as input
Returns:
out: (num_tokens, hidden_size) same as input
"""
if out is None:
out = torch.empty_like(input)
ops.infer.bert_add_norm(input, residual, ln_weight, ln_bias, out, epsilon)
return out
def ref_bert_unpack_start_end_logits(
logits: torch.Tensor,
cu_seq_lens: torch.Tensor,
max_seq_len: int,
start_logits: torch.Tensor = None,
end_logits: torch.Tensor = None,
):
batch_size = cu_seq_lens.shape[0] - 1
if start_logits is None:
start_logits = logits.new_empty([batch_size, max_seq_len])
if end_logits is None:
end_logits = logits.new_empty([batch_size, max_seq_len])
cu_seq_len_cpu = cu_seq_lens.detach().cpu()
for i in range(batch_size):
start_idx = cu_seq_len_cpu[i]
end_idx = cu_seq_len_cpu[i + 1]
cur_len = end_idx - start_idx
start_logits[i, :cur_len] = logits[start_idx:end_idx, 0]
end_logits[i, :cur_len] = logits[start_idx:end_idx, 1]
return start_logits, end_logits
def bert_unpack_start_end_logits(
logits: torch.Tensor,
cu_seq_lens: torch.Tensor,
max_seq_len: int,
start_logits: torch.Tensor = None,
end_logits: torch.Tensor = None,
):
"""
Args:
logits: (num_tokens, 2) torch.float16, torch.bfloat16
cu_seq_lens: (batch_size+1) torch.int32, torch.int64
max_seq_len: int
start_logits: (batch_size, max_seq_len) same as logits
end_logits: (batch_size, max_seq_len) same as logits
Returns:
start_logits: (batch_size, max_seq_len) same as logits
end_logits: (batch_size, max_seq_len) same as logits
"""
batch_size = cu_seq_lens.shape[0] - 1
if start_logits is None:
start_logits = logits.new_empty([batch_size, max_seq_len])
if end_logits is None:
end_logits = logits.new_empty([batch_size, max_seq_len])
ops.infer.bert_unpack_start_end_logits(
logits, cu_seq_lens, start_logits, end_logits
)
return start_logits, end_logits
def ref_bert_linear_residual(
input: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, out: torch.Tensor
):
return torch.nn.functional.linear(input, weight, bias) + out
def bert_linear_residual(
input: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, out: torch.Tensor
):
"""
Args:
input: (m, k) torch.float16, torch.bfloat16
weight: (n, k) same as input
bias: (n) same as input
out: (m, n) same as input
Returns:
out: (m, n) same as input
"""
ops.infer.bert_linear_residual(input, weight, bias, out)
return out

View File

@@ -0,0 +1,55 @@
from typing import List, Union
import ixformer._C as ops
import torch
from torch.autograd.function import Function, FunctionCtx
__all__ = [
"bnb_dequant",
"ref_bnb_dequant",
]
def ref_bnb_dequant(
qA: torch.Tensor,
SA: torch.Tensor,
training: bool = False,
scale: float = 127.0,
dequant_type: int = 0,
):
A = torch.empty(qA.shape, dtype = SA.dtype, device = SA.device)
if dequant_type == 0:
for i in range(qA.size(0)):
A[i:] = qA[i:] * (SA[i].to(torch.float) / scale).to(SA.dtype)
else:
for i in range(qA.size(1)):
A[:,i] = qA[:,i] * (SA[i].to(torch.float) / scale).to(SA.dtype)
return A
def bnb_dequant(
qA: torch.Tensor,
SA: torch.Tensor,
training: bool = False,
scale: float = 127.0,
dequant_type: int = 0,
) -> torch.Tensor:
"""
Args:
qA: (row, col) torch.int8
dequant input
SA: (row) or (col) torch.half
scale vector
training: bool
scale: float
dequnt_type: int
0 : every row shared a scale, SA shape : [row]
1 : every col shared a scale, SA shape : [col]
Returns:
Tensor: (row, col) torch.half
dequant output
"""
return ops.infer.bnb_dequant(qA, SA, scale, dequant_type)

View File

@@ -0,0 +1,175 @@
from typing import List, Union
import ixformer._C as ops
from torch.autograd.function import Function, FunctionCtx
__all__ = ["bnb_double_quant"]
import ctypes as ct
import torch
from torch import Tensor
def get_ptr(A):
if A is None:
return None
else:
return ct.c_void_p(A.data.data_ptr())
class COOSparseTensor:
def __init__(self, rows, cols, nnz, rowidx, colidx, values):
assert rowidx.dtype == torch.int
assert colidx.dtype == torch.int
assert values.dtype == torch.half
assert values.numel() == nnz
assert rowidx.numel() == nnz
assert colidx.numel() == nnz
self.rows = rows
self.cols = cols
self.nnz = nnz
self.rowidx = rowidx
self.colidx = colidx
self.values = values
def coo_zeros(rows, cols, nnz, device, dtype=torch.half):
rowidx = torch.full(size=(nnz,), fill_value=0, dtype=torch.int, device=device)
colidx = torch.full((nnz,), fill_value=0, dtype=torch.int, device=device)
values = torch.full((nnz,), fill_value=0, dtype=dtype, device=device)
return COOSparseTensor(rows, cols, nnz, rowidx, colidx, values)
def get_colrow_absmax(
A, row_stats=None, col_stats=None, nnz_block_ptr=None, threshold=0.0
):
cols = A.shape[-1]
if len(A.shape) == 3:
rows = A.shape[0] * A.shape[1]
else:
rows = A.shape[0]
col_tiles = (cols + 255) // 256
tiled_rows = ((rows + 15) // 16) * 16
if row_stats is None:
row_stats = torch.full(
size=(rows,), fill_value=-50000.0, dtype=torch.float, device=A.device
)
if col_stats is None:
col_stats = torch.full(
size=(cols,), fill_value=-50000.0, dtype=torch.float, device=A.device
)
# if nnz_block_ptr is None and threshold > 0.0:
nnz_block_ptr = torch.full(
size=(tiled_rows * col_tiles + 1,),
fill_value=0,
dtype=torch.int,
device=A.device,
)
ops.infer.bnb_getColRowStats(
A, row_stats, col_stats, nnz_block_ptr, threshold, rows, cols
)
return row_stats, col_stats, nnz_block_ptr
# A : quant input shape : [row, col] shape:torch.half
def bnb_double_quant(
A: torch.Tensor, training: bool = False, threshold: float = 0.0
) -> torch.Tensor:
"""
Args:
A: (row, col) torch.float16
quant input
training: bool
threshold: float
abs of element exceeds threshold will be ignored
Returns:
out_row: (row, col) torch.int8
out_col: (row, col) torch.int8
row_stats (row) torch.float
col_stats (col) torch.float
coo_tensor
"""
assert A.dtype == torch.half
cols = A.shape[-1]
if len(A.shape) == 3:
rows = A.shape[0] * A.shape[1]
else:
rows = A.shape[0]
row_stats, col_stats, nnz_row_ptr = get_colrow_absmax(A, threshold=threshold)
out_col = torch.full(size=A.shape, fill_value=0, dtype=torch.int8, device=A.device)
out_row = torch.full(size=A.shape, fill_value=0, dtype=torch.int8, device=A.device)
coo_tensor = None
if threshold > 0.0:
nnz = nnz_row_ptr.cpu().numpy()[-1]
if nnz > 0:
coo_tensor = coo_zeros(A.shape[0], A.shape[1], nnz, A.device)
ops.infer.bnb_doubleRowColQuant(
A,
row_stats,
col_stats,
out_col,
out_row,
coo_tensor.rowidx,
coo_tensor.colidx,
coo_tensor.values,
nnz_row_ptr,
threshold,
rows,
cols,
)
val, idx = torch.sort(torch.Tensor(coo_tensor.rowidx.cpu().numpy()))
coo_tensor.rowidx = val
coo_tensor.colidx = torch.Tensor(coo_tensor.colidx.cpu().numpy())[idx].to(
torch.int32
)
coo_tensor.values = torch.Tensor(coo_tensor.values.cpu().numpy())[idx].to(
torch.half
)
# coo_tensor.colidx = coo_tensor.colidx[idx]
# coo_tensor.values = coo_tensor.values[idx]
else:
ops.infer.bnb_doubleRowColQuant(
A,
row_stats,
col_stats,
out_col,
out_row,
out_row,
out_row,
out_row,
out_row,
0.0,
rows,
cols,
)
else:
ops.infer.bnb_doubleRowColQuant(
A,
row_stats,
col_stats,
out_col,
out_row,
out_row,
out_row,
out_row,
out_row,
threshold,
rows,
cols,
)
return out_row, out_col, row_stats, col_stats, coo_tensor

View File

@@ -0,0 +1,73 @@
from typing import List, Union
import ixformer._C as ops
import torch
from torch.autograd.function import Function, FunctionCtx
__all__ = ["bnb_mm_dequant"]
# A : quant input shape : [row, col] shape : torch.int
def bnb_mm_dequant(
A: torch.Tensor,
quant_state: tuple,
row_stats: torch.Tensor,
col_stats: torch.Tensor,
bias: torch.Tensor = None,
add_bias: bool = False,
training: bool = False,
) -> torch.Tensor:
"""
Args:
A: (row, col) torch.int8
quant_state: tuple
row_stats: (row) torch.float
col_stats: (col) torch.float
bias: (col) torch.half
add_bias: bool
training: bool
Returns:
Tensor: (row, col) torch.half
"""
assert A.dtype == torch.int
if bias is not None:
add_bias = True
print("bias.dtype:", bias.dtype)
assert bias.dtype == torch.half
else:
bias = A
out_shape = quant_state[0]
if len(out_shape) == 3:
out_shape = (out_shape[0] * out_shape[1], out_shape[2])
out = torch.full(size=out_shape, fill_value=0, dtype=torch.half, device=A.device)
new_row_stats = torch.full(
size=(out_shape[0],), fill_value=0, dtype=torch.float, device=A.device
)
new_col_stats = torch.full(
size=(out_shape[1],), fill_value=0, dtype=torch.float, device=A.device
)
assert (
new_row_stats.shape[0] == row_stats.shape[0]
), f"{new_row_stats.shape} vs {row_stats.shape}"
assert (
new_col_stats.shape[0] == col_stats.shape[0]
), f"{new_col_stats.shape} vs {col_stats.shape}"
numRows = out_shape[0]
numCols = out_shape[1]
ops.infer.bnb_mm_dequant(
A,
row_stats,
col_stats,
out,
new_row_stats,
new_col_stats,
numRows,
numCols,
add_bias,
bias,
)
return out

View File

@@ -0,0 +1,56 @@
from typing import List, Union
import ixformer._C as ops
import torch
__all__ = ["bnb_qgemm", "ref_bnb_qgemm"]
# qA : quant input shape : [bs, in_feature]
# qW : quant weight shape : [out_feature, in_feature]
# SA : scale vector of qA shape : [bs]
# SW : scale vector of qW shape : [out_feature]
def ref_bnb_qgemm(
qA: torch.Tensor,
qW: torch.Tensor,
SA: torch.Tensor,
SW: torch.Tensor,
training: bool = False,
scaleA: float = 127.0,
scaleW: float = 127.0,
):
y = torch.nn.functional.linear(qA.to(torch.float), qW.to(torch.float))
out = torch.empty(y.shape, dtype = SA.dtype, device = SA.device)
for i in range(qA.size(0)):
for j in range(qW.size(0)):
out[i][j] = y[i][j] * (SA[i].to(torch.float) / scaleA) * (SW[j].to(torch.float) / scaleW)
return out.to(SA.dtype)
def bnb_qgemm(
qA: torch.Tensor,
qW: torch.Tensor,
SA: torch.Tensor,
SW: torch.Tensor,
training: bool = False,
scaleA: float = 127.0,
scaleW: float = 127.0,
) -> torch.Tensor:
"""
Args:
qA: (bs, in_feature) torch.int8
qW: (out_feature, in_feature) torch.int8
SA: (bs) torch.half
scale vector of qA
SA: (out_feature) torch.half
scale vector of qW
training: bool
scaleA: float
scaleW: float
Returns:
Tensor: (bs, out_feature) torch.half
"""
return ops.infer.bnb_qgemm(qA, qW, SA, SW, scaleA, scaleW)

View File

@@ -0,0 +1,58 @@
from typing import List, Union
import ixformer._C as ops
import torch
__all__ = ["bnb_quant", "ref_bnb_quant"]
# A : input shape : [row, col]
# SA : scale vector
# quant_type
# 0 : every row shared a scale, SA shape : [row]
# 1 : every col shared a scale, SA shape : [col]
def ref_bnb_quant(
A: torch.Tensor,
SA: torch.Tensor,
training: bool = False,
scale: float = 127.0,
quant_type: int = 0,
):
qA = torch.empty(A.shape, device = SA.device)
if quant_type == 0:
for i in range(A.size(0)):
qA[i:] = torch.round(A[i:] * (scale / SA[i].to(torch.float)))
else:
for i in range(A.size(1)):
qA[:,i] = torch.round(A[:,i] * (scale / SA[i].to(torch.float)))
qA_clamped = torch.clamp(qA, min=-128, max=127)
qA = qA_clamped.to(torch.int8)
return qA
def bnb_quant(
A: torch.Tensor,
SA: torch.Tensor,
training: bool = False,
scale: float = 127.0,
quant_type: int = 0,
) -> torch.Tensor:
"""
Args:
A: (row, col) torch.half
quant input
SA: (row) or (col) torch.half
scale vector
training: bool
scale: float
qunt_type: int
0 : every row shared a scale, SA shape : [row]
1 : every col shared a scale, SA shape : [col]
Returns:
Tensor: (row, col) torch.int8
quant output
"""
return ops.infer.bnb_quant(A, SA, scale, quant_type)

View File

@@ -0,0 +1,52 @@
from typing import List, Union
import ixformer._C as ops
import torch
__all__ = ["bnb_rowcol_absmax", "ref_bnb_rowcol_absmax"]
# input : input shape : [row, col]
# threshold : abs of element exceeds threshold will be ignored
# type
# 0 : row absmax
def ref_bnb_rowcol_absmax(
input: torch.Tensor,
training: bool = False,
threshold: float = 0.0,
type: int = 0,
):
input = input.float()
if threshold ==0.0:
threshold = float('inf')
mask = (torch.abs(input) < threshold)
masked_input = mask * input
masked_input = masked_input.half()
if type == 0:
out = torch.amax(torch.abs(masked_input), dim=1)
else:
out = torch.amax(torch.abs(masked_input), dim=0)
return out
def bnb_rowcol_absmax(
input: torch.Tensor,
training: bool = False,
threshold: float = 0.0,
type: int = 0,
) -> torch.Tensor:
"""
Args:
input: (row, col) torch.half
目前col值必须满足col%2==0
training: bool
threshold: float
abs of element exceeds threshold will be ignored
type: int
row absmax, 目前只支持type=0
Returns:
Tensor: (row) torch.half
"""
return ops.infer.bnb_rowcol_absmax(input, threshold, type)

View File

@@ -0,0 +1,198 @@
from typing import Union
import ixformer._C as ops
import torch
import torch.nn.functional as NNF
__all__ = ["conv2d", "ref_conv2d", "ref_conv2d_nhwc", "conv2d_nhwc"]
def is_channels_last(ten):
return torch._prims_common.suggest_memory_format(ten) == torch.channels_last
def _pair(x):
if isinstance(x, (list, tuple)):
return x
return (x, x)
def ref_conv2d(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
stride: Union[int, tuple] = 1,
padding: Union[int, tuple] = 0,
dilation: Union[int, tuple] = 1,
groups: int = 1,
):
output = NNF.conv2d(input, weight, bias, stride, padding, dilation, groups)
return output
# conv2d官方接口如果weight是torch.channels_last,输出也是torch.channels_last如果weight是nchw那么输出也是nchw;特殊情况如果输入是nchwweight是torch.channels_last输出也是torch.channels_last
def conv2d(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
stride: Union[int, tuple] = 1,
padding: Union[int, tuple] = 0,
dilation: Union[int, tuple] = 1,
groups: int = 1,
):
"""
Args:
input: (n,in_c,h,w) torch.float16
weight: (out_c,in_c/groups,kH,kW) torch.float16
bias: (out_c) torch.float16
stride: int or tuple
Stride of the convolution. Default: 1
padding: int or tuple
Padding added to all four sides of the input. Default: 0
dilation: int or tuple
Spacing between kernel elements. Default: 1
groups: int
Number of blocked connections from input channels to output channels. Default: 1
Returns:
Tensor: (n,out_c,h_out,w_out) torch.float16
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) / stride_h + 1;
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) / stride_w + 1;
"""
stride = _pair(stride)
padding = _pair(padding)
dilation = _pair(dilation)
channel_last = is_channels_last(weight)
if not is_channels_last(input) and channel_last:
input = input.to(memory_format=torch.channels_last)
# compute outshape
n, in_c, h_in, w_in = input.shape
out_c, _, kernel_h, kernel_w = weight.shape
pad_h = padding[0]
pad_w = padding[1]
stride_h = stride[0]
stride_w = stride[1]
dilation_h = dilation[0]
dilation_w = dilation[1]
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) // stride_h + 1
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) // stride_w + 1
if channel_last:
output_shape = [n, out_c, h_out, w_out]
output = torch.empty(
output_shape,
memory_format=torch.channels_last,
dtype=input.dtype,
device=input.device,
)
else:
output_shape = [n, out_c, h_out, w_out]
output = input.new_empty(output_shape)
if channel_last:
input = input.permute(0, 2, 3, 1)
weight = weight.permute(0, 2, 3, 1)
output = output.permute(0, 2, 3, 1)
if bias is not None:
bias = bias.float()
ops.infer.conv2d(
input, weight, bias, output, stride, padding, dilation, groups, channel_last
)
if channel_last:
output = output.permute(0, 3, 1, 2)
return output
def ref_conv2d_nhwc(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
stride: Union[int, tuple] = 1,
padding: Union[int, tuple] = 0,
dilation: Union[int, tuple] = 1,
groups: int = 1,
):
output = NNF.conv2d(
input.permute(0, 3, 1, 2).contiguous(),
weight.permute(0, 3, 1, 2).contiguous(),
bias,
stride,
padding,
dilation,
groups,
)
return output.permute(0, 2, 3, 1).contiguous()
# conv2d_nhwc
# conv2d官方接口解决两种情况
# 1、务必输入tensor内存上是nhwc,且tensor属于memory_format=torch.channels_last
# 2、或者输入tensor内存上是nchw并且是contiguous
# conv2d官方接口不能解决conv2d_nhwc则可处理这种情况的
# 输入tensor内存上是nhwc的但tensor没有用memory_format=torch.channels_last进行过处理不会有memory_format=torch.channels_last的标签
def conv2d_nhwc(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
stride: Union[int, tuple] = 1,
padding: Union[int, tuple] = 0,
dilation: Union[int, tuple] = 1,
groups: int = 1,
):
"""
Args:
input: (n,h,w,in_c) torch.float16
weight: (out_c,kH,kW,in_c/groups) torch.float16
bias: (out_c) torch.float16
stride: int or tuple
Stride of the convolution. Default: 1
padding: int or tuple
Padding added to all four sides of the input. Default: 0
dilation: int or tuple
Spacing between kernel elements. Default: 1
groups: int
Number of blocked connections from input channels to output channels. Default: 1
Returns:
Tensor: (n,h_out,w_out,out_c) torch.float16
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) / stride_h + 1;
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) / stride_w + 1;
"""
stride = _pair(stride)
padding = _pair(padding)
dilation = _pair(dilation)
assert input.is_contiguous()
assert weight.is_contiguous()
# compute outshape
n, h_in, w_in, in_c = input.shape
(
out_c,
kernel_h,
kernel_w,
_,
) = weight.shape
pad_h = padding[0]
pad_w = padding[1]
stride_h = stride[0]
stride_w = stride[1]
dilation_h = dilation[0]
dilation_w = dilation[1]
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) // stride_h + 1
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) // stride_w + 1
output_shape = [n, h_out, w_out, out_c]
output = torch.empty(output_shape, dtype=input.dtype, device=input.device)
if bias is not None:
bias = bias.float()
ops.infer.conv2d(
input, weight, bias, output, stride, padding, dilation, groups, True
)
return output

View File

@@ -0,0 +1,204 @@
from typing import Union
import ixformer._C as ops
import torch
__all__ = ["vocab_parallel_cross_entropy", "ref_vocab_parallel_cross_entropy"]
def ref_vocab_parallel_cross_entropy(
vocab_parallel_logits: torch.Tensor,
target: torch.Tensor,
label_smoothing: float = 0.0,
world_size: int = 1,
vocab_start_index: int = 0,
vocab_end_index: int = 320000,
group=None,
):
if world_size == 1:
vocab_parallel_logits = vocab_parallel_logits.float()
partition_vocab_size = vocab_parallel_logits.size()[-1]
logits_max = torch.max(vocab_parallel_logits, dim=-1)[0]
vocab_parallel_logits = vocab_parallel_logits - logits_max.unsqueeze(dim=-1)
target_mask = (target < vocab_start_index) | (target >= vocab_end_index)
masked_target = target.clone() - vocab_start_index
masked_target[target_mask] = 0
logits_2d = vocab_parallel_logits.view(-1, partition_vocab_size)
masked_target_1d = masked_target.view(-1)
arange_1d = torch.arange(
start=0, end=logits_2d.size()[0], device=logits_2d.device
)
predicted_logits_1d = logits_2d[arange_1d, masked_target_1d]
predicted_logits_1d = predicted_logits_1d.clone().contiguous()
predicted_logits = predicted_logits_1d.view_as(target)
predicted_logits[target_mask] = 0.0
exp_logits = vocab_parallel_logits
torch.exp(vocab_parallel_logits, out=exp_logits)
sum_exp_logits = exp_logits.sum(dim=-1)
loss = torch.log(sum_exp_logits) - predicted_logits
exp_logits.div_(sum_exp_logits.unsqueeze(dim=-1))
if label_smoothing > 0:
"""
We'd like to assign 1 / (K - 1) probability mass to every index that is not the ground truth.
= (1 - alpha) * y_gt + alpha * mean(y_{i for i != gt})
= (1 - alpha) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
= ((K - 1) * (1 - alpha) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
= (K * (1 - alpha) - 1) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i} y_i
= (1 - (alpha * K) / (K - 1)) * y_gt + ( (alpha * K) / (K - 1) ) * \sum_{i} y_i / K
From: https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/common/losses/smoothed_cross_entropy.py
"""
assert 1.0 > label_smoothing > 0.0
smoothing = label_smoothing * partition_vocab_size / (partition_vocab_size - 1)
# Exp logits at this point are normalized probabilities. So we can just take the log to get log-probs.
log_probs = torch.log(exp_logits)
mean_log_probs = log_probs.mean(dim=-1)
loss = (1.0 - smoothing) * loss - smoothing * mean_log_probs
else:
# Maximum value along vocab dimension across all GPUs.
logits_max = torch.max(vocab_parallel_logits, dim=-1)[0]
torch.distributed.all_reduce(
logits_max, op=torch.distributed.ReduceOp.MAX, group=group
)
# Subtract the maximum value.
vocab_parallel_logits = vocab_parallel_logits - logits_max.unsqueeze(dim=-1)
# Get the partition's vocab indecies
partition_vocab_size = vocab_parallel_logits.size()[-1]
# Create a mask of valid vocab ids (1 means it needs to be masked).
target_mask = (target < vocab_start_index) | (target >= vocab_end_index)
masked_target = target.clone() - vocab_start_index
masked_target[target_mask] = 0
# Get predicted-logits = logits[target].
# For Simplicity, we convert logits to a 2-D tensor with size
# [*, partition-vocab-size] and target to a 1-D tensor of size [*].
logits_2d = vocab_parallel_logits.view(-1, partition_vocab_size)
masked_target_1d = masked_target.view(-1)
arange_1d = torch.arange(
start=0, end=logits_2d.size()[0], device=logits_2d.device
)
predicted_logits_1d = logits_2d[arange_1d, masked_target_1d]
predicted_logits_1d = predicted_logits_1d.clone().contiguous()
predicted_logits = predicted_logits_1d.view_as(target)
predicted_logits[target_mask] = 0.0
# All reduce is needed to get the chunks from other GPUs.
torch.distributed.all_reduce(
predicted_logits,
op=torch.distributed.ReduceOp.SUM,
group=group,
)
# Sum of exponential of logits along vocab dimension across all GPUs.
exp_logits = vocab_parallel_logits
torch.exp(vocab_parallel_logits, out=exp_logits)
sum_exp_logits = exp_logits.sum(dim=-1)
torch.distributed.all_reduce(
sum_exp_logits,
op=torch.distributed.ReduceOp.SUM,
group=group,
)
# Loss = log(sum(exp(logits))) - predicted-logit.
loss = torch.log(sum_exp_logits) - predicted_logits
# Normalize and optionally smooth logits
exp_logits.div_(sum_exp_logits.unsqueeze(dim=-1))
vocab_size = exp_logits.size(-1)
if label_smoothing > 0:
"""
We'd like to assign 1 / (K - 1) probability mass to every index that is not the ground truth.
= (1 - alpha) * y_gt + alpha * mean(y_{i for i != gt})
= (1 - alpha) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
= ((K - 1) * (1 - alpha) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
= (K * (1 - alpha) - 1) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i} y_i
= (1 - (alpha * K) / (K - 1)) * y_gt + ( (alpha * K) / (K - 1) ) * \sum_{i} y_i / K
From: https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/common/losses/smoothed_cross_entropy.py
"""
assert 1.0 > label_smoothing > 0.0
smoothing = label_smoothing * vocab_size / (vocab_size - 1)
# Exp logits at this point are normalized probabilities. So we can just take the log to get log-probs.
log_probs = torch.log(exp_logits)
mean_log_probs = log_probs.mean(dim=-1)
loss = (1.0 - smoothing) * loss - smoothing * mean_log_probs
return loss
def vocab_parallel_cross_entropy(
vocab_parallel_logits: torch.Tensor,
target: torch.Tensor,
label_smoothing: float = 0.0,
world_size: int = 1,
vocab_start_index: int = 0,
vocab_end_index: int = 320000,
group=None,
):
"""
Args:
vocab_parallel_logits: (seq_len,1,vocal_size) torch.float16, torch.bfloat16, torch.float
target: (seq_len,1) torch.int64
label_smoothing: float
默认为0.0. 用于标签平滑
world_size: int
当world_size = 1时,目前只支持batch_size = 1 的情况
vocab_start_index: int
vocab_end_index: int
group:
TP 并行组
Returns:
loss: (seq_len,1) torch.float
"""
if world_size == 1:
device = vocab_parallel_logits.device
xnumel = vocab_parallel_logits.shape[0]
rnumel = vocab_parallel_logits.shape[-1]
exp_logits = torch.empty(
(xnumel, 1, rnumel), device=device, dtype=torch.float32
)
masked_target_1d = torch.empty((xnumel,), device=device, dtype=torch.int32)
loss = torch.empty((xnumel, 1), device=device, dtype=torch.float32)
ops.train.cross_entropy_loss_forward(
vocab_parallel_logits, target.int(), exp_logits, masked_target_1d, loss
)
vocab_size = exp_logits.size(-1)
if label_smoothing > 0:
"""
We'd like to assign 1 / (K - 1) probability mass to every index that is not the ground truth.
= (1 - alpha) * y_gt + alpha * mean(y_{i for i != gt})
= (1 - alpha) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
= ((K - 1) * (1 - alpha) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
= (K * (1 - alpha) - 1) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i} y_i
= (1 - (alpha * K) / (K - 1)) * y_gt + ( (alpha * K) / (K - 1) ) * \sum_{i} y_i / K
From: https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/common/losses/smoothed_cross_entropy.py
"""
assert 1.0 > label_smoothing > 0.0
smoothing = label_smoothing * vocab_size / (vocab_size - 1)
# Exp logits at this point are normalized probabilities. So we can just take the log to get log-probs.
log_probs = torch.log(exp_logits)
mean_log_probs = log_probs.mean(dim=-1)
loss = (1.0 - smoothing) * loss - smoothing * mean_log_probs
# Store softmax, target-mask and masked-target for backward pass.
return loss
else:
loss = ref_vocab_parallel_cross_entropy(
vocab_parallel_logits,
target,
label_smoothing,
world_size,
vocab_start_index,
vocab_end_index,
group,
)
return loss

View File

@@ -0,0 +1,201 @@
import math
from typing import List, Union
import ixformer._C as ops
import torch
from torch.autograd.function import Function, FunctionCtx
__all__ = [
"ixinfer_flash_attn_unpad",
"ixinfer_flash_attn_pad",
"ref_ixinfer_flash_attn_pad",
]
def ixinfer_flash_attn_unpad(
# total_q x num_heads x head_size, total_q := \sum_{i=0}^{b} s_i
q: "torch.Tensor",
# total_k x num_heads_k x head_size, total_k := \sum_{i=0}^{b} s_i
k: "torch.Tensor",
# total_k x num_heads_k x head_size, total_k := \sum_{i=0}^{b} s_i
v: "torch.Tensor",
# total_q x num_heads x head_size, total_k := \sum_{i=0}^{b} s_i
cu_seqlens_q: "torch.Tensor", # b+1
cu_seqlens_k: "torch.Tensor", # b+1
max_seqlen_q: int,
max_seqlen_k: int,
is_causal: bool = False,
atten_scale: float = None,
sqrt_alibi: bool = False,
# total_q x num_heads x head_size, total_k := \sum_{i=0}^{b} s_i
alibi_slopes: "torch.Tensor" = None,
out: "torch.Tenosr" = None,
):
"""
Args:
q: (total_q, nheads, headdim) torch.float16, torch.bfloat16
where total_q = total number of query tokens in the batch.
k: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
where total_k = total number of key tokens in the batch.
v: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
cu_seqlens_q: (batch_size + 1) torch.int32
The cumulative sequence lengths of the sequences in the batch, used to index into q.
cu_seqlens_k: (batch_size + 1) torch.int32
The cumulative sequence lengths of the sequences in the batch, used to index into kv.
max_seqlen_q: int
Maximum query sequence length in the batch.
max_seqlen_k: int
Maximum key sequence length in the batch.
atten_scale: float
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
is_causal: bool
Whether to apply causal attention mask (e.g., for auto-regressive modeling).
sqrt_alibi: bool
Whether to apply abilimode
out: (total, nheads, headdim) torch.float16, torch.bfloat16
Returns:
out: (total, nheads, headdim) torch.float16, torch.bfloat16
if not q.size(-1) % 32 == 0: out shape is (total_q, nheads, q.size(-1) + (32 - q.size(-1) % 32))
"""
if atten_scale is None:
atten_scale = 1.0 / (q.size(-1) ** 0.5)
# 判断是否pad
cur_head = q.size(-1)
cur_head32 = cur_head
if not cur_head % 32 == 0:
cur_head32 = cur_head + (32 - cur_head % 32)
q_infer = torch.nn.functional.pad(q, [0, cur_head32 - cur_head, 0, 0], value=0)
k_infer = torch.nn.functional.pad(k, [0, cur_head32 - cur_head, 0, 0], value=0)
v_infer = torch.nn.functional.pad(v, [0, cur_head32 - cur_head, 0, 0], value=0)
else:
q_infer = q
k_infer = k
v_infer = v
if out is None:
out = torch.empty_like(q_infer)
# ixinfer 新接口版
ops.infer.ixinfer_flash_attn_unpad(
q_infer,
k_infer,
v_infer,
out,
cu_seqlens_q,
cu_seqlens_k,
max_seqlen_q,
max_seqlen_k,
is_causal,
False, # need_lse =False
atten_scale,
sqrt_alibi,
alibi_slopes,
)
if not cur_head % 32 == 0:
out = out[:, :, :cur_head]
return out
def ref_ixinfer_flash_attn_pad(
# [ batch num_heads seq_q head_size]
q: torch.Tensor,
# [ batch num_heads_k max_seq_kv head_size]
k: torch.Tensor,
# [ batch num_heads_k max_seq_kv head_size]
v: torch.Tensor,
# [ batch num_heads seq_q seq_kv] seq_kv<=max_seq_kv
mask: torch.Tensor,
# [ batch num_heads seq_q head_size]
atten_scale: float = None,
kv_seq_start: int = None,
kv_seq_end: int = None,
):
head_dim = q.size(-1)
k_effective = k[:, :, kv_seq_start:kv_seq_end, :]
v_effective = v[:, :, kv_seq_start:kv_seq_end, :]
# 2. q*kt softmax
scores_qk = (
torch.matmul(q.float(), k_effective.float().transpose(-2, -1)) * atten_scale
)
# softmax
# print(scores_qk.shape,mask.shape)
if mask is not None:
if mask.dtype == torch.int32:
scores_qk = scores_qk + mask * (-100000)
elif mask.dtype == torch.float32:
scores_qk = scores_qk + mask
else:
print(
f"mask dtype is not surported {mask.dtype},now surport int32 and float32"
)
scores_qk = torch.nn.functional.softmax(scores_qk, dim=-1)
# 3. x = qk_scores * v
scores_v = torch.matmul(scores_qk, v_effective.float())
return scores_v.half()
def ixinfer_flash_attn_pad(
# [ batch num_heads seq_q head_size]
q: torch.Tensor,
# [ batch num_heads_k max_seq_kv head_size]
k: torch.Tensor,
# [ batch num_heads_k max_seq_kv head_size]
v: torch.Tensor,
# [ batch num_heads seq_q seq_kv] seq_kv<=max_seq_kv
mask: torch.Tensor,
# [ batch num_heads seq_q head_size]
atten_scale: float = None,
kv_seq_start: int = None,
kv_seq_end: int = None,
):
"""
Args:
q: (batch_size, num_head, seq_len_q, head_dim) torch.float16, torch.bfloat16
k: (batch_size, num_head_kv, seq_len_kv, head_dim) torch.float16, torch.bfloat16
v: (batch_size, num_head_kv, seq_len_kv, head_dim) torch.float16, torch.bfloat16
mask: (batch_size, num_head, seq_len_q, kv_seq_start:kv_seq_end) torch.int32, torch.int64, torch.float32
atten_scale: float
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
kv_seq_start: int
kv sequence start index used for computation in the batch
kv_seq_end: int
kv sequence end index used for computation in the batch.
Returns:
out: (batch_size, num_head, seq_len_q, head_dim) torch.float16, torch.bfloat16
"""
# 判断是否pad
cur_head = q.size(-1)
cur_head32 = cur_head
if not cur_head % 32 == 0:
cur_head32 = cur_head + (32 - cur_head % 32)
q_infer = torch.nn.functional.pad(q, [0, cur_head32 - cur_head, 0, 0], value=0)
k_infer = torch.nn.functional.pad(k, [0, cur_head32 - cur_head, 0, 0], value=0)
v_infer = torch.nn.functional.pad(v, [0, cur_head32 - cur_head, 0, 0], value=0)
else:
q_infer = q
k_infer = k
v_infer = v
if atten_scale is None:
atten_scale = 1.0 / (q.size(-1) ** 0.5)
if kv_seq_start is None or kv_seq_end is None:
kv_seq_start = 0
kv_seq_end = k.size(-2) # kv seq len
elif kv_seq_start < 0 or kv_seq_end > k.size(-2) or kv_seq_start >= kv_seq_end:
raise NotImplementedError(
"must kv_seq_start<0 or kv_seq_end>k.size(-2) or kv_seq_start>=kv_seq_end!"
)
out_shape = list(q_infer.shape)
out = torch.empty(out_shape, dtype=q.dtype, device=q.device)
if mask is not None:
ops.infer.ixinfer_flash_attn_pad_fwd(
q_infer, k_infer, v_infer, mask, out, atten_scale, kv_seq_start, kv_seq_end
)
else:
ops.infer.ixinfer_flash_attn_pad_fwd_nomask(
q_infer, k_infer, v_infer, out, atten_scale, kv_seq_start, kv_seq_end
)
if not cur_head % 32 == 0:
out = out[:, :, :, :cur_head]
return out

View File

@@ -0,0 +1,350 @@
import math
from typing import List, Union
import ixformer._C as ops
import torch
from .flash_attn import ixinfer_flash_attn_unpad
__all__ = [
"flash_attn_varlen_func",
"ref_flash_attn_varlen_func",
"flash_attn_func",
"ref_flash_attn_func",
]
def ref_flash_attn_varlen_func(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
cu_seqlens_q: torch.Tensor,
cu_seqlens_k: torch.Tensor,
max_seqlen_q: int,
max_seqlen_k: int,
dropout_p: float = 0.0,
softmax_scale: float = None,
causal: bool = False,
return_attn_probs: bool = False,
):
if return_attn_probs:
raise NotImplementedError("return_attn_probs not supported!")
out = torch.zeros_like(q)
unpad_causal_torch(
q,
k,
v,
out,
cu_seqlens_q,
cu_seqlens_k,
max_seqlen_q,
max_seqlen_k,
torch.float16,
softmax_scale,
causal,
)
return out
def unpad_causal_torch(
q,
k,
v,
output,
cu_seqlens_q,
cu_seqlens_k,
max_seq_len_q,
max_seq_len_kv,
dtype,
atten_scale,
is_causal=True,
):
head_num = q.size(1)
head_num_kv = k.size(1)
head_dim = q.size(2)
assert head_num % head_num_kv == 0
if atten_scale == None:
atten_scale = 1.0 / (q.size(-1) ** 0.5)
# tokens,head_num,head_dim
if head_num != head_num_kv:
# k = k.repeat(1, head_num//head_num_kv, 1)#[0,1,2,0,1,2,0,1,2,0,1,2]
# v = v.repeat(1, head_num//head_num_kv, 1)
k = repeat_kv(k, head_num // head_num_kv) # [0,0,0,0,1,1,1,1,2,2,2,2] GROUP
v = repeat_kv(v, head_num // head_num_kv)
batch_size = cu_seqlens_q.size(0) - 1
for i in range(batch_size):
q_start_index = cu_seqlens_q[i]
q_end_index = cu_seqlens_q[i + 1]
cur_q_len = q_end_index - q_start_index
# 1*seq_len,head_num,head_dim
cur_q = q[q_start_index:q_end_index]
k_start_index = cu_seqlens_k[i]
k_end_index = cu_seqlens_k[i + 1]
cur_k_len = k_end_index - k_start_index
cur_k = k[k_start_index:k_end_index]
cur_v = v[k_start_index:k_end_index]
# mask = torch.tril(torch.ones([cur_q_len, cur_k_len], dtype=torch.bool)).cuda()
# mask = mask.unsqueeze(0).unsqueeze(0)
if is_causal:
# Create attention mask.
attn_mask = torch.triu(
torch.ones(cur_q_len, cur_k_len, dtype=dtype), diagonal=1
)
attn_mask = attn_mask * torch.finfo(dtype).min
attn_mask = attn_mask.to(dtype=dtype, device="cuda")
else:
attn_mask = None
ref_output = ref_masked_attention(
cur_q,
cur_k,
cur_v,
atten_scale,
attn_mask=attn_mask,
)
output[q_start_index:q_end_index].copy_(ref_output)
def ref_masked_attention(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
scale: float,
attn_mask=None,
) -> torch.Tensor:
query = query * scale
dtype = query.dtype
device = query.device
query = query.to(torch.float32).cpu()
key = key.to(torch.float32).cpu()
value = value.to(torch.float32).cpu()
attn = torch.einsum("qhd,khd->hqk", query, key)
if attn_mask is not None:
attn_mask = attn_mask.cpu()
attn = attn + attn_mask
attn = torch.softmax(attn, dim=-1)
out = torch.einsum("hqk,khd->qhd", attn, value)
out = out.to(device).to(dtype)
return out
def flash_attn_varlen_func(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
cu_seqlens_q: torch.Tensor,
cu_seqlens_k: torch.Tensor,
max_seqlen_q: int,
max_seqlen_k: int,
dropout_p: float = 0.0,
softmax_scale: float = None,
causal: bool = False,
return_attn_probs: bool = False,
out: torch.Tensor = None,
):
"""
Args:
q: (total_q, nheads, headdim) torch.float16, torch.bfloat16
where total_q = total number of query tokens in the batch.
k: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
where total_k = total number of key tokens in the batch.
v: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
cu_seqlens_q: (batch_size + 1) torch.int32
The cumulative sequence lengths of the sequences in the batch, used to index into q.
cu_seqlens_k: (batch_size + 1) torch.int32
The cumulative sequence lengths of the sequences in the batch, used to index into kv.
max_seqlen_q: int
Maximum query sequence length in the batch.
max_seqlen_k: int
Maximum key sequence length in the batch.
dropout_p: float
Dropout probability. dropout_p should be set to 0.0 during evaluation
softmax_scale: float
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
causal: bool
Whether to apply causal attention mask (e.g., for auto-regressive modeling).
return_attn_probs: bool
Whether to return the attention probabilities. This option is for testing only. The returned probabilities are not guaranteed to be correct (they might not have the right scaling).
out: (total, nheads, headdim) torch.float16, torch.bfloat16
Returns:
out: (total, nheads, headdim) torch.float16, torch.bfloat16
"""
assert len(q.shape) == 3, "q.shape != [total_q, nheads, head_dim]"
assert len(k.shape) == 3, "k.shape != [total_k, nheads_k, head_dim]"
assert len(v.shape) == 3, "v.shape != [total_k, nheads_k, head_dim]"
assert len(cu_seqlens_q.shape) == 1, "cu_seqlens_q.shape != [batch_size+1]"
assert len(cu_seqlens_k.shape) == 1, "cu_seqlens_k.shape != [batch_size+1]"
if return_attn_probs:
raise NotImplementedError("return_attn_probs not supported!")
atten_scale = softmax_scale
training = q.requires_grad
nheads = q.size(1)
nheads_k = k.size(1)
if training:
raise NotImplementedError("not support training!")
else: # 推理支持group query attention
assert nheads % nheads_k == 0
return ixinfer_flash_attn_unpad(
q,
k,
v,
cu_seqlens_q,
cu_seqlens_k,
max_seqlen_q,
max_seqlen_k,
causal,
atten_scale,
out=out,
)
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
"""torch.repeat_interleave(x, dim=2, repeats=n_rep)"""
if len(x.shape) == 4:
batch, seq_len, n_kv_heads, head_dim = x.shape
elif len(x.shape) == 3:
tokens, n_kv_heads, head_dim = x.shape
if n_rep == 1:
return x
if len(x.shape) == 4:
return (
x[:, :, :, None, :]
.expand(batch, seq_len, n_kv_heads, n_rep, head_dim)
.reshape(batch, seq_len, n_kv_heads * n_rep, head_dim)
)
elif len(x.shape) == 3:
return (
x[:, :, None, :]
.expand(tokens, n_kv_heads, n_rep, head_dim)
.reshape(tokens, n_kv_heads * n_rep, head_dim)
)
def mha(q, k, v, atten_scale, is_causal):
q = q.permute(0, 2, 1, 3).contiguous() # batch num_head seq_len head_dim
k = k.permute(0, 2, 1, 3).contiguous()
v = v.permute(0, 2, 1, 3).contiguous()
# 2. q*kt softmax
scores_qk = torch.matmul(q.float(), k.float().transpose(-2, -1)) * atten_scale
q_seq_len = q.size(2)
kv_seq_len = k.size(2)
if is_causal:
# Create attention mask.
attn_mask = torch.triu(
torch.ones(q_seq_len, kv_seq_len, dtype=torch.int), diagonal=1
)
attn_mask = attn_mask.to(dtype=torch.int, device="cuda")
else:
attn_mask = None
# softmax
# print(scores_qk.shape,attn_mask.shape)
if attn_mask is not None:
# print(scores_qk.shape,attn_mask.shape)
scores_qk = scores_qk + attn_mask * (-100000)
scores_qk = torch.nn.functional.softmax(scores_qk, dim=-1)
# 3. x = qk_scores * v
scores_v = torch.matmul(scores_qk, v.float())
scores_v = scores_v.half()
return scores_v.permute(0, 2, 1, 3).contiguous()
def ref_flash_attn_func(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
dropout_p: float = 0.0,
softmax_scale: float = None,
causal: bool = False,
return_attn_probs: bool = False,
):
if return_attn_probs:
raise NotImplementedError("return_attn_probs not supported!")
head_num = q.size(2)
head_num_kv = k.size(2)
if head_num != head_num_kv:
k = repeat_kv(k, head_num // head_num_kv) # [0,0,0,0,1,1,1,1,2,2,2,2] GROUP
v = repeat_kv(v, head_num // head_num_kv)
output_pt = mha(q, k, v, softmax_scale, causal)
return output_pt
def flash_attn_func(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
dropout_p: float = 0.0,
softmax_scale: float = None,
causal: bool = False,
return_attn_probs: bool = False,
):
"""
Args:
q: (batch_size, seqlen, nheads, headdim) torch.float16, torch.bfloat16
k: (batch_size, seqlen, nheads_k, headdim) torch.float16, torch.bfloat16
v: (batch_size, seqlen, nheads_k, headdim) torch.float16, torch.bfloat16
dropout_p: float
Dropout probability. dropout_p should be set to 0.0 during evaluation
softmax_scale: float
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
causal: bool
Whether to apply causal attention mask (e.g., for auto-regressive modeling).
return_attn_probs: bool
Whether to return the attention probabilities. This option is for testing only. The returned probabilities are not guaranteed to be correct (they might not have the right scaling).
Returns:
Tensor: (total, nheads, headdim) torch.float16, torch.bfloat16
"""
if return_attn_probs:
raise NotImplementedError("return_attn_probs not supported!")
atten_scale = softmax_scale
training = q.requires_grad
q_dim = q.dim()
assert q_dim == 4
batch_size, max_seqlen_q, nheads, head_dim = q.shape
_, max_seqlen_k, nheads_k, head_dim_k = k.shape
assert head_dim == head_dim_k
if training:
raise NotImplementedError("not support training!")
else: # 推理支持group query attention
assert nheads % nheads_k == 0
q = q.view(batch_size * max_seqlen_q, nheads, head_dim)
k = k.view(batch_size * max_seqlen_k, nheads_k, head_dim)
v = v.view(batch_size * max_seqlen_k, nheads_k, head_dim)
cu_seqlens_q = torch.ones([batch_size + 1]) * max_seqlen_q
cu_seqlens_q[0] = 0
cu_seqlens_k = torch.ones([batch_size + 1]) * max_seqlen_k
cu_seqlens_k[0] = 0
cu_seqlens_q = cu_seqlens_q.cuda().int()
cu_seqlens_k = cu_seqlens_k.cuda().int()
cu_seqlens_q = torch.cumsum(cu_seqlens_q, dim=0).int()
cu_seqlens_k = torch.cumsum(cu_seqlens_k, dim=0).int()
output = ixinfer_flash_attn_unpad(
q,
k,
v,
cu_seqlens_q,
cu_seqlens_k,
max_seqlen_q,
max_seqlen_k,
causal,
atten_scale,
sqrt_alibi=False,
alibi_slopes=None,
)
return output.view(batch_size, max_seqlen_q, nheads, head_dim)

View File

@@ -0,0 +1,76 @@
from typing import List, Tuple, Union
import ixformer._C as ops
import torch
# adding by xuelu.peng 20240417
# from https://github.com/NVIDIA/apex/blob/master/apex/transformer/functional/fused_rope.py#L59
__all__ = ["fused_apply_rotary_pos_emb", "ref_fused_apply_rotary_pos_emb"]
# Copied from Megatron-Core for testing.
# https://github.com/NVIDIA/Megatron-LM/blob/5f2877d85cb26e47ce6dcdae4b80adf376abf4e8/megatron/core/models/common/embeddings/rotary_pos_embedding.py#L139
def apply_rotary_pos_emb(t: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
"""Apply rotary positional embedding to input tensor T.
check https://kexue.fm/archives/8265 for detailed formulas
Arguments:
t (Tensor): Input tensor T is of shape [seq_length, ... , dim]
freqs (Tensor): Rotary Positional embedding tensor freq is of shape [seq_length, ..., dim]
Returns:
Tensor: The input tensor after applying RoPE
"""
rot_dim = freqs.shape[-1]
# ideally t_pass is empty so rotary pos embedding is applied to all tensor t
t, t_pass = t[..., :rot_dim], t[..., rot_dim:]
# first part is cosine component
# second part is sine component, need to change signs with _rotate_half method
cos_ = torch.cos(freqs).to(t.dtype)
sin_ = torch.sin(freqs).to(t.dtype)
t = (t * cos_) + (_rotate_half(t) * sin_)
return torch.cat((t, t_pass), dim=-1)
def _rotate_half(x: torch.Tensor) -> torch.Tensor:
"""Change sign so the last dimension becomes [-odd, +even]
Arguments:
x (Tensor): Input tensor
Returns:
Tensor: Tensor rotated half
"""
x1, x2 = torch.chunk(x, 2, dim=-1)
return torch.cat((-x2, x1), dim=-1)
def ref_fused_apply_rotary_pos_emb(
t: torch.Tensor, freqs: torch.Tensor, transpose_output_memory: bool = False
):
output_unfused = apply_rotary_pos_emb(t, freqs)
return output_unfused
def fused_apply_rotary_pos_emb(
t: torch.Tensor,
freqs: torch.Tensor,
transpose_output_memory: bool = False,
) -> torch.Tensor:
"""
Args:
t: (sequence length,batch size,head num,head_dim) torch.float16, torch.bfloat16, torch.float32
freqs: (sequence length,1 ,1, head_dim) torch.float32
transpose_output_memory: bool
Default to False. Whether to transpose the 's' and 'b' dimension of the output's underlying memory format. This is very helpful when you want to get a contiguous tensor after calling `output.transpose(0, 1)`.
Returns:
Tensor: (sequence length,batch size,head num,head_dim) torch.float16, torch.bfloat16, torch.float32
"""
output = ops.train.fused_rope_forward(t, freqs, transpose_output_memory)
return output

View File

@@ -0,0 +1,48 @@
import os
from typing import Union
import ixformer._C as ops
import torch
__all__ = ["gemv", "ref_gemv"]
def ref_gemv(x: torch.Tensor, A: torch.Tensor, gemv_max_batch: int = 1):
output = torch.nn.functional.linear(x, A)
return output
def gemv_conditions(input, weight, gemv_max_batch):
# gemv 使用的条件 input:[m,k] weight:[n,k]
# 1. m<=gemv_max_batch
# 2. k%2==0 n%2==0
# 3. bias is None
input = input.view(-1, input.shape[-1])
weight = weight.view(-1, weight.shape[-1])
m = input.shape[0]
k = input.shape[1]
n = weight.shape[0]
if m <= gemv_max_batch and k % 2 == 0 and n % 2 == 0:
return True
return False
def gemv(x: torch.Tensor, A: torch.Tensor, gemv_max_batch: int = 1):
"""
Args:
x: (..., k) torch.float16, torch.bfloat16
A: (n,k) torch.float16, torch.bfloat16, torch.float32
gemv_max_batch: int
用于是否满足gemv使用条件的判断,目前只支持到1
Returns:
Tensor: (..., n) torch.float16, torch.bfloat16
"""
disable_infer_gemm_ex = os.getenv("DISABLE_INFER_GEMM_EX", "0")
use_gemv = gemv_conditions(x, A, gemv_max_batch) and disable_infer_gemm_ex != "1"
assert use_gemv == True
output_shape = list(x.shape)
output_shape[-1] = A.shape[0]
output = x.new_empty(output_shape)
output = ops.infer.linear_ex(x, A, None, output)
return output

View File

@@ -0,0 +1,120 @@
from typing import List, Tuple, Union
import ixformer._C as ops
import torch
from torch.autograd.function import Function, FunctionCtx
import ixformer
__all__ = [
"group_norm",
"ref_group_norm",
"ref_fused_group_norm_silu",
"fused_group_norm_silu",
"ref_fused_group_norm_silu_nhwc",
"fused_group_norm_silu_nhwc"
]
def is_channels_last(ten):
return torch._prims_common.suggest_memory_format(ten) == torch.channels_last
def ref_group_norm(input, num_groups, weight, bias, eps):
output = torch.nn.functional.group_norm(input, num_groups, weight, bias, eps)
return output
#group_norm官方接口如果input是nhwc(channel_last),输出则不是channel_last,而是nchw如果input是nchw那么输出也是nchw
def group_norm(
input: torch.Tensor,
num_groups: int,
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-05,
):
"""
Args:
input: (n,c,h,w) or (n,c,h) or (n,h,w,c) torch.float16
"contiguous_format":(n,c,h,w) or (n,c,h) "channels_last": (n,h,w,c)
num_groups: int
weight: (c) torch.float16
bias: (c) torch.float16
eps: float
Returns:
Tensor: (n,c,h,w) torch.float16
"""
is_nhwc=is_channels_last(input)
out = ops.infer.groupnorm(input, num_groups, weight, bias, eps, is_nhwc, 0)
if is_nhwc:
out=out.permute(0,3,1,2).contiguous()
return out
def ref_fused_group_norm_silu_nhwc(
input: torch.Tensor,
num_groups: int,
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-05,
act_type:int = 0
):
output = torch.nn.functional.group_norm(input.permute(0,3,1,2).contiguous(), num_groups, weight, bias, eps)
if act_type:
output = output * torch.sigmoid(output)
output = output.permute(0,2,3,1).contiguous()
return output
#为了减少permute/contiguous,新接口支持输入输出都是nhwc的融合silu
def fused_group_norm_silu_nhwc(
input: torch.Tensor,
num_groups: int,
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-05,
act_type:int = 0
):
"""
Args:
input: (n,h,w,c) torch.float16
num_groups: int
weight: (c) torch.float16
bias: (c) torch.float16
eps: float
act_type: int
0 or 1,if act_type=1, silu
Returns:
Tensor: (n,h,w,c) torch.float16
"""
out = ops.infer.groupnorm(input, num_groups, weight, bias, eps, True, act_type)
return out
def ref_fused_group_norm_silu(
input: torch.Tensor,
num_groups: int,
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-05,
):
output = torch.nn.functional.group_norm(input, num_groups, weight, bias, eps)
output = output * torch.sigmoid(output)
return output
def fused_group_norm_silu(
input: torch.Tensor,
num_groups: int,
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-05,
):
"""
Args:
input: (n,c,h,w) or (n,c,h) torch.float16
num_groups: int
weight: (c) torch.float16
bias: (c) torch.float16
eps: float
Returns:
output: (n,c,h,w) or (n,c,h) torch.float16
"""
out = ops.infer.groupnorm(input, num_groups, weight, bias, eps, False, 1)
return out

View File

@@ -0,0 +1,32 @@
import os
from typing import Union
import ixformer._C as ops
import torch
__all__ = ["i8w8o32", "ref_i8w8o32"]
def ref_i8w8o32(input: torch.Tensor, weight: torch.Tensor):
output = torch.nn.functional.linear(input.float(), weight.float()).int()
return output
def i8w8o32(input: torch.Tensor, weight: torch.Tensor):
"""
Args:
input: (bs, ic) torch.int8
weight: (oc, ic) torch.int8
Returns:
Tensor: (bs, oc)) torch.int32
"""
if not torch.is_tensor(input):
raise RuntimeError("Not impl.")
output_shape = list(input.shape)
output_shape[-1] = weight.size(0)
output = torch.empty(output_shape, dtype=torch.int32, device=input.device)
ic_dim = input.size(-1)
input = input.view(-1, ic_dim)
ops.infer.linear_i8w8o32(input.view(-1, ic_dim), weight, output)
return output

View File

@@ -0,0 +1,429 @@
from typing import List, Tuple, Union
import ixformer._C as ops
import torch
__all__ = [
"layer_norm",
"ref_layer_norm",
"residual_layer_norm",
"ref_residual_layer_norm",
"ref_residual_layer_norm_bias_alpha",
"residual_layer_norm_bias_alpha",
"ref_layer_norm_2sb_fused",
"layer_norm_2sb_fused",
]
def ref_layer_norm(
input: torch.Tensor,
normalized_shape: List[int],
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-5,
output: torch.Tensor = None,
):
if weight is None or bias is None or weight.dim() > 1 or bias.dim() > 1:
raise NotImplementedError(
"layer_norm only support weight.dim() ==1 and bias.dim()==1!"
)
if normalized_shape == None:
norm_size = weight.size(-1)
normalized_shape = [norm_size]
else:
if (
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
) and len(normalized_shape) == 1:
norm_size = normalized_shape[0]
else:
raise ValueError(
f"layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
)
if norm_size != weight.size(-1):
raise ValueError(f"layer_norm(): argument 'norm_size' must == weight.size(-1)")
norm_out = torch.nn.functional.layer_norm(
input, normalized_shape, weight, bias, eps=eps
)
if output is not None:
assert output.shape == norm_out.shape
output.copy_(norm_out)
else:
output = norm_out
return output
def layer_norm(
input: torch.Tensor,
normalized_shape: List[int],
weight: torch.Tensor,
bias: torch.Tensor,
eps: float = 1e-5,
output: torch.Tensor = None,
):
"""
This function is deprecated, please use residual_layer_norm.
等价实现:
torch.nn.functional.layer_norm( input, normalized_shape, weight, bias, eps=0.000001)
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
normalized_shape: list[int]
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
eps: float32
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
"""
if weight is None or bias is None or weight.dim() > 1 or bias.dim() > 1:
raise NotImplementedError(
"layer_norm only support weight.dim() ==1 and bias.dim()==1!"
)
if normalized_shape == None:
norm_size = weight.size(-1)
normalized_shape = [norm_size]
else:
if (
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
) and len(normalized_shape) == 1:
norm_size = normalized_shape[0]
else:
raise ValueError(
f"layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
)
if norm_size != weight.size(-1):
raise ValueError(f"layer_norm(): argument 'norm_size' must == weight.size(-1)")
if output is None:
output = torch.empty_like(input)
ops.infer.layer_norm(input, weight, bias, None, output, eps)
return output
def ref_residual_layer_norm(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
eps: float = 1e-5,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
):
normalized_shape = [weight.size(-1)]
if residual_bias is not None:
input = input + residual_bias
if residual is not None:
residual_output = torch.add(input, residual, out=residual_output)
input = residual_output
norm_out = torch.nn.functional.layer_norm(
input, normalized_shape, weight, bias, eps=eps
)
if output is None:
output = norm_out
else:
output.copy_(norm_out)
return output, residual_output
def residual_layer_norm(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
eps: float = 1e-5,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
):
"""
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
eps: float32
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on input.
residual_output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on residual.
"""
if residual is None:
if output is None:
output = torch.empty(input.shape, device=input.device, dtype=input.dtype)
ops.infer.layer_norm(input, weight, bias, residual_bias, output, eps)
else:
ops.infer.residual_layer_norm(
input,
residual,
weight,
bias,
residual_bias,
output,
residual_output,
1.0,
eps,
False,
)
residual_output = residual_output if residual_output is not None else residual
output = output if output is not None else input
return output, residual_output
def ref_residual_layer_norm_bias_alpha(
input: torch.Tensor,
normalized_shape: List[int],
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor,
residual_bias: torch.Tensor = None,
alpha: float = 1.0,
eps: float = 1e-5,
is_post_ln=False,
):
if (
weight is None
or bias is None
or residual is None
or weight.dim() > 1
or bias.dim() > 1
):
raise NotImplementedError(
"residual_layer_norm only support weight.dim() ==1 and bias.dim()==1!"
)
if normalized_shape == None:
norm_size = weight.size(-1)
normalized_shape = [norm_size]
else:
if (
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
) and len(normalized_shape) == 1:
norm_size = normalized_shape[0]
else:
raise ValueError(
f"residual_layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
)
if norm_size != weight.size(-1):
raise ValueError(
f"residual_layer_norm(): argument 'norm_size' must == weight.size(-1)"
)
dtype = input.dtype
if residual_bias is None:
x = input.float() + residual.float() * alpha
else:
x = input.float() + residual.float() * alpha + residual_bias.float()
y = torch.nn.functional.layer_norm(
x.to(dtype), normalized_shape, weight, bias, eps=eps
)
if is_post_ln:
return y, y
else:
return y, x.to(dtype)
def residual_layer_norm_bias_alpha(
input: torch.Tensor,
normalized_shape: List[int],
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor,
residual_bias: torch.Tensor = None,
alpha: float = 1.0,
eps: float = 1e-5,
is_post_ln=False,
):
"""
等价实现:
residual = input + residual.float() * alpha + residual_bias
output = torch.nn.functional.layer_norm(
residual, normalized_shape, weight, bias, eps=eps
)
residual = output if is_post_ln else residual
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
normalized_shape list[int]
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
alpha: float32
eps: float32
is_post_ln: bool
Returns:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
Inplace operation will be performed on residual and input.
"""
if (
weight is None
or bias is None
or residual is None
or weight.dim() > 1
or bias.dim() > 1
):
raise NotImplementedError(
"residual_layer_norm only support weight.dim() ==1 and bias.dim()==1!"
)
if normalized_shape == None:
norm_size = weight.size(-1)
normalized_shape = [norm_size]
else:
if (
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
) and len(normalized_shape) == 1:
norm_size = normalized_shape[0]
else:
raise ValueError(
f"residual_layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
)
if norm_size != weight.size(-1):
raise ValueError(
f"residual_layer_norm(): argument 'norm_size' must == weight.size(-1)"
)
ops.infer.residual_layer_norm(
input, residual, weight, bias, residual_bias, None, None, alpha, eps, is_post_ln
)
return input, residual
def ref_layer_norm_2sb_fused(
input: torch.Tensor,
normalized_shape: List[int],
weight1: torch.Tensor,
bias1: torch.Tensor,
weight2: torch.Tensor,
bias2: torch.Tensor,
eps: float = 1e-5,
):
assert input.shape[-1] <= 16384
if not (input.dtype == torch.float16 or input.dtype == torch.bfloat16):
raise NotImplementedError(
"layer_norm_2sb() only support data format of float16 or bfloat16 now!"
)
if (
weight1 is None
or bias1 is None
or weight2 is None
or bias2 is None
or weight1.dim() > 1
or bias1.dim() > 1
or weight2.dim() > 1
or bias2.dim() > 1
):
raise NotImplementedError(
"layer_norm_2sb only support weight1.dim() ==1, bias1.dim()==1, weight2.dim() ==1 and bias2.dim()==1 !"
)
if normalized_shape == None:
norm_size = weight1.size(-1)
normalized_shape = [norm_size]
else:
if (
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
) and len(normalized_shape) == 1:
norm_size = normalized_shape[0]
else:
raise ValueError(
f"layer_norm_2sb(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
)
if norm_size != weight1.size(-1) or norm_size != weight2.size(-1):
raise ValueError(
f"layer_norm_2sb(): argument 'norm_size' must == weight.size(-1)"
)
output1 = torch.nn.functional.layer_norm(
input, normalized_shape, weight1, bias1, eps=eps
)
output2 = torch.nn.functional.layer_norm(
input, normalized_shape, weight2, bias2, eps=eps
)
return output1, output2
def layer_norm_2sb_fused(
input: torch.Tensor,
normalized_shape: List[int],
weight1: torch.Tensor,
bias1: torch.Tensor,
weight2: torch.Tensor,
bias2: torch.Tensor,
eps: float = 1e-5,
):
"""
等价实现:
output1 = torch.nn.functional.layer_norm(
input, normalized_shape, weight1, bias1, eps=eps
)
output2 = torch.nn.functional.layer_norm(
input, normalized_shape, weight2, bias2, eps=eps
)
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16
normalized_shape list[int]
weight1: (hidden_size) torch.float16, torch.bfloat16
bias1: (hidden_size) torch.float16, torch.bfloat16
weight2: (hidden_size) torch.float16, torch.bfloat16
bias2: (hidden_size) torch.float16, torch.bfloat16
eps: float32
Returns:
output1: (..., hidden_size) torch.float16, torch.bfloat16
output2: (..., hidden_size) torch.float16, torch.bfloat16
"""
assert input.shape[-1] <= 16384
if not (input.dtype == torch.float16 or input.dtype == torch.bfloat16):
raise NotImplementedError(
"layer_norm_2sb() only support data format of float16 or bfloat16 now!"
)
if (
weight1 is None
or bias1 is None
or weight2 is None
or bias2 is None
or weight1.dim() > 1
or bias1.dim() > 1
or weight2.dim() > 1
or bias2.dim() > 1
):
raise NotImplementedError(
"layer_norm_2sb only support weight1.dim() ==1, bias1.dim()==1, weight2.dim() ==1 and bias2.dim()==1 !"
)
if normalized_shape == None:
norm_size = weight1.size(-1)
normalized_shape = [norm_size]
else:
if (
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
) and len(normalized_shape) == 1:
norm_size = normalized_shape[0]
else:
raise ValueError(
f"layer_norm_2sb(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
)
if norm_size != weight1.size(-1) or norm_size != weight2.size(-1):
raise ValueError(
f"layer_norm_2sb(): argument 'norm_size' must == weight.size(-1)"
)
output1 = torch.empty_like(input)
output2 = torch.empty_like(input)
ops.infer.layer_norm_2sb(
input, weight1, bias1, weight2, bias2, eps, output1, output2
)
return output1, output2

View File

@@ -0,0 +1,277 @@
from typing import Union
import ixformer._C as ops
import torch
__all__ = [
"lightllm_tokenattention",
"ref_lightllm_tokenattention",
"lightllm_destindex_copy_kv",
"ref_lightllm_destindex_copy_kv",
"lightllm_apply_penalty",
"ref_lightllm_apply_penalty",
"lightllm_glm2_rope",
"ref_lightllm_glm2_rope",
]
def ref_lightllm_glm2_rope(
x: torch.Tensor, # tokens,head_num,head_dim
cos: torch.Tensor, # tokens,rotdim
sin: torch.Tensor,
):
num_tokens, _, rot_dim = list(cos.shape)
head_num = x.shape[1]
x12 = x[:, :, : rot_dim * 2]
x3 = x[:, :, rot_dim * 2 :]
x12 = x12.reshape(num_tokens, head_num, rot_dim, 2)
x1 = x12[:, :, :, 0]
x2 = x12[:, :, :, 1]
# out0 = q0 * cos - q1 * sin
# out1 = q0 * sin + q1 * cos
# q1, q2 是沿着 head_dim维度交叉取值的
q1 = x1 * cos - x2 * sin
q2 = x2 * cos + x1 * sin
q12 = torch.stack([q1, q2], dim=-1)
q12 = q12.reshape(num_tokens, head_num, -1)
x_pytorch = torch.cat([q12, x3], dim=-1)
return x_pytorch
def lightllm_glm2_rope(
x: torch.Tensor, # tokens,head_num,head_dim
cos: torch.Tensor, # tokens,rotdim
sin: torch.Tensor,
):
"""
Args:
x: (num_tokens, head_num, head_dim) torch.half
cos: (num_tokens,1,head_dim//2//2) torch.half
sin: (num_tokens,1,head_dim//2//2) torch.half
Returns:
x: (num_tokens, head_num, head_dim) torch.half
"""
if isinstance(x, torch.Tensor):
ops.infer.lightllm_glm2_rope(x, cos, sin)
return x
else:
raise NotImplementedError()
def ref_lightllm_apply_penalty(
Logits: torch.Tensor,
presence_penalty: torch.Tensor,
freqency_penalty: torch.Tensor,
p_token_ids: torch.Tensor,
p_token_counts: torch.Tensor,
p_cumsum_seq_len: torch.Tensor,
p_max_len_in_batch: int,
):
batch_size = Logits.size(0)
output = Logits.clone()
for cur_batch in range(batch_size):
cur_freqency = freqency_penalty[cur_batch]
cur_presence = presence_penalty[cur_batch]
cur_batch_start_index = p_cumsum_seq_len[cur_batch]
cur_batch_end_index = p_cumsum_seq_len[cur_batch + 1]
for token_idx in range(cur_batch_start_index, cur_batch_end_index):
batch_ids = p_token_ids[token_idx]
batch_ids_count = p_token_counts[token_idx]
cur_logits = output[cur_batch][batch_ids]
freq_logits = cur_logits - batch_ids_count * cur_freqency
pre_logits = freq_logits - cur_presence
# if token_idx==0:
# print(f"batch_ids {batch_ids} cur_logits {cur_logits} pre_logits {pre_logits}")
output[cur_batch][batch_ids] = pre_logits
return output
def lightllm_apply_penalty(
Logits: torch.Tensor,
presence_penalty: torch.Tensor,
freqency_penalty: torch.Tensor,
p_token_ids: torch.Tensor,
p_token_counts: torch.Tensor,
p_cumsum_seq_len: torch.Tensor,
p_max_len_in_batch: int,
):
"""
Args:
logits: (batch_size, vocab_size) torch.float
presence_penalty: (batch_size) torch.float
freqency_penalty: (batch_size) torch.float
p_token_ids: (num_tokens) torch.int
p_token_counts: (num_tokens) torch.int
p_cumsum_seq_len: (batch_size+1) torch.int
p_max_len_in_batch: int
在一个batch中seq的最大长度
Returns:
logits: (batch_size, vocab_size) torch.float
"""
if isinstance(Logits, torch.Tensor):
ops.infer.lightllm_apply_penalty(
Logits,
presence_penalty,
freqency_penalty,
p_token_ids,
p_token_counts,
p_cumsum_seq_len,
p_max_len_in_batch,
)
return Logits
else:
raise NotImplementedError()
def ref_lightllm_destindex_copy_kv(
key_cache: torch.Tensor,
mem_idx: torch.Tensor,
output: torch.Tensor,
):
if key_cache.dim() != 3 or key_cache.size(-1) != 128:
raise NotImplementedError(
"lightllm_destindex_copy_kv only support key_cache.dim()==3 and head_size ==128 !"
)
output[mem_idx.long()] = key_cache
return output
def lightllm_destindex_copy_kv(
key_cache: torch.Tensor,
mem_idx: torch.Tensor,
output: torch.Tensor,
):
"""
Args:
key_cache: (tokens, num_kv_heads, head_size) torch.half
目前head_size 只支持128的情况
mem_idx: (tokens) torch.int
output: (max_tokens, num_kv_heads, head_size) torch.half
Returns:
output: (max_tokens, num_kv_heads, head_size) torch.half
"""
if key_cache.dim() != 3 or key_cache.size(-1) != 128:
raise NotImplementedError(
"lightllm_destindex_copy_kv only support key_cache.dim()==3 and head_size ==128 !"
)
if isinstance(key_cache, torch.Tensor):
ops.infer.lightllm_destindex_copy_kv(key_cache, mem_idx, output)
else:
raise NotImplementedError()
return output
def ref_lightllm_tokenattention(
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
reg_tokens: torch.Tensor,
b_req_idx: torch.Tensor,
b_seq_len: torch.Tensor,
scale: float,
max_context_len: int,
):
batch_size, tp_q_head_num_, head_dim_ = query.shape
tp_k_head_num_ = key_cache.size(-2)
sm_scale = scale
curbatch_max_context_len = max_context_len
tmp_k = torch.zeros(
(batch_size, tp_q_head_num_, curbatch_max_context_len, head_dim_),
dtype=query.dtype,
device="cuda",
)
tmp_v = torch.zeros(
(batch_size, tp_q_head_num_, curbatch_max_context_len, head_dim_),
dtype=query.dtype,
device="cuda",
)
mask = torch.ones([batch_size, 1, 1, curbatch_max_context_len])
kv_group_num = tp_q_head_num_ // tp_k_head_num_
for cur_batch in range(batch_size):
cur_batch_req_idx = b_req_idx[cur_batch]
seq_len = b_seq_len[cur_batch]
mask[cur_batch, :, :, :seq_len] = 0
# print(f"cur_batch {cur_batch}")
for seq_idx in range(seq_len):
k_loc = reg_tokens[cur_batch_req_idx][seq_idx]
# print(k_loc)
for cur_head in range(tp_q_head_num_):
cur_kv_head = cur_head // kv_group_num
tmp_k[cur_batch, cur_head, seq_idx, :] = key_cache[k_loc][cur_kv_head]
tmp_v[cur_batch, cur_head, seq_idx, :] = value_cache[k_loc][cur_kv_head]
mask = mask.cuda()
# batch_size, self.tp_q_head_num_, 1, max_len_in_batch
attn_score = (
torch.matmul(
query.view(batch_size, tp_q_head_num_, 1, head_dim_),
tmp_k.transpose(-1, -2),
)
* sm_scale
)
attn_score = attn_score + mask * -1000
attn_score = torch.softmax(attn_score, dim=-1)
# batch_size, self.tp_q_head_num_, 1, head_dim
py_out = torch.matmul(attn_score.to(query.dtype), tmp_v).view(
batch_size, tp_q_head_num_, -1
)
return py_out
def lightllm_tokenattention(
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
reg_tokens: torch.Tensor,
b_req_idx: torch.Tensor,
b_seq_len: torch.Tensor,
scale: float,
max_context_len: int,
partition: int,
output: torch.Tensor,
):
"""
Args:
query: (batch_size,head_num,head_dim) torch.float16, torch.bfloat16
key_cache: (max_num_tokens, head_num_kv, head_dim) torch.float16, torch.bfloat16
value_cache: (max_num_tokens, head_num_kv, head_dim) torch.float16, torch.bfloat16
reg_tokens: (max_request,max_tokens) torch.int32
目前max_tokens只支持3080
b_req_idx: (batch_size) torch.int32
b_req_len: (batch_size) torch.int32
scale: float
The scaling of QK^T before applying softmax.
max_context_len: int
b_seq_len.max()
partition: int
Returns:
output: (batch_size,head_num,head_dim) torch.float16, torch.bfloat16
"""
_,max_tokens=reg_tokens.shape
if not max_tokens == 3080:
raise NotImplementedError(
"lightllm_tokenattention only support reg_tokens.size(-1)==3080"
)
if isinstance(query, torch.Tensor):
ops.infer.lightllm_tokenattention(
query,
key_cache,
value_cache,
reg_tokens,
b_req_idx,
b_seq_len,
scale,
max_context_len,
partition,
output,
)
else:
raise NotImplementedError()
return output

View File

@@ -0,0 +1,50 @@
from typing import Union
import ixformer._C as ops
import torch
__all__ = ["solve", "ref_slove"]
def ref_slove(
A: torch.Tensor, B: torch.Tensor, *, left: bool = True, out: torch.Tensor = None
):
out = torch.linalg.solve(A, B, left=left)
return out
def solve(
A: torch.Tensor, B: torch.Tensor, *, left: bool = True, out: torch.Tensor = None
):
"""
Args:
A: (..., n, n) torch.float
B: (..., n) or (..., n, k) or (n,...) or (n, k) or (n) torch.float
left: bool
whether to solve the system AX=B or XA=B. Default: True, 目前只支持left =True
out: (..., n, k) torch.float
Returns:
out: (..., n, k) torch.float
"""
n = A.shape[-1]
batch_count = A.numel() // (n * n)
if B.dim() == 1:
k = 1
elif B.dim() == 2:
if A.dim() > 2 and B.shape == (batch_count, n):
k = 1
else:
nid = 0 if left else 1
k = B.shape[nid ^ 1]
else:
k = B.size(B.dim() - 1 if left else B.dim() - 2)
if n <= 64 and k <= 64 and left:
return ops.infer.solve(A, B, left)
else:
device = A.device
cpu_A = A.cpu()
cpu_B = B.cpu()
cpu_res = ref_slove(A=cpu_A, B=cpu_B, left=left)
return cpu_res.to(device)

View File

@@ -0,0 +1,122 @@
import os
from typing import Union
import ixformer._C as ops
import torch
__all__ = ["linear", "ref_linear", "mixed_type_linear", "ref_mixed_type_linear"]
def ref_linear(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
act_type=-1,
):
output = torch.nn.functional.linear(input, weight, bias)
if act_type == -1:
act_fn = torch.nn.Identity()
elif act_type == 3:
act_fn = torch.nn.GELU()
elif act_type == 4:
act_fn = torch.nn.ReLU()
elif act_type == 12:
act_fn = torch.nn.SiLU()
else:
raise KeyError("act_type not supported")
output = act_fn(output)
return output
def gemv_conditions(input, weight, bias, gemv_max_batch):
# gemv 使用的条件 input:[m,k] weight:[n,k]
# 1. m<=gemv_max_batch
# 2. k%32==0 n%2==0
# 3. bias is None
input = input.view(-1, input.shape[-1])
weight = weight.view(-1, weight.shape[-1])
m = input.shape[0]
k = input.shape[1]
n = weight.shape[0]
if bias is None and m <= gemv_max_batch and k % 32 == 0 and n % 2 == 0:
return True
return False
def linear(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
output: torch.Tensor = None,
persistent: bool = False,
act_type : int = -1,
):
"""
Args:
input: (...,k) torch.float16, torch.bfloat16
weight: (n, k) torch.float16, torch.bfloat16
bias: (n) torch.float16, torch.bfloat16
output: (...,n) torch.float16, torch.bfloat16
persistent: bool
是否限制 Gemm Kernel 的 Block 数量
Returns:
output: (...,n) torch.float16, torch.bfloat16
"""
if not input.is_contiguous():
input = input.contiguous()
if not weight.is_contiguous():
weight = weight.contiguous()
use_gemv = True
gemv_max_batch = 1
disable_infer_gemm_ex = os.getenv("DISABLE_INFER_GEMM_EX", "0")
use_gemv = (
use_gemv
and gemv_conditions(input, weight, bias, gemv_max_batch)
and disable_infer_gemm_ex != "1"
)
if output is None:
output_shape = list(input.shape)
output_shape[-1] = weight.shape[0]
output = input.new_empty(output_shape)
if not use_gemv:
output = ops.infer.linear(input, weight, act_type, bias, output, persistent)
else:
output = ops.infer.linear_ex(input, weight, bias, output)
return output
def ref_mixed_type_linear(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
output: torch.Tensor = None,
persistent=False, # TODO: support persistent
):
input = input.to(weight.dtype)
if bias:
bias = bias.to(weight.dtype)
output = torch.nn.functional.linear(input, weight, bias)
return output
def mixed_type_linear(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor = None,
output: torch.Tensor = None,
persistent=False, # TODO: support persistent
):
"""
Args:
input: (...,k) torch.half, torch.bfloat16
weight: (m, k) torch.float32
bias: not supported
output: (...,m) torch.float32
persistent: bool
Returns:
output: (...,m) torch.float32
"""
output = ops.infer.mixed_type_linear(input, weight, bias, output)
return output

View File

@@ -0,0 +1,212 @@
import math
from typing import Literal, Optional, Union
import ixformer._C as ops
import ixformer._C._functions as CF
import torch
from ixformer.core import config
from .linear import linear
from .paged_attention import paged_attention as paged_attention_ixformer_impl
__all__ = [
"ref_lmdeploy_paged_attention",
"lmdeploy_paged_attention",
]
weak_ref_tensor = ops.infer.weak_ref_tensor
def ref_lmdeploy_paged_attention(
output: torch.Tensor,
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
num_kv_heads: torch.Tensor,
scale: float,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
block_size: int,
max_context_len: int,
alibi_slopes: torch.Tensor = None,
softcap: float = 0.0,
window_left: int = -1,
window_right: int = -1,
use_sqrt_alibi: bool = False,
quant_type: int = 0,
is_bbhh: bool = False,
):
assert window_right in [-1, 0]
if is_bbhh:
key_cache = key_cache.permute(0, 2, 1, 3).contiguous()
value_cache = value_cache.permute(0, 2, 1, 3).contiguous()
def get_alibi_mask(num_heads, seqlen, device, dtype):
x = torch.arange(0, seqlen, device=device, dtype=torch.float32).view(-1, 1)
y = torch.tensor(seqlen - 1, device=device, dtype=torch.float32).view(1, -1)
offsets = -(y - x).view(1, 1, seqlen)
return offsets
def ref_masked_attention(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
scale: float,
attn_mask: Optional[torch.Tensor] = None,
) -> torch.Tensor:
query = query * scale
dtype = query.dtype
device = query.device
query = query.to(torch.float32)
key = key.to(torch.float32)
value = value.to(torch.float32)
attn = torch.einsum("qhd,khd->hqk", query, key)
if attn_mask is not None:
attn_mask = attn_mask
attn = attn + attn_mask
attn = torch.softmax(attn, dim=-1)
out = torch.einsum("hqk,khd->qhd", attn, value)
out = out.to(device).to(dtype)
return out
head_size = query.shape[-1]
num_query_heads = query.shape[1]
num_kv_heads = value_cache.shape[1]
num_input_tokens = query.shape[0]
num_q_per_kv = num_query_heads // num_kv_heads
slopes = (
alibi_slopes.view(num_query_heads, 1, 1)
if alibi_slopes is not None
else alibi_slopes
)
for i in range(num_input_tokens):
q = query[i].unsqueeze(0)
block_table = block_tables[i]
context_len = int(context_lens[i])
keys = []
values = []
for j in range(context_len):
block_number = int(block_table[j // block_size])
block_offset = j % block_size
k = key_cache[block_number, :, block_offset, :]
keys.append(k)
v = value_cache[block_number, :, block_offset, :]
values.append(v)
keys = torch.stack(keys, dim=0)
values = torch.stack(values, dim=0)
if num_q_per_kv > 1:
keys = torch.repeat_interleave(keys, num_q_per_kv, dim=1)
values = torch.repeat_interleave(values, num_q_per_kv, dim=1)
if alibi_slopes is not None:
offsets = get_alibi_mask(
num_query_heads, context_len, output.device, output.dtype
)
mask = offsets * slopes
mask = mask.to(output.dtype)
if window_left != -1:
index = torch.ones_like(mask, dtype=torch.int32, device=mask.device)
index[:, :, (context_len - 1 - window_left) :] = 0
index = index.bool()
mask.masked_fill_(index, float("-inf"))
else:
if window_left != -1:
mask = torch.zeros([1, 1, context_len], dtype=q.dtype, device=q.device)
index = torch.ones_like(mask, dtype=torch.int32, device=mask.device)
index[:, :, (context_len - 1 - window_left) :] = 0
index = index.bool()
mask.masked_fill_(index, float("-inf"))
else:
mask = None
out = ref_masked_attention(
q,
keys,
values,
scale,
mask,
)
out = out.view(num_query_heads, head_size)
if softcap != 0.0:
out = softcap * torch.tanh(out / softcap)
output[i].copy_(out, non_blocking=True)
return output
def lmdeploy_paged_attention(
output: torch.Tensor,
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
num_kv_heads: torch.Tensor,
scale: float,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
block_size: int,
max_context_len: int,
alibi_slopes: torch.Tensor = None,
softcap: float = 0.0,
causal: bool = True,
window_left: int = -1,
window_right: int = -1,
use_cuda_graph: bool = False,
use_sqrt_alibi: bool = False,
quant_type: int = 0,
is_bbhh: bool = False,
):
"""
is_bbhh = False key_cache, value_cache: [num_blocks, block_size, num_kv_heads, head_size]
is_bbhh = True key_cache, value_cache: [num_blocks, num_kv_heads, block_size, head_size]
is_bbhh = False
Arguments:
query: [torch.half, torch.bfloat16] [num_tokens, num_heads, head_size]
key_cache: [torch.half, torch.bfloat16] [num_blocks, num_kv_heads, block_size, head_size]
value_cache: [torch.half, torch.bfloat16] [num_blocks, num_kv_heads, block_size, head_size]
num_kv_heads: int
scale: float
block_tables: [torch.int64] [num_tokens, max_num_blocks_per_seq]
context_lens: [torch.int32] [num_tokens]
block_size: int
max_context_len: int
alibi_slopes: [torch.float32] [num_heads]
softcap: float
causal: bool
window_left: int
window_right: int
use_sqrt_alibi: bool: False
Return:
output: [torch.half, torch.bfloat16] [num_tokens, num_heads, head_size]
"""
ops.infer.lmdeploy_paged_attention(
output,
query,
key_cache,
value_cache,
num_kv_heads,
scale,
block_tables,
context_lens,
block_size,
max_context_len,
alibi_slopes,
causal,
window_left,
window_right,
softcap,
use_cuda_graph,
use_sqrt_alibi,
is_bbhh,
quant_type,
)
return output
# lmdeploy_paged_attention = lmdeploy_paged_attention_ixinfer

View File

@@ -0,0 +1,234 @@
import ixformer._C as ops
import torch
__all__ = [
"marlin_w4a16",
"marlin_w4_weight_repack",
"marlin_w8a16",
"marlin_w8_weight_repack",
]
def marlin_w4a16(
inputs: torch.Tensor,
weights: torch.Tensor,
scales: torch.Tensor,
zeros: torch.Tensor,
bias: torch.Tensor = None, # TODO
group_size: int = -1,
format: str = "k16n32",
batch_first: bool = True,
outputs: torch.Tensor = None,
):
"""
Args:
inputs: (batch, m, k) if batch_first else (m, batch, k) torch.float16, torch.bfloat16
weights: (batch, k/16, n/32, 64) torch.int32
scales:
(batch, k_groups, n) format:k16n32 torch.float16, torch.bfloat16
(batch, n_groups, k) format:k16n32_grouped_n torch.float16, torch.bfloat16
zeros:
(batch, k_groups, n/8) format:k16n32 torch.int32
(batch, n_groups, k/8) format:k16n32_grouped_n torch.int32
group_size: int
group size of quant
format: str
describe format of weight
batch_first: bool
describe format of input and output
Returns:
outputs: (batch, m, n) if batch_first else (m, batch, n) torch.float16, torch.bfloat16
"""
if outputs is None:
batch, m = (
(inputs.shape[0], inputs.shape[1])
if batch_first
else (inputs.shape[1], inputs.shape[0])
)
if format.startswith("k16n32"):
n = weights.shape[2] * 32
outputs = torch.empty(
(batch, m, n) if batch_first else (m, batch, n),
dtype=inputs.dtype,
device=inputs.device,
)
ops.infer.marlin_w4a16(
outputs, inputs, weights, scales, zeros, bias, group_size, format, batch_first
)
return outputs
def marlin_w4_weight_repack(
weights: torch.Tensor,
scales: torch.Tensor = None,
zeros: torch.Tensor = None,
weight_format: str = "gptq",
reformat: str = "k16n32",
pack_order: str = "default",
repack_weight: torch.Tensor = None,
):
"""
Args:
weights:
(batch, k, n/8) weight_format:awq torch.int32
(batch, k/8, n) weight_format:gptq torch.int32
scales:
(batch, k_groups, n) format:k16n32 torch.float16, torch.bfloat16
(batch, n_groups, k) format:k16n32_grouped_n torch.float16, torch.bfloat16
zeros:
(batch, k_groups, n/8) format:k16n32 torch.int32
(batch, n_groups, k/8) format:k16n32_grouped_n torch.int32
weight_format: str
describe format of weight
reformat: str
describe format of repacked weight
pack_order: str
describe pack order on a pack unit
Returns:
repack_weight: (batch, k/16, n/32, 64) torch.int32
"""
assert weight_format in ["gptq", "gptq_grouped_n", "awq"]
assert reformat in ["k16n32", "k16n32_grouped_n"]
assert pack_order in ["default", "02461357", "01234567"]
if pack_order == "default":
default_order = {
"gptq": "01234567",
"awq": "02461357",
"gptq_grouped_n": "02461357",
}
pack_order = default_order[weight_format]
if weight_format.startswith("gptq"):
batch, pack_k, n = weights.shape
k = pack_k * 8
elif weight_format == "awq":
batch, k, pack_n = weights.shape
n = pack_n * 8
repack_scales, repack_zeros = None, None
if reformat.startswith("k16n32"):
if repack_weight is None:
repack_weight = torch.empty(
(batch, k // 16, n // 32, 64),
dtype=torch.int32,
device=weights.device,
)
if scales is not None:
repack_scales = torch.empty_like(scales)
if zeros is not None:
repack_zeros = torch.empty_like(zeros)
ops.infer.marlin_w4_weight_repack(
weights,
repack_weight,
scales,
repack_scales,
zeros,
repack_zeros,
weight_format,
reformat,
pack_order,
)
if repack_scales is not None and repack_zeros is not None:
return repack_weight, repack_scales, repack_zeros
else:
return repack_weight
def marlin_w8a16(
inputs: torch.Tensor,
weights: torch.Tensor,
scales: torch.Tensor,
bias: torch.Tensor = None, # TODO
group_size: int = -1,
format: str = "k16n16",
batch_first: bool = True,
outputs: torch.Tensor = None,
):
"""
Args:
inputs: (batch, m, k) if batch_first else (m, batch, k) torch.float16, torch.bfloat16
weights: (batch, k/16, n/16, 64) torch.int32
scales:
(batch, k_groups, n) format:k16n16 torch.float32
(batch, n_groups, k) format:k16n16_grouped_n torch.float32
group_size: int
group size of quant
format: str
describe format of weight
batch_first: bool
describe format of input and output
Returns:
outputs: (batch, m, n) if batch_first else (m, batch, n) torch.float16, torch.bfloat16
"""
if outputs is None:
batch, m = (
(inputs.shape[0], inputs.shape[1])
if batch_first
else (inputs.shape[1], inputs.shape[0])
)
if format.startswith("k16n16"):
n = weights.shape[2] * 16
outputs = torch.empty(
(batch, m, n) if batch_first else (m, batch, n),
dtype=inputs.dtype,
device=inputs.device,
)
ops.infer.marlin_w8a16(
outputs, inputs, weights, scales, bias, group_size, format, batch_first
)
return outputs
def marlin_w8_weight_repack(
weights: torch.Tensor,
scales: torch.Tensor = None,
weight_format: str = "int8",
reformat: str = "k16n16",
):
"""
Args:
weights:
(batch, k, n) weight_format:int8 torch.int8
scales:
(batch, k_groups, n) format:k16n16 torch.float32
(batch, n_groups, k) format:k16n16_grouped_n torch.float32
weight_format: str
describe format of weight
reformat: str
describe format of repacked weight
Returns:
repack_weight:
(batch, k/16, n/16, 64) torch.int32
"""
assert weight_format in ["int8"]
assert reformat in ["k16n16", "k16n16_grouped_n"]
repack_scales = None
if weight_format == "int8":
batch, k, n = weights.shape
repack_weight = torch.empty(
(batch, k // 16, n // 16, 64),
dtype=torch.int32,
device=weights.device,
)
if scales is not None:
repack_scales = torch.empty_like(scales)
ops.infer.marlin_w8_weight_repack(
weights,
repack_weight,
scales,
repack_scales,
weight_format,
reformat,
)
if repack_scales is not None:
return repack_weight, repack_scales
else:
return repack_weight

View File

@@ -0,0 +1,50 @@
import ixformer._C as ops
import torch
__all__ = ["matmul", "ref_matmul"]
def ref_matmul(input, other, *, transa, transb, alpha):
if transa:
dims = list(range(input.ndim))
dims[-1], dims[-2] = dims[-2], dims[-1]
input = input.permute(*dims).contiguous()
if transb:
dims = list(range(other.ndim))
dims[-1], dims[-2] = dims[-2], dims[-1]
other = other.permute(*dims).contiguous()
return alpha * torch.matmul(input, other)
def matmul(
input: torch.Tensor,
other: torch.Tensor,
*,
transa: bool = False,
transb: bool = False,
alpha: float = 1.0,
) -> torch.Tensor:
"""
Args:
input: (...,m,k) or (...,k,m) torch.half
当transa为False shape : [...,m,k], 当transa为True shape : [...,k,m]
other: (...,k,n) or (...,n,k) torch.half
当transa为False shape : [...,m,k], 当transa为True shape : [...,k,m]
transa: bool
transb: bool
alpha: float
Returns:
Tensor: (..., m, n) torch.half
"""
if not input.is_contiguous():
input = input.contiguous()
if not other.is_contiguous():
if not other.transpose(-2, -1).is_contiguous():
other = other.contiguous()
return ops.train.matmul(
input, other, transa=transa, transb=transb, alpha=alpha, beta=0.0
)

View File

@@ -0,0 +1,325 @@
from typing import Optional
import ixformer._C as ops
import torch
__all__ = [
# 0.6.3
"ref_minicpm3_fused_rope",
"ref_minicpm3_fused_copy_kv",
"minicpm3_fused_rope",
"minicpm3_fused_copy_kv",
# 0.6.6
"ref_mla_rope_phi",
"mla_rope_phi",
"ref_mla_rope",
"mla_rope",
"ref_mla_copy_kv",
"mla_copy_kv",
]
def _rotate_neox(x: torch.Tensor) -> torch.Tensor:
x1 = x[..., : x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2 :]
return torch.cat((-x2, x1), dim=-1)
def _rotate_gptj(x: torch.Tensor) -> torch.Tensor:
x1 = x[..., ::2]
x2 = x[..., 1::2]
x = torch.stack((-x2, x1), dim=-1)
return x.flatten(-2)
# vllm 0.6.3
def ref_minicpm3_fused_rope(
positions: torch.Tensor,
long_prompt_offset: torch.Tensor,
long_short_cos_sin_cache: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor,
out_query: Optional[torch.Tensor] = None,
out_key: Optional[torch.Tensor] = None,
):
idx = torch.add(positions, long_prompt_offset)
cos_sin = torch.index_select(long_short_cos_sin_cache, 0, idx)
cos, sin = cos_sin.chunk(2, dim=-1)
cos = cos.repeat(1, 2).unsqueeze(-2)
sin = sin.repeat(1, 2).unsqueeze(-2)
out_query = query * cos + _rotate_neox(query) * sin
out_key = key * cos + _rotate_neox(key) * sin
return out_query, out_key
def minicpm3_fused_rope(
positions: torch.Tensor,
long_prompt_offset: torch.Tensor,
long_short_cos_sin_cache: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor,
out_query: Optional[torch.Tensor] = None,
out_key: Optional[torch.Tensor] = None,
):
"""
Args:
positions: (num_tokens,) torch.int64
long_prompt_offset: (num_tokens,) torch.int64
long_short_cos_sin_cache: (max_length, head_dim) torch.float16, torch.bfloat16
query: (num_tokens, num_q_heads, head_dim) same as long_short_cos_sin_cache
key: (num_tokens, num_kv_heads, head_dim) same as long_short_cos_sin_cache
out_query: same as query
out_key: same as key
Returns:
out_query: same as query
out_key: same as key
"""
if out_query is None:
out_query = torch.empty_like(query)
if out_key is None:
out_key = torch.empty_like(key)
ops.infer.minicpm3_fused_rope(
positions,
long_prompt_offset,
long_short_cos_sin_cache,
query,
key,
out_query,
out_key,
)
return out_query, out_key
def ref_minicpm3_fused_copy_kv(
k_nope: torch.Tensor,
k_pe: torch.Tensor,
v: torch.Tensor,
new_k: Optional[torch.Tensor] = None,
new_v: Optional[torch.Tensor] = None,
):
num_tokens, num_heads, k_head_dim = k_nope.shape
head_dim = k_pe.shape[-1] + k_head_dim
v_head_dim = v.shape[-1]
if new_k is None:
new_k = k_nope.new_empty([num_tokens, num_heads, head_dim])
if new_v is None:
new_v = k_nope.new_empty([num_tokens, num_heads, head_dim])
new_k[:, :, :k_head_dim] = k_nope
new_k[:, :, k_head_dim:] = k_pe
new_v[:, :, :v_head_dim] = v
new_v[:, :, v_head_dim:] = 0
return new_k.view(num_tokens, -1), new_v.view(num_tokens, -1)
def minicpm3_fused_copy_kv(
k_nope: torch.Tensor,
k_pe: torch.Tensor,
v: torch.Tensor,
new_k: Optional[torch.Tensor] = None,
new_v: Optional[torch.Tensor] = None,
):
"""
Args:
k_nope: (num_tokens, num_heads, k_head_dim) torch.float16, torch.bfloat16
k_pe: (num_tokens, 1, head_dim - k_head_dim) same as k_nope
v: (num_tokens, num_heads, v_head_dim) same as k_nope
new_k: (num_tokens, num_heads, head_dim) same as k_nope
new_v: (num_tokens, num_heads, head_dim) same as k_nope
Returns:
new_k: (num_tokens, num_heads, head_dim) same as k_nope
new_v: (num_tokens, num_heads, head_dim) same as k_nope
"""
num_tokens, num_heads, k_head_dim = k_nope.shape
head_dim = k_pe.shape[-1] + k_head_dim
if new_k is None:
new_k = k_nope.new_empty([num_tokens, num_heads * head_dim])
if new_v is None:
new_v = k_nope.new_empty([num_tokens, num_heads * head_dim])
ops.infer.minicpm3_fused_copy_kv(k_nope, k_pe, v, new_k, new_v)
return new_k, new_v
# vllm 0.6.6
def ref_mla_rope_phi(
positions: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor,
long_short_cos_sin_cache: torch.Tensor,
k: int,
offsets: Optional[torch.Tensor] = None,
):
long_prompt_offset = (
torch.any(positions > k).float() * torch.full_like(positions, k)
).long()
idx = (
torch.add(positions, long_prompt_offset)
if long_prompt_offset is not None
else positions
)
idx = torch.add(idx, offsets) if offsets is not None else idx
cos_sin = torch.index_select(long_short_cos_sin_cache, 0, idx)
cos, sin = cos_sin.chunk(2, dim=-1)
cos = cos.repeat(1, 2).unsqueeze(-2)
sin = sin.repeat(1, 2).unsqueeze(-2)
query = query * cos + _rotate_neox(query) * sin
key = key * cos + _rotate_neox(key) * sin
return query, key
def mla_rope_phi(
positions: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor,
key_out: torch.Tensor,
long_short_cos_sin_cache: torch.Tensor,
long_offset: torch.Tensor,
k: int,
offsets: Optional[torch.Tensor] = None,
):
"""
Args:
positions: (num_tokens,) torch.int64
query: (num_tokens, num_q_heads, head_dim) same as long_short_cos_sin_cache
key: (num_tokens, 1, head_dim) same as long_short_cos_sin_cache
key_out: (num_tokens, num_q_heads, head_dim) same as long_short_cos_sin_cache
long_short_cos_sin_cache: (max_length, head_dim) same as long_short_cos_sin_cache
long_offset: (1,) torch.bool
k: int
offsets: (num_tokens,)
Returns:
query:
key_out:
"""
ops.infer.mla_rope_phi(
positions,
query,
key,
key_out,
long_short_cos_sin_cache,
long_offset,
k,
offsets,
)
return query, key_out
def ref_mla_rope(
positions: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor,
cos_sin_cache: torch.Tensor,
offsets: Optional[torch.Tensor] = None,
rotary_dim: int = None,
is_neox_style: bool = False,
):
"""PyTorch-native implementation equivalent to forward()."""
head_size = query.size(-1)
rotary_dim = rotary_dim or head_size
query_rot = query[..., :rotary_dim]
key_rot = key[..., :rotary_dim]
if rotary_dim < head_size:
query_pass = query[..., rotary_dim:]
key_pass = key[..., rotary_dim:]
cos_sin = cos_sin_cache[
torch.add(positions, offsets) if offsets is not None else positions
]
cos, sin = cos_sin.chunk(2, dim=-1)
if is_neox_style:
cos = cos.repeat(1, 1, 2).unsqueeze(-2)
sin = sin.repeat(1, 1, 2).unsqueeze(-2)
else:
cos = cos.repeat_interleave(2, dim=-1).unsqueeze(-2)
sin = sin.repeat_interleave(2, dim=-1).unsqueeze(-2)
rotate_fn = _rotate_neox if is_neox_style else _rotate_gptj
query_rot = query_rot * cos + rotate_fn(query_rot) * sin
key_rot = key_rot * cos + rotate_fn(key_rot) * sin
if rotary_dim < head_size:
query = torch.cat((query_rot, query_pass), dim=-1)
key = torch.cat((key_rot, key_pass), dim=-1)
else:
query = query_rot
key = key_rot
return query, key
def mla_rope(
positions: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor,
key_out: torch.Tensor,
cos_sin_cache: torch.Tensor,
offsets: Optional[torch.Tensor] = None,
is_neox_style: bool = False,
):
"""
Args:
positions: (num_tokens,) torch.int64
query: (num_tokens, num_q_heads, head_dim) torch.half torch.bfloat torch.float
key: (num_tokens, 1, head_dim) same as query
key_out: (num_tokens, num_q_heads, head_dim) same as query
cos_sin_cache: (max_length, head_dim) same as query
offsets: (num_tokens,) same as query
is_neox_style: bool
Returns:
query:
key_out:
"""
ops.infer.mla_rope(
positions,
query,
key,
key_out,
cos_sin_cache,
is_neox_style,
offsets,
)
return query, key_out
def ref_mla_copy_kv(key_pe, key_nope, value_nope):
shape = key_nope.shape[:-1] + (key_pe.shape[-1] + key_nope.shape[-1],)
key = torch.empty(shape, device=key_nope.device, dtype=key_nope.dtype)
value = torch.empty_like(key)
key[..., : key_nope.size(-1)] = key_nope
key[..., key_nope.size(-1) :] = key_pe
value[..., : value_nope.size(-1)] = value_nope
value[..., value_nope.size(-1) :] = 0.0
return key, value
def mla_copy_kv(key_nope, value_nope, key, value):
"""
Args:
key_nope: (num_tokens, num_heads, k_nope_dim) torch.float16, torch.bfloat16, torch.float
value_nope: (num_tokens, num_heads, v_head_dim) same as key_nope
key: (num_tokens, num_heads, head_dim) same as key_nope
value: (num_tokens, num_heads, head_dim) same as key_nope
Returns:
key:
value:
"""
ops.infer.mla_copy_kv(key_nope, value_nope, key, value)
return key, value

View File

@@ -0,0 +1,315 @@
import ixformer._C as ops
import torch
import torch.nn.functional
__all__ = [
"mm",
"addmm",
"fused_addmm_bias_col_act",
"ref_fused_addmm_bias_col_act",
"ref_addmm",
"ref_mm",
"ref_bmm",
"bmm",
]
def ref_mm(input, mat, *, out=None):
out = torch.mm(input, mat, out = out)
return out
def mm(input, mat, *, out=None):
"""
Args:
input: (m,k) torch.float16, torch.bfloat16, torch.float32
mat: (k,n) torch.float16, torch.bfloat16, torch.float32
out: (m,n) torch.float16, torch.bfloat16, torch.float32
Returns:
out: (m,n) torch.float16, torch.bfloat16, torch.float32
"""
assert input.dim() == mat.dim(), "mm tensors must be 2-D"
assert input.size(1) == mat.size(
0
), f"mm cannot be multiplied, {input.size(0)}X{input.size(1)} and {mat.size(0)}X{mat.size(1)}"
m = input.shape[0]
n = mat.shape[-1]
if out is None:
out = input.new_empty([m, n])
ops.infer.mm(input, mat, out)
return out
"""ixinfer support activations
/// @ingroup GEMM
typedef enum {
CUINFER_BLAS_GEMM_CUSTOM_NONE = 0,
CUINFER_BLAS_GEMM_CUSTOM_BIAS_ADD_ROW_OUT = 1,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS = 2,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_GELU = 3,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_RELU = 4,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_TRANSPOSE = 5,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS = 6,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS_GELU = 7,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS_RELU = 8,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS_TRANSPOSE = 9,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_SIGMOID = 10,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS_SIGMOID = 11,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_SILU = 12,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS_SILU = 13,
CUINFER_BLAS_GEMM_CUSTOM_SIGMOID = 14,
CUINFER_BLAS_GEMM_CUSTOM_SILU = 15,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_TANH = 16,
CUINFER_BLAS_GEMM_CUSTOM_FLOATBIAS_TANH = 17,
CUINFER_BLAS_GEMM_SPECIAL_INT8_FLOATBIAS = 18,
CUINFER_BLAS_GEMM_SPECIAL_INT8_FLOATBIAS_GELU = 19,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_SWISH = 20,
CUINFER_BLAS_GEMM_CUSTOM_HALFBIAS_ERF_GELU = 21
} cuinferGEMMCustomOption_t;
"""
activation_to_id = {
"fused_bias_col": 2, # support bf16, fp16
"fused_bias_gelu": 3, # support fp16
"fused_bias_relu": 4, # support fp16
}
id_to_activation = {value: key for key, value in activation_to_id.items()}
def ref_addmm(input, mat1, mat2, *, beta=1, alpha=1, out=None):
output_pt = torch.addmm(input, mat1, mat2, alpha=alpha, beta=beta)
return output_pt
def ref_fused_addmm_bias_col_act(
input, mat1, mat2, *, beta=1, alpha=1, out=None, bias=None, activation=2
):
if isinstance(activation, int):
assert activation in id_to_activation
if isinstance(activation, str):
assert activation in activation_to_id
activation = activation_to_id[activation]
if activation == 2:
output_pt = torch.addmm(input, mat1, mat2, alpha=alpha, beta=beta) + bias
elif activation == 3:
output_pt = torch.nn.functional.gelu(
torch.addmm(input, mat1, mat2, alpha=alpha, beta=beta) + bias
)
else:
output_pt = torch.nn.functional.relu(
torch.addmm(input, mat1, mat2, alpha=alpha, beta=beta) + bias
)
return output_pt
def addmm(input, mat1, mat2, *, beta=1, alpha=1, out=None):
"""
Args:
input: (m,n) torch.float16, torch.bfloat16, torch.float32
mat1: (m,k) torch.float16, torch.bfloat16, torch.float32
mat2: (k,n) torch.float16, torch.bfloat16, torch.float32
beta: float
alpha: float
out: (m,n) torch.float16, torch.bfloat16, torch.float32
Returns:
out: (m,n) torch.float16, torch.bfloat16, torch.float32
"""
assert mat1.dim() == mat2.dim(), "addmm mat1 mat2 tensors must be 2-D"
assert mat1.size(1) == mat2.size(
0
), f"addmm cannot be multiplied, {mat1.size(0)}X{mat1.size(1)} and {mat2.size(0)}X{mat2.size(1)}"
m = mat1.shape[0]
n = mat2.shape[-1]
if input is not None and len(input.shape) == 1:
input = input.view(1, -1)
if out is None:
out = input.new_empty([m, n])
if input is None:
input = out
beta = 0
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, None, 0)
return out
def fused_addmm_bias_col_act(
input, mat1, mat2, *, beta=1, alpha=1, out=None, bias=None, activation=2
):
"""
Args:
input: (m,n) torch.float16, torch.bfloat16, torch.float32
mat1: (m,k) torch.float16, torch.bfloat16, torch.float32
mat2: (k,n) torch.float16, torch.bfloat16, torch.float32
beta: float
alpha: float
out: (m,n) torch.float16, torch.bfloat16, torch.float32
当out的shape为(m,n)时,如果out是is_continouns,则bias 必须为(1,n), 否则,bias为(m,1)
bias: (1,n) or (m,1)
activation: str or int
"fused_bias_col": 2, "fused_bias_gelu": 3, "fused_bias_relu": 4
Returns:
out: (m,n) torch.float16, torch.bfloat16, torch.float32
"""
assert mat1.dim() == mat2.dim(), "addmm mat1 mat2 tensors must be 2-D"
assert mat1.size(1) == mat2.size(
0
), f"addmm cannot be multiplied, {mat1.size(0)}X{mat1.size(1)} and {mat2.size(0)}X{mat2.size(1)}"
if isinstance(activation, int):
assert activation in id_to_activation
if isinstance(activation, str):
assert activation in activation_to_id
activation = activation_to_id[activation]
m = mat1.shape[0]
n = mat2.shape[-1]
if out is None:
out = input.new_empty([m, n])
if input is None:
input = out
beta = 0
# activations
if activation == 2:
assert bias is not None
if mat1.dtype == torch.float:
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, None, 0)
out.add_(bias)
return out
elif activation == 3:
assert bias is not None
if mat1.dtype == torch.float:
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, None, 0)
out.add_(bias)
out.copy_(torch.nn.functional.gelu(out))
return out
elif mat1.dtype == torch.bfloat16:
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, bias, 2)
out.copy_(torch.nn.functional.gelu(out))
return out
elif activation == 4:
assert bias is not None
if mat1.dtype == torch.float:
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, None, 0)
out.add_(bias)
out.copy_(torch.nn.functional.relu(out))
return out
elif mat1.dtype == torch.bfloat16:
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, bias, 2)
out.copy_(torch.nn.functional.relu(out))
return out
ops.infer.addmm(input, mat1, mat2, beta, alpha, out, bias, activation)
return out
def ref_bmm(
input: torch.Tensor,
mat2: torch.Tensor,
alpha: float = 1,
format: str = "NN",
input_scales: torch.Tensor = None,
mat2_scales: torch.Tensor = None,
out_dtype: torch.dtype = None,
out: torch.Tensor = None,
):
if format[1] == "T":
input = input.transpose(-1, -2)
if format[0] == "T":
mat2 = mat2.transpose(-1, -2)
m = input.size(-2)
n = mat2.size(-1)
bs = input.size(0)
if input.dtype != torch.int8:
out_dtype = input.dtype
if out is None:
out = torch.empty([bs, m, n], dtype=out_dtype, device=input.device)
if input.dtype != torch.int8:
torch.bmm(input, mat2, out=out)
if alpha != 1:
out = out * alpha
else:
input = input.float() * input_scales.view(1, -1, 1)
mat2 = mat2.float() * mat2_scales.view(1, 1, -1)
out = torch.bmm(input.float(), mat2.float()) * alpha
out = out.to(out_dtype)
return out
def bmm(
input: torch.Tensor,
mat2: torch.Tensor,
alpha: float = 1,
format: str = "NN",
input_scales: torch.Tensor = None,
mat2_scales: torch.Tensor = None,
out_dtype: torch.dtype = None,
out: torch.Tensor = None,
):
"""
out = (input@mat2)*alpha
Support three formats:
format: "NN" input shape: (b, m, k) mat2 shape: (b, k, n) out shape: (b, m, n).
If the dtype of input is int8, the following conditions need to be met: n%64==0 k%64==0
format: "TN" input shape: (b, m, k) mat2 shape: (b, n, k) out shape: (b, m, n)
If the dtype of input is int8, the following conditions need to be met: n%2==0 k%64==0
format: "NT" input shape: (b, k, m) mat2 shape: (b, k, n) out shape: (b, m, n)
If the dtype of input is int8, the following conditions need to be met: m%64==0 n%64==0 k%64==0
If the dtype of input is int8, it is necessary to specify out_dtype.
Args:
input: (b, m, k) or (b, k, m) torch.float16, torch.bfloat16, int8
mat2: (b, k, n) or (b, n, k) torch.float16, torch.bfloat16, int8
alpha: float32
format: TN,NN,NT string
input_scales: (m) torch.float32
mat2_scales: (n) torch.float32
out_dtype: torch.float16, torch.bfloat16
out: (b, m, n) torch.float16, torch.bfloat16
Returns:
out: (b, m, n) torch.float16, torch.bfloat16
"""
if format[1] == "N":
m = input.size(-2)
k = input.size(-1)
else:
m = input.size(-1)
k = input.size(-2)
if format[0] == "N":
n = mat2.size(-1)
else:
n = mat2.size(-2)
if input.dtype == torch.int8:
if format == "TN":
assert (
n % 2 == 0 and k % 64 == 0
), f"bmm shape error, m={m} n={n} k={k}."
elif format == "NT":
assert (
m % 64 == 0 and n % 64 == 0 and k % 64 == 0
), f"bmm shape error, m={m} n={n} k={k}."
elif format == "NN":
assert (
n % 64 == 0 and k % 64 == 0
), f"bmm shape error, m={m} n={n} k={k}."
bs = input.size(0)
if out is None:
if input.dtype != torch.int8:
out_dtype = input.dtype
else:
assert out_dtype is not None
out = torch.empty([bs, m, n], dtype=out_dtype, device=input.device)
ops.infer.bmm(input, mat2, input_scales, mat2_scales, alpha, format, out)
return out

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
import itertools
from functools import partial
from typing import Callable, Dict, Iterable, Tuple
import torch
import torch.distributed as dist
import ixformer.distributed as ixfd
from ixformer.core.dispatcher import Dispatcher
from ixformer.core.operator_autotuning import (
OperatorPreBaseRangeAutotuning,
sync_ranks_metric,
)
from ixformer.distributed import overlap_comm
from ixformer.inference.overlap.linear_mlp_overlap_comm import linear_mlp_overlap
from ixformer.distributed.overlap_comm import GemmMethod
__all__ = ["linear_allreduce_overlap", "linear_mlp_overlap"]
class LinearAllReducePreAutotuning(OperatorPreBaseRangeAutotuning, Dispatcher):
def __init__(self, comm_group, *args, **kwargs):
dist_barrier = True
if "dist_barrier" in kwargs:
dist_barrier = kwargs.pop("dist_barrier")
super().__init__(dist_barrier=dist_barrier, *args, **kwargs)
self._comm_group = comm_group
self._world_size = ixfd.get_group_world_size(comm_group)
@classmethod
def dispatcher_key(cls, comm_group, *args, **kwargs):
return (comm_group,)
def operators(self):
chunks = [2, 4]
gemm_algos = [GemmMethod.kCUINFER, GemmMethod.kCUBLAS, GemmMethod.kLIMITED_GEMM]
candidate_ops = [overlap_comm.GemmAllReduceSplitOverlapComm.native_forward]
for num_chunks, algo in itertools.product(chunks, gemm_algos):
candidate_ops.append(
partial(
overlap_comm.linear_allreduce_overlap,
num_chunks=num_chunks,
gemm_method=algo,
)
)
return candidate_ops
@property
def _gemm_shapes(self):
basic_k = [4096, 6114, 8192]
tp_k = [k // self._world_size for k in basic_k]
basic_k = tp_k
basic_m = (512, 1024, 2048, 4096, 8192)
shapes = set(itertools.product(basic_m, basic_k))
return shapes
def get_operator_key(self, input, *args, **kwargs):
ndim = input.ndim
shape = input.shape
if ndim == 1:
return (1, shape[0])
elif ndim == 2:
return shape
else:
return (sum(shape[:-1]), shape[-1])
def generate_operator_inputs(self) -> Iterable[Tuple[Tuple, Dict]]:
for m, kn in self._gemm_shapes:
input = torch.randn(m, kn, device="cuda", dtype=torch.half)
weight = torch.randn(kn, kn, device="cuda", dtype=torch.half)
yield (input, weight), {}
def perf_operator_time(self, op: Callable, *args, **kwargs) -> float:
op_time = super().perf_operator_time(op, *args, **kwargs)
return sync_ranks_metric(op_time, group=self._comm_group)
linear_allreduce_overlap = overlap_comm.linear_allreduce_overlap

View File

@@ -0,0 +1,143 @@
from typing import Union
import ixformer._C as ops
import torch
__all__ = [
"paged_attention",
"paged_attention_flashinfer",
"paged_attention_cache_appended",
]
# paged_attention_cache_append
def paged_attention_cache_appended(
key: torch.Tensor,
value: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
slot_mapping: torch.Tensor,
kv_cache_format: str = "HND", # STD NHD HND
key_cache_scales: torch.Tensor = None,
value_cache_scales: torch.Tensor = None,
):
if isinstance(key, torch.Tensor):
ops.infer.paged_attention_cache_appended(
key,
value,
key_cache,
value_cache,
slot_mapping,
key.stride(0),
value.stride(0),
key_cache.stride(0),
value_cache.stride(0),
kv_cache_format,
key_cache_scales,
value_cache_scales,
)
else:
raise NotImplementedError()
def paged_attention(
output: torch.Tensor,
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
num_kv_heads: int,
scale: float,
block_tables: torch.Tensor,
seq_lens: torch.Tensor,
block_size: int,
max_seq_len: int,
alibi_slopes: torch.Tensor = None,
use_sqrt_alibi: bool = False,
key_cache_scales: torch.Tensor = None,
value_cache_scales: torch.Tensor = None,
kv_cache_format: str = "HND",
algo: int = -1,
):
"""
kv_cache_format
STD : k/v format as same as vllm
NHD : k/v format is [block_size, num_kv_heads, head_dim] in one page
HND : k/v format is [num_kv_heads, block_size, head_dim] in one page
algo
-1 : auto chooes algorithm according to kv_cache_format
0 : use the first algorithm
1 : use the second algorithm
"""
if isinstance(query, torch.Tensor):
ops.infer.paged_attention(
output,
query,
key_cache,
value_cache,
num_kv_heads,
scale,
block_tables,
seq_lens,
block_size,
max_seq_len,
use_sqrt_alibi,
alibi_slopes,
key_cache_scales,
value_cache_scales,
kv_cache_format,
algo,
)
else:
raise NotImplementedError()
def paged_attention_flashinfer(
output: torch.Tensor,
query: torch.Tensor,
paged_kv_data,
paged_kv_indptr: torch.Tensor,
paged_kv_indices: torch.Tensor,
paged_kv_last_page_len: torch.Tensor,
scale: float,
max_seq_len: int = -1,
use_sqrt_alibi: bool = False,
alibi_slopes: torch.Tensor = None,
kv_cache_format: str = "HND",
# key_cache_scales: torch.Tensor = None,
# value_cache_scales: torch.Tensor = None,
):
"""
out / query : [num_seqs, num_qo_heads, head_size]
paged_kv_data
Tensor:
NHD [max_num_pages, 2, page_size, num_kv_heads, head_size]
HND [max_num_pages, 2, num_kv_heads, page_size, head_size]
tuple(k_data, v_data)
NHD [max_num_pages, page_size, num_kv_heads, head_size]
HND [max_num_pages, num_kv_heads, page_size, head_size]
paged_kv_indptr int32 : [num_seqs + 1]
paged_kv_indices int32 : [max_num_pages]
paged_kv_last_page_len int32 : [num_seqs]
"""
if isinstance(paged_kv_data, tuple):
k_data, v_data = paged_kv_data
pack_kv_data = (None, k_data, v_data)
else:
pack_kv_data = (paged_kv_data, None, None)
if isinstance(query, torch.Tensor):
ops.infer.paged_attention_flashinfer(
output,
query,
*pack_kv_data,
paged_kv_indptr,
paged_kv_indices,
paged_kv_last_page_len,
scale,
max_seq_len,
use_sqrt_alibi,
alibi_slopes,
kv_cache_format,
)
else:
raise NotImplementedError()

View File

@@ -0,0 +1,238 @@
from typing import Union
import ixformer._C as ops
import torch
from torch import Tensor
__all__ = [
"quantized_linear",
"quantized_weight_dequant",
"ref_quantized_weight_dequant",
"weight_quantize",
]
def quantized_linear(
inputs: torch.Tensor,
qweights: torch.Tensor,
scales: torch.Tensor,
quant_type: str,
bits: int,
qzeros: torch.Tensor = None,
bias: torch.Tensor = None,
group_size: int = -1,
g_idx: torch.Tensor = None,
format: str = "unknown",
):
"""
QuantType inputs qweights Scales bits qzeros bias GroupSize Format ApiCall 备注
awq (bs, ic) bf16/fp16 int32 NN:(ic, oc // 8) TN:(oc, ic // 8) (ic // group_size, oc)fp16/bf16 4/8 int32(ic // group_size, oc // 8) (oc) or None fp16/bf16 32/128 TN/NN vllm & auto-awq
gptq (bs, ic) bf16/fp16 int32 (ic//8, oc) (ic // group_size, oc)fp16/bf16 4 int32(ic // group_size, oc // 8) (oc) or None fp16/bf16 ic/128 \ auto-gptq bs 只支持到8
fp4 (bs, ic) bf16/fp16 uint8 (oc * ic // 2, 1) (oc * ic // group_size)fp32 4 \ (oc) or None fp16/bf16 64 \ bitsandbytes bs 只支持到8
nf4 (bs, ic) bf16/fp16 uint8 (oc * ic // 2, 1) (oc * ic // group_size)fp32 4 \ (oc) or None fp16/bf16 64 \ bitsandbytes bs 只支持到8
int8 (bs, ic) bf16/fp16 int8 TN:(oc, ic) NN:(ic, oc) (1, oc)fp16/bf16 8 \ (oc) or None fp16/bf16 -1 TN/NN vllm & bitsandbytes
"""
if isinstance(inputs, torch.Tensor) and not inputs.requires_grad:
return ops.infer.quantized_linear(
inputs,
qweights,
scales,
quant_type,
bits,
qzeros,
bias,
group_size,
g_idx,
format,
)
raise NotImplementedError()
def quantized_weight_dequant(
qweights: torch.Tensor,
scales: torch.Tensor,
quant_type: str,
output_type: str,
bits: int,
qzeros: torch.Tensor = None,
group_size: int = -1,
g_idx: torch.Tensor = None,
):
"""
Args:
qweights: (oc, ic//2) or (ic// (32/bits, oc) torch.unint8 or torch.int32
scales: (oc * ic//g) or (ic // g, oc) torch.float16, torch.bfloat16, torch.float32
quant_type: str
可选项:fp4/nf4/gptq/gptq-ex
output_type: str
可选项:fp16/bf16
bits: int
可选项:4/8
qzeros: (ic//g, oc//(32/bits)) torch.int32
group_size: int
可选项:-1/64/128
g_idx: (ic) torch.int
Returns:
Tensor: (oc, ic) or (ic, oc) torch.float16, torch.bfloat16
quant_type qweights scales qzeros output_type bits group_size g_idx
fp4/nf4 (oc, ic//2) uint8 (oc * ic//g) fp32 fp16/bf16 / 64/128 /
gptq/gptq-ex (ic// (32/bits, oc) int32 (ic // g, oc) fp16/bf16 (ic // g, oc // (32/bits)) int32 fp16/bf16 4/8 -1 (ic)
"""
if isinstance(qweights, torch.Tensor) and not qweights.requires_grad:
return ops.infer.quantized_weight_dequant(
qweights, scales, quant_type, output_type, bits, qzeros, group_size, g_idx
)
raise NotImplementedError()
def ref_quantized_weight_dequant(
qweights: torch.Tensor,
scales: torch.Tensor,
quant_type: str,
output_type: torch.dtype,
bits: int,
qzeros: torch.Tensor = None,
group_size: int = -1,
g_idx: torch.Tensor = None,
order_map: list = None,
):
assert quant_type in ["awq"]
if quant_type == "awq":
# qweights:(k, n/8) int32
# scale:(k/group_size, n) f16
# qzeros:(k/group_size, n/8) int32
ic, oc = qweights.shape[0], scales.shape[1]
assert bits == 4
if order_map is None:
order_map = [0, 2, 4, 6, 1, 3, 5, 7]
order_map = torch.Tensor(order_map).to(torch.int32).to(qweights.device)
order_map = order_map.argsort()
# (1, 8)
wf = (
torch.tensor(list(range(0, 32, bits)), dtype=torch.int32)
.unsqueeze(0)
.to(qweights.device)
)
# unpack qzeros
unpack_zeros = torch.bitwise_right_shift(
torch.unsqueeze(qzeros, 2).expand(-1, -1, 32 // bits), wf.unsqueeze(0)
).to(torch.int16 if bits == 8 else torch.int8)
unpack_zeros = unpack_zeros[:, :, order_map]
unpack_zeros = torch.bitwise_and(unpack_zeros, (2**bits) - 1)
# groups, 1, n
unpack_zeros = unpack_zeros.reshape(unpack_zeros.shape[0], 1, -1)
# unpack weights
unpack_weights = torch.bitwise_right_shift(
torch.unsqueeze(qweights, 2).expand(-1, -1, 32 // bits),
wf.unsqueeze(0),
).to(torch.int16 if bits == 8 else torch.int8)
unpack_weights = unpack_weights[:, :, order_map]
unpack_weights = torch.bitwise_and(unpack_weights, (2**bits) - 1)
# w : groups, group_size, n
unpack_weights = unpack_weights.reshape(
-1, group_size, unpack_weights.shape[1] * unpack_weights.shape[2]
)
deq_weights = (unpack_weights - unpack_zeros) * scales.reshape(
-1, 1, scales.shape[-1]
)
deq_weights = deq_weights.reshape(ic, oc)
return deq_weights.to(output_type)
def create_dynamic_map(signed=True, max_exponent_bits=7, total_bits=8):
"""
Creates the dynamic quantiztion map.
The dynamic data type is made up of a dynamic exponent and
fraction. As the exponent increase from 0 to -7 the number
of bits available for the fraction shrinks.
This is a generalization of the dynamic type where a certain
number of the bits and be reserved for the linear quantization
region (the fraction). n determines the maximum number of
exponent bits.
For more details see
(8-Bit Approximations for Parallelism in Deep Learning)[https://arxiv.org/abs/1511.04561]
"""
data = []
# these are additional items that come from the case
# where all the exponent bits are zero and no
# indicator bit is present
non_sign_bits = total_bits - (1 if signed else 1)
additional_items = 2 ** (non_sign_bits - max_exponent_bits) - 1
for i in range(max_exponent_bits):
fraction_items = int(
2 ** (i + non_sign_bits - max_exponent_bits) + 1
if signed
else 2 ** (i + non_sign_bits - max_exponent_bits + 1) + 1,
)
boundaries = torch.linspace(0.1, 1, fraction_items)
means = (boundaries[:-1] + boundaries[1:]) / 2.0
data += ((10 ** (-(max_exponent_bits - 1) + i)) * means).tolist()
if signed:
data += (-(10 ** (-(max_exponent_bits - 1) + i)) * means).tolist()
if additional_items > 0:
boundaries = torch.linspace(0.1, 1, additional_items + 1)
means = (boundaries[:-1] + boundaries[1:]) / 2.0
data += ((10 ** (-(max_exponent_bits - 1) + i)) * means).tolist()
if signed:
data += (-(10 ** (-(max_exponent_bits - 1) + i)) * means).tolist()
data.append(0)
data.append(1.0)
assert len(data) == 2**total_bits
gap = 256 - len(data)
for i in range(gap):
data.append(0)
data.sort()
return Tensor(data)
def weight_quantize(
A: torch.Tensor,
absmax: torch.Tensor,
out: torch.Tensor,
blocksize: int,
n: int,
quant_dtype: str,
code: torch.Tensor = None,
):
"""
Args:
A: (row ,col) torch.float16, torch.bfloat16, torch.float32
absmax: (blocks) torch.float32
blocks = n // blocksize, blocks += 1 if n % blocksize > 0 else 0
quant_dtype: str
目前可支持"int8"/"fp4"/"nf4"
blocksize: int
目前只支持4096, 2048, 1024, 512, 256, 128, 64
n: int
n = A.numel()
out: (row ,col) torch.int8
code: torch.float32
the quantization map
Returns:
out: (row ,col) torch.int8
"""
assert quant_dtype == "int8" or "fp4" or "nf4"
if code is None and quant_dtype == "int8":
code = create_dynamic_map().to(A.device)
if isinstance(A, torch.Tensor) and not A.requires_grad:
ops.infer.weight_quantize(A, absmax, out, blocksize, n, quant_dtype, code)
else:
raise NotImplementedError()

View File

@@ -0,0 +1,47 @@
from typing import Union
import ixformer._C as ops
import torch
__all__ = ["residual_bias", "ref_residual_bias"]
def ref_residual_bias(
input: torch.Tensor,
residual: torch.Tensor,
bias: torch.Tensor = None,
alpha: float = 1,
):
if bias is not None:
output = residual.float() * alpha + input.float() + bias.float()
else:
output = residual.float() * alpha + input.float()
return output.to(residual.dtype)
def residual_bias(
input: torch.Tensor,
residual: torch.Tensor,
bias: torch.Tensor = None,
alpha: float = 1,
output: torch.Tensor = None
):
"""
Args:
input: [batch_count, seq_len, hidden_size] or [batch_tokens, hidden_size] torch.half
residual: [batch_count, seq_len, hidden_size] or [batch_tokens, hidden_size] torch.half
bias: [hidden_size] torch.half
alpha: float
Returns:
output: [batch_count, seq_len, hidden_size] or [batch_tokens, hidden_size] torch.half
"""
if output is None:
output = torch.empty_like(input)
if alpha is None:
alpha = 1
if bias is not None:
ops.train.add_residual_bias_forward(input, residual, bias, alpha, output)
else:
ops.train.add_residual_bias_forward(input, residual, alpha, output)
return output

View File

@@ -0,0 +1,134 @@
from typing import Union
import ixformer._C as ops
import torch
from torch.nn import init
__all__ = ["ref_rms_norm", "rms_norm", "ref_residual_rms_norm", "residual_rms_norm"]
def ref_rms_norm(
input: torch.Tensor,
weight: torch.Tensor,
eps: float = 1e-5,
output: torch.Tensor = None,
):
dtype = input.dtype
input = input.float()
weight = weight.float()
rms_out = input * torch.rsqrt(input.pow(2).mean(-1, keepdim=True) + eps)
rms_out = rms_out * weight
rms_out = rms_out.to(dtype)
if output is not None:
output.copy_(rms_out)
else:
output = rms_out
return output
def rms_norm(
input: torch.Tensor,
weight: torch.Tensor,
eps: float = 1e-5,
output: torch.Tensor = None,
):
"""
This function is deprecated, please use residual_rms_norm.
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
eps: float32
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
"""
if output is None:
output = torch.empty_like(input)
ops.infer.rms_norm(input, weight, output, None, eps)
return output
def ref_residual_rms_norm(
input: torch.Tensor,
weight: torch.Tensor,
eps: float = 1e-5,
residual_alpha: float = 1.0,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
is_post: bool = False,
):
dtype = input.dtype
if residual_bias is not None:
input = input + residual_bias
if residual is not None:
residual_output = torch.add(
input, residual * residual_alpha, out=residual_output
)
input = input.float() + residual.float() * residual_alpha
else:
input = input.float()
weight = weight.float()
rms_out = input * torch.rsqrt(input.pow(2).mean(-1, keepdim=True) + eps)
rms_out = rms_out * weight
rms_out = rms_out.to(dtype)
if output is not None:
output.copy_(rms_out)
else:
output = rms_out
if is_post and residual_output is not None:
residual_output = output
return output, residual_output
def residual_rms_norm(
input: torch.Tensor,
weight: torch.Tensor,
eps: float = 1e-5,
residual_alpha: float = 1.0,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
is_post: bool = False,
):
"""
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
eps: float32
residual_alpha: float32
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
is_post: bool
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on input.
residual_output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on residual.
"""
if residual is None:
if output is None:
output = torch.empty_like(input)
ops.infer.rms_norm(input, weight, output, residual_bias, eps)
else:
ops.infer.residual_rms_norm(
input,
residual,
weight,
output,
residual_output,
residual_bias,
residual_alpha,
eps,
is_post,
)
residual_output = residual_output if residual_output is not None else residual
output = output if output is not None else input
return output, residual_output

View File

@@ -0,0 +1,120 @@
import math
from typing import List, Union
import torch
from .flash_attn import ixinfer_flash_attn_pad
__all__ = ["scaled_dot_product_attention", "ref_scaled_dot_product_attention"]
def ref_scaled_dot_product_attention(
query: "torch.Tensor",
key: "torch.Tensor",
value: "torch.Tensor",
attn_mask=None,
dropout_p=0.0,
is_causal=False,
):
assert len(query.shape) >= 3
assert len(key.shape) >= 3
assert len(value.shape) >= 3
batch_size = query.shape[0]
L = query.shape[-2]
S = key.shape[-2]
if is_causal and attn_mask is not None:
raise RuntimeError()
if attn_mask is None and is_causal is False:
attn_mask = torch.ones([batch_size, 1, 1, S]).bool().to(query.device)
if is_causal:
attn_mask = torch.ones(L, S, dtype=torch.bool).tril(diagonal=0).to(query.device)
if attn_mask.dtype == torch.bool:
attn_mask = (
torch.zeros_like(attn_mask).to(query.dtype).masked_fill(~attn_mask, -10000)
)
# attn_mask = torch.ones(L, S, dtype=torch.bool).tril(diagonal=0) if is_causal else attn_mask
# attn_mask = attn_mask.masked_fill(not attn_mask, -float('inf')) if attn_mask.dtype==torch.bool else attn_mask
attn_weight = torch.softmax(
(query @ key.transpose(-2, -1) / math.sqrt(query.size(-1))) + attn_mask, dim=-1
)
attn_weight = torch.dropout(attn_weight, dropout_p, True)
return attn_weight.to(query.dtype) @ value
def scaled_dot_product_attention(
query: "torch.Tensor",
key: "torch.Tensor",
value: "torch.Tensor",
attn_mask=None,
dropout_p=0.0,
is_causal=False,
):
# 1. pytorch版本 query,key的head_dim相等 value可以不相等。但是我们实现的版本需要相等
# 2. pytorch版本的 L, S可以不相等 但是我们必须相等并且为64的倍数
# 3. pytorch支持 加上 mask
# 4. mask: 0 表示padding[与后端相反所以注意如果用传进来的取反转int如果是自己生成则直接生成后端的mask]
"""
Args:
query: (N, ..., L, E) torch.float16, torch.bfloat16
key: (N, ..., S, E) torch.float16, torch.bfloat16
value: (N, ..., S, E) torch.float16, torch.bfloat16
attn_mask: (N, ..., L, S) bool, torch.float32
dropout_p: float32
Dropout probability; if greater than 0.0, dropout is applied
is_causal: bool
If true, assumes causal attention masking and errors if both attn_mask and is_causal are set.
Returns:
Tensor: (N, ..., L, E) torch.float16, torch.bfloat16
"""
assert len(query.shape) >= 4, "len(query.shape) <4"
assert len(key.shape) >= 4, "len(key.shape) <4"
assert len(value.shape) >= 4, "len(value.shape) <4"
query_shape = list(query.shape)
key_shape = list(key.shape)
value_shape = list(value.shape)
batch_size = query_shape[0]
q_seq_len = query_shape[-2]
kv_seq_len = key_shape[-2]
if len(query_shape) > 4:
query.view(batch_size, -1, q_seq_len, query_shape[-1])
if len(key_shape) > 4:
key.view(batch_size, -1, kv_seq_len, key_shape[-1])
if len(value_shape) > 4:
value.view(batch_size, -1, kv_seq_len, value_shape[-1])
training = query.requires_grad
atten_scale = 1.0 / (query.size(-1) ** 0.5)
if is_causal and attn_mask is not None: # 两者必须有有一个
raise RuntimeError()
head_num = query.size(1)
# 注意training,inference都支持mask广播
if training:
raise NotImplementedError("not support training!")
else: # inference支持mask广播
if attn_mask is None and is_causal is False: # mask 全0
attn_mask = None
# attn_mask = (
# torch.zeros([batch_size, 1, 1, kv_seq_len]).int().to(query.device)
# ) # 底层代码1代表mask0代表保留
elif is_causal: # mask 下三角是0上三角是1
attn_mask = (
torch.ones([batch_size, 1, q_seq_len, kv_seq_len], dtype=torch.int)
.triu(diagonal=1)
.to(query.device)
) # 上三角是1下三角和对角线是0
elif attn_mask is not None: # 外部传进来的取反转int
assert attn_mask.dtype == torch.bool or attn_mask.dtype == torch.float
assert attn_mask.dim() == 4 # 必须是4维
if attn_mask.dtype == torch.bool:
attn_mask = (~attn_mask).int() # 取非操作然后转int
return ixinfer_flash_attn_pad(query, key, value, attn_mask, None, atten_scale)

View File

@@ -0,0 +1,510 @@
import ixformer._C as ops
import torch
import torch.nn.functional as NNF
__all__ = [
"ref_dynamic_scaled_quant_dynamic_int8",
"dynamic_scaled_quant_dynamic_int8",
"dynamic_scaled_quant_smoothquant",
"ref_silu_and_mul_smoothquant",
"silu_and_mul_smoothquant",
"ref_residual_rms_norm_dynamic_int8",
"residual_rms_norm_dynamic_int8",
"ref_residual_layer_norm_dynamic_int8",
"residual_layer_norm_dynamic_int8",
"ref_layer_norm_2sb_smoothquant",
"layer_norm_2sb_smoothquant",
"ref_residual_layer_norm_2sb_smoothquant",
"residual_layer_norm_2sb_smoothquant",
]
def ref_dynamic_scaled_quant_dynamic_int8(
input: torch.Tensor,
smooth_scales: torch.Tensor = None,
i8_output: torch.Tensor = None,
output_scales: torch.Tensor = None,
):
if i8_output is None:
i8_output = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
scales_shape = input.shape[:-1]
output = input.float()
if smooth_scales is not None:
output *= smooth_scales.view(1, -1)
amax_, _ = torch.max(torch.abs(output), dim=-1, keepdim=True)
scales = amax_ / 127.0
output = output / scales
output = torch.clamp(torch.round(output), -127, 127).to(torch.int8)
if i8_output is not None:
i8_output.copy_(output)
output = i8_output
if output_scales is not None:
output_scales.view(-1).copy_(scales.view(-1))
scales = output_scales
return output, scales.view(scales_shape)
def dynamic_scaled_quant_dynamic_int8(
input: torch.Tensor,
smooth_scales: torch.Tensor = None,
i8_output: torch.Tensor = None,
output_scales: torch.Tensor = None,
):
"""
Args:
input: (..., k) torch.float16,torch.bfloat16
smooth_scales: (k) torch.float16,torch.bfloat16
if smooth_scales is None, api is dynamic-per-token quantization.
Returns:
i8_output: (..., k) torch.int8
output_scales: (...) torch.float32
"""
if i8_output is None:
i8_output = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
hidden_size = input.shape[-1]
if smooth_scales is None:
ops.infer.scaled_int8_quant(i8_output, input, output_scales, 1)
return i8_output, output_scales
ops.infer.dynamic_scaled_quant_smoothquant(
input.view(-1, hidden_size),
smooth_scales,
i8_output.view(-1, hidden_size),
output_scales,
)
return i8_output, output_scales
# For backward compatibility
dynamic_scaled_quant_smoothquant = dynamic_scaled_quant_dynamic_int8
def ref_silu_and_mul_smoothquant(
input, smooth_scales, i8_output=None, output_scales=None
):
x1, x2 = input.chunk(chunks=2, dim=-1)
x = NNF.silu(x1) * x2
return ref_dynamic_scaled_quant_dynamic_int8(
x, smooth_scales, i8_output, output_scales
)
def silu_and_mul_smoothquant(input, smooth_scales, i8_output=None, output_scales=None):
"""
Args:
input: (..., 2*k) torch.float16,torch.bfloat16
smooth_scales: (k) torch.float16,torch.bfloat16
if smooth_scales is None, api is dynamic-per-token quantization.
Returns:
i8_output: (..., k) torch.int8
output_scales: (...) torch.float32
"""
if i8_output is None:
output_shape = input.shape[:-1] + (input.shape[-1] // 2,)
i8_output = torch.empty(output_shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
ops.infer.silu_and_mul_smoothquant(i8_output, input, smooth_scales, output_scales)
return i8_output, output_scales
def ref_residual_rms_norm_dynamic_int8(
input: torch.Tensor,
weight: torch.Tensor,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
eps: float = 1e-5,
smooth_scales: torch.Tensor = None,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
output_scales: torch.Tensor = None,
is_post: bool = False,
):
dtype = input.dtype
if output is None:
output = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
if residual_bias is not None:
input = input + residual_bias
if residual is not None:
residual_output = torch.add(input, residual, out=residual_output)
input = residual_output
input = input.float()
weight = weight.float()
rms_output = input * torch.rsqrt(input.pow(2).mean(-1, keepdim=True) + eps)
rms_output = (rms_output * weight).to(dtype)
if residual is not None and is_post:
residual_output.copy_(rms_output)
output, output_scales = ref_dynamic_scaled_quant_dynamic_int8(
rms_output, smooth_scales, output, output_scales.view(-1)
)
return output, residual_output, output_scales
def residual_rms_norm_dynamic_int8(
input: torch.Tensor,
weight: torch.Tensor,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
eps: float = 1e-5,
smooth_scales: torch.Tensor = None,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
output_scales: torch.Tensor = None,
is_post: bool = False,
):
"""
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
eps: float32
smooth_scales: (hidden_size) torch.float16, torch.bfloat16, torch.float32
is_post: bool
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on residual.
output_scales: (...) torch.float16, torch.bfloat16, torch.float32
"""
if output is None:
output = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
if residual is None:
ops.infer.rmsnorm_dynamic_int8(
input, weight, output, output_scales, smooth_scales, residual_bias, eps
)
else:
ops.infer.residual_rmsnorm_dynamic_int8(
input,
residual,
weight,
output,
output_scales,
smooth_scales,
residual_output,
residual_bias,
eps,
is_post,
)
residual_output = residual if residual_output is None else residual_output
return output, residual_output, output_scales
def ref_residual_layer_norm_dynamic_int8(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
eps: float = 1e-5,
smooth_scales: torch.Tensor = None,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
output_scales: torch.Tensor = None,
):
normalized_shape = [weight.size(-1)]
if output is None:
output = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
if residual_bias is not None:
input = input + residual_bias
if residual is not None:
residual_output = torch.add(input, residual, out=residual_output)
input = residual_output
norm_output = torch.nn.functional.layer_norm(
input, normalized_shape, weight, bias, eps=eps
)
output, output_scales = ref_dynamic_scaled_quant_dynamic_int8(
norm_output, smooth_scales, output, output_scales.view(-1)
)
return output, residual_output, output_scales
def residual_layer_norm_dynamic_int8(
input: torch.Tensor,
weight: torch.Tensor,
bias: torch.Tensor,
residual: torch.Tensor = None,
residual_bias: torch.Tensor = None,
eps: float = 1e-5,
smooth_scales: torch.Tensor = None,
output: torch.Tensor = None,
residual_output: torch.Tensor = None,
output_scales: torch.Tensor = None,
):
"""
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
eps: float32
smooth_scales: (hidden_size) torch.float16, torch.bfloat16, torch.float32
Returns:
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
residual_output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on residual.
output_scales: (...) torch.float16, torch.bfloat16, torch.float32
"""
if output is None:
output = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales is None:
output_scales = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
if residual is None:
ops.infer.layer_norm_dynamic_int8(
input,
weight,
bias,
output,
output_scales,
smooth_scales,
residual_bias,
eps,
)
else:
ops.infer.residual_layer_norm_dynamic_int8(
input,
residual,
weight,
bias,
output,
output_scales,
smooth_scales,
residual_output,
residual_bias,
eps,
)
residual_output = residual_output if residual_output is not None else residual
return output, residual_output, output_scales
def ref_layer_norm_2sb_smoothquant(
input,
weight1,
bias1,
smooth_scales1,
weight2,
bias2,
smooth_scales2,
i8_output1=None,
output_scales1=None,
i8_output2=None,
output_scales2=None,
eps=1e-5,
):
input1 = torch.nn.functional.layer_norm(
input, [weight1.shape[-1]], weight1, bias1, eps=eps
)
input2 = torch.nn.functional.layer_norm(
input, [weight2.shape[-1]], weight2, bias2, eps=eps
)
i8_output1, output_scales1 = ref_dynamic_scaled_quant_dynamic_int8(
input1, smooth_scales1, i8_output1, output_scales1
)
i8_output2, output_scales2 = ref_dynamic_scaled_quant_dynamic_int8(
input2, smooth_scales2, i8_output2, output_scales2
)
return i8_output1, output_scales1, i8_output2, output_scales2
def layer_norm_2sb_smoothquant(
input,
weight1,
bias1,
smooth_scales1,
weight2,
bias2,
smooth_scales2,
output1=None,
output_scales1=None,
output2=None,
output_scales2=None,
eps=1e-5,
):
"""
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16
weight1: (hidden_size) torch.float16, torch.bfloat16
bias1: (hidden_size) torch.float16, torch.bfloat16
smooth_scales1: (hidden_size) torch.float16, torch.bfloat16
weight2: (hidden_size) torch.float16, torch.bfloat16
bias2: (hidden_size) torch.float16, torch.bfloat16
smooth_scales2: (hidden_size) torch.float16, torch.bfloat16
eps: float32
Returns:
output1: (..., hidden_size) torch.float16, torch.bfloat16
output_scales1: (...) torch.float16, torch.bfloat16
output2: (..., hidden_size) torch.float16, torch.bfloat16
output_scales2: (...) torch.float16, torch.bfloat16
"""
if output1 is None:
output1 = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales1 is None:
output_scales1 = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
if output2 is None:
output2 = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales2 is None:
output_scales2 = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
ops.infer.layer_norm_2sb_smoothquant(
input,
weight1,
bias1,
smooth_scales1,
weight2,
bias2,
smooth_scales2,
output1,
output_scales1,
output2,
output_scales2,
eps,
)
return output1, output_scales1, output2, output_scales2
def ref_residual_layer_norm_2sb_smoothquant(
input,
residual,
weight1,
bias1,
smooth_scales1,
weight2,
bias2,
smooth_scales2,
i8_output1=None,
output_scales1=None,
i8_output2=None,
output_scales2=None,
eps=1e-5,
):
residual_out = input + residual
input1 = torch.nn.functional.layer_norm(
residual_out, [weight1.shape[-1]], weight1, bias1, eps=eps
)
input2 = torch.nn.functional.layer_norm(
residual_out, [weight2.shape[-1]], weight2, bias2, eps=eps
)
i8_output1, output_scales1 = ref_dynamic_scaled_quant_dynamic_int8(
input1, smooth_scales1, i8_output1, output_scales1
)
i8_output2, output_scales2 = ref_dynamic_scaled_quant_dynamic_int8(
input2, smooth_scales2, i8_output2, output_scales2
)
return residual_out, i8_output1, output_scales1, i8_output2, output_scales2
def residual_layer_norm_2sb_smoothquant(
input,
residual,
weight1,
bias1,
smooth_scales1,
weight2,
bias2,
smooth_scales2,
output1=None,
output_scales1=None,
output2=None,
output_scales2=None,
eps=1e-5,
):
"""
Args:
input: (..., hidden_size) torch.float16, torch.bfloat16
residual: (..., hidden_size) torch.float16, torch.bfloat16
weight1: (hidden_size) torch.float16, torch.bfloat16
bias1: (hidden_size) torch.float16, torch.bfloat16
smooth_scales1: (hidden_size) torch.float16, torch.bfloat16
weight2: (hidden_size) torch.float16, torch.bfloat16
bias2: (hidden_size) torch.float16, torch.bfloat16
smooth_scales2: (hidden_size) torch.float16, torch.bfloat16
eps: float32
Returns:
output1: (..., hidden_size) torch.float16, torch.bfloat16
output_scales1: (...) torch.float16, torch.bfloat16
output2: (..., hidden_size) torch.float16, torch.bfloat16
output_scales2: (...) torch.float16, torch.bfloat16
"""
if output1 is None:
output1 = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales1 is None:
output_scales1 = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
if output2 is None:
output2 = torch.empty(input.shape, dtype=torch.int8, device=input.device)
if output_scales2 is None:
output_scales2 = torch.empty(
input.shape[:-1], dtype=torch.float32, device=input.device
)
ops.infer.residual_layer_norm_2sb_smoothquant(
input,
residual,
weight1,
bias1,
smooth_scales1,
weight2,
bias2,
smooth_scales2,
output1,
output_scales1,
output2,
output_scales2,
eps,
)
return residual, output1, output_scales1, output2, output_scales2

View File

@@ -0,0 +1,33 @@
from typing import Union
import ixformer._C as ops
import torch
from torch.autograd.function import Function, FunctionCtx
__all__ = ["softmax", "ref_softmax"]
def ref_softmax(input: torch.Tensor, dim: int = None, _stacklevel: int = 3, dtype=None):
out = torch.nn.functional.softmax(
input, dim=dim, _stacklevel=_stacklevel, dtype=dtype
)
return out
def softmax(input: torch.Tensor, dim=None, _stacklevel=3, dtype=None):
"""
Args:
input: (...) torch.float16
dim: int
要进行softmax的维度,目前只支持最后一维, dim==-1 or dim == input.dim()-1
_stacklevel: int
这个参数只是为了与pytorch中对齐。 stacklevel is used in python to indicate warning mechanism how far up the stack it has to go to find the line that called the function which issued the warning.
dtype: torch.float16
Returns:
Tensor: (...) torch.float16
"""
output = torch.empty_like(input)
ops.infer.softmax(input, output, dim)
output = output.to(dtype)
return output

View File

@@ -0,0 +1,75 @@
import ixformer._C as ops
import torch
__all__ = [
"store_kv_cache",
"ref_store_kv_cache",
]
def ref_store_kv_cache(
k: torch.Tensor,
v: torch.Tensor,
k_cache: torch.Tensor,
v_cache: torch.Tensor,
cache_batch_idx: torch.Tensor,
cache_seqlens: torch.Tensor,
):
"""
Args:
k: (batch_size, seqlen_new, head_num, head_dim) torch.float16, torch.bfloat16
v: (batch_size, seqlen_new, head_num, head_dim) torch.float16, torch.bfloat16
k_cache: (batch_size_cache, seqlen_cache, head_num, head_dim) torch.float16, torch.bfloat16
v_cache: (batch_size_cache, seqlen_cache, head_num, head_dim) torch.float16, torch.bfloat16
cache_batch_idx: (batch_size,) torch.int32
The indices used to index into the KV cache.
cache_seqlens: (batch_size,) torch.int32
The sequence lengths of the KV cache.
Returns:
None
"""
# 等价实现
# concatenate k with k_cache, starting at the indices specified by cache_seqlens.
seqlen_new = k.size(1)
for kv_batch_idx, cache_kv_batch_idx in enumerate(cache_batch_idx):
cache_len = cache_seqlens[kv_batch_idx]
cache_start_idx = cache_len
cache_end_idx = cache_len + seqlen_new
k_cache[cache_kv_batch_idx, cache_start_idx:cache_end_idx] = k[kv_batch_idx]
v_cache[cache_kv_batch_idx, cache_start_idx:cache_end_idx] = v[kv_batch_idx]
def store_kv_cache(
k: torch.Tensor,
v: torch.Tensor,
k_cache: torch.Tensor,
v_cache: torch.Tensor,
cache_batch_idx: torch.Tensor,
cache_seqlens: torch.Tensor,
):
"""
Currently, only head_dim%2==0 is supported.
Args:
k: (batch_size, seqlen_new, head_num, head_dim) torch.float16, torch.bfloat16
v: (batch_size, seqlen_new, head_num, head_dim) torch.float16, torch.bfloat16
k_cache: (batch_size_cache, seqlen_cache, head_num, head_dim) torch.float16, torch.bfloat16
v_cache: (batch_size_cache, seqlen_cache, head_num, head_dim) torch.float16, torch.bfloat16
cache_batch_idx: (batch_size,) torch.int32
The indices used to index into the KV cache.
cache_seqlens: (batch_size,) torch.int32
The sequence lengths of the KV cache
Returns:
None
"""
head_dim = k.size(-1)
assert head_dim % 2 == 0, "Currently, only head_dim%2==0 is supported."
ops.infer.store_kv_cache(
k,
v,
k_cache,
v_cache,
cache_batch_idx,
cache_seqlens,
)

View File

@@ -0,0 +1,97 @@
from typing import Union
import ixformer._C as ops
import torch
__all__ = [
"t5_split_qkv",
"t5_split_qkv_update_kv_cache",
"ref_t5_split_qkv_update_kv_cache",
"ref_t5_split_qkv",
]
def reshape_query(query, head_num, head_dim):
batch_size, seq_len, _ = query.shape
query = query.view(batch_size, seq_len, head_num, head_dim)
query = query.transpose(1, 2)
return query
def ref_t5_split_qkv(qkv: "torch.Tensor", head_num: int, head_dim: int):
assert qkv.size(-1) == head_dim * head_num * 3
batch_size, seq_len, _ = qkv.shape
q, k, v = torch.chunk(qkv, 3, dim=-1)
q = reshape_query(q, head_num, head_dim)
k = reshape_query(k, head_num, head_dim)
v = reshape_query(v, head_num, head_dim)
return q, k, v
def t5_split_qkv(qkv: "torch.Tensor", head_num: int, head_dim: int):
"""
Args:
qkv: (batch_size, seq_len, head_dim * head_num * 3) torch.half, torch.bfloat16
head_num: int
head_dim: int
Returns:
q: (batch_size, head_num, seq_len, head_dim) torch.half, torch.bfloat16
k: (batch_size, head_num, seq_len, head_dim) torch.half, torch.bfloat16
v: (batch_size, head_num, seq_len, head_dim) torch.half, torch.bfloat16
"""
batch_size, seq_len, _ = qkv.shape
q = qkv.new_empty([batch_size, head_num, seq_len, head_dim])
k = qkv.new_empty([batch_size, head_num, seq_len, head_dim])
v = qkv.new_empty([batch_size, head_num, seq_len, head_dim])
ops.infer.t5_split_qkv(qkv, q, k, v, head_num, head_dim)
return q, k, v
def ref_t5_split_qkv_update_kv_cache(
qkv: "torch.Tensor",
past_key: "torch.Tensor",
past_value: "torch.Tensor",
head_num: int,
head_dim: int,
):
assert qkv.size(-1) == head_dim * head_num * 3
batch_size, seq_len, _ = qkv.shape
q, k, v = torch.chunk(qkv, 3, dim=-1)
q = reshape_query(q, head_num, head_dim)
k = reshape_query(k, head_num, head_dim)
v = reshape_query(v, head_num, head_dim)
k = torch.cat([past_key, k], dim=2)
v = torch.cat([past_value, v], dim=2)
return q, k, v
def t5_split_qkv_update_kv_cache(
qkv: "torch.Tensor",
past_key: "torch.Tensor",
past_value: "torch.Tensor",
head_num: int,
head_dim: int,
):
"""
Args:
qkv: (batch_size, 1 , head_dim * head_num * 3) torch.half, torch.bfloat16
past_key: (batch_size, head_num, seq_len - 1, head_dim) torch.half, torch.bfloat16
past_value: (batch_size, head_num, seq_len - 1, head_dim) torch.half, torch.bfloat16
head_num: int
head_dim: int
Returns:
q: (batch_size, head_num, 1, head_dim) torch.half, torch.bfloat16
k: (batch_size, head_num, seq_len, head_dim) torch.half, torch.bfloat16
v: (batch_size, head_num, seq_len, head_dim) torch.half, torch.bfloat16
"""
batch_size, _, past_seq_len, _ = list(past_key.shape)
seq_len = past_seq_len + 1
q = qkv.new_empty([batch_size, head_num, 1, head_dim])
k = qkv.new_empty([batch_size, head_num, seq_len, head_dim])
v = qkv.new_empty([batch_size, head_num, seq_len, head_dim])
ops.infer.t5_split_qkv_update_kv_cache(
qkv, past_key, past_value, q, k, v, head_num, head_dim
)
return q, k, v

View File

@@ -0,0 +1,617 @@
import math
from typing import List, Optional
import ixformer._C as ops
import torch
__all__ = [
"tgi_apply_rotary_emb_torch",
"tgi_apply_rotary",
"tgi_gather_prefill_logprobs",
"ref_paged_attention_v1",
"ref_paged_attention_v3",
"get_alibi_slopes",
"paged_attention_v1",
"reshape_and_cache_v1",
"paged_attention_v7",
"reshape_and_cache",
"paged_attention_v3",
"ref_reshape_and_cache_v3",
"reshape_and_cache_v3",
]
def get_alibi_slopes(total_num_heads: int) -> torch.Tensor:
closest_power_of_2 = 2 ** math.floor(math.log2(total_num_heads))
base = torch.tensor(
2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3))),
dtype=torch.float32,
)
powers = torch.arange(1, 1 + closest_power_of_2, dtype=torch.int32)
slopes = torch.pow(base, powers)
if closest_power_of_2 != total_num_heads:
extra_base = torch.tensor(
2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3))),
dtype=torch.float32,
)
num_remaining_heads = min(
closest_power_of_2, total_num_heads - closest_power_of_2
)
extra_powers = torch.arange(
start=1, end=1 + 2 * num_remaining_heads, step=2, dtype=torch.int32
)
slopes = torch.cat([slopes, torch.pow(extra_base, extra_powers)], dim=0)
return slopes
def get_alibi_mask(num_heads, seqlen, device, dtype):
x = torch.arange(0, seqlen, device=device, dtype=torch.float32).view(-1, 1)
y = torch.tensor(seqlen - 1, device=device, dtype=torch.float32).view(1, -1)
offsets = -(y - x).view(1, 1, seqlen)
return offsets
def ref_masked_attention(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
scale: float,
attn_mask: Optional[torch.Tensor] = None,
) -> torch.Tensor:
query = query * scale
dtype = query.dtype
device = query.device
query = query.to(torch.float32).cpu()
key = key.to(torch.float32).cpu()
value = value.to(torch.float32).cpu()
attn = torch.einsum("qhd,khd->hqk", query, key)
if attn_mask is not None:
attn_mask = attn_mask.cpu()
attn = attn + attn_mask
attn = torch.softmax(attn, dim=-1)
out = torch.einsum("hqk,khd->qhd", attn, value)
out = out.to(device).to(dtype)
return out
def ref_paged_attention_v1(
output: torch.Tensor,
query: torch.Tensor,
num_q_per_kv: int,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
use_alibi: bool,
) -> None:
num_query_heads = query.shape[1]
num_kv_heads = value_cache.shape[1]
head_size = value_cache.shape[2]
block_size = value_cache.shape[3]
num_input_tokens = query.shape[0]
device = output.device
slopes = (
get_alibi_slopes(num_query_heads)
.to(device)
.to(torch.float32)
.view(num_query_heads, 1, 1)
)
for i in range(num_input_tokens):
q = query[i].unsqueeze(0)
block_table = block_tables[i]
context_len = int(context_lens[i])
keys = []
values = []
for j in range(context_len):
block_number = int(block_table[j // block_size])
block_offset = j % block_size
k = key_cache[block_number, :, :, block_offset, :]
k = k.reshape(num_kv_heads, head_size)
keys.append(k)
v = value_cache[block_number, :, :, block_offset]
values.append(v)
keys = torch.stack(keys, dim=0)
values = torch.stack(values, dim=0)
if num_q_per_kv > 1:
keys = torch.repeat_interleave(keys, num_q_per_kv, dim=1)
values = torch.repeat_interleave(values, num_q_per_kv, dim=1)
scale = 1.0 / (head_size**0.5)
if use_alibi:
offsets = get_alibi_mask(
num_query_heads, context_len, output.device, output.dtype
)
mask = offsets * slopes
mask = mask.to(output.dtype)
else:
mask = None
out = ref_masked_attention(
q,
keys,
values,
scale,
mask,
)
out = out.view(num_query_heads, head_size)
output[i].copy_(out, non_blocking=True)
def ref_paged_attention_v3(
output: torch.Tensor,
query: torch.Tensor,
num_q_per_kv: int,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
use_alibi: bool,
) -> None:
num_query_heads = query.shape[1]
num_kv_heads = value_cache.shape[1]
head_size = query.shape[2]
block_size = value_cache.shape[2] * 4
num_input_tokens = query.shape[0]
device = output.device
slopes = (
get_alibi_slopes(num_query_heads)
.to(device)
.to(torch.float32)
.view(num_query_heads, 1, 1)
)
for i in range(num_input_tokens):
q = query[i].unsqueeze(0)
block_table = block_tables[i]
context_len = int(context_lens[i])
keys = []
values = []
for j in range(context_len):
block_number = int(block_table[j // block_size])
block_offset = j % block_size
k = key_cache[block_number, :, block_offset // 4, :, block_offset % 4, :]
k = k.reshape(num_kv_heads, head_size)
keys.append(k)
v = value_cache[block_number, :, block_offset // 4, :, block_offset % 4, :]
v = v.reshape(num_kv_heads, head_size)
values.append(v)
keys = torch.stack(keys, dim=0)
values = torch.stack(values, dim=0)
if num_q_per_kv > 1:
keys = torch.repeat_interleave(keys, num_q_per_kv, dim=1)
values = torch.repeat_interleave(values, num_q_per_kv, dim=1)
scale = 1.0 / (head_size**0.5)
if use_alibi:
offsets = get_alibi_mask(
num_query_heads, context_len, output.device, output.dtype
)
mask = offsets * slopes
mask = mask.to(output.dtype)
else:
mask = None
out = ref_masked_attention(
q,
keys,
values,
scale,
mask,
)
out = out.view(num_query_heads, head_size)
output[i].copy_(out, non_blocking=True)
def rotate_half(x, interleaved=False):
if not interleaved:
x1, x2 = x.chunk(2, dim=-1)
return torch.cat((-x2, x1), dim=-1)
else:
x1, x2 = x[..., ::2], x[..., 1::2]
seq_len, head_nums, _ = x.shape
return torch.stack((-x2, x1), dim=-1).reshape(seq_len, head_nums, -1)
def tgi_apply_rotary_emb_torch(
x: "torch.Tensor",
cos: "torch.Tensor",
sin: "torch.Tensor",
interleaved: bool = False,
):
"""
x: (seqlen, num_heads, headdim)
cos, sin: (seqlen, 1, rotary_dim / 2)
interleaved: bool. 在interleaved的实现中,对奇偶维度旋转需要将维度两两交错,实现较为复杂。
"""
ro_dim = cos.shape[-1] * 2
assert ro_dim <= x.shape[-1]
assert cos.shape == sin.shape
if cos.dim() == 2:
cos = cos.unsqueeze(1)
sin = sin.unsqueeze(1)
if interleaved:
cos = cos.repeat_interleave(2, dim=-1)
sin = sin.repeat_interleave(2, dim=-1)
else:
cos = cos.repeat(1, 1, 2)
sin = sin.repeat(1, 1, 2)
return torch.cat(
[
x[..., :ro_dim].float() * cos.float()
+ rotate_half(x[..., :ro_dim].float(), interleaved) * sin.float(),
x[..., ro_dim:].float(),
],
dim=-1,
).to(x.dtype)
def tgi_apply_rotary(
querys: List[torch.Tensor],
cos: "torch.Tensor",
sin: "torch.Tensor",
outs: List[torch.Tensor] = None,
is_neox_style: bool = True,
):
"""
Args:
querys: [(num_tokens, num_heads, head_size)] List[torch.half],List[torch.float],List[torch.bfloat16]
cos: (max_position, 1, head_size //2) torch.half, torch.float, torch.bfloat16
sin: (max_position, 1, head_size //2) torch.half, torch.float, torch.bfloat16
is_neox_style: bool
判断是否使用Neox,默认为True,即不使用interleaved
outs: [(num_tokens, num_heads, head_size)] List[torch.half],List[torch.float],List[torch.bfloat16]
Returns:
outs: [(num_tokens, num_heads, head_size)] List[torch.half],List[torch.float],List[torch.bfloat16]
"""
assert sin.shape == cos.shape
rotary_dim = cos.shape[-1]
return_type = False
if len(querys) == 1:
query = querys[0]
query_dim = query.shape[-1]
query1 = query[..., :rotary_dim]
query2 = query[..., rotary_dim : 2 * rotary_dim]
elif len(querys) == 2:
return_type = True
query1 = querys[0]
query2 = querys[1]
assert query1.shape == query2.shape
query_dim = query1.shape[-1] * 2
else:
raise ValueError(
f"Invalid number for querys: {len(querys)}. " "Expected number 1, or 2."
)
assert rotary_dim * 2 <= query_dim
if outs is None:
query_shape = query1.shape
out = torch.empty(*(query_shape[:-1] + [rotary_dim * 2]))
out1 = out[..., :rotary_dim]
out2 = out[..., rotary_dim : 2 * rotary_dim]
else:
assert len(querys) == len(outs)
for query, out in zip(querys, outs):
assert query.shape == out.shape
if len(outs) == 1:
out = outs[0]
out1 = out[..., :rotary_dim]
out2 = out[..., rotary_dim : 2 * rotary_dim]
else:
out1 = outs[0]
out2 = outs[1]
if cos.dim() == 2:
cos = cos.unsqueeze(1)
sin = sin.unsqueeze(1)
ops.infer.tgi_rotary_embedding_neox(
query1, query2, cos, sin, out1, out2, is_neox_style
)
if return_type:
return out1, out2
else:
return torch.cat([out1, out2], dim=-1)
def tgi_gather_prefill_logprobs(
logits: "torch.Tensor",
prefill_tokens_indices: "torch.Tensor",
output: "torch.Tensor" = None,
):
"""
Args:
logits: (num_tokens, vocab_size) torch.half, torch.bfloat16
prefill_tokens_indices: (tokens_indices) torch.int
output: (tokens_indices, 1) torch.half, torch.bfloat16
Returns:
output: (tokens_indices, 1) torch.half, torch.bfloat16
"""
if output is None:
output = logits.new_empty(prefill_tokens_indices.shape)
ops.infer.tgi_gather_prefill_logprobs(logits, prefill_tokens_indices, output)
return output
def paged_attention_v1(
output: torch.Tensor,
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
num_kv_heads: torch.Tensor,
scale: float,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
block_size: int,
max_context_len: int,
alibi_slopes: torch.Tensor = None,
use_sqrt_alibi: bool = False,
):
"""
Args:
output: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
query: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
key_cache: (num_blocks, num_kv_heads, block_size, head_size) torch.half, torch.bfloat16
value_cache: (num_blocks, num_kv_heads, block_size, head_size) torch.half, torch.bfloat16
num_kv_heads: int
scale: float
block_tables: (num_tokens, max_num_blocks_per_seq) torch.int64
context_lens_cpu: (num_tokens) torch.int32
context_lens: (num_tokens) torch.int32
block_size: int
max_context_len: int
alibi_slopes: (num_heads) torch.float32
use_sqrt_alibi: bool
Returns:
output: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
"""
ops.infer.tgi_single_query_cached_kv_attention(
output,
query,
key_cache,
value_cache,
num_kv_heads,
scale,
block_tables,
context_lens,
block_size,
max_context_len,
query.stride(0),
use_sqrt_alibi,
alibi_slopes,
)
def paged_attention_v3(
output: "torch.Tensor",
query: "torch.Tensor",
key_cache: "torch.Tensor",
value_cache: "torch.Tensor",
head_mapping: "torch.Tensor",
scale: float,
block_tables: "torch.Tensor",
context_lens: "torch.Tensor",
block_size: int,
max_context_len: int,
alibi_slopes: "torch.Tensor" = None,
use_sqrt_alibi: bool = False,
):
"""
Args:
output: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
query: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
key_cache: (num_blocks, num_kv_heads, block_size, head_size) torch.half, torch.bfloat16
value_cache: (num_blocks, num_kv_heads, block_size, head_size) torch.half, torch.bfloat16
num_kv_heads: int
scale: float
block_tables: (num_tokens, max_num_blocks_per_seq) torch.int64
context_lens_cpu: (num_tokens) torch.int32
context_lens: (num_tokens) torch.int32
block_size: int
max_context_len: int
alibi_slopes: (num_heads) torch.float32
use_sqrt_alibi: bool
Returns:
output: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
"""
ops.infer.single_query_cached_kv_attention_v3(
output,
query,
key_cache,
value_cache,
head_mapping,
scale,
block_tables,
context_lens,
block_size,
max_context_len,
query.stride(0),
use_sqrt_alibi,
alibi_slopes,
)
def paged_attention_v7(
output: torch.Tensor,
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
num_kv_heads: int,
scale: float,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
block_size: int,
max_context_len: int,
alibi_slopes: torch.Tensor = None,
use_sqrt_alibi: bool = False,
):
"""
Args:
output: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
query: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
key_cache: (num_blocks, num_kv_heads, block_size, head_size) torch.half, torch.bfloat16
value_cache: (num_blocks, num_kv_heads, block_size, head_size) torch.half, torch.bfloat16
num_kv_heads: int
scale: float
block_tables: (num_tokens, max_num_blocks_per_seq) torch.int64
context_lens: (num_tokens) torch.int32
block_size: int
max_context_len: int
alibi_slopes: (num_heads) torch.float32
use_sqrt_alibi: bool
Returns:
output: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
"""
num_blocks = key_cache.size(0)
head_size = query.size(-1)
key_cache = key_cache.view(num_blocks, num_kv_heads, block_size, head_size)
value_cache = value_cache.view(num_blocks, num_kv_heads, block_size, head_size)
ops.infer.vllm_paged_attention(
output,
query,
key_cache,
value_cache,
num_kv_heads,
scale,
block_tables,
context_lens,
block_size,
max_context_len,
alibi_slopes,
True,
-1,
-1,
0.0,
False,
use_sqrt_alibi,
)
return output
def reshape_and_cache_v1(
key: torch.Tensor,
value: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
slot_mapping: torch.Tensor,
):
"""
Args:
key: (num_tokens, num_heads, head_size) torch.half, torch.float, torch.bfloat16
value: (num_tokens, num_heads, head_size) torch.half, torch.float, torch.bfloat16
key_cache: (num_blocks, num_heads, head_size//8, block_size, 8) or (num_blocks, num_heads, head_size//4, block_size, 4) torch.half, torch.float, torch.bfloat16
if dtype=torch.half or torch.bfloat16,key_cache shape: (num_blocks, num_heads, head_size//8, block_size, 8)
if dtype=torch.float,key_cache shape: (num_blocks, num_heads, head_size//4, block_size, 4)
value_cache: (num_blocks, num_heads, head_size//8, block_size, 8) or (num_blocks, num_heads, head_size//4, block_size, 4) torch.half, torch.float, torch.bfloat16
if dtype=torch.half or torch.bfloat16,value_cache shape: (num_blocks, num_heads, head_size//8, block_size, 8)
if dtype=torch.float,value_cache shape: (num_blocks, num_heads, head_size//4, block_size, 4)
slot_mapping: (num_tokens) torch.long
Returns:
None, 对key_cache,value_cache进行in place 操作
"""
ops.infer.vllm_cache_ops_reshape_and_cache_v4(
key,
value,
key_cache,
value_cache,
slot_mapping,
key.stride(0),
value.stride(0),
)
def reshape_and_cache(
key: torch.Tensor,
value: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
slot_mapping: torch.Tensor,
):
"""
Args:
key: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
value: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
key_cache: (num_blocks, num_heads, block_size, head_size) torch.half, torch.bfloat16
value_cache: (num_blocks, num_heads, block_size, head_size) torch.half, torch.bfloat16
slot_mapping: (num_tokens) torch.long
Returns:
None, 对key_cache,value_cache进行in place 操作
"""
num_tokens, num_kv_heads, head_size = key.shape
num_blocks = key_cache.size(0)
key_cache = key_cache.view(num_blocks, num_kv_heads, -1, head_size)
value_cache = value_cache.view(num_blocks, num_kv_heads, -1, head_size)
ops.infer.vllm_cache_ops_reshape_and_cache(
key,
value,
key_cache,
value_cache,
slot_mapping,
key.stride(0),
value.stride(0),
)
def ref_reshape_and_cache_v3(
key,
value,
key_cache,
value_cache,
slot_mapping,
num_tokens,
num_heads,
head_size,
block_size,
):
reshaped_key = key.view(num_tokens, num_heads, head_size // 32, 32)
reshaped_value = value.reshape(num_tokens, num_heads, head_size // 32, 32)
for i in range(num_tokens):
block_idx = torch.div(slot_mapping[i], block_size, rounding_mode="floor")
block_offset = slot_mapping[i] % block_size
key_cache[
block_idx, :, block_offset // 4, :, block_offset % 4, :
] = reshaped_key[i]
value_cache[
block_idx, :, block_offset // 4, :, block_offset % 4, :
] = reshaped_value[i]
def reshape_and_cache_v3(
key: "torch.Tensor",
value: "torch.Tensor",
key_cache: "torch.Tensor",
value_cache: "torch.Tensor",
slot_mapping: "torch.Tensor",
):
"""
Args:
key: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
value: (num_tokens, num_heads, head_size) torch.half, torch.bfloat16
key_cache: (num_blocks, num_heads, block_size // 4, head_size // 32, 4, 32) torch.half, torch.bfloat16
目前block_size 只支持16,head_size 只支持64,128,256
value_cache: (num_blocks, num_heads, block_size // 4, head_size // 32, 4, 32) torch.half, torch.bfloat16
slot_mapping: (num_tokens) torch.int
Returns:
None, 对key_cache,value_cache进行in place 操作
"""
if key.dim() != 3 or key.shape != value.shape or key.size(-1) not in [64, 128, 256]:
raise NotImplementedError(
"reshape_and_cache_v3 only support key.dim()==3 and key.shape== value.shape and head_size must be 64, 128 , 256!"
)
ops.infer.cache_ops_reshape_and_cache_v3(
key,
value,
key_cache,
value_cache,
slot_mapping,
key.stride(0),
value.stride(0),
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
import math
from typing import List, Union, Optional
import ixformer._C as ops
import torch
from torch.autograd.function import Function, FunctionCtx
import ixformer
from ixformer.core import config
__all__ = [
"w8a16_gemm",
"w8a16_gemv",
"w8a16",
"ref_w8a16",
"wu8a16",
"ref_wu8a16",
]
def w8a16_gemv(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
group_size: int = -1,
format: str = "unknown",
output: Optional[torch.Tensor] = None
):
"""
w8a16 gemv 接口
input : bf16|fp16 (bs, ic)
qweights : int8 TN:(oc, ic) NN:(ic, oc)
scales : bf16|fp16 TN: 当groupsize为-1时, shape: (1, oc), 否则,shape: (ic/group_size, oc) NN:(1, oc)
TN 支持条件: ic % groupSize = 0, oc % 2 = 0, bs<=4
NN 支持条件: groupsize = -1 or groupsize = ic, oc % 4 = 0, bs<=4
"""
assert format in ["TN", "NN"]
assert len(qweights.shape) == 2
assert len(scales.shape) == 2
input_shape = list(inputs.shape)
inputs = inputs.view(-1, input_shape[-1])
if format == "TN":
output_shape = input_shape[:-1] + [qweights.shape[0]]
else:
output_shape = input_shape[:-1] + [qweights.shape[1]]
if output is None:
output = inputs.new_empty(output_shape).view(-1, output_shape[-1])
ops.infer.w8a16_gemv(output, inputs, qweights, scales, group_size, format)
return output.view(output_shape)
def w8a16_gemm(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
persistent: int = 0,
output: Optional[torch.Tensor] = None
):
"""
w8a16 gemm 接口
1. group_size=-1 or group_size=ic
input : bf16|fp16 (bs, ic)
qweights : int8 TN:(oc, ic) NN:(ic, oc)
scales : bf16|fp16 (1, oc)
NN 支持条件: ic%64==0, oc%64==0
2. group_size=64
input : bf16|fp16 (bs, ic)
qweights : int8 TN:(oc, ic)
scales : bf16|fp16 (ic/64, oc)
TN 支持条件: oc%2==0, ic%64==0
NN 不支持
"""
assert format in ["TN", "NN"]
assert len(qweights.shape) == 2
assert len(scales.shape) == 2
input_shape = list(inputs.shape)
inputs = inputs.view(-1, input_shape[-1])
if format == "TN":
output_shape = input_shape[:-1] + [qweights.shape[0]]
else:
output_shape = input_shape[:-1] + [qweights.shape[1]]
if output is None:
output = inputs.new_empty(output_shape).view(-1, output_shape[-1])
ops.infer.w8a16_gemm(
output, inputs, qweights, scales, group_size, format, persistent
)
return output.view(output_shape)
def dequant(qweight, scales, group_size):
IC, OC = qweight.shape
weight = qweight.t().reshape(OC, -1, group_size).to(
torch.float32
) * scales.t().unsqueeze(-1)
return weight.reshape(OC, IC)
def ref_w8a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
):
if group_size == -1:
group_size = inputs.shape[1]
if format == "TN":
weights = dequant(qweights.transpose(0, 1), scales, group_size)
elif format == "NN":
weights = dequant(qweights, scales, group_size)
return torch.nn.functional.linear(inputs, weights.to(inputs.dtype))
def w8a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
output: Optional[torch.Tensor] = None,
persistent: int = 0,
):
input_shape = inputs.shape
inputs = inputs.view(-1, input_shape[-1])
bs = inputs.size(0)
inputs = inputs.view(input_shape)
if bs <= config.IXFORMER_GEMV_THRESHOLD:
return w8a16_gemv(
inputs=inputs,
qweights=qweights,
scales=scales,
group_size=group_size,
format=format,
output=output
)
else:
return w8a16_gemm(
inputs=inputs,
qweights=qweights,
scales=scales,
group_size=group_size,
format=format,
output=output,
persistent=persistent
)
def ref_wu8a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
):
assert format in ["TN"]
assert len(qweights.shape) == 2
assert len(scales.shape) == 2
org_w_shape = qweights.shape
scales = scales.transpose(0, 1).flatten().view(-1, 1)
zeros = zeros.transpose(0, 1).flatten().view(-1, 1)
if group_size != -1:
qweights = qweights.reshape(-1, group_size)
w = (qweights - zeros) * scales
w = w.reshape(org_w_shape)
output = torch.matmul(inputs, w.t())
return output
def wu8a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
persistent: int = 0,
):
"""
http://confluence.iluvatar.ai:8090/display/SW/cuinferCustomGemm+Interface+Doc
wu8a16 非对称量化 gemm 接口
1. group_size=-1
input : bf16|fp16 (bs, ic)
qweights : uint8 TN:(oc, ic)
scales : bf16|fp16 (1,oc)
zeros : bf16|fp16 (1,oc)
TN 支持条件: ic % 64 == 0
NN 不支持
2. group_size=64
input : bf16|fp16 (bs, ic)
qweights : int8 TN:(oc, ic)
scales : bf16|fp16 (ic/64, oc)
zeros : bf16|fp16 (ic/64, oc)
TN 支持条件: oc % 2 == 0 && ic % 64 == 0
NN 不支持
"""
assert format in ["TN"]
assert len(qweights.shape) == 2
assert len(scales.shape) == 2
input_shape = list(inputs.shape)
inputs = inputs.view(-1, input_shape[-1])
output_shape = input_shape[:-1] + [qweights.shape[0]]
output = inputs.new_empty(output_shape).view(-1, output_shape[-1])
ops.infer.wu8a16_gemm(
output, inputs, qweights, scales, zeros, group_size, format, persistent
)
return output.view(output_shape)

View File

@@ -0,0 +1,317 @@
from typing import Optional, Tuple
import ixformer._C as ops
import torch
__all__ = [
"w8a8",
"ref_w8a8",
"dynamic_scaled_int8_quant",
"ref_dynamic_scaled_int8_quant",
"static_scaled_int8_quant",
"ref_static_scaled_int8_quant",
"scaled_int8_quant",
]
def ref_w8a8(
input: "torch.Tensor",
weight: "torch.Tensor",
i_scales: "torch.Tensor",
w_scales: "torch.Tensor",
output: "torch.Tensor",
format: str = "TN",
persistent=0,
bias: torch.Tensor = None,
):
dtype = output.dtype
input_f32 = input.to(torch.float32)
weight_f32 = weight.to(torch.float32)
assert format in ["TN", "NN", "NT"]
if format == "TN":
weight_f32 = weight_f32.transpose(0, 1)
if format == "NT":
input_f32 = input_f32.transpose(0, 1)
output_f32 = (
torch.matmul(input_f32, weight_f32)
* i_scales.view(-1, 1)
* w_scales.view(1, -1)
)
if bias is not None:
bias_f32 = bias.to(torch.float32)
output_f32 += bias_f32.view(1, -1)
output.copy_(output_f32.to(dtype))
return output
def w8a8_gemm(
input: "torch.Tensor",
weight: "torch.Tensor",
i_scales: "torch.Tensor",
w_scales: "torch.Tensor",
bias: Optional[torch.Tensor] = None,
output: Optional[torch.Tensor] = None,
format: str = "TN",
persistent: bool = False,
out_dtype: torch.dtype = None,
):
"""
Args:
input: (n, k) torch.int8
weight: (m, k) if format == "TN" else (k, m) torch.int8
i_scales: (n) torch.float32
w_scales: (m) torch.float32
bias: (m) torch.float32, same as output_type
format: str
Options include TN, NN and NT
persistent: Whether to use overleap bool
out_dtype: torch.float16, torch.bfloat16
Returns:
output: (n, m) torch.float16, torch.bfloat16
"""
input_shape = input.shape
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,
)
output_shape = output.shape
input = input.view(-1, input_shape[-1])
output = output.view(-1, output_shape[-1])
ops.infer.w8a8_gemm(
output, input, weight, i_scales, w_scales, bias, format, int(persistent)
)
return output.view(*output_shape)
def ref_static_scaled_int8_quant(output, input, scale):
"""
Args:
output: [torch.int8] [m, k]
input: [torch.half,torch.bfloat16] [m, k]
scale: [torch.float32] [1]
Returns:
output: [torch.int8] [m, k]
scale: [torch.float32] [1]
"""
# [m, 1]
f_input = input / scale.to(input.dtype)
i_output = torch.clamp(torch.round(f_input), -127, 127).to(torch.int8)
output.copy_(i_output)
return output, scale
# for vllm: https://github.com/vllm-project/vllm/blob/v0.5.4/vllm/_custom_ops.py#L387
def static_scaled_int8_quant(output, input, scale):
"""
Args:
output: [torch.int8] [m, k]
input: [torch.half,torch.bfloat16] [m, k]
scale: [torch.float32] [1]
Returns:
output: [torch.int8] [m, k]
scale: [torch.float32] [1]
"""
ops.infer.scaled_int8_quant(output, input, scale, 0)
return output, scale
def ref_dynamic_scaled_int8_quant(output, input, scale):
"""
Args:
output: [torch.int8] [m, k]
input: [torch.half,torch.bfloat16] [m, k]
scale: [torch.float32] [m]
Returns:
output: [torch.int8] [m, k]
scale: [torch.float32] [m]
"""
# [m, 1]
amax_, _ = torch.max(torch.abs(input), dim=-1, keepdim=True)
f_scale = amax_.float() / 127.0
scale.view(-1).copy_(f_scale.view(-1))
f_input = input / f_scale.to(input.dtype)
i_output = torch.clamp(torch.round(f_input), -127, 127).to(torch.int8)
output.copy_(i_output)
return output, scale.view(input.shape[:-1])
# for vllm: https://github.com/vllm-project/vllm/blob/v0.5.4/vllm/_custom_ops.py#L394
def dynamic_scaled_int8_quant(output, input, scale):
"""
Args:
output: [torch.int8] [m, k]
input: [torch.half,torch.bfloat16] [m, k]
scale: [torch.float32] [m]
Returns:
output: [torch.int8] [m, k]
scale: [torch.float32] [m]
"""
ops.infer.scaled_int8_quant(output, input, scale, 1)
return output, scale
def scaled_int8_quant(
input: torch.Tensor, scale: Optional[torch.Tensor] = None
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Quantize the input tensor to int8 and return the quantized tensor and scale.
Args:
input: The input tensor to be quantized to int8.
scale: Optional scaling factor for the int8 quantization.
When not provided, we invoke dynamic-per-token quantization.
Returns:
Tuple[Torch.Tensor, Torch.Tensor] : Output int8 tensor and scales.
"""
output = torch.empty_like(input, dtype=torch.int8)
if scale is not None:
# static-per-tensor quantization.
static_scaled_int8_quant(output, input, scale)
return output, scale
# dynamic-per-token quantization.
input_scales = torch.empty(
(input.numel() // input.shape[-1], 1), device=input.device, dtype=torch.float32
)
dynamic_scaled_int8_quant(output, input, input_scales)
return output, input_scales
def w8a8_gemv(
input: "torch.Tensor",
weight: "torch.Tensor",
i_scales: "torch.Tensor",
w_scales: "torch.Tensor",
bias: Optional[torch.Tensor] = None,
output: Optional[torch.Tensor] = None,
format: str = "TN",
persistent: bool = False,
out_dtype: torch.dtype = None,
):
"""
Args:
input: (n, k) torch.int8
weight: (m, k) if format == "TN" else (k, m) torch.int8
i_scales: (n) torch.float32
w_scales: (m) torch.float32
bias: (m) torch.float32 same as output_type
format: str
Options include TN and NN
persistent: Whether to use overleap bool
out_dtype: torch.float16, torch.bfloat16
Returns:
output: (n, m) torch.float16, torch.bfloat16
"""
input_shape = input.shape
if output is None:
if out_dtype is None:
raise RuntimeError("w8a8 gemv need out_dtype argument when output is none.")
output = torch.empty(
(input_shape[:-1] + (weight.shape[0],)),
dtype=out_dtype,
device=input.device,
)
input = input.view(-1, input_shape[-1])
ops.infer.w8a8_gemv(
output, input, weight, i_scales, w_scales, bias, format, int(persistent)
)
return output
def handle_pading(weight: torch.Tensor, format: str, is_gemm: bool):
"""Handle padding alignment for weight matrices
Args:
weight: Original weight matrix [m, k]
format: Matrix format, TN indicates transposed layout
is_gemm: Whether for GEMM operation (requires extra alignment checks)
Returns:
torch.Tensor: Padded weight matrix
Raises:
AssertionError: When is_gemm=True requires 4-byte alignment for m/k
"""
# weight should have been pad before w8a8 is called, handle _padding here just ensure the code run success,
# but performance is low, please refer to vllm/model_executor/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a8_int8.py
m, k = weight.shape
s = weight.stride(0)
if s % 64 != 0 and format == "TN":
pad_k = (s // 64 + 1) * 64
weight_pad = torch.empty((m, pad_k), dtype=weight.dtype, device=weight.device)
_weight = weight_pad[:, :k]
if is_gemm:
assert m % 4 == 0 and k % 4 == 0
_weight.copy_(weight)
return _weight
else:
return weight
def w8a8(
input: torch.Tensor,
weight: torch.Tensor,
i_scales: torch.Tensor,
w_scales: torch.Tensor,
bias: Optional[torch.Tensor] = None,
output: Optional[torch.Tensor] = None,
format: str = "TN",
persistent: bool = False,
out_dtype: torch.dtype = None,
):
"""
Args:
input: (n, k) torch.int8
weight: (m, k) if format == "TN" else (k, m) torch.int8
i_scales: (n) torch.float32
w_scales: (m) torch.float32
bias: (m) torch.float32, same as output_type
format: str
Options include TN and NN
persistent: Whether to use overleap bool
out_dtype: torch.float16, torch.bfloat16
Returns:
output: (n, m) torch.float16, torch.bfloat16
"""
bs = input.numel() // input.shape[-1]
gemv_condition = (format == "TN" and bs <= 1) or (format == "NN" and bs <= 16)
if gemv_condition:
weight = handle_pading(weight, format, is_gemm=False)
return w8a8_gemv(
input,
weight,
i_scales,
w_scales,
bias=bias,
output=output,
format=format,
persistent=persistent,
out_dtype=out_dtype,
)
else:
weight = handle_pading(weight, format, is_gemm=True)
return w8a8_gemm(
input,
weight,
i_scales,
w_scales,
bias=bias,
output=output,
format=format,
persistent=persistent,
out_dtype=out_dtype,
)

View File

@@ -0,0 +1,157 @@
import ixformer._C as ops
import torch
__all__ = ["wi4a16_gemm", "wi4a16_gemv", "wi4a16", "ref_wi4a16"]
def dequant_weight(tensor, scales, zeros, block_size):
# from CPM
"""
tensor: (oc/2, ic)
scales: (oc, ic/group_size)
zeros: (oc, ic/group_size)
"""
dtype = scales.dtype
left = tensor >> 4
right = tensor << 4 >> 4
left, right = right, left
ret = torch.cat((left, right), dim=-1).reshape(-1, left.size(-1))
ret_shape = ret.size()
ret = ret.view(-1, block_size)
ret = scales.view(-1, 1) * (ret - zeros.view(-1, 1))
ret = ret.reshape(ret_shape).to(dtype=dtype)
return ret
def ref_wi4a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
):
assert format in ["TN"]
weights = dequant_weight(
qweights,
scales.transpose(0, 1).contiguous(),
zeros.transpose(0, 1).contiguous(),
group_size,
)
output = torch.nn.functional.linear(inputs, weights.to(inputs.dtype))
return output
def wi4a16_gemm(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
output=None,
):
"""
wi4a16 gemm 接口
支持条件:
format = TN
group_size = 128
input : fp16 (bs, ic)
qweights : int8 (oc/2, ic)
scales : fp16 (ic/group_size, oc)
zeros : fp16 (ic/group_size, oc)
TN 支持条件: oc % 2 == 0 && ic % 128 == 0
NN 支持条件: 不支持
"""
assert format in ["TN"]
assert len(qweights.shape) == 2
assert len(scales.shape) == 2
assert len(zeros.shape) == 2
input_shape = list(inputs.shape)
inputs = inputs.view(-1, input_shape[-1])
if output is None:
output_shape = input_shape[:-1] + [scales.shape[1]]
output = inputs.new_empty(output_shape).view(-1, output_shape[-1])
else:
output_shape = output.shape
ops.infer.wi4a16_gemm(output, inputs, qweights, scales, zeros, group_size, format)
return output.view(output_shape)
def wi4a16_gemv(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
output=None,
):
"""
wi4a16 gemv 接口
支持条件:
format = TN
group_size = 128
input : bf16|fp16 (bs, ic)
qweights : int8 (oc/2, ic)
scales : bf16|fp16 (ic/group_size, oc)
zeros : bf16|fp16 (ic/group_size, oc)
TN 支持条件: oc % 2 == 0 && ic % 128 == 0
NN 支持条件: 不支持
"""
assert format in ["TN"]
assert len(qweights.shape) == 2
assert len(scales.shape) == 2
assert len(zeros.shape) == 2
input_shape = list(inputs.shape)
inputs = inputs.view(-1, input_shape[-1])
if output is None:
output_shape = input_shape[:-1] + [scales.shape[1]]
output = inputs.new_empty(output_shape).view(-1, output_shape[-1])
else:
output_shape = output.shape
ops.infer.wi4a16_gemv(output, inputs, qweights, scales, zeros, group_size, format)
return output.view(output_shape)
def wi4a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
group_size: int = -1,
format: str = "TN",
output=None,
):
input_shape = inputs.shape
inputs = inputs.view(-1, input_shape[-1])
bs = inputs.size(0)
inputs = inputs.view(input_shape)
if bs <= 1:
return wi4a16_gemv(
inputs=inputs,
qweights=qweights,
scales=scales,
zeros=zeros,
group_size=group_size,
format=format,
output=output,
)
else:
return wi4a16_gemm(
inputs=inputs,
qweights=qweights,
scales=scales,
zeros=zeros,
group_size=group_size,
format=format,
output=output,
)

View File

@@ -0,0 +1,155 @@
import ixformer._C as ops
import torch
__all__ = ["wui4a16_gemm", "wui4a16_gemv", "wui4a16", "ref_wui4a16"]
def dequant_weight(tensor, scales, zeros, block_size):
"""
tensor: (oc/2, ic)
scales: (oc, ic/group_size)
zeros: (oc, ic/group_size)
"""
dtype = scales.dtype
left = tensor >> 4
right = tensor << 4 >> 4
left, right = right, left
ret = torch.cat((left, right), dim=-1).reshape(-1, left.size(-1))
ret_shape = ret.size()
ret = ret.view(-1, block_size)
ret = scales.view(-1, 1) * (ret - zeros.view(-1, 1))
ret = ret.reshape(ret_shape).to(dtype=dtype)
return ret
def ref_wui4a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
bias: "torch.Tensor" = None,
group_size: int = -1,
format: str = "NN",
only_return_weight: bool = False,
):
"""
format = TN,NN
group_size = TN(128),NN(128, 32)
input : bfloat16|fp16 (bs, ic)
qweights : int32 NN: (ic, oc // 8) TN:(oc, ic // 8)
scales : bfloat16|fp16 (ic // group_size, oc)
zeros : int32 (ic // group_size, oc // 8)
bias : bfloat16|fp16 (oc, )
output : bfloat16|fp16 (bs, oc)
"""
def unpack_tensor(x, pack_num=8, order_map=None):
if order_map is None:
order_map = [0, 1, 2, 3, 4, 5, 6, 7]
unit = 32 // pack_num
rows, cols = x.shape
res = torch.zeros((rows, cols * pack_num), dtype=torch.int32, device=x.device)
for col in range(cols):
for k in range(pack_num):
res[:, col * pack_num + order_map[k]] = (x[:, col] >> (unit * k)) & 0xF
return res
scales = scales.t().contiguous()
if format == "NN":
zeros = unpack_tensor(zeros, order_map=[0, 2, 4, 6, 1, 3, 5, 7])
zeros = zeros.t().contiguous()
qweights = unpack_tensor(qweights, order_map=[0, 2, 4, 6, 1, 3, 5, 7])
qweights = qweights.t().contiguous()
else:
zeros = unpack_tensor(zeros)
zeros = zeros.t().contiguous()
qweights = unpack_tensor(qweights)
output_dim, input_dim = qweights.shape
qweights = qweights.view(output_dim, input_dim // group_size, group_size)
zeros = zeros.view(output_dim, input_dim // group_size, 1)
scales = scales.view(output_dim, input_dim // group_size, 1)
qweights = (qweights - zeros) * scales
qweights = qweights.view(output_dim, input_dim)
if only_return_weight:
return qweights
output = torch.nn.functional.linear(inputs, qweights.to(inputs.dtype))
return output, qweights
def wui4a16_gemm(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
bias: "torch.Tensor" = None,
group_size: int = 128,
format: str = "NN",
):
output_shape = inputs.shape[:-1] + (scales.shape[1],)
output = ops.infer.wui4a16_gemm(
inputs, qweights, scales, zeros, bias, group_size, format
)
return output.view(output_shape)
def wui4a16_gemv(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
bias: "torch.Tensor" = None,
group_size: int = 128,
format: str = "NN",
):
output_shape = inputs.shape[:-1] + (scales.shape[1],)
output = ops.infer.wui4a16_gemv(
inputs, qweights, scales, zeros, bias, group_size, format
)
return output.view(output_shape)
def wui4a16(
inputs: "torch.Tensor",
qweights: "torch.Tensor",
scales: "torch.Tensor",
zeros: "torch.Tensor",
bias: "torch.Tensor" = None,
group_size: int = 128,
format: str = "NN",
):
"""
format = TN,NN
group_size = TN(128),NN(128, 32)
input : bfloat16|fp16 (bs, ic)
qweights : int32 NN: (ic, oc // 8) TN:(oc, ic // 8)
scales : bfloat16|fp16 (ic // group_size, oc)
zeros : int32 (ic // group_size, oc // 8)
bias : bfloat16|fp16 (oc, )
output : bfloat16|fp16 (bs, oc)
支持条件 : NN: oc % 8 == 0 && ic % group_size == 0 && ic % 2 == 0
TN: oc % 2 == 0 && ic % group_size == 0
"""
batch = inputs.numel() // inputs.shape[-1]
if batch <= 1:
return wui4a16_gemv(
inputs=inputs,
qweights=qweights,
scales=scales,
zeros=zeros,
bias=bias,
group_size=group_size,
format=format,
)
else:
return wui4a16_gemm(
inputs=inputs,
qweights=qweights,
scales=scales,
zeros=zeros,
bias=bias,
group_size=group_size,
format=format,
)