Kestrel_VLLM_CUDA - 05 · 卸载集合:小投影为何是负收益

简介: 本文揭示小投影(如QKV/O)单独卸载至GPU反致性能下降的反直觉现象:其微弱算力(~8.4 MFLOP)远低于单次调用约100μs固定往返开销。解法是双管齐下——设FLOP门槛(1e7)自动筛选,再通过投影融合摊薄开销;同时补全lm_head挂钩、强制预热、依赖PROF分段分析精准优化。实测2B模型decode提速显著。(239字)

05 · 卸载集合:小投影为何是负收益

一句话:决定「哪些投影该上 GPU」时,直觉说「全都上」,实测说不是——QKV/O 这类小投影单独卸载反而更慢,因为每次调用的固定往返盖过了它们的算力;解法是 FLOP 门槛 + 投影融合。
前置:建议先读 第 04 篇 · 分组量化 GEMM 内核。
环境:x86-64 + NVIDIA RTX 5090(sm_120)· 阶段一 2B 稠密(Qwen3-VL-2B q8)。

一、问题与结论

问题 做法 判定
卸载集合怎么定? 不是「全上」,由分桶实测选出来 实测
小投影单独 offload 值不值? 不值(固定往返 > 其算力) 实测:变慢
那它们就永远在 CPU? 不——用 FLOP 门槛自动筛,用融合摊薄往返 实测:融合后转正
lm_head 为什么之前没快? 它走非批式路径,压根没被 offload 实测:补挂钩后 6.31 → 0.39

二、背景:一个反直觉的现象

最初的想法很自然:既然 GPU 快,就把 QKV / O / gate / up / down / lm_head 全卸载。实测(2B q8,decode,ms/token):

投影 卸载前 卸载后 判定
QKV 6.39 7.24 变慢
O 3.86 4.39 变慢
gate/up 17.10 8.66 大幅变快
down 10.71 3.77 大幅变快
lm_head 6.31 0.39 巨幅变快

另一轮记录(README 的分桶表)方向相同、绝对值略有差异(QKV 7.64→11.17、gate/up 16.96→8.66)——测量轮次不同,不可混比。

小投影卸载反而更慢。

三、根因:固定往返 > 小投影算力

每次设备调用的固定成本是一整条往返:

H2D(激活上传) → kernel launch → D2H(结果下载) → cudaDeviceSynchronize

实测这一条约 ~100 µs。而 decode 时 M=1,小投影本身的算力只有:

  • Q/O:8.4 MFLOP
  • K/V:4.2 MFLOP

在 ~100 µs 里做 8.4 MFLOP,等于要求 84 GFLOP/s——远低于 GPU 能力,说明时间全花在往返上,不在算。

根因一句话:瓶颈是固定开销,不是算力。 所以「让每次调用更值钱」才是解法方向。

四、核心机制

4.1 解决一:FLOP 门槛

用 FLOP 数把「太小不值得」的投影自动留在 CPU。宿主侧在每次尝试 offload 前先过一道门槛:

/* 文件:src/model/vllm_safetensors.c(st_cuda_try_gemm_batched_impl,节选) */
// MEASURED (2B q8, decode buckets, ms/token): offloading the small
// projections LOSES - QKV 7.64 -> 11.17, O 3.70 -> 3.73 - because the
// ~100 us per-call round trip exceeds their compute; gate/up (16.96 ->
// 7.56) and down (10.79 -> 3.98) win big. 1e7 FLOP cleanly separates ...
double minflop = 1e7;
const char *mf = getenv("VLLM_CUDA_MINFLOP");
if (mf && mf[0]) {
    double v = atof(mf); if (v >= 0) minflop = v; }
if (2.0 * (double)n_batch * (double)n_rows * (double)cols < minflop)
    ST_CUDA_REJ("below VLLM_CUDA_MINFLOP");

1e7 这条线在 M=1 时干净地切开两组:

投影 M=1 FLOPs 是否放行
K / V 4.2 M ✗
Q / O 8.4 M ✗
gate / up / down 25 M ✓
lm_head 622 M ✓

而 prefill 是 M=32,所有投影的 FLOPs ×32 → 全部放行。所以这个门槛只影响 decode,prefill 的收益不受影响。

4.2 解决二:投影融合

有些投影本身不值得(QKV/O),但它们共享同一次激活量化——那就把同层的几个投影拼进一次设备调用,一次往返摊给多个投影:

融合 范围 原因
QKV 三合一 仅 prefill(n_batch > 8) decode 下 QKV 仍被 FLOPs 排除
gate+up 二合一 全局 本身盈利,融合进一步摊薄

QKV 的 prefill 门就是这句判断(src/model/vllm_safetensors.c):

/* 文件:src/model/vllm_safetensors.c(dyn_matvec_q8_fused_qkv_batched,节选) */
if (st_cuda_enabled() && n_batch > 8) {
   
    ...
    if (st_cuda_try_gemm_fused(outs, ow, 3, x_batch, ws, nr, cols, n_batch,
                               g_cuda_layer, pj, ST_CUDA_FUSED_QKV, 0))
        return;   /* 已在 GPU 上完成融合的 Q/K/V */
}

设备侧实现是 col_off 转置进一个 [K][ΣN] 缓冲(见 第 03 篇),一次 launch、一次同步。融合路径自己也有一道同样的 FLOP 门槛(st_cuda_try_gemm_fused),且要求每个成员 N 是 4 的倍数(无空隙拼接)。

关键性质:融合只消掉往返,不改变每个输出元素的算术。VLLM_CUDA_VERIFY=1 全量对拍 0 失败——这是它能安全落地的依据。

4.3 解决三:lm_head 此前根本没被卸载

一个容易漏掉的点:lm_head 走的是非批式路径(dyn_matvec_q8 / dyn_matvec_q4_q8),不在批式勾子路径上,所以最初的 offload 根本没碰它。而它是单个最大的矩阵(2B 里 ~310 MB)且每 token 只调用 1 次——正是最该卸载的那个。

源码注释把这段经过记了下来:

/* 文件:src/model/vllm_safetensors.c(st_cuda_try_gemm_lmhead 之前,节选) */
 * ... the entire weight read leaves the host. Measured on the 2B q8 model:
 * lm_head cost 6.87 ms/token on the CPU and was UNCHANGED with the CUDA
 * backend on, because it is dispatched through the non-batched
 * dyn_matvec_q8/_q4_q8 wrappers, which were not on the offload path.
 *
 * Identity: those two wrappers are called with q8_lm_weight / q4_lm_weight
 * ONLY (verified at every call site), so a fixed sentinel layer id gives a
 * stable, collision-free wkey.
 */
#define ST_CUDA_LMHEAD_LAYER 0x7FFFFFF0

补上挂钩后 6.31 → 0.39 ms/token。

教训:「所有 GEMM 都挂了 offload」是个需要验证的假设,不能假设。拿分段耗时表逐项对,才发现有个大项压根没进来。

4.4 分段耗时表:定位优化点的第一站

VLLM_CUDA_PROF=1 在退出时打印每次调用的分段分解(cache_probe / upload / h2d / gemm / reduce / d2h+sync):

/* 文件:src/npu/cuda/vllm_cuda_kernels.cu(vcuda_dev_destroy 里的 prof 打印,节选) */
fprintf(stderr,
        "[CUDA-PROF] calls=%lld (hits=%lld uploads=%lld)  per-call avg us:\n"
        "  total=%.1f  cache_probe=%.1f  upload=%.1f  h2d_act=%.1f  "
        "gemm=%.1f  reduce=%.1f  d2h+sync=%.1f\n"
        "  share: gemm=%.1f%%  d2h+sync=%.1f%%  h2d=%.1f%%  "
        "reduce=%.1f%%  cache=%.1f%%\n", ...);

2B Q8 实测的占比:

分段 占比 说明
GEMM 仅 18% 内核本身
传输 + 同步 ~40% H2D / D2H / sync
上装 33% 只在首轮(预热后应为 0)

GEMM 只占 18% 这个数字直接说明:优化内核是次要的,先优化「调用形态」(融合、预热、减少同步)。这张表是后面所有优化的依据——定位优化点先看它,别猜。

4.5 预热:把上装成本移出计时窗口

不预热时,196 个权重的 strip-cache 收集 + 上传 + 转置(实测 ~0.82 ms × 196 ≈ 160 ms)落在第一次 prefill 的计时窗口里,会把 GPU 收益整个吃掉,使 prefill 净慢于 CPU。所以 VLLM_CUDA_INFER=1 在模型加载后自动预热全部权重,并打印两个数字:

/* 文件:src/main.c(模型加载期预热,节选) */
fprintf(stderr, "[CUDA] preload: %d/%d weights warmed in %.2fs; "
                "device cache %.1f MB / %d entries\n",
        warmed, want_all, st_now_sec() - tp, dbytes / 1048576.0, dent);
if (dent < want_wc)
    fprintf(stderr, "[CUDA] WARNING: only %d/%d weights stay resident -> "
                    "raise VLLM_CUDA_WCACHE_MB (device) AND "
                    "VLLM_GW_BUDGET_MB / VLLM_GW_SLOTS (host), or the "
                    "lazy rebuild dominates again\n", dent, want_wc);

一个实际运行的样例:

[CUDA] preload: 253/253 weights warmed in 0.36s; device cache 2854 MB / 253 entries

预算是硬前提:

VLLM_GW_BUDGET_MB=4096 VLLM_GW_SLOTS=256 VLLM_CUDA_WCACHE_MB=8192   # 2B Q8 参考值

宿主侧 ≥ 模型 int8 权重大小(2B≈1.4 GB / 8B≈7 GB),设备侧同理。预算不足会静默退化成「边跑边重建」。实测:宿主 strip-cache 抖动时,decode 会从 20 ms/tok 退化到 ~190 ms/tok。这个 WARNING 必须看,否则会拿一个退化的配置去下结论。

五、实测数据

口径 数值 判定 标注
QKV 单投影 offload(2B q8 decode) 6.39 → 7.24 ms/tok 变慢 实测
O 3.86 → 4.39 ms/tok 变慢 实测
gate/up 17.10 → 8.66 ms/tok 大幅变快 实测
down 10.71 → 3.77 ms/tok 大幅变快 实测
lm_head 6.31 → 0.39 ms/tok 巨幅变快 实测
每次调用固定往返 ~100 µs 小投影的主导开销 实测
PROF 分段占比 GEMM 18% / 传输+同步 ~40% / 上装 33% 先优化调用形态 实测
预热成本 ~0.82 ms × 196 ≈ 160 ms(2B q8 口径) 必须移出 prefill 窗口 实测
预算不足退化 20 → ~190 ms/tok 必须看 WARNING 实测

六、边界与已知限制

  • 这里的实测是 2B q8 decode 的口径;~100 µs 固定往返与 1e7 门槛都绑定到当时那台设备,换设备要重测。
  • 「小投影负收益」是单投影结论;一旦融合(QKV 三合一、gate+up 二合一)往返被摊薄,结论就变了——两者不能混用。
  • QKV 融合只在 prefill(n_batch > 8)生效;decode 下 QKV 仍走 CPU。
  • 「预算不足会静默退化」意味着:不看 WARNING 就可能拿退化配置去下错误结论。
  • lm_head 走的是非批式路径,它的挂钩是后来补的;新加投影时要逐项确认它到底在不在 offload 路径上。

CPU 对照(迁移前基线)

  • CPU 参考:kestrel-llm/src/model/vllm_safetensors.c(函数 dyn_matvec_q8 / dyn_matvec_q4_q8)—— 非批式路径,lm_head 在 CPU 侧就走它。
  • 迁移要点:CPU 侧全部在本机算 → CUDA 侧把「小投影单独 offload 是负收益」交给 FLOP 门槛 1e7 自动留在 CPU;QKV/gate+up 融合摊薄固定往返;补 lm_head 挂钩;预热把上装成本移出计时窗。
  • 真机验证:未独立复现:需 2B Q8 decode 分桶耗时表 + VLLM_CUDA_PROF 分段占比与预热日志,均需模型运行。

七、小结(可复用结论)

  1. 卸载集合由实测选定,不是全上:小投影的 ~100 µs 固定往返超过其算力,单独上 GPU 是负收益。
  2. FLOP 门槛 1e7 是最省事的筛子:M=1 时干净切开「不值得」与「值得」;prefill 因 M=32 全部放行,不受影响。
  3. 融合只消往返、不改算术:QKV / gate+up 拼成一次调用,每个输出元素的算术不变——这才是它能安全落地的前提。
  4. 别假设「所有 GEMM 都挂了 offload」:lm_head 走非批式路径,最初压根没被卸载;要靠分段耗时表逐项对。
  5. 先看 PROF 分段表:GEMM 只占 18%,先优化调用形态(融合、预热、减少同步),再谈内核。

相关篇目:第 04 篇 · 分组量化 GEMM 内核、第 06 篇 · VRAM 驻留窗口的两个坑
源码与配套资源:本仓库 https://gitee.com/pei-xiaoguang/kestrel-llm-cuda.git;
CPU 推理源码 https://gitee.com/pei-xiaoguang/kestrel-llm

相关文章
|
21天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9133 29
|
20天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
4305 16
|
20天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2363 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
8天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
6天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
520 1
|
14天前
|
人工智能 Linux 开发者
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
Codex是OpenAI推出的AI编程智能体,可读取本地项目、理解需求并自动修改代码。支持桌面GUI、命令行(CLI)及VS Code/Cursor插件三种形态,覆盖可视化操作、终端高效开发与编辑器无缝集成场景,助开发者用自然语言驱动编码全流程。(239字)
1140 2
【2026国内使用】Codex安装过程一篇讲透(Win/Mac/Linux全支持)
|
9天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
1089 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面

热门文章

最新文章