2019 lines
81 KiB
Plaintext
2019 lines
81 KiB
Plaintext
//
|
||
// ConvFpAIntBExecution.cpp
|
||
// MNN
|
||
//
|
||
// Created by MNN on 2024/03/11.
|
||
// Copyright © 2018, Alibaba Group Holding Limited
|
||
//
|
||
|
||
// precision = low/normal 时使用运行时离线反量化,precision = high 时使用在线反量化
|
||
#include "ConvFpAIntBExecution.hpp"
|
||
#include "../Raster.cuh"
|
||
#include "../ConvBaseKernel.cuh"
|
||
#include <float.h>
|
||
#include <cublas_v2.h>
|
||
|
||
//#define DEBUG
|
||
|
||
namespace MNN {
|
||
namespace CUDA {
|
||
|
||
const int TILE_DIM = 16; // GEMM_FpAIntB
|
||
const int GEMV_TILE = 64; // GEMV_FpAIntB (legacy, kept for INT8)
|
||
const int GEMV_TILE_V2 = 256; // P1: Optimized GEMV tile size
|
||
const int GEMV_OC_PER_BLOCK = 4; // P1: Output channels per block for GEMV
|
||
const int SUB_GEMM_TILE_DIM = 16; // GEMM_Int
|
||
const int BLOCK_SIZE = 256; // Quant / SumBq / Bias
|
||
const int DEQUANT_TILE_DIM = 32; // Dequant
|
||
const int WARP_SIZE = 32;
|
||
|
||
template<typename T, typename dT>
|
||
__global__ void DequantizeInt8Weight(
|
||
const int8_t* quantized_kernel,
|
||
dT* dequantized_kernel,
|
||
const T* scale,
|
||
const T* offset,
|
||
const int oc, const int ic, const int ic_p, const int quanC
|
||
) {
|
||
// 每个线程负责反量化一个权重值
|
||
const int col = blockIdx.x * blockDim.x + threadIdx.x; // input channel
|
||
const int row = blockIdx.y * blockDim.y + threadIdx.y; // output channel
|
||
|
||
if (row < oc && col < ic) {
|
||
// 计算量化参数的索引
|
||
const int num_quan_groups_per_channel = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
const int group_idx = col / ic_per_group;
|
||
const int quan_param_index = row * num_quan_groups_per_channel + group_idx;
|
||
|
||
const float x_scale = (float)scale[quan_param_index];
|
||
const float x_offset = (float)offset[quan_param_index];
|
||
|
||
// 读取量化的权重
|
||
const float quantized_val = quantized_kernel[row * ic_p + col];
|
||
|
||
// 反量化并写入目标 Tensor
|
||
dequantized_kernel[row * ic_p + col] = (dT)(quantized_val * x_scale + x_offset);
|
||
}
|
||
}
|
||
|
||
// Optimized INT4 weight dequantization: each thread unpacks 8 bytes → 16 FP16 values
|
||
// Uses uint2 vectorized load for weights and float4 (8 half) vectorized writes
|
||
template<typename T, typename dT>
|
||
__global__ void DequantizeInt4Weight(
|
||
const uint8_t* __restrict__ packed_weight,
|
||
dT* __restrict__ output,
|
||
const T* __restrict__ scale,
|
||
const T* __restrict__ offset,
|
||
const int oc, const int ic, const int ic_p, const int quanC
|
||
) {
|
||
// Each thread handles 8 packed bytes = 16 int4 values
|
||
const int tid = blockIdx.y * blockDim.x + threadIdx.x;
|
||
const int row = blockIdx.x; // OC index
|
||
|
||
if (row >= oc) return;
|
||
|
||
const int half_ic = ic_p / 2; // bytes per row
|
||
const int col_byte = tid * 8; // starting byte within row
|
||
if (col_byte >= half_ic) return;
|
||
|
||
const int num_qg = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = max(1, (num_qg > 0) ? (ic / num_qg) : ic);
|
||
const int qparam_base = row * num_qg;
|
||
|
||
// Vectorized 8-byte weight load (two uint32s)
|
||
const uint32_t* src = reinterpret_cast<const uint32_t*>(packed_weight + row * half_ic + col_byte);
|
||
const uint32_t packed0 = __ldg(src);
|
||
const uint32_t packed1 = (col_byte + 4 < half_ic) ? __ldg(src + 1) : 0;
|
||
|
||
const int col = col_byte * 2; // starting ic index
|
||
const int out_base = row * ic_p + col;
|
||
|
||
// Process 8 bytes = 16 int4 values, write as half2 pairs
|
||
half result[16];
|
||
#pragma unroll
|
||
for (int j = 0; j < 4; j++) {
|
||
const int kk = col + j * 2;
|
||
if (kk >= ic) break;
|
||
const int gidx = min(kk / ic_per_group, num_qg - 1);
|
||
const float s = (float)__ldg(&scale[qparam_base + gidx]);
|
||
const float o = (float)__ldg(&offset[qparam_base + gidx]);
|
||
const uint8_t byte_val = (packed0 >> (j * 8)) & 0xFF;
|
||
result[j * 2] = (half)(((float)(byte_val >> 4) - 8.0f) * s + o);
|
||
result[j * 2 + 1] = (half)(((float)(byte_val & 0x0F) - 8.0f) * s + o);
|
||
}
|
||
#pragma unroll
|
||
for (int j = 0; j < 4; j++) {
|
||
const int kk = col + 8 + j * 2;
|
||
if (kk >= ic) break;
|
||
const int gidx = min(kk / ic_per_group, num_qg - 1);
|
||
const float s = (float)__ldg(&scale[qparam_base + gidx]);
|
||
const float o = (float)__ldg(&offset[qparam_base + gidx]);
|
||
const uint8_t byte_val = (packed1 >> (j * 8)) & 0xFF;
|
||
result[8 + j * 2] = (half)(((float)(byte_val >> 4) - 8.0f) * s + o);
|
||
result[8 + j * 2 + 1] = (half)(((float)(byte_val & 0x0F) - 8.0f) * s + o);
|
||
}
|
||
|
||
// Vectorized write: 16 halves = 32 bytes = float4 + float4
|
||
if (col + 8 <= ic) {
|
||
*reinterpret_cast<float4*>(output + out_base) = *reinterpret_cast<float4*>(result);
|
||
} else {
|
||
for (int i = 0; i < 8 && col + i < ic; i++) output[out_base + i] = (dT)result[i];
|
||
}
|
||
if (col + 16 <= ic) {
|
||
*reinterpret_cast<float4*>(output + out_base + 8) = *reinterpret_cast<float4*>(result + 8);
|
||
} else if (col + 8 < ic) {
|
||
for (int i = 8; i < 16 && col + i < ic; i++) output[out_base + i] = (dT)result[i];
|
||
}
|
||
}
|
||
|
||
// R6: Pre-compute GEMV params: scale and adj_offset as float2 array
|
||
// Eliminates scattered offset loads and runtime computation in GEMV
|
||
// Grid: (ceil(total/256)), Block: 256
|
||
template<typename T>
|
||
__global__ void PrecomputeGemvParams(
|
||
const T* __restrict__ scale,
|
||
const T* __restrict__ offset,
|
||
float2* __restrict__ params, // [oc * num_qg], .x=scale, .y=adj_offset
|
||
const int total // oc * num_qg
|
||
) {
|
||
const int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||
if (idx >= total) return;
|
||
const float s = (float)scale[idx];
|
||
const float o = (float)offset[idx];
|
||
params[idx] = make_float2(s, o - 8.0f * s); // adj_offset = offset - 8*scale
|
||
}
|
||
|
||
// 动态量化 A、计算 sum_A
|
||
template<typename T>
|
||
__global__ void QuantA(
|
||
const T* A_sub_fp, int8_t* A_sub_q,
|
||
T* scale_A_out, T* offset_A_out, int32_t* sum_A_q_out,
|
||
const int M, const int K_i, const int lda
|
||
) {
|
||
// 使用 union 复用共享内存,减少总占用量
|
||
__shared__ union {
|
||
float min_max_vals[2][BLOCK_SIZE / WARP_SIZE]; // 用于 min/max 的 warp 间规约
|
||
int32_t sum_vals[BLOCK_SIZE / WARP_SIZE]; // 用于 sum 的 warp 间规约
|
||
float scale_offset[2]; // 用于向块内所有线程广播 scale 和 offset
|
||
} smem;
|
||
|
||
const int m = blockIdx.x;
|
||
if (m >= M) return;
|
||
|
||
const int tid = threadIdx.x;
|
||
const int warp_id = tid / WARP_SIZE;
|
||
const int lane_id = tid % WARP_SIZE;
|
||
|
||
float my_min = FLT_MAX;
|
||
float my_max = -FLT_MAX;
|
||
|
||
for (int k = tid; k < K_i; k += BLOCK_SIZE) {
|
||
float val = (float)A_sub_fp[m * lda + k];
|
||
my_min = min(my_min, val);
|
||
my_max = max(my_max, val);
|
||
}
|
||
|
||
// Warp 内规约:使用 __shfl_down_sync 在 warp 内无锁计算 min/max
|
||
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
|
||
my_min = min(my_min, __shfl_down_sync(0xFFFFFFFF, my_min, offset));
|
||
my_max = max(my_max, __shfl_down_sync(0xFFFFFFFF, my_max, offset));
|
||
}
|
||
|
||
// Warp 间规约:每个 warp 的 0 号线程将 warp 结果写入共享内存
|
||
if (lane_id == 0) {
|
||
smem.min_max_vals[0][warp_id] = my_min;
|
||
smem.min_max_vals[1][warp_id] = my_max;
|
||
}
|
||
__syncthreads();
|
||
|
||
if (warp_id == 0) {
|
||
// 从共享内存加载各 warp 的结果
|
||
if (lane_id < (BLOCK_SIZE / WARP_SIZE)) {
|
||
my_min = smem.min_max_vals[0][lane_id];
|
||
my_max = smem.min_max_vals[1][lane_id];
|
||
} else {
|
||
my_min = FLT_MAX;
|
||
my_max = -FLT_MAX;
|
||
}
|
||
|
||
// 在第一个 warp 内部完成最终规约
|
||
for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset >>= 1) {
|
||
my_min = min(my_min, __shfl_down_sync(0xFFFFFFFF, my_min, offset));
|
||
my_max = max(my_max, __shfl_down_sync(0xFFFFFFFF, my_max, offset));
|
||
}
|
||
}
|
||
// 线程 0 计算 scale/offset 并写入共享内存进行广播
|
||
if (tid == 0) {
|
||
float scale = (my_max - my_min) / 255.0f;
|
||
float offset;
|
||
if (abs(scale) > 1e-5) {
|
||
offset = my_max - scale * 127.0f; // my_max 映射到 127, my_min 映射到 -128
|
||
} else {
|
||
scale = 1.0f;
|
||
offset = my_max;
|
||
}
|
||
|
||
scale_A_out[m] = (T)scale;
|
||
offset_A_out[m] = (T)offset;
|
||
|
||
smem.scale_offset[0] = scale;
|
||
smem.scale_offset[1] = offset;
|
||
}
|
||
__syncthreads();
|
||
|
||
// 所有线程从共享内存读取 scale/offset, 并进行量化和求和
|
||
const float s_scale_A = smem.scale_offset[0];
|
||
const float s_offset_A = smem.scale_offset[1];
|
||
|
||
int32_t my_sum_q = 0;
|
||
for (int k = tid; k < K_i; k += BLOCK_SIZE) {
|
||
float val_fp = (float)A_sub_fp[m * lda + k];
|
||
int32_t val_q = roundf((val_fp - s_offset_A) / s_scale_A);
|
||
int8_t a_q = (int8_t)max(-128, min(127, val_q));
|
||
A_sub_q[m * K_i + k] = a_q;
|
||
my_sum_q += a_q;
|
||
}
|
||
|
||
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
|
||
my_sum_q += __shfl_down_sync(0xFFFFFFFF, my_sum_q, offset);
|
||
}
|
||
|
||
if (lane_id == 0) smem.sum_vals[warp_id] = my_sum_q;
|
||
__syncthreads();
|
||
|
||
if (warp_id == 0) {
|
||
my_sum_q = (lane_id < (BLOCK_SIZE / WARP_SIZE)) ? smem.sum_vals[lane_id] : 0;
|
||
for (int offset = (BLOCK_SIZE / WARP_SIZE) / 2; offset > 0; offset >>= 1) {
|
||
my_sum_q += __shfl_down_sync(0xFFFFFFFF, my_sum_q, offset);
|
||
}
|
||
}
|
||
|
||
// 线程 0 将最终的和写入全局内存
|
||
if (tid == 0) sum_A_q_out[m] = my_sum_q;
|
||
}
|
||
|
||
__global__ void GEMM_Int8(
|
||
const int8_t* A_q, const int8_t* B_q, int32_t* C_q,
|
||
const int M, const int N, const int K_i,
|
||
const int lda_q, const int ldb, const int ldc
|
||
) {
|
||
__shared__ int8_t A_tile_s8[SUB_GEMM_TILE_DIM][SUB_GEMM_TILE_DIM];
|
||
__shared__ int8_t B_tile_s8[SUB_GEMM_TILE_DIM][SUB_GEMM_TILE_DIM];
|
||
|
||
const int block_row = blockIdx.y;
|
||
const int block_col = blockIdx.x;
|
||
const int ty = threadIdx.y;
|
||
const int tx = threadIdx.x;
|
||
|
||
const int row = block_row * SUB_GEMM_TILE_DIM + ty;
|
||
const int col = block_col * SUB_GEMM_TILE_DIM + tx;
|
||
|
||
int32_t acc = 0;
|
||
const int num_k_tiles = UP_DIV(K_i, SUB_GEMM_TILE_DIM);
|
||
|
||
for (int k_tile = 0; k_tile < num_k_tiles; ++k_tile) {
|
||
const int k_tile_base = k_tile * SUB_GEMM_TILE_DIM;
|
||
|
||
A_tile_s8[ty][tx] = (row < M && (k_tile_base + tx) < K_i) ? A_q[row * lda_q + k_tile_base + tx] : 0;
|
||
B_tile_s8[ty][tx] = (col < N && (k_tile_base + ty) < K_i) ? B_q[col * ldb + k_tile_base + ty] : 0;
|
||
__syncthreads();
|
||
|
||
#pragma unroll
|
||
for (int k = 0; k < SUB_GEMM_TILE_DIM; ++k) {
|
||
acc += (int32_t)A_tile_s8[ty][k] * (int32_t)B_tile_s8[k][tx];
|
||
}
|
||
__syncthreads();
|
||
}
|
||
|
||
if (row < M && col < N) C_q[row * ldc + col] = acc;
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void DequantAndAcc(
|
||
const int32_t* C_q, T* C_fp_final,
|
||
const T* scale_A_in, const T* offset_A_in,
|
||
const T* base_scale_B, const T* base_offset_B,
|
||
const int32_t* base_sum_B_q,
|
||
const int group_idx, const int num_oc_groups,
|
||
const int32_t* sum_A_q_in,
|
||
const int M, const int N, const int K_i, const int ldc
|
||
) {
|
||
// 使用共享内存缓存行和列的量化参数
|
||
__shared__ float smem_scale_A[DEQUANT_TILE_DIM];
|
||
__shared__ float smem_offset_A[DEQUANT_TILE_DIM];
|
||
__shared__ int32_t smem_sum_A_q[DEQUANT_TILE_DIM];
|
||
|
||
__shared__ float smem_scale_B[DEQUANT_TILE_DIM];
|
||
__shared__ float smem_offset_B[DEQUANT_TILE_DIM];
|
||
__shared__ int32_t smem_sum_B_q[DEQUANT_TILE_DIM];
|
||
|
||
const int tx = threadIdx.x;
|
||
const int ty = threadIdx.y;
|
||
|
||
const int block_row_start = blockIdx.y * DEQUANT_TILE_DIM;
|
||
const int block_col_start = blockIdx.x * DEQUANT_TILE_DIM;
|
||
|
||
// 每个线程加载一个 A 的参数
|
||
int m_load_idx = block_row_start + ty;
|
||
if (tx == 0 && m_load_idx < M) {
|
||
smem_scale_A[ty] = (float)scale_A_in[m_load_idx];
|
||
smem_offset_A[ty] = (float)offset_A_in[m_load_idx];
|
||
smem_sum_A_q[ty] = sum_A_q_in[m_load_idx];
|
||
}
|
||
|
||
// 每个线程加载一个 B 的参数
|
||
int n_load_idx = block_col_start + tx;
|
||
if (ty == 0 && n_load_idx < N) {
|
||
const size_t b_param_idx = n_load_idx * num_oc_groups + group_idx;
|
||
smem_scale_B[tx] = (float)base_scale_B[b_param_idx];
|
||
smem_offset_B[tx] = (float)base_offset_B[b_param_idx];
|
||
smem_sum_B_q[tx] = base_sum_B_q[b_param_idx];
|
||
}
|
||
|
||
__syncthreads();
|
||
|
||
// 计算每个线程负责的输出点
|
||
const int m = block_row_start + ty;
|
||
const int n = block_col_start + tx;
|
||
|
||
if (m < M && n < N) {
|
||
// 从共享内存中读取参数
|
||
const float scale_A = smem_scale_A[ty];
|
||
const float offset_A = smem_offset_A[ty];
|
||
const float sum_A_q = (float)smem_sum_A_q[ty];
|
||
|
||
const float scale_B = smem_scale_B[tx];
|
||
const float offset_B = smem_offset_B[tx];
|
||
const float sum_B_q = (float)smem_sum_B_q[tx];
|
||
|
||
const float c_q_val = (float)C_q[m * ldc + n];
|
||
|
||
float term1 = scale_A * (c_q_val * scale_B + sum_A_q * offset_B);
|
||
float term2 = offset_A * (sum_B_q * scale_B + K_i * offset_B);
|
||
float final_val = term1 + term2;
|
||
|
||
#if (defined(__CUDACC__) && (!defined(__CUDA_ARCH__) || (__CUDA_ARCH__ >= 700))) || defined(_NVHPC_CUDA)
|
||
if constexpr (std::is_same_v<T, float>) {
|
||
atomicAdd(&C_fp_final[m * ldc + n], final_val);
|
||
} else if constexpr (std::is_same_v<T, half>) {
|
||
// printf("[final_val]%.4f %.4f\n", __half2float(C_fp_final[m * ldc + n]), final_val);
|
||
atomicAdd(&C_fp_final[m * ldc + n], __float2half(final_val));
|
||
// printf("[C_fp_final]%.4f\n", __half2float(C_fp_final[m * ldc + n]));
|
||
}
|
||
#else
|
||
C_fp_final[m * ldc + n] += final_val;
|
||
#endif
|
||
}
|
||
}
|
||
|
||
// 预计算权重 B 的修正项 (sum_B_q)
|
||
__global__ void Precompute_SumBq(
|
||
const int8_t* B_q, int32_t* sum_B_q_out,
|
||
const int num_groups, const int ic_per_group,
|
||
const int oc, const int ic_p
|
||
) {
|
||
// 每个线程处理一个 (output_channel, group) 的修正项
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < (size_t)oc * num_groups; index += blockDim.x * gridDim.x) {
|
||
|
||
const int i = index % num_groups; // group_idx
|
||
const int n = index / num_groups; // output_channel
|
||
|
||
const int k_start = i * ic_per_group;
|
||
|
||
int32_t sum = 0;
|
||
for (int k_offset = 0; k_offset < ic_per_group; ++k_offset) {
|
||
// 访问 B 矩阵的布局是 [oc][ic_p]
|
||
sum += (int32_t)B_q[n * ic_p + k_start + k_offset];
|
||
}
|
||
sum_B_q_out[index] = sum;
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void BiasAndActivation(
|
||
T* data, const T* bias,
|
||
const float minV, const float maxV,
|
||
const int M, const int N, const int ldc
|
||
) {
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < (size_t)M * N; index += blockDim.x * gridDim.x) {
|
||
const int m = index / N;
|
||
const int n = index % N;
|
||
|
||
const size_t buffer_idx = m * ldc + n;
|
||
float val = (float)data[buffer_idx];
|
||
val += (float)bias[n];
|
||
val = max(val, minV);
|
||
val = min(val, maxV);
|
||
data[buffer_idx] = (T)val;
|
||
}
|
||
}
|
||
|
||
template <typename T>
|
||
__global__ void print_tensor_kernel(const T* d_tensor, int rows, int cols) {
|
||
if (blockIdx.x == 0 && threadIdx.x == 0) {
|
||
printf("----------- Tensor in Kernel -----------\n");
|
||
printf("Rows: %d, Cols: %d\n", rows, cols);
|
||
for (int i = 0; i < rows; ++i) {
|
||
for (int j = 0; j < cols; ++j) {
|
||
T value = d_tensor[i * cols + j];
|
||
|
||
if constexpr (std::is_same_v<T, half>) {
|
||
printf("%.4f ", __half2float(value));
|
||
} else {
|
||
printf("%.4f ", value);
|
||
}
|
||
}
|
||
printf("\n");
|
||
}
|
||
printf("---------------------------------------\n");
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void GEMM_FpAInt8B(
|
||
const T* input,
|
||
const int8_t* kernel,
|
||
const T* scale, const T* offset, const T* bias,
|
||
T* output,
|
||
const float maxV, const float minV,
|
||
const int ic, const int ic_p, const int oc, const int oc_p,
|
||
const int batch, const int quanC
|
||
) {
|
||
__shared__ T A_tile[TILE_DIM][TILE_DIM]; // [batch, ic]
|
||
__shared__ int8_t B_tile_s8[TILE_DIM][TILE_DIM]; // [ic, oc] kernel 本身是 B^T [oc, ic]
|
||
__shared__ T B_tile_fp[TILE_DIM][TILE_DIM];
|
||
|
||
const int tx = threadIdx.x;
|
||
const int ty = threadIdx.y;
|
||
|
||
const int block_row = blockIdx.y; // batch
|
||
const int block_col = blockIdx.x; // output channel
|
||
|
||
// 每个线程负责计算输出Tile中的一个元素
|
||
const int out_row = block_row * TILE_DIM + ty; // M
|
||
const int out_col = block_col * TILE_DIM + tx; // N
|
||
|
||
float acc = 0.0f;
|
||
// 沿K维度(输入通道ic)分块循环
|
||
const int num_k_tiles = UP_DIV(ic, TILE_DIM);
|
||
|
||
const int num_quan_groups_per_channel = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
|
||
for (int k_tile = 0; k_tile < num_k_tiles; ++k_tile) {
|
||
const int k_tile_base = k_tile * TILE_DIM;
|
||
|
||
// 合并访问加载 A_tile (input),线程 (ty, tx) 加载 A_tile[ty][tx]
|
||
int a_col_idx = k_tile_base + tx;
|
||
A_tile[ty][tx] = (out_row < batch && a_col_idx < ic) ? input[out_row * ic_p + a_col_idx] : (T)0.0f;
|
||
|
||
// 合并访问加载 B_tile (kernel),线程 (ty, tx) 加载 B_tile[ty][tx]
|
||
// kernel 布局为 [oc, ic],需要 B(k,n),即 kernel(n,k)
|
||
int b_load_row = block_col * TILE_DIM + ty;
|
||
int b_col_idx = k_tile_base + tx;
|
||
B_tile_s8[ty][tx] = (b_load_row < oc && b_col_idx < ic) ? kernel[b_load_row * ic_p + b_col_idx] : 0;
|
||
|
||
__syncthreads();
|
||
|
||
// 反量化 + 转置
|
||
const int K = ty;
|
||
const int N = tx;
|
||
|
||
const int global_k = k_tile_base + K;
|
||
const int global_n = block_col * TILE_DIM + N;
|
||
|
||
if (global_n < oc && global_k < ic) {
|
||
const int group_idx = global_k / ic_per_group;
|
||
const int quan_param_index = global_n * num_quan_groups_per_channel + group_idx;
|
||
|
||
const float x_scale = (float)scale[quan_param_index];
|
||
const float x_offset = (float)offset[quan_param_index];
|
||
|
||
// B_tile_s8(n,k), thread(n,k) -> (tx, ty)
|
||
// So we need to read from B_tile_s8[n_dim_in_tile][k_dim_in_tile] -> B_tile_s8[tx][ty]
|
||
const float b_quant = (float)B_tile_s8[N][K];
|
||
|
||
// B_tile_fp[k][n] -> B_tile_fp[ty][tx]
|
||
B_tile_fp[K][N] = (T)(b_quant * x_scale + x_offset);
|
||
}
|
||
|
||
__syncthreads();
|
||
|
||
// 在 SMEM 中进行子矩阵乘法
|
||
if (out_col < oc) {
|
||
#pragma unroll
|
||
for (int k = 0; k < TILE_DIM; ++k) {
|
||
acc += (float)A_tile[ty][k] * (float)B_tile_fp[k][tx];
|
||
}
|
||
}
|
||
|
||
__syncthreads();
|
||
}
|
||
|
||
// 写回全局内存
|
||
if (out_row < batch && out_col < oc) {
|
||
acc += (float)bias[out_col];
|
||
acc = max(acc, minV);
|
||
acc = min(acc, maxV);
|
||
output[out_row * oc_p + out_col] = (T)acc;
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void GEMV_FpAInt8B(
|
||
const T* input,
|
||
const int8_t* kernel,
|
||
const T* scale, const T* offset, const T* bias,
|
||
T* output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int quanC
|
||
) {
|
||
__shared__ float partial_sums[GEMV_TILE]; // 每个线程块计算一个输出通道
|
||
|
||
const int oz = blockIdx.x;
|
||
const int b = blockIdx.y;
|
||
|
||
// if (oz >= oc || b >= batch) return;
|
||
|
||
const int tid = threadIdx.x; // 块内线程ID
|
||
|
||
// 准备量化参数
|
||
const int num_quan_groups_per_channel = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
const int quan_param_index_base = oz * num_quan_groups_per_channel;
|
||
|
||
// 每个线程计算自己的部分和
|
||
float my_sum = 0.0f;
|
||
for (int k = tid; k < ic; k += blockDim.x) {
|
||
const float inp0 = input[b * ic_p + k];
|
||
const int8_t ker0 = kernel[oz * ic_p + k];
|
||
|
||
const int group_idx = k / ic_per_group;
|
||
const int quan_param_index = quan_param_index_base + group_idx;
|
||
const float x_scale = scale[quan_param_index];
|
||
const float x_offset = offset[quan_param_index];
|
||
|
||
my_sum += inp0 * ((float)ker0 * x_scale + x_offset);
|
||
}
|
||
|
||
partial_sums[tid] = my_sum;
|
||
__syncthreads();
|
||
|
||
// 在 SMEM 中执行并行规约(蝶式交换)
|
||
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
|
||
if (tid < s) partial_sums[tid] += partial_sums[tid + s];
|
||
__syncthreads();
|
||
}
|
||
|
||
if (tid == 0) { // 写回全局内存
|
||
float final_val = partial_sums[0] + (float)bias[oz];
|
||
final_val = max(final_val, minV);
|
||
final_val = min(final_val, maxV);
|
||
output[b * oc_p + oz] = (T)final_val;
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void GEMM_FpAInt4B(
|
||
const T* input,
|
||
const uint8_t* kernel, // kernel 是打包的 int4
|
||
const T* scale, const T* offset, const T* bias,
|
||
T* output,
|
||
const float maxV, const float minV,
|
||
const int ic, const int ic_p, const int oc, const int oc_p,
|
||
const int batch, const int quanC
|
||
) {
|
||
__shared__ T A_tile[TILE_DIM][TILE_DIM]; // [batch, ic]
|
||
__shared__ uint8_t B_tile_u8[TILE_DIM][TILE_DIM / 2]; // [ic, oc] kernel 本身是 B^T [oc, ic]
|
||
__shared__ T B_tile_fp[TILE_DIM][TILE_DIM];
|
||
|
||
const int tx = threadIdx.x;
|
||
const int ty = threadIdx.y;
|
||
|
||
const int block_row = blockIdx.y; // batch
|
||
const int block_col = blockIdx.x; // output channel
|
||
|
||
const int out_row = block_row * TILE_DIM + ty; // M
|
||
const int out_col = block_col * TILE_DIM + tx; // N
|
||
|
||
float acc = 0.0f;
|
||
const int num_k_tiles = UP_DIV(ic, TILE_DIM);
|
||
|
||
const int num_quan_groups_per_channel = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
|
||
for (int k_tile = 0; k_tile < num_k_tiles; ++k_tile) {
|
||
const int k_tile_base = k_tile * TILE_DIM;
|
||
|
||
// 合并访问加载 A_tile (input)
|
||
int a_col_idx = k_tile_base + tx;
|
||
A_tile[ty][tx] = (out_row < batch && a_col_idx < ic) ? input[out_row * ic_p + a_col_idx] : (T)0.0f;
|
||
|
||
// 合并访问加载 B_tile (kernel),每个线程加载一个 byte,但 tile 宽度减半
|
||
if (tx < TILE_DIM / 2) {
|
||
int b_load_row = block_col * TILE_DIM + ty;
|
||
// kernel 布局为 [oc, ic/2],所以列索引要除以2
|
||
int b_col_idx = (k_tile_base / 2) + tx;
|
||
B_tile_u8[ty][tx] = (b_load_row < oc && (k_tile_base + tx*2) < ic) ? kernel[b_load_row * (ic_p / 2) + b_col_idx] : 0;
|
||
}
|
||
|
||
__syncthreads();
|
||
|
||
// 反量化 + 转置
|
||
const int K = ty;
|
||
const int N = tx;
|
||
|
||
const int global_k = k_tile_base + K;
|
||
const int global_n = block_col * TILE_DIM + N;
|
||
|
||
if (global_n < oc && global_k < ic) {
|
||
const int group_idx = global_k / ic_per_group;
|
||
const int quan_param_index = global_n * num_quan_groups_per_channel + group_idx;
|
||
|
||
const float x_scale = (float)scale[quan_param_index];
|
||
const float x_offset = (float)offset[quan_param_index];
|
||
|
||
// B_tile_u8(n,k) -> B_tile_u8[tx][ty/2] (转置后)
|
||
const uint8_t b_packed = B_tile_u8[N][K / 2];
|
||
// 根据 K 的奇偶性解包 int4
|
||
const int8_t b_quant_s8 = (K % 2 == 0) ? ((b_packed >> 4) - 8) : ((b_packed & 0x0F) - 8);
|
||
const float b_quant = (float)b_quant_s8;
|
||
|
||
// B_tile_fp[k][n] -> B_tile_fp[ty][tx]
|
||
B_tile_fp[K][N] = (T)(b_quant * x_scale + x_offset);
|
||
}
|
||
|
||
__syncthreads();
|
||
|
||
// 在 SMEM 中进行子矩阵乘法
|
||
if (out_col < oc) {
|
||
#pragma unroll
|
||
for (int k = 0; k < TILE_DIM; ++k) {
|
||
acc += (float)A_tile[ty][k] * (float)B_tile_fp[k][tx];
|
||
}
|
||
}
|
||
|
||
__syncthreads();
|
||
}
|
||
|
||
// 写回全局内存
|
||
if (out_row < batch && out_col < oc) {
|
||
acc += (float)bias[out_col];
|
||
acc = max(acc, minV);
|
||
acc = min(acc, maxV);
|
||
output[out_row * oc_p + out_col] = (T)acc;
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void GEMV_FpAInt4B(
|
||
const T* input,
|
||
const uint8_t* kernel, // kernel 是打包的 int4
|
||
const T* scale, const T* offset, const T* bias,
|
||
T* output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int quanC
|
||
) {
|
||
extern __shared__ uint8_t smem_buffer[];
|
||
T* smem_input = reinterpret_cast<T*>(smem_buffer);
|
||
float* partial_sums = reinterpret_cast<float*>(smem_buffer + ic_p * sizeof(T));
|
||
|
||
const int oz = blockIdx.x; // 当前线程块负责计算的输出通道索引
|
||
const int b = blockIdx.y;
|
||
|
||
const int tid = threadIdx.x;
|
||
|
||
// if (oz >= oc || b >= batch) return;
|
||
|
||
// 加载 input 到共享内存
|
||
for (int i = tid; i < ic; i += blockDim.x) smem_input[i] = input[b * ic_p + i];
|
||
for (int i = ic + tid; i < ic_p; i += blockDim.x) smem_input[i] = (T)0.0f;
|
||
__syncthreads();
|
||
|
||
const int num_quan_groups_per_channel = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
const int quan_param_index_base = oz * num_quan_groups_per_channel;
|
||
|
||
float my_sum = 0.0f;
|
||
for (int k = tid * 2; k < ic; k += blockDim.x * 2) {
|
||
// 加载打包的权重并解包
|
||
const uint8_t ker_packed = kernel[oz * (ic_p / 2) + k / 2];
|
||
const int8_t ker0_s8 = (ker_packed >> 4) - 8;
|
||
const int8_t ker1_s8 = (ker_packed & 0x0F) - 8;
|
||
|
||
const int group_idx0 = k / ic_per_group;
|
||
const int quan_param_index0 = quan_param_index_base + group_idx0;
|
||
const float x_scale0 = scale[quan_param_index0];
|
||
const float x_offset0 = offset[quan_param_index0];
|
||
my_sum += (float)smem_input[k] * ((float)ker0_s8 * x_scale0 + x_offset0);
|
||
my_sum += (float)smem_input[k + 1] * ((float)ker1_s8 * x_scale0 + x_offset0);
|
||
|
||
// if (k + 1 < ic) {
|
||
// const float inp1 = (float)smem_input[k + 1];
|
||
// const int group_idx1 = (k + 1) / ic_per_group;
|
||
|
||
// if (group_idx0 == group_idx1) {
|
||
// my_sum += inp1 * ((float)ker1_s8 * x_scale0 + x_offset0);
|
||
// } else {
|
||
// const int quan_param_index1 = quan_param_index_base + group_idx1;
|
||
// const float x_scale1 = (float)scale[quan_param_index1];
|
||
// const float x_offset1 = (float)offset[quan_param_index1];
|
||
// my_sum += inp1 * ((float)ker1_s8 * x_scale1 + x_offset1);
|
||
// }
|
||
// }
|
||
}
|
||
|
||
partial_sums[tid] = my_sum;
|
||
__syncthreads();
|
||
|
||
// 并行规约
|
||
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
|
||
if (tid < s) partial_sums[tid] += partial_sums[tid + s];
|
||
__syncthreads();
|
||
}
|
||
|
||
if (tid == 0) {
|
||
float final_val = partial_sums[0] + (float)bias[oz];
|
||
final_val = max(final_val, minV);
|
||
final_val = min(final_val, maxV);
|
||
output[b * oc_p + oz] = (T)final_val;
|
||
}
|
||
}
|
||
|
||
|
||
// =====================================================================
|
||
// P1-V5: 128-thread GEMV — all 128 threads cooperate on 1 OC
|
||
// - Grid: (oc, batch), Block: 128
|
||
// =====================================================================
|
||
template<typename T>
|
||
__global__ void GEMV_FpAInt4B_V5(
|
||
const T* __restrict__ input,
|
||
const uint8_t* __restrict__ kernel, // packed int4 weights [oc, ic_p/2]
|
||
const T* __restrict__ scale, const T* __restrict__ offset, const T* __restrict__ bias,
|
||
T* __restrict__ output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int quanC
|
||
) {
|
||
const int oz = blockIdx.x; // 1 OC per block
|
||
const int b = blockIdx.y;
|
||
if (oz >= oc || b >= batch) return;
|
||
|
||
const int tid = threadIdx.x; // 0..127
|
||
const int warp_id = tid / 32;
|
||
const int lane_id = tid % 32;
|
||
|
||
const int num_qg = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_qg > 0) ? (ic / num_qg) : ic;
|
||
const int qparam_base = oz * num_qg;
|
||
|
||
const int row_bytes = ic_p / 2;
|
||
const uint8_t* w_row = kernel + oz * row_bytes;
|
||
const T* in_ptr = input + b * ic_p;
|
||
|
||
float acc = 0.0f;
|
||
|
||
// 128 threads × 8 elements = 1024 per iteration
|
||
for (int k = tid * 8; k < ic; k += 1024) {
|
||
uint32_t w32 = __ldg(reinterpret_cast<const uint32_t*>(w_row + k / 2));
|
||
|
||
const int gidx = k / ic_per_group;
|
||
const float s = (float)__ldg(&scale[qparam_base + gidx]);
|
||
const float o = (float)__ldg(&offset[qparam_base + gidx]);
|
||
const float adj_off = o - 8.0f * s;
|
||
|
||
#pragma unroll
|
||
for (int j = 0; j < 4; j++) {
|
||
const int kk = k + j * 2;
|
||
if (kk >= ic) break;
|
||
const uint8_t packed = (w32 >> (j * 8)) & 0xFF;
|
||
const float w0 = (float)(packed >> 4) * s + adj_off;
|
||
const float w1 = (float)(packed & 0x0F) * s + adj_off;
|
||
acc += (float)in_ptr[kk] * w0;
|
||
if (kk + 1 < ic) acc += (float)in_ptr[kk + 1] * w1;
|
||
}
|
||
}
|
||
|
||
// Warp-level reduction
|
||
#pragma unroll
|
||
for (int off = 16; off > 0; off >>= 1) {
|
||
acc += __shfl_xor_sync(0xffffffff, acc, off);
|
||
}
|
||
|
||
// Cross-warp reduction via shared memory (4 warps → 1 result)
|
||
__shared__ float smem[4];
|
||
if (lane_id == 0) smem[warp_id] = acc;
|
||
__syncthreads();
|
||
|
||
if (tid == 0) {
|
||
float result = smem[0] + smem[1] + smem[2] + smem[3];
|
||
result += (float)bias[oz];
|
||
result = fmaxf(result, minV);
|
||
result = fminf(result, maxV);
|
||
output[b * oc_p + oz] = (T)result;
|
||
}
|
||
}
|
||
|
||
// =====================================================================
|
||
// V9: Multi-OC GEMV — each block processes OC_PER_BLK output channels
|
||
// 128 threads cooperatively reduce each OC in sequence (fewer blocks, better latency hiding)
|
||
// Grid: (ceil(oc/OC_PER_BLK), batch), Block: 128
|
||
// =====================================================================
|
||
template<typename T, int OC_PER_BLK>
|
||
__global__ void GEMV_FpAInt4B_V9(
|
||
const T* __restrict__ input,
|
||
const uint8_t* __restrict__ kernel, // packed int4 weights [oc, ic_p/2]
|
||
const T* __restrict__ scale, const T* __restrict__ offset, const T* __restrict__ bias,
|
||
T* __restrict__ output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int quanC
|
||
) {
|
||
const int oc_base = blockIdx.x * OC_PER_BLK;
|
||
const int b = blockIdx.y;
|
||
if (oc_base >= oc || b >= batch) return;
|
||
|
||
const int tid = threadIdx.x;
|
||
const int warp_id = tid / 32;
|
||
const int lane_id = tid % 32;
|
||
|
||
const int num_qg = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_qg > 0) ? (ic / num_qg) : ic;
|
||
|
||
const int row_bytes = ic_p / 2;
|
||
const T* in_ptr = input + b * ic_p;
|
||
|
||
// blockDim.x threads, (blockDim.x/32) warps
|
||
const int nwarps = blockDim.x / 32;
|
||
__shared__ float smem[8]; // up to 8 warps (256 threads)
|
||
|
||
#pragma unroll
|
||
for (int oc_off = 0; oc_off < OC_PER_BLK; oc_off++) {
|
||
const int oz = oc_base + oc_off;
|
||
if (oz >= oc) break;
|
||
|
||
const int qparam_base = oz * num_qg;
|
||
const uint8_t* w_row = kernel + oz * row_bytes;
|
||
|
||
float acc = 0.0f;
|
||
|
||
// Each thread handles 8 IC elements per iteration, stride = nthreads * 8
|
||
const int stride = blockDim.x * 8;
|
||
for (int k = tid * 8; k < ic; k += stride) {
|
||
uint32_t w32 = __ldg(reinterpret_cast<const uint32_t*>(w_row + k / 2));
|
||
|
||
const int gidx = k / ic_per_group;
|
||
const float s = (float)__ldg(&scale[qparam_base + gidx]);
|
||
const float o = (float)__ldg(&offset[qparam_base + gidx]);
|
||
const float adj_off = o - 8.0f * s;
|
||
|
||
#pragma unroll
|
||
for (int j = 0; j < 4; j++) {
|
||
const int kk = k + j * 2;
|
||
if (kk >= ic) break;
|
||
const uint8_t packed = (w32 >> (j * 8)) & 0xFF;
|
||
const float w0 = (float)(packed >> 4) * s + adj_off;
|
||
const float w1 = (float)(packed & 0x0F) * s + adj_off;
|
||
acc += (float)in_ptr[kk] * w0;
|
||
if (kk + 1 < ic) acc += (float)in_ptr[kk + 1] * w1;
|
||
}
|
||
}
|
||
|
||
// Warp-level reduction
|
||
#pragma unroll
|
||
for (int off = 16; off > 0; off >>= 1)
|
||
acc += __shfl_xor_sync(0xffffffff, acc, off);
|
||
|
||
if (lane_id == 0) smem[warp_id] = acc;
|
||
__syncthreads();
|
||
|
||
if (tid == 0) {
|
||
float result = 0.0f;
|
||
for (int w = 0; w < nwarps; w++) result += smem[w];
|
||
result += (float)bias[oz];
|
||
result = fmaxf(result, minV);
|
||
result = fminf(result, maxV);
|
||
output[b * oc_p + oz] = (T)result;
|
||
}
|
||
__syncthreads();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// =====================================================================
|
||
// V14: Factored-dequant multi-OC GEMV — reduces ALU per weight byte.
|
||
// Key insight: acc += in * (s * nibble + adj) = s * sum(in*nibble) + adj * sum(in)
|
||
// By factoring out scale/adj, we save 6 FMA per OC per 8-IC iteration.
|
||
// Also: explicit load-compute separation for better instruction scheduling.
|
||
// Grid: (ceil(oc/OC_PER_BLK), batch), Block: 128
|
||
// =====================================================================
|
||
template<typename T, int OC_PER_BLK>
|
||
__global__ void __launch_bounds__(128, 12) GEMV_FpAInt4B_V14(
|
||
const T* __restrict__ input,
|
||
const uint8_t* __restrict__ kernel, // packed int4 weights [oc, ic_p/2]
|
||
const float2* __restrict__ gemv_params, // [oc * num_qg], .x=scale, .y=adj_offset
|
||
const T* __restrict__ bias,
|
||
T* __restrict__ output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int num_qg
|
||
) {
|
||
// Grid layout: (oc/OC_PER_BLK, batch)
|
||
const int oc_base = blockIdx.x * OC_PER_BLK;
|
||
const int b = blockIdx.y;
|
||
if (oc_base >= oc || b >= batch) return;
|
||
|
||
const int tid = threadIdx.x;
|
||
const int warp_id = tid / 32;
|
||
const int lane_id = tid % 32;
|
||
const int nwarps = blockDim.x / 32;
|
||
|
||
const int ic_per_group = ic / num_qg;
|
||
const int row_bytes = ic_p / 2;
|
||
const T* in_ptr = input + b * ic_p;
|
||
|
||
// Compute per-OC base pointers (on-the-fly, no pointer arrays to save registers)
|
||
float accs[OC_PER_BLK];
|
||
#pragma unroll
|
||
for (int i = 0; i < OC_PER_BLK; i++)
|
||
accs[i] = 0.0f;
|
||
|
||
// Main IC loop — all OCs processed simultaneously
|
||
const int stride = blockDim.x * 8;
|
||
for (int k = tid * 8; k < ic; k += stride) {
|
||
// Phase 1: Load ALL input values
|
||
float in_vals[8];
|
||
#pragma unroll
|
||
for (int j = 0; j < 8; j++)
|
||
in_vals[j] = (float)in_ptr[k + j];
|
||
|
||
// Pre-compute input sum for factored adj_offset term (shared across all OCs)
|
||
float input_sum = in_vals[0] + in_vals[1] + in_vals[2] + in_vals[3]
|
||
+ in_vals[4] + in_vals[5] + in_vals[6] + in_vals[7];
|
||
|
||
const int gidx = k / ic_per_group;
|
||
|
||
// Phase 2: Load ALL weights and params before any compute
|
||
uint32_t w_data[OC_PER_BLK];
|
||
float s_data[OC_PER_BLK], adj_data[OC_PER_BLK];
|
||
#pragma unroll
|
||
for (int oi = 0; oi < OC_PER_BLK; oi++) {
|
||
const int oc_idx = oc_base + oi;
|
||
if (oc_idx < oc) {
|
||
w_data[oi] = __ldg(reinterpret_cast<const uint32_t*>(
|
||
kernel + oc_idx * row_bytes + k / 2));
|
||
const float2 p = __ldg(gemv_params + oc_idx * num_qg + gidx);
|
||
s_data[oi] = p.x;
|
||
adj_data[oi] = p.y;
|
||
}
|
||
}
|
||
|
||
// Phase 3: Compute raw dot products (no scale/adj in the hot loop)
|
||
#pragma unroll
|
||
for (int oi = 0; oi < OC_PER_BLK; oi++) {
|
||
if (oc_base + oi >= oc) break;
|
||
float raw_dot = 0.0f;
|
||
#pragma unroll
|
||
for (int j = 0; j < 4; j++) {
|
||
const uint32_t packed = (w_data[oi] >> (j * 8)) & 0xFF;
|
||
raw_dot += in_vals[j * 2] * (float)(packed >> 4);
|
||
raw_dot += in_vals[j * 2 + 1] * (float)(packed & 0x0F);
|
||
}
|
||
// Apply scale and adj_offset: acc += s * raw_dot + adj * input_sum
|
||
accs[oi] = fmaf(adj_data[oi], input_sum, fmaf(s_data[oi], raw_dot, accs[oi]));
|
||
}
|
||
}
|
||
|
||
// Warp-level reduction for all accumulators
|
||
#pragma unroll
|
||
for (int oi = 0; oi < OC_PER_BLK; oi++) {
|
||
#pragma unroll
|
||
for (int off = 16; off > 0; off >>= 1)
|
||
accs[oi] += __shfl_xor_sync(0xffffffff, accs[oi], off);
|
||
}
|
||
|
||
// Single-sync parallel cross-warp reduction for ALL OCs at once
|
||
__shared__ float smem[OC_PER_BLK][8]; // [oc_idx][warp_idx]
|
||
|
||
if (lane_id == 0) {
|
||
#pragma unroll
|
||
for (int oi = 0; oi < OC_PER_BLK; oi++)
|
||
smem[oi][warp_id] = accs[oi];
|
||
}
|
||
__syncthreads();
|
||
|
||
// First OC_PER_BLK threads each reduce one OC
|
||
if (tid < OC_PER_BLK && oc_base + tid < oc) {
|
||
float result = 0.0f;
|
||
for (int w = 0; w < nwarps; w++) result += smem[tid][w];
|
||
result += (float)__ldg(&bias[oc_base + tid]);
|
||
result = fmaxf(result, minV);
|
||
result = fminf(result, maxV);
|
||
output[b * oc_p + oc_base + tid] = (T)result;
|
||
}
|
||
}
|
||
|
||
// V14_MB: Multi-batch variant — one block processes ALL batch elements for the same OC group.
|
||
// Weights are loaded ONCE and reused across batch elements, reducing memory bandwidth by ~batch×.
|
||
template<typename T, int OC_PER_BLK, int MAX_BATCH>
|
||
__global__ void __launch_bounds__(128, 8) GEMV_FpAInt4B_V14_MB(
|
||
const T* __restrict__ input,
|
||
const uint8_t* __restrict__ kernel,
|
||
const float2* __restrict__ gemv_params,
|
||
const T* __restrict__ bias,
|
||
T* __restrict__ output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int num_qg
|
||
) {
|
||
const int oc_base = blockIdx.x * OC_PER_BLK;
|
||
if (oc_base >= oc) return;
|
||
|
||
const int tid = threadIdx.x;
|
||
const int warp_id = tid / 32;
|
||
const int lane_id = tid % 32;
|
||
const int nwarps = blockDim.x / 32;
|
||
|
||
const int ic_per_group = ic / num_qg;
|
||
const int row_bytes = ic_p / 2;
|
||
|
||
float accs[MAX_BATCH * OC_PER_BLK];
|
||
#pragma unroll
|
||
for (int i = 0; i < MAX_BATCH * OC_PER_BLK; i++)
|
||
accs[i] = 0.0f;
|
||
|
||
const int stride = blockDim.x * 8;
|
||
for (int k = tid * 8; k < ic; k += stride) {
|
||
const int gidx = k / ic_per_group;
|
||
|
||
// Load weights ONCE for all batch elements
|
||
uint32_t w_data[OC_PER_BLK];
|
||
float s_data[OC_PER_BLK], adj_data[OC_PER_BLK];
|
||
#pragma unroll
|
||
for (int oi = 0; oi < OC_PER_BLK; oi++) {
|
||
const int oc_idx = oc_base + oi;
|
||
if (oc_idx < oc) {
|
||
w_data[oi] = __ldg(reinterpret_cast<const uint32_t*>(
|
||
kernel + oc_idx * row_bytes + k / 2));
|
||
const float2 p = __ldg(gemv_params + oc_idx * num_qg + gidx);
|
||
s_data[oi] = p.x;
|
||
adj_data[oi] = p.y;
|
||
}
|
||
}
|
||
|
||
// Process each batch element with the cached weights
|
||
#pragma unroll
|
||
for (int b = 0; b < MAX_BATCH; b++) {
|
||
if (b >= batch) break;
|
||
const T* in_ptr = input + b * ic_p;
|
||
float in_vals[8];
|
||
#pragma unroll
|
||
for (int j = 0; j < 8; j++)
|
||
in_vals[j] = (float)in_ptr[k + j];
|
||
|
||
float input_sum = in_vals[0] + in_vals[1] + in_vals[2] + in_vals[3]
|
||
+ in_vals[4] + in_vals[5] + in_vals[6] + in_vals[7];
|
||
|
||
#pragma unroll
|
||
for (int oi = 0; oi < OC_PER_BLK; oi++) {
|
||
if (oc_base + oi >= oc) break;
|
||
float raw_dot = 0.0f;
|
||
#pragma unroll
|
||
for (int j = 0; j < 4; j++) {
|
||
const uint32_t packed = (w_data[oi] >> (j * 8)) & 0xFF;
|
||
raw_dot += in_vals[j * 2] * (float)(packed >> 4);
|
||
raw_dot += in_vals[j * 2 + 1] * (float)(packed & 0x0F);
|
||
}
|
||
accs[b * OC_PER_BLK + oi] = fmaf(adj_data[oi], input_sum, fmaf(s_data[oi], raw_dot, accs[b * OC_PER_BLK + oi]));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Warp-level reduction for all accumulators
|
||
#pragma unroll
|
||
for (int i = 0; i < MAX_BATCH * OC_PER_BLK; i++) {
|
||
#pragma unroll
|
||
for (int off = 16; off > 0; off >>= 1)
|
||
accs[i] += __shfl_xor_sync(0xffffffff, accs[i], off);
|
||
}
|
||
|
||
// Cross-warp reduction using shared memory
|
||
__shared__ float smem[MAX_BATCH * OC_PER_BLK][4]; // [batch*oc][warp_id], nwarps<=4
|
||
|
||
if (lane_id == 0) {
|
||
#pragma unroll
|
||
for (int i = 0; i < MAX_BATCH * OC_PER_BLK; i++)
|
||
smem[i][warp_id] = accs[i];
|
||
}
|
||
__syncthreads();
|
||
|
||
// First batch*OC_PER_BLK threads each reduce one accumulator
|
||
const int reduce_count = batch * OC_PER_BLK;
|
||
if (tid < reduce_count) {
|
||
const int b = tid / OC_PER_BLK;
|
||
const int oi = tid % OC_PER_BLK;
|
||
if (oc_base + oi < oc) {
|
||
float result = 0.0f;
|
||
for (int w = 0; w < nwarps; w++) result += smem[tid][w];
|
||
result += (float)__ldg(&bias[oc_base + oi]);
|
||
result = fmaxf(result, minV);
|
||
result = fminf(result, maxV);
|
||
output[b * oc_p + oc_base + oi] = (T)result;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// P1: Optimized INT8 GEMV with warp shuffle reduction
|
||
template<typename T>
|
||
__global__ void GEMV_FpAInt8B_V2(
|
||
const T* __restrict__ input,
|
||
const int8_t* __restrict__ kernel,
|
||
const T* __restrict__ scale, const T* __restrict__ offset, const T* __restrict__ bias,
|
||
T* __restrict__ output,
|
||
const float maxV, const float minV,
|
||
const int batch, const int ic, const int ic_p,
|
||
const int oc, const int oc_p, const int quanC
|
||
) {
|
||
const int b = blockIdx.y;
|
||
const int oz_base = blockIdx.x * GEMV_OC_PER_BLOCK;
|
||
const int warp_id = threadIdx.y;
|
||
const int lane_id = threadIdx.x;
|
||
const int oz = oz_base + warp_id;
|
||
|
||
if (oz >= oc || b >= batch) return;
|
||
|
||
const int num_quan_groups_per_channel = (quanC > 0) ? (quanC / oc) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
const int quan_param_index_base = oz * num_quan_groups_per_channel;
|
||
|
||
float my_sum = 0.0f;
|
||
for (int k = lane_id; k < ic; k += WARP_SIZE) {
|
||
const float inp0 = (float)input[b * ic_p + k];
|
||
const int8_t ker0 = __ldg(&kernel[oz * ic_p + k]);
|
||
|
||
const int group_idx = k / ic_per_group;
|
||
const int quan_param_index = quan_param_index_base + group_idx;
|
||
const float x_scale = (float)__ldg(&scale[quan_param_index]);
|
||
const float x_offset = (float)__ldg(&offset[quan_param_index]);
|
||
|
||
my_sum += inp0 * ((float)ker0 * x_scale + x_offset);
|
||
}
|
||
|
||
// Warp shuffle reduction
|
||
#pragma unroll
|
||
for (int offset_val = WARP_SIZE / 2; offset_val > 0; offset_val >>= 1) {
|
||
my_sum += __shfl_xor_sync(0xffffffff, my_sum, offset_val);
|
||
}
|
||
|
||
if (lane_id == 0) {
|
||
float final_val = my_sum + (float)bias[oz];
|
||
final_val = max(final_val, minV);
|
||
final_val = min(final_val, maxV);
|
||
output[b * oc_p + oz] = (T)final_val;
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void CONV_FpAInt8B(const T* input,
|
||
const int8_t* kernel,
|
||
const T* scale, const T* offset, const T* bias,
|
||
T *output,
|
||
const float maxV, const float minV,
|
||
const int ic, const int ic_p, const int iw, const int ih,
|
||
const int c, const int c_p, const int ow, const int oh,
|
||
const int kw, const int kh, const int dw, const int dh,
|
||
const int sw, const int sh, const int pw, const int ph,
|
||
const int total, const int quanC,
|
||
DivModFast d_oc, DivModFast d_ow, DivModFast d_oh
|
||
) {
|
||
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < total; index += blockDim.x * gridDim.x) {
|
||
int oz_2, tmp2, oy, ox, tmp1, ob;
|
||
d_oc.divmod(index, tmp1, oz_2);
|
||
d_ow.divmod(tmp1, tmp2, ox);
|
||
d_oh.divmod(tmp2, ob, oy);
|
||
|
||
int oz = oz_2;
|
||
int ix = ox * sw - pw;
|
||
int iy = oy * sh - ph;
|
||
float color0 = bias[oz];
|
||
|
||
const int num_quan_groups_per_channel = (c > 0 && quanC > 0) ? (quanC / c) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
const int quan_param_index_base = oz * num_quan_groups_per_channel;
|
||
|
||
int fxSta = max(0, (UP_DIV(-ix, dw)));
|
||
int fySta = max(0, (UP_DIV(-iy, dh)));
|
||
int fxEnd = min(kw, UP_DIV(iw - ix, dw));
|
||
int fyEnd = min(kh, UP_DIV(ih - iy, dh));
|
||
int fx, fy, fz;
|
||
for (fy=fySta; fy<fyEnd; ++fy) {
|
||
int sy = fy*dh + iy;
|
||
for (fx=fxSta; fx<fxEnd; ++fx) {
|
||
int sx = fx*dw + ix;
|
||
for (int group_idx = 0; group_idx < num_quan_groups_per_channel; ++group_idx) {
|
||
const int quan_param_index = quan_param_index_base + group_idx;
|
||
const float x_scale = scale[quan_param_index];
|
||
const float x_offset = offset[quan_param_index];
|
||
|
||
const int sz_start = group_idx * ic_per_group;
|
||
const int sz_end = sz_start + ic_per_group;
|
||
|
||
for (int sz = sz_start; sz < sz_end && sz < ic_p; ++ sz) {
|
||
int src_offset = ((ob * ih + sy) * iw + sx) * ic_p + sz;
|
||
float inp0 = input[src_offset];
|
||
//[Cop, KhKw, Cip]
|
||
int8_t ker0 = kernel[((oz * kh + fy) * kw + fx) * ic_p + sz];
|
||
|
||
color0 = color0 + inp0 * ((float)ker0 * x_scale + x_offset);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
color0 = max(color0, minV);
|
||
color0 = min(color0, maxV);
|
||
|
||
int dst_offset = ((ob * oh + oy) * ow + ox) * c_p + oz;
|
||
|
||
output[dst_offset] = color0;
|
||
}
|
||
}
|
||
|
||
template<typename T>
|
||
__global__ void CONV_FpAInt4B(const T* input,
|
||
const uint8_t* kernel,
|
||
const T* scale, const T* offset, const T* bias,
|
||
T *output,
|
||
const float maxV, const float minV,
|
||
const int ic, const int ic_p, const int iw, const int ih,
|
||
const int c, const int c_p, const int ow, const int oh,
|
||
const int kw, const int kh, const int dw, const int dh,
|
||
const int sw, const int sh, const int pw, const int ph,
|
||
const int total, const int quanC,
|
||
DivModFast d_oc, DivModFast d_ow, DivModFast d_oh
|
||
) {
|
||
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < total; index += blockDim.x * gridDim.x) {
|
||
int oz_2, tmp2, oy, ox, tmp1, ob;
|
||
d_oc.divmod(index, tmp1, oz_2);
|
||
d_ow.divmod(tmp1, tmp2, ox);
|
||
d_oh.divmod(tmp2, ob, oy);
|
||
|
||
int oz = oz_2;
|
||
int ix = ox * sw - pw;
|
||
int iy = oy * sh - ph;
|
||
float color0 = bias[oz];
|
||
|
||
const int num_quan_groups_per_channel = (c > 0 && quanC > 0) ? (quanC / c) : 1;
|
||
const int ic_per_group = (num_quan_groups_per_channel > 0) ? (ic / num_quan_groups_per_channel) : ic;
|
||
const int quan_param_index_base = oz * num_quan_groups_per_channel;
|
||
|
||
int fxSta = max(0, (UP_DIV(-ix, dw)));
|
||
int fySta = max(0, (UP_DIV(-iy, dh)));
|
||
int fxEnd = min(kw, UP_DIV(iw - ix, dw));
|
||
int fyEnd = min(kh, UP_DIV(ih - iy, dh));
|
||
int fx, fy, fz;
|
||
for (fy=fySta; fy<fyEnd; ++fy) {
|
||
int sy = fy*dh + iy;
|
||
for (fx=fxSta; fx<fxEnd; ++fx) {
|
||
int sx = fx*dw + ix;
|
||
for (int group_idx = 0; group_idx < num_quan_groups_per_channel; ++group_idx) {
|
||
const int quan_param_index = quan_param_index_base + group_idx;
|
||
const float x_scale = scale[quan_param_index];
|
||
const float x_offset = offset[quan_param_index];
|
||
|
||
const int sz_start = group_idx * ic_per_group / 2;
|
||
const int sz_end = sz_start + ic_per_group / 2;
|
||
|
||
for (int sz = sz_start; sz < sz_end && sz * 2 < ic_p; ++ sz) {
|
||
int src_offset = ((ob * ih + sy) * iw + sx) * ic_p + 2 * sz;
|
||
float inp0 = input[src_offset];
|
||
float inp1 = input[src_offset+1];
|
||
|
||
//[Cop, KhKw, Cip]
|
||
uint8_t ker = kernel[((oz * kh + fy) * kw + fx) * ic_p / 2 + sz];
|
||
int8_t ker0 = (ker >> 4) - 8;
|
||
int8_t ker1 = (ker & 15) - 8;
|
||
color0 = color0 + inp0 * ((float)ker0 * x_scale + x_offset);
|
||
color0 = color0 + inp1 * ((float)ker1 * x_scale + x_offset);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
color0 = max(color0, minV);
|
||
color0 = min(color0, maxV);
|
||
|
||
int dst_offset = ((ob * oh + oy) * ow + ox) * c_p + oz;
|
||
|
||
output[dst_offset] = color0;
|
||
}
|
||
}
|
||
|
||
__global__ void Rearrange_Packed_Weight_Int4(const uint8_t* param,
|
||
uint8_t* output,
|
||
const int khw,
|
||
const size_t maxCount,
|
||
const int oc,
|
||
const int ic,
|
||
DivModFast d_khw,
|
||
DivModFast d_icp2
|
||
) {
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < maxCount; index += blockDim.x * gridDim.x) {
|
||
int icp2Index, temp, ocpIndex, khwIndex;
|
||
d_icp2.divmod(index, temp, icp2Index);
|
||
d_khw.divmod(temp, ocpIndex, khwIndex);
|
||
if(ocpIndex >= oc || 2 * icp2Index >= ic) {
|
||
output[index] = 0;
|
||
continue;
|
||
}
|
||
|
||
int src_index = (ocpIndex * UP_DIV(ic, 2) + icp2Index) * khw + khwIndex;
|
||
output[index] = param[src_index];
|
||
}
|
||
}
|
||
|
||
__global__ void Rearrange_Weight_Int4(const int8_t* param,
|
||
uint8_t* output,
|
||
const int khw,
|
||
const size_t maxCount,
|
||
const int oc,
|
||
const int ic,
|
||
DivModFast d_khw,
|
||
DivModFast d_icp2
|
||
) {
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < maxCount; index += blockDim.x * gridDim.x) {
|
||
int icp2Index, temp, ocpIndex, khwIndex;
|
||
d_icp2.divmod(index, temp, icp2Index);
|
||
d_khw.divmod(temp, ocpIndex, khwIndex);
|
||
if(2*icp2Index >= ic || ocpIndex >= oc) {
|
||
output[index] = 0;
|
||
continue;
|
||
}
|
||
// [Co, Ci, KhKw] -> [Cop, KhKw, Cip/2], Ci available for vectorize
|
||
output[index] = ((uint8_t)(param[(ocpIndex * ic + 2*icp2Index+0) * khw + khwIndex] + 8 )) * 16;
|
||
if(2*icp2Index+1 < ic) {
|
||
output[index] += ((uint8_t)(param[(ocpIndex * ic + 2*icp2Index+1) * khw + khwIndex] + 8 ));
|
||
}
|
||
}
|
||
}
|
||
|
||
__global__ void Rearrange_Weight_Int8(const int8_t* param,
|
||
int8_t* output,
|
||
const int khw,
|
||
const size_t maxCount,
|
||
const int oc,
|
||
const int ic,
|
||
DivModFast d_khw,
|
||
DivModFast d_icp
|
||
) {
|
||
for (size_t index = blockIdx.x * blockDim.x + threadIdx.x; index < maxCount; index += blockDim.x * gridDim.x) {
|
||
int icpIndex, temp, ocpIndex, khwIndex;
|
||
d_icp.divmod(index, temp, icpIndex);
|
||
d_khw.divmod(temp, ocpIndex, khwIndex);
|
||
if(icpIndex >= ic || ocpIndex >= oc) {
|
||
output[index] = 0;
|
||
continue;
|
||
}
|
||
// [Co, Ci, KhKw] -> [Cop, KhKw, Cip], Ci available for vectorize
|
||
output[index] = param[(ocpIndex * ic + icpIndex) * khw + khwIndex];
|
||
}
|
||
}
|
||
|
||
bool ConvFpAIntBExecution::isValid(const Convolution2D* conv, Backend* backend) {
|
||
return true;
|
||
}
|
||
|
||
|
||
ConvFpAIntBExecution::Resource::Resource(Backend* bn, const MNN::Op* op) {
|
||
mBackend = bn;
|
||
auto runtime = static_cast<CUDABackend*>(bn)->getCUDARuntime();
|
||
|
||
auto conv = op->main_as_Convolution2D();
|
||
auto common = conv->common();
|
||
|
||
//weight host->device
|
||
std::shared_ptr<ConvolutionCommon::Int8Common> quanCommon = ConvolutionCommon::load(op, mBackend, false, true);
|
||
|
||
auto oc = common->outputCount();
|
||
if (quanCommon->asymmetric) {
|
||
mQuanC = quanCommon->alpha.size() / 2;
|
||
} else {
|
||
mQuanC = quanCommon->alpha.size();
|
||
}
|
||
auto weightSize = quanCommon->weight.size();
|
||
int l = weightSize / oc;
|
||
int h = oc;
|
||
int ic = common->inputCount();
|
||
if(ic == 0) {
|
||
ic = l / common->kernelX() / common->kernelY();
|
||
}
|
||
|
||
int lp = UP_DIV(l, 8) * 8;
|
||
int hp = UP_DIV(h, 8) * 8;
|
||
int quanHp = UP_DIV(mQuanC, 8) * 8;
|
||
|
||
// set dequant scale/offset
|
||
{
|
||
float * dequantAlpha = quanCommon->alpha.get();
|
||
std::vector<float> dequantScale(quanHp, 0.0);
|
||
std::vector<float> dequantOffset(quanHp, 0.0);
|
||
|
||
for (int o = 0; o < mQuanC; o++) {
|
||
float min = 0.0f;
|
||
float alpha = 0.0f;
|
||
if (quanCommon->asymmetric) {
|
||
min = dequantAlpha[2*o];
|
||
alpha = dequantAlpha[2*o+1];
|
||
} else {
|
||
alpha = dequantAlpha[o];
|
||
}
|
||
dequantScale[o] = alpha;
|
||
dequantOffset[o] = min;
|
||
}
|
||
|
||
if(static_cast<CUDABackend*>(bn)->useFp16()) {
|
||
auto tempScaleStorage = static_cast<CUDABackend*>(bn)->getStaticBufferPool()->alloc(quanHp*sizeof(float));
|
||
auto scaleTemp = (float*)((uint8_t*)tempScaleStorage.first + tempScaleStorage.second);
|
||
cuda_check(cudaMemcpy(scaleTemp, dequantScale.data(), quanHp*sizeof(float), cudaMemcpyHostToDevice));
|
||
|
||
scaleTensor.reset(Tensor::createDevice<int16_t>({quanHp}));
|
||
bn->onAcquireBuffer(scaleTensor.get(), Backend::STATIC);
|
||
mScale = (void *)scaleTensor.get()->buffer().device;
|
||
callFloat2Half((const void*)scaleTemp, (void*)mScale, quanHp, runtime);
|
||
|
||
// Reuse scaleTemp buffer
|
||
cuda_check(cudaMemcpy(scaleTemp, dequantOffset.data(), quanHp*sizeof(float), cudaMemcpyHostToDevice));
|
||
|
||
offsetTensor.reset(Tensor::createDevice<int16_t>({quanHp}));
|
||
bn->onAcquireBuffer(offsetTensor.get(), Backend::STATIC);
|
||
mOffset = (void *)offsetTensor.get()->buffer().device;
|
||
callFloat2Half((const void*)scaleTemp, (void*)mOffset, quanHp, runtime);
|
||
|
||
static_cast<CUDABackend*>(bn)->getStaticBufferPool()->free(tempScaleStorage);
|
||
} else {
|
||
scaleTensor.reset(Tensor::createDevice<int32_t>({quanHp}));
|
||
bn->onAcquireBuffer(scaleTensor.get(), Backend::STATIC);
|
||
mScale = (void *)scaleTensor.get()->buffer().device;
|
||
cuda_check(cudaMemcpy(mScale, dequantScale.data(), quanHp*sizeof(float), cudaMemcpyHostToDevice));
|
||
|
||
offsetTensor.reset(Tensor::createDevice<int32_t>({quanHp}));
|
||
bn->onAcquireBuffer(offsetTensor.get(), Backend::STATIC);
|
||
mOffset = (void *)offsetTensor.get()->buffer().device;
|
||
cuda_check(cudaMemcpy(mOffset, dequantOffset.data(), quanHp*sizeof(float), cudaMemcpyHostToDevice));
|
||
}
|
||
}
|
||
|
||
// Reorder weight
|
||
{
|
||
int khw = common->kernelX() * common->kernelY();
|
||
int icp = UP_DIV(ic, 8) * 8;
|
||
DivModFast khwD(khw);
|
||
DivModFast icp2D(icp/2);
|
||
DivModFast icpD(icp);
|
||
|
||
if(quanCommon->canUseInt4) {
|
||
mIsWeightInt4 = true;
|
||
|
||
auto tempCacheBuffer = static_cast<CUDABackend*>(bn)->getStaticBufferPool()->alloc(weightSize * sizeof(int8_t));
|
||
float* cacheWeight = (float*)((uint8_t*)tempCacheBuffer.first + tempCacheBuffer.second);
|
||
runtime->memcpy(cacheWeight, quanCommon->weight.get(), weightSize * sizeof(int8_t), MNNMemcpyHostToDevice);
|
||
|
||
weightTensor.reset(Tensor::createDevice<uint8_t>({khw * icp/2 * hp}));
|
||
bn->onAcquireBuffer(weightTensor.get(), Backend::STATIC);
|
||
mFilter = (void *)weightTensor.get()->buffer().device;
|
||
|
||
//[Co, Ci, KhKw] -> [Cop, KhKw, Cip/2]
|
||
int block_num = runtime->blocks_num(khw*icp/2*hp);
|
||
int block_size = runtime->threads_num();
|
||
Rearrange_Packed_Weight_Int4<<<block_num, block_size>>>((const uint8_t*)cacheWeight, (uint8_t*)mFilter, khw, khw*icp/2*hp, oc, ic, khwD, icp2D);
|
||
checkKernelErrors;
|
||
|
||
static_cast<CUDABackend*>(bn)->getStaticBufferPool()->free(tempCacheBuffer);
|
||
|
||
// R6: Pre-compute GEMV params (scale, adj_offset) as float2 array for 1x1 conv
|
||
if (khw == 1) {
|
||
mNumQg = (mQuanC > 0) ? (mQuanC / oc) : 1;
|
||
const size_t param_count = (size_t)oc * mNumQg;
|
||
cudaError_t err = cudaMalloc(&mGemvParams, param_count * sizeof(float2));
|
||
if (err == cudaSuccess) {
|
||
dim3 pg_block(256);
|
||
dim3 pg_grid((param_count + 255) / 256);
|
||
if (static_cast<CUDABackend*>(bn)->useFp16()) {
|
||
PrecomputeGemvParams<half><<<pg_grid, pg_block>>>(
|
||
(const half*)mScale, (const half*)mOffset,
|
||
mGemvParams, (int)param_count);
|
||
} else {
|
||
PrecomputeGemvParams<float><<<pg_grid, pg_block>>>(
|
||
(const float*)mScale, (const float*)mOffset,
|
||
mGemvParams, (int)param_count);
|
||
}
|
||
checkKernelErrors;
|
||
}
|
||
}
|
||
} else {
|
||
auto tempCacheBuffer = static_cast<CUDABackend*>(bn)->getStaticBufferPool()->alloc(weightSize * sizeof(int8_t));
|
||
float* cacheWeight = (float*)((uint8_t*)tempCacheBuffer.first + tempCacheBuffer.second);
|
||
runtime->memcpy(cacheWeight, quanCommon->weight.get(), weightSize * sizeof(int8_t), MNNMemcpyHostToDevice);
|
||
|
||
weightTensor.reset(Tensor::createDevice<int8_t>({khw * icp * hp}));
|
||
bn->onAcquireBuffer(weightTensor.get(), Backend::STATIC);
|
||
mFilter = (void *)weightTensor.get()->buffer().device;
|
||
|
||
//[Co, Ci, KhKw] -> [Cop, KhKw, Cip]
|
||
int block_num = runtime->blocks_num(khw*icp*hp);
|
||
int block_size = runtime->threads_num();
|
||
Rearrange_Weight_Int8<<<block_num, block_size>>>((const int8_t*)cacheWeight, (int8_t*)mFilter, khw, khw*icp*hp, oc, ic, khwD, icpD);
|
||
checkKernelErrors;
|
||
static_cast<CUDABackend*>(bn)->getStaticBufferPool()->free(tempCacheBuffer);
|
||
}
|
||
}
|
||
|
||
// Copy Bias
|
||
{
|
||
if(static_cast<CUDABackend*>(bn)->useFp16()) {
|
||
int biasSize = conv->bias()->size();
|
||
int hp = UP_DIV(biasSize, 8) * 8;
|
||
|
||
auto tempBiasStorage = static_cast<CUDABackend*>(bn)->getStaticBufferPool()->alloc(hp*sizeof(float));
|
||
auto biasTemp = (float*)((uint8_t*)tempBiasStorage.first + tempBiasStorage.second);
|
||
runtime->memset(biasTemp, 0, hp * sizeof(int32_t));
|
||
cuda_check(cudaMemcpy(biasTemp, conv->bias()->data(), conv->bias()->size()*sizeof(float), cudaMemcpyHostToDevice));
|
||
|
||
biasTensor.reset(Tensor::createDevice<int16_t>({hp}));
|
||
bn->onAcquireBuffer(biasTensor.get(), Backend::STATIC);
|
||
mBias = (void *)biasTensor.get()->buffer().device;
|
||
callFloat2Half((const void*)biasTemp, (void*)mBias, hp, runtime);
|
||
|
||
static_cast<CUDABackend*>(bn)->getStaticBufferPool()->free(tempBiasStorage);
|
||
} else {
|
||
int biasSize = conv->bias()->size();
|
||
int hp = UP_DIV(biasSize, 8) * 8;
|
||
biasTensor.reset(Tensor::createDevice<int32_t>({hp}));
|
||
bn->onAcquireBuffer(biasTensor.get(), Backend::STATIC);
|
||
mBias = (void *)biasTensor.get()->buffer().device;
|
||
runtime->memset(mBias, 0, hp * sizeof(int32_t));
|
||
cuda_check(cudaMemcpy(mBias, conv->bias()->data(), conv->bias()->size()*sizeof(float), cudaMemcpyHostToDevice));
|
||
}
|
||
}
|
||
|
||
// High + Int8:预计算 B 的修正项 sum_B_q
|
||
// if(!quanCommon->canUseInt4) {
|
||
// const int icp = UP_DIV(ic, 8) * 8;
|
||
// const int num_groups = (mQuanC > 0) ? (mQuanC / oc) : 1;
|
||
// const int ic_per_group = (num_groups > 0) ? (ic / num_groups) : ic;
|
||
|
||
// const int param_count = oc * num_groups;
|
||
// mSumBQTensor.reset(Tensor::createDevice<int32_t>({param_count}));
|
||
// bn->onAcquireBuffer(mSumBQTensor.get(), Backend::STATIC);
|
||
// mSumBQ = (void*)mSumBQTensor.get()->buffer().device;
|
||
|
||
// const int block_size = BLOCK_SIZE;
|
||
// const int num_blocks = (param_count + block_size - 1) / block_size;
|
||
|
||
// Precompute_SumBq<<<num_blocks, block_size>>>((const int8_t*)mFilter, (int32_t*)mSumBQ, num_groups, ic_per_group, oc, icp);
|
||
// checkKernelErrors;
|
||
// }
|
||
}
|
||
|
||
ConvFpAIntBExecution::Resource::~Resource() {
|
||
if (mGemvParams) cudaFree(mGemvParams);
|
||
}
|
||
ConvFpAIntBExecution::ConvFpAIntBExecution(Backend* backend, const MNN::Op* op, std::shared_ptr<Resource> res) : CutlassConvCommonExecution(backend) {
|
||
mOp = op;
|
||
mResource = res;
|
||
auto runtime = static_cast<CUDABackend*>(backend)->getCUDARuntime();
|
||
mPrecisonLevel = static_cast<CUDABackend*>(backend)->getPrecision();
|
||
mFp16Infer = (mPrecisonLevel == 2);
|
||
mFp32Infer = (mPrecisonLevel == 1);
|
||
mFp16Fp32MixInfer = (mPrecisonLevel == 0);
|
||
mBf16Infer = (mPrecisonLevel == 3);
|
||
}
|
||
|
||
ConvFpAIntBExecution::~ConvFpAIntBExecution() {
|
||
// DYNAMIC dequant buffers are managed by the memory planner (alloc+release in onResize)
|
||
// Only STATIC buffers need explicit release in destructor
|
||
if (mDequantFilterTensor && mDequantIsStatic) {
|
||
backend()->onReleaseBuffer(mDequantFilterTensor.get(), Backend::STATIC);
|
||
}
|
||
}
|
||
bool ConvFpAIntBExecution::onClone(Backend* bn, const Op* op, Execution** dst) {
|
||
if (!mValid) {
|
||
return false;
|
||
}
|
||
if (nullptr == dst) {
|
||
return true;
|
||
}
|
||
auto dstExe = new ConvFpAIntBExecution(bn, op, mResource);
|
||
*dst = dstExe;
|
||
return true;
|
||
}
|
||
|
||
|
||
ErrorCode ConvFpAIntBExecution::onResize(const std::vector<Tensor*> &inputs, const std::vector<Tensor*> &outputs) {
|
||
auto runtime = static_cast<CUDABackend*>(backend())->getCUDARuntime();
|
||
auto input = inputs[0], output = outputs[0];
|
||
const int UNIT = PACK_NUMBER;
|
||
auto convCommon = mOp->main_as_Convolution2D()->common();
|
||
auto pads = ConvolutionCommon::convolutionPadFull(input, output, mOp->main_as_Convolution2D()->common());
|
||
int ic = input->channel();
|
||
const int icp = UP_DIV(ic, 8) * 8;
|
||
auto icDiv = UP_DIV(ic, UNIT);
|
||
|
||
mIm2ColParamter.dilateX = convCommon->dilateX();
|
||
mIm2ColParamter.dilateY = convCommon->dilateY();
|
||
mIm2ColParamter.strideX = convCommon->strideX();
|
||
mIm2ColParamter.strideY = convCommon->strideY();
|
||
mIm2ColParamter.icDiv4 = icDiv;
|
||
mIm2ColParamter.ic = ic;
|
||
mIm2ColParamter.kernelX = convCommon->kernelX();
|
||
mIm2ColParamter.kernelY = convCommon->kernelY();
|
||
mIm2ColParamter.padX = std::get<0>(pads);
|
||
mIm2ColParamter.padY = std::get<1>(pads);
|
||
|
||
mIm2ColParamter.ih = input->height();
|
||
mIm2ColParamter.iw = input->width();
|
||
mIm2ColParamter.oh = output->height();
|
||
mIm2ColParamter.ow = output->width();
|
||
mIm2ColParamter.srcZStep = input->height() * input->width() * UNIT * input->batch();
|
||
mIm2ColParamter.srcYStep = input->width() * UNIT;
|
||
mIm2ColParamter.packCUnit = UNIT;
|
||
|
||
mActivationType = convCommon->relu() ? 1 : convCommon->relu6() ? 2 : 0;
|
||
|
||
//MNN_PRINT("conv size:%d-%d, %d-%d-%d, %d-%d-%d\n", mIm2ColParamter.kernelX, mIm2ColParamter.strideX, input->height(), input->width(), input->channel(), output->height(), output->width(), output->channel());
|
||
int e = output->height() * output->width() * output->batch();
|
||
int l = ic * mIm2ColParamter.kernelX * mIm2ColParamter.kernelY;
|
||
int h = output->channel();
|
||
mGemmInfo.elh[0] = e;
|
||
mGemmInfo.elh[1] = l;
|
||
mGemmInfo.elh[2] = h;
|
||
mGemmInfo.elhPad[0] = UP_DIV(e, 8) * 8;
|
||
mGemmInfo.elhPad[1] = UP_DIV(l, 8) * 8;
|
||
mGemmInfo.elhPad[2] = UP_DIV(h, 8) * 8;
|
||
const int oc = mGemmInfo.elh[2];
|
||
const int ocp = mGemmInfo.elhPad[2];
|
||
|
||
mIsConv1x1S1D1P0 = (mIm2ColParamter.kernelX == 1 && mIm2ColParamter.kernelY == 1 &&
|
||
mIm2ColParamter.strideX == 1 && mIm2ColParamter.strideY == 1 &&
|
||
mIm2ColParamter.dilateX == 1 && mIm2ColParamter.dilateY == 1 &&
|
||
mIm2ColParamter.padX == 0 && mIm2ColParamter.padY == 0);
|
||
|
||
mNeedIm2Col = !(mIsConv1x1S1D1P0 && (mFp16Infer || mFp32Infer));
|
||
|
||
auto pool = static_cast<CUDABackend*>(backend())->getBufferPool();
|
||
if(mNeedIm2Col) {
|
||
size_t im2colBytes = 2;
|
||
// Only when fp32 Im2Col convert to fp32, Fp16Fp32Mix Im2Col convert to fp16
|
||
if(mFp32Infer) {
|
||
im2colBytes = 4;
|
||
}
|
||
auto buffer = pool->alloc(im2colBytes * (size_t)mGemmInfo.elh[0] * (size_t)mGemmInfo.elhPad[1]);
|
||
mIm2ColBuffer = (void*)((uint8_t*)buffer.first + buffer.second);
|
||
pool->free(buffer);
|
||
}
|
||
|
||
if (mDequantFilterTensor) {
|
||
if (mDequantIsStatic) {
|
||
backend()->onReleaseBuffer(mDequantFilterTensor.get(), Backend::STATIC);
|
||
}
|
||
// DYNAMIC buffers were already released in previous onResize, no action needed
|
||
mDequantFilterTensor = nullptr;
|
||
}
|
||
mDequantFilter = nullptr;
|
||
mNeedRuntimeDequant = false;
|
||
mDequantIsStatic = false;
|
||
|
||
// Weight dequantization: use DYNAMIC buffer with runtime dequant in onExecute.
|
||
// DYNAMIC buffers are shared across all Conv layers (only one active at a time),
|
||
// saving massive GPU memory vs STATIC (which would need ~30GB for 27B models).
|
||
if (!mFp32Infer) {
|
||
if (mIsConv1x1S1D1P0) {
|
||
std::vector<int> dequantShape = {mGemmInfo.elhPad[2], mGemmInfo.elhPad[1]}; // Shape: [oc_p, ic_p]
|
||
if (mFp16Infer || mFp16Fp32MixInfer) {
|
||
mDequantFilterTensor.reset(Tensor::createDevice<int16_t>(dequantShape));
|
||
} else {
|
||
mDequantFilterTensor.reset(Tensor::createDevice<float>(dequantShape));
|
||
}
|
||
backend()->onAcquireBuffer(mDequantFilterTensor.get(), Backend::DYNAMIC);
|
||
backend()->onReleaseBuffer(mDequantFilterTensor.get(), Backend::DYNAMIC);
|
||
mNeedRuntimeDequant = true;
|
||
mDequantIsStatic = false;
|
||
|
||
mBiasAddr = mResource->mBias;
|
||
mBackendPtr = mResource->mBackend;
|
||
mDequantFilter = (void*)mDequantFilterTensor->buffer().device;
|
||
mFilterAddr = mDequantFilter;
|
||
|
||
if (mFp32Infer) {
|
||
return callCutlassGemmCudaCoreFloat32(inputs, outputs);
|
||
}
|
||
mGpuComputeCap = static_cast<CUDABackend*>(backend())->getCUDARuntime()->compute_capability();
|
||
if (mGpuComputeCap < 70) {
|
||
return callCutlassGemmCudaCoreFloat16(inputs, outputs);
|
||
} else if (mGpuComputeCap < 75) {
|
||
return callCutlassGemmTensorCore884(inputs, outputs);
|
||
}
|
||
return callCutlassGemmTensorCore(inputs, outputs);
|
||
}
|
||
}
|
||
|
||
return NO_ERROR;
|
||
}
|
||
|
||
ErrorCode ConvFpAIntBExecution::onExecute(const std::vector<Tensor*> &inputs, const std::vector<Tensor*> &outputs) {
|
||
MNN_ASSERT(inputs.size() == 1);
|
||
MNN_ASSERT(outputs.size() == 1);
|
||
auto input = inputs[0];
|
||
auto output = outputs[0];
|
||
|
||
//printf("convcutlass:%p %p\n", input->deviceId(), output->deviceId());
|
||
//MNN_PRINT("cutlass hw:%d-%d\n", input->height(), input->width());
|
||
auto runtime = static_cast<CUDABackend*>(backend())->getCUDARuntime();
|
||
const void *input_addr = (const void*)inputs[0]->deviceId();
|
||
const void *filter_addr = mResource->mFilter;
|
||
const void *bias_addr = mResource->mBias;
|
||
auto bn = backend();
|
||
void *output_addr = (void*)outputs[0]->deviceId();
|
||
|
||
const int sw = mIm2ColParamter.strideX;
|
||
const int sh = mIm2ColParamter.strideY;
|
||
const int kw = mIm2ColParamter.kernelX;
|
||
const int kh = mIm2ColParamter.kernelY;
|
||
const int dw = mIm2ColParamter.dilateX;
|
||
const int dh = mIm2ColParamter.dilateY;
|
||
const int pw = mIm2ColParamter.padX;
|
||
const int ph = mIm2ColParamter.padY;
|
||
const int ic = mIm2ColParamter.ic;
|
||
const int icp = UP_DIV(ic, 8) * 8;
|
||
const int iw = mIm2ColParamter.iw;
|
||
const int ih = mIm2ColParamter.ih;
|
||
|
||
const int oc = mGemmInfo.elh[2];
|
||
const int ocp = mGemmInfo.elhPad[2];
|
||
const int ow = mIm2ColParamter.ow;
|
||
const int oh = mIm2ColParamter.oh;
|
||
|
||
float maxV = FLT_MAX;
|
||
float minV = -FLT_MAX;
|
||
if (mActivationType == 1) {
|
||
minV = 0.0f;
|
||
}
|
||
if (mActivationType == 2) {
|
||
minV = 0.0f;
|
||
maxV = 6.0f;
|
||
}
|
||
|
||
auto total = outputs[0]->batch() * oh * ow * ocp;
|
||
auto& prop = runtime->prop();
|
||
int limitThreads = UP_DIV(total, prop.multiProcessorCount);
|
||
int threadNum = ALIMIN(prop.maxThreadsPerBlock/2, limitThreads);
|
||
int blockNum = prop.multiProcessorCount;
|
||
|
||
DivModFast d_oc(ocp);
|
||
DivModFast d_ow(ow);
|
||
DivModFast d_oh(oh);
|
||
|
||
const int batch = inputs[0]->batch();
|
||
|
||
// Weight dequantization now happens once in onResize (STATIC buffer).
|
||
// GEMV path (batch <= 4) reads packed INT4 weights directly.
|
||
|
||
|
||
if(mResource->mIsWeightInt4) {
|
||
if (mIsConv1x1S1D1P0) {
|
||
if (mFp32Infer) { // High + Int4
|
||
if (batch <= 16) {
|
||
// P1-V5: 128-thread GEMV, 1 OC per block
|
||
dim3 v5_grid(oc, batch);
|
||
dim3 v5_block(128);
|
||
GEMV_FpAInt4B_V5<<<v5_grid, v5_block>>>(
|
||
(const float*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC);
|
||
} else {
|
||
dim3 threads(TILE_DIM, TILE_DIM);
|
||
dim3 blocks(UP_DIV(ocp, TILE_DIM), UP_DIV(batch, TILE_DIM));
|
||
|
||
GEMM_FpAInt4B<<<blocks, threads>>>(
|
||
(const float*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, ic, icp, oc, ocp, batch, mResource->mQuanC
|
||
);
|
||
}
|
||
} else {
|
||
if (batch <= 4) {
|
||
const int num_qg = (mResource->mQuanC > 0) ? (mResource->mQuanC / oc) : 1;
|
||
if (mResource->mGemvParams) {
|
||
constexpr int V14_OC = 4;
|
||
if (batch > 1) {
|
||
// V14_MB: One block processes ALL batch elements, weights loaded once
|
||
dim3 mb_grid((oc + V14_OC - 1) / V14_OC);
|
||
dim3 mb_block(128);
|
||
if (mFp16Infer) {
|
||
GEMV_FpAInt4B_V14_MB<half, V14_OC, 4><<<mb_grid, mb_block>>>(
|
||
(const half*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
mResource->mGemvParams,
|
||
(const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, num_qg);
|
||
} else if (mFp16Fp32MixInfer) {
|
||
GEMV_FpAInt4B_V14_MB<float, V14_OC, 4><<<mb_grid, mb_block>>>(
|
||
(const float*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
mResource->mGemvParams,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, num_qg);
|
||
}
|
||
} else {
|
||
// batch=1: original V14
|
||
dim3 v14_grid((oc + V14_OC - 1) / V14_OC, batch);
|
||
dim3 v14_block(128);
|
||
if (mFp16Infer) {
|
||
GEMV_FpAInt4B_V14<half, V14_OC><<<v14_grid, v14_block>>>(
|
||
(const half*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
mResource->mGemvParams,
|
||
(const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, num_qg);
|
||
} else if (mFp16Fp32MixInfer) {
|
||
GEMV_FpAInt4B_V14<float, V14_OC><<<v14_grid, v14_block>>>(
|
||
(const float*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
mResource->mGemvParams,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, num_qg);
|
||
}
|
||
}
|
||
} else {
|
||
// Fallback to V9/V5
|
||
dim3 v5_grid(oc, batch);
|
||
dim3 v5_block(128);
|
||
if (mFp16Infer) {
|
||
constexpr int OC_PER_BLK = 2;
|
||
dim3 v9_grid((oc + OC_PER_BLK - 1) / OC_PER_BLK, batch);
|
||
GEMV_FpAInt4B_V9<half, OC_PER_BLK><<<v9_grid, v5_block>>>(
|
||
(const half*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset,
|
||
(const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC);
|
||
} else if (mFp16Fp32MixInfer) {
|
||
GEMV_FpAInt4B_V5<<<v5_grid, v5_block>>>(
|
||
(const float*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC);
|
||
}
|
||
}
|
||
} else {
|
||
// Prefill/batched GEMM path: CUTLASS with dequantized FP16 weights
|
||
if (mNeedRuntimeDequant) {
|
||
// Runtime dequantization: dequant INT4→FP16 into DYNAMIC buffer before CUTLASS
|
||
if (mResource->mIsWeightInt4) {
|
||
int threads_per_row = UP_DIV(icp, 16);
|
||
dim3 dq_block(min(threads_per_row, 256));
|
||
dim3 dq_grid(oc, UP_DIV(threads_per_row, (int)dq_block.x));
|
||
if (mFp16Infer) {
|
||
DequantizeInt4Weight<half, half><<<dq_grid, dq_block>>>(
|
||
(const uint8_t*)mResource->mFilter, (half*)mDequantFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset,
|
||
oc, ic, icp, mResource->mQuanC);
|
||
} else if (mFp16Fp32MixInfer) {
|
||
DequantizeInt4Weight<float, half><<<dq_grid, dq_block>>>(
|
||
(const uint8_t*)mResource->mFilter, (half*)mDequantFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
oc, ic, icp, mResource->mQuanC);
|
||
}
|
||
} else {
|
||
dim3 threads(32, 32);
|
||
dim3 blocks(UP_DIV(ic, threads.x), UP_DIV(oc, threads.y));
|
||
if (mFp16Infer) {
|
||
DequantizeInt8Weight<half, half><<<blocks, threads>>>(
|
||
(const int8_t*)mResource->mFilter, (half*)mDequantFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset,
|
||
oc, ic, icp, mResource->mQuanC);
|
||
} else if (mFp16Fp32MixInfer) {
|
||
DequantizeInt8Weight<float, half><<<blocks, threads>>>(
|
||
(const int8_t*)mResource->mFilter, (half*)mDequantFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
oc, ic, icp, mResource->mQuanC);
|
||
}
|
||
}
|
||
}
|
||
if (mFp16Fp32MixInfer) {
|
||
size_t maxCount = mGemmInfo.elh[0] * mGemmInfo.elhPad[1];
|
||
callFloat2Half(input_addr, mIm2ColBuffer, maxCount, runtime);
|
||
}
|
||
runCutlassGemmFunc();
|
||
}
|
||
}
|
||
} else {
|
||
if(mFp16Infer) {
|
||
CONV_FpAInt4B<<<blockNum, threadNum>>>((const half*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset, (const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, ic, icp, iw, ih, oc, ocp, ow, oh, kw, kh, dw, dh, sw, sh, pw, ph, total, mResource -> mQuanC,
|
||
d_oc, d_ow, d_oh);
|
||
} else {
|
||
CONV_FpAInt4B<<<blockNum, threadNum>>>((const float*)input_addr, (const uint8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset, (const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, ic, icp, iw, ih, oc, ocp, ow, oh, kw, kh, dw, dh, sw, sh, pw, ph, total, mResource -> mQuanC,
|
||
d_oc, d_ow, d_oh);
|
||
}
|
||
}
|
||
} else { // int8
|
||
if (mIsConv1x1S1D1P0) {
|
||
if (mFp32Infer) { // High + Int8
|
||
if (batch <= 16) {
|
||
// P1: Use optimized GEMV with warp shuffle reduction
|
||
dim3 threads(WARP_SIZE, GEMV_OC_PER_BLOCK);
|
||
dim3 blocks(UP_DIV(oc, GEMV_OC_PER_BLOCK), batch);
|
||
|
||
if (mFp16Infer) {
|
||
GEMV_FpAInt8B_V2<<<blocks, threads>>>(
|
||
(const half*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset,
|
||
(const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC
|
||
);
|
||
} else {
|
||
GEMV_FpAInt8B_V2<<<blocks, threads>>>(
|
||
(const float*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC
|
||
);
|
||
}
|
||
} else {
|
||
dim3 threads(TILE_DIM, TILE_DIM);
|
||
dim3 blocks(UP_DIV(ocp, TILE_DIM), UP_DIV(batch, TILE_DIM));
|
||
|
||
GEMM_FpAInt8B<<<blocks, threads>>>(
|
||
(const float*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, ic, icp, oc, ocp, batch, mResource->mQuanC
|
||
);
|
||
}
|
||
} else {
|
||
if (batch <= 16) {
|
||
// P1: FP16 direct GEMV for INT8 small batch
|
||
dim3 threads(WARP_SIZE, GEMV_OC_PER_BLOCK);
|
||
dim3 blocks(UP_DIV(oc, GEMV_OC_PER_BLOCK), batch);
|
||
if (mFp16Infer) {
|
||
GEMV_FpAInt8B_V2<<<blocks, threads>>>(
|
||
(const half*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset,
|
||
(const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC
|
||
);
|
||
} else if (mFp16Fp32MixInfer) {
|
||
GEMV_FpAInt8B_V2<<<blocks, threads>>>(
|
||
(const float*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset,
|
||
(const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, batch, ic, icp, oc, ocp, mResource->mQuanC
|
||
);
|
||
}
|
||
} else {
|
||
// Prefill path: CUTLASS GEMM with dequantized FP16 weights
|
||
if (mFp16Fp32MixInfer) {
|
||
size_t maxCount = mGemmInfo.elh[0] * mGemmInfo.elhPad[1];
|
||
callFloat2Half(input_addr, mIm2ColBuffer, maxCount, runtime);
|
||
}
|
||
runCutlassGemmFunc();
|
||
}
|
||
}
|
||
} else {
|
||
if(mFp16Infer) {
|
||
CONV_FpAInt8B<<<blockNum, threadNum>>>((const half*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const half*)mResource->mScale, (const half*)mResource->mOffset, (const half*)bias_addr, (half*)output_addr,
|
||
maxV, minV, ic, icp, iw, ih, oc, ocp, ow, oh, kw, kh, dw, dh, sw, sh, pw, ph, total, mResource -> mQuanC,
|
||
d_oc, d_ow, d_oh);
|
||
} else {
|
||
CONV_FpAInt8B<<<blockNum, threadNum>>>((const float*)input_addr, (const int8_t*)mResource->mFilter,
|
||
(const float*)mResource->mScale, (const float*)mResource->mOffset, (const float*)bias_addr, (float*)output_addr,
|
||
maxV, minV, ic, icp, iw, ih, oc, ocp, ow, oh, kw, kh, dw, dh, sw, sh, pw, ph, total, mResource -> mQuanC,
|
||
d_oc, d_ow, d_oh);
|
||
}
|
||
}
|
||
}
|
||
|
||
checkKernelErrors;
|
||
return NO_ERROR;
|
||
}
|
||
|
||
|
||
}// namespace CUDA
|
||
}// namespace MNN
|