// // 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 #include //#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 __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 __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(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(output + out_base) = *reinterpret_cast(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(output + out_base + 8) = *reinterpret_cast(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 __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 __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 __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) { atomicAdd(&C_fp_final[m * ldc + n], final_val); } else if constexpr (std::is_same_v) { // 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 __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 __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) { printf("%.4f ", __half2float(value)); } else { printf("%.4f ", value); } } printf("\n"); } printf("---------------------------------------\n"); } } template __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 __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 __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 __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(smem_buffer); float* partial_sums = reinterpret_cast(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 __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(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 __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(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 __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( 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 __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( 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 __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 __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 __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> 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(bn)->getCUDARuntime(); auto conv = op->main_as_Convolution2D(); auto common = conv->common(); //weight host->device std::shared_ptr 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 dequantScale(quanHp, 0.0); std::vector 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(bn)->useFp16()) { auto tempScaleStorage = static_cast(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({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({quanHp})); bn->onAcquireBuffer(offsetTensor.get(), Backend::STATIC); mOffset = (void *)offsetTensor.get()->buffer().device; callFloat2Half((const void*)scaleTemp, (void*)mOffset, quanHp, runtime); static_cast(bn)->getStaticBufferPool()->free(tempScaleStorage); } else { scaleTensor.reset(Tensor::createDevice({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({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(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({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<<>>((const uint8_t*)cacheWeight, (uint8_t*)mFilter, khw, khw*icp/2*hp, oc, ic, khwD, icp2D); checkKernelErrors; static_cast(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(bn)->useFp16()) { PrecomputeGemvParams<<>>( (const half*)mScale, (const half*)mOffset, mGemvParams, (int)param_count); } else { PrecomputeGemvParams<<>>( (const float*)mScale, (const float*)mOffset, mGemvParams, (int)param_count); } checkKernelErrors; } } } else { auto tempCacheBuffer = static_cast(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({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<<>>((const int8_t*)cacheWeight, (int8_t*)mFilter, khw, khw*icp*hp, oc, ic, khwD, icpD); checkKernelErrors; static_cast(bn)->getStaticBufferPool()->free(tempCacheBuffer); } } // Copy Bias { if(static_cast(bn)->useFp16()) { int biasSize = conv->bias()->size(); int hp = UP_DIV(biasSize, 8) * 8; auto tempBiasStorage = static_cast(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({hp})); bn->onAcquireBuffer(biasTensor.get(), Backend::STATIC); mBias = (void *)biasTensor.get()->buffer().device; callFloat2Half((const void*)biasTemp, (void*)mBias, hp, runtime); static_cast(bn)->getStaticBufferPool()->free(tempBiasStorage); } else { int biasSize = conv->bias()->size(); int hp = UP_DIV(biasSize, 8) * 8; biasTensor.reset(Tensor::createDevice({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({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<<>>((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 res) : CutlassConvCommonExecution(backend) { mOp = op; mResource = res; auto runtime = static_cast(backend)->getCUDARuntime(); mPrecisonLevel = static_cast(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 &inputs, const std::vector &outputs) { auto runtime = static_cast(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(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 dequantShape = {mGemmInfo.elhPad[2], mGemmInfo.elhPad[1]}; // Shape: [oc_p, ic_p] if (mFp16Infer || mFp16Fp32MixInfer) { mDequantFilterTensor.reset(Tensor::createDevice(dequantShape)); } else { mDequantFilterTensor.reset(Tensor::createDevice(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(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 &inputs, const std::vector &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(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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>( (const uint8_t*)mResource->mFilter, (half*)mDequantFilter, (const half*)mResource->mScale, (const half*)mResource->mOffset, oc, ic, icp, mResource->mQuanC); } else if (mFp16Fp32MixInfer) { DequantizeInt4Weight<<>>( (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<<>>( (const int8_t*)mResource->mFilter, (half*)mDequantFilter, (const half*)mResource->mScale, (const half*)mResource->mOffset, oc, ic, icp, mResource->mQuanC); } else if (mFp16Fp32MixInfer) { DequantizeInt8Weight<<>>( (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<<>>((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<<>>((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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>( (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<<>>((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<<>>((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