尧图精选

Soup 层流式 NF4 训练实测:在 4 GB 显存上以位级一致标准完成 8B 模型 LoRA 微调

🕒 发布时间:2026/9/17 8:33:41 📁 来源:尧图网络
Soup 层流式 NF4 训练实测在 4 GB 显存上以位级一致标准完成 8B 模型 LoRA 微调【免费下载链接】SoupFine-tune LLMs from one YAML. Layer streaming trains an 8B model on a 4 GB laptop GPU.项目地址: https://gitcode.com/GitHub_Trending/soup12/Soup本文基于benchmarks/gate-v0.72.2-nf4.md这份按原样存档的 Gate 实测记录结合src/soup_cli/下流式训练相关源码展开。它回答一个具体的工程问题当层流式训练从 bf16 升级到 NF4 量化后如何证明「流式加载的权重与常驻显存的权重在数值上完全等价」以及在此过程中 Gate 又发现了哪些真实缺陷。导读Soup项目描述Fine-tune LLMs from one YAML的层流式layer streaming训练技术核心思路是让模型骨架驻留在meta设备上每次前向只把一层解码器的权重从 CPU 内存搬运进一块很小的显存缓冲池从而使峰值显存从「整个模型」缩小到「一层模型」。本文要解析的是该特性的NF4 版本 Gatev0.72.2 槽位一条以 4 GB 显存 RTX 3050 Laptop 显卡为基准硬件的验证链条其验收标准不是「loss 差不多」而是位级一致bit-exact——流式 NF4 与常驻 NF4 持有完全相同的量化字节、运行完全相同的 bitsandbytes 内核因此输出必须逐位相同。读完本文你将掌握NF4 离线分片如何保证可复现性、Params4bit视图重建与QuantState组装的技术原理、为什么参考标准必须是「常驻 NF4」而非「常驻 bf16」、Gate 如何捕获到已发布的 v0.72.0 适配器不可加载缺陷以及 TFLOPS 回验中「GPU 时钟状态」这一方法论陷阱。1. 验证背景与硬件前提1.1 一次「边开发边记录」的 Gate而非事后报告文档开头就声明了这份记录的性质这是特性开发过程中逐条写下的工作记录包含了失败的尝试、被纠正的假设和被废弃的数字而非事后整理的验收报告。它是论文《Exact Layer Streaming: LoRA Fine-Tuning of an 8B Model on a 4 GB Laptop GPU》的证据材料。文件名的历史也值得注意本文件刻意保留为v0721-*命名因为 Gate 运行时 NF4 还处于 v0.72.1 版本。这些 Gate 暴露了一个已发布的 v0.72.0 正确性缺陷适配器以.inner.键前缀保存后重载为零张量该缺陷被拆分出来单独以 v0.72.1 发布NF4 随后才进入 v0.72.2 槽位。因此这些结果「原样成立、无需重跑」。1.2 全部数字的硬件边界所有数字均在同一台机器上测得不做任何外推项目数值操作系统Windows 11GPURTX 3050 Laptop 4 GBCC 8.6可用 4.29 GB内存16.9 GB 主机 RAMNVMe软件栈torch 2.5.1cu121 · bitsandbytes 0.49.2 · transformers 4.57.6 · peft 0.18.1 · trl 0.19.1 · accelerate 1.12.0 · Python 3.10.8单位约定全篇使用十进制 GB与 v0.72.0 Gate 文档一致1.3 为什么参考标准必须是「常驻 NF4」Gate 的参考对象不是常驻 bf16而是常驻 NF4。原因很关键若以 bf16 为参考两者的差异中会混入量化误差从而把真实的 bug 隐藏在噪声里。而流式 NF4 与常驻 NF4 持有相同的量化字节、运行相同的 bitsandbytes 内核因此验收标准可以并且必须是位级一致bit-exactness。这一点在源码中得到印证_setup_streaming_transformers中quant QUANT_NF4 if tcfg.quantization 4bit else QUANT_NONE见 stream_setup.py且double_quant标志必须被同时传给分片器与骨架构建否则「流式与常驻位级一致」的声明就会失效对应 issue #321。2. GATE 1 — 正确性验证6/6 全过2.1 被测构造GATE 1 使用一次性 spikescratchpad/v0721_gate1.py未触及src/下任何代码模型为HuggingFaceTB/SmolLM2-135Mllama 架构30 层tied embeddings采用NF4 double quant bf16 compute——即仓库quant_menu的默认配置LoRA r16 作用于q_proj, v_projseq 512batch 1双缓冲n2并配有专用预取流。被测构造的四个关键环节每一环都能在今天的源码中找到对应实现检查点离线预量化在 GPU 上一次一个张量地量化写入按层分片的uint8打包 absmaxdouble quant 下再附加嵌套absmax/offset。对应 layer_shard.py 的shard_checkpoint与_quantize_nf4——后者调用bitsandbytes.functional.quantize_4bit将 packed 字节与absmax、嵌套absmax/offset作为 sidecar 落盘。Params4bit视图重建每次调用都在缓冲池上重建Params4bit视图并用流式张量 两个共享常量码表重新组装QuantState。对应 layer_stream_runtime.py 的rebuild_quant_state与rebuild_params4bit——bnb_quantizedTrue正是阻止Params4bit再次尝试量化的关键。meta骨架通过init_empty_weightsreplace_with_bnb_linear构建骨架270 个解码器权重张量从未离开meta设备。functional_call注入把重建的Params4bit喂给未修改的 HF 层checkpoint(use_reentrantFalse)包裹层体基座requires_gradFalse不做detach()。对应StreamedDecoderLayer.forward中的checkpoint(self._body, ...)调用见 layer_stream_runtime.py。2.2 六项检查与实测结果#检查项阈值实测结论0离线分片字节 vs 常驻模型的Params4bit完全相同IDENTICAL第 0 层 7 个权重packed absmax 嵌套 absmaxPASS1流式 vs 常驻 NF4 logits 最大绝对差 1e-30.0位级一致PASS2第 0 层 LoRA 梯度! 04.921e-0130/30 层非零PASS3流式 vs 常驻 25 步 loss 曲线噪声范围内最大相对差 0.0两侧同为 12.99325 → 12.09359PASS4相同种子两次运行相同 loss0.0PASS5缓冲数量不变性 n2 vs n3相同0.0PASSRAM store 仅占用 0.055 GB 固定内存缓冲池 3.7 MB × 2。2.3 P3 问题已解决NF4 权重不能字节拷贝Params4bit携带quant_state并在转移到 CUDA 时自行量化因此 NF4 权重无法被直接字节拷贝进普通缓冲这就是计划中的 P3 问题。实测结论scratchpad/probe_nf4_api.pyquantize_4bit跨重复、跨 CPU 往返确定且字节相同——离线分片能精确复现常驻加载会产生的字节检查 0 端到端证实了这一点在已有打包缓冲上、以显式quant_statebnb_quantizedTrue重建的Params4bit前向位级相同torch.func.functional_call接受它包括跨不同 shape 的meta占位符——v0.72.0 的替换机制原样沿用梯度在权重requires_gradFalse的情况下通过matmul_4bit流向层输入——计划 P2 成立。2.4 每权重流式字节数double quant 的代价double quant 下每权重流式字节为packedN/2 absmaxN/64 嵌套 absmaxN/4096 4 字节 offset ≈0.516 字节/参数。NF4 码表16 个 fp32与嵌套码表256 个 fp32经逐权重验证恒定因此共享一份常驻副本是安全的——分片器对此进行断言而非假设。这一断言在源码中即 layer_shard.py 的_CodeTables类任何权重若给出不同的码表直接抛错拒绝因为「每权重一份码表会在解量化时静默使用错误常量」。2.5 Gate 捕获了什么绿色通过反而发现不了的三个问题发现 1本 Gate 最重要的发现PEFT 静默使用了不同的 LoRA 数学路径。在适配器字节相同、权重字节相同的前提下流式与常驻的 logits 竟相差9.375e-01。根因transformers.from_pretrained会在模型上盖上is_loaded_in_4bit标记PEFT 读取该标记来分发lora.bnb.Linear4bit而meta骨架没有这个标记PEFT 便分发到通用的lora.layer.Linear——它在Linear4bit基座上仍能运行只是以不同的方式转换与累加。不崩溃、无警告、loss 曲线看起来健康。修复方式把from_pretrained设置的标记重新盖上。发现 2Gate 自身险些失效的两个 harness bug已在取数前修复PEFT 把lora_B初始化为 0因此 step 0 时适配器不贡献任何输出dL/dA结构上为零——任何正确实现都一样。第一次运行因此「位级通过」了检查 1而实际上适配器路径什么都没做检查 2 的「失败」也与流式无关。Gate 现在先随机化lora_B让适配器路径真正承载检查 1 与 3 的验证。两模型之间的适配器同步因下面的键前缀 bug 静默拷贝了0 个张量导致两个模型带着不同适配器被比较。现在 harness 会拒绝运行而不是拿两个初始化不同的模型做比较。3. 阻塞性 rider已发布的 v0.72.0 适配器「保存即不可加载」3.1 缺陷复现通过已发布的build_streamed_model复现scratchpad/check_inner_keys.pyCPUtiny-random-Llamainstall_streaming会把layers[i]替换成一个以名为inner的子模块持有真实层的包装器。于是每个适配器参数在 state-dict 路径中都多出.inner.段而get_peft_model_state_dict→save_pretrained正是把这个名字写进磁盘base_model.model.model.layers.0.inner.self_attn.q_proj.lora_A.weight ^^^^^^把该适配器加载回普通模型时8 个张量全部丢失。PEFT 只发出一条UserWarning提示 missing keys返回的是与未调优基座逐字节相同的模型reload into a plain model: 0/4 lora_B tensors non-zero VERDICT: ADAPTER SILENTLY LOST ON RELOAD3.2 影响范围v0.72.0 中soup train --stream-layers产出的每一个适配器在流式路径之外都是无效的——soup merge、soup serve、soup chat以及PeftModel.from_pretrained都会把它当成空操作加载。训练过程本身是正确的Gate 已证明只有写出的产物不可用。v0.72.0 的 159 个测试中没有任何一个「保存适配器再加载回来」。这一缺陷在今天的源码中留下了清晰的修复痕迹StreamedDecoderLayer.state_dict被重写为在自身前缀处委托给inner.state_dict使流式适配器在序列化上与普通 LoRA 运行无法区分见 layer_stream_runtime.py同时配套一个 load 侧的_register_load_state_dict_pre_hook_redirect_canonical_keys把规范键在加载时重定向回inner.前缀这正是后续--resume得以工作的基础对应 v0.72.3 的 resume 支持。3.3 两个候选修复都已 spike 验证变体保存的键仍含.inner.重载进普通模型已发布 v0.72.0对照8 / 80/4 适配器——丢失A包装器提供透明的state_dict()04/4——往返成功B包装器共享 inner 层的_parameters/_modules04/4——往返成功方案 A 是外科手术式修复只影响序列化方案 B 直接移除嵌套层级让named_parameters()也回归规范形态同时解锁向流式模型内加载即--resume属于 v0.72.2 的待办项。两者都保持前向可用最终选择留待实现时以 TDD 先行、以重载往返为验收规格。4. GATE 2 — 数字8B 头条与 TFLOPS 回验4.1 与 v0.72.0 保持可比的协议协议与 v0.72.0 完全一致以保证行间可比预热 10 步后测 50 步的 tok/s ·torch.cuda.max_memory_allocated()取峰值显存 ·nvidia-smi dmon -s u取 SM 占用率 ·PagedAdamW8bit· batch 1 · 双缓冲 · TFLOPS 以 C6 回验对照本机实测的 6.76 TFLOPS 稠密 bf16 GEMM 上限GA107 理论峰值约 24.5计划中的「9」对这张卡是错的。参数计数从 safetensors 头部读取因此 tiedlm_head不会被重复计数对meta骨架调用model.parameters()会把 Qwen2.5-3B 数成 3.40 B 而非 3.09 B从而虚增 TFLOPS。4.2 核心测量表模型量化存储Stok/sGPU 利用率峰值显存有效 TFLOPSLlama-3.1-8BNF43.60 GB 固定内存512122.598.9%/ 100%3.45 GB5.90Qwen2.5-3BNF41.43 GB固定内存512244.394.5% / 100%1.91 GB4.53Qwen2.5-3Bbf16v0.72.05.55 GB 可分页512143.179.3% / 100%2.15 GB2.654.3 8B 头条Llama-3.1-8B 在 4 GB 显卡上以 122.5 tok/s 微调基座页锁定3.60 GB store低于本机实测的 7.65 GB 固定内存上限峰值显存 3.45 GB——真正落在卡内不是 WDDM 溢出。未 tied 的embed_tokenslm_head保持常驻且为 bf16占去 3.45 GB 中的 2.10 GB这正是 8B 逼近该卡上限的原因把它们当作流式大层处理是 v0.72.3 的待办项。NF4 分片集在磁盘上 5.70 GB只分片一次并缓存。推导算术明确标注为计算所得100 万训练 token 8B 下 2.3 小时3B 下 1.1 小时。4.4 TFLOPS 回验解析错的是「分母」首轮读数为 5.90 TFLOPS v0.72.0 Gate 中 6.76 TFLOPS「可达稠密 bf16 GEMM」的87%——对一个还带着 NF4 反量化的 eager 循环来说这个数字不可信因此被扣下未发布。两种候选解释随后都被测试(b) 梯度检查点关闭C6 常数错误——证伪。流式层在 grad 开启时总是用checkpoint(use_reentrantFalse)包裹层体layer_stream_runtime.py 可见实测循环是前向反向C6 正确。(a) 6.76 上限在未达峰值的 shape 下测得——部分成立但主导因素其实是 GPU 时钟状态。首次重测提示 6.76 只是低估方形 shape 回 7.08 / 7.52 / 7.66另一会话中对相同 shape重测得到6.23 / 6.63 / 6.75——即 6.76 几乎精确复现。随后在同一会话内连续六次 4096³测量稳定在1%6.93, 6.94, 6.93, 6.94, 6.93, 6.94SM 时钟固定在862 MHz / 63 C。结论约 13% 的离散度存在于会话之间且与升压时钟boost clock同步不是测量噪声。6.76 从未出错——它是在低时钟会话中测得的。由此得出比原修正更强的方法论规则一个上限只能与同一会话、同一时钟下测得的吞吐量比较任何「占上限百分比」都必须同时声明其测量时钟。shape 仍然重要但不如时钟重要。同会话 862 MHz、bf16、B1/S512 下、按 shape 匹配的上限算子M × K × NTFLOPSFLOP 占比q_proj / o_proj512 × 4096 × 40967.7115.4%k_proj / v_proj512 × 4096 × 10247.573.8%gate/up_proj512 × 4096 × 143367.4953.8%down_proj512 × 14336 × 40967.6926.9%FLOP 加权7.58较小模型的加权上限更低其 GEMM 过小无法饱和例如 Qwen2.5-3B 为 6.67、1.5B 为 6.39、0.5B 为 5.90。分子约定已固定并声明读者可复现解码器参数按C62 前向 2 重算 2 dL/dx。不是 C8——基座冻结不为其计算 dL/dWC8 多出的正是这一项。embed_tokens贡献0 FLOPs查表操作。lm_head按C42 前向 2 dL/dx它位于被 checkpoint 的解码器层之外前向不会重算且它自身冻结。运行GFLOP/token有效 TFLOPS占其 shape 匹配上限百分比Qwen2.5-0.5B 978.6 tok/s2.692.6345%Qwen2.5-1.5B 525.0 tok/s8.794.6272%Qwen2.5-3B 143.1 tok/s17.892.5638%Llama-3.1-8B NF4 122.5 tok/s43.985.3971%1.5B 行最高完全符合预期它是唯一在 96.8% SM 占用率下测得的运行即计算受限3B bf16 行 38% 则是传输受限的分页 store 运行。四行数据至此自洽。必须随这些百分比一起传播的 caveat吞吐量来自更早的会话、上限来自本会话因此每个比值都继承了约 13% 的跨会话时钟不确定性。在任何占比被当作负载依据发布之前应在同一会话内背靠背重测吞吐量与上限并同时报告 SM 时钟。发布建议tok/s 与峰值显存作为实测值发布它们同样对时钟敏感但它们是实际观测到的TFLOPS 列是派生值引用时必须同时声明常数、参数核算与时钟。NF4 让 3B 行比 bf16 流式快 1.71 倍原因不在算术——而是 1.43 GB 的 store 能落在页锁定上限之内5.55 GB 的不行。页锁定恢复了异步copy_表现为利用率从 79.3% 升至 94.5%。这正是 v0.72.0 诚实性 caveat #1「3B 数字是下界因为 store 走了可分页内存」被 NF4 解除。4.5 常驻 NF4 的 3B 基线测了然后废弃首次尝试34.1 tok/s4 GB 卡上峰值显存 6.07 GB100% 利用率0.63 TFLOPS。4 GB 卡上出现 6.07 GB 峰值是 WDDM 共享主机内存溢出——正是 v0.72.0 废弃 Qwen2.5-1.5B 常驻行的同一失效模式。若发布它就会产生一个基于 Windows 换页而非训练本身的「流式比常驻快 7.2 倍」标题。该数字被废弃而非报告。它也不是公平比较常驻一侧关闭了梯度检查点以 C4 运行流式侧是 C6且持有全部激活——正是这把它推过了显存。harness 现在为常驻吞吐量运行启用检查点重测排在 8B 头条之后。需要在发布说明中声明的后果截至本 Gate本机仍没有有效的 3B 常驻基线——常驻 bf16 OOMv0.72.0常驻 NF4 溢出。诚实的声明仍是 v0.72.0 在 0.5B 处做出的那个而不是一个 3B 加速比。5. v0.72.2 step-6 复现走「已发布代码」重测GATE 2 的数字来自一次性 spike。step-6 用发布形态的shard_checkpoint/build_streamed_model重测协议相同预热 10 步后 50 步、batch 1、S512、PagedAdamW8bit、双缓冲且 GEMM 上限按 Gate 自己确立的规则在同一会话、声明时钟下测得模型量化存储tok/sGPU 利用率峰值显存SM 时钟有效 TFLOPS占同会话上限Llama-3.1-8B-InstructNF43.60 GB 固定119.6100%3.32 GB952 MHz / 70 C5.2668%Qwen2.5-3BNF41.43 GB 固定264.2100%1.76 GB960 MHz / 72 C4.7361%与 spike 的一致性很近8B119.6 vs 122.5 tok/s、3.32 vs 3.45 GB 峰值、固定 store 同为 3.60 GB即发布实现复现了 Gate而不只是形似残余差异落在 Gate 记录的约 13% 跨会话时钟离散内。分片缓存行为正常第二个 8B 运行报告shards ready in 0.0s。3B 的 NF4 vs bf16 对比及其 caveat264.2 tok/sNF4对比 v0.72.0 的 143.1 tok/sbf16是 1.85 倍但原因不是算术——是 1.43 GB 的 store 能页锁定而 5.55 GB 的不能从而恢复了异步copy_、利用率从 79.3% 升到 100%。bf16 行是在更早会话、未记录时钟下测的比值继承了该不确定性承载结论的是机制页锁定不是倍率。推导算术100 万 token 8B 下 2.3 小时3B 下 1.05 小时。step-6 还发现并修复了一处显示问题流式 NF4 模型对 SmolLM2-135M 报告了 878,154,048 个参数真实 134,515,008常驻 NF4 路径报告 134,975,808。PEFT 将Params4bit总量算作numel * 2 * quant_storage.itemsize——对 numel 即打包计数的常驻张量正确但对携带逻辑 shape 的meta占位符不正确。仅影响显示修复前后 loss 曲线字节相同。若不加修复8B 会打印出约 520 亿参数的荒谬值。6. 从这份 Gate 中可以带走的工程方法论位级一致性是量化流式的最高标准当两侧共享量化字节与内核时「数值几乎相同」不再够用0.0最大绝对差才是验收线。正确性 Gate 必须主动制造差异随机化lora_B、拒绝同步失败继续运行否则 Gate 会在「适配器根本没生效」的情况下全绿通过。静默降级比报错更危险PEFT 的is_loaded_in_4bit分发缺失不报错、不警告loss 曲线照常健康——只有逐字节对比 logits 才能抓到。序列化路径是制品质量的生命线一个训练正确的特性其适配器产物若无法被下游工具加载就等同于未交付save/load 往返必须成为测试规格。TFLOPS 必须同会话同时钟比较13% 的「测量误差」其实是升压时钟的会话间漂移任何占比数字都要携带时钟声明。如果你在 4 GB 显存的笔记本上复现本文场景可先阅读 层流式训练文档 与 性能与量化文档再通过soup train --stream-layersquantization: 4bit在 示例配置 基础上验证峰值显存应收敛到「一层模型」的量级而非整个模型。本文全部 Gate 数字对应的复现程序位于 benchmarks/harness/ 下的探针与门禁脚本完整的 Gate 记录可对比阅读同目录下的 gate-v0.72.0-layer-streaming.md 与 gate-v0.72.3-breadth.md。【免费下载链接】SoupFine-tune LLMs from one YAML. Layer streaming trains an 8B model on a 4 GB laptop GPU.项目地址: https://gitcode.com/GitHub_Trending/soup12/Soup创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →