[控场AI]
· 8 分钟阅读· 4,317 字

4GB显卡从零训练语言模型:失败比成功更有价值

4GB显卡从零训练语言模型:失败比成功更有价值

用4GB显存从零训练2200万参数模型,用一连串失败揭示预训练模型掩盖的底层机制。

作者用一台搭载4GB显存GTX 1650的游戏本,从随机权重出发训练了一个2200万参数的语言模型,语料来自维基百科与技术文档。实验并非为了产出可用产品,而是为了亲历那些被预训练模型封装掉的"脏活":数据清洗的决策如何塑造后期行为、验证损失的局限(它无法暴露贪婪解码下的生成死循环)、CUDA崩溃时检查点如何保住整个训练过程,以及按语料来源分拆评估比单一聚合损失能提出更有价值的问题。最终模型学会了技术文档"长什么样",却无法写出一个能运行的斐波那契函数——正因为它糟糕,才把容量、数据配比和"预测文本"与"真正理解"之间的鸿沟暴露得一览无余。

一块4GB显存的笔记本显卡、一个小规模语料库,以及一连串极具教学价值的翻车现场。

有人用一台只有4GB显存的游戏本,从随机权重开始训练了一个2200万参数的语言模型。它学会了模仿技术文档的样子,却陷入了不断重复片段的死循环,始终没能写出一个能运行的斐波那契函数。训练接近尾声时,CUDA还崩溃了一次。最终的产物算不上一个有用的聊天机器人——但正是那些失败,构成了整件事最有价值的部分。

通过 Ollama 拉取一个预训练模型,会让现代 AI 显得异常简单。而从零开始训练哪怕一个极小的模型,这种错觉会迅速瓦解。

4GB显卡从零训练语言模型

为什么要去看“推理之前”发生了什么

调用 API 和加载本地预训练模型固然方便,但它们恰恰隐藏了作者最想看清的东西:数据集究竟长什么样?清洗到底有多重要?一份文档是如何变成 token 的?检查点(checkpoint)本质上是什么?loss 告诉了我们什么,又悄悄隐瞒了什么?在模型变得可用之前,它的生成结果是什么模样?

这些问题无法通过调用一个“已经能跑”的东西来回答。作者的思路很直接:动手造一个不那么光鲜的版本,然后亲眼看着它出错。这是一种带着研究目的的“故意不走捷径”,而不是产品开发。

4GB显存:约束反而是最好的老师

实验用的是一台联想 IdeaPad 游戏本——Intel i7-10750H、16GB 内存、搭载 4GB 显存的 GTX 1650,在 WSL2 下运行 PyTorch 和 CUDA。作者坦言这并不是一台适合训练的机器,也从未把它包装成这样。

但约束本身就是重点。显存充裕的大显卡能把许多草率的决策藏在冗余的空间背后;而只有 4GB 时,你必须想清楚为什么上下文长度是这个数值、为什么批次要拆成微批次(microbatch)、优化器状态到底占用多少内存。

作者最终采用了 256 的上下文长度、微批次为 16,并配合梯度累积,让每次参数更新仍能看到一个相对较大的有效批次。主训练过程的峰值内存为 1.54 GiB,远低于显卡上限。作者的解读很克制:这不是显卡慷慨,而是从一开始就围绕限制去设计整条流水线的结果,而不是中途才发现瓶颈。

梯度累积(gradient accumulation)是在显存受限时扩大有效批次的核心技巧:将一个大批次拆成若干微批次(microbatch)依次前向传播,每次只累加梯度而不更新参数,待累积到目标步数后再统一执行一次参数更新。这样模型看到的有效样本量等同于大批次,但峰值显存只需承载单个微批次的激活值。混合精度训练(mixed precision)则是另一个关键的内存优化手段:前向传播和梯度计算使用FP16(半精度浮点),仅在参数更新时保留FP32主权重副本,理论上可将显存占用减少约一半,但需要梯度缩放器(GradScaler)防止FP16下溢导致梯度消失——这也正是CUDA报"黏性错误"后需要连同缩放器状态一并恢复的原因。

语料库:下载是容易的,筛选才是功夫

语料来自西班牙语和英语维基百科、Python 文档,以及若干技术文档集。收集文本很简单,谁都能下载一份数据转储。真正关键的,是决定什么能进入训练集,以及这个决定会在后续带来什么后果。

作者选择了一个受控的语料库,而非庞大但未经过滤的网络数据集,并从质量、授权、语言平衡、重复风险和预处理难度等维度来评判每个来源。清洗与过滤正是语料从“我能下载到的一切”转变为“我有意构建的东西”的分界点。

AI.001实验工作流

整条工作流清晰可见:选择源文本 → 清洗过滤 → 分词 → 从随机权重开始训练 → 保存可恢复的检查点 → 在留出文本上评估并检查生成输出。作者特别强调,这些早期的选择最终会在评估阶段以可指认的具体行为重新浮现出来。

下降的loss、崩溃的CUDA,以及救命的检查点

验证损失从 9.48 起步。在词表大小为 12,000 的情况下,均匀随机猜测下一个 token 的损失约为 9.39。经过大约一轮完整语料遍历后,模型降到了 4.27。这是实实在在的进步,看着也令人满足——但它只回答了一个很窄的问题:在留出文本上的预测是否改善;它并没有回答模型能否生成任何有用的东西。

检查点的价值也在此时从抽象概念变成了现实。作者在正式长训练前先用短训练测试过断点续训,恢复后打印出了期望的结果:

Resumed from: results/EXP-001/resume-test/latest.pt
Resume step: 10

事实证明这极为明智。在主训练跑到第 3,540 步后不久,CUDA 报了一个未知的黏性错误,训练中断。这并不是显存溢出,loss 当时也一切正常。作者从第 3,500 步的检查点恢复——连同优化器、混合精度缩放器和随机状态一并还原——顺利跑到了终点。

更具启发性的意外来自生成环节。使用采样时,后期的检查点能生成看起来像技术文档的片段;而一个早期检查点则把 WordPress、MDN 和 Docker 的内容杂乱地混在一起。一旦切换到贪婪解码(永远选择概率最高的下一个 token),最终模型常常陷入循环——而验证损失完全没有暴露这种失败模式。原因在于:生成时模型必须基于自己的输出继续,而不是基于评估用的真实文本。几个提示词的结果如下:

  • “A WordPress plugin can”(采样,temperature 0.8,最终检查点) → “A WordPress plugin can be used when the plugin is loaded”“A plugin contains a PHP file”
  • “Barcelona es una ciudad”(贪婪解码) → 反复输出 “de la ciudad” 的各种变体
  • “A language model learns”(贪婪解码) → “the language is the language language…”
  • “def fibonacci(n):” → 所有测试过的检查点都没能生成有效实现

验证损失(validation loss)是衡量模型在未见过的文本上预测下一个token能力的指标,通常用交叉熵表示。词表大小为12,000时,随机猜测的期望损失约为ln(12000)≈9.39,因此从9.48起步意味着模型初始状态接近纯随机。损失降至4.27对应的困惑度(perplexity)约为71,即模型每步平均在71个候选token中做选择——这表明模型确实从语料中提取到了统计规律。然而验证损失是在"教师强制"(teacher forcing)模式下计算的:每一步都用真实token作为上下文输入,而非模型自身的上一步输出。这与推理时的自回归生成截然不同——生成时一旦某步预测偏差,错误会随上下文累积放大,最终导致循环或语义漂移,而这种误差传播在损失曲线上完全不可见。

评估比训练本身更有意思

这些生成样本比单看 loss 曲线更能说明问题。于是作者没有只依赖一个聚合分数,而是按语料组成分别评估最终检查点。结果显示:技术文档的损失低于一般散文,但不同来源之间差距很大——MDN 和 WordPress 的文本对这个模型来说比 Docker、Python 或 Git 文档更可预测。

作者对这个排序保持了克制的态度:它只是一次训练中某一个检查点的观察,并不能证明模型有多好。但它的价值在于提出了比聚合损失更好的问题——某个来源“容易”,是因为它写得好,还是因为它重复性高?低损失里有多少是模型真的“懂”,又有多少只是格式可预测?不同领域之间是否存在未被察觉的重叠?单凭 loss 无法区分这些。

贪婪解码(greedy decoding)指每步都选择概率最高的token,是最确定性的生成策略,能最直接暴露模型的重复倾向——因为一旦某个高频片段进入上下文,它往往会继续以最高概率强化自身。采样(sampling)结合temperature参数则通过对logits除以temperature值后重新归一化,使概率分布更平坦(temperature>1)或更尖锐(temperature<1),从而在多样性与连贯性之间权衡。temperature 0.8略低于1,意味着分布比原始输出更集中,生成结果更趋向高概率token但仍保有随机性。两种策略在同一模型上产生截然不同表现,印证了评估策略本身是影响观察结论的独立变量。

小模型是残忍地诚实的

一个仅 2200 万参数、只训练了约一轮的模型,无处可藏。它学会了技术文档“长什么样”,远胜于理解它“在说什么”。它能为根本不存在的 API 写出像模像样的标题和参数列表,会在一个段落内从一个领域漂移到另一个领域,并且在任何检查点都写不出能运行的 def fibonacci(n):。

恰恰因为它糟糕,才对学习格外有用。强模型会把自己的内部机制抹平;弱模型则把它们暴露无遗:容量、数据配比、重复倾向,以及“预测文本”与“真正知道事情”之间的鸿沟。这是研究,而非产品开发——作者反复提醒自己要分清这一点。

真正的收获是什么

收获不是一个有竞争力的模型,也不是一个能拿来回答问题的工具,而是一套可复用的研究工作流:可重复的语料处理工具、能暂停和恢复的训练脚本,以及按来源拆分的评估方法。更重要的是,作者学到了一个判断原则——在断定模型“学到了什么”之前,要同时查看留出损失和真实生成结果。这比一个只看起来唬人的演示要有用得多。

下一步往哪走

作者没有承诺要做更大的模型。第一次训练留下的开放问题都围绕着语料选择、更好的评估方法,以及“一次只改一个决定”,以便能清楚地判断每个改动带来了什么。后续计划会纳入更多源代码,并设定一个固定的评估基准,以便与这次实验对比。用作者自己的话说:与其迈出一大步却只能描述它,不如迈出能被真正理解的、受控的一小步。

对于想入门底层 AI 原理的开发者,这个实验传递的核心价值并不在于结果,而在于它把被预训练模型掩盖的所有“脏活累活”重新摊开——数据、分词、检查点、评估的局限,都在一次次翻车中变得可见。

#从零训练语言模型#4GB显存训练#GTX 1650 LLM#小参数语言模型#LLM训练教程#检查点断点续训#模型评估方法#PyTorch CUDA
分享:

相关推荐