news 2026/10/12 3:59:52

06 · VRAM 驻留窗口的两个坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
06 · VRAM 驻留窗口的两个坑

06 · VRAM 驻留窗口的两个坑

一句话:显存装不下整模型时,要让设备侧只保留一层有界工作集——但「驱逐」这件事有两个反直觉的坑:驱逐未来层会让代价对 keep 完全平坦,用cudaFree做驱逐的同步抖动比省下的重传还贵。
前置:建议先读 第 03 篇 · 权重条带缓存与融合权重、第 05 篇 · 卸载集合。
环境:x86-64 + NVIDIA RTX 5090(sm_120)· 阶段一 2B 稠密(Qwen3-VL-2B q8,1.85 GB)。

一、问题与结论

这不是加速开关,是「跑得起」的开关。目标是让显存装不下的模型能以有界显存工作集跑起来。

问题做法判定
该驱逐哪些层?只驱逐过去层,保留当前与未来实测:不对称才按 keep 缩放
驱逐时怎么释放?走设备缓冲池,不用cudaFree实测:cudaFree同步更贵
驻留会影响输出吗?不会——只改缓存占用实测:7 档 TOKIDS 8/8 相同
开窗时还要预热吗?跳过(会被立刻驱逐)实测:纯浪费

二、背景与规则

引擎宿主侧已有「逐层驻留」(VLLM_VQF_STREAM)。GPU 侧在同一个层边界钩子上推进,两边锁步:

驱逐 layer < cur_layer - keep + 1 的所有权重; cur_layer 本身与所有「未到来」的层保持常驻; lm_head 永远常驻。

规则落在vcuda_dev_wcache_window()里,挂载点是vqf_stream_layer_advance的同一处(4 处层循环):

/* 文件:src/npu/cuda/vllm_cuda_kernels.cu(vcuda_dev_wcache_window,节选) */constintlo=cur_layer-keep+1;intevicted=0;for(inti=0;i<VCUDA_MAX_ENTRIES;++i){vc_wentry_t*e=&s->ent[i];if(!e->used)continue;if(e->layer>=VCUDA_LMHEAD_LAYER)continue;/* lm_head: always keep */if(e->layer>=lo)continue;/* current + all upcoming: keep */s->bytes-=e->bytes;vc_ent_release(s,e);e->used=0;e->key=0;s->n_ent--;s->n_evict++;evicted++;}

为什么不对称?因为一层循环只向前扫:

  • cur之上的层马上要用——驱逐它们等于下一层立刻重传;
  • cur-keep+1之下的层本轮已消费完——它们才是可以丢的。

三、核心机制

3.1 坑 1:驱逐未来层 → 代价对 keep 完全平坦

第一版实现「同时驱逐> cur+1的未来层」,结果:

keep124816
ms/tok184178170163159

代价与 keep 几乎无关——不管留多少,都是 ~160–184 ms/tok。

原因就是第二节说的:未来层马上要用,驱逐它们 → 每 token 把所有层权重重传一遍。于是「省显存」变成了「每 token 全量重传」,keep 这个参数失去了意义。

改法:只驱逐过去层。改完后代价才按预期随 keep 缩放。

3.2 坑 2:用cudaFree驱逐 → 抖动比省下的重传还贵

第二版改对了方向,但驱逐动作是cudaFree一对:

cudaFree是设备同步调用。每层 6 次 free 的抖动,比它省下的重传还贵。

改法:加设备缓冲池(同几何复用,VCUDA_POOL_MAX=128槽),驱逐退化成 O(1) 指针移动:

/* 文件:src/npu/cuda/vllm_cuda_kernels.cu(vc_ent_release,节选) */if(e->dWT&&e->dbsT&&s->n_pool<VCUDA_POOL_MAX&&e->Npad>0&&e->ngroups>0){s->pool[s->n_pool].dWT=e->dWT;s->pool[s->n_pool].wt_bytes=(size_t)e->K*(size_t)e->Npad;s->pool[s->n_pool].dbsT=e->dbsT;s->pool[s->n_pool].bs_bytes=(size_t)e->ngroups*(size_t)e->Npad*sizeof(float);s->pool[s->n_pool].Npad=e->Npad;s->pool[s->n_pool].ngroups=e->ngroups;s->n_pool++;e->dWT=NULL;e->dbsT=NULL;e->bytes=0;return;}vc_ent_free(e);/* 池满时的退化路径:真 free */

池复用要求几何完全一致(Npad与ngroups都相同),这样布局逐字节匹配、无需重排:

/* 文件:src/npu/cuda/vllm_cuda_kernels.cu(vc_ent_alloc,节选) */if(s->pool[i].Npad==e->Npad&&s->pool[i].ngroups==e->ngroups&&s->pool[i].wt_bytes>=wt_bytes&&s->pool[i].bs_bytes>=bs_bytes){…}

resident 窗口下同一批形状循环出现,所以复用几乎总命中,预热后不再cudaMalloc。

效果:keep=1从 159 →94–106 ms/tok。

3.3 修正后的代价曲线

2B Q8,8 token 均值:

keepoff2726201241
ms/tok3233376178115106
设备常驻条数19719018314185298

读法:

  • 常驻43%权重(keep=12)→ decode 慢2.4×
  • 常驻14%权重(keep=4)→ decode 慢3.6×

模型装得下就别开它。它是「跑得起大模型」的开关,不是加速开关。(附带的一个反直觉点:keep=1 反而比 keep=4 略快——常驻越少,池复用率越高。)

3.4 不变式:驻留不影响输出

这是它能安全落地的前提。实测 keep ∈ {1,4,12,20,26,27} 与关闭共7 档:

greedy TOKIDS8/8 完全相同。

原因:驻留只改缓存占用,不改任何 GEMM 的输入、顺序或结果——下次用就重新上传。这与宿主侧「跨驻留档 TOKIDS 逐位相同」的红线同构(但只在本后端内成立)。同理,预热与懒加载两条路径的输出也一致(TOKIDS 前缀与翻转点逐位相同)。

3.5 开窗时自动跳过预热

窗口会在第一个层边界驱逐窗口外的权重,所以全量预热等于「传完即丢」——纯浪费。因此VLLM_CUDA_STREAM > 0时跳过预热,让窗口的按需路径自己传:

/* 文件:src/model/vllm_safetensors.c(st_cuda_preload_all,节选) */constchar*e=getenv("VLLM_CUDA_STREAM");if(e&&e[0]&&atoi(e)>0)return0;/* 窗口模式:跳过全量预热 */

四、实测数据

口径数值判定标注
驱逐未来层(第一版)keep=1…16184 / 178 / 170 / 163 / 159 ms/tok代价对 keep 平坦实测
仅驱逐过去 + 缓冲池 keep=1159 → 94–106 ms/tok缓冲池有效实测
2B Q8 8-token 均值代价曲线见上表(off 32 → keep=4 115 / keep=1 106 ms/tok)装得下就别开实测
驻留不变式7 档 keep 与关闭 TOKIDS 8/8 相同输出不变实测
窗口重传吞吐~7.7 GB/s离 PCIe 上限还远实测

五、边界与已知限制

  • 只有 2B(1.85 GB)在本机可跑,窗口的省显存收益需要 8B/30B 才能实测(现有 8B VQF 的布局标志0x1与引擎修订构建的0x3不匹配,无法加载——见 第 07 篇)。
  • 窗口的重传成本实测 ~7.7 GB/s,离 PCIe 上限还很远,瓶颈在 memset + 转置 + 同步的固定开销。「按层预取上装」(算第l层时并行上装l+1)是明确的下一步。
  • 「驻留不变式」(7 档 TOKIDS 8/8)只在本后端内成立,不能外推到与 CPU 的位级一致。
  • 池复用的 O(1) 驱逐依赖「同几何形状循环出现」;形状频繁变化的负载下命中率会下降。

CPU 对照(迁移前基线)

  • CPU 参考:kestrel-llm/src/model/vqf.c(函数vqf_stream_layer_advance,VLLM_VQF_STREAM逐层驻留)—— 每层权重用时建文件页、算完立即MADV_DONTNEED释放,只留 keep 层常驻。
  • 迁移要点:CPU 的逐层驻留 → CUDA 侧的不对称驱逐窗口(只丢过去层、保当前与未来,lm_head永不驱逐);cudaFree换成缓冲池 O(1) 复用;窗口档输出不变(TOKIDS 8/8)。
  • 真机验证:未独立复现:需 1.85GB 2B Q8 + keep∈{1…27} 七档实测与重传吞吐(~7.7 GB/s),无对应模型与探针。

六、小结(可复用结论)

  1. 只驱逐过去层,不碰未来:层循环只向前扫;驱逐未来 → 每 token 全量重传,代价对 keep 平坦、参数失去意义。
  2. 驱逐别用cudaFree:它是设备同步调用,每层 6 次 free 的抖动比它省下的重传还贵;缓冲池把驱逐退化成 O(1)。
  3. 池复用要求几何一致:Npad与ngroups都相同,布局才逐字节匹配、无需重排。
  4. 驻留必须输出不变:7 档 TOKIDS 8/8 相同,这条不变式是它能安全落地的前提;它只改缓存占用。
  5. 开窗跳过预热:预热会被窗口立刻驱逐,等于纯浪费;让窗口的按需路径自己传。

相关篇目:第 03 篇 · 权重条带缓存与融合权重、第 05 篇 · 卸载集合、第 07 篇 · 8B 阶段
源码与配套资源:本仓库 https://gitee.com/pei-xiaoguang/kestrel-llm-cuda.git;
CPU 推理源码 https://gitee.com/pei-xiaoguang/kestrel-llm

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 3:59:50

Work Agent深度解读:AI长程任务的执行机制与能力边界

AI的交互形态正在发生持续迭代&#xff0c;从最早期的单轮问答&#xff0c;到支持上下文记忆的多轮对话&#xff0c;再到可以调用外部工具完成特定动作&#xff0c;如今已经演化出能够自主推进多步骤工作链的Work Agent。普通对话型AI擅长即时应答&#xff0c;针对用户单次提出…

作者头像 李华
网站建设 2026/10/12 3:59:33

大语言模型的技术发展现状与应用场景解析

构建一个高质量的国外参考文献库&#xff0c;听起来很宏大&#xff0c;但其实就是把“找、管、用”这三件事做对。整个过程最关键的一步&#xff0c;是选对一个能陪你走完全程的“智能伙伴”。我强烈推荐 切问学术&#xff0c;它能让这件事从杂乱无序变得井井有条。 第一步&am…

作者头像 李华
网站建设 2026/10/12 3:59:24

GPU算力涨价潮下,如何选择平台与省钱实战指南

说句实在话&#xff0c;干深度学习这几年&#xff0c;我见过比模型loss还要让人心慌的东西&#xff0c;就是算力账单。2026年这波算力涨价潮来得既猛又急&#xff0c;GPU算力平台的时租价格一个季度内动辄上涨两到三成&#xff0c;很多原本低价能捡到的算力资源&#xff0c;一夜…

作者头像 李华
网站建设 2026/10/12 3:58:23

【知识讲解】 Linux之动静态库的了解

目录 前言 Part1. 库的意义 Part2. 静态库制作、编译与发布 Part2.1. 静态库制作步骤 Part2.2. 使用静态库编译业务代码 Part2.3. 静态库的打包分发 Part2.4. Makefile 自动化构建静态库、一键打包发布 Part3. 动态库制作、编译、部署 Part3.1. 动态库制作 Part3.2. …

作者头像 李华
网站建设 2026/10/12 3:57:59

【学习记录】PCB与PCBA核心知识全解析:从设计到制造的关键细节

【学习记录】PCB与PCBA核心知识全解析&#xff1a;从设计到制造的关键细节 在硬件设计中&#xff0c;PCB和PCBA是绕不开的基础。本文在原有框架上补充关键细节&#xff0c;把从设计到制造的知识点串起来&#xff0c;涵盖PCB/PCBA区别、阻焊颜色、表面处理、基板、过孔、制造流程…

作者头像 李华
网站建设 2026/10/12 3:57:17

std1.97.1——iter模块总览

目录1. Iterator2. 三种迭代形式3. 实现Iterator4. for循环与IntoIterator5. 按引用迭代6. 适配器7. 惰性8. 无穷参考1. Iterator 本模块的核心与灵魂是Iterator Trait。Iterator的核心部分如下所示&#xff1a; trait Iterator {type Item;fn next(&mut self) -> Opti…

作者头像 李华