chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .quantize import FP_Quantize, Quantizer
|
||||
from .fp8_gemm import matmul_fp8
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
######## Fused MoE kernel #########
|
||||
# These kernels are implemented for
|
||||
# fusing GeMM with dequantization of
|
||||
# fp8 weight data when using bit-16
|
||||
# activation.
|
||||
###################################
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def matmul_fp8(inp, weight, scale, quantization_group_size, quantizer):
|
||||
from deepspeed import get_accelerator
|
||||
|
||||
if not get_accelerator().is_triton_supported():
|
||||
return matmul_fp8_fallback(inp, weight, scale, quantization_group_size, quantizer)
|
||||
else:
|
||||
# Import dynamically to prevent failures on systems without triton.
|
||||
from .fp8_gemm_triton import matmul_fp8_triton
|
||||
return matmul_fp8_triton(inp, weight, scale, quantization_group_size)
|
||||
|
||||
|
||||
def matmul_fp8_fallback(inp, weight, scale, quantization_group_size, quantizer):
|
||||
return torch.matmul(inp, quantizer.dequantize(weight, scale=scale))
|
||||
@@ -0,0 +1,165 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
######## Fused MoE kernel #########
|
||||
# These kernels are implemented for
|
||||
# fusing GeMM with dequantization of
|
||||
# fp8 weight data when using bit-16
|
||||
# activation.
|
||||
###################################
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
@triton.jit
|
||||
def matmul_kernel_fp8_bf16(inp_ptr, weight_ptr, out_ptr, scale_ptr, M, N, K, stride_am, stride_ak, stride_bk,
|
||||
stride_bn, stride_cm, stride_cn, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr,
|
||||
BLOCK_SIZE_K: tl.constexpr, GROUP_SIZE_M: tl.constexpr,
|
||||
quantization_group_size: tl.constexpr):
|
||||
pid = tl.program_id(axis=0)
|
||||
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
||||
num_pid_n = tl.cdiv(N, BLOCK_SIZE_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 % num_pid_in_group) % group_size_m)
|
||||
pid_n = (pid % num_pid_in_group) // group_size_m
|
||||
|
||||
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
||||
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
||||
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
||||
|
||||
inp_data = inp_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
||||
weight_data = weight_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
||||
weight_ptrs_offset = offs_k[:, None] * (stride_bk // quantization_group_size) + (
|
||||
(pid_n * BLOCK_SIZE_N) // quantization_group_size)
|
||||
|
||||
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
||||
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
||||
inp = tl.load(inp_data, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
|
||||
weight = tl.load(weight_data, mask=offs_k[:, None] < K, other=0.0)
|
||||
scale = tl.load(scale_ptr + weight_ptrs_offset + ((k * BLOCK_SIZE_K * stride_bk) // quantization_group_size))
|
||||
# Dequantize weight (fp8 -> bf16)
|
||||
w = (weight & 0x80).to(tl.uint16) << 8
|
||||
w = w | ((weight & 0x7f).to(tl.uint16) << 4)
|
||||
w = (w + 0x3C00).to(tl.uint16)
|
||||
w = (w.to(tl.bfloat16, bitcast=True).to(tl.float32) * scale).to(tl.bfloat16)
|
||||
|
||||
inp_data += BLOCK_SIZE_K * stride_ak
|
||||
weight_data += BLOCK_SIZE_K * stride_bk
|
||||
|
||||
accumulator += tl.dot(inp, w)
|
||||
|
||||
out = accumulator.to(tl.bfloat16)
|
||||
|
||||
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
||||
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
||||
out_data = out_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
||||
tl.store(out_data, out, mask=(offs_cm[:, None] < M) & (offs_cn[None, :] < N))
|
||||
|
||||
|
||||
@triton.jit
|
||||
def matmul_kernel_fp8_fp16(inp_ptr, weight_ptr, out_ptr, scale_ptr, M, N, K, stride_am, stride_ak, stride_bk,
|
||||
stride_bn, stride_cm, stride_cn, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr,
|
||||
BLOCK_SIZE_K: tl.constexpr, GROUP_SIZE_M: tl.constexpr,
|
||||
quantization_group_size: tl.constexpr):
|
||||
pid = tl.program_id(axis=0)
|
||||
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
||||
num_pid_n = tl.cdiv(N, BLOCK_SIZE_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 % num_pid_in_group) % group_size_m)
|
||||
pid_n = (pid % num_pid_in_group) // group_size_m
|
||||
|
||||
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
||||
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
||||
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
||||
|
||||
inp_data = inp_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
||||
weight_data = weight_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
||||
weight_ptrs_offset = offs_k[:, None] * (stride_bk // quantization_group_size) + (
|
||||
(pid_n * BLOCK_SIZE_N) // quantization_group_size)
|
||||
|
||||
weight = tl.load(weight_data, mask=offs_k[:, None] < K, other=0.0)
|
||||
scale = tl.load(scale_ptr + weight_ptrs_offset)
|
||||
|
||||
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
||||
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
||||
inp = tl.load(inp_data, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
|
||||
# Dequantize weight (fp8 -> fp16)
|
||||
w = (((weight & 0x80) << 8) | ((weight & 0x7f) << 7)).to(tl.uint16)
|
||||
w = (w + 0x2000).to(tl.uint16)
|
||||
w = (w.to(tl.float16, bitcast=True) * scale).to(tl.float16)
|
||||
|
||||
inp_data += BLOCK_SIZE_K * stride_ak
|
||||
weight_data += BLOCK_SIZE_K * stride_bk
|
||||
|
||||
weight = tl.load(weight_data, mask=offs_k[:, None] < K - (k + 1) * BLOCK_SIZE_K, other=0.0)
|
||||
scale = tl.load(scale_ptr + (weight_ptrs_offset +
|
||||
(((k + 1) * BLOCK_SIZE_K * stride_bk) // quantization_group_size)))
|
||||
|
||||
accumulator += tl.dot(inp, w)
|
||||
|
||||
out = accumulator.to(tl.float16)
|
||||
|
||||
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
||||
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
||||
out_data = out_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
||||
tl.store(out_data, out, mask=(offs_cm[:, None] < M) & (offs_cn[None, :] < N))
|
||||
|
||||
|
||||
def matmul_fp8_triton(inp, weight, scale, quantization_group_size):
|
||||
|
||||
assert inp.shape[1] == weight.shape[0], \
|
||||
f"Incompatible dimensions (input: {inp.shape}, weight: {weight.shape})"
|
||||
|
||||
M, K = inp.shape
|
||||
K, N = weight.shape
|
||||
|
||||
out = torch.empty((M, N), device=inp.device, dtype=inp.dtype)
|
||||
|
||||
# GEMM tuning parameters!
|
||||
# TODO: Add a more configurable tuning for selecting the best GeMM
|
||||
BLOCK_SIZE_M = 16 if M <= 16 else 32 if M <= 32 else 64 if M <= 64 else 128
|
||||
BLOCK_SIZE_N = 64
|
||||
BLOCK_SIZE_K = max(64, quantization_group_size)
|
||||
GROUP_SIZE_M = 8
|
||||
num_stages = 4
|
||||
num_warps = 4
|
||||
if M >= 256:
|
||||
BLOCK_SIZE_M = 256
|
||||
BLOCK_SIZE_N = 128
|
||||
BLOCK_SIZE_K = max(128, quantization_group_size)
|
||||
num_stages = 3
|
||||
num_warps = 8
|
||||
|
||||
grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(N, META['BLOCK_SIZE_N']), )
|
||||
kernel = matmul_kernel_fp8_bf16 if inp.dtype == torch.bfloat16 else matmul_kernel_fp8_fp16
|
||||
kernel[grid](inp,
|
||||
weight,
|
||||
out,
|
||||
scale,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
inp.stride(0),
|
||||
inp.stride(1),
|
||||
weight.stride(0),
|
||||
weight.stride(1),
|
||||
out.stride(0),
|
||||
out.stride(1),
|
||||
quantization_group_size=quantization_group_size,
|
||||
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
||||
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
||||
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
||||
GROUP_SIZE_M=GROUP_SIZE_M,
|
||||
num_stages=num_stages,
|
||||
num_warps=num_warps)
|
||||
return out
|
||||
@@ -0,0 +1,167 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
import abc
|
||||
from abc import ABC
|
||||
|
||||
import gc
|
||||
from deepspeed.ops.op_builder import FPQuantizerBuilder
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
|
||||
fp_quant_module = None
|
||||
|
||||
|
||||
class Quantizer(ABC):
|
||||
"""
|
||||
Abstract Quantizer class that implements quantize/dequantize methods.
|
||||
|
||||
Arguments:
|
||||
group_size (int, optional): number of values or elements that are grouped
|
||||
together for the quantization process.
|
||||
"""
|
||||
|
||||
def __init__(self, group_size=512) -> None:
|
||||
self.group_size = group_size
|
||||
|
||||
@abc.abstractmethod
|
||||
def quantize(self,
|
||||
input,
|
||||
q_bits=8,
|
||||
q_mantisa_bits=3,
|
||||
stochastic_mode=False,
|
||||
return_meta_tensor=False) -> torch.Tensor:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def dequantize(self, input_q, fp_out=None, q_bits=8, q_mantisa_bits=3, scale=None) -> torch.Tensor:
|
||||
...
|
||||
|
||||
|
||||
class FP_Quantize(Quantizer):
|
||||
|
||||
def __init__(self, quantization_config) -> None:
|
||||
global fp_quant_module
|
||||
super().__init__(group_size=quantization_config.group_size)
|
||||
if fp_quant_module is None:
|
||||
fp_quant_module = FPQuantizerBuilder().load()
|
||||
self.cuda_impl = getattr(fp_quant_module, "CUDA_IMPL", True)
|
||||
self.q_config = quantization_config
|
||||
|
||||
self.orig_dtype = None
|
||||
self.num_groups = None
|
||||
self.input_q = None
|
||||
self.scale = None
|
||||
|
||||
def quantize(self,
|
||||
input,
|
||||
q_bits=8,
|
||||
q_mantisa_bits=3,
|
||||
stochastic_mode=False,
|
||||
return_meta_tensor=False) -> torch.Tensor:
|
||||
assert input.dtype == torch.bfloat16, "only support bf16 for now"
|
||||
if return_meta_tensor:
|
||||
assert q_bits == 8, "meta tensor is only supported with q_bit=8"
|
||||
|
||||
self.orig_dtype = input.dtype
|
||||
self.orig_shape = input.shape
|
||||
|
||||
if q_bits == 8:
|
||||
pass
|
||||
elif q_bits == 12:
|
||||
q_mantisa_bits = 4
|
||||
elif q_bits == 6:
|
||||
q_mantisa_bits = 2
|
||||
elif q_bits == 4:
|
||||
q_mantisa_bits = 1
|
||||
else:
|
||||
assert (0), \
|
||||
f"Missing {q_bits}-quantization, please add the template arguments for the kernel to support this precision!"
|
||||
self.num_groups = input.numel() // self.group_size
|
||||
self.input_q = torch.ones(self.num_groups,
|
||||
int(self.group_size * q_bits) // 8 + 4,
|
||||
dtype=torch.uint8,
|
||||
device=input.device)
|
||||
out = fp_quant_module.quantize(self.input_q, input, self.group_size, stochastic_mode, q_bits, q_mantisa_bits)
|
||||
if return_meta_tensor:
|
||||
data, self.scale = out.split(self.group_size, dim=-1)
|
||||
data = data.contiguous().reshape(input.shape)
|
||||
self.scale = self.scale.contiguous()
|
||||
del self.input_q
|
||||
del out
|
||||
gc.collect()
|
||||
get_accelerator().empty_cache()
|
||||
return data, self.scale
|
||||
|
||||
return out
|
||||
|
||||
def to(self, *args, **kwargs):
|
||||
# Intermediate tensors may need to be moved to different devices
|
||||
if hasattr(self, 'input_q'):
|
||||
self.input_q = self.input_q.to(*args, **kwargs)
|
||||
if hasattr(self, 'scale'):
|
||||
self.scale = self.scale.to(*args, **kwargs)
|
||||
|
||||
def get_scales(self):
|
||||
return fp_quant_module.get_scales(self.scale, self.num_groups)
|
||||
|
||||
def dequantize(self, input_q, fp_out=None, q_bits=8, q_mantisa_bits=3, scale=None) -> torch.Tensor:
|
||||
assert (self.orig_dtype is not None), \
|
||||
"[De-quantization Error]: you need to call quantize before dequantizing!"
|
||||
fp_out = torch.empty(self.orig_shape, dtype=self.orig_dtype,
|
||||
device=input_q.device) if fp_out is None else fp_out
|
||||
if q_bits == 8:
|
||||
pass
|
||||
elif q_bits == 12:
|
||||
q_mantisa_bits = 4
|
||||
elif q_bits == 6:
|
||||
q_mantisa_bits = 2
|
||||
elif q_bits == 4:
|
||||
q_mantisa_bits = 1
|
||||
else:
|
||||
assert (0), \
|
||||
f"Missing {q_bits}-dequantization, please add the template arguments for the kernel to support this precision!"
|
||||
|
||||
if scale is not None:
|
||||
assert input_q.numel() == fp_out.numel(), \
|
||||
'[De-quantization Error]: quantized data should have the same size as original tensor when scale is not None!'
|
||||
input_q = torch.cat([input_q.reshape(-1, self.group_size), scale], dim=-1).contiguous()
|
||||
fp_quant_module.dequantize(fp_out, input_q, self.group_size, q_mantisa_bits, q_bits - q_mantisa_bits - 1)
|
||||
return fp_out
|
||||
|
||||
def selective_dequantize(self,
|
||||
input_q,
|
||||
indexes,
|
||||
fp_out=None,
|
||||
q_bits=8,
|
||||
q_mantisa_bits=3,
|
||||
scale=None) -> torch.Tensor:
|
||||
assert (not hasattr(self, 'orig_shape') or len(self.orig_shape) == 3), \
|
||||
"Selective-Dequantization works on 3d tensor only! Please reshape the tensor before calling dequantize function."
|
||||
assert (self.orig_dtype is not None), \
|
||||
"[De-quantization Error]: you need to call quantize before dequantizing!"
|
||||
fp_out = torch.empty(
|
||||
(indexes.shape[0],
|
||||
*self.orig_shape[1:]), dtype=self.orig_dtype, device=input_q.device) if fp_out is None else fp_out
|
||||
if q_bits == 8:
|
||||
pass
|
||||
elif q_bits == 12:
|
||||
q_mantisa_bits = 4
|
||||
elif q_bits == 6:
|
||||
q_mantisa_bits = 2
|
||||
elif q_bits == 4:
|
||||
q_mantisa_bits = 1
|
||||
else:
|
||||
assert (0), \
|
||||
f"Missing {q_bits}-dequantization, please add the template arguments for the kernel to support this precision!"
|
||||
|
||||
if scale is not None:
|
||||
assert input_q.numel() == fp_out.numel(), \
|
||||
'[De-quantization Error]: quantized data should have the same size as original tensor when scale is not None!'
|
||||
input_q = torch.cat([input_q.reshape(-1, self.group_size), scale], dim=-1).contiguous()
|
||||
|
||||
fp_quant_module.selective_dequantize(fp_out, input_q, indexes, self.group_size, q_mantisa_bits,
|
||||
q_bits - q_mantisa_bits - 1)
|
||||
return fp_out
|
||||
Reference in New Issue
Block a user