微软 GitHub 副总裁 Scott Hanselman 在 11 月 6 日公开了他新开源的工具 gputaskman,它把显卡占用从整机 15 GB 已用这种粗颗粒读数,拆到了哪个进程占了多少、里面装的是哪个模型、权重和 KV cache 各占多少这一层。对本地跑大模型的人来说,这件事的价值比它听上去更大,因为显存不够时,真正缺的信息从来不是总量,而是结构。
一句话结论:gputaskman 是一套用 Python 写的终端界面工具,用 Windows WDDM 计数器加 NVML、AMD ADL/SMI 来做进程级 GPU 归因,还能识别 Ollama、llama.cpp、LM Studio 里的具体模型并给出权重与 KV cache 拆分,Windows、Linux、WSL 和 Apple Silicon Mac 四端都能跑,MIT 协议。代价是它仍是 v0.3.0 的实验性软件,很多读数是推断而非实测。
它到底比现有工具多看到了哪一层
Windows 任务管理器的详细信息页已经能按进程列显存,性能页也能看到 GPU 利用率、温度、功耗。英伟达官方的 GPU 活动监视器则列出正在占用显卡的程序。问题在于,这两条路都停在哪个程序占的,不回答这个程序在干什么。
gputaskman 想补的就是这最后一层。它在进程列表之外多做了三件事。
第一,把系统内存里显卡可访问的部分单独拆成一列。Windows 的显存管理里,显卡放不下的数据会挪到系统内存,这部分在任务管理器里混在别的数字里,gputaskman 把它叫 Spill,用排序键直接排在最前面。
第二,给每个进程做模型鉴定。它能读出进程加载了哪个 GGUF 文件、量化到什么位、堆了多少层,并按 GGUF 头部元数据判断模型类型:带 decision.type 键的是决策模型,带 pooling_type 的是嵌入模型或重排模型,带 stt.* 的是语音模型,其余带层数和已训练上下文的才按因果语言模型算。这套判定会顺带标出哪些是决策模型,例如 Laya 和 OpenJev 这类只做决策不分配 KV cache 的模型。
第三,给出证据等级。每个模型行都会标明它是怎么被认出来的:api 表示运行时本地接口报告模型已加载,mapped 表示操作系统显示该文件此刻正被映射进进程,cmdline 表示文件是启动参数带进来的,installed 只是安装目录里有这个文件、并不能证明加载。两个来源里只要有一个是 api 或 mapped,才算确认。仓库里特意写明,加载了某个 DLL 不构成这个库正在跑 GPU 运算的证据,映射了某个模型文件也不构成这个模型此刻在显卡上。
和任务管理器、nvidia-smi 的能力对照
把这几套工具放在一张表里对照,差别会清楚很多。
| 问题 | Windows 任务管理器 | nvidia-smi | gputaskman |
|---|---|---|---|
| 整机显存占用 | 能 | 能 | 能 |
| 进程级显存占用 | 能,但含共享资源重复计数 | WDDM 下常报 N/A,WSL 下完全不列进程 | 能,优先用 Local Usage 与 Non Local Usage |
| 进程级 GPU 占用率 | 不提供 | 能 | 能,分 SM、编码器、解码器三路 |
| 显卡可访问的系统内存 | 混在进程内存里 | 不提供 | 单独成列,Spill 与 Paged 分开 |
| 识别加载的模型 | 不能 | 不能 | 能,给出模型名、量化、层数与证据等级 |
| 权重 / KV cache 拆分 | 不能 | 不能 | 能给,KV 为估算值 |
| 快照对比 | 不能 | 不能 | 有 diff 子命令 |
其中最值得单独说的是 Windows 的显存计数重复问题。同一块共享的图形表面,可能同时记在它的应用、csrss.exe 和 dwm.exe 三行上,直接把三行加起来就重复算了。gputaskman 优先采用 Local Usage 和 Non Local Usage 这组计数器,只有拿不到时才退回 Dedicated Usage 与 Shared Usage。仓库里记了一次实测对照:同一次采样下,桌面窗口管理器的旧计数器读出约 15.3 GiB,而 Local Usage 只有约 1.4 GiB,差了十倍量级。适配器总量仍然是最终参照,当各进程之和超过适配器总量时,界面会做归一化并明确标注,而不是让数字看起来自洽。
至于 nvidia-smi 为什么在 Windows 上常常给不出进程列表,原因在 WDDM:Windows 自己管理显存,NVML 的进程级查询经常返回 N/A。设备级读数它依然准,但在 WSL 里它连一个进程都列不出来,因为显卡归 Windows 侧所有,Windows 把所有 Linux 进程的显存都记在 vmwp.exe 这个虚拟机工作进程头上。要在 WSL 里看到逐进程数据,就得走 gputaskman 的主机适配器路径。
一组实测数字:估算 504 MB,实际只涨了 83 MB
这是这个工具最有说服力的一个例子。开发者在一台配 RTX 4080 SUPER 的机器上,用 Docker Model Runner 里的 llama-server(走 Vulkan)加载 gemma-3-270m,先把上下文长度从 4096 改到 32768,工具给出的 KV cache 估算值是增加约 504 MB,而服务器实测的专用显存只增加了约 83 MB。
差距为什么这么大,有两层原因。第一层是估算口径本身偏保守:Gemma 3 用的是滑动窗口注意力,注意力实际能触及的上下文远小于标称值,按满窗口算出来的 KV 需求自然是个上限。第二层是分配器的行为:CUDA 和 Vulkan 都是惰性分配,释放的显存还会被缓存起来不立刻归还,所以变小了这件事本身也可能在快照里滞后出现。
工具的做法不是把这个矛盾藏掉,而是把它显式摆出来。运行时自己上报的缓冲区大小和基于 GGUF 元数据的估算值会被单独列在每个进程下面,永远不会加进实测总数里;一个模型文件出现在某个进程里,也只作为这个进程打开或提到过它的证据,不计入显存增长。这套把测得值和推断值分开放的原则贯穿了整个设计。
另一个数值来自进程匹配。在同一台机器上,2 GB 和 5 GB 两次 PyTorch 分配被匹配到正确的 PID,显示为 2.119 GiB 和 4.914 GiB,差额约 0.26 GiB 来自 CUDA 上下文本身。作者在文档里明确标注了这是一台机器一个驱动的结果,不构成保证:早于首次使用 GPU 就启动的进程会破坏按启动顺序的猜测,同一刷新周期内启动的多个进程也只能靠尺寸是否相同来区分。
四个平台的差异比想象中大
README 里有一张矩阵,把 Linux 原生、WSL 互通开启、WSL 互通关闭三种情况下的可测量项列得很清楚。
| 项目 | Linux 原生 | WSL(互通开启) | WSL(互通关闭 / –no-host) |
|---|---|---|---|
| 设备总量、占用率、温度、功耗、频率 | NVML | NVML | NVML |
| 逐进程显存 | NVML 实测,容器内带 * 或 ? 标记 | 从 Windows 主机匹配,带 * 或 ? 标记 | 显示 n/a |
| 逐进程 GPU 占用率 | NVML 分 SM、编码、解码 | Linux 进程为 n/a | n/a |
| Spill / Paged | 不可用(留空而非显示 0) | 仅 Windows 行 | 不可用 |
| WSL 虚拟机总量、Windows 应用 | 无 | 读取 Windows 主机计数器 | 无 |
| 运行时、模型文件、LLM 拆分 | 读 /proc/<pid>/maps 与命令行 | 同左 | 同左 |
WSL 那一列背后是一整套推断机制。Linux 侧无法知道自己用掉了多少显存,NVML 采样回来的进程 ID 又是 Windows 的 PID,Windows 把所有 Linux GPU 显存都记在 vmwp.exe 名下。gputaskman 的做法是启动一个常驻的 powershell.exe,通过同一套 PDH 数组 API 读 Windows 侧计数器,每秒回传一次,再把 vmwp.exe 的 GPU 上下文和 Linux 进程做配对。首次主机采样约需 2.5 秒,界面上先显示 starting。
配对结果用三种标记区分可信度:* 表示已匹配,是进程与上下文一一对应得出的确切数字;? 表示猜测,针对的是监控启动前就已在跑的进程,靠新进程对应新上下文、老的归最老这类启发式推的;n/a 表示找不到可分配上下文。文档里还有一条原则:猜测每次刷新都会重算,绝不把自己当成自己的证据;已经退出的进程其上下文会被单独扣留,不让给新来的进程,防止串号。
容器场景是另一个坑。NVML 报的是宿主机 PID,在容器的 /proc 里根本没有意义。gputaskman 会把 NVML 未解析的 PID 与容器内映射了 libcuda.so 的进程配对,因为纯 NVML 工具比如 nvidia-smi 只会打开同样的设备文件而从不加载 libcuda,两者的进程集合因此可以区分开。两个 PID 命名空间都按创建顺序编号,配对只能按顺序进行,所以顺序对上了标 *,数量对不上标 ?,剩下的显示成 H:<pid> 表示在本容器之外。文档里记了一次在 Vast.ai 的 Ubuntu 22.04 容器、RTX 3060 Ti、驱动 580.82.09 上的实测:两个启动前已在跑的 PyTorch 任务、一个监控期间新启动的任务和一个被杀掉的任务,都配到了正确的容器 PID,显存分别为 1.12、2.52 和 1.59 GB。
macOS 侧的路径完全不同。Apple GPU 与 CPU 共享一块内存池,没有独立显存可分,工具改用 IOAccelerator 的 PerformanceStatistics 取设备级分配量,每个 Metal 客户端通过 AGXDeviceUserClient 报出创建者 PID 和累计 GPU 时间,两次采样之间的增量就是该进程的占用。进程显存则取 footprint,也就是 macOS 记在图形名目下的 Metal 缓冲与纹理。这项测量每个进程约 70 毫秒,因此放在后台线程里跑:繁忙进程和新进程每轮约 2 秒测一次,空闲进程每 30 秒测一次。实测环境是一台 M4 Mac mini(macOS 26.3.1),一个带 2 GiB 缓冲的 PyTorch MPS 任务被显示为 3.06 GB、99% 占用,设备利用率同为 99%。
怎么装怎么跑
Windows 用户有两条路。 Releases 页面提供免安装的便携包,Intel 与 AMD 平台的 x64 机器取 gputaskman-VERSION-windows-x64.zip,原生 Windows ARM64 取 gputaskman-VERSION-windows-arm64.zip,两者是各自独立的原生构建,不是通用可执行文件。解压时要把整个 ZIP 里的 _internal 目录和 gputaskman.exe 放在一起,然后打开 Windows Terminal 或 PowerShell 直接运行。
| 命令 | 作用 |
|---|---|
| gputaskman | 进入实时终端界面 |
| gputaskman –once | 输出一份文本快照后退出 |
| gputaskman –json | 输出一份 JSON 快照,自动隐含 –once |
| gputaskman diff before.json after.json | 对比两份 JSON 快照,看哪块内存真的涨了 |
| gputaskman -i 2 | 改为每 2 秒采样一次 |
| gputaskman –list-gpus | 列出可选适配器及其厂商与 LUID |
| gputaskman –gpu N | 指定适配器,越界会报错而非静默改选 |
| gputaskman –no-host | WSL 下不通过互通读取 Windows 主机计数器 |
不想装 Python 环境的话 Releases 页直接下包就行。需要源码运行的,仓库推荐用 uv,uv run --locked gputaskman 一条命令会自己拿到锁定的 Python 版本、创建虚拟环境并装好锁定依赖,第一次运行需要联网。已经有 Python 3.11 以上版本的用户用 pip 也可以:py -m venv .venv 然后 .\.venv\Scripts\python.exe -m pip install .。Linux 与 WSL 的便携包要求 glibc 2.35 以上,也就是 Ubuntu 22.04、Debian 12 或更新的系统。
要验证 KV cache 涨了多少,标准流程是存两份快照再对比。先 --json > before.json,加载模型或改完上下文参数、让它处理完一个请求之后再 --json > after.json,然后跑 diff。对比时只比较实测计数器,运行时上报的段和估算值会分开列示。进程的配对顺序是先按 PID,再按名称和监听端口,最后才按唯一名称,因为模型重载经常换一个 PID 重启服务器。两个快照间变化小于 16 MB 的进程在文本输出里会被隐藏,但在 JSON 里仍然保留。
读数时最容易踩的三个坑
第一个坑是 Spill 的含义。它表示显卡可访问的系统内存,不等于发生了溢出。里面可能包含有意分配的、固定(pinned)的宿主缓冲。数值很大且显卡接近满载,通常暗示推理已经回落到系统内存、速度会变慢,但单看这个计数器推不出原因。
第二个坑是 Paged 是估算值,计算式是 max(总提交量 - Local Usage - Non Local Usage, 0)。它不是淘汰轨迹,也不是页面文件测量,更不能证明 Windows 刚把多少字节挪出了显存。而且在使用回退计数器时,这个值本身就更不可靠。
第三个坑是内存单位的标注。界面用的 GB 和 MB 实际是二进制单位,也就是 GiB 和 MiB。另外 Mem activity 那一栏读的是 NVML 的显存活动百分比,不是实测的带宽 GB/s。还有一处需要区分:显卡上 CUDA 的活动有时会挂在 3D 引擎那一列上,这不代表它在跑图形渲染。
温度、功耗、频率、PCIe 读数在拿不到时显示 N/A 或 JSON 里的 null,这是驱动没暴露该读数,不能直接当成硬件故障。反过来也一样,进程内存或引擎采样缺失也是显示 N/A,而不是显示 0。文档里有一句提醒很实在:进程的命令行、监听端口这类探测,单次成本在 1 毫秒以内,所以模型与运行时的发现做了节流,新加载的模型可能要等大约 15 秒才出现。
它做不到的事
这个工具的价值有边界,作者本人写得很清楚。它不扫描显存内容,不枚举游戏贴图,不读取别的进程内存里的模型名称,也不做 3D、计算、拷贝、视频这几类引擎的拆分。想看纹理、张量或 kernel 级别的占用,还是得用 PIX、Nsight 或运行时自己的插桩。CPU 推理、NPU 推理和云端负载都在范围之外,Windows 的 Studio Effects 里如果实际是 NPU 执行,不能把 GPU 上的相机或渲染活动当成模型运行的证据。
平台覆盖上也有明确的缺口。仓库里的 issue 显示,原生 Linux 目前只发现 NVIDIA 的 GPU,AMD 在 Linux 侧的支持是一个待办;多 GPU 机器也有用户报告说程序只在第一张卡上工作。这两条都还在 open 状态。PyPI 发布的计划同样只是 issue 里的提问,还没有落地。另外 Windows 上还没有代码签名,也没有安装程序,便携包是唯一分发形式,不过每个 ZIP 都附带许可证和独立的 sha256 校验文件。
常见问题
gputaskman 和 nvidia-smi 该用哪个
看平台和目的。设备级的显存、占用率、功耗、频率,nvidia-smi 依然准,而且轻量得多。要追哪个进程吃掉显存、这个进程跑的是哪个模型、权重和 KV cache 各占多少,Windows 上就得靠 gputaskman,因为 WDDM 下 nvidia-smi 的进程级查询经常给不出答案,WSL 下它更是完全列不出进程。两者可以并行用:nvidia-smi 看卡,gputaskman 看进程。
在 WSL2 里跑 Ollama,为什么看到的显存数字对不上
这是路径本身的问题,不是 bug。WSL 下显卡归 Windows 所有,Windows 把所有 Linux 进程的显存都记在 vmwp.exe 头上,Linux 侧看不到自己的占用。gputaskman 会读 Windows 主机计数器再回传并做配对,标 * 的是确切匹配,标 ? 的是启发式猜测。如果不想要这层推断,可以加 --no-host,代价是逐进程显存直接显示 n/a。想彻底避开这套复杂度,用原生 Linux 或直接跑 Windows 版会干净很多。
KV cache 估算值和实测值差这么多,该信哪个
信实测。估算值来自 GGUF 元数据和上下文长度参数,滑动窗口注意力这类机制会让它明显偏高,它的作用是给出一个上限参考。实测的专用显存增长才是显卡真正被占掉的量。要判断换上下文长度到底加了多少显存,用 --json 存前后两份快照再 diff,这比看单次读数可靠得多。
小结
gputaskman 目前的形态更像一个把显存黑箱拆成可核对清单的工具,而不是一个替代任务管理器的监控面板。它的核心主张是:显卡占用的数字必须能追到具体进程和具体模型头上,并且明确区分哪些是测得的、哪些是推断出来的。做本地大模型推理的人对这件事的痛感往往比做训练的人更深,因为训练时显存不够会直接报错,本地推理时显存不足的表现是悄悄变慢,而原因往往藏在系统内存那一栏里。
要留意的是成熟度。v0.3.0、MIT 协议、149 星、9 个 issue,Windows 便携包只有几百次下载。仓库自己把软件定义为早期实验性质,多 GPU、原生 Linux 上的 AMD、PyPI 分发都还悬着。想尝鲜的话,先在一台有明确用途的机器上跑 --once 和一次快照 diff,比一上来就盯实时界面更容易拿到有用的结论。
相关阅读
- DeepSeek Harness 接本地 Ollama:8192 上下文必崩的三个坑
- EdgeRock-4B 开源:72 MiB 固定 KV,AIME 82.9 分
- Hy-Nomas 拆解:8B 世界模型用 20.53 GiB 显存跑 100 秒
- Recursive Flint 开源:40B 只激活 2.5B,本地跑 131 tok/s
- GPU Unlock 解锁 RTX 30 系:3090 Ti BF16 算力提升 80%
延伸阅读
- 腾讯 Miora 正式开放使用:Google/GitHub 账号直登,一句话 Brief 生成全套视觉方案2 月前
- 天数智芯天垓300实机跑通:单卡吞吐达H200的1.05倍1 周前
- 阶跃星辰开源 Step 5 Preview:昇腾壁仞沐曦 0Day 支持3 周前
- OpenClaw v2026.10.6 更新发布2 周前
继续读
同一批更新里这几篇值得一看:
