comfy_backup / custom_nodes /ComfyUI-INT8-Fast /int8_fused_kernel.py
saik0s's picture
Add files using upload-large-folder tool
8d5e143 verified
Raw
History Blame Contribute Delete
13.7 kB
import torch
import triton
import triton.language as tl
from triton.language.extra import libdevice
# =============================================================================
# Kernel 1: Fused Row-wise Quantization (FP16/BF16 -> INT8 + Scale)
# =============================================================================
@triton.jit
def _quantize_rowwise_kernel(
x_ptr, # Input pointer (FP16/BF16)
y_ptr, # Output pointer (INT8)
s_ptr, # Scale pointer (FP32)
n_elements, # Number of columns
BLOCK_SIZE: tl.constexpr,
):
# Row index we are processing
row_idx = tl.program_id(0)
# Pointers to the start of the row
x_row_ptr = x_ptr + row_idx * n_elements
y_row_ptr = y_ptr + row_idx * n_elements
# 1. Compute Max Abs Value for the row
offsets = tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
# Load data
x = tl.load(x_row_ptr + offsets, mask=mask, other=0.0)
# Absolute value
abs_x = tl.abs(x)
# Reduction to find max
max_val = tl.max(abs_x, axis=0)
# 2. Compute Scale
# scale = max_val / 127.0
scale = tl.maximum(max_val / 127.0, 1e-30)
# 3. Quantize
# q = x / scale
q_f = x / scale
# Round and Clamp
# FIX: Use floor(x + 0.5) for rounding. This is portable across Triton versions.
q_i = libdevice.rint(q_f).to(tl.int32)
q_i = tl.clamp(q_i, -128.0, 127.0)
# 4. Store
tl.store(y_row_ptr + offsets, q_i.to(tl.int8), mask=mask)
tl.store(s_ptr + row_idx, scale.to(tl.float32))
def triton_quantize_rowwise(x: torch.Tensor):
"""
Input: [Batch, Dim] (float16/bfloat16/float32)
Output: [Batch, Dim] (int8), [Batch, 1] (float32)
"""
rows, cols = x.shape
y = torch.empty_like(x, dtype=torch.int8)
s = torch.empty((rows, 1), device=x.device, dtype=torch.float32)
# Heuristic for block size
BLOCK_SIZE = triton.next_power_of_2(cols)
if BLOCK_SIZE < 128: BLOCK_SIZE = 128
# Note: If cols > BLOCK_SIZE (e.g. > 8192 usually), this naive block logic needs a loop.
# For Flux2 Klein, Z-Image, Chroma layers this appears fine afaik.
grid = (rows,)
_quantize_rowwise_kernel[grid](x, y, s, cols, BLOCK_SIZE=BLOCK_SIZE)
return y, s
# =============================================================================
# Kernel 2: INT8 GEMM + Fused Dequantization Epilogue
# =============================================================================
@triton.autotune(
configs=[
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
],
key=['M', 'N', 'K'],
)
@triton.jit
def _int8_matmul_dequant_kernel(
# Pointers
a_ptr, b_ptr, c_ptr,
a_scale_ptr, b_scale_ptr, bias_ptr,
# Matrix Dimensions
M, N, K,
# Strides
stride_am, stride_ak,
stride_bk, stride_bn,
stride_cm, stride_cn,
# Meta-parameters
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
HAS_BIAS: tl.constexpr
):
"""
Computes: C = ((A * B) * (scale_a * scale_b)) + bias
A: [M, K] int8
B: [N, K] int8 (Transposed physically or logically via strides)
"""
pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_M)
num_pid_n = tl.cdiv(N, BLOCK_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_SIZE_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
pid_m = first_pid_m + (pid % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
# 1. Prepare Pointers for A and B
# A block pointer: [BLOCK_M, BLOCK_K]
offs_am = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)) % M
offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
offs_k = tl.arange(0, BLOCK_K)
a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
# 2. Main Loop (Accumulate in Int32)
accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.int32)
for k in range(0, tl.cdiv(K, BLOCK_K)):
# Load chunks
a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_K, other=0.0)
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_K, other=0.0)
# Matrix Multiply (Int8 inputs -> Int32 accum)
accumulator += tl.dot(a, b)
# Advance pointers
a_ptrs += BLOCK_K * stride_ak
b_ptrs += BLOCK_K * stride_bk
# 3. Fused Epilogue (Dequantize & Bias)
# Load dynamic scales
# A Scale is per-row [M, 1]
scale_a = tl.load(a_scale_ptr + offs_am) # Vector [BLOCK_M]
# B Scale is scalar or tensor.
scale_b = tl.load(b_scale_ptr)
# Convert Accumulator to Float
c = accumulator.to(tl.float32)
# Combine scales: scale_a (broadcast columns) * scale_b
total_scale = scale_a[:, None] * scale_b
c = c * total_scale
# Add Bias if present
if HAS_BIAS:
bias = tl.load(bias_ptr + offs_bn) # Vector [BLOCK_N]
c = c + bias[None, :]
# 4. Store Result (Cast to output dtype, usually FP16)
c_ptrs = c_ptr + stride_cm * offs_am[:, None] + stride_cn * offs_bn[None, :]
c_mask = (offs_am[:, None] < M) & (offs_bn[None, :] < N)
# We write as fp16 or bf16 implicitly by the pointer type, but explicit cast is safer
tl.store(c_ptrs, c, mask=c_mask)
# =============================================================================
# Python Wrapper
# =============================================================================
def triton_int8_linear(x: torch.Tensor, weight: torch.Tensor, weight_scale, bias=None, compute_dtype=torch.float16):
"""
Fused pipeline for W8A8 Linear Layer.
"""
# 1. Flatten inputs if 3D [Batch, Tokens, Dim] -> [Batch*Tokens, Dim]
x_shape_orig = x.shape
x_2d = x.reshape(-1, x_shape_orig[-1])
M, K = x_2d.shape
N = weight.shape[0]
# 2. Kernel 1: Dynamic Activation Quantization
# (This is much faster than Python-loop based axiswise quant)
x_int8, x_scale = triton_quantize_rowwise(x_2d)
# 3. Allocate Output
output = torch.empty((M, N), device=x.device, dtype=compute_dtype)
# 4. Prepare Scales for Kernel
# Ensure weight_scale is a tensor on device
if not isinstance(weight_scale, torch.Tensor):
weight_scale = torch.tensor([weight_scale], device=x.device, dtype=torch.float32)
else:
weight_scale = weight_scale.to(x.device, non_blocking=True).reshape(1) if weight_scale.numel() == 1 else weight_scale.to(x.device, non_blocking=True)
# 5. Kernel 2: Fused GEMM + Dequant
grid = lambda META: (triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']), )
# Check if we have bias
has_bias = bias is not None
bias_ptr = bias if has_bias else x # Dummy pointer if None
# NOTE: PyTorch Linear weights are [Out, In] (N, K).
# The kernel expects B to be [K, N] logically.
# Since weight is [N, K], we can treat it as [K, N] TRANSPOSED.
# Stride of W is [K, 1]. To read as column-major [K, N], stride is [1, K].
_int8_matmul_dequant_kernel[grid](
# Pointers
a_ptr=x_int8,
b_ptr=weight,
c_ptr=output,
a_scale_ptr=x_scale,
b_scale_ptr=weight_scale,
bias_ptr=bias_ptr,
# Shapes
M=M, N=N, K=K,
# Strides
stride_am=x_int8.stride(0), stride_ak=x_int8.stride(1),
stride_bk=weight.stride(1), stride_bn=weight.stride(0), # Transposed access of W
stride_cm=output.stride(0), stride_cn=output.stride(1),
# Meta
HAS_BIAS=has_bias
)
# 6. Reshape output
return output.reshape(x_shape_orig[:-1] + (N,))
# =============================================================================
# Kernel 3: INT8 GEMM + Fused Dequant with Per-Row Weight Scales
# =============================================================================
@triton.autotune(
configs=[
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4),
],
key=['M', 'N', 'K'],
)
@triton.jit
def _int8_matmul_dequant_per_row_kernel(
# Pointers
a_ptr, b_ptr, c_ptr,
a_scale_ptr, b_scale_ptr, bias_ptr,
# Matrix Dimensions
M, N, K,
# Strides
stride_am, stride_ak,
stride_bk, stride_bn,
stride_cm, stride_cn,
# Meta-parameters
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
HAS_BIAS: tl.constexpr
):
"""
Computes: C = ((A * B) * (scale_a[:, None] * scale_b[None, :])) + bias
A: [M, K] int8, scale_a: [M, 1] per-row activation scales
B: [N, K] int8, scale_b: [N, 1] per-row weight scales
"""
pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_M)
num_pid_n = tl.cdiv(N, BLOCK_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_SIZE_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
pid_m = first_pid_m + (pid % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
# 1. Prepare Pointers for A and B
offs_am = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)) % M
offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
offs_k = tl.arange(0, BLOCK_K)
a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
# 2. Main Loop (Accumulate in Int32)
accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.int32)
for k in range(0, tl.cdiv(K, BLOCK_K)):
a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_K, other=0.0)
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_K, other=0.0)
accumulator += tl.dot(a, b)
a_ptrs += BLOCK_K * stride_ak
b_ptrs += BLOCK_K * stride_bk
# 3. Fused Epilogue (Dequantize & Bias)
# A Scale is per-row [M, 1]
scale_a = tl.load(a_scale_ptr + offs_am) # Vector [BLOCK_M]
# B Scale is per-row [N, 1] (the key difference from the scalar kernel)
scale_b = tl.load(b_scale_ptr + offs_bn) # Vector [BLOCK_N]
c = accumulator.to(tl.float32)
# Outer product of scales: [BLOCK_M, 1] * [1, BLOCK_N]
total_scale = scale_a[:, None] * scale_b[None, :]
c = c * total_scale
if HAS_BIAS:
bias = tl.load(bias_ptr + offs_bn)
c = c + bias[None, :]
# 4. Store Result
c_ptrs = c_ptr + stride_cm * offs_am[:, None] + stride_cn * offs_bn[None, :]
c_mask = (offs_am[:, None] < M) & (offs_bn[None, :] < N)
tl.store(c_ptrs, c, mask=c_mask)
# =============================================================================
# Python Wrapper (Per-Row Weight Scales)
# =============================================================================
def triton_int8_linear_per_row(x: torch.Tensor, weight: torch.Tensor, weight_scale: torch.Tensor, bias=None, compute_dtype=torch.float16):
"""
Fused pipeline for W8A8 Linear Layer with per-row weight quantization.
weight_scale: [N, 1] per-row scales
"""
# 1. Flatten inputs if 3D
x_shape_orig = x.shape
x_2d = x.reshape(-1, x_shape_orig[-1])
M, K = x_2d.shape
N = weight.shape[0]
# 2. Dynamic Activation Quantization
x_int8, x_scale = triton_quantize_rowwise(x_2d)
# 3. Allocate Output
output = torch.empty((M, N), device=x.device, dtype=compute_dtype)
# 4. Prepare weight scales - flatten [N, 1] -> [N] for kernel
ws = weight_scale.reshape(N).contiguous()
# 5. Fused GEMM + Per-Row Dequant
grid = lambda META: (triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']), )
has_bias = bias is not None
bias_ptr = bias if has_bias else x # Dummy pointer if None
_int8_matmul_dequant_per_row_kernel[grid](
a_ptr=x_int8,
b_ptr=weight,
c_ptr=output,
a_scale_ptr=x_scale,
b_scale_ptr=ws,
bias_ptr=bias_ptr,
M=M, N=N, K=K,
stride_am=x_int8.stride(0), stride_ak=x_int8.stride(1),
stride_bk=weight.stride(1), stride_bn=weight.stride(0),
stride_cm=output.stride(0), stride_cn=output.stride(1),
HAS_BIAS=has_bias
)
# 6. Reshape output
return output.reshape(x_shape_orig[:-1] + (N,))