建GPT:核心組件與工程實踐)
今天文章的主題是“用 PyTorch 從零構(gòu)建 GPT”。開頭不繞彎子先從我一次帶新人跑代碼的真實場景說起因為那個場景幾乎濃縮了所有初學(xué)者會遇到的問題。大概是一年前有個剛?cè)?NLP 方向的同學(xué)問我他想自己寫一個 GPT但照著開源代碼敲了一遍前向傳播還是跑不通。他把報錯信息發(fā)給我我發(fā)現(xiàn)問題根本不在模型結(jié)構(gòu)上而是他用的 PyTorch 版本和代碼里的F.scaled_dot_product_attention不兼容。他折騰了兩天最后把 PyTorch 降級到 2.0 以上問題就消失了。這種經(jīng)歷其實很常見。很多人以為從零構(gòu)建 GPT 的難點在“注意力機(jī)制”“Transformer 架構(gòu)”“訓(xùn)練策略”這些聽起來很高級的概念上。但真正動手以后你會發(fā)現(xiàn)攔住你的往往不是算法理解而是環(huán)境、版本、數(shù)據(jù)類型、設(shè)備分配這些看起來特別不起眼的問題。這篇文章我想講的不是把 GPT 的源碼逐行抄一遍給你看。我更想和你聊清楚如果你真的想用 PyTorch 從零構(gòu)建一個 GPT你最應(yīng)該把精力花在什么地方哪些坑是可以提前避開的什么樣才算真正“跑通”了以及從“能跑通”到“能用”之間還差哪些關(guān)鍵拼圖。這個主判斷先放在這里從零構(gòu)建 GPT 這件事真正鍛煉你的不是 Transformer 理論的背誦而是一套工程化拆解能力——把論文細(xì)節(jié)變成模型代碼把模型代碼變成可訓(xùn)練流程再把可訓(xùn)練流程變成可控的實驗。任何一個環(huán)節(jié)掉鏈子你都會卡在原地。1. 先搞清楚“從零構(gòu)建 GPT”到底是在構(gòu)建什么很多教程喜歡把 GPT 描述成一棟宏偉的建筑但實際動手時你是在一塊一塊地砌磚。你不需要真的去實現(xiàn)一個 GPU 矩陣乘法庫也不需要自己寫 CUDA 內(nèi)核。你要做的是用 PyTorch 這種深度學(xué)習(xí)框架提供的“積木”把論文《Attention Is All You Need》和 GPT 系列的技術(shù)報告中描述的結(jié)構(gòu)一塊一塊還原出來。1.1 它不是一個“大”模型而是一套有邊界的結(jié)構(gòu)如果你搜索“大語言模型”看到的都是幾百億參數(shù)、幾千張 GPU 之類的新聞。但你自己動手用 PyTorch 構(gòu)建 GPT 時完全不需要追求“大”。你完全可以先寫一個參數(shù)只有幾百萬的小模型放在 CPU 上也能跑甚至在普通的筆記本上就能完成前向傳播和過擬合測試。這里有一個非常重要的認(rèn)知轉(zhuǎn)變GPT 并不是“因為大才叫 GPT”而是它采用了 GPT 這種架構(gòu)。架構(gòu)決定了模型如何組織信息流參數(shù)規(guī)模決定了它能裝下多少知識。你從零構(gòu)建時核心目標(biāo)是把 GPT 架構(gòu)中的幾個關(guān)鍵組件準(zhǔn)確實現(xiàn)出來Tokenizer分詞器把文本切分成 token再把 token 映射成整數(shù) ID。Token Embedding Position Embedding把 token ID 變成向量并把位置信息編碼進(jìn)去。Transformer Decoder Block這是重中之重包含多頭自注意力機(jī)制、交叉注意力嚴(yán)格來說 GPT 的 Decoder Block 里沒有交叉注意力這一點后文會細(xì)說、前饋神經(jīng)網(wǎng)絡(luò)和層歸一化。輸出投影層把最后一個隱藏狀態(tài)映射到詞表大小的概率分布上。很多人第一次接觸會搞混一個點GPT 用的 Transformer Decoder和原始的 Transformer Decoder 并不完全一樣。原始 Transformer 的 Decoder 里有交叉注意力cross-attention用來訪問 Encoder 的輸出而 GPT 里的 Decoder Block 只有 masked self-attention 和一個前饋網(wǎng)絡(luò)。這個區(qū)別會導(dǎo)致你對結(jié)構(gòu)的理解完全不同。注意寫代碼前先想清楚你構(gòu)建的 GPT 是哪種形態(tài)。如果參考“GPT 風(fēng)格的 decoder-only”結(jié)構(gòu)就不要把 cross-attention 硬塞進(jìn)去。1.2 結(jié)構(gòu)看清了才能知道每一步代碼在做什么當(dāng)我拿到一份從零構(gòu)建 GPT 的代碼時我通常會按順序檢查四個模塊是否存在且正確輸入處理模塊原始文本能否正確變成[batch_size, seq_len]的整數(shù)張量。位置信息注入是用可學(xué)習(xí)的 Positional Embedding還是用 Sinusoidal 編碼還是用 RoPE旋轉(zhuǎn)位置編碼。注意力掩碼GPT 是自回歸模型必須保證當(dāng)前位置的注意力只能看到前面位置不能看到未來。這個掩碼做錯了整個訓(xùn)練過程會崩掉但不報錯因為損失依然會下降只是模型變成了“作弊”模型。分類頭和損失函數(shù)輸出維度是否等于詞表大小損失是否在忽略填充位置的前提下計算。如果你能把這個 4 個點講清楚寫成注釋和文檔那這篇文章的真實價值就出來了。因為大多數(shù)教程只會給你看完整代碼而不會告訴你“為什么這段代碼要這樣組織”。2. 環(huán)境搭建不是簡單的 pip install而是版本對齊工程剛才我提到有過一個同學(xué)卡在 PyTorch 版本上兩天沒進(jìn)展。這其實不是個例。從熱搜詞里能看到大量人都在搜“pytorch安裝”“pytorch環(huán)境搭建”“anaconda配置pytorch環(huán)境”“ubuntu系統(tǒng)下載pytorch教程”“pytorch cu130”這類關(guān)鍵詞。這說明環(huán)境搭建本身就是從零構(gòu)建 GPT 的第一道坎而且攔住了很多人。2.1 為什么版本不對模型代碼會出各種怪問題PyTorch 的 API 變化速度極快。舉個例子torch.nn.functional.scaled_dot_product_attention是在 PyTorch 2.0 引入的高效注意力實現(xiàn)。如果你拿到的參考代碼用了這個函數(shù)但你的 PyTorch 還是 1.x就會報一個不那么明顯的錯誤甚至有時候只是慢不是直接崩。再比如torch.compile它在 PyTorch 2.0 被引入可以明顯加速訓(xùn)練但它對 Python 版本、CUDA 版本和 GPU 型號都有要求。如果你是在 Windows 上用 pip 裝的 CPU 版 PyTorch很多加速能力都用不上。所以在開始構(gòu)建模型之前先確定幾個版本而不是拿到最新版就裝Python 版本建議 3.9 到 3.11太新有時候會遇到 wheel 不匹配PyTorch 版本建議 2.x除非你刻意學(xué)習(xí) 1.x 的舊代碼CUDA 版本取決于你的驅(qū)動支持不要盲目選 cu130依賴包版本transformers、tiktoken、numpy、datasets 等2.2 我的建議流程先固定依賴再寫代碼這里我用一個通用流程來說明具體版本號會變化但順序是穩(wěn)定的# 1. 創(chuàng)建獨立虛擬環(huán)境避免污染全局 Python conda create -n gpt-from-scratch python3.10 -y conda activate gpt-from-scratch # 2. 安裝 PyTorch根據(jù)你的機(jī)器選擇 CPU 版或 CUDA 版 # CPU 版先保證能跑通代碼 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 3. 安裝訓(xùn)練和分詞相關(guān)依賴 pip install numpy tiktoken datasets matplotlib如果你的機(jī)器有 NVIDIA 顯卡并且已經(jīng)安裝了合適的驅(qū)動你可以去 PyTorch 官網(wǎng)選擇對應(yīng) CUDA 版本的安裝命令。但這里有一個經(jīng)驗第一次用 PyTorch 從零構(gòu)建 GPT不要直接上 CUDA 版。先用 CPU 版把完整流程跑通確認(rèn)模型代碼沒有邏輯錯誤再安裝 GPU 版加速訓(xùn)練。為什么因為 CPU 和 GPU 的報錯信息不一樣把兩個變量混在一起排查你會浪費大量時間。環(huán)境搭建的核心邏輯是把你跑通的實驗環(huán)境記錄下來形成確定性的依賴清單pip freeze requirements.txt這樣你未來重跑、換機(jī)器、給別人復(fù)現(xiàn)都能回到同一起點。這一步必須在第一行模型代碼之前完成。3. 從零寫一個最小 GPT 結(jié)構(gòu)核心組件逐個拆解當(dāng)環(huán)境穩(wěn)定下來就可以開始寫模型代碼了。這個階段我的建議是不要照抄完整開源項目而是用最少的代碼把核心結(jié)構(gòu)寫出來。你可以不用考慮訓(xùn)練效率不用優(yōu)化顯存甚至可以不用考慮 GPU。你只需要一個能在小規(guī)模數(shù)據(jù)上過擬合的最小模型。3.1 最簡結(jié)構(gòu)的“零件清單”在開始寫代碼前先像列購物清單一樣把零件列出來。下面這個清單是從零構(gòu)建 GPT 時至少需要涉及的部分模塊作用常見坑點分詞器文本和 token ID 互相轉(zhuǎn)換詞表大小不一致導(dǎo)致 embedding 維度錯位詞嵌入層將 token ID 映射成向量忘記設(shè)置padding_idx或詞表大小參數(shù)位置編碼給模型提供序列順序信息序列長度和位置編碼長度不一致多頭自注意力提取上下文關(guān)系mask 形狀錯誤、attn_mask廣播問題前饋網(wǎng)絡(luò)非線性變換殘差連接維度不匹配層歸一化穩(wěn)定訓(xùn)練歸一化的維度和形狀對不對輸出投影層映射到詞表概率分布權(quán)重與嵌入層是否共享不共享也沒錯這些零件組合起來從結(jié)構(gòu)上看GPT 就是一個把“嵌入層 N 個 Transformer Decoder Block 輸出層”串起來的網(wǎng)絡(luò)。Block 里最核心的組件就是 masked multi-head self-attention。3.2 自注意力機(jī)制為什么 GPT 必須要 mask自注意力的公式看起來簡單Attention(Q, K, V) softmax(Q K^T / sqrt(d_k)) V但在 GPT 這樣的自回歸模型里Q K^T之后不能直接 softmax。因為在生成第 t 個 token 時模型不能看到第 t1、t2 等未來 token。這需要在計算注意力分?jǐn)?shù)后、softmax 之前把未來位置的值替換成一個極大的負(fù)數(shù)比如-inf這樣 softmax 之后這些位置的權(quán)重就會變成 0。如果你用了F.scaled_dot_product_attention它可以通過is_causalTrue參數(shù)直接實現(xiàn)因果掩碼非常方便。但為了理解機(jī)制我建議你至少手寫一次完整的注意力函數(shù)哪怕它慢一點import torch import torch.nn as nn import torch.nn.functional as F class CausalSelfAttention(nn.Module): def __init__(self, embed_dim, num_heads, dropout0.0): super().__init__() assert embed_dim % num_heads 0 self.embed_dim embed_dim self.num_heads num_heads self.head_dim embed_dim // num_heads self.qkv nn.Linear(embed_dim, 3 * embed_dim) self.out_proj nn.Linear(embed_dim, embed_dim) self.dropout nn.Dropout(dropout) def forward(self, x): B, T, C x.size() qkv self.qkv(x) # [B, T, 3*C] q, k, v qkv.chunk(3, dim-1) # 拆分多頭 q q.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) k k.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) v v.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) # 縮放點積注意力 attn q k.transpose(-2, -1) * (self.head_dim ** -0.5) # 因果掩碼下三角矩陣為 0上三角為 -inf causal_mask torch.tril(torch.ones(T, T, devicex.device, dtypetorch.bool)) attn attn.masked_fill(~causal_mask, float(-inf)) attn F.softmax(attn, dim-1) attn self.dropout(attn) y attn v # [B, num_heads, T, head_dim] y y.transpose(1, 2).contiguous().view(B, T, C) return self.out_proj(y)這段代碼是“示例結(jié)構(gòu)”不是某個開源項目里的原版。但它包含了你理解 GPT 必需的核心邏輯QKV 投影、多頭拆分、縮放、因果掩碼、softmax、attention 加權(quán)、輸出投影。這里有一個很容易踩的坑causal_mask只在 T 等于當(dāng)前序列長度時是對的。如果訓(xùn)練時輸入序列長度固定這沒問題但推理時如果想生成長度大于 T 的序列位置編碼和掩碼都需要外推這時就會出問題。這也是為什么很多 GPT 實現(xiàn)會使用相對位置編碼或 RoPE。3.3 從一個小模型跑通到過擬合這才是真正的“從零構(gòu)建”寫完結(jié)構(gòu)以后不要直接去找大型語料。先用一個很小的文本文件比如幾千個字的莎士比亞作品片段或者你自己寫的一段日志把它切成 token然后用一個小模型去過擬合這一段數(shù)據(jù)。你可以用一個非常小的配置config { vocab_size: 256, # 比如用字節(jié)級分詞詞表就是 0-255 block_size: 128, # 最大上下文長度 n_embd: 64, # 嵌入維度 n_head: 4, # 注意力頭數(shù) n_layer: 2, # Transformer Block 數(shù)量 batch_size: 4, learning_rate: 3e-4, }這種規(guī)模的模型在 CPU 上也能跑。關(guān)鍵目標(biāo)是讓訓(xùn)練 loss 持續(xù)下降最后在訓(xùn)練集上過擬合到非常小的數(shù)值。如果過擬合都做不到說明你的模型代碼或數(shù)據(jù)管道有問題。這是最好的調(diào)試方式。一個常見經(jīng)驗是如果你連一小段文本都無法過擬合問題大概率出在輸入 ID 和標(biāo)簽的對齊上。檢查一下你生成的訓(xùn)練樣本中輸入序列和標(biāo)簽序列是不是錯位了一個 token。GPT 訓(xùn)練時通常輸入是第 i 到 T-1 個 token標(biāo)簽是第 i1 到 T 個 token。4. 訓(xùn)練一個迷你 GPT數(shù)據(jù)、損失與采樣流程模型代碼跑通后就要進(jìn)入訓(xùn)練階段。訓(xùn)練過程就像把零件組裝好的汽車第一次點火。很多新手在這一步會發(fā)現(xiàn)模型不是“不能跑”而是“跑得非常詭異”loss 不下降、loss 變成 NaN、模型生成的內(nèi)容全是重復(fù) token。這些問題的原因往往不只在模型結(jié)構(gòu)上而是在數(shù)據(jù)準(zhǔn)備和訓(xùn)練配置上。4.1 大語言模型下載下來是什么先弄清數(shù)據(jù)和權(quán)重的區(qū)別熱搜詞里有一句“大語言模型下載下來是什么”非常能代表新手的困惑。很多人以為下載一個模型就像下載一個軟件雙擊就能打開。但實際上你下載的是一個權(quán)重文件里面全是浮點數(shù)它必須配合模型結(jié)構(gòu)代碼和分詞器文件才能運行。從零構(gòu)建 GPT 時你也會碰到同樣的問題。你的模型訓(xùn)練完之后需要把權(quán)重保存下來。通常做法是torch.save(model.state_dict(), mini_gpt.pt)這不是一個完整的模型包。它只是把模型參數(shù)以字典形式保存了。如果別人拿到這個.pt文件但沒有你的model.py他根本無法把權(quán)重加載回去。所以如果你的最終目標(biāo)是分享一個別人能用的模型不僅要保存權(quán)重還要保存配置文件、分詞器文件和一份加載腳本。在 PyTorch 生態(tài)里更推薦的是用.pt或.pth保存完整的模型 checkpoint包括優(yōu)化器狀態(tài)、訓(xùn)練步數(shù)、loss 曲線等。如果你只是保存權(quán)重那就把它視為“推理權(quán)重”加載時要注意構(gòu)建完全相同的模型結(jié)構(gòu)。4.2 訓(xùn)練過程中的 loss 曲線就是你的儀表盤GPT 訓(xùn)練時你關(guān)注的核心指標(biāo)通常有三個訓(xùn)練集 loss反映模型對訓(xùn)練數(shù)據(jù)的擬合程度。驗證集 loss反映模型的泛化能力。生成效果語言模型特有的評估方式直接看模型在給定前綴后能生成什么。如果訓(xùn)練集 loss 一直不下降先檢查學(xué)習(xí)率是否合適再檢查輸入數(shù)據(jù)是否存在大量噪聲。如果是訓(xùn)練集 loss 下降但驗證集 loss 升高那就是過擬合可以考慮增加數(shù)據(jù)、減小模型或者加 dropout。如果生成結(jié)果是亂碼或無限重復(fù)可能是分詞器配置錯誤、重復(fù)懲罰設(shè)置不當(dāng)或者模型還沒訓(xùn)練充分。我一般會建議用一個小工具來記錄 loss 曲線比如matplotlib或在CSV文件里記錄。這一個動作雖然簡單但它能讓你在訓(xùn)練過程中快速判斷模型狀態(tài)而不是等幾個小時后才發(fā)現(xiàn)模型早就跑偏了。4.3 從“訓(xùn)練完成”到“模型能生成文字”還有最后一步寫到這里很多教程會到此為止——損失下降了模型保存了任務(wù)“完成”了。但真正讓 GPT 有可用價值的是生成過程。生成過程也是自回歸的把當(dāng)前 token 序列輸入模型取最后一個位置的輸出用 softmax 得到下一個 token 的概率分布然后從中采樣一個 token接入序列繼續(xù)循環(huán)。最基礎(chǔ)的采樣方法是貪心解碼def generate(model, idx, max_new_tokens, block_size): model.eval() for _ in range(max_new_tokens): idx_cond idx[:, -block_size:] # 只保留最近的 block_size 個 token logits model(idx_cond) logits logits[:, -1, :] # 只取最后一個位置 probs F.softmax(logits, dim-1) next_token torch.argmax(probs, dim-1, keepdimTrue) idx torch.cat([idx, next_token], dim1) return idx貪心解碼每次選概率最高的 token結(jié)果往往非常平淡而且容易重復(fù)。更自然的方法是引入溫度參數(shù)和 top-k 采樣。這些策略在真正使用模型時幾乎必須掌握。但在從零構(gòu)建階段先跑通貪心解碼就好不要一開始就上復(fù)雜采樣否則你會分不清問題是出在模型沒訓(xùn)練好還是采樣策略不對。注意生成時序列長度超過block_size會導(dǎo)致問題。最常見的是位置編碼越界或因果掩碼維度錯誤。這個坑非常隱蔽很多人調(diào)了半天最后發(fā)現(xiàn)輸入序列超過了訓(xùn)練時的最大上下文長度。5. 從“能跑”到“能用來做事”你離生產(chǎn)還差幾塊拼圖當(dāng)你成功訓(xùn)練完一個小型 GPT并且它能根據(jù)前綴生成看起來有點意思的文本之后你很容易產(chǎn)生一個錯覺我已經(jīng)會構(gòu)建大語言模型了。從學(xué)習(xí)和鍛煉工程能力的角度看確實有很大收獲但距離真正“可用”還有很長的路。5.1 工程化缺失項數(shù)據(jù)質(zhì)量、訓(xùn)練效率、服務(wù)化一個真實的大語言模型從訓(xùn)練到上線涉及的東西遠(yuǎn)不止模型結(jié)構(gòu)本身。熱搜詞里很多人關(guān)注的“本地部署大語言模型”“大語言模型界面”就對應(yīng)了這條鏈路。從工程視角看把訓(xùn)練代碼跑通只是一小步。后續(xù)你還需要處理數(shù)據(jù)清洗與配比從原始語料到高質(zhì)量訓(xùn)練數(shù)據(jù)需要去重、過濾垃圾文本、控制各類數(shù)據(jù)比例。這一步對模型最終效果的影響往往比模型結(jié)構(gòu)和訓(xùn)練參數(shù)還大。訓(xùn)練加速與顯存優(yōu)化梯度累積、混合精度、分布式訓(xùn)練、模型并行、ZeRO 優(yōu)化等。你訓(xùn)練的小模型用不上但這是從小模型走向大模型的必經(jīng)之路。評估體系不能只看 loss 下降還要有評估集評測、人類反饋、對齊等。推理服務(wù)把模型部署成 API 接口做顯存管理、并發(fā)控制、請求排隊、流式輸出。這些內(nèi)容看起來和“從零構(gòu)建 GPT”無關(guān)但其實是同一個命題。因為一旦你確定以后要長期在這條路上走從第一天就應(yīng)該清楚模型代碼只是一張門票完整的大語言模型工程還有全景圖和路線圖。越早意識到這一點你就越不會把“訓(xùn)練了一個小模型”當(dāng)成最終的終點。5.2 如何繼續(xù)深入一個可復(fù)用的進(jìn)階路線如果你已經(jīng)完成了文章前面所有步驟下一個階段可以按下面的路徑繼續(xù)往前探索。這也是我比較推薦的一個“三步延伸法”復(fù)現(xiàn)一個開源的中型 GPT 結(jié)構(gòu)比如 1 億參數(shù)左右的模型。找一個公開的教程或代碼庫自己照著思路寫一遍先不追求完全一致重點是理解每個組件為什么這樣設(shè)計。自己構(gòu)造一個真實任務(wù)不要只用莎士比亞文本做學(xué)習(xí)玩具找一個你能拿到的、有明確領(lǐng)域結(jié)構(gòu)的數(shù)據(jù)集比如代碼補(bǔ)全、GitHub Issue 摘要、技術(shù)問答等訓(xùn)練一個針對特定任務(wù)的 GPT。從單機(jī)訓(xùn)練切到分布式訓(xùn)練哪怕你只有一臺機(jī)器、兩張顯卡也可以體驗DistributedDataParallelDDP。這會讓你對“大語言模型為什么需要多卡”和“數(shù)據(jù)并行”有真正的直覺。這三步看起來每一步都不復(fù)雜但它能推動你從一個“會用 PyTorch 寫模型的人”變成一個“能判斷技術(shù)方案、能做實驗設(shè)計、能排查訓(xùn)練鏈路問題”的人。這才是從零構(gòu)建 GPT 真正值得訓(xùn)練的能力。6. 常見問題排查遇到錯誤先修思想和環(huán)境再扣代碼最后整理一份適合放在手邊的排查思路。不針對某一段代碼而是對整個從零構(gòu)建 GPT 的過程。如果你在實驗過程中卡住按照下面的順序逐層排查大概率能比盲目搜報錯信息更快定位問題。先確認(rèn)報錯來自哪一層是數(shù)據(jù)管道、模型構(gòu)造、前向傳播、loss 計算、梯度回傳還是采樣生成不同層的報錯完全指向不同原因。再確認(rèn)環(huán)境是否對齊Python 版本、PyTorch 版本、CUDA 版本、transformers版本、tiktoken版本。版本不一致導(dǎo)致的怪問題靠讀代碼是找不出來的。接著檢查輸入輸出形狀對每個關(guān)鍵張量在調(diào)試模式下把.shape打印出來或者用assert檢查形狀。形狀不一致是最容易發(fā)現(xiàn)也最容易被忽略的錯誤。然后核對掩碼和數(shù)據(jù)對齊因果掩碼的維度對不對訓(xùn)練樣本中輸入和標(biāo)簽是否錯位block_size是否覆蓋了所有序列長度。這一步出問題很多情況下模型也能訓(xùn)練但效果極差。最后檢查數(shù)值穩(wěn)定性loss 是否出現(xiàn)nan權(quán)重是否變成nan學(xué)習(xí)率是不是太大梯度是否爆掉。如果是優(yōu)先降低學(xué)習(xí)率或者加入梯度裁剪。這部分如果你已經(jīng)順利跑到生成階段大概率不會遇到災(zāi)難級的錯誤。但未來你寫完更復(fù)雜的代碼、遷移到其他框架或更高版本的 PyTorch 時這套排查鏈路可以反復(fù)復(fù)用。回到最開始的那個判斷。用 PyTorch 從零構(gòu)建 GPT本質(zhì)上是一個工程訓(xùn)練而不是理論背誦。你可以在一天內(nèi)看完注意力機(jī)制的講解但要自己寫出一段既能訓(xùn)練又能生成文本的最小 GPT通常需要幾天甚至更久而且大部分時間會花在環(huán)境修復(fù)、形狀調(diào)整、掩碼錯誤和數(shù)據(jù)對齊上。這個時間花得值。因為只有真正寫過一遍你才會理解為什么大語言模型需要海量數(shù)據(jù)、為什么自回歸結(jié)構(gòu)能生成自然語言、為什么一個看似簡單的 Transformer Block 能裝下那么多知識。這些理解比單純跑一個開源代碼能帶來的收獲要深得多。所以如果你正在準(zhǔn)備用 PyTorch 構(gòu)建自己的第一個 GPT我的建議只有一句話先裝好環(huán)境再把最小模型跑通讓它在小數(shù)據(jù)上過擬合一次。這一步做到了你的從零構(gòu)建之路就已經(jīng)成功了三分之一。剩下的三分之二都是在學(xué)會面對不確定性時不斷修正自己判斷的過程。