MicroGPT

一個看得完的語言模型

4192 個參數、199 行純 Python——結構與大型模型完全相同,只是縮小
大型語言模型 MicroGPT 詞彙表:十幾萬個 token 27 個字元(a–z + BOS) 向量:數千維 16 維 數十層 ・ 數十個 Attention 頭 1 層 ・ 4 個頭 任務:接續任何文字 任務:學人名,產生下一個字母
← → 翻頁 ・ f 全螢幕
explanation of the great work karpathy.github.io/2026/02/12/microgpt

先看結果執行這支程式,會得到什麼

讀 32033 個真實人名,寫出 20 個不存在的名字
$ python microgpt.py num docs: 32033 vocab size: 27 num params: 4192 step 1000 / 1000 | loss 2.6497 --- inference (new, hallucinated names) --- sample 1: kamon sample 2: ann sample 3: karai sample 4: jaire sample 5: vialan sample 6: karia sample 7: yeran sample 8: anna sample 9: areli sample 10: kaina sample 11: konna sample 12: keylen sample 13: liole sample 14: alerin sample 15: earan sample 16: lenne sample 17: kana sample 18: lara sample 19: alela sample 20: anton 讀入 32033 個真實的英文名字 詞彙表 27 項:26 個字母 + BOS 模型的全部參數只有 4192 個 誤差指標:數字愈小,預測愈準 這 20 個名字都不存在 讀進來的 32033 個名字裡,沒有 任何一個是這樣;全部由模型 一個字母、一個字母自己產生。 這場演講要講的 就是這支程式從讀進名字、 到寫出上面這 20 個名字, 中間到底發生了什麼。 本場只看模型如何寫出下一個字母這一段。 後續各頁以資料裡的 emma 為例,拆解的是同一套流程。

概念 1自回歸:一次一個 token

輸出成為下一輪的輸入
本例採字元級 tokenizer:一個字母一個 token;BOS 標記序列開頭。 已有的文字(context) BOS e m 模型走一遍 整段 context 都會被讀到 下一個 token m emma 的第 2 個 m 接回 context,再走一遍 逐輪展開 每一輪產生一個 token(金色為該輪新產生) 第 1 輪 BOS e m m 第 2 輪 BOS e m m a 第 3 輪 BOS e m m a BOS BOS 再次出現=「寫完了」,迴圈在此停止。 文字為什麼要先切成 token、又為什麼會切成這樣,見概念 3。 白話 每寫一個字之前,先重新讀一次目前的全文, 再決定下一個字。 整段回覆不是一次成形,而是同一個流程 重複數百、數千次的結果。 實作上,各輪算過的中間結果(K/V)會留存 為記憶(KV cache),直接取用不必重算。

概念 2推論的五個步驟

文字進來,一個 token 出去
每產生一個 token,就完整走過這五個步驟 本頁只看「一輪之內」;產生的 token 如何接回輸入,見概念 1。 ① Tokenize 把文字切開, 換成 token 編號 ② Embedding 每個編號查表, 換成一條向量 ③ Transformer ×N Attention 只看前面 MLP 加工 向量帶上了上下文 ④ 打分 算出每個 token 各自的機率 ⑤ 抽樣 依機率取出 一個 token 詳見概念 3 詳見概念 4 詳見概念 5、6 詳見概念 7 接下來五頁,依序把 ① 到 ⑤ 逐一放大。

概念 3① Tokenize:文字換成編號

先決定一份固定的清單,模型終其一生只認得表上的項目
ⓐ 為什麼需要這一步 模型只做數字運算,讀不了文字。 文字 編號 讀入 產生 中間靠的是一份固定的清單:詞彙表。 表訂下去就固定了 表以外的字,讀不進來, 也永遠寫不出來。 ⓑ 本次採用的切法:字元 27 項 一個字元就是一個 token。 詞彙表 26 個字母 + BOS a b c … m … z 0 1 2 12 25 BOS 26 BOS 標示一個名字的開頭與結尾。 實例 emma BOS e m m a 26 4 12 12 0 往後模型看到的只有這串編號。 後續各頁都以「emma 寫到第二個 m」 這個時刻為例。 ⓒ 切法的取捨 切得細或粗,是可以選的。 字元級(本次) 27 項 e m m a 一輪一個字母,走好幾輪。 子詞級(大型模型) 十幾萬項 emm a 一輪可寫出一整個詞根。 五個步驟完全相同, 差別只在表的大小與粒度。 「一個 token 是多少東西」就在這步決定。

概念 4② Embedding:編號換成向量

兩張對照表,一次查表、一次相加
ⓐ wte:我是誰 27 列 × 16 一張 27 列的表,每個 token 一列, 每列是 16 個數字。 編號 12 取出第 12 列 …共 16 個 查表就是這麼直接:沒有計算, 只是把第 12 列抄出來。 這 16 個數字是什麼? 沒有人指定過它們的意義, 全部由訓練調整出來。 用途相近的字元,這 16 個數字 也會比較接近。 「一個字元的意義」在模型裡, 就等於 16 維空間中的一個座標。 ⓑ wpe:我在第幾個 16 列 × 16 emma 有兩個 m。它們查 wte 得到的 16 個數字,完全一樣。 BOS e m m a 0 1 2 3 4 ← 位置 位置不同,但角色也不同:一個接在 e 後面,一個接在 m 後面。模型必須分得 出來,否則無從決定下一個字母。 因此另備一張表:每個位置一列, 同樣是 16 個數字。 位置 0 … 位置 1 … 表的列數=能讀多長 本次是 16 列,最多處理 16 個位置, 這就是所謂的 context length。 大型模型的這張表有數萬到數十萬列。 ⓒ 兩者相加 wte 第 12 列 + wpe 第 2 列 進入 ③ 的向量 逐位相加:第 1 個數加第 1 個數,依此類推。 值得停下來看一眼 整個第 ② 步,只有「查兩次表」 和「加一次」,沒有別的運算。 後面所有的計算,全部建立在 這條相加得到的向量之上。 相加之後,這條向量同時帶著 「我是誰」與「我在第幾個」。 接下來,它才開始讀別人。

概念 5Transformer 內部的三件事

Attention 溝通 ・ MLP 加工 ・ 殘差疊加
ⓐ Attention:互相取用 橫向 概念 4 得到的向量,還沒讀過任何前文。 BOS e m 前方各位置 的資訊流入 m 這條向量向前方各位置索取資訊, 依相關程度決定各取多少, 再加總成為自己的新向量。 BOS 與 e 只提供資訊,本輪不必重算。 位置之間的橫向溝通,僅發生於此。 這一步實際如何運算 —— 三種角色、 相似度、多個頭 —— 詳見概念 6。 ⓑ MLP:各自加工 16 → 64 → 16 Attention 之後,每個位置各自通過 同一個小型網路,位置之間不互通。 16 個數值 展開為 64 項檢查 ReLU 壓回 16 個數值 展開=同時進行 64 項檢查,例如 「前一字是否為母音」「是否接近結尾」。 ReLU:檢查結果為正者保留,為負者歸零。 再壓回 16 個數值,將結論寫回這條向量。 一橫一縱,交替進行 Attention:位置之間橫向溝通。 MLP:每個位置縱向加工。 一個 Transformer block 即為這兩站。 ⓒ 疊回原本的資料 殘差 ・ ×N 原資料 留一份 RMSNorm Attention 或 MLP + 疊加後的新資料 此流程於單一 block 內執行兩次: 一次供 Attention,一次供 MLP。 疊加,而非取代 子層的輸出加回原資料,原有資訊不被 覆蓋;進出形狀一致,故可重複 N 次。 RMSNorm 進入子層前,先將數值壓回相近尺度。 它不含任何訓練參數,詳見概念 8。

概念 6放大看 Attention

三種角色 ・ 取用比重 ・ 多個頭
① 三種角色如何產生 Wq ・ Wk ・ Wv m 的向量 分別乘上三張權重表: Q 我在找什麼 Wq K 我是什麼 Wk V 我能提供什麼 Wv Wq、Wk、Wv 是三張權重表, 其內容由訓練決定,並非人工指定。 前文的每個位置,同樣各自產生 自己的 Q、K、V。 已算出的 K、V 會留存重複取用 (KV cache,見概念 1)。 ② 如何決定取用比重 相似度 m 的 Q 與前方各位置的 K 逐一比對, 兩者方向愈接近,分數愈高。 方向接近 → 分數高 方向偏離 → 分數低 每個位置各得到一個分數,再一併換算 成加總為 1 的比重。 分數高的位置,取用其 V 的比重就大; 各處的 V 依比重加總,成為新的向量。 ③ 多個頭平行進行 4 個頭 ・ wo 一條向量 頭 1 頭 2 頭 3 頭 4 每個頭各自進行一次 ① ② 四份結果接回一條向量 再通過權重表 wo 各頭關注的面向不同:有的偏向鄰近 字元,有的偏向較遠的位置。 這樣的分工由訓練自然形成,並非 事先指定哪個頭負責什麼。 wo 同樣是一張由訓練得到的權重表。

概念 7④ 打分與 ⑤ 抽樣

從一條向量,到真的寫出一個字母
ⓐ 出來的是一條向量 走完 Transformer 的,自始至終只有 m 這一條向量。 m 的向量 Transformer ×N BOS、e 的 K、V …共 16 個 出來的仍是一條 16 個數字的向量, 但它已經讀過 BOS 與 e。 BOS 與 e 在先前各輪就算過了,其 K、V 留在記憶裡(KV cache),本輪直接取用, 不必再跑一次 Transformer。 每一輪只產生一個 token, 而它接在目前最後一個字之後。 ⓑ 打分:向量變成機率 lm_head ・ softmax …共 16 個 lm_head(27 × 16) m 27 個字元,各得到一個機率,加總為 1 這條向量通過 lm_head,得到 27 個分數, 每個字元一個;softmax 再將這些分數 換算成加總為 1 的機率。 與第 ② 步方向相反 wte 由字元查出向量; lm_head 由向量算回每個字元的分數。 ⓒ 抽樣 全流程唯一的隨機 依這組機率抽取一次,取出一個 token。 落在哪一段,就取出哪一個字元 m 本輪產生:emma 的第 2 個 m 前面每一步都是固定計算:同樣的輸入 必定得到同樣的分數。隨機僅發生於此。 溫度(temperature) 抽樣前可調的一個數字:27 個分數 先同除以它,再交給 softmax。 低 → 分佈變尖,輸出保守;高 → 分佈變平。 抽到 BOS,即代表這個名字結束。 否則將新 token 接回 context, 回到第 ① 步,再走一輪(概念 1)。

概念 84192 個參數在哪裡

模型裡只有兩種東西:查表得到的數字,與固定的算法
參數:模型裡實際存放的數字 名稱沿用程式中的變數名,稍後閱讀 MicroGPT 原始碼時會直接對應到。 格數 wte 字元對照表:27 個字元 × 16 432 wpe 位置對照表:16 個位置 × 16 256 Wq Wk Wv 產生 Q、K、V 的三張表 768 wo 多個頭合併後的表 256 MLP 展開 16→64、再壓回 64→16 2048 lm_head 由向量算出 27 個字元的分數 432 合計 4192 無參數:固定算法 RMSNorm 將一條向量的數值壓回相近尺度 ReLU 正值保留,負值歸零 softmax 把一組分數換算成加總為 1 的機率 + 兩條向量逐元素相加,融合彼此的意義 抽樣 依機率取出一個 token 這些步驟不存放任何數字,每次都以 相同規則計算,對任何模型都一樣。 接下來:同一條管線,以 3D 重新走過一次 3D 流程以 emma 的第一個 m 當主角——正是前面各頁追蹤的那條向量——逐關展示這五個步驟。