首页 / 本地大模型

显存、内存、硬盘在本地大模型里各扮演什么角色

作者 JC · 更新于 · 最后查证

简体版由繁体自动转换,少数用词可能和你习惯的不同。

要点速览

硬盘存放模型档,内存(RAM)是加载后的中继站,显存(VRAM)放最常被反复读取、运算的权重与 KV cache。最理想是全部放进显存;放不下时,部分层改放系统内存并由 CPU 运算,速度会明显变慢;连内存都不够时,操作系统只能靠硬盘分页顶着,那时才真的会卡。判断顺序是:先算总需求,再看显存、内存、硬盘依序能接住多少。

很多人买显卡之前,只问「显存多大」;也有人觉得「我内存很大,应该什么模型都能跑」。这两种想法都只对一半。显存、内存、硬盘在本地大模型里各自负责不同的工作:硬盘存盘,内存当中继,显存负责最吃力的运算。这篇文章先带你走一遍模型从文件到产生第一个字的旅程,再整理三者的角色比较表,最后给你一套「放不下时怎么分流」的判断流程,让你在看到错误消息或速度变慢时,知道问题出在哪一层。

先把三个词讲清楚:

  • 显存(VRAM):显卡自己带的内存,GPU 运算时直接读写它。
  • 内存(RAM):电脑主板上的系统内存,CPU 直接读写它。
  • 硬盘:长期存放文件的保存空间,模型档平常就躺在这里。

这里的「本地大模型」是指在你自己的电脑上运行的大型语言模型(LLM,术语表有简短说明)。下面的细节,以 llama.cpp 这个常见的本地推论引擎的官方说明为主要依据;其他工具的行为可能不同,但三种保存各自的角色是相通的。

一个模型从文件到产生第一个字的旅程

先交代两个名词。**权重(weights)**是模型训练出来的那一大堆数字,决定模型怎么思考,文件里存的主要就是它。GGUF 是 llama.cpp 生态系常用的模型档格式,官方规格写明它是「为了用 GGML 及相关运行器推论而存放模型的文件格式」,并且设计成可以快速加载与保存,特色之一是兼容 mmap,也就是模型可以用 mmap 加载。

整趟旅程可以拆成四站。

第一站:硬盘上的模型档。 你下载的 GGUF 档就是权重加上所需的描述信息(官方规格说明,所有加载模型需要的信息都在同一个文件里)。这个阶段它只是文件,还没有占用任何显存。

第二站:加载到内存或显存。 加载模型时,引擎要决定每一块权重住在哪里。llama.cpp 预设的加载方式是 mmap(官方说明写的是 auto:除非设备不支持,否则使用内存映射)。**mmap(memory-map,内存映射)**是让操作系统把文件对应到程序的内存地址,需要的内容才被读进来,而不是一次先拷贝一份。被映射的内容由操作系统的分页缓存(page cache)管理;官方在谈 NUMA 选项时也顺带说明,当 mmap 的预取与预读被禁用,页面会改成「第一次访问时才加载,而不是一次全部加载」。

另一个关键参数是 GPU 层数。大模型由很多层(layer,一层一层重复的运算区块)堆栈而成,llama.cpp 用 -ngl(--n-gpu-layers,「最多有多少层存放在显存」)来决定放多少层上显卡,默认值是 auto,也可设为 all。这种「放一部分在显存、其余留在系统内存」的做法,官方 README 称为 CPU+GPU 混合推论,用途是「部分加速比显存总量还大的模型」。

第三站:配置推论时才会用到的空间。 加载权重之外,还要预留两种空间:KV cache(下一节详述),以及运算过程需要的暂存缓冲区(官方 fit-params 范例里称为 compute)。

第四站:运算并产生第一个字。 引擎读进你的提示词,逐层计算,产生第一个 token(术语表:模型处理文字的最小单位)。之后每多生成一个 token,KV cache 就会再长一点。

官方说明中和这段旅程最相关的旗标有这几个。旗标名称和默认值会随版本调整,以官方文档为准,你在较旧的教学里看到的写法可能和现行说明不同:

旗标(以官方文档为准)官方说明的意思
-lm, --load-mode模型加载模式,预设 auto(用 mmap,除非设备不支持)。可选 none、mmap、mlock、mmap+mlock、dio
-ngl, --n-gpu-layers最多有几层存放在显存,可填数字、auto 或 all,预设 auto
-c, --ctx-size提示词的上下文(context)大小,预设 0,代表从模型读取
-ctk / -ctvKV cache 的数据类型(K 与 V),预设 f16
-kvo, --kv-offload是否让 KV cache 也卸载到加速设备,预设激活
-fit, --fit是否自动调整未指定的参数,使模型塞进设备内存,预设 on

加载模式的几个选项,官方的描述是:mmap 为内存映射;mlock 是「锁定在内存中,防止它在被映射时被换出」,可能提升性能,但代价是需要更多 RAM,加载时间也可能变长;none 代表不使用特别的加载模式,加载会较慢,但在不用 mlock 时可能减少分页换出,而且官方特别提醒:如果模型比全部的 RAM 还大,关掉 mmap 会导致模型根本载不进去。

三种保存的角色比较表

先看「谁负责什么」。下表的「相对快慢」与「容量」栏是一般硬件原则的定性描述,不是任何官方说明给的数字;其中「系统内存比显存慢」这点,llama.cpp 多 GPU 说明里有一句直接的依据(「相对较慢的系统 RAM」)。

类别显存(VRAM)系统内存(RAM)硬盘
本地推论里放什么优先放权重(-ngl 决定放几层);预设连 KV cache 与运算缓冲也跟着放这里放不下显存的那几层权重;mmap 读进来的文件页面;CPU 运算要用的数据模型档本身(GGUF);系统开始分页时充当溢出空间
相对速度(一般原则)相对最快,GPU 直接访问比显存慢;CPU 访问相对最慢,通常只在加载或分页时被读写
容量(一般原则)通常最小,换显卡才能升级通常中等,可另购加装通常最大
用完了会怎样加载时错误(OOM,out of memory,内存耗尽);或在 --fit 预设激活下被自动缩小上下文、把部分层改放别处打开 mmap 时,系统可能把页面换出再读回;关掉 mmap 且模型比 RAM 大时加载失败文件放不下,下载或拷贝失败,与推论无关
影响什么能不能放得下、速度快不快放不下显存时的接住能力、速度掉多少加载快慢、能存多少模型

再看「推论时用到的四样东西,各自住在哪」。这一张对照官方 fit-params 范例的三栏(model、context、compute)加上文件本身:

东西是什么预设住在哪(依官方说明)随什么成长
权重模型本体的数字由 -ngl 决定几层在显存,其余在系统内存随模型参数量与量化方式
KV cache把已处理过的 token 的中间结果存起来,避免重复计算预设激活 KV cache 卸载;多卡时第 l 层的 KV cache 放在拥有第 l 层的那张卡随上下文长度大致成正比
运算缓冲(compute)推论过程的暂存空间同样计入设备内存随批量大小等参数
模型档硬盘上的 GGUF硬盘;加载后(mmap)会有文件页面常驻内存固定

KV cache 的来源,Hugging Face 官方的优化教学这样说:它保存了所有先前输入位置、所有层、所有注意力头的 key/value 矢量,因此在长输入或多轮对话时会变得很占内存;该教学也写明,使用 KV cache 之后,最大所需内存随串行长度是线性成长,而不是平方。教学中的范例模型(bigcode/octocoder)在假设输入长度 16000 的情况下,KV cache 大约要 15 GB,「约为模型权重本身的一半」。这是该文档的特定范例,不要拿来当作你的模型的数字;估算你自己模型的 KV cache,请看 怎么依显存选模型。至于权重为什么可以缩小成原来的几分之一,那是 量化 的主题,这里不重复。

总需求怎么分配到三种保存

先把总需求拆成三项。

总需求 ≈ 权重 + KV cache + 额外开销

权重        :GGUF 档大小是很好的起点(文件里主要就是权重)
KV cache    :随上下文长度(-c)增加,大致成正比
额外开销    :运算缓冲、操作系统与其他程序要用的空间,要留余裕

详细怎么逐项估算,放在 依显存选模型+估算方法 那篇;这篇只用它来做分流判断。下面是判断流程,从上往下一步一步看:

  1. 全部放得进显存吗? 比较「总需求」与「可用显存扣掉余裕」。可以,就全放显存,这是最快、最单纯的情况。官方 --fit 的预设余裕(--fit-target)是每张设备 1024 MiB,可以当作「别把显存塞到满」的参考,旗标细节请以官方说明为准。
  2. 放不下,先缩小可调的部分。 上下文长度是第一个可以动的,因为 KV cache 跟它成正比。官方 fit-params 范例输出的顺序就是先把上下文从 40960 缩到 4096(--fit-ctx 预设下限 4096),然后才处理层的分配。
  3. 还放不下,把部分层留在系统内存。 这是前面提到的混合推论。引擎会依 -ngl 把一部分层放显存、其余留在系统内存,由 CPU 运算。先确认一件事:系统内存放得下剩下的权重,还留得出给操作系统与其他程序。
  4. 内存也不够,才轮到硬盘。 这时要靠 mmap 让操作系统用分页方式顶着。它不是什么好选择,下面专门讲。

如果是 MoE(混合专家)模型,第 3 步有更细的做法:官方提供 --cpu-moe、--n-cpu-moe 之类的旗标,让你指定把哪些专家权重留在 CPU;为什么 MoE 可以这样拆,请看 MoE 混合专家模型。

示范计算一:官方 fit-params 范例里的三栏

这是官方说明里贴出的一段实际输出(官方范例用的是一张 24GB 等级的显卡),这里只引用其中的数字:

  • 可用显存 24077 MiB(MiB 是 1024×1024 字节,与 GB 的 10 亿字节不同)。
  • 全放显存的预估需求 61807 MiB,超过可用显存,所以不行。
  • 引擎把上下文从 40960 缩到 4096,输出写的是「需要的内存总共减少 3456 MiB」。
  • 最后的内存分解中,设备上使用 19187 MiB,其中 model(权重)17904、context(KV cache)384、compute(运算缓冲)898。同一段输出还显示,其中 34 层的专家(expert)权重被指定放在 CPU 端,也就是系统内存。

验算两件事(示范计算,前提是 KV cache 与上下文长度成正比):

上下文 4096 时的 KV cache:384 MiB
上下文 40960 时的 KV cache:384 × (40960 ÷ 4096) = 384 × 10 = 3840 MiB
少掉的部分:3840 − 384 = 3456 MiB   ← 与官方输出「减少 3456 MiB」吻合

三栏加总:17904 + 384 + 898 = 19186 MiB   ← 官方写 19187,差 1 是四舍五入

这个例子同时示范了本篇最重要的观念:权重、KV cache、运算缓冲是三笔分开的帐,而且光看权重(17904)不等于整体占用(19187)。

示范计算二:假设一个密集模型,12 GiB 显存放不下

下面是自己假设的情境,不是任何实际型号。

假设前提:

  • 一个 300 亿参数的密集模型(所有参数每次都用,与 MoE 相对)。
  • 量化密度比照 llama-bench 官方范例档:Qwen2.5-7B-Instruct 的 Q4_K_M 档,官方范例列出 model_size 为 4677120000 字节、model_n_params 为 7615616512。
  • 可用显存假设 12 GiB;KV cache 假设 1.5 GiB、缓冲与余裕假设 1 GiB(两者皆为示范假设值)。
每参数比特数 = 4677120000 × 8 ÷ 7615616512 ≈ 4.91 bit
权重大小 ≈ 30×10^9 × 4.91 ÷ 8 ≈ 18.4×10^9 字节 ≈ 17.2 GiB
(同样的 300 亿参数若用 FP16:依 Hugging Face 教学的经验法则「约 2×X GB」,X=30,约 60 GB)

显存可放权重 = 12 − 1.5 − 1 = 9.5 GiB
放得进显存的比例 ≈ 9.5 ÷ 17.2 ≈ 55%
需要留在系统内存的权重 ≈ 17.2 − 9.5 ≈ 7.7 GiB

这是粗估:各层大小不完全相同、输入输出层也要另外考虑,实际分配以引擎报告为准。结论是:这个情境下,大约一半多的权重在显存,其余约 7.7 GiB 要在系统内存,加上操作系统与其他程序的需求,内存要有余裕。只要系统内存接得住,硬盘就不会参与推论。

为什么放到系统内存会变慢,放到硬盘会更慢

系统内存为什么慢。 官方多 GPU 说明讲得很直接:模型放不进显存时,「模型的一部分得在相对较慢的系统 RAM 上运行」;同一份说明在排错表里也写,把 --n-gpu-layers 调低时,「剩下的层会在 CPU 上运行,推论会慢很多」。换句话说,被留在内存的那几层,不只是数据放在比较慢的地方,连运算也是在 CPU 上做,两件事同时发生。这份说明没有给具体倍数,本文也不给。

另一个相关的机制是 CUDA 的统一内存:llama.cpp 建置说明提到,在 Linux 上设置环境变量 GGML_CUDA_ENABLE_UNIFIED_MEMORY=1,可以让显存用尽时改为换到系统 RAM,而不是直接当掉(Windows 上对应的是 NVIDIA 控制面板里的一个设置)。这是驱动层级的替代方案,不是上面那种「由引擎明确分配层数」的做法,行为差异请以官方说明为准。

硬盘为什么是最后手段。 回到 mmap:它让操作系统像用内存一样访问文件,页面被读进 RAM 后可以重复使用。问题出在 RAM 不够大的时候,系统可能把页面换出去,之后再读回来。官方说明的许多选项,正是为了处理这件事:mlock 的用途就是「强制系统把模型留在 RAM,而不是换出或压缩」;官方加载模式的说明也提到分页换出(pageouts)是需要留意的事。另外一个例子是 --lazy-mode:官方说明它可以让某些张量(例如每层的嵌入)「按需从硬盘读取,而不是常驻」,预设只对大于 4 GiB 的张量激活,而且需要 mmap。这说明硬盘确实可以成为运行时的数据源,但官方是把它当作针对特定张量的选项,而不是通用的救急办法。

至于「这样到底多慢」:本文引用的官方说明没有给数字,所以这里也不写数字。可以确认的方向是:硬盘是三者中通常最慢的保存(一般硬件原则),而推论每生成一个 token 都要用到模型的大部分权重,这是密集模型的特性;如果这些权重得靠硬盘反复读回,速度就会被硬盘拖住。以上最后这段是根据一般原理做的推论,不是官方说明的原文。想知道你的机器实际会怎样,要自己量,量法见 Token/s 怎么看。

我的瓶颈在哪?三种限制的对照

速度不理想时,先分辨是哪一种限制。下面是定性的对照,不含任何数字。其中「带宽」的说法有部分依据:Hugging Face 教学指出,自回归解码时,大量的 key/value 矢量每一步都要重新读取,所需的内存带宽可能成为严重的时间瓶颈;它也提到 Flash Attention 的优势来自减少对显存的读写,把中间运算留在速度更快的芯片内内存(SRAM)里,也就是说教学是拿显存和芯片内的 SRAM 相比,才说显存「较慢」。其余字段属于一般原则。

瓶颈类型常见症状可以怎么对应
容量不足加载就失败或报内存不足;上下文一拉长就爆;系统整体卡顿、硬盘灯狂闪缩小上下文、改用更小或量化更狠的模型、把部分层改放系统内存、补足 RAM
带宽受限模型已经加载、内存也够,但逐字生成的速度不如预期;上下文越长越明显缩小上下文、考虑 KV cache 的数据类型(-ctk/-ctv,请以官方说明为准)、尽量让更多层留在显存
运算受限处理长提示词(预填)特别久;GPU 或 CPU 使用率很高;笔电烫到降频缩短提示词、换更小的模型,笔电请看 散热与电源

测量时,llama-bench 把「处理提示词(pp)」和「生成文字(tg)」分开测,官方说明列出的三种测试是 pp、tg 与两者合并的 pg。你可以分开看哪一段拖累整体感受,再回到上表对照。

常见误解

「内存很大,就能取代显存。」 内存大解决的是「放不放得下」,不是「跑得快不快」。官方说明把系统 RAM 称为相对较慢,被留在那边的层还要由 CPU 运算。所以容量够只代表模型能加载并运作,不代表速度好。

「硬盘越快越好,所以不用管内存。」 快的硬盘可以缩短加载时间,但模型加载后,推论时主要读写的是内存与显存。只有在内存不足、系统开始用硬盘顶替时,硬盘速度才会变成瓶颈;那是要避免的状态,而不是靠换快硬盘就能优雅解决的。

「文件大小就是需要的显存。」 前面的示范计算一就是反例:权重 17904 MiB,但整体占用是 19187 MiB,还有 KV cache 与运算缓冲。另外要小心单位,4677120000 字节在不同单位下是不同的数字:

4677120000 ÷ 10^9    ≈ 4.68 GB
4677120000 ÷ 2^30    ≈ 4.36 GiB

下载页面常显示 GB,工具回报常用 MiB 或 GiB,比对时要看清楚单位。

用实际工具对照

本站的 Strata vs PolyStrata 一文,列需求时就是把「RAM 加显存合计」与「硬盘空间」分开列出,正好对应本文的分工:硬盘看模型档放不放得下,内存加显存看加载与运行放不放得下。选模型、看需求时,用本篇的顺序去读那种规格,比只看某一个数字更准。

延伸阅读

常见问题

本地跑大模型,显存、内存、硬盘哪个最重要?

以能不能跑得顺来看,显存最重要,因为能放进显存的部分最不会拖慢速度。内存决定放不下显存的部分有没有地方接,硬盘则主要影响模型档的存放与加载。三者分工不同,缺哪一个的症状也不一样。

显存不够,用内存补可以吗?

可以。llama.cpp 官方说明有提到 CPU+GPU 混合推论,用来部分加速比显存还大的模型。但官方也把系统内存称为相对较慢,放到那边的层会在 CPU 上运行,整体速度会下降。

模型文件有多大,就需要多少显存吗?

不是。文件大小只是权重的部分,运行时还要加上 KV cache(随上下文长度成长)和运算用的缓冲区。官方 fit-params 范例里,这三项是分开列出的。

硬盘换成更快的 SSD,推论会变快吗?

主要会让加载模型的时间缩短。模型加载完成并常驻在内存或显存之后,推论时主要读写的是内存与显存。只有在内存不足、系统开始用硬盘分页顶替的时候,硬盘速度才会直接影响生成速度。

什么是 mmap?要不要关掉?

mmap(内存映射)是让操作系统把模型档对应到内存地址、需要时再读入的加载方式,llama.cpp 预设会使用。官方说明指出关闭它会让加载较慢,但在不使用 mlock 时可能减少分页换出;模型比 RAM 大时关掉 mmap 甚至会完全载不进去。要不要调整请以官方说明为准,一般人维持预设即可。

留言

用 GitHub 账号登录就能留言,留言会公开显示在 GitHub Discussions。留言前请先看社区规则。

本地大模型