1. 显存不是算法参数,而是决定「能不能私有化、能扛多少并发」的硬约束

在大模型工程落地里,这个话题绕不开。很多 Java 同学刚接触时容易只看结论、不究原理,一旦线上出问题就无从下手。先把「为什么重要」说清楚,后面才好理解它怎么用。

很多 Java 同学第一次被问到「7B 模型微调要几张卡」,会下意识回答「模型才 14G,一张 24G 卡够了」。这个回答混淆了两件完全不同的事:推理只需要放下权重,训练还要放下梯度和优化器状态。差多少?整整 8 倍。这个数字一旦算错,需求评审时给出的预算就会错一个数量级,最后要么临时申请机器耽误两周,要么砍需求。

显存之所以值得工程侧专门吃透,是因为它同时锁死了三件事。第一是硬件门槛:能不能在自己的机房私有化,取决于最省的那条路(LoRA)要几张卡,而不是最贵的那条(PPO)。第二是并发上限:线上 OOM 十次有九次不是权重放不下,而是 KV cache 没预留。第三是迭代成本:一次微调的账是「卡时 × 单价 × 调参轮次」,PPO 因为要在线采样和反复调参,实际轮次常常是 SFT 的五到十倍。

这三种误判在项目里反复出现,值得逐条记住。以为「参数小就能单卡训」——忽略了训练态是推理态的 8 倍。以为「LoRA 便宜所以全流程都便宜」——训练成本低不等于推理成本低,adapter 不合并时每层多一次低秩乘,延迟和吞吐都要重新测。以为「强化学习只是多一步」——PPO 是四个模型同时常驻,量级差一个数量级,这不是调参能解决的。

所以这一篇不讲「怎么调超参」,只讲一件事:给定业务目标和预算,怎么在动手之前把这笔账算清楚,并且把算账这件事变成 Java 侧可执行、可审计的代码。

图 17-1 显存与成本:全参 vs LoRA vs 强化学习——全局视角

图 17-1 显存与成本:全参 vs LoRA vs 强化学习——全局视角

2. 显存账本的四个格子:权重、梯度、优化器状态、激活

先用一句话给概念下定义,再划清它的能力边界——什么它能做、什么它做不了。边界感比死记公式更重要。

显存账本只有四个格子,把这四个格子填对,估算误差不会超过 20%。

第一个格子是权重。混合精度训练时参数以 bf16/fp16 存放,每个参数 2 字节,所以是 2P。第二个格子是梯度,与参数同精度同尺寸,也是 2P。第三个格子是优化器状态,这是最容易被忽略的一块:Adam 要为每个参数保存 fp32 的主权重副本、一阶动量 m、二阶动量 v,共 12 字节,即 12P。三个格子相加,全参训练就是 16P——这就是「12 到 16 倍」这个经验值的来源,具体落在哪一端取决于优化器(SGD 动量只要 8P,8bit Adam 约 10P)。第四个格子是激活,它与 batch × 序列长度 × 隐藏维度 × 层数成正比,不是一个固定倍数,长上下文场景下能反超前三项之和。

推理态的账本简单得多:只有权重 2P,外加 KV cache。7B 模型 fp16 是 14GB,int8 是 7GB,int4 是 3.5GB。但真正决定线上能扛多少并发的是 KV cache,而它不在「模型多大」这个口径里,这也是容量规划最容易翻车的地方。

LoRA 省钱的原理,是把后两个格子几乎清零。主干冻结,不需要存梯度和优化器状态,只保留前向所需的权重(2P,量化后更低)和激活;可训练的低秩矩阵参数量通常不到主干的 1%,它的梯度和优化器状态可以忽略。所以同样 7B,全参需要 130GB 以上,LoRA 在 24G 卡上压一压也能跑——差的是那两个格子,不是模型本身。

能力边界也要说清楚:显存优化手段(梯度检查点、ZeRO 分片、CPU/NVMe offload)换来的都是「能放下」,代价是时间和通信。梯度检查点用约 30% 的额外计算换 5–10 倍的激活显存;ZeRO-3 把权重也分片,通信量涨约 1.5 倍;offload 到 NVMe 能让显存降七成,但吞吐掉 3–10 倍。它们不能让你用 4 张卡训出 32 张卡的速度,只能让你在小集群上「勉强跑完」。

3. 逐项算账:7B / 13B / 70B 在四种范式下分别要多少卡

它不是黑盒,拆开看就是几个清晰的步骤。下面按执行顺序逐步说明,建议结合你自己的业务场景在脑子里走一遍。

把公式代进真实规模,三档模型的结论差别很大。

7B 全参:16 × 7 = 112GB 只是静态部分,加 20%–40% 激活后是 130–150GB。所以实践下限是 8 张 80G 卡配 ZeRO-2/3 加梯度检查点;4 张卡理论够,但 batch 只能压到 1,训练时间翻倍。13B 全参:16 × 13 = 208GB,静态就要 3 张 80G,考虑激活和并行效率,实践是 8 卡。70B 全参:16 × 70 = 1120GB,理论 14 张 80G,实践要 16–32 卡并强制 ZeRO-3,很多团队在这一档直接放弃全参,改 LoRA 或继续预训练 + 蒸馏。

LoRA 的算法完全不同。7B 主干 bf16 占 14GB,激活在 seq=4k、bs=4 时约 20–40GB,所以 24G 卡必须 bs=1 加梯度检查点,48G 卡才谈得上舒适;13B LoRA 实践要 40–48G。注意这里的瓶颈是激活而不是权重:很多人看到「权重只有 14G」就以为 24G 卡很宽裕,结果 seq 一拉到 8k 就 OOM。

偏好对齐的账要按模型个数乘。DPO 只有 policy(16P,要训)和 reference(2P,只前向),7B 是 126GB,理论 2 张 80G,实践 4 卡。PPO 是 actor(16P)+ critic(16P)+ reference(2P)+ reward(2P)= 36P,7B 静态就 252GB,理论 4 张 80G;但实际还要加在线采样的推理引擎(vLLM/SGLang 会再复制一份权重并占 KV cache),所以实践是 8 卡,70B 级别直接进多机集群。

再看推理侧,这是线上事故的高发区。KV cache 每 token 的字节数约等于 2 × 层数 × 隐藏维度 × 精度字节。7B(32 层、4096 维、fp16)算下来每 token 约 512KB,8k 上下文单请求就是 4GB。并发 32、上下文 8k,KV cache 就要 128GB,而权重只有 14GB——真正的显存杀手从来不是模型,是并发乘上下文。这也解释了为什么「本地单机跑得好好的,一上线就 OOM」。

图 17-2 显存与成本:全参 vs LoRA vs 强化学习——处理流程

图 17-2 显存与成本:全参 vs LoRA vs 强化学习——处理流程

4. Java 工程侧如何落地(含代码示例)

对 Java 工程师来说,最终要落到代码和工程集成上。下面给出可运行的骨架,重点是理解「数据流怎么走、异常怎么兜」。

Java 侧在这条链路上的职责不是跑训练脚本,而是把「要多少卡、要多少钱、能不能放量」变成可计算、可审计、可单测的判定。训练本身交给算法同学或平台(LLaMA-Factory、ms-swift、云厂商微调服务),Java 侧做三件事。

第一件是容量规划。训练态和推理态必须分开算:训练看 16P(或 LoRA 的 2P + 激活),推理看 2P + KV cache。这两套公式经常被混用,混用的后果就是预算错一个数量级。规划结果要落到「几张什么型号的卡、跑多久」这种采购能听懂的口径。

第二件是预算审批。把卡时折算成钱,再折算成单位业务量的成本(每次训练多少钱、每千次调用多少钱)。这一步的价值在于把技术选型翻译成业务语言:业务方听不懂「DPO 比 PPO 省 20 倍显存」,但听得懂「一轮对齐 240 元 vs 5760 元」。成本数字必须带口径——按卡时算而不是按峰值卡数 × 24 小时算,否则会系统性高估三到五倍。

第三件是上线门禁。私有化交付最常见的翻车是「模型能跑,并发一上来就 OOM」。门禁要在发布前做一次预检:按声明的最大并发 × 最大上下文算一遍 KV cache,加上权重和框架开销,超出卡容量就拒绝放量,并要求降并发或开量化。这个检查必须放在流水线里,因为人一定会忘。

下面这段代码把三件事写成了一个类,实际项目里挂在需求评审和发布流水线前面。

图 17-4 显存与成本:全参 vs LoRA vs 强化学习——调用链路

图 17-4 显存与成本:全参 vs LoRA vs 强化学习——调用链路

5. 与相近方案的对比取舍:训练便宜不等于推理便宜

它不是唯一解法,和它容易混淆的还有几个方案。一张表看清区别与取舍,选型时才不踩坑。

「训练便宜」和「推理便宜」是两笔独立的账,混在一起判断是选型最常犯的错。

LoRA 的训练成本确实低一个数量级:7B 全参一轮约 24 卡时,LoRA 约 1.5 卡时,差 16 倍。但推理侧不必然便宜。adapter 不合并时,每层要多算一次低秩乘加,延迟增加 5%–15%,吞吐下降;合并进基座后延迟恢复,但失去了「一份基座挂多套 adapter」的能力。所以正确的用法是分场景:单租户高吞吐,训完合并;多租户、多业务风格,保留 adapter 按请求路由,此时显存是「一份基座 + N 份几十 MB 的 adapter」,而不是 N 份模型——这才是 LoRA 在私有化项目里真正的价值。

什么时候必须全参,也有清楚的判据:要注入大量新知识(领域语料继续预训练)、要扩词表(新语言、大量专有术语)、或者模型容量本身不够(同样的数据,LoRA 学不进去而全参能学进去)。除此之外,全参换来的是更高的过拟合风险和更贵的回滚成本,收益往往抵不上。

偏好对齐这一档,成本视角比效果视角更能决定选型。PPO 贵的不只是显存:四模型常驻、在线采样、奖励模型要长期维护和迭代、调参周期以周计。它值得的场景很窄——你有可验证奖励(代码能否跑通、检索是否命中、工具调用是否成功),且团队能长期养一个 RM。否则 DPO 用 1/10 的成本拿到 80% 的收益,是更理性的选择。

最后一条最容易被跳过:什么时候根本不该动权重。知识时效性强(政策、价格、库存)走 RAG;任务能被确定性规则覆盖(金额计算、权限判断)写代码。这两种情况硬上微调,得到的是一个「看起来懂了但会算错」的模型,比直接报错更危险,因为错误不再可预测。

图 17-3 显存与成本:全参 vs LoRA vs 强化学习——方案对比

图 17-3 显存与成本:全参 vs LoRA vs 强化学习——方案对比

关键参数与调优

参数 / 系数作用建议起点调错的表现
BYTES_PER_PARAM权重精度换算基数bf16/fp16 取 2按 4 字节算→显存高估一倍
优化器倍数全参静态显存主项Adam fp32 取 12;8bit 取 6漏算→按 2P 备卡,训练必 OOM
actRatio(激活占比)静态显存的加成seq≤2k 取 0.2;8k 取 0.5 以上固定按 0.2 估→长上下文必备不足
nodeSize(节点卡数)卡数向上对齐8不对齐→跨机通信吃掉并行收益
quantFactor(量化系数)推理权重占比fp16=1;int8=0.5;int4=0.25量化后不重测→格式通过率下滑
ctxTokens(最大上下文)KV cache 乘数取 P99 而非平均值按均值估→真实长文档一贴就崩
concurrencyKV cache 另一乘数按压测峰值 × 1.5按均值估→线上并发一高就 OOM
gpuHourPrice成本折算单价按实际卡型/合同价回填用挂牌价→预算高估 2–3 倍
预留余量防碎片与突发单卡容量 × 90%顶满用→显存碎片导致随机 OOM

6. 生产环境踩坑与面试高频点

真正用过的人,踩过的坑都差不多。这里把最常见的几个和对应的面试追问列出来,提前避坑、也方便复盘。

这一节的坑,几乎都能用「算一遍账」提前发现。

最典型的是把推理显存当训练显存。有人用 24G 卡试跑 7B 推理成功,就认为同一张卡能微调,实际全参要 130GB 以上,LoRA 也要看序列长度。第二类是长上下文引发的突然 OOM:seq 从 2k 拉到 8k,激活涨约 4 倍,昨天能跑的配置今天直接挂,正确做法是先开梯度检查点再调 batch。第三类是 PPO 的隐性副本:算显存时只算了四个模型,忘了采样引擎自己还要一份权重和 KV cache,实测常超预算 30% 以上。

推理侧的坑更隐蔽。线上 OOM 很少是权重问题,都是 KV cache 没按「并发 × 最大上下文」预留;很多框架默认按平均上下文分配,压测时用短 prompt 一切正常,真实用户一贴长文档就崩。另外量化不是免费的:int4 权重能省 4 倍显存,但精度损失在结构化输出(JSON 字段、金额、编号)上最先暴露,量化后必须重跑格式通过率。

成本侧还有两个管理坑。一是按峰值卡数 × 24 小时估预算,实际按卡时计费,会高估三到五倍,导致项目一开始就被否。二是训练完不释放实例,闲置卡持续计费,这在云上是最常见的浪费。

面试问这块,重点不在背倍数,而在「你能不能把业务需求翻译成卡数和钱」。下面四组是高频问法。

踩坑清单

现象根因修法
24G 卡试跑 7B 推理成功,微调就 OOM混淆推理态 2P 与训练态 16P按范式套倍数,全参按 130GB 以上备卡
昨天能训,今天把 seq 拉到 8k 就挂激活随序列长度近似线性(注意力项更快)增长先开梯度检查点,再降 batch,最后才加卡
PPO 预算超 30% 以上只算四个模型,漏了采样引擎的权重与 KV 副本预算里显式加一份推理副本与 KV 预留
压测正常,真实用户一贴长文档就 OOMKV cache 按平均上下文分配按 P99 上下文 × 峰值并发预留
int4 量化后 JSON 字段频繁错结构化输出对数值精度最敏感量化后重跑格式通过率,不达标回 int8
ZeRO-3 开了吞吐反而掉一半通信瓶颈:无 NVLink/IB 或 bucket 过小检查互联拓扑,调 bucket size,必要时退 ZeRO-2
8bit Adam 训完效果明显变差优化器状态精度损失累积敏感任务回 fp32 优化器,或只对 embedding 降精度
预算按峰值卡数 × 24h 估,被业务否掉实际按卡时计费用卡时 × 单价,训练结束立即释放实例
LoRA 合并后效果掉一截merge 精度或 adapter 遗漏合并后跑同一套评测集回归,退化超 1pt 不放量

面试追问与答法

Q:7B 模型全参微调到底要多少显存?

A:静态就要 112GB(16 × 7B),加激活后 130–150GB,所以实践下限是 8 张 80G 卡配 ZeRO-2/3 加梯度检查点。记住这个倍数关系比记绝对值有用:全参 = 16 倍参数量,推理 = 2 倍,差 8 倍。凡是听到「模型才 14G,一张卡够了」的说法,先问一句「你说的是推理还是训练」。

Q:为什么 LoRA 训练便宜,推理却不一定便宜?

A:训练便宜是因为不存梯度与优化器状态,静态显存从 16P 掉到 2P。推理不便宜有两个原因:adapter 不合并时每层多一次低秩乘加,延迟增加 5%–15%;合并进基座后又失去「一份基座挂多套 adapter」的多租户能力。所以选型要分场景:单租户高吞吐就合并;多业务多风格就保留 adapter 按请求路由,此时显存是「一份基座 + N 份几十 MB 的 adapter」。

Q:线上推理显存怎么估?为什么说权重只占一小部分?

A:分三块:权重(2P,量化后 0.5P–1P)、KV cache(每 token ≈ 2 × 层数 × 隐藏维度 × 精度字节)、框架开销(图捕获、临时张量,留 1–2GB)。7B 模型权重 14GB,但 8k 上下文单请求 KV 就 4GB,并发 32 就是 128GB——权重反而成了小头。结论是先算 KV 再算权重,且上下文要取 P99 而不是均值。

Q:什么情况下值得上 PPO,而不是 DPO?

A:只看两点:奖励是否可验证、团队能否长期养 RM。有可验证信号(代码能否跑通、检索是否命中、工具调用是否成功),PPO 的在线探索能拿到 DPO 拿不到的收益;没有的话,PPO 的成本是 DPO 的二十倍左右(7B 一轮 5760 元 vs 240 元,还不算调参周期),收益却很难证明。工程判断很简单:如果说不清「RM 下个季度谁来迭代」,就别上 PPO。

/**
 * Java 程序员第 50 阶段17:显存与成本:全参 vs LoRA vs 强化学习
 * Java 侧不跑训练,但要回答三个问题:
 *   1) 这条路线要几张什么卡(训练态 / 推理态分开算)
 *   2) 一轮要多少钱,折到每千次调用是多少
 *   3) 按声明的并发与上下文,上线会不会 OOM
 * 全部写成可单测的纯函数,挂在需求评审与发布流水线前面。
 */
public final class CapacityPlanner {

    /** 显存按 GB 计,价格按「元 / 卡 / 小时」计;数值一律外部配置,禁止硬编码进业务代码 */
    public record ModelSpec(long params, int layers, int hidden,
                            int gpuMemGb, double gpuHourPrice) { }

    public enum Paradigm { LORA, FULL_SFT, DPO, RLHF_PPO }

    private static final double BYTES_PER_PARAM = 2.0;   // bf16 / fp16
    private static final double GB = 1024.0 * 1024 * 1024;

    /** 静态显存倍数:权重 + 梯度 + 优化器状态(Adam fp32 为 12 字节) */
    static double multiplier(Paradigm p) {
        return switch (p) {
            case LORA -> 2.0;        // 主干只前向,adapter 参数量可忽略
            case FULL_SFT -> 16.0;   // 2 权重 + 2 梯度 + 12 优化器
            case DPO -> 18.0;        // policy 16 + reference 2
            case RLHF_PPO -> 36.0;   // actor 16 + critic 16 + ref 2 + rm 2
        };
    }

    /** 训练显存:静态 + 激活;actRatio 由 seq×batch 决定,长上下文必须上调 */
    public double trainMemGb(ModelSpec m, Paradigm p, double actRatio) {
        double staticGb = m.params() * BYTES_PER_PARAM * multiplier(p) / GB;
        return staticGb * (1 + actRatio);
    }

    /** 卡数:向上取整到 8 卡节点,避免跨机通信把收益吃掉 */
    public int gpusNeeded(ModelSpec m, Paradigm p, double actRatio, int nodeSize) {
        double need = trainMemGb(m, p, actRatio);
        int raw = (int) Math.ceil(need / m.gpuMemGb());
        return Math.max(1, (int) Math.ceil(raw / (double) nodeSize) * nodeSize);
    }

    /** 推理 KV cache:每 token 约 2 × layers × hidden × 精度字节,并发与上下文是乘数 */
    public double kvCacheGb(ModelSpec m, int concurrency, int ctxTokens) {
        double perToken = 2.0 * m.layers() * m.hidden() * BYTES_PER_PARAM;
        return perToken * ctxTokens * concurrency / GB;
    }

    /** 推理总显存:权重 + KV + 框架开销(图捕获、临时张量,经验留 1.5GB) */
    public double inferMemGb(ModelSpec m, int concurrency, int ctxTokens, double quantFactor) {
        double weightGb = m.params() * BYTES_PER_PARAM * quantFactor / GB;
        return weightGb + kvCacheGb(m, concurrency, ctxTokens) + 1.5;
    }

    /** 成本:按卡时计,训练完必须释放,不能按峰值卡数 × 24h 估 */
    public double trainCostYuan(ModelSpec m, Paradigm p, double actRatio, int nodeSize, double hours) {
        return gpusNeeded(m, p, actRatio, nodeSize) * m.gpuHourPrice() * hours;
    }

    /** 单位成本:把预算翻译成业务方听得懂的口径 */
    public double costPer1kCalls(ModelSpec m, double qps, int ctxTokens, double gpuHourPrice) {
        double perSec = Math.max(qps, 1e-6);                 // 吞吐按实测回填,禁止猜测
        double callsPerHour = perSec * 3600;
        return gpuHourPrice * 1000.0 / callsPerHour;
    }

    /** 上线门禁:声明并发下放不下就直接拒绝,不允许「先上再看」 */
    public void preflight(ModelSpec m, int concurrency, int ctxTokens, double quantFactor) {
        double need = inferMemGb(m, concurrency, ctxTokens, quantFactor);
        if (need > m.gpuMemGb() * 0.9) {                     // 留 10% 余量给碎片与突发
            throw new IllegalStateException(
                    "并发 %d / 上下文 %d 需 %.1fGB,超单卡 %.0fGB 的 90%%:降并发、开量化或扩卡"
                            .formatted(concurrency, ctxTokens, need, (double) m.gpuMemGb()));
        }
        log.info("preflight ok need={}GB concurrency={}", need, concurrency);
    }
}
路线训练态显存倍数7B 实践卡数一轮卡时 / 成本省在哪代价
LoRA≈2P + 激活1×24G(短序列)≈1.5 卡时 / 约 23 元不存梯度与优化器状态复杂推理学不动;推理可能变慢
全参 SFT16P + 激活8×80G≈24 卡时 / 约 360 元容量足、能注入新知识过拟合风险高、回滚成本贵
DPO18P + 激活4×80G≈16 卡时 / 约 240 元无 RM、无在线采样离线无探索;偏好对噪声被放大
RLHF-PPO36P + 采样副本8×80G(7B)≈384 卡时 / 约 5760 元可用可验证奖励、能持续探索四模型常驻、RM 要长期维护
推理 fp162P + KV1×24G(7B 短上下文)—精度最好KV 随并发线性涨
推理 int40.5P + KV1×16G(7B)—权重省 4 倍结构化输出精度先崩
Logo

更多推荐