#include #include #include #include #include #include #include #include "src/turbomind/core/allocator.h" #include "src/turbomind/core/context.h" #include "src/turbomind/core/data_type.h" #include "src/turbomind/kernels/core/array_ops.h" #include "src/turbomind/kernels/core/common.h" #include "src/turbomind/kernels/core/floating_point.h" #include "src/turbomind/kernels/core/math.h" #include "src/turbomind/kernels/quantization.cuh" #include "src/turbomind/kernels/quantization.h" #include "src/turbomind/kernels/attention/quantization.h" namespace turbomind { template __global__ void quant_symm_row( Tout* out, int out_ld, Tscale* scales, int scales_ld, const T* src, int src_ld, int num, int dim, Tscale qmax) { #if TURBOMIND_ARCH_SM90 static_assert(group_size % vec_size == 0); constexpr int threads = group_size / vec_size; const int dim1 = round_up(dim, WARP_SIZE * vec_size); for (int ti = blockIdx.x; ti < num; ti += gridDim.x) { for (int di = threadIdx.x * vec_size; di < dim1; di += blockDim.x * vec_size) { Array vec{}; if (di < dim) { Ldg(vec, src + ti * src_ld + di); } auto absmax = fmaxf(static_cast(find_absmax(vec)), 1e-8f); const Tscale scale = absmax / qmax; const Tscale inv_scale = qmax / absmax; if (threadIdx.x % threads == 0 && di < dim) { // column-major scales[(di / group_size) * scales_ld + ti] = scale; } Array tmp; PRAGMA_UNROLL for (int c = 0; c < vec_size; ++c) { tmp[c] = Tout(static_cast(vec[c]) * inv_scale); } if (di < dim) { Store(out + ti * out_ld + di, tmp); } } } #endif } void QuantizeSymm(Tensor& out, Tensor& scale, const Tensor& src, cudaStream_t st) { TM_CHECK_EQ(src.ndim(), 2); TM_CHECK_EQ(src.stride(1), 1); // row-major const auto [num, dim] = src.shapes(0, 1); using T = bfloat16_t; using Tout = fp8_e4m3_t; using Tscale = float; constexpr int group_size = 128; constexpr int vec_size = 8; constexpr int alignment = 16 / sizeof(Tscale); if (!out) { out = Tensor_{src.shape(), kDEVICE}; } else { TM_CHECK(out.shape() == src.shape()); } const int aligned_num = round_up(num, alignment); const int s_dim = cdiv(dim, group_size); if (!scale) { scale = Tensor_({{s_dim, num}, {aligned_num, 1}}, kDEVICE); } else { TM_CHECK(std::make_tuple(s_dim, num) == scale.shapes(0, 1)); TM_CHECK(scale.stride(1) == 1); TM_CHECK(scale.stride(0) % alignment == 0); } constexpr int block_dim = 512; quant_symm_row<<>>(out.data(), // out.stride(0), scale.data(), scale.stride(0), src.data(), src.stride(0), num, dim, 448.f); } template __global__ void dequant_symm_row(Tout* out, int out_ld, const T* src, int src_ld, const Tscale* scales, int scales_ld, int num, int dim) { #if TURBOMIND_ARCH_SM90 static_assert(group_size % vec_size == 0); for (int ti = blockIdx.x; ti < num; ti += gridDim.x) { for (int di = threadIdx.x * vec_size; di < dim; di += blockDim.x * vec_size) { Array vec; Ldg(vec, src + ti * src_ld + di); const auto scale = __ldg(&scales[(di / group_size) * scales_ld + ti]); Array tmp; PRAGMA_UNROLL for (int c = 0; c < vec_size; ++c) { tmp[c] = Tout(static_cast(vec[c]) * scale); } Store(out + ti * out_ld + di, tmp); } } #endif } void DequantizeSymm(Tensor& out, const Tensor& src, const Tensor& scale, cudaStream_t st) { using T = fp8_e4m3_t; using Tout = bfloat16_t; using Tscale = float; if (!out) { out = Tensor_{src.layout(), kDEVICE}; } else { TM_CHECK(out.layout() == src.layout()); } auto [num, dim] = src.shapes(0, 1); constexpr int group_size = 128; constexpr int vec_size = 8; constexpr int block_dim = 512; dequant_symm_row<<>>(out.data(), // out.stride(0), src.data(), src.stride(0), scale.data(), scale.stride(0), num, dim); } template __global__ void quant_symm_block(Tout* out, Tscale* scales, const T* src, Tscale qmax, int num, int dim) { if constexpr (TURBOMIND_ARCH_BF16_GUARD(data_type_v)) { static_assert(block_size % vec_size == 0); constexpr int threads = block_size / vec_size; static_assert(cta_size % threads == 0); constexpr int rows = cta_size / threads; constexpr int S = cdiv(block_size, rows); using BlockReduce = cub::BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; __shared__ T shared_inv_scale; const int row = threadIdx.x / threads; const int col = threadIdx.x % threads; const int ti = blockIdx.x * block_size; const int di = blockIdx.y * block_size + col * vec_size; T absmax{}; Array xs[S]{}; PRAGMA_UNROLL for (int s = 0; s < S; ++s) { if (auto r = ti + s * rows + row; r < num && di < dim) { Ldg(xs[s], src + (int64_t)r * dim + di); } PRAGMA_UNROLL for (int i = 0; i < vec_size; ++i) { absmax = __hmax(absmax, __habs(xs[s][i])); } } absmax = BlockReduce{temp_storage}.Reduce(absmax, [](auto a, auto b) { return __hmax(a, b); }); if (threadIdx.x == 0) { auto maxval = fmaxf(static_cast(absmax), 1e-8f); scales[blockIdx.x * gridDim.y + blockIdx.y] = maxval / qmax; shared_inv_scale = qmax / maxval; } __syncthreads(); const Tscale inv_scale = shared_inv_scale; Array ys[S]; PRAGMA_UNROLL for (int s = 0; s < S; ++s) { PRAGMA_UNROLL for (int i = 0; i < vec_size; ++i) { ys[s][i] = Tout(static_cast(xs[s][i]) * inv_scale); } if (auto r = ti + s * rows + row; r < num && di < dim) { Store(out + (int64_t)r * dim + di, ys[s]); } } } } void QuantizeSymmBlock(Ref out_, Ref scale_, const Tensor& src, cudaStream_t st) { TM_CHECK(src.is_contiguous()); TM_CHECK_EQ(src.ndim(), 2); auto invoke = [&](auto t) { using T = decltype(t); using Tout = fp8_e4m3_t; using Tscale = float; constexpr int block_size = 128; constexpr int vec_size = 8; const auto [num, dim] = src.shapes(0, 1); const int bnum = cdiv(num, block_size); const int bdim = cdiv(dim, block_size); constexpr int cta_size = 1024; const dim3 grid(bnum, bdim); auto& out = out_.get(); auto& scale = scale_.get(); if (!out) { out = Tensor_{src.layout(), kDEVICE}; } else { TM_CHECK(out.layout() == src.layout()); } if (!scale) { scale = Tensor_({bnum, bdim}, kDEVICE); } else { TM_CHECK(std::make_tuple(bnum, bdim) == scale.shapes(0, 1)); } quant_symm_block<<>>( // out.data(), scale.data(), src.data(), 448.f, num, dim); }; TM_DISPATCH_PRIMARY_DTYPES(src.dtype(), invoke); } template __global__ void dequant_symm_block(Tout* out, const T* src, const Tscale* scales, int num, int dim) { if constexpr (TURBOMIND_ARCH_BF16_GUARD(data_type_v)) { static_assert(block_size % vec_size == 0); constexpr int threads = block_size / vec_size; static_assert(cta_size % threads == 0); constexpr int rows = cta_size / threads; constexpr int S = cdiv(block_size, rows); const int col = threadIdx.x % threads; const int row = threadIdx.x / threads; const auto scale = __ldg(&scales[blockIdx.x * gridDim.y + blockIdx.y]); const auto di = blockIdx.y * block_size + col * vec_size; PRAGMA_UNROLL for (int s = 0; s < S; ++s) { const auto ti = blockIdx.x * block_size + s * rows + row; if (ti < num && di < dim) { Array x; Ldg(x, src + (int64_t)ti * dim + di); Array y; PRAGMA_UNROLL for (int i = 0; i < vec_size; ++i) { y[i] = Tout(static_cast(x[i]) * scale); } Store(out + (int64_t)ti * dim + di, y); } } } } void DequantizeSymmBlock(Ref out_, Ref src_, const Tensor& scale, cudaStream_t st) { auto invoke = [&](auto tout) { using T = fp8_e4m3_t; using Tout = decltype(tout); using Tscale = float; constexpr int block_size = 128; constexpr int vec_size = 8; auto& out = out_.get(); auto& src = src_.get(); if (!out) { out = Tensor_{src.layout(), kDEVICE}; } else { TM_CHECK(out.layout() == src.layout()); } const auto [num, dim] = src.shapes(0, 1); const int bnum = cdiv(num, block_size); const int bdim = cdiv(dim, block_size); constexpr int cta_size = 1024; const dim3 grid(bnum, bdim); dequant_symm_block<<>>( // out.data(), src.data(), scale.data(), num, dim); }; if (!out_.get()) { return invoke(nv_bfloat16{}); } TM_DISPATCH_PRIMARY_DTYPES(out_.get().dtype(), invoke); } template __global__ void Compact1D_Kernel(D* d, const T* s, int n) { constexpr int bits = end_bit - start_bit; constexpr int vec_size = bitsof / bits; const auto idx = threadIdx.x + (int64_t)blockIdx.x * blockDim.x; if (idx * vec_size >= n) { return; } Array s_vec; Load(s_vec, &s[idx * vec_size]); constexpr T mask = ((1 << bits) - 1) << start_bit; D pack{}; PRAGMA_UNROLL for (int i = 0; i < vec_size; ++i) { pack |= ((s_vec[i] & mask) >> start_bit) << (i * bits); } d[idx] = pack; } template struct IntegralQuantizer { using T = T_; using Q = Q_; using Scale = T; using Zero = T; static constexpr int bits = bits_; static constexpr int max_q = (1 << bits) - 1; template __device__ void operator()(const Array& x, // const Array& pred, const R& rbits, Array& q, Array& d, T& scale, T& zero, int threads) const { auto f = cast(x); float minval = std::numeric_limits::infinity(); float maxval = -minval; PRAGMA_UNROLL for (int i = 0; i < N; ++i) { if (pred[i]) { minval = fminf(minval, f[i]); maxval = fmaxf(maxval, f[i]); } } for (int offset = threads / 2; offset >= 1; offset /= 2) { minval = fminf(minval, __shfl_xor_sync((uint32_t)-1, minval, offset)); maxval = fmaxf(maxval, __shfl_xor_sync((uint32_t)-1, maxval, offset)); } auto clamp = [](int x, int a, int b) { return max(a, min(b, x)); }; float scale_ = fmaxf(maxval - minval, 1e-5f) / (float)max_q; int zero_ = clamp(-round(minval / scale_), 0, max_q); scale = (T)scale_; zero = (T)zero_; // T sz = zero_ * scale_; PRAGMA_UNROLL for (int i = 0; i < N; ++i) { q[i] = clamp(round(f[i] / scale_) + zero_, 0, max_q); d[i] = (T)((int)q[i] - zero_) * (T)scale_; // d[i] = __hfma((T)q[i], (T)scale_, -sz); } } }; template struct FloatingPointQuantizer { using T = T_; using Q = Q_; using Scale = uint8_t; using Zero = void; using traits = FloatingPoint; static constexpr int bits = traits::bits; float pre_rounding_scale_; __host__ __device__ FloatingPointQuantizer(float pre_rounding_scale = 1.f): pre_rounding_scale_{pre_rounding_scale} { } template __device__ void operator()(const Array& x, // const Array& pred, const R& rbits, Array& q, Array& d, Scale& scale, Z ignore, int threads) const { auto f = cast(x); float absmax = 0.f; PRAGMA_UNROLL for (int i = 0; i < N; ++i) { if (pred[i]) { absmax = fmaxf(absmax, fabsf(f[i])); } } for (int offset = threads / 2; offset >= 1; offset /= 2) { absmax = fmaxf(absmax, __shfl_xor_sync((uint32_t)-1, absmax, offset)); } auto get_exponent = [](float x) -> int { return (__float_as_uint(x) >> 23U) & 0xFFU; }; int scale_i32 = get_exponent(absmax) - (traits::exponent_bias + 1); // int scale_i32 = 127; if (scale_i32 < 0) { // absmax(group) < 2*2^-125, flush to zero scale_i32 = 0; f = {}; } scale = scale_i32; float scale_f32 = __uint_as_float((uint32_t)scale_i32 << 23U); PRAGMA_UNROLL for (int i = 0; i < N; ++i) { q[i] = traits::from_f32((f[i] * pre_rounding_scale_) / scale_f32, rbits[i]); d[i] = (traits::to_f32(q[i]) * scale_f32) / pre_rounding_scale_; } } }; template __global__ void QuantizeGroupwise_Kernel(Quantizer quantizer, Q* q, S* s, Z* z, T* d, const T* x, const unsigned* r, Array stride_q, Array stride_s, Array stride_d, Array stride_x, int M, int K, int G) { if constexpr (TURBOMIND_ARCH_BF16_GUARD(data_type_v)) { static constexpr bool has_zero = !std::is_void_v; int m = blockIdx.x; int k = threadIdx.x + blockIdx.y * blockDim.x; const int threads_per_group = G / vec_size; const int warp_k = WARP_SIZE * vec_size; k *= vec_size; for (; k < round_up(K, warp_k); k += gridDim.y * blockDim.x * vec_size) { Array x_vec; Array p_vec; Array r_vec; PRAGMA_UNROLL for (int i = 0; i < vec_size; ++i) { p_vec[i] = k + i < K; x_vec[i] = p_vec[i] ? x[stride_x[0] * m + stride_x[1] * (k + i)] : T{0}; if (r) { r_vec[i] = p_vec[i] ? r[m * K + k] : 0; } } Array q_vec; Array d_vec; S scale; std::conditional_t zero{}; auto invoke = [&](auto rbits) { quantizer(x_vec, p_vec, rbits, q_vec, d_vec, scale, zero, threads_per_group); }; r ? invoke(r_vec) : invoke(Array{}); PRAGMA_UNROLL for (int i = 0; i < vec_size; ++i) { const auto idx = stride_q[0] * m + stride_q[1] * (k + i); if (p_vec[i]) { q[idx] = q_vec[i]; d[idx] = d_vec[i]; } } if (threadIdx.x % threads_per_group == 0) { const auto idx = stride_s[0] * m + stride_s[1] * (k / G); if (p_vec[0]) { s[idx] = (S)scale; if constexpr (has_zero) { z[idx] = (S)zero; } } } } } } void QuantizeGroupwise(Tensor quant, // (m,k) Tensor scales, // (m,k/g) Tensor zeros, // (m,k/g) Tensor dequant, // (m,k) Tensor src, // (m,k) Buffer_ rbits, // (m*k) int group_size) { // std::cout << quant << std::endl; // std::cout << scales << std::endl; // std::cout << zeros << std::endl; // std::cout << dequant << std::endl; // std::cout << src << std::endl; if (zeros) { TM_CHECK(scales.layout() == zeros.layout()); } TM_CHECK(quant.shape() == dequant.shape()); TM_CHECK(quant.size() == quant.layout().cosize()); auto stream = core::Context::stream().handle(); auto stride_2d = [](const Tensor& t) { TM_CHECK_EQ(t.ndim(), 2); auto [a, b] = t.strides(0, 1); return Array{(int)a, (int)b}; }; const int m = src.shape(0); const int k = src.shape(1); // std::cout << "m" << m << "k" << k << "\n"; auto invoke = [&](auto quantizer) { using Quantizer = decltype(quantizer); using T = typename Quantizer::T; using Q = typename Quantizer::Q; using S = typename Quantizer::Scale; using Z = typename Quantizer::Zero; constexpr int bits = Quantizer::bits; Tensor_ proxy = empty_like(quant, data_type_v); constexpr int vec = 8; TM_CHECK((group_size & (group_size - 1)) == 0); TM_CHECK_GE(group_size, vec); TM_CHECK_LE(group_size, WARP_SIZE * vec); const int threads = round_up(std::min(cdiv(k, vec), 1024), WARP_SIZE); QuantizeGroupwise_Kernel<<>>(quantizer, proxy.data(), scales.data(), zeros.data_or((Z*)nullptr), dequant.data(), src.data(), rbits.data_or(nullptr), stride_2d(proxy), stride_2d(scales), stride_2d(dequant), stride_2d(src), m, k, group_size); Compact1D_Kernel<0, bits><<>>( (uint32_t*)quant.raw_data(), (Q*)proxy.raw_data(), quant.size()); }; if (0) {} else if (src.dtype() == kHalf && quant.dtype() == kUint4) { invoke(IntegralQuantizer{}); } else if (src.dtype() == kBfloat16 && quant.dtype() == kFloat4_e2m1) { invoke(FloatingPointQuantizer{}); } else if (src.dtype() == kHalf && quant.dtype() == kFloat4_e2m1) { invoke(FloatingPointQuantizer{}); } else { TM_CHECK(0) << "Unsupported types: " << to_string(src.dtype()) << ", " << to_string(quant.dtype()); } } } // namespace turbomind