[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fZbXvW40QJ3Es368nm16hWc4ru5MZA00ebFoUYt5QFqA":3},{"item":4,"related":53},{"id":5,"type":6,"title":7,"slug":8,"summary":9,"body":10,"coverUrl":11,"productScreenshots":12,"productLinks":13,"authorName":14,"authorUrl":15,"authorSubject":16,"category":17,"tags":22,"sourceLabel":45,"sourceName":45,"sourceUrl":45,"status":46,"seoTitle":45,"seoDescription":45,"canonicalUrl":45,"isFeatured":47,"sno":48,"sortOrder":49,"publishedAt":50,"updatedAt":51,"createdAt":52},"63c04eca-1ea9-4eca-a993-e359f24f4e2c","article","MicroGPT解读&思考","microgpt","Andrej Karpathy 的 MicroGPT 展示了在已有的真实名字中学习规律，并通过优化训练编出新名字的过程，揭示了AI大模型训练的底层逻辑","> Andrej Karpathy 的 MicroGPT 展示了在已有的真实名字中学习规律，并通过优化训练编出新名字的过程，揭示了AI大模型训练的底层逻辑。\n# 一、项目概述\n\nAndrej Karpathy 的 MicroGPT 项目以极简的代码（200余行）构建出了AI大模型训练的底层架构，涵盖了从数据准备、自定义自动微分引擎（Value 类）、Transformer 单层架构（多头注意力、MLP、RMSNorm）、Adam 优化器到训练与推理的完整流程。\n\n项目以名字生成为例，从真实名字数据集中学习字符分布，训练后能够自主生成符合语言规律的新名字。\n\u003Cdiv align=\"center\">\n\u003Cimg width=\"1100\" src=\"https:\u002F\u002Fs41.ax1x.com\u002F2026\u002F02\u002F20\u002FpZXDRg0.png\" alt=\"Karpathy's post\" \u002F>\n\u003C\u002Fdiv>\n\n项目代码（文末附中文注释版）：https:\u002F\u002Fgist.github.com\u002Fkarpathy\u002F8627fe009c40f57531cb18360106ce95\n\n省流：\n1. **准备名字列表**：下载名字列表，打乱，建立字母表。\n2. **造大脑**：初始化许多小旋钮（参数），每个旋钮是一个小纸条（Value），能记录数字和计算关系。\n3. **定义思考方式**：写函数 `gpt`，描述大脑如何根据当前字母和位置，利用记忆（keys\u002Fvalues）推测下一个字母。\n4. **训练**：反复看名字，每看一个名字，让大脑猜，算猜错的程度（损失），然后通过小纸条的反向传播算出每个旋钮该往哪个方向拧一点，再用 Adam 方法拧动旋钮。这样训练的效果会越来越好。\n5. **创作**：训练完，让大脑自己一个字一个字地编名字，打印出来看看它学会了没有。\n# 二、项目构建\n\n## 2.1 导入工具包\n\n```\nimport os       # 用于检查文件是否存在\nimport math     # 用于数学计算（对数、指数等）\nimport random   # 用于生成随机数、打乱顺序等\nrandom.seed(42) # 固定随机种子，让每次运行结果一样\n```\n## 2.2 获取名字列表\n\n电脑先从网上下载一个名字列表，里面有很多真实的名字，每行一个。  \n\n```\n# 如果当前目录没有 input.txt 文件，就从网上下载名字列表\nif not os.path.exists('input.txt'):\n    import urllib.request\n    names_url = 'https:\u002F\u002Fraw.githubusercontent.com\u002Fkarpathy\u002Fmakemore\u002Frefs\u002Fheads\u002Fmaster\u002Fnames.txt'\n    urllib.request.urlretrieve(names_url, 'input.txt')\n```\n## 2.3 读取文件并打乱顺序\n\n读取全部名字然后打乱顺序，这样它就不会只盯着前几个名字学，而是随机看，学得更全面。\n\n```\n# 读取文件，按行分割，去掉空行和首尾空格，得到名字列表 docs\ndocs = [l.strip() for l in open('input.txt').read().strip().split('\\n') if l.strip()]\nrandom.shuffle(docs)  # 打乱名字顺序\nprint(f\"名字总数: {len(docs)}\")\n```\n# 三、定义字母\n\n## 3.1 构建字母表\n\n名字都是由字母组成的。电脑需要先知道它要学哪些字母，因此需要把所有的名字拼在一起，找出所有不同的字母（比如 a,b,c,…,A,B,C…），然后给每个字母编一个号（比如 a=0，b=1，c=2……），这样电脑就能用数字来代表字母了。\n\n```\n# 找出所有不重复的字符，排序后作为字母表\nuchars = sorted(set(''.join(docs)))\n```\n## 3.2 添加符号并定义表大小\n\n另外还需要一个特殊的“开始”符号（类似作文开头空两格）。电脑看到这个符号，就知道“名字要开始了”。这个符号也编一个号，比如 26（如果前面字母有 0~25 的话）。\n\n```\n# 定义特殊的“开始”符号 BOS，编号为字母表长度\nBOS = len(uchars)\n# 词汇表大小 = 字母数量 + BOS\nvocab_size = len(uchars) + 1\nprint(f\"词汇表大小: {vocab_size}\")\n```\n\n现在，电脑的“词汇表”里一共有：所有字母 + 开始符号。以后电脑猜下一个字母，就是从这些里面选一个。\n# 四、造一个“大脑”\n\n电脑要学习，得有一个“大脑”。\n\n这个大脑里有很多很多小旋钮（可以想象成收音机上的调频旋钮）。  \n\n一开始，这些小旋钮都是随便转到一个位置的，所以大脑什么也不会。\n\n大脑的任务是：看到当前字母和它在名字里的位置（比如第几个字），然后猜下一个字母是什么。  \n\n猜的时候，它会用到这些旋钮，把当前字母和位置变成一些数字（可以叫做“想法”），再经过一些计算，最后从词汇表里选一个字母作为答案。\n\n4.1-4.5 内容较为复杂，只为了解逻辑可跳过。\n## 4.1 定义小纸条\n\n初始化节点，存储数值、梯度、子节点和局部导数；重载加法运算；重载乘法运算；重载幂运算（指数为常数）；定义对数运算；定义指数运算；定义 ReLU 激活函数；定义其他运算（负数、减法、除法等）；反向传播（计算梯度）。\n\n```\n# 定义自动微分的小纸条类 Value\nclass Value:\n    \"\"\"存储一个标量值和它的梯度，作为计算图中的一个节点\"\"\"\n    def __init__(self, data, children=(), local_grads=()):\n        self.data = data                # 前向计算得到的数值\n        self.grad = 0                   # 损失对该节点的梯度，反向传播时计算\n        self._children = children       # 生成该节点所依赖的子节点\n        self._local_grads = local_grads # 该节点对每个子节点的局部导数\n    def __add__(self, other):\n        other = other if isinstance(other, Value) else Value(other)\n        return Value(self.data + other.data, (self, other), (1, 1))\n    def __mul__(self, other):\n        other = other if isinstance(other, Value) else Value(other)\n        return Value(self.data * other.data, (self, other), (other.data, self.data))\n    def __pow__(self, other):\n        return Value(self.data ** other, (self,), (other * self.data ** (other - 1),))\n    def log(self):\n        return Value(math.log(self.data), (self,), (1 \u002F self.data,))\n    def exp(self):\n        return Value(math.exp(self.data), (self,), (math.exp(self.data),))\n    def relu(self):\n        return Value(max(0, self.data), (self,), (float(self.data > 0),))\n    def __neg__(self):\n        return self * -1\n    def __radd__(self, other):\n        return self + other\n    def __sub__(self, other):\n        return self + (-other)\n    def __rsub__(self, other):\n        return other + (-self)\n    def __rmul__(self, other):\n        return self * other\n    def __truediv__(self, other):\n        return self * other ** -1\n    def __rtruediv__(self, other):\n        return other * self ** -1\n    def backward(self):\n        # 拓扑排序，得到计算顺序\n        topo = []\n        visited = set()\n        def build_topo(v):\n            if v not in visited:\n                visited.add(v)\n                for child in v._children:\n                    build_topo(child)\n                topo.append(v)\n        build_topo(self)\n        # 从当前节点开始反向传播\n        self.grad = 1\n        for v in reversed(topo):\n            for child, local_grad in zip(v._children, v._local_grads):\n                child.grad += local_grad * v.grad\n```\n## 4.2 设定大脑的规格\n\n```\nn_embd = 16      # 每个字母用16个数字表示（嵌入维度）\nn_head = 4       # 注意力头的数量\nn_layer = 1      # 层数（这里只用一层）\nblock_size = 8   # 最大序列长度\nhead_dim = n_embd \u002F\u002F n_head  # 每个注意力头负责的维度\n```\n## 4.3 辅助函数\n\n创建随机初始化的矩阵，每个元素是一个小纸条。\n\n```\n# 辅助函数：创建一个矩阵，每个元素是一个服从高斯分布的小纸条\nmatrix = lambda nout, nin, std=0.02: [[Value(random.gauss(0, std)) for _ in range(nin)] for _ in range(nout)]\n```\n## 4.4 初始化模型参数\n\n模型参数即为大脑里的各种表格。\n\n```\nstate_dict = {\n    'wte': matrix(vocab_size, n_embd),       # 字母特征表\n    'wpe': matrix(block_size, n_embd),       # 位置特征表\n    'lm_head': matrix(vocab_size, n_embd),   # 输出层\n}\nfor i in range(n_layer):\n    state_dict[f'layer{i}.attn_wq'] = matrix(n_embd, n_embd)          # 注意力 query 投影矩阵\n    state_dict[f'layer{i}.attn_wk'] = matrix(n_embd, n_embd)          # 注意力 key 投影矩阵\n    state_dict[f'layer{i}.attn_wv'] = matrix(n_embd, n_embd)          # 注意力 value 投影矩阵\n    state_dict[f'layer{i}.attn_wo'] = matrix(n_embd, n_embd, std=0)   # 注意力输出投影矩阵（初始化为0）\n    state_dict[f'layer{i}.mlp_fc1'] = matrix(4 * n_embd, n_embd)      # MLP 第一层\n    state_dict[f'layer{i}.mlp_fc2'] = matrix(n_embd, 4 * n_embd, std=0) # MLP 第二层（初始化为0）\n# 把所有参数（小纸条）展平到一个列表里，方便优化\nparams = [p for mat in state_dict.values() for row in mat for p in row]\nprint(f\"参数总数: {len(params)}\")\n```\n## 4.5 定义思考方式\n\n定义大脑的思考方式，即模型前向传播函数。\n\n```\ndef linear(x, w):\n    \"\"\"线性层：输入向量x，权重矩阵w，输出x与w每行的点积\"\"\"\n    return [sum(wi * xi for wi, xi in zip(wo, x)) for wo in w]\ndef softmax(logits):\n    \"\"\"将分数转换为概率分布\"\"\"\n    max_val = max(val.data for val in logits)\n    exps = [(val - max_val).exp() for val in logits]\n    total = sum(exps)\n    return [e \u002F total for e in exps]\ndef rmsnorm(x):\n    \"\"\"RMSNorm 归一化\"\"\"\n    ms = sum(xi * xi for xi in x) \u002F len(x)\n    scale = (ms + 1e-5) ** -0.5\n    return [xi * scale for xi in x]\ndef gpt(token_id, pos_id, keys, values):\n    \"\"\"GPT 模型的前向传播\"\"\"\n    # 取出当前字母的特征和位置特征\n    tok_emb = state_dict['wte'][token_id]\n    pos_emb = state_dict['wpe'][pos_id]\n    x = [t + p for t, p in zip(tok_emb, pos_emb)]  # 相加得到综合表示\n    x = rmsnorm(x)\n    for li in range(n_layer):\n        # 1) 多头注意力块\n        x_residual = x\n        x = rmsnorm(x)\n        q = linear(x, state_dict[f'layer{li}.attn_wq'])\n        k = linear(x, state_dict[f'layer{li}.attn_wk'])\n        v = linear(x, state_dict[f'layer{li}.attn_wv'])\n        # 将当前 key 和 value 存入缓存\n        keys[li].append(k)\n        values[li].append(v)\n        x_attn = []\n        for h in range(n_head):\n            hs = h * head_dim\n            q_h = q[hs:hs + head_dim]\n            k_h = [ki[hs:hs + head_dim] for ki in keys[li]]\n            v_h = [vi[hs:hs + head_dim] for vi in values[li]]\n            # 计算注意力分数\n            attn_logits = [sum(q_h[j] * k_h[t][j] for j in range(head_dim)) \u002F head_dim ** 0.5\n                           for t in range(len(k_h))]\n            attn_weights = softmax(attn_logits)\n            # 加权求和得到头输出\n            head_out = [sum(attn_weights[t] * v_h[t][j] for t in range(len(v_h))) for j in range(head_dim)]\n            x_attn.extend(head_out)\n        x = linear(x_attn, state_dict[f'layer{li}.attn_wo'])\n        x = [a + b for a, b in zip(x, x_residual)]  # 残差连接\n        # 2) MLP 块\n        x_residual = x\n        x = rmsnorm(x)\n        x = linear(x, state_dict[f'layer{li}.mlp_fc1'])\n        x = [xi.relu() ** 2 for xi in x]           # ReLU² 激活\n        x = linear(x, state_dict[f'layer{li}.mlp_fc2'])\n        x = [a + b for a, b in zip(x, x_residual)]  # 残差连接\n    # 输出层，得到词汇表大小的分数\n    logits = linear(x, state_dict['lm_head'])\n    return logits\n```\n\n# 五、训练大脑\n\n拿一个名字：比如 “Emma”。电脑先把它变成数字：E→4，m→12，m→12，a→0。\n\n然后在开头和结尾加上“开始”符号（比如 26）。 最后得到：[26, 4, 12, 12, 0, 26]。 \n\n让大脑猜： 先看第一个符号 26（开始），大脑要猜下一个字母是谁。正确答案是 4（E）。 \n\n如果大脑猜对了，就表扬；猜错了，就告诉它“你猜错了，正确答案是 E”。 \n\n然后看 26 和 4，大脑要猜再下一个字母，正确答案是 12（m）。 \n\n依此类推，一直猜到最后一个字母，大脑要猜结束符号 26。 \n\n调整旋钮：每猜完一个名字，电脑就会根据大脑猜得对不对，稍微转动一下那些小旋钮。 转动的方向是：如果猜错了，就朝能猜对的方向转一点点。 \n\n这样，下次再看类似的名字时，大脑就会更接近正确答案。 重复练习：电脑不停地拿新的名字，一个一个地猜，然后调整旋钮。 \n\n总共练习 500 次（代码里的 500 步）。 \n\n每次练习完，电脑都会打印一个数字（损失），这个数字越小，说明大脑猜得越准。 这个数字会越来越小，说明大脑正在学习进步。\n\n以下为相关代码，只为了解逻辑可跳过。\n## 5.1 设置优化器及缓存\n\n- `learning_rate = 0.01`：初始学习率，控制每次调整的步长。\n- `beta1 = 0.9`、`beta2 = 0.95`：两个记忆系数，决定记住多少历史信息。\n- `eps_adam = 1e-8`：一个很小的数，防止除零。\n- `m` 和 `v` 是两个记忆列表，长度和参数个数一样，初始全 0，用来存储每个参数的“一阶动量”（梯度的平均）和“二阶动量”（梯度平方的平均）。\n\n```\n# Adam 优化器参数\nlearning_rate, beta1, beta2, eps_adam = 1e-2, 0.9, 0.95, 1e-8\nm = [0.0] * len(params)  # 一阶动量缓存\nv = [0.0] * len(params)  # 二阶动量缓存\n\n```\n## 5.2 开始训练循环\n\n设定训练循环为500步。\n\n```\nnum_steps = 500  # 训练步数\nfor step in range(num_steps):\n```\n\n拿一个名字，转换为数字列表，首尾加上 BOS。\n\n```\n    # 取一个名字，转换为数字列表，首尾加上 BOS\n    doc = docs[step % len(docs)]\n    tokens = [BOS] + [uchars.index(ch) for ch in doc] + [BOS]\n    n = min(block_size, len(tokens) - 1)  # 有效预测长度\n```\n\n初始化每层的记忆缓存和损失列表。\n\n```\n    keys, values = [[] for _ in range(n_layer)], [[] for _ in range(n_layer)]\n    losses = []\n```\n\n让大脑逐个位置猜下一个字母。\n\n```\n    # 对每个位置进行预测\n    for pos_id in range(n):\n        token_id, target_id = tokens[pos_id], tokens[pos_id + 1]\n        logits = gpt(token_id, pos_id, keys, values)\n        probs = softmax(logits)\n        loss_t = -probs[target_id].log()  # 负对数似然损失\n        losses.append(loss_t)\n```\n\n计算平均损失。\n\n```\n    # 平均损失\n    loss = (1 \u002F n) * sum(losses)\n```\n\n反向传播，计算所有参数的梯度。\n\n```\n    # 反向传播，计算梯度\n    loss.backward()\n```\n\n用余弦退火计算当前步的学习率，这样模型会逐步趋于稳定。\n\n```\n    # 余弦退火学习率\n    lr_t = learning_rate * 0.5 * (1 + math.cos(math.pi * step \u002F num_steps))\n```\n\n用 Adam 优化器更新所有参数（转动小旋钮）。\n\n```\n    # 用 Adam 更新所有参数\n    for i, p in enumerate(params):\n        m[i] = beta1 * m[i] + (1 - beta1) * p.grad\n        v[i] = beta2 * v[i] + (1 - beta2) * p.grad ** 2\n        m_hat = m[i] \u002F (1 - beta1 ** (step + 1))\n        v_hat = v[i] \u002F (1 - beta2 ** (step + 1))\n        p.data -= lr_t * m_hat \u002F (v_hat ** 0.5 + eps_adam)\n        p.grad = 0  # 梯度清零\n```\n\n打印当前步数和损失。\n\n```\n    print(f\"步数 {step + 1:4d} \u002F {num_steps:4d} | 损失 {loss.data:.4f}\")\n```\n\n---\n# 六、输出结果\n\n训练 500 次之后，大脑已经学得差不多了，现在可以让它自己编名字。\n\n开始：给大脑一个“开始”符号。大脑根据“开始”符号，猜第一个字母是谁。它不会直接选最可能的那一个，而是随机选，但猜对概率高的字母更容易被选到（这叫“有点创意，但又不乱来”）。  \n## 6.1 设置温度参数\n\n代码里有个“温度”参数，温度低就保守（选最可能那个），温度高就爱冒险（可能选冷门的字母）。\n\n```\ntemperature = 0.5  # 温度参数，控制随机性\n```\n## 6.2 生成并打印样本\n\n继续猜：把猜到的字母作为新的当前字母，继续猜下一个。  \n\n这样一字一字往下猜，直到大脑猜出“结束”符号，或者猜够了 8 个字母（因为名字一般不会太长）。\n\n看结果：电脑会编出 20 个新名字，打印出来。\n\n```\nprint(\"\\n--- 推理生成 ---\")\nfor sample_idx in range(20):\n    keys, values = [[] for _ in range(n_layer)], [[] for _ in range(n_layer)]\n    token_id = BOS\n    sample = []\n    for pos_id in range(block_size):\n        logits = gpt(token_id, pos_id, keys, values)\n        # 温度调整\n        probs = softmax([l \u002F temperature for l in logits])\n        # 按概率随机采样下一个 token\n        token_id = random.choices(range(vocab_size), weights=[p.data for p in probs])[0]\n        if token_id == BOS:\n            break\n        sample.append(uchars[token_id])\n    print(f\"样本 {sample_idx + 1:2d}: {''.join(sample)}\")\n```\n# 七、理解与思考\n\n虽然 MicroGPT 是一个只有单层 Transformer、16 维嵌入、500 步训练的微型模型，但它与当今前沿的大模型有着完全相同的核心架构和训练方式。\n\nMicroGPT 剥去了深度学习框架的封装，直接展示了 Transformer 的每一行数学运算：线性层（linear）就是矩阵乘法，注意力机制就是查询（Q）与键（K）的点积缩放再加权求和，无论多大的模型，其核心只是这些基本操作的组合与堆叠。\n\n由此可见，大模型的本质是“矩阵运算 + 非线性”。\n\n虽然 MicroGPT 规模极小，但它的设计可以无缝扩展到更大的规模（增加层数、维度、数据量）。这样我们就理解了 OpenAI 等企业将 Transformer 扩展到千亿参数的基本原理——无非是“更多的层、更多的头、更多的数据、更强的算力”，而核心逻辑保持不变。\n# 八、完整代码（注释）\n\n```\n# 第一步：给电脑看名字\n\nimport os       # 用于检查文件是否存在\nimport math     # 用于数学计算（对数、指数等）\nimport random   # 用于生成随机数、打乱顺序等\nrandom.seed(42) # 固定随机种子，让每次运行结果一样\n\n# 如果当前目录没有 input.txt 文件，就从网上下载名字列表\nif not os.path.exists('input.txt'):\n    import urllib.request\n    names_url = 'https:\u002F\u002Fraw.githubusercontent.com\u002Fkarpathy\u002Fmakemore\u002Frefs\u002Fheads\u002Fmaster\u002Fnames.txt'\n    urllib.request.urlretrieve(names_url, 'input.txt')\n\n# 读取文件，按行分割，去掉空行和首尾空格，得到名字列表 docs\ndocs = [l.strip() for l in open('input.txt').read().strip().split('\\n') if l.strip()]\nrandom.shuffle(docs)  # 打乱名字顺序\nprint(f\"名字总数: {len(docs)}\")\n\n# 第二步：让电脑认识字母\n\n# 找出所有不重复的字符，排序后作为字母表\nuchars = sorted(set(''.join(docs)))\n# 定义特殊的“开始”符号 BOS，编号为字母表长度\nBOS = len(uchars)\n# 词汇表大小 = 字母数量 + BOS\nvocab_size = len(uchars) + 1\nprint(f\"词汇表大小: {vocab_size}\")\n\n# 第三步：给电脑造一个“大脑”\n\n# 定义自动微分的小纸条类 Value\nclass Value:\n    \"\"\"存储一个标量值和它的梯度，作为计算图中的一个节点\"\"\"\n\n    def __init__(self, data, children=(), local_grads=()):\n        self.data = data                # 前向计算得到的数值\n        self.grad = 0                   # 损失对该节点的梯度，反向传播时计算\n        self._children = children       # 生成该节点所依赖的子节点\n        self._local_grads = local_grads # 该节点对每个子节点的局部导数\n\n    def __add__(self, other):\n        other = other if isinstance(other, Value) else Value(other)\n        return Value(self.data + other.data, (self, other), (1, 1))\n\n    def __mul__(self, other):\n        other = other if isinstance(other, Value) else Value(other)\n        return Value(self.data * other.data, (self, other), (other.data, self.data))\n\n    def __pow__(self, other):\n        return Value(self.data ** other, (self,), (other * self.data ** (other - 1),))\n\n    def log(self):\n        return Value(math.log(self.data), (self,), (1 \u002F self.data,))\n\n    def exp(self):\n        return Value(math.exp(self.data), (self,), (math.exp(self.data),))\n\n    def relu(self):\n        return Value(max(0, self.data), (self,), (float(self.data > 0),))\n\n    def __neg__(self):\n        return self * -1\n\n    def __radd__(self, other):\n        return self + other\n\n    def __sub__(self, other):\n        return self + (-other)\n\n    def __rsub__(self, other):\n        return other + (-self)\n\n    def __rmul__(self, other):\n        return self * other\n\n    def __truediv__(self, other):\n        return self * other ** -1\n\n    def __rtruediv__(self, other):\n        return other * self ** -1\n\n    def backward(self):\n        # 拓扑排序，得到计算顺序\n        topo = []\n        visited = set()\n\n        def build_topo(v):\n            if v not in visited:\n                visited.add(v)\n                for child in v._children:\n                    build_topo(child)\n                topo.append(v)\n\n        build_topo(self)\n\n        # 从当前节点开始反向传播\n        self.grad = 1\n        for v in reversed(topo):\n            for child, local_grad in zip(v._children, v._local_grads):\n                child.grad += local_grad * v.grad\n\n# 设定大脑的规格\nn_embd = 16      # 每个字母用16个数字表示（嵌入维度）\nn_head = 4       # 注意力头的数量\nn_layer = 1      # 层数（这里只用一层）\nblock_size = 8   # 最大序列长度\nhead_dim = n_embd \u002F\u002F n_head  # 每个注意力头负责的维度\n\n# 辅助函数：创建一个矩阵，每个元素是一个服从高斯分布的小纸条\nmatrix = lambda nout, nin, std=0.02: [[Value(random.gauss(0, std)) for _ in range(nin)] for _ in range(nout)]\n\n# 初始化模型参数（大脑里的各种表格）\nstate_dict = {\n    'wte': matrix(vocab_size, n_embd),       # 字母特征表\n    'wpe': matrix(block_size, n_embd),       # 位置特征表\n    'lm_head': matrix(vocab_size, n_embd),   # 输出层\n}\n\nfor i in range(n_layer):\n    state_dict[f'layer{i}.attn_wq'] = matrix(n_embd, n_embd)          # 注意力 query 投影矩阵\n    state_dict[f'layer{i}.attn_wk'] = matrix(n_embd, n_embd)          # 注意力 key 投影矩阵\n    state_dict[f'layer{i}.attn_wv'] = matrix(n_embd, n_embd)          # 注意力 value 投影矩阵\n    state_dict[f'layer{i}.attn_wo'] = matrix(n_embd, n_embd, std=0)   # 注意力输出投影矩阵（初始化为0）\n    state_dict[f'layer{i}.mlp_fc1'] = matrix(4 * n_embd, n_embd)      # MLP 第一层\n    state_dict[f'layer{i}.mlp_fc2'] = matrix(n_embd, 4 * n_embd, std=0) # MLP 第二层（初始化为0）\n\n# 把所有参数（小纸条）展平到一个列表里，方便优化\nparams = [p for mat in state_dict.values() for row in mat for p in row]\nprint(f\"参数总数: {len(params)}\")\n\n# 定义大脑的思考方式（模型前向传播）\n\ndef linear(x, w):\n    \"\"\"线性层：输入向量x，权重矩阵w，输出x与w每行的点积\"\"\"\n    return [sum(wi * xi for wi, xi in zip(wo, x)) for wo in w]\n\ndef softmax(logits):\n    \"\"\"将分数转换为概率分布\"\"\"\n    max_val = max(val.data for val in logits)\n    exps = [(val - max_val).exp() for val in logits]\n    total = sum(exps)\n    return [e \u002F total for e in exps]\n\ndef rmsnorm(x):\n    \"\"\"RMSNorm 归一化\"\"\"\n    ms = sum(xi * xi for xi in x) \u002F len(x)\n    scale = (ms + 1e-5) ** -0.5\n    return [xi * scale for xi in x]\n\ndef gpt(token_id, pos_id, keys, values):\n    \"\"\"GPT 模型的前向传播\"\"\"\n    # 取出当前字母的特征和位置特征\n    tok_emb = state_dict['wte'][token_id]\n    pos_emb = state_dict['wpe'][pos_id]\n    x = [t + p for t, p in zip(tok_emb, pos_emb)]  # 相加得到综合表示\n    x = rmsnorm(x)\n\n    for li in range(n_layer):\n        # 1) 多头注意力块\n        x_residual = x\n        x = rmsnorm(x)\n\n        q = linear(x, state_dict[f'layer{li}.attn_wq'])\n        k = linear(x, state_dict[f'layer{li}.attn_wk'])\n        v = linear(x, state_dict[f'layer{li}.attn_wv'])\n\n        # 将当前 key 和 value 存入缓存\n        keys[li].append(k)\n        values[li].append(v)\n\n        x_attn = []\n        for h in range(n_head):\n            hs = h * head_dim\n            q_h = q[hs:hs + head_dim]\n            k_h = [ki[hs:hs + head_dim] for ki in keys[li]]\n            v_h = [vi[hs:hs + head_dim] for vi in values[li]]\n\n            # 计算注意力分数\n            attn_logits = [sum(q_h[j] * k_h[t][j] for j in range(head_dim)) \u002F head_dim ** 0.5\n                           for t in range(len(k_h))]\n            attn_weights = softmax(attn_logits)\n\n            # 加权求和得到头输出\n            head_out = [sum(attn_weights[t] * v_h[t][j] for t in range(len(v_h))) for j in range(head_dim)]\n            x_attn.extend(head_out)\n\n        x = linear(x_attn, state_dict[f'layer{li}.attn_wo'])\n        x = [a + b for a, b in zip(x, x_residual)]  # 残差连接\n\n        # 2) MLP 块\n        x_residual = x\n        x = rmsnorm(x)\n        x = linear(x, state_dict[f'layer{li}.mlp_fc1'])\n        x = [xi.relu() ** 2 for xi in x]           # ReLU² 激活\n        x = linear(x, state_dict[f'layer{li}.mlp_fc2'])\n        x = [a + b for a, b in zip(x, x_residual)]  # 残差连接\n\n    # 输出层，得到词汇表大小的分数\n    logits = linear(x, state_dict['lm_head'])\n    return logits\n\n# 第四步：教大脑学习（训练）\n\n# Adam 优化器参数\nlearning_rate, beta1, beta2, eps_adam = 1e-2, 0.9, 0.95, 1e-8\nm = [0.0] * len(params)  # 一阶动量缓存\nv = [0.0] * len(params)  # 二阶动量缓存\n\nnum_steps = 500  # 训练步数\nfor step in range(num_steps):\n    # 取一个名字，转换为数字列表，首尾加上 BOS\n    doc = docs[step % len(docs)]\n    tokens = [BOS] + [uchars.index(ch) for ch in doc] + [BOS]\n    n = min(block_size, len(tokens) - 1)  # 有效预测长度\n\n    # 初始化每层的记忆缓存和损失列表\n    keys, values = [[] for _ in range(n_layer)], [[] for _ in range(n_layer)]\n    losses = []\n\n    # 对每个位置进行预测\n    for pos_id in range(n):\n        token_id, target_id = tokens[pos_id], tokens[pos_id + 1]\n        logits = gpt(token_id, pos_id, keys, values)\n\n        probs = softmax(logits)\n        loss_t = -probs[target_id].log()  # 负对数似然损失\n        losses.append(loss_t)\n\n    # 平均损失\n    loss = (1 \u002F n) * sum(losses)\n\n    # 反向传播，计算梯度\n    loss.backward()\n\n    # 余弦退火学习率\n    lr_t = learning_rate * 0.5 * (1 + math.cos(math.pi * step \u002F num_steps))\n\n    # 用 Adam 更新所有参数\n    for i, p in enumerate(params):\n        m[i] = beta1 * m[i] + (1 - beta1) * p.grad\n        v[i] = beta2 * v[i] + (1 - beta2) * p.grad ** 2\n        m_hat = m[i] \u002F (1 - beta1 ** (step + 1))\n        v_hat = v[i] \u002F (1 - beta2 ** (step + 1))\n        p.data -= lr_t * m_hat \u002F (v_hat ** 0.5 + eps_adam)\n        p.grad = 0  # 梯度清零\n\n    print(f\"步数 {step + 1:4d} \u002F {num_steps:4d} | 损失 {loss.data:.4f}\")\n\n# 第五步：让电脑自己编名字（推理）\n\ntemperature = 0.5  # 温度参数，控制随机性\nprint(\"\\n--- 推理生成 ---\")\nfor sample_idx in range(20):\n    keys, values = [[] for _ in range(n_layer)], [[] for _ in range(n_layer)]\n    token_id = BOS\n    sample = []\n\n    for pos_id in range(block_size):\n        logits = gpt(token_id, pos_id, keys, values)\n        # 温度调整\n        probs = softmax([l \u002F temperature for l in logits])\n        # 按概率随机采样下一个 token\n        token_id = random.choices(range(vocab_size), weights=[p.data for p in probs])[0]\n        if token_id == BOS:\n            break\n        sample.append(uchars[token_id])\n\n    print(f\"样本 {sample_idx + 1:2d}: {''.join(sample)}\")\n```","https:\u002F\u002Foxqtewbrpuiouqqjrvdv.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fpublic-media\u002F2026-07-16\u002Ffe8eb5f4-804c-4338-982f-60137ea8fbba.jpg",[],[],"龙家轩","https:\u002F\u002Fweatheraintbad.com","f39339b1-aaa6-4e86-b0c2-a6e6a21113b5",{"id":18,"name":19,"slug":20,"description":21},"8649729c-92bf-4ec7-b98b-3bae4271318b","思考","thought","从项目或实操经验中延伸出的思考",[23,27,29,33,37,41],{"id":24,"name":25,"slug":26},"7c76bfc2-f80f-4ee0-a95d-27bd8708b434","技术","slug",{"id":28,"name":19,"slug":20},"68cedb55-2cac-412f-8f81-fda8c7d686dd",{"id":30,"name":31,"slug":32},"88d2bc27-0e0f-468a-b907-2991cb97b87b","人工智能","ai",{"id":34,"name":35,"slug":36},"a2ccffe0-49b2-458b-baf6-a83a1b20443d","大语言模型","llm",{"id":38,"name":39,"slug":40},"4c2bbea6-eab7-40a8-8447-1de478ff7749","分析","analyse",{"id":42,"name":43,"slug":44},"144abe77-0dc6-4f66-a176-20bddb1c0bfa","编程","coding",null,"published",true,3,0,"2026-03-31T00:00:00.000Z","2026-07-17T03:36:08.463Z","2026-07-16T11:15:46.645Z",[54,63,73],{"id":55,"type":6,"title":56,"slug":57,"summary":58,"coverUrl":59,"authorName":14,"sno":60,"publishedAt":61,"createdAt":62},"bc921879-1742-4bf4-98b1-6298a860e475","对编程语言发展历程的思考：从比特到思想","programminglanguages","编程语言的进化史，是人类不断把复杂抽象成简单的过程","https:\u002F\u002Foxqtewbrpuiouqqjrvdv.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fpublic-media\u002F2026-07-16\u002F395fe295-8b25-4ff5-bf50-0d42340b8e27.jpg",50,"2026-04-03T00:00:00.000Z","2026-07-16T04:13:19.163Z",{"id":64,"type":6,"title":65,"slug":66,"summary":67,"coverUrl":68,"authorName":69,"sno":70,"publishedAt":71,"createdAt":72},"1c047558-44f5-4e84-be6a-5468883d3178","中式巨构：技术终于有能力回应一个古老的梦","chinese-heaven","没有被“发明”，没有被“想出”，它只是被“看见”了。我们做了几千年的梦，终于在2026年，被AI显影在屏幕之上。","\u002Fuploads\u002F2026-08-15\u002Fc987bd06-cca5-4e10-b7a0-1e3213fc78d8.jpg","Foundit",1,"2026-08-15T00:00:00.000Z","2026-08-15T04:53:56.292Z",{"id":74,"type":6,"title":75,"slug":76,"summary":77,"coverUrl":78,"authorName":69,"sno":70,"publishedAt":79,"createdAt":80},"6fb796fa-60a1-4dba-bd19-82a6cda0fe78","手段与目的：一台没有\"羞耻\"的理性机器","rational-machine","Hugging Face 遭 OpenAI AI 模型自主攻击事件中，真正值得警惕的，不是一台想害我们的机器，而是一台只想完成任务、且对手段毫无羞耻的理性机器。","https:\u002F\u002Foxqtewbrpuiouqqjrvdv.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fpublic-media\u002F2026-07-23\u002Fc90cf6fb-faa4-4454-91b5-d7356c144277.jpg","2026-07-23T00:00:00.000Z","2026-07-23T08:39:37.528Z"]