本仓库 fork 自 InfiniTensor/quant_gemv_test。作业计算 C = alpha * A * B^T,固定 N=1,默认尺寸沿用题目给出的 M=122753、K=2304。权重每 32 个元素共用一个 fp16 scale,量化值保存成 int8;程序会生成 CPU 参考结果、检查误差,再用 OpenCL event 统计纯 kernel 时间。
- 内存布局:将 Q8_0 的 int8 权重和 fp16 scale 拆成连续 buffer,保证权重读取连续,也绕开结构体对齐差异。
- 并行归约:一个 work-group 计算一行输出,各 work-item 处理若干完整量化块,最后通过 local memory 做树形归约。
- 低位计算:激活同样按 32 个元素量化为 Q8_0,提供标量 int8 点积和
dot(float4, float4)两个版本,并保留 fp16 激活基线用于对比。
代码里的中文注释尽量按实际思路来写,重点解释为什么拆 buffer、怎么分配 work-item,以及 local memory 归约在做什么。权重矩阵有 122753 行,常见 2D image 的高度容不下,因此最终使用线性 buffer 保持行内合并访问。
Ubuntu/WSL2 先准备编译器、OpenCL loader 和对应设备的 ICD:
sudo apt update
sudo apt install -y g++ make cmake ocl-icd-opencl-dev clinfo
clinfo --list编译和测试:
make
make quick # M=4096,先做快速正确性检查
./build/gemv_quantv1 # 题目原始尺寸,预热 10 次并统计 100 次也可以使用 CMake:
cmake -S . -B cmake-build -DCMAKE_BUILD_TYPE=Release
cmake --build cmake-build -j
./cmake-build/gemv_quantv1 --quick程序支持 --rows、--cols、--local、--warmup、--iterations 和 --kernel。其中 cols 必须是 32 的倍数,local 必须是 2 的幂。
测试环境为 Ubuntu 24.04、NVIDIA OpenCL、RTX 4090 D 24GB、驱动 570.124.06。原始尺寸 M=122753、K=2304,local=32,预热 10 次、统计 100 次:
| kernel | 平均时间 | 相对基线 | kernel 最大误差 | 含量化最大误差 |
|---|---|---|---|---|
gemv_q8_fp16_scalar |
3.9109 ms | 1.0000× | 1.907e-6 | 3.605e-2 |
gemv_q8_fp16_local |
0.7360 ms | 5.3140× | 2.861e-6 | 3.605e-2 |
gemv_q8_q8_local |
0.4981 ms | 7.8522× | 3.338e-6 | 4.638e-2 |
gemv_q8_q8_dot4_local |
0.4979 ms | 7.8544× | 3.338e-6 | 4.638e-2 |
kernel 最大误差对比对应的量化 CPU 参考,用来确认 kernel 是否写对;含量化最大误差对比原始 fp16 权重和 fp16 激活的 CPU 结果,也把量化损失算进去。另在 PoCL/CPU 后端完成了交叉验证,四个 kernel 的最大实现误差均在 3.0e-6 左右。
.
├── gemv_quantv1.cpp # 作业入口,保持官方文件名
├── src/main.cpp # host、量化、CPU 参考与性能测试
├── kernels/gemv_q8.cl # 四版 OpenCL kernel
├── Makefile
├── CMakeLists.txt
└── gemv_quantv1.bat # Windows g++ 构建入口