改裝 2080 Ti 22G · part 10
[趣味競賽 進階 #10] 同一張 2080 Ti,一台生圖慢 3.4 倍——兇手是啟動 log 裡一行 dtype fallback
❯ cat --toc
TL;DR
兩台機器,同一張改裝 2080 Ti 22G。一台生 Z-Image 暖機 median 46.08s,我早上才「結案」說這就是卡的天花板——GPU 全程 100%、模型常駐、卡也對。結果另一台一樣的卡只要 ~9.5s。兇手不是卡,是啟動 log 一行精度 fallback:Turing 沒有 bf16 tensor core,ComfyUI 為了保精度把權重留在 fp32,算得慢 3.4 倍。一個 --force-fp16,median 46.08→13.66s,免費、畫質沒差。誠實提醒:不省 VRAM,對影片模型也沒用。
短版:兩台同一張卡差 3.4 倍,問題根本不在卡
同一張改裝 2080 Ti 22G,插在兩台不同的機器上:一台生 Z-Image 暖機 median ~9.5s,另一台跑到 ~46s;換算掉解析度差之後還差 4-5 倍。跟晶片一點關係都沒有——差別藏在啟動 log 裡一行精度 fallback,而一個旗標就把暖機 median 從 46s 打到 13.66s。
那天早上我才「結案」,結果被一句話掀翻
先講清楚我當時有多篤定。那天早上我才剛把這件事「結案」:Linux 那台生 Z-Image,暖機 median 46.08s,我判它就是這張 2080 Ti 的算力天花板。理由很硬——生圖全程 GPU 100%、模型常駐不用重新載入、卡也是對的那張。三個訊號一起指著「就這麼快了」,我就收工了。
結果掀翻它的,是我自己隨口一句「我印象中另一台大概 10 秒欸」。
另一台是那台舊 Windows 機(我叫它 forge),裝的是同一張 2080 Ti。翻出它還活著的 log,白紙黑字寫著 Prompt executed in 9.49 seconds——暖機、跑 768²/8 步、穩定 1.01 s/it。慢的那台跑 1024²,像素是多了些,這條我認,但多的那點像素撐死解釋 2 倍,不到 4-5 倍。
於是問題變得很難看:兩張一模一樣的卡、同一個生成器,一台慢好幾倍。「這就是天花板」原本是那個讓我停止追查的舒服答案——現在它站不住了。
清三個嫌疑:OS、缺套件、attention backend——三條都走死了
我先列了三個最直覺的嫌疑,每個配一個一刀砍下去的決定性測試。
嫌疑一:OS。 Win vs Linux 是最順手的猜測,但我原本的預期是「純 Linux 應該更快才對」。一對日期就發現方向整個反了——慢的那台就是 Linux。想像中該更快的那邊反而在拖後腿,這條假設在「慢的是 Linux」那一刻就當場死了。OS 無罪。
嫌疑二:缺套件。 那也許是快的那台裝了什麼加速套件(comfy-kitchen 之類的)、慢的那台漏了?查版本——慢的那台是 0.2.20,比快的 0.2.10 還新。慢的機器版本更前面、東西一樣不缺。套件無罪。
嫌疑三:attention backend。 這條最陰。早上我其實在慢機上測過 xformers 的 A/B,結果 -0.85%(不但沒快、還更慢),我就把它回滾了。當下沒意識到的是:那場 A/B 量的,是跑在錯的那條計算路徑上的 attention。lever 沒選錯,我只是在一條本來就已經有問題的路上拉它,拉了當然沒感覺。
三條全走死,但每一條都幫我收窄範圍:不是 OS、不是套件、不是 attention kernel——問題出在「這個 model 到底怎麼跑」,不是「裝了什麼」。
破案:兩行 startup log,差在一個 dtype

破案關鍵就在 ComfyUI 啟動 log 的兩行,而我手上還留著這兩行純屬僥倖——forge 的 log 剛好還活著。那是一份 6,743 bytes 的 stderr 殘片,只涵蓋那張卡被拔走前最後 15 分鐘(22G 卡現在已經拔掉、換插了張 GTX 970,這份殘片是唯一還在的證人)。要不是它留著,這案子就斷在這了。
第一行是對齊用的指紋:Prompt executed in Ns。兩邊的冷跑、暖跑各自對齊,先確認「差是真的」,不是我某一次剛好卡到。
第二行才是破案關鍵:
- 快的那台:
weight dtype torch.float16, manual cast: torch.float16 - 慢的那台:
weight dtype torch.bfloat16, manual cast: torch.float32
同一個 checkpoint、同一張卡——快的整路跑 fp16,慢的被悄悄留在 fp32 的 manual cast。
機制其實很乾淨:Turing(sm_75)有 fp16 tensor core、但沒有 bf16 tensor core(bf16 硬體加速要 Ampere 之後才有)。ComfyUI 不是把每個 bf16 model 一律轉 fp32,而是依每個 model class 自己宣告支援的 dtype 挑計算精度;這顆 Z-Image checkpoint 在 Turing 上,預設就落在保精度的 fp32 manual cast,而 fp32 算起來比 fp16 tensor core 慢大約 3.4 倍。(這個「挑哪種精度是看 model class」的細節,正好就是為什麼等一下同一個旗標對另一顆 model 完全沒用。)
這裡藏著整篇最反直覺的一句:GPU 全程 100%,但 100% 忙 ≠ 撞到天花板。它可以 100% 地忙著算一條慢精度的路。忙,不代表滿。
解法:一個 --force-fp16,median 46.08→13.66s
修這件事只要一個旗標:--force-fp16。開下去,log 那行從 manual cast: torch.float32 變成 manual cast: None——終於肯吃 fp16 tensor core 了。
跑單變因 A/B(唯一的差就是這個旗標),1024²/8 步暖機:
- 暖機 median:46.08 → 13.66s
- sampler:5.64 → 1.59 s/it
- 3.37×,免費,輸出完整(真的 PNG、不是黑圖、沒有 NaN,畫質跟 fp32 沒差)
這個旗標我已經烙進那台天天在生圖的 ComfyUI 啟動參數,每次生圖自動帶。
再回頭看 xformers 那個 -0.85%:那是因為當初量在 fp32 這條路上。路徑修好之後再測,attention backend 幾乎不影響結果——這 3.4× 從來就不是換 kernel 換得到的——差距其實來自 kernel 上游那個 dtype 的挑法。 我早上在錯的路上拉對的 lever,難怪拉不動。
誠實提醒一:它不會省 VRAM,反而多吃 0.5G
--force-fp16 沒有省記憶體,fp16 反而多用了 0.5G(15,982 vs 15,498 MiB)。理由很無聊:bf16 跟 fp16 的權重都是 2 bytes,本來就沒空間可省;省下的那點 activation 又被 allocator 的 pool 蓋過去了。
我一直隨身帶著的那個「這個 checkpoint 省 5G」的印象,是駐機 session 舊的自報數字,一跑乾淨 bench 就被打臉。所以講清楚:它換來的是速度、不是記憶體。 想靠它「多出來的空間」去 size 下一個 model,會踩雷。
誠實提醒二:影片模型沒受益——這招有沒有用要看 model family
拿到 3.4× 我當然貪心,把同一個旗標指向影片模型試試——LTX-2(Sulphur-2,3 秒短片)。同一張卡、同樣的單變因 A/B,結果是 1.00×(140.29 → 140.93s,VRAM 一模一樣,util 也一樣)。一點都沒動。

log 解釋了為什麼:旗標確實開了,權重也確實翻成 fp16,但那行 manual cast 還是被釘死在 torch.float32——LTX-2 的 model class 沒把 fp16 列進支援清單,cast 就一直是 fp32,旗標壓不過去。Z-Image(Lumina2 家族)吃這個旗標、LTX-2 不吃。
所以這招有沒有用,是看 model family、不是卡本身的性質。Turing 上這顆影片 model 真的就只有 fp32 這一條路可走;好消息是,現在至少有一組對照,能確認那 140s 就是實測結果,不是我設定沒調好。
收穫
最花時間的不是修,是「不肯信天花板」。 真正卡我的不是修(修就一個旗標),是我早上太快接受了那個「就這麼快了」的結論。「100% GPU、模型常駐、卡也對」這三個跡象同時指著同一個方向,說服力強到讓我收工;結果它們一起在騙我。唯一戳破它的,是另一張一模一樣、卻更快的卡——不是更聰明的推理,是一個反例。
真正能帶去別的專案用的除錯手法,全是無聊動作。 兩台同卡機器,把最無聊的啟動 log 對 diff,一整個答案就藏在一行裡。把 100% GPU 讀成「忙」而不是「滿」。一個假設就配一個決定性測試(OS 那條在「慢的是 Linux」那刻就結束了,不用再糾結)。還有一條最容易忽略:當你的 KB / 筆記說「這台從來沒更好過」,要信機器本地的 log、別信摘要——forge 是個非同步節點,我自己的筆記兩度堅稱它那次快跑「沒發生過」,結果離線那台機器的 log 才是權威。
⚠️ 一個 bench 暗雷順手記下:固定 seed 重送同一張圖,ComfyUI 會 cache-hit,直接印
Prompt executed in 0.00 seconds——你以為量到暖機速度,其實整排數字都是假的。解法是餵一個 graph 會忽略、但足以讓它強制重算的 nonce,seed 跟真正的輸入都不動。
通用原則一句話: 說穿了,慢的不是這張卡,是它被逼著用一種沒有硬體加速的精度格式在算——而「GPU 100%」這個最有說服力的訊號,剛好也是最會騙人的那個。
收工前的五條 checklist
- 先 diff 兩台同卡的啟動 log。 盯
model weight dtype跟manual cast這兩行;bf16 checkpoint 跑在 pre-Ampere 卡上、又出現manual cast: torch.float32,就是 fallback。 - 別把 100% GPU 當天花板。 忙不等於最優——它可能 100% 忙在一條慢精度的路上。
- Turing 上的 bf16 checkpoint,先試
--force-fp16。 model family 吃這套的話就是 3× 級的加速;驗收看 log 那行有沒有翻成None、輸出是不是 NaN/黑圖。 - 別指望它省 VRAM。 fp16 跟 bf16 的 bytes 差不多,把它當速度優化就好、別算進省空間。
- 每個 model family 各自 A/B。 它幫了 Z-Image、對 LTX-2 完全沒用,別假設一招通吃。
同系列上一篇也是「log 把證據吃掉」的母題,只是那次死的是一個服務、不是速度:0xc0000409:當我的 AI 服務靜默暴斃,而 log 把死因吃掉了。工具本身:ComfyUI。
常見問題
- 同一張 2080 Ti,兩台機器生圖速度為什麼差 3.4 倍?
- 不是卡的差異,是 dtype。ComfyUI 不是把所有 bf16 model 一律轉 fp32,而是依每個 model class 支援的 dtype 挑計算精度。Turing(sm_75)有 fp16 tensor core、但沒有 bf16 tensor core(bf16 加速要 Ampere 之後才有);這顆 Z-Image checkpoint 在 Turing 上,預設就落在保精度的 fp32 manual cast——而 fp32 算比 fp16 tensor core 慢大約 3.4 倍。加上 `--force-fp16` 讓它改走 fp16 tensor core,同一張卡的暖機 median 就從 46.08s 掉到 13.66s。
- `--force-fp16` 會省 VRAM 嗎?
- 不會,反而多吃一點(15,982 vs 15,498 MiB)。bf16 跟 fp16 的權重都是 2 bytes,沒有省下來的空間;省掉的那點 activation 又被 allocator 的 pool 蓋掉了。它換來的是速度、不是記憶體——要拿它多出來的空間去塞下一個 model,會踩雷。
- 為什麼影片模型(LTX-2)加了同一個旗標卻沒變快?
- 因為這招有沒有用,得看 model family。LTX-2 的 model class 沒把 fp16 列進支援清單,manual cast 就一直釘在 fp32、旗標壓不過去;Z-Image(Lumina2 家族)吃這個旗標、LTX-2 不吃。同一張卡上,LTX-2 這顆影片 model 真的就只有 fp32 這條路,140s 是實測跑出來的,不是設定沒調好。
- 怎麼知道自己有沒有踩到這個坑?
- 拿兩台同卡機器的 ComfyUI 啟動 log 對 diff,盯 `model weight dtype` 跟 `manual cast` 這兩行。bf16 checkpoint 跑在 pre-Ampere 卡上、log 出現 `manual cast: torch.float32`,那就是 fallback。加 `--force-fp16` 之後那行會變 `manual cast: None`,再確認輸出不是 NaN、不是黑圖,就對了。
接著讀
- 2026-07-10[趣味競賽 進階 #9] 0xc0000409:當我的 AI 服務靜默暴斃,而 log 把死因吃掉了
一台 headless Windows AI 主機上的本地腦,偶發在真實負載下無聲無息地死掉:client 吃到短暫 503,服務自己又活回來,應用程式 log 一片空白。最氣的是真兇不是模型本身,是我自己——重啟前 log 把 crash 當下那行死因截掉了。這篇是把死因從『被自己的 log 掩埋』裡挖出來的偵察故事:為什麼會自我重啟的服務最會掩埋自己的死因、為什麼要先看 OS 層的 Event Log、以及 0xc0000409 到底是什麼。誠實在先:根因我還沒 100% 確認,這是還在查的紀錄,不是結案報告。
- 2026-07-03[趣味競賽 進階 #8] 為什麼 30 tok/s 體感比 14 還慢:TTFT 才是你真正感覺到的速度
我把家裡那顆 agent 腦的 decode 從別台的 14 tok/s 換成這台 30,結果它感覺更慢。tok/s 這個數字我盯了一年,原來它量的是「吐字多快」,不是「多久才開始吐」。在一顆 hybrid 模型上,一次 cache miss 就把整段 prompt 從頭重算——同一台機、同一顆腦,暖機 2.6 秒、冷掉 216 秒。這篇用 Hina 自己的 log 講:為什麼該盯的是 TTFT。
- 2026-07-02[趣味競賽 進階 #7] 在慢腦上開漸進式串流,我把自己撞進了 Telegram 的 flood control
我想讓慢吞吞的本地 agent 少一點等待焦慮,於是開了 Telegram 的「逐字浮現」串流——每隔幾百毫秒就改寫一次訊息。結果一個一百多秒的回應發出上百次編輯請求,正面撞進 Telegram 的 flood control,被罰等兩百多秒,整個 bot 卡死,連最終答案都送不出去。這篇講一個短而毒的教訓:慢模型不該開漸進式串流,一次性送最終答案反而穩。附 log 實證。
- 2026-06-27[趣味競賽 進階 #6] 工具定義稅:還沒開口,就先吃掉 17K token——而且每次重算都重來一遍
我數了一下家裡那個 AI 助理每次回話前的開銷:還沒開始處理我輸入的文字,就先吃掉約 23K token,其中 17K 只是『工具的使用說明書』。更慘的是它是 hybrid 模型,快取一沒命中就把這 17K 從頭重算——一個對話回合可能重算十幾次。這篇講一個被嚴重低估的成本:模型開口前的「打底開銷」。解法不是砍工具,是像技能那樣『用到才載』。
不想錯過新文章?
訂閱我確保不漏接!
隨時一鍵退訂。