执行摘要
本 PR 通过重新映射 CUDA 线程索引以合并 HBM 访问,将 AWQ int4→fp8 权重预处理 kernel 提速 2.22 倍。仅修改一个文件,正确性测试通过,评审中两个小问题均已解决。
功能与动机
原始 kernel 中相邻线程读取 qweight 时步长为 size_n/8 个 int32,导致非合并访问,扇区利用率极低。PR 旨在通过简单线程映射调整充分利用 HBM 带宽,加快模型加载冷路径。
实现拆解
- 线程映射重排:
threadIdx.x 从行维移至列维,blockIdx.x 直接表示行,blockIdx.y 索引列块。
- 网格配置更新:从
(size_k/32, size_n/8) 变为 (size_k, (size_n/8+31)/32),按行平铺。
- 边界保护:增加
if(col >= size_n/8) return; 处理尾块。
- 编译警告清理:添加
(void)size_k;。
- 冗余检查移除:采纳 review 意见,删除无实际作用的
size_n % 8 断言。
csrc/libtorch_stable/quantization/marlin/marlin_int4_fp8_preprocess.cu
唯一修改文件,包含核心 CUDA kernel 和 host 包装函数的变更。线程映射重排是实现性能提升的关键。
// 优化后的 marlin_int4_fp8_preprocess_kernel_awq kernel
// 线程映射:blockIdx.x 为行索引,blockIdx.y * 32 + threadIdx.x 为列索引
__global__ void marlin_int4_fp8_preprocess_kernel_awq(
const int32_t* __restrict__ qweight,
const int32_t* __restrict__ qzeros,
int32_t size_n, int32_t size_k, int32_t group_size) {
// 列索引:连续 threadIdx.x 读取连续列 -> 合并访问
int col = blockIdx.y * 32 + threadIdx.x;
if (col >= size_n / 8) return; // 边界检查
(void)size_k; // 抑制未使用参数警告
// 合并读取:同一 blockIdx.x 行内连续 col 地址
int32_t val = qweight[blockIdx.x * (size_n / 8) + col];
int32_t zero = qzeros[blockIdx.x / group_size * (size_n / 8) + col];
int32_t new_val = 0;
#pragma unroll
for (int i = 0; i < 8; ++i) {
// 解包 4-bit 权重并重新打包为 fp8 所需的格式(简化示意)
new_val |= (val & 0xF) << (i * 4);
val >>= 4;
}
// 类似处理 zero(略)
output[blockIdx.x * (size_n / 8) + col] = new_val;
}
(注:实际 zero 处理循环与 val 类似,此处省略保持简洁。)
评论区精华
- Copilot 指出
size_k 不再使用,可能触发 -Wunused-parameter 错误 → 作者添加 (void)size_k。
- Copilot 和 Harry-Chen 认为新加的
size_n % 8 == 0 检查恒真且冗余 → 作者删除。
风险与影响
- 正确性:相同元素空间,测试通过。
- 性能:2.22x 提升,有量化数据支持。
- 兼容性:仅修改单文件,无 API 变化,风险集中于极端尺寸的边界处理。
关联脉络
当前未发现与此 PR 直接关联的其他历史 PR。该优化是 vLLM 持续提升量化推理性能的一部分。
参与讨论