如何从零开始训练你自己的 ChatGPT(完整构建指南)

@Av1dlive
英语3周前 · 2026年6月26日
119K
112
16
81
295

TL;DR

本综合指南将带你了解如何使用 Andrej Karpathy 的 nanochat 从零开始训练个人 LLM,内容涵盖从 GPU 租赁到基于个人聊天记录进行微调的所有步骤。

你不需要数十亿来训练下一个 ChatGPT

你只需要 100 美元和 Andrej Karpathy 的 Nanochat

我上周试用了它,以下是我发现的内容

免责声明:计算的成本预计在未来十年会下降。虽然我的说法有点夸张,但你可以用不到 100 美元得到一个可用的版本。这是一个构建过程,不是夸大其词。我同意目前训练这些 AI 模型的资本支出确实高得离谱,但我相信总有一天我们能够以非常经济实惠的价格训练出优秀的前沿模型。

我花了大约 100 美元和一个周末,从零开始用我自己的笔记、写作和导出的 AI 聊天记录训练了一个 ChatGPT 风格的模型。

现在它可以用我的声音回答问题,并且记住我自己的想法,不需要 API,也不需要租用“大脑”。

这份指南是我希望当初就有的一份指南:每个命令、每个代码改动,以及 对术语的通俗易懂的解释,这样你就不会卡住。

如果你从未训练过模型,你就是目标读者。一步一步来。

先读这个(你要做什么)

你将得到什么: 一个小型 GPT,大约相当于 OpenAI 最初的 GPT-2(2019年)的能力,并针对你个人的数据进行微调,听起来像你,并且知道你知道的东西。你可以通过一个 ChatGPT 风格的网页与它聊天。

诚实的预期: 这不是 GPT-4。它更像“一个拥有你记忆的幼儿园孩子”,在回忆和草稿方面很迷人,有时也会自信地犯错。它的魔力不在于原始的智力,而在于它是 你的,是私密的,并且你理解它的每一部分。

成本: 完整运行需要 大约 48–100 美元 的租用 GPU 时间。你可以 大约花 0 美元 先学习整个流程(下面会详细说明)。

你需要的技能:

  • 能舒适地在终端中输入命令(复制粘贴就可以)。
  • 在数据处理步骤中,基本的 Python 知识会有所帮助,但我会提供可用的脚本。
  • 不需要机器学习背景。 我会在过程中解释相关概念。

时间: 预留一个周末。实际训练大约 3 小时;其余是设置和准备数据。

60 秒心智模型

训练一个聊天机器人分为 两个主要阶段。弄清楚了这两点,其他一切就都明白了。

  1. 预训练 → 产生 基础模型。模型阅读海量的互联网文本,学习一个技能:预测下一个词。它在这里学习语法、事实和推理。这一步成本高昂(这就是大约 3 小时的 GPU 时间)。结果模型说话像互联网,它可以 完成 文本,但不能 聊天
  2. 微调(SFT) → 产生 聊天模型。你给基础模型展示成千上万个示例对话,让它学会像助手一样回答问题。这一步便宜且快速(几分钟)。这是你的个人数据发挥作用的地方。

🔑

类比:

预训练是送模型去学校学习语言和世界知识。微调是学徒期,它学习一个特定的工作,在我们的情况中,就是

成为你的第二大脑

还有可选的第三阶段,RL(强化学习),可以增强数学等特定技能。我们将在主要构建中跳过它,并在最后提及。

你会经常看到的词(快速浏览)

你不需要记住这些,底部有完整的词汇表,但先了解一下主要的:

  • GPU: 做数学运算的专用芯片。训练需要强大的 GPU(如 H100)。你可以按小时租用它们。
  • Token: 文本块(一个词或词的一部分)。模型读取和写入的是 token,而不是字母。
  • Tokenizer: 将文本切分成 token 的工具。你首先要训练一个。
  • 参数: 模型的“旋钮”(它学习的数字)。参数越多 = 模型越大、越智能、越慢。
  • 深度: nanochat 用于调节模型大小的单一旋钮:层数。深度越大 = 模型越大。
  • 批次 / 批次大小: 模型在更新自己之前看到的样本数量。
  • 检查点: 模型权重在磁盘上的保存副本。
  • 损失: 衡量模型错误程度的数字。训练过程会降低它。

工具:nanochat

我们使用 nanochat,这是 Andrej Karpathy(同时也是 nanoGPT 和许多基础 LLM 教学背后的那个人)的一个开源项目。

它大约有 8,000 行干净、可读的 PyTorch 代码,涵盖了 整个流程: tokenizer、预训练、微调、强化学习、推理以及一个网络聊天界面,设计用于在一台机器上从开始到结束运行。

为什么它非常适合初次构建者:

  • 一个旋钮。 你设置 --depth(层数),它会自动使用缩放法则计算 所有 其他设置:宽度、学习率、批次大小、训练长度。你不需要调整十几个旋钮。
  • 可读。 没有庞大的配置系统或框架魔法。每一步都是一个你可以打开并理解的脚本。
  • 便宜。 达到 GPT-2 级能力只需约 100 美元,而不是 2019 年花费的大约 43,000 美元。

🔑

通俗来说:

“缩放法则”是一些经过充分测试的经验法则,告诉你对于给定大小的模型,需要多少数据和多大的批次才能获得最佳结果。nanochat 内置了这些规则,所以你不需要知道它们。

在花钱之前:先进行一次免费的模拟运行

我强烈建议这样做。在租用任何 GPU 之前,先在本地笔记本上用小模型运行整个流程一次。 这样你可以免费学习所有命令并发现错误。

nanochat 包含一个专门用于此目的的脚本。在 Mac(Apple Silicon)或任何电脑上:

bash
1git clone https://github.com/karpathy/nanochat.git
2cd nanochat
3bash runs/runcpu.sh

这个过程会:

  • 安装所有东西,下载一点数据,训练一个 tokenizer,然后训练一个 depth-6 的玩具模型(非常小)并进行微调。
  • 在笔记本上大约需要 30-60 分钟。这个模型会 很笨: 这是预料之中的,没关系。
  • 这样做是为了让你看到每个阶段都能完整运行,从而熟悉流程。

🔑

为什么要费这个功夫:

初学者浪费钱的头号原因是租了昂贵的 GPU,然后花一个小时摸索设置,而计时器一直在跑。先免费进行摸索。

真正运行之前你需要准备什么

一个简短的清单:

  1. 一个 GPU 云账户。 我用的是 Lambda;RunPod、Vast.ai、Paperspace、AWS 等也可以。你需要一个 8×H100 节点(一台带有 8 块 H100 GPU 的机器)。
  2. 该账户中大约 100 美元的余额。
  3. 你的数据,收集好:导出的 AI 聊天记录、笔记、任何你想让它学习的内容(我们将在第 7 步准备)。
  4. (可选) 一个 Weights & Biases 账户(免费),用于查看漂亮的训练图表。
  5. (可选) 一个 OpenRouter API 密钥 + 几美元余额,用于生成合成的“身份”数据。你可以使用任何 LLM API 替代。

🔑

什么是“8×H100 节点”?

一台租用的计算机(“节点”),内部有 8 块 H100 GPU。nanochat 经过调整,可以将工作分配到全部 8 块 GPU 上,这就是为什么训练只需要大约 3 小时而不是 24 小时。

第 1 步:租用并连接到 GPU 机器

这是让新手感到畏惧的部分。其实只需几次点击和一个命令。

1a. 启动实例。

  • 登录你的 GPU 提供商,启动一个新实例。选择 8×H100 选项(有时标记为“8x H100 SXM”或“H100 80GB ×8”)。
  • 选择一个离你近的区域。在提示时添加你的 SSH 密钥(提供商会引导你完成,这是你的笔记本证明是你自己的方式)。
  • 启动它。你会得到一个 公共 IP 地址,类似于 209.20.xxx.xxx

🔑

什么是 SSH?

一种从你的终端安全地控制另一台计算机的方式。你“SSH 进入”租用的机器,然后像坐在它面前一样输入命令。

1b. 从你的笔记本终端连接:

text
1ssh [email protected] # 使用你实例的用户名 + IP

如果连接成功,你现在正在 租用的机器上 输入命令。从这里开始,所有操作都在那台机器上执行。

1c. 检查 GPU 是否存在:

bash
1nvidia-smi

你应该会看到一个列出 8 块 GPU 的表格。如果你看到 8 块 H100,那就没问题了。

⚠️

花钱的规则:

这台机器只要开着就会按分钟计费(大约每小时 24 美元)。当你用完时,终止 它(而不仅仅是“停止”),否则你会继续付费。更多关于关闭的信息请参见第 12 步。

第 2 步:安装 nanochat

在机器上,获取代码并安装依赖项。

bash
1# 获取代码
2git clone https://github.com/karpathy/nanochat.git
3cd nanochat
4
5# 安装 'uv',一个快速的 Python 包管理器
6curl -LsSf https://astral.sh/uv/install.sh | sh
7
8# 创建一个隔离的 Python 环境并安装所有东西
9uv venv
10uv sync --extra gpu
11
12# '激活'该环境,让 'python' 使用它
13source .venv/bin/activate

用通俗的语言解释刚才发生了什么:

  • git clone 下载 nanochat 代码。
  • uv 是一个工具,它安装 nanochat 所需的精确 Python 库(PyTorch 等)。
  • uv venv 创建一个 虚拟环境,一个仅用于此项目的沙盒化 Python,这样就不会与其他东西冲突。
  • source .venv/bin/activate 将你的终端切换到那个沙盒中。每次你在机器上打开新终端时都需要重新运行这一行。

🔑

--extra gpu vs --extra cpu:

告诉安装程序获取 PyTorch 的 GPU(CUDA)版本。在 H100 机器上,你需要 gpu。在 Mac 上模拟运行,你会使用 cpu。

第 3 步:了解文件存放位置

nanochat 将所有内容写入一个文件夹:~/.cache/nanochat。了解这一点可以避免以后的混乱。

在文件夹内,随着运行过程,你会得到:

  • base_data_climbmix/,已下载的训练文本(parquet 文件)。
  • tokenizer/,你训练好的 tokenizer。
  • base_checkpoints/d26/,保存的基础模型权重。
  • chatsft_checkpoints/d26/,保存的聊天模型权重(你最终与之对话的东西)。
  • report/,一个自动生成的 report.md,总结整个运行过程。

🔑

提示:

d26 只是 nanochat 用于 depth-26 模型的文件夹名称。如果你训练不同深度的模型,文件夹名称会相应改变。

第 4 步:下载训练数据

基础模型从一个大型公共文本数据集 ClimbMix(一个 4000 亿 token 的网络语料库,经过清洗和混洗)中学习。你以 分片 的形式下载它。

bash
1# 先下载 8 个分片(足够训练 tokenizer)
2python -m nanochat.dataset -n 8
3
4# 后台下载约 240 个分片,用于预训练
5python -m nanochat.dataset -n 240 &

解释:

  • -n 8 = 下载 8 个分片。每个分片约 100 MB,约 2.5 亿个字符。
  • 末尾的 & 让第二个命令 在后台 运行,这样你可以在下载约 24GB 数据的同时继续工作。
  • 大约 150 个分片足以达到 GPT-2 级能力;240 个提供了充足的余量。
  • nanochat 会自动 保留最后一个分片作为验证集(模型永远不会训练的数据,用于衡量真实的进展)。

🔑

什么是“分片”?

就是一个编号的文件,包含数据集的一部分。将大型数据集拆分成多个分片,可以让你一部分一部分地下载和流式传输,而不是一次全部加载。

🔑

磁盘空间:

240 个分片 ≈ ~24GB,加上检查点。一台 8×H100 机器通常有充足的磁盘,但如果你看到“no space left”(空间不足),就下载少一些分片。

第 5 步:训练 Tokenizer

在模型能够阅读文本之前,你需要一个 tokenizer 将文本切分成 token。

bash
1python -m scripts.tok_train # 训练 tokenizer(词汇表大小为 32,768 个 token)
2python -m scripts.tok_eval # 显示压缩文本的效率

发生了什么:

  • Tokenizer 从你刚下载的几十亿字符数据中,学习 最常用的 32,768 个文本块(“tokens”)。常见的词成为一个单独 token;罕见的词会被拆分成多个部分。
  • 这只需要几分钟。结果保存在 ~/.cache/nanochat/tokenizer/ 下。
  • tok_eval 只会报告一个“压缩比”(每个 token 包含多少个字符),越高越高效。你不需要对此采取什么行动。

🔑

为什么训练自己的 tokenizer?

对于小型模型来说,定制的小型词汇表比重新使用为大型模型构建的巨大词汇表更高效。

🔑

特殊 token:

Tokenizer 还会保留一些特殊的标记,比如 <|user_start|> 和 <|assistant_start|>。这些标记用于模型后来识别

谁在说话

。你不需要碰它们,只要知道它们存在就可以了。

第 6 步:预训练基础模型(最贵的一步)

这是最重要的一步:大约需要 3 小时,占你大部分成本。你正在创建 基础模型,一个通用的“互联网大脑”,稍后你会对它进行个性化定制。

6a. 首先在 “screen” 会话中启动它

因为运行需要几个小时,所以在 screen 内运行,这样即使你的连接断开,它也能继续运行。

bash
1screen -S train # 打开一个名为 "train" 的持久会话

(如果你的 SSH 断开连接,重新连接后输入 screen -r train 重新连接。要让它保持运行并分离:先按 Ctrl-A,然后按 D。)

🔑

为什么用 screen?

如果你只是正常地运行命令,然后笔记本休眠或 Wi-Fi 信号不好,训练就会中断。screen 可以让它在服务器上不受影响地继续运行。

6b. 运行预训练

bash
1torchrun --standalone --nproc_per_node=8 -m scripts.base_train -- \
2 --depth=26 \
3 --target-param-data-ratio=8 \
4 --device-batch-size=16 \
5 --fp8 \
6 --run=base_d26

我们来逐部分解码这个命令,因为初学者经常在这里卡住:

  • torchrun: 同时在多个 GPU 上启动训练。
  • --nproc_per_node=8: 使用 8 块 GPU(每块 GPU 一个进程)。在单 GPU 机器上,你将其设置为 1,或者完全跳过 torchrun(见下文)。
  • --standalone: 全部在一台机器上(而不是集群)。
  • -m scripts.base_train: 运行 nanochat 的预训练脚本。
  • --: 一个分隔符。之前 的所有内容配置 torchrun;之后 的所有内容配置训练脚本。忘记这个 -- 是一个常见错误。
  • --depth=26: 唯一的旋钮:一个 26 层模型(GPT-2 级)。其他所有内容(宽度、学习率、批次大小、训练长度)都会从这一设置自动推导出来。
  • --target-param-data-ratio=8: 训练的时长。8 表示“每个参数训练 8 个 token”,这会让 d26 模型 稍微欠训练,从而正好有效地达到 GPT-2 级能力。
  • --device-batch-size=16: 每个 GPU 同时处理的序列数量。如果你遇到 GPU 内存不足(“OOM”错误),降低这个值 到 8,然后 4 等(2的幂次)。
  • --fp8: 使用超快速的 8 位数学运算(H100 支持)。如果你的 GPU 不支持,只需要移除这个标志;它会使用稍慢的 16 位数学运算,仍然可以工作。
  • --run=base_d26: 这次运行的名称(如果你设置了 Weights & Biases,会用到它)。保留这个标志;或者为了启用图表,先运行 wandb login

🔑

只有一块 GPU 而不是 8 块?

运行 python -m scripts.base_train --depth=26 --device-batch-size=16(没有 torchrun,没有 --)。nanochat 会自动通过“梯度累积”进行补偿。它会得到相同的结果,但需要大约 8 倍的时间。

🔑

什么是“OOM”?

“内存不足”,GPU 没有空间了。解决方法几乎总是降低 --device-batch-size。数学计算仍然能工作,因为 nanochat 会在后台保持

有效

批次大小不变。

6c. 运行时你会看到什么

  • step X/Y: 进度(当前步数 / 总步数)。
  • loss: 模型的错误程度。它应该 随时间下降(从大约 10 开始,趋向于大约 3)。下降 = 学习。
  • ETA / time: 完成还需要多长时间。

如果你设置了 Weights & Biases,打开它的网页查看两个关键图表:

  • val_bpb: 验证损失,以“每字节比特数”计算。数值越低越好。清晰的进度信号。
  • core_metric: CORE 分数,一项行业基准。你的目标:超越 0.256525,这是 GPT-2 的分数。跨过这条线,你就匹配了一个曾在 2019 年花费 43,000 美元的模型。

6d. 完成后,给它评分

bash
1torchrun --standalone --nproc_per_node=8 -m scripts.base_eval -- --device-batch-size=16

这会打印最终的 CORE 分数。如果它高于 0.256525,那么你的基础大脑就达到了 GPT-2 级别。🎉

🔑

如果损失上升或变成“NaN”怎么办?

“NaN”意味着数学运算出错了(很罕见)。通常是由于设置过高;对于第一次运行,只需使用上面的确切命令,这是已知的可靠命令。如果发生这种情况,重新启动这一步。

第 7 步:准备你的数据(使其成为 的部分)

这是实际工作中 90% 的部分。模型有多像“你”,完全取决于你喂给它的数据。基础模型给它语言能力;

你的数据给了它你。

你要把你的个人资料转换成 nanochat 微调所使用的格式:对话,保存为一个 .jsonl 文件(每行一个对话)。

我使用了三个数据源,每个教会模型不同的东西:

  1. 导出的 AI 聊天历史记录(ChatGPT + Claude),教 你问的问题类型 和你喜欢的回答风格。这已经是对话形式,所以最容易处理。
  2. 你的写作(笔记、博客文章、日记),教 你的知识和声音
  3. 你的简历/身份信息: 教模型 它是谁 以及 它承载着谁的大脑

🔑

为什么需要全部三种?

每种解决了不同的失败情况。跳过聊天历史,它不知道你怎么说话;跳过笔记,它不知道你知道什么;跳过身份信息,它没有自我意识。

nanochat 期望的精确格式

nanochat 有一个内置的加载器叫做 CustomJSON。它的规则是:一个 .jsonl 文件,其中 每一行是一个对话,写成 JSON 消息列表,并且 严格交替:用户、助手、用户、助手……

一行看起来像这样:

markdown
1[{"role":"user","content":"我关于优秀软件的核心论点是什么?"},{"role":"assistant","content":"我的核心论点是,优秀的工具有强烈的观点和很少的设置..."}]

🔑

什么是 JSONL?

“JSON Lines”,一种文本文件,每行是一个独立的有效的 JSON 对象。生成和追加内容都很容易。角色必须以 user 开头,并且严格交替。

数据卫生(一定要做:这很重要)

小型模型在这个规模下会 记忆,所以先清理你的数据:

  • 去重 几乎相同的笔记(它们会导致过拟合)。
  • 清除秘密和别人的私人信息: 密码、API 密钥、任何你不希望模型脱口而出的东西。它 记住它们。
  • 保留大约 50 个你自己的问答 作为私有测试集,不用于训练,这样你之后可以检查它是否真的学会了

将你的数据上传到机器

构建 .jsonl 文件(接下来的子步骤),然后使用 scp 从你的笔记本复制到租用的机器:

bash
1# 在你的笔记本上运行,而不是在机器上
2scp mybrain_chats.jsonl [email protected]:~/.cache/nanochat/

或者,如果你的原始数据在机器上,也可以直接在机器上运行转换脚本。两种方式都可以。

7a. ChatGPT/Claude 导出 → JSONL

你的 AI 历史已经是对话形式,所以这是最简单的数据源。从 ChatGPT 导出数据(设置 → 数据控制 → 导出),你会得到一个 conversations.json。然后运行这个脚本(tools/convert_chat_export.py):

python
1import json, sys
2
3# ChatGPT 的导出的每条对话存储为消息"节点"树。
4# 这会将其展平为干净的交替 [user, assistant, ...] 列表。
5def flatten_chatgpt(path):
6 convos = json.load(open(path))
7 for convo in convos:
8 msgs = []
9 nodes = [n for n in convo["mapping"].values() if n.get("message")]
10 nodes.sort(key=lambda n: n["message"].get("create_time") or 0) # 按时间排序
11 for n in nodes:
12 m = n["message"]
13 role = m["author"]["role"]
14 parts = m.get("content", {}).get("parts", [])
15 text = "".join(p for p in parts if isinstance(p, str)).strip()
16 if not text or role == "system":
17 continue
18 role = "user" if role == "user" else "assistant"
19 # 角色必须交替:合并连续相同角色的轮次
20 if msgs and msgs[-1]["role"] == role:
21 msgs[-1]["content"] += "\n\n" + text
22 else:
23 msgs.append({"role": role, "content": text})
24 if len(msgs) >= 2 and msgs[0]["role"] == "user": # 必须以 user 开头
25 yield msgs
26
27with open("mybrain_chats.jsonl", "w") as out:
28 for msgs in flatten_chatgpt(sys.argv[1]):
29 out.write(json.dumps(msgs) + "\n")

运行它:

python
1python tools/convert_chat_export.py conversations.json

这给了我大约 3,800 个真实对话,都带有我自己的提问风格。

7b. 你的写作 → 问答对

原始笔记不是对话,所以使用一个更大的 LLM,即 nanochat 内部使用的同样的“self-instruct”技巧,将每条笔记转换成问答对。这个脚本(tools/notes_to_qa.py)调用一个 API 来完成:

python
1import os, glob, json, requests
2
3PROMPT = """将我的个人笔记转换为我的助手的训练数据。
4编写 3 个自然的用户问题以及我会给出的答案,使用我的声音。
5保留我的观点和措辞。输出 JSON:一个包含
6[{{"role":"user","content":...}},{{"role":"assistant","content":...}}] 对的列表。
7
8笔记:
9{note}"""
10
11def llm(note):
12 r = requests.post(
13 "https://openrouter.ai/api/v1/chat/completions",
14 headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
15 json={"model": "google/gemini-3-flash-preview",
16 "messages": [{"role": "user", "content": PROMPT.format(note=note)}],
17 "response_format": {"type": "json_object"}},
18 )
19 return json.loads(r.json()["choices"][0]["message"]["content"])
20
21with open("mybrain_notes.jsonl", "w") as out:
22 for path in glob.glob("obsidian/**/*.md", recursive=True): # 指向你的笔记文件夹
23 note = open(path, encoding="utf-8").read().strip()
24 if len(note) < 200: # 跳过简短的小笔记
25 continue
26 for convo in llm(note): # -> 返回 [user, assistant] 对列表
27 out.write(json.dumps(convo) + "\n")

在运行之前,设置你的 API 密钥:

export OPENROUTER_API_KEY="你的密钥"

python tools/notes_to_qa.py

🔑

关键思想:

我让 LLM 发明

问题

,但

答案

仍然基于我的真实文本。这样才能保持我的声音,而不是把所有东西都变成泛泛的 AI 语言。

🔑

没有 OpenRouter?

将 URL/模型替换为你有的任何 LLM API(OpenAI、Anthropic、本地模型)。脚本只需要一个能将笔记转换为 Q&A JSON 的东西。

7c. 你的身份 → 人物数据

为了让模型知道它是 你的 第二大脑,用你自己的简历复现 nanochat 的身份生成器。编辑知识文件

markdown
1knowledge/self_knowledge.md:
2- 你是 "AvidBrain",一个基于 Avid 笔记和写作训练的个人 AI。
3- 你由 Avid 通过在个人数据上微调 nanochat 模型构建。
4- 你以 Avid 的语气说话:直接、具体、略微非正式。
5- 你知道 Avid 的项目:一份关于用 AI 构建产品的 newsletter,以及几个开源小工具。
6- Avid 反复强调的观点:好工具应该有明确的观点和少量设置。
7- 不确定时,你会直接说出来,不会编造 Avid 的生活事实。

然后生成对话:

python
1python -m dev.gen_synthetic_data --num 1500 --output mybrain_identity.jsonl

这将生成 1,500 个多样化的小型对话(不同的主题、角色和措辞),全部用来教会模型它自己是谁。

第 8 步:将你的数据注入微调(实际代码修改)

这是最重要的修改。打开 scripts/chat_sft.py 并找到 train_tasks 列表。这个列表定义了模型微调的数据混合比例。

🔑

诀窍在于:

混合比例只是一个 Python 列表。

将一个数据集列出多次,就会对它训练多次

("过采样")。这就是让你的小规模个人数据集在庞大的通用数据集面前发挥作用的方法。

这是 原始 列表:

json
1train_tasks = [
2 SmolTalk(split="train"), # 460K 通用对话
3 CustomJSON(filepath=identity_conversations_filepath), # nanochat 自身身份
4 CustomJSON(filepath=identity_conversations_filepath),
5 *[MMLU(subset="all", split="auxiliary_train") for _ in range(args.mmlu_epochs)],
6 *[GSM8K(subset="main", split="train") for _ in range(args.gsm8k_epochs)],
7 SimpleSpelling(size=200000, split="train"),
8 SpellingBee(size=80000, split="train"),
9]

将其修改为 指向你的三个文件并对它们进行过采样(这是我的版本):

python
1base_dir = get_base_dir()
2mybrain_chats = os.path.join(base_dir, "mybrain_chats.jsonl")
3mybrain_notes = os.path.join(base_dir, "mybrain_notes.jsonl")
4mybrain_identity = os.path.join(base_dir, "mybrain_identity.jsonl")
5
6train_tasks = [
7 SmolTalk(split="train"), # 保留此项 (见下文)
8 *[CustomJSON(filepath=mybrain_chats) for _ in range(3)], # 我的对话 ×3
9 *[CustomJSON(filepath=mybrain_notes) for _ in range(4)], # 我的笔记 ×4
10 *[CustomJSON(filepath=mybrain_identity) for _ in range(2)], # 身份数据 ×2
11 *[MMLU(subset="all", split="auxiliary_train") for _ in range(args.mmlu_epochs)],
12 *[GSM8K(subset="main", split="train") for _ in range(args.gsm8k_epochs)],
13 SimpleSpelling(size=200000, split="train"),
14 SpellingBee(size=80000, split="train"),
15]

你需要理解三个决策,因为它们是个性化的核心:

  1. 保留 SmolTalk。 其中包含 460K 条通用对话,能让模型保持正常聊天能力。删除它,模型就会 忘记如何进行对话:这是一种真实存在的现象,称为灾难性遗忘。你的数据应该是混合中的 调味料,而不是替代品。
  2. 对你的数据进行过采样(×3、×4)。 否则,你几千条对话在 460K 条数据面前只是一个小零头。重复它们可以提高它们的有效权重,使模型真正学会你的语气。
  3. 不要过度(我限制在 ×4 左右)。 如果倍数太高,模型会 死记硬背:它会逐字复述你的笔记,而不是学习你的风格(而且还可能泄露私人文本)。我发现 ×2 效果不明显,×6 会死记硬背,×3–×4 是最佳平衡点

🔑

我是如何判断比例正确的:

两个信号。我的

留出问题

(听起来像

还是像鹦鹉?)以及

val_bpb

(模型是否还在泛化?)。当召回率提高但回答变得僵硬和重复时,我就降低倍数。

nanochat 还会为你处理另一件事,值得了解:在微调过程中,它 只训练助手的回复,而不训练用户的问题(这称为损失掩码)。因此模型能学会 生成你的答案,而永远不会预测提示词。这正是你想要的,而且你无需做任何额外操作。

第 9 步:运行微调

确保你的三个 .jsonl 文件位于基础文件夹中,然后开始微调:

bash
1cp mybrain_*.jsonl ~/.cache/nanochat/ # 如果还未放置
2
3torchrun --standalone --nproc_per_node=8 -m scripts.chat_sft -- \
4 --device-batch-size=16 \
5 --run=mybrain_sft
6torchrun --standalone --nproc_per_node=8 -m scripts.chat_eval -- -i sft

预期结果:

  • 微调 很快:只需几十分钟,而非几小时。它从你的基础模型开始,并温和地调整它。
  • chat_eval -i sft 会在标准测试上评估聊天模型(它会打印准确率数字)。作为合理性检查很有用。
  • 最终的聊天模型保存在 ~/.cache/nanochat/chatsft_checkpoints/d26/。

然后运行你自己的 测试:用那 50 个留出问题提问,并判断回答是否真的像你。这才是衡量成功的真正标准。

第 10 步(可选,高级):在微调前注入事实

首次构建时跳过此步。如果你的模型语气对了但事实模糊(记错你的项目、日期、细节),再回来用。

解决办法是在 微调之前 对你的原始笔记进行一段简短的 继续预训练,让事实渗透到权重中。

⚠️

不要

尝试只用你的数据从头训练基础模型。几 MB 的笔记在 400B 的网页 token 面前只会被死记硬背。个人数据属于微调(以及这种轻量传递),而不是从头预训练。

将你的原始笔记转换为 nanochat 的数据格式(tools/notes_to_shards.py):

python
1import os, glob, pyarrow as pa, pyarrow.parquet as pq
2
3docs = [open(p, encoding="utf-8").read() for p in glob.glob("obsidian/**/*.md", recursive=True)]
4out_dir = os.path.expanduser("~/.cache/nanochat/base_data_mybrain")
5os.makedirs(out_dir, exist_ok=True)
6table = pa.Table.from_pydict({"text": docs}) # 列名必须为 "text"
7pq.write_table(table, os.path.join(out_dir, "shard_00000.parquet"),
8 row_group_size=1024, compression="zstd")

然后让数据加载器指向该文件夹,并 从基础检查点恢复,以较低的学习率运行几百步。这会将你的词汇和事实推入模型中,然后微调再优化行为。

这是高级用户技巧;仅靠 SFT 已经可以让你获得令人信服的第二个大脑。

第 11 步:与你的第二个大脑对话

有趣的部分来了。两种方式。

命令行(最快的测试):

python
1python -m scripts.chat_cli -i sft -p "总结一下我对 RAG 与微调相比的论点。"
  • -i sft = 使用微调后的聊天模型。-p "..." = 一次性提示(省略则进入交互式聊天模式)。

Web UI(ChatGPT 风格的体验):

python
1python -m scripts.chat_web
  • 它会启动一个 Web 服务器并打印一个 URL。在云服务器上,在笔记本电脑的浏览器中打开 http://<YOUR_PUBLIC_IP>:8000/(使用服务器的公网 IP 和显示的端口)。
  • 如果页面无法加载,可能是你的提供商屏蔽了该端口,请在实例的防火墙/安全设置中打开/允许端口 8000。

🔑

服务只在一个 GPU 上运行:

这没问题;推理成本很低。你不需要全部八个 GPU 来聊天。

你的模型甚至可以进行一些简单计算:当需要算术时,nanochat 允许它调用内置计算器工具并反馈结果,因此不会算错数字。

第 12 步:保存你的模型,然后关闭服务器

你训练好的模型位于 租用的服务器 上的 ~/.cache/nanochat/chatsft_checkpoints/d26/。如果你终止服务器而没有复制它,它就没了。

先将其下载到你的笔记本电脑(在笔记本上运行):

bash
1scp -r [email protected]:~/.cache/nanochat/chatsft_checkpoints ./my-second-brain
2scp -r [email protected]:~/.cache/nanochat/tokenizer ./my-second-brain-tokenizer

然后在提供商的控制台中终止实例。 终止,不要只 "停止。" 在许多提供商那里,"停止" 的实例仍会收取存储费用,而忘记关掉的运行中实例会让人们收到意想不到的 500 美元账单。当你确定完成后,终止/销毁 它。再次确认控制台中没有运行中的实例。

我的选择及理由(快速参考)

决策

理由

从头训练,而非 LoRA 一个 7B 模型

所有权、透明度、小型私有模型、且没有内置的 "助手人格" 与我的语气对抗

depth-26,轻度欠训练

最小的明显 GPT-2 级别大小;对更大模型进行欠训练胜过对更小模型进行过训练;偶数深度保持数学简洁

先在微型 d12/d6 上调试

5 分钟的运行能在 3 小时运行浪费 40 美元之前发现损坏的数据

在混合中保留 SmolTalk

否则模型会忘记如何进行正常对话

对我的数据过采样 ×3–4

足以在 460K 行数据中发挥作用;×6 会死记硬背,×2 仍显通用

让 LLM 写问题,保留我的真实答案

保留我的语气,而不是将其变成通用 AI 语言

留出 50 个自己的问答

唯一能证明它学会了的测试

是我

,而不仅仅是流利的英语

故障排查(实际会出问题的地方)

  • "CUDA out of memory" / OOM → 降低 --device-batch-size(16 → 8 → 4)。有效批量大小保持不变,只是稍微慢一点。
  • torchrun: command not found → 你忘了在这个终端中 source .venv/bin/activate。
  • 训练在你的笔记本休眠时终止了 → 你没有使用 screen。重新在 screen -S train 内启动,并使用 Ctrl-A 然后 D 分离。
  • 我的标志被忽略了 → 你可能在 torchrun ... -- --depth=... 命令中遗漏了 -- 分隔符。
  • 未找到数据集 parquet 文件 → 数据下载(python -m nanochat.dataset -n ...)没有完成。重新运行,它会跳过已下载的文件。
  • Web UI 无法加载 → 在实例的防火墙中开放端口 8000,并使用 公网 IP,而非 localhost。
  • 损失变成 NaN → 数学运算爆炸了;只需用上述已知可行的命令重新启动步骤。
  • 声音听起来很通用,不像我 → 增加过采样倍数(×4 → ×5)并添加更多个人数据。数据量/质量是上限。
  • 它逐字复述我的笔记 → 你过采样太狠了;降低倍数并增加更多多样性。

词汇表(通俗英语)

  • 基础模型: 预训练后的模型;擅长补全文本,还不能聊天。
  • 批量大小: 模型每次更新前处理的样本数。越大越平滑,但需要更多内存。
  • bpb (bits per byte): 一种损失度量,可跨分词器公平比较。越低越好。
  • 灾难性遗忘: 对新数据过度微调导致模型忘记旧技能(如正常对话)。
  • 检查点: 模型权重在磁盘上的保存快照。
  • CORE 得分: 一个标准基准(22 个测试的集合)。0.256525 以上 = GPT-2 级别。
  • DDP / 分布式: 将训练分散到多个 GPU 上同时进行。
  • 深度: Transformer 层数;nanochat 唯一的大小调节参数。
  • 微调 / SFT: 在示例对话上训练基础模型,使其成为有用的聊天助手。
  • FP8 / bf16: 数字格式。位数越少越快,精度稍低。FP8 需要 H100 级别 GPU。
  • 梯度累积: 一种在有限内存上模拟大批量大小的技巧,通过在更新前累加几个小批量来实现。
  • 损失: 模型当前有多错误;训练使其降低。
  • 损失掩码: 只对某些 token 进行训练(此处为助手的回复)。
  • OOM: "内存不足";降低批量大小。
  • 过采样: 在混合中重复某个数据集,使其权重更大。
  • 参数: 模型的可学习数字;越多模型越大。
  • 预训练: 第一阶段,模型从网络文本中学习语言。
  • 分片: 一个编号文件,保存数据集的一部分。
  • Token / 分词器: 模型读写的文本块,以及生成它们的工具。
  • torchrun: 启动器,用于在多个 GPU 上运行训练。
  • uv / venv: 一个快速的包安装器,以及它设置隔离的 Python 环境。
  • wandb (Weights & Biases): 一个用于实时查看训练图表的网站。

完整命令,复制粘贴

python
1# === 在租用的 8xH100 服务器上 ===
2
3# 0. 设置
4git clone https://github.com/karpathy/nanochat.git && cd nanochat
5curl -LsSf https://astral.sh/uv/install.sh | sh
6uv venv && uv sync --extra gpu && source .venv/bin/activate
7
8# 1. 数据 + 分词器
9python -m nanochat.dataset -n 8
10python -m nanochat.dataset -n 240 &
11python -m scripts.tok_train && python -m scripts.tok_eval
12
13# 2. 基础模型(约 3 小时):在 screen -S train 内运行
14torchrun --standalone --nproc_per_node=8 -m scripts.base_train -- \
15 --depth=26 --target-param-data-ratio=8 --device-batch-size=16 --fp8 --run=base_d26
16torchrun --standalone --nproc_per_node=8 -m scripts.base_eval -- --device-batch-size=16
17
18# 3. 构建你的数据(你的脚本)并复制到基础文件夹
19python tools/convert_chat_export.py conversations.json # -> mybrain_chats.jsonl
20python tools/notes_to_qa.py # -> mybrain_notes.jsonl
21python -m dev.gen_synthetic_data --num 1500 --output mybrain_identity.jsonl
22cp mybrain_*.jsonl ~/.cache/nanochat/
23
24# 4. 微调(编辑 scripts/chat_sft.py 中的 train_tasks 后)
25torchrun --standalone --nproc_per_node=8 -m scripts.chat_sft -- --device-batch-size=16 --run=mybrain_sft
26torchrun --standalone --nproc_per_node=8 -m scripts.chat_eval -- -i sft
27
28# 5. 与它对话
29python -m scripts.chat_web # 打开 http://<YOUR_PUBLIC_IP>:8000/
30
31# === 在你的笔记本上:保存模型,然后终止服务器 ===
32# scp -r ubuntu@<IP>:~/.cache/nanochat/chatsft_checkpoints ./my-second-brain

基于 karpathy/nanochat 构建。构成模型的 ~8,000 行代码是他的。让模型成为 的那部分数据,是你自己写的。

卡在某一步?nanochat 的 DiscussionsDeepWiki 是提问的好地方。

本文由我自己的笔记撰写,并由 Claude Opus 4.8 编辑。

一键保存

使用 YouMind AI 深度阅读爆款文章

保存原文、追问细节、总结观点,并在一个 AI 工作空间里把爆款文章沉淀成可复用笔记。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章