“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
MicroGPT:200行Python实现完整GPT训练与推理的极简艺术
用200行纯Python无依赖代码,完整实现GPT从数据集、分词器、自动求导到训练推理的全流程,揭示大模型算法本质。
MicroGPT:将GPT压缩至200行纯Python的艺术
引言:为什么这是一个“艺术项目”
Andrej Karpathy(OpenAI创始成员、前特斯拉AI总监)在2026年2月发布了一个名为microgpt的开源项目,再次引发技术社区热议。HN上获得1936分、324条评论的热度表明,这个看似简单的文件触动了无数AI从业者和学习者的神经。
microgpt本质上是一个单文件、200行、零依赖的纯Python脚本,却包含了训练和推理一个GPT(Generative Pre-trained Transformer)所需的全部算法内容。这并非一个玩具,它涵盖了从数据预处理到神经网络训练、再到文本生成的完整技术栈。Karpathy称之为“艺术项目”,是因为它在极简性与完整性之间达到了近乎完美的平衡。
这一项目是Karpathy多个知名开源项目的集大成者:
- micrograd:微型自动求导引擎(约150行)
- makemore:字符级语言模型(用于生成人名)
- nanogpt:简洁但完整的GPT训练实现(约300行,仍需PyTorch)
microgpt将三者融合到一个文件中,并去掉了所有外部依赖(包括PyTorch),只保留核心数学逻辑。Karpathy坦言,这是他“十年痴迷于将LLM简化到最本质元素”的巅峰之作。他甚至将代码排版为三联画(triptych)形式来出售,并在自己的艺术商店karpathy.art上架——这进一步印证了其“代码即艺术”的理念。
一、数据集:32,000个人名的“语言宇宙”
大型语言模型的燃料是文本数据流,通常可按文档集合组织。在生产环境中,每个文档通常是一个网页;但对于microgpt,数据集被简化为一个相当直观的例子:32,000个人名,每行一个。
python
下载并加载数据集
if not os.path.exists('input.txt'):
import urllib.request
names_url = 'https://raw.githubusercontent.com/karpathy/makemore/refs/heads/master/names.txt'
urllib.request.urlretrieve(names_url, 'input.txt')
docs = [l.strip() for l in open('input.txt').read().strip().split('\n') if l.strip()]
random.shuffle(docs)
数据集内容形如:
emma
olivia
ava
isabella
sophia
...
(约32,000个人名)
这里的关键概念是文档(document)。在GPT的视角下,每个名字都是一个独立的“文档”。模型的目标是学习这些文档中的统计模式,然后生成相似的新文档。Karpathy特别提醒读者注意一个深刻的类比:当你与ChatGPT对话时,那段对话不过是另一个“长得有点怪”的文档。你用提示词初始化这个文档,模型的回复本质上只是统计意义上的“文档续写”——这个视角揭示了所有现代LLM的底层哲学。
从实际运行效果来看,训练完成后模型可以生成看似合理的新人名(阅读前的剧透):
sample 1: kamon
sample 2: ann
sample 3: karai
sample 4: jaire
sample 5: vialan
...
sample 20: anton
这些名字并非真实存在的人名,而是模型根据训练数据中学到的字符组合规律(如元音辅音交替、常见后缀模式等)进行的“幻觉”生成,看起来相当可信。
二、Tokenizer:字符级Token化的极简方案
神经网络无法直接处理文本,它们只理解数字。因此需要一种机制将字符串转换为整数序列(token ids),并能反向转换。这一步就是分词(tokenization)。
生产级的分词器如tiktoken(GPT-4使用)按字节对(byte pair)或字符块进行编码以提高效率,但microgpt使用了最简单的方案:给数据集中每个唯一字符分配一个整数。
python
uchars = sorted(set(''.join(docs))) # 所有唯一字符
BOS = len(uchars) # 特殊的BOS token id
vocab_size = len(uchars) + 1 # +1 为BOS token
在32,000个人名的数据集中,唯一字符实际上就是26个小写字母(a-z)。每个字母占一个id,外加一个特殊token:BOS(Beginning of Sequence,序列开始)。最终词表大小为27。
这里需要理解两个重要设计:
整数ID本身无意义:Karpathy指出,token的整数编码只是符号的离散标识,换成emoji也完全等价。这打破了“数字=语义”的直觉误区。
BOS token的双重作用:训练时,每个文档两端都包裹BOS token,即人名"emma"变为
[BOS, e, m, m, a, BOS]。模型在学习过程中逐渐明白:BOS表示新名字的开始/结束。这样在推理时,只需输入BOS,模型就知道应当开始生成一个新的名字,直到遇到BOS则停止。
这一设计完美演示了如何用最简单的方式让模型理解序列的边界语义,从而区分不同文档(在微调阶段,类似的特殊分隔符token被用于区分指令、上下文和回复)。
三、自动求导引擎(Autograd):微积分的乐高积木
训练神经网络的核心需求是梯度(gradient):对模型中每个参数,我们需要知道——“如果我把这个数值微调大一点点,损失(loss)会增加还是减少?变化幅度是多少?”
计算图与反向传播
计算图从大量输入(模型参数、输入token)汇聚到单个标量输出(损失)。反向传播从该输出开始,逆着计算图传播,通过链式法则计算损失对每个输入的梯度。
在生产环境中,PyTorch等框架自动处理这一切。而microgpt用单个Value类从零实现,仅约80行代码。这无疑是全文件中最数学密集、最算法密集的部分,Karpathy为此专门制作了2.5小时的讲解视频(micrograd视频)。
Value类的核心设计
python
class Value:
slots = ('data', 'grad', '_children', '_local_grads')
def __init__(self, data, children=(), local_grads=()):
self.data = data # 前向传播中该节点的标量值
self.grad = 0 # 损失关于该节点的导数(反向传播中计算)
self._children = children # 计算图中该节点的子节点
self._local_grads = local_grads # 该节点关于子节点的局部导数
其核心思想可以类比为乐高积木:每个数值打包成一个Value,它记录两件事——自身的数值(data)以及这个值是通过哪些操作计算出来的(_children和_local_grads)。
每次对Value对象进行数学运算(加、乘等),返回的永远是新的Value,它记住了输入(子节点)和该运算的局部导数。例如:
__mul__操作记录:∂(a·b)/∂a = b,∂(a·b)/∂b = a__add__操作记录:∂(a+b)/∂a = 1,∂(a+b)/∂b = 1
实现的操作表
| 操作 | 前向计算 | 局部梯度 |
|---|---|---|
| a + b | a+b | ∂/∂a = 1, ∂/∂b = 1 |
| a * b | a*b | ∂/∂a = b, ∂/∂b = a |
| a ** c | a^c | ∂/∂a = c·a^(c-1) |
| log(a) | ln(a) | ∂/∂a = 1/a |
| exp(a) | e^a | ∂/∂a = e^a |
| relu(a) | max(0, a) | ∂/∂a = [a>0] |
反向传播的精髓
python
def backward(self):
topo = []
visited = set()
def build_topo(v):
if v not in visited:
visited.add(v)
for child in v._children:
build_topo(child)
topo.append(v)
build_topo(self)
self.grad = 1
for v in reversed(topo):
for child, local_grad in zip(v._children, v._local_grads):
child.grad += local_grad * v.grad
反向传播的实现分为两个阶段:
- 拓扑排序:先通过递归构建计算图节点的拓扑顺序(后序遍历),确保每个节点的梯度能在其所有子节点之后计算。
- 梯度传播:从输出节点(损失)开始,设置
grad=1代表d(loss)/d(loss)=1,然后逆序访问每个节点,用链式法则将其梯度乘以其局部梯度,累加到子节点的梯度上。
关键细节:累加(+=而非=)是正确的,因为一个子节点可能被多个操作使用(如一个参数出现在多个加法中),其梯度应为各路径贡献之和。
这就是微积分的全部应用。正如Karpathy所强调——除了链式法则之外,没有任何魔法。这是理解自动求导的最佳教学范例。
四、模型架构:精简版GPT-2
microgpt的神经网络架构是GPT-2的极简复刻,包含以下关键组件:
4.1 嵌入层(Embedding)
每个token ID需要映射为高维度向量表示。microgpt通过查询嵌入矩阵(Embedding Matrix)实现:
python
class Embedding:
def init(self, num_embeddings, embedding_dim):
self.weight = [Value(random.uniform(-1, 1)) for _ in range(num_embeddings * embedding_dim)]
# ... 维度重组逻辑
def __call__(self, idx):
# 返回形状为 (B, T, C) 的张量,其中idx的每个元素作为行索引提取向量
...
同时,为了保留序列中token的顺序信息(Transformer本身不含位置概念),需加入位置编码(Positional Embedding)——一个与嵌入维度相同的可学习位置向量表,加到token嵌入上。
4.2 多头自注意力(Multi-Head Self-Attention)
这是Transformer的核心。对于一个由token组成的序列,自注意力机制让每个位置能够“关注”序列中其他位置,并聚合信息。其计算公式为:
$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$
其中Q(查询)、K(键)、V(值)由输入向量分别乘以三个可学习矩阵得到。“多头”表示并行进行多次这样的注意力计算,每个“头”关注不同子空间模式,最后拼接结果。
microgpt的实现使用因果掩码(causal mask),即在注意力矩阵上方三角置为-∞,经softmax后变为0,确保每个位置只能看到自身及之前的位置——这是生成模型的关键属性。
4.3 前馈网络(MLP)与残差连接
每个注意力层后跟随一个简单的两层前馈网络(通常是ReLU激活),并且每层都配有残差连接(Residual Connection)和层归一化(Layer Normalization)。残差连接通过旁路加法让梯度直接流过深层网络,解决深层模型训练困难问题。
对于microgpt,模型只有一层Transformer块(包含单头自注意力和一个隐藏维度较小的MLP),但总架构仍然是完整的、可扩展的GPT-2式结构。参数维度设为n_embd=32(嵌入维度)和n_head=4,总共约6000个参数——相比GPT-2的15亿参数,这是“果壳中的GPT”。
4.4 输出头与损失函数
模型的最后部分是一个线性层,将隐藏状态映射到词表大小的logits。训练时使用交叉熵损失(Cross-Entropy Loss)(对softmax后的logits取负对数),该损失可通过一个手工实现的cross_entropy函数计算,它也基于Value类构建计算图。
python
def cross_entropy(logits, targets):
# logits: (BT, vocab_size), targets: (BT,)
... # 手写softmax与负对数似然
五、Adam优化器:现代训练的引擎
得到梯度后,需要更新参数以降低损失。使用随机梯度下降的朴素版本(参数 -= 学习率*梯度)在现代网络中收敛过慢,因此microgpt实现了Adam优化器。
Adam(Adaptive Moment Estimation)维护每个参数的一阶动量(m)和二阶动量(v)(即梯度的指数移动平均和梯度平方的移动平均),并结合偏差校正让早期训练更稳定。更新公式简化为:
python
每个参数的更新逻辑(伪代码)
m = beta1 * m + (1 - beta1) * grad
v = beta2 * v + (1 - beta2) * grad2
m_hat = m / (1 - beta1t)
v_hat = v / (1 - beta2**t)
param -= lr * m_hat / (sqrt(v_hat) + epsilon)
每步更新时,Adam对每个参数自动适应学习率——梯度小的维度以较大步伐探索,梯度大的维度则谨慎小步。microgpt中设定了超参数:lr=0.001(初始学习率)、beta1=0.9、beta2=0.999、eps=1e-8(数值稳定性)。
由于microgpt不依赖NumPy(纯Python列表和标量),Adam更新通过逐元素操作实现,尽管在效率上远逊于矩阵库,但在教学维度上完全表现出核心思想。
六、训练循环:参数更新的节奏
训练循环是驱动模型学习的骨架代码,逻辑如下:
python
def train_step(model, optimizer, docs, tokenizer):
# 1. 采样一批文档(例如8个)
batch_docs = random.sample(docs, batch_size)
# 2. Token化并封装BOS:每个文档变为 [BOS, ...字符ids..., BOS]
# 3. 前向传播:得到对每个位置下一token的预测logits
logits = model(token_ids)
# 4. 计算交叉熵损失(比较所有位置预测与真实下一token)
loss = cross_entropy(logits, target_ids)
# 5. 反向传播:计算loss对每个参数的梯度
loss.backward()
# 6. Adam优化器更新所有参数
optimizer.step()
# 7. 清零梯度(为了避免累积,PyTorch特性是梯度默认累加,但这里手动清零)
model.zero_grad()
其中“下一token预测”的具体操作是:对于一个文档[BOS, e, m, m, a, BOS],输入模型前5个token [BOS, e, m, m, a](不移位),要求模型预测第2到第6个位置(即原序列的每个下一token)[e, m, m, a, BOS]。损失覆盖序列的每个位置。这种方式让每个位置同时学会预测其下一个字符。
训练持续数千步后,损失会降低到约1.2-1.5 nats(自然对数底)的量级,直观上模型已学会字母接龙的概率规律(如'q'之后很少直接跟'z',但'q'后常跟'u';名字通常以'a'、'e'等元音结尾等)。
七、推理循环:生成新文档
训练完成后,推理模式只需迭代采样:
python
def generate(model, tokenizer, max_length=20):
seq = [BOS] # 从BOS开始
while seq[-1] != BOS and len(seq) < max_length:
logits = model(seq) # 仅使用前缀
probs = softmax(logits[-1]) # 取最后一个位置的分布
next_token = sample(probs) # 按概率采样(非贪婪),以采样温度控制多样性
seq.append(next_token)
return tokenizer.decode(seq) # 去除BOS和尾部的BOS
生成时温度参数(temperature)可控制输出多样性,例如温度>1.0增加随机性,<1.0则更保守。默认在微GP项目中,以temperature=1.0采样即可产生前文所列的人名。
有趣的是,生成过程并不需要显式指定“终止”条件,模型自己学会在生成完一个名字后输出BOS标记来结束。
八、性能与效率:为什么可以如此慢?
microgpt故意不使用Python数值计算库(NumPy、PyTorch均已去除),所有矩阵乘法均是嵌套循环的纯标量运算,速度极慢。例如,一个batch forward+backward需要数秒,相比之下,同样的模型在PyTorch上毫秒可完成。
但这正是其教学价值所在:去掉“效率”的面纱后,每一行代码都在刻画算法的数学本质。 200行代码从零完整复现了GPT全栈——数据→token→嵌入→Transformer→损失→梯度→优化→生成。没有隐藏的魔法,没有任何第三方调用。
九、核心启示:为什么这个项目如此重要?
理解深度学习符号主义底层的连接主义本质:通过阅读这200行代码,你会清晰地看到,所谓“智能”不过是:在一大堆标量数值上反复进行算术运算,根据损失信号调整这些数值,直到它们能够以较高概率预测训练数据的模式。
打破黑箱焦虑:对于习惯PyTorch的开发者,这是一个认知校准器——提醒你框架简洁的API后究竟发生了什么。当你掌握了Value类(自动求导)、Transformer公式、Adam更新这三个主干,你可以用任何语言(甚至C)重写一个GPT。
架构可分离性的教学设计:不同模块(数据、layer、optimizer)的组织方式干净而可替换。你也可以将Value替换为NumPy数组从而“提速”,或将自注意力替换为线性注意力等,每一次替换迫使你深入理解被替换组件的职责。
十、扩展延伸:从microgpt到真实世界
microgpt是理解GPT的起点。现实的LLM工程是在此基础上的“效率加成”而非算法革新:
- tiktoken字节对编码替代字符级分词(压缩序列长度数百倍)
- GPU/TPU上的矩阵乘法优化(并行计算)
- 混合精度训练、梯度裁剪、学习率调度(训练稳定)
- 数十层的Transformer堆叠与数千亿参数(模型容量扩展)
Karpathy历来的教学项目(micrograd → makemore → nanogpt → microgpt)是一条精心设计的学习路径:从零散模块到完整框架,逐步完成代码简化但知识含量递增的微积分学习过程。
结语:代码之美的极致
正如Karpathy在文中所言:“Everything else is just efficiency.”(其余的一切仅仅是效率问题。)这句话与高德纳(D.E. Knuth)的“过早优化是万恶之源”一脉相承。当无数AI工程师沉浸在超参数调优、GPU集群部署时,Karpathy再次提醒我们——算法本身的美感和可理解性才是这个领域根基。
如果这份200行代码让你体会到某种美感,那么便找到了通往深度学习底层直觉的钥匙。艺术并不总是在画廊里——它可能是200行诚实而优雅的Python。
原文链接:http://karpathy.github.io/2026/02/12/microgpt/
项目源码:microgpt.py gist,在线运行版:Colab note
*本文为技术翻译与深度扩展,保留作者观点与全部技术细节,仅供学习研究使用。