AI工程:為什么先別急著調(diào)包)
1. 從零搭建AI工程能力為什么我勸你別一上來(lái)就調(diào)包這兩年“AI工程”這個(gè)詞被說(shuō)得太多了多到有點(diǎn)變味。招聘JD上寫(xiě)著“AI工程師”進(jìn)去一看是調(diào)API課程大綱寫(xiě)著“從零到一”點(diǎn)開(kāi)第一節(jié)課是pip install transformers。我不是說(shuō)調(diào)包不好工程效率本來(lái)就是靠抽象層堆出來(lái)的但問(wèn)題在于——如果你不知道transformers里generate()背后發(fā)生了什么遇到顯存炸了、輸出亂碼、推理慢十倍的時(shí)候你連從哪下手都不知道。ai-engineering-from-scratch這個(gè)標(biāo)題我理解的核心不是“不用框架”而是把AI工程當(dāng)成一門(mén)手藝來(lái)練從張量怎么在內(nèi)存里排布、梯度怎么回傳、注意力怎么算到推理服務(wù)怎么部署、顯存怎么省、延遲怎么壓。這套東西你光看論文是學(xué)不會(huì)的必須自己手寫(xiě)一遍、跑崩幾次、再修好才算真正長(zhǎng)在身上。這篇文章適合誰(shuí)看如果你是剛轉(zhuǎn)方向的學(xué)生或者做了幾年后端想切AI工程又或者你已經(jīng)在調(diào)包但總覺(jué)得心里沒(méi)底那這篇就是寫(xiě)給你的。我會(huì)按我自己帶人、自己踩坑的順序把從零搭建AI工程能力的路徑拆開(kāi)講——不是列書(shū)單是講每一步為什么這么走、坑在哪、怎么驗(yàn)證自己真的會(huì)了。2. 整體學(xué)習(xí)路徑設(shè)計(jì)為什么我堅(jiān)持“先手寫(xiě)再調(diào)包”2.1 先搞清楚“AI工程”到底包含哪幾塊能力很多人把AI工程等同于“訓(xùn)練模型”這是最大的誤解。訓(xùn)練只是其中一環(huán)而且在實(shí)際工作里訓(xùn)練代碼往往是最短的那部分。我習(xí)慣把AI工程拆成四層能力數(shù)學(xué)與算法層矩陣運(yùn)算、梯度推導(dǎo)、損失函數(shù)、優(yōu)化器。這層決定你能不能看懂論文、能不能改模型結(jié)構(gòu)。框架與實(shí)現(xiàn)層張量操作、自動(dòng)求導(dǎo)、數(shù)據(jù)加載、混合精度。這層決定你能不能把想法變成能跑的代碼。訓(xùn)練與調(diào)優(yōu)層數(shù)據(jù)清洗、超參搜索、分布式訓(xùn)練、斷點(diǎn)續(xù)訓(xùn)。這層決定你能不能把模型訓(xùn)到可用。部署與運(yùn)維層模型導(dǎo)出、推理優(yōu)化、服務(wù)化、監(jiān)控告警。這層決定你的模型能不能真正產(chǎn)生價(jià)值。ai-engineering-from-scratch的關(guān)鍵在于這四層你都得碰而且順序不能亂。我見(jiàn)過(guò)太多人直接跳到第四層用現(xiàn)成的推理框架搭了個(gè)服務(wù)結(jié)果模型精度掉點(diǎn)都不知道為什么——因?yàn)樗焕斫鈱?dǎo)出時(shí)算子融合對(duì)數(shù)值精度的影響。2.2 為什么“從零手寫(xiě)”比“直接調(diào)包”更高效這里有個(gè)反直覺(jué)的結(jié)論短期看調(diào)包快長(zhǎng)期看手寫(xiě)快。原因很簡(jiǎn)單調(diào)包時(shí)你遇到問(wèn)題只能搜issue、猜參數(shù)而手寫(xiě)過(guò)一遍的人看到報(bào)錯(cuò)就能定位到是哪個(gè)環(huán)節(jié)的問(wèn)題。舉個(gè)我自己的例子。早年我用PyTorch訓(xùn)練一個(gè)文本分類(lèi)模型loss死活不降。我調(diào)了學(xué)習(xí)率、換了優(yōu)化器、加了正則折騰兩天沒(méi)結(jié)果。后來(lái)我靜下心用NumPy手寫(xiě)了一遍兩層MLP的前向和反向才發(fā)現(xiàn)問(wèn)題出在數(shù)據(jù)預(yù)處理——我把標(biāo)簽和特征的維度搞反了廣播機(jī)制讓代碼沒(méi)報(bào)錯(cuò)但梯度全錯(cuò)了。這種bug調(diào)包時(shí)你根本看不出來(lái)因?yàn)榭蚣芴皩捜荨绷恕K晕业慕ㄗh是每個(gè)核心組件至少手寫(xiě)一次最小可用版本。不用寫(xiě)得多優(yōu)雅能跑通、能驗(yàn)證梯度正確就行。手寫(xiě)完之后再用框架實(shí)現(xiàn)一遍對(duì)比兩者的輸出你會(huì)對(duì)框架的抽象有完全不同的理解。2.3 一條可落地的四周路徑按每周10小時(shí)算我把這條路徑壓縮成四周適合有Python基礎(chǔ)、學(xué)過(guò)一點(diǎn)線代和微積分的人周次核心任務(wù)產(chǎn)出物驗(yàn)證標(biāo)準(zhǔn)第1周手寫(xiě)張量庫(kù)與自動(dòng)求導(dǎo)一個(gè)支持加減乘、矩陣乘、ReLU的迷你 autograd梯度數(shù)值校驗(yàn)誤差小于1e-6第2周手寫(xiě)注意力與Transformer塊單頭注意力前饋網(wǎng)絡(luò)的前向反向與PyTorch同結(jié)構(gòu)輸出誤差小于1e-5第3周訓(xùn)練一個(gè)小型語(yǔ)言模型在字符級(jí)數(shù)據(jù)集上訓(xùn)練loss降到合理區(qū)間能生成通順的短句第4周模型導(dǎo)出與推理服務(wù)一個(gè)HTTP接口支持并發(fā)請(qǐng)求單條延遲低于200msQPS達(dá)標(biāo)這張表的關(guān)鍵不是時(shí)間而是每一周都有可驗(yàn)證的產(chǎn)出。學(xué)AI工程最怕“感覺(jué)自己懂了”必須用數(shù)值校驗(yàn)、對(duì)比測(cè)試、性能壓測(cè)來(lái)逼自己面對(duì)現(xiàn)實(shí)。提示如果你時(shí)間有限第1周和第2周絕對(duì)不能跳。這兩周建立的是“手感”后面所有優(yōu)化都建立在這個(gè)手感上。3. 核心細(xì)節(jié)拆解手寫(xiě)自動(dòng)求導(dǎo)與注意力的關(guān)鍵點(diǎn)3.1 手寫(xiě)自動(dòng)求導(dǎo)計(jì)算圖到底怎么建自動(dòng)求導(dǎo)的核心是計(jì)算圖。每個(gè)張量是一個(gè)節(jié)點(diǎn)每個(gè)操作是一條邊反向傳播就是沿著邊把梯度乘回去。聽(tīng)起來(lái)簡(jiǎn)單但有幾個(gè)細(xì)節(jié)決定成敗。第一前向時(shí)要記錄依賴(lài)關(guān)系。比如c a * b你得知道c的梯度要往a和b傳。我的做法是每個(gè)張量存一個(gè)_prev集合和_backward函數(shù)class Tensor: def __init__(self, data, _children(), _op): self.data data self.grad 0.0 self._backward lambda: None self._prev set(_children) self._op _op def __mul__(self, other): other other if isinstance(other, Tensor) else Tensor(other) out Tensor(self.data * other.data, (self, other), *) def _backward(): self.grad other.data * out.grad other.grad self.data * out.grad out._backward _backward return out第二反向傳播要按拓?fù)湫颉2荒茈S便遞歸否則一個(gè)節(jié)點(diǎn)被多條路徑用到時(shí)梯度會(huì)重復(fù)累加或漏加。正確做法是先做一次拓?fù)渑判蛉缓竽嫘蛘{(diào)用_backward。第三梯度必須累加而不是覆蓋。用而不是因?yàn)橐粋€(gè)張量可能被多個(gè)下游節(jié)點(diǎn)使用。這個(gè)坑我踩過(guò)當(dāng)時(shí)梯度總是偏小查了一晚上才發(fā)現(xiàn)是覆蓋了。3.2 數(shù)值梯度校驗(yàn)別信自己的推導(dǎo)信數(shù)字手寫(xiě)反向傳播最容易出錯(cuò)的地方是符號(hào)和系數(shù)。我的習(xí)慣是每寫(xiě)完一個(gè)操作立刻用數(shù)值梯度校驗(yàn)def numerical_grad(f, x, eps1e-6): return (f(x eps) - f(x - eps)) / (2 * eps)拿它和解析梯度對(duì)比誤差在1e-6以?xún)?nèi)才算過(guò)。這個(gè)步驟看起來(lái)笨但能幫你省下大量調(diào)試時(shí)間。我見(jiàn)過(guò)有人手寫(xiě)注意力反向傳播里softmax的雅可比矩陣少了一項(xiàng)訓(xùn)練時(shí)loss就是不動(dòng)查了兩天才發(fā)現(xiàn)。注意數(shù)值梯度校驗(yàn)只在float64下可靠float32的精度不夠誤差會(huì)到1e-3級(jí)別容易誤判。3.3 手寫(xiě)注意力縮放點(diǎn)積到底在縮放什么注意力公式大家都知道Attention(Q,K,V) softmax(QK^T / sqrt(d_k)) V。但為什么除以sqrt(d_k)很多人背下來(lái)了但沒(méi)理解。假設(shè)Q和K的每個(gè)元素獨(dú)立同分布均值0方差1那么QK^T的每個(gè)元素是d_k個(gè)乘積之和方差會(huì)變成d_k。當(dāng)d_k很大時(shí)比如512點(diǎn)積的數(shù)值會(huì)很大softmax之后會(huì)變得極其尖銳——幾乎變成one-hot梯度趨近于0訓(xùn)練就卡住了。除以sqrt(d_k)就是把方差拉回1讓softmax保持平滑。手寫(xiě)的時(shí)候這個(gè)縮放因子必須放在softmax之前而且反向傳播時(shí)要記得它對(duì)梯度的影響。我建議你先寫(xiě)一個(gè)不帶縮放的版本觀察softmax輸出的分布再加上縮放對(duì)比感受會(huì)非常直觀。3.4 層歸一化與殘差連接為什么它們讓訓(xùn)練穩(wěn)如老狗Transformer能訓(xùn)起來(lái)層歸一化和殘差連接功不可沒(méi)。殘差連接解決的是梯度消失——反向傳播時(shí)梯度可以直接通過(guò)跳躍連接回傳不用經(jīng)過(guò)一堆非線性層。層歸一化解決的是內(nèi)部協(xié)變量偏移——每層的輸入分布被拉回均值0方差1訓(xùn)練更穩(wěn)定。手寫(xiě)的時(shí)候有個(gè)細(xì)節(jié)層歸一化的均值和方差是在特征維度上算的不是batch維度。這和BatchNorm正好相反。我一開(kāi)始搞混了訓(xùn)練時(shí)loss震蕩得厲害后來(lái)打印了每層的輸入分布才發(fā)現(xiàn)問(wèn)題。def layernorm(x, gamma, beta, eps1e-5): mean x.mean(axis-1, keepdimsTrue) var x.var(axis-1, keepdimsTrue) x_norm (x - mean) / np.sqrt(var eps) return gamma * x_norm beta反向傳播時(shí)均值和方差本身也依賴(lài)輸入所以梯度要分三路回傳直接路徑、均值路徑、方差路徑。這部分推導(dǎo)比較繞建議用數(shù)值梯度校驗(yàn)。4. 實(shí)操過(guò)程從手寫(xiě)代碼到訓(xùn)練出能用的模型4.1 環(huán)境準(zhǔn)備與依賴(lài)選擇手寫(xiě)階段我強(qiáng)烈建議只用NumPy不要引入任何深度學(xué)習(xí)框架。原因很簡(jiǎn)單框架會(huì)幫你處理太多東西你感受不到底層。等手寫(xiě)跑通了再用PyTorch復(fù)現(xiàn)一遍做對(duì)比。環(huán)境配置python -m venv venv source venv/bin/activate pip install numpy matplotlib tqdmNumPy版本建議1.24以上老版本在某些矩陣運(yùn)算上有性能問(wèn)題。如果你用Mac M系列芯片注意NumPy的BLAS后端默認(rèn)可能沒(méi)啟用加速可以裝numpy時(shí)確認(rèn)一下。4.2 數(shù)據(jù)準(zhǔn)備字符級(jí)語(yǔ)言模型的數(shù)據(jù)集為了快速驗(yàn)證我用字符級(jí)語(yǔ)言模型。數(shù)據(jù)集隨便找一段英文文本比如莎士比亞全集或者維基百科摘要構(gòu)建字符到索引的映射text open(input.txt).read() chars sorted(set(text)) stoi {c: i for i, c in enumerate(chars)} itos {i: c for c, i in stoi.items()} data [stoi[c] for c in text]然后按固定長(zhǎng)度切塊比如每塊64個(gè)字符預(yù)測(cè)下一個(gè)字符。這個(gè)任務(wù)足夠簡(jiǎn)單手寫(xiě)模型能在幾十分鐘內(nèi)訓(xùn)出能看的輸出但又不至于簡(jiǎn)單到學(xué)不到東西。4.3 訓(xùn)練循環(huán)學(xué)習(xí)率、批大小、梯度裁剪怎么定訓(xùn)練循環(huán)看起來(lái)簡(jiǎn)單但超參選擇有講究。我的經(jīng)驗(yàn)值學(xué)習(xí)率手寫(xiě)模型用3e-3到1e-2因?yàn)閰?shù)量小大一點(diǎn)收斂快??蚣苣P陀?e-4到3e-4。批大小32到128。太小梯度噪聲大太大顯存吃緊且泛化可能變差。梯度裁剪閾值設(shè)1.0。手寫(xiě)模型容易梯度爆炸裁剪是保命手段。for step in range(max_steps): xb, yb get_batch(data, block_size, batch_size) logits model(xb) loss cross_entropy(logits, yb) loss.backward() # 梯度裁剪 for p in model.parameters(): p.grad np.clip(p.grad, -1.0, 1.0) for p in model.parameters(): p.data - lr * p.grad p.grad 0.0這里有個(gè)細(xì)節(jié)梯度清零要在參數(shù)更新之后。我見(jiàn)過(guò)有人先清零再更新結(jié)果參數(shù)根本沒(méi)動(dòng)還以為是模型結(jié)構(gòu)有問(wèn)題。4.4 從手寫(xiě)到框架對(duì)比驗(yàn)證的實(shí)操方法手寫(xiě)跑通后用PyTorch搭一個(gè)結(jié)構(gòu)完全相同的模型加載相同的權(quán)重輸入相同的數(shù)據(jù)對(duì)比輸出。誤差應(yīng)該在1e-5以?xún)?nèi)。如果誤差大逐層對(duì)比找到第一個(gè)出問(wèn)題的層。這個(gè)對(duì)比過(guò)程極其有價(jià)值。我第一次做的時(shí)候發(fā)現(xiàn)手寫(xiě)和PyTorch的輸出在第二層就分叉了查了半天發(fā)現(xiàn)是初始化方式不同——我手寫(xiě)用的是均勻分布PyTorch默認(rèn)是Kaiming初始化。統(tǒng)一之后誤差就降到1e-6了。4.5 模型導(dǎo)出與推理服務(wù)從訓(xùn)練到上線訓(xùn)練完的模型要能服務(wù)化。手寫(xiě)模型導(dǎo)出很簡(jiǎn)單把參數(shù)存成.npz推理時(shí)加載。但生產(chǎn)環(huán)境要考慮批處理單條推理浪費(fèi)算力攢一批一起算能提升吞吐。量化float32轉(zhuǎn)float16或int8顯存減半速度提升精度損失可控。緩存相同輸入直接返回緩存結(jié)果適合重復(fù)查詢(xún)場(chǎng)景。我用FastAPI搭了個(gè)最小服務(wù)from fastapi import FastAPI import numpy as np app FastAPI() model load_model(model.npz) app.post(/predict) def predict(text: str): tokens tokenize(text) logits model.forward(tokens) return {next_char: decode(np.argmax(logits[-1]))}壓測(cè)用wrk或ab關(guān)注P99延遲和QPS。如果延遲高先看是不是每次請(qǐng)求都重新加載了模型——這個(gè)坑我踩過(guò)把模型加載放在請(qǐng)求處理函數(shù)里延遲直接飆到秒級(jí)。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 梯度校驗(yàn)總是不通過(guò)怎么辦梯度校驗(yàn)不通過(guò)九成是以下三個(gè)原因現(xiàn)象可能原因排查方法誤差在1e-3量級(jí)float32精度不夠改用float64重跑某個(gè)參數(shù)梯度完全對(duì)不上反向傳播漏了這條路徑打印計(jì)算圖檢查該參數(shù)的依賴(lài)誤差隨網(wǎng)絡(luò)深度增大梯度累加方式錯(cuò)誤檢查是否用了而非我自己的習(xí)慣是每加一個(gè)新操作立刻單獨(dú)校驗(yàn)它的梯度不要等整個(gè)網(wǎng)絡(luò)搭完再查。這樣出問(wèn)題時(shí)范圍小好定位。5.2 訓(xùn)練loss不降的五個(gè)排查方向loss不降是最常見(jiàn)的問(wèn)題我按排查優(yōu)先級(jí)列一下數(shù)據(jù)對(duì)不對(duì)打印幾個(gè)batch的輸入和標(biāo)簽看是否匹配。我遇到過(guò)標(biāo)簽整體偏移一位的bugloss就是不動(dòng)。學(xué)習(xí)率合不合適太大震蕩太小不動(dòng)。先試1e-3不行再調(diào)。梯度有沒(méi)有回傳打印每層梯度的范數(shù)如果全是0說(shuō)明反向傳播斷了。初始化有沒(méi)有問(wèn)題全零初始化會(huì)讓所有神經(jīng)元學(xué)一樣的東西必須隨機(jī)初始化。損失函數(shù)對(duì)不對(duì)分類(lèi)用交叉熵回歸用MSE別搞反。5.3 顯存不夠用的實(shí)戰(zhàn)解法顯存不夠是AI工程的日常。按性?xún)r(jià)比排序的解法減小批大小最直接但可能影響訓(xùn)練穩(wěn)定性。梯度累積小批量跑多次累積梯度再更新等效大批量?;旌暇萬(wàn)loat16前向float32主權(quán)重顯存減半。梯度檢查點(diǎn)用計(jì)算換顯存適合超深網(wǎng)絡(luò)。模型并行把不同層放不同卡上適合超大模型。我一般先用梯度累積因?yàn)楦膭?dòng)最小。如果還不夠再上混合精度。梯度檢查點(diǎn)改動(dòng)大除非必要不用。5.4 推理延遲高的優(yōu)化清單推理延遲高按這個(gè)順序查模型加載位置是不是每次請(qǐng)求都加載模型改成全局加載一次。輸入預(yù)處理tokenization是不是在請(qǐng)求線程里做的可以異步或預(yù)計(jì)算。批處理能不能攢批單條推理GPU利用率極低。算子融合導(dǎo)出時(shí)做圖優(yōu)化把連續(xù)的小算子合并。量化float16或int8速度通常有1.5到3倍提升。提示優(yōu)化前先壓測(cè)拿基線優(yōu)化后再壓測(cè)對(duì)比。沒(méi)有基線的優(yōu)化都是瞎猜。5.5 手寫(xiě)代碼與框架結(jié)果不一致的定位方法手寫(xiě)和框架結(jié)果不一致按這個(gè)流程定位固定隨機(jī)種子確保初始化一致。逐層對(duì)比從第一層開(kāi)始對(duì)比輸出找到第一個(gè)不一致的層。檢查算子語(yǔ)義比如PyTorch的CrossEntropyLoss自帶softmax你手寫(xiě)時(shí)如果又加了一次softmax就錯(cuò)了。檢查維度順序PyTorch是(batch, seq, feature)NumPy手寫(xiě)時(shí)容易搞成(seq, batch, feature)。我第一次做對(duì)比時(shí)卡在維度順序上整整一個(gè)下午。后來(lái)養(yǎng)成習(xí)慣每個(gè)張量都打印shape問(wèn)題就少多了。6. 工具選型與效率提升我實(shí)際在用的組合6.1 手寫(xiě)階段NumPy Jupyter 數(shù)值校驗(yàn)?zāi)_本手寫(xiě)階段不需要復(fù)雜工具。NumPy做計(jì)算Jupyter做交互調(diào)試再寫(xiě)一個(gè)數(shù)值梯度校驗(yàn)的輔助函數(shù)基本就夠了。Jupyter的好處是能分塊運(yùn)行改一行代碼不用重跑整個(gè)訓(xùn)練。我習(xí)慣在Jupyter里先寫(xiě)前向驗(yàn)證輸出shape和數(shù)值范圍再寫(xiě)反向用數(shù)值梯度校驗(yàn)。這個(gè)流程比寫(xiě)完整個(gè)模型再調(diào)試高效得多。6.2 框架階段PyTorch Weights Biases Hydra框架階段我用PyTorch配合兩個(gè)工具Weights Biases記錄loss曲線、梯度分布、超參??梢暬蠛芏鄦?wèn)題一眼就能看出來(lái)。Hydra管理配置文件。超參搜索時(shí)不用改代碼改yaml就行。這兩個(gè)工具的學(xué)習(xí)成本很低但回報(bào)很高。尤其是WB訓(xùn)練時(shí)能實(shí)時(shí)看曲線不用盯著終端刷日志。6.3 部署階段ONNX Runtime FastAPI Prometheus部署我用ONNX Runtime做推理比原生PyTorch快而且跨平臺(tái)。FastAPI做服務(wù)Prometheus做監(jiān)控。監(jiān)控指標(biāo)至少要有請(qǐng)求延遲P50/P99、QPS、錯(cuò)誤率、GPU利用率。這套組合的好處是輕量一臺(tái)機(jī)器就能跑起來(lái)適合中小規(guī)模場(chǎng)景。如果規(guī)模再大再考慮Triton或TorchServe。6.4 版本管理與實(shí)驗(yàn)追蹤別讓實(shí)驗(yàn)結(jié)果變成一筆糊涂賬AI工程最怕實(shí)驗(yàn)做多了記不清哪個(gè)配置對(duì)應(yīng)哪個(gè)結(jié)果。我的做法代碼用Git每次實(shí)驗(yàn)前commitcommit message寫(xiě)清楚改了什么。配置用文件所有超參寫(xiě)進(jìn)yaml跟代碼一起版本管理。結(jié)果用WB每次實(shí)驗(yàn)自動(dòng)記錄配置和指標(biāo)能按條件篩選對(duì)比。這樣三個(gè)月后回頭看還能復(fù)現(xiàn)出當(dāng)時(shí)的實(shí)驗(yàn)。我見(jiàn)過(guò)太多人實(shí)驗(yàn)做完就忘了配置想復(fù)現(xiàn)都復(fù)現(xiàn)不了。7. 我踩過(guò)的坑與給你的實(shí)操建議7.1 別跳過(guò)數(shù)值梯度校驗(yàn)數(shù)值梯度校驗(yàn)看起來(lái)笨但它是手寫(xiě)反向傳播的唯一可靠驗(yàn)證手段。我早期嫌麻煩跳過(guò)結(jié)果一個(gè)符號(hào)錯(cuò)誤查了兩天。后來(lái)養(yǎng)成習(xí)慣每寫(xiě)一個(gè)操作就校驗(yàn)一次反而整體速度更快。7.2 先跑通再優(yōu)化別過(guò)早追求性能手寫(xiě)階段的目標(biāo)是正確不是快。我見(jiàn)過(guò)有人一上來(lái)就想著用向量化、用C擴(kuò)展結(jié)果正確性都沒(méi)保證優(yōu)化了半天全是錯(cuò)的。正確做法是先用最樸素的循環(huán)寫(xiě)跑通、校驗(yàn)通過(guò)再考慮優(yōu)化。7.3 訓(xùn)練日志要記全不然復(fù)現(xiàn)時(shí)想哭訓(xùn)練日志至少記loss、學(xué)習(xí)率、梯度范數(shù)、每層輸出均值方差。這些指標(biāo)在排查問(wèn)題時(shí)極其有用。我習(xí)慣每100步打印一次同時(shí)寫(xiě)進(jìn)文件。出問(wèn)題時(shí)翻日志比重新跑一遍快得多。7.4 部署前一定要做壓力測(cè)試本地跑通不代表線上能用。部署前必須壓測(cè)關(guān)注P99延遲和QPS。我遇到過(guò)本地單條延遲50ms線上并發(fā)一上來(lái)直接飆到2秒的情況——原因是模型加載沒(méi)做全局緩存每個(gè)請(qǐng)求都重新加載。壓測(cè)能提前暴露這類(lèi)問(wèn)題。7.5 保持手寫(xiě)習(xí)慣哪怕工作里用框架工作里當(dāng)然用框架效率優(yōu)先。但我建議每個(gè)月至少手寫(xiě)一個(gè)小模塊保持手感。手寫(xiě)讓你對(duì)框架的抽象有更深的理解遇到框架bug時(shí)也能更快定位。這個(gè)習(xí)慣我堅(jiān)持了幾年受益很大。最后分享一個(gè)我自己的體會(huì)AI工程這門(mén)手藝看十篇教程不如自己手寫(xiě)一遍。你會(huì)在手寫(xiě)過(guò)程中遇到各種教程里不會(huì)提的細(xì)節(jié)問(wèn)題而解決這些問(wèn)題的過(guò)程才是真正長(zhǎng)本事的時(shí)候。ai-engineering-from-scratch不是一句口號(hào)是一種學(xué)習(xí)方式——從最底層開(kāi)始一層一層往上搭搭到能用的那天你就真的會(huì)了。