Prhub

#47268 Fixes non-coalesced HBM access in marlin_int4_fp8_preprocess_kernel_awq

原始 PR 作者 flutist 合并时间 2026-07-21 09:30 文件变更 1 提交数 6 评论 12 代码增减 +11 / -7

执行摘要

修复 AWQ 预处理内核非合并 HBM 访问,提速 2.22x

原始线程映射中相邻线程(threadIdx.x)读取的 qweight 元素间隔 size_n/8 个 int32,导致 HBM 访问非合并,扇区利用率仅约12.5%。重映射后相邻线程读取连续 int32,充分利用 HBM 带宽,显著缩短模型加载冷路径时间。

建议仔细阅读以学习 GPU 内存合并优化模式。评审中 Copilot 对冗余检查和未使用参数的提醒展示了代码审查的价值,值得在其他内核优化中参考。

讨论亮点
  • size_k 未使用参数警告:Copilot 指出 kernel 中 size_k 不再使用,某些构建将 -Wunused-parameter 视为错误。作者添加了 (void)size_k; 解决。
  • size_n % 8 检查冗余:Copilot 提出该检查因推导关系始终为真,不提供有效验证。Harry-Chen 同意,作者确认并删除。

实现拆解

  1. 重新设计线程映射:将 threadIdx.x 从行索引改为列索引,blockIdx.x 直接对应行,blockIdx.y 组索引列块(每块32列)。相邻线程现在读取同一行中连续的 int32 字。
  2. 调整网格配置:从 dim3 blocks(size_k / 32, size_n / 8) 改为 dim3 blocks(size_k, (size_n / 8 + 31) / 32),确保每个线程处理一行内的一列,并覆盖可能不完整的尾块。
  3. 添加列边界检查:在 kernel 开头检查 col >= size_n / 8 时提前返回,处理网格中可能多余的线程。
  4. 处理未使用参数size_k 在 kernel 内不再使用,添加 (void)size_k; 以抑制编译警告。
  5. 移除冗余检查(根据 review):删除了后来添加的 STD_TORCH_CHECK(size_n % 8 == 0),因为 size_n 来自 qweight.size(1) * 8,该检查始终为真。
文件 模块 状态 重要度
csrc/libtorch_stable/quantization/marlin/marlin_int4_fp8_preprocess.cu 量化内核 modified 4.47

关键符号

marlin_int4_fp8_preprocess_kernel_awq marlin_int4_fp8_preprocess

关键源码片段

csrc/libtorch_stable/quantization/marlin/marlin_int4_fp8_preprocess.cu core-logic

唯一修改文件,包含核心 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 类似,此处省略保持简洁。)

评论区精华

size_k 参数未使用警告 style

Copilot 指出 kernel 中 size_k 不再使用,某些构建将 -Wunused-parameter 视为错误,建议标记未使用或删除参数。

结论:作者添加了 (void)size_k; 以抑制警告。 · 已解决

size_n % 8 == 0 检查冗余 正确性

Copilot 指出 size_n 来自 qweight.size(1) * 8,因此 size_n % 8 永远为真,该检查不提供额外验证。Harry-Chen 同意,建议移除。

结论:作者同意并删除该检查。 · 已解决

风险与影响

主要风险来自线程映射更改导致的正确性。作者通过了 test_marlin_int4_fp8_preprocess_awqtest_marlin_int4_fp8_preprocess_without_zp 测试,确认新旧映射覆盖相同元素空间。性能测量显示无回归。对于极端尺寸(如 size_n/8 不是32倍数),代码通过 if 边界检查保证安全。无新依赖或 API 变更。

直接影响 AWQ int4→fp8 量化模型的加载速度(冷路径),对使用 Marlin 内核的部署场景有明显改善。只涉及单个 CUDA 文件,无用户 API 变化。团队可在不同 GPU 架构上进一步验证性能提升的一致性。

单文件变更 内核调整 覆盖测试通过

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论