
今天談 GPU 推論。這是三種工作負載裡我踩最多坑、也最沒把握說「我懂了」的一類。這篇比較像事故報告,結論的部分請當成「這是我目前的理解」來讀。
我有打造自己一個影片自動化網站: LeapieVideo),負責把影片的語音轉成字幕。一開始用 OpenAI 的 Whisper API,很省事。後來遇到一個很煩的問題:字幕的時間戳會漂移,前面的字幕太早出現、後面的太晚,而且影片越長越明顯。
追了一陣子,我判斷跟切段(chunking)和靜音處理的方式有關,而 API 給的控制權不夠讓我調這些。所以我決定自己架 Whisper 的 GPU 推論引擎。
這就是很典型的「AI 工程師被迫去碰 infra」的例子:原本只是想調整參數,調一調之後,變成在研究怎麼管理 GPU。
Day 07 講 CPU 服務的記憶體上限,我的原則是「峰值加 buffer」。GPU 完全不是這樣。
模型多大,VRAM 就要有多大,少一點就載不進去(不做量化或 offload 的前提下);不是變慢,是直接失敗。而且不同大小的 Whisper 模型(tiny / base / small / medium / large)VRAM 需求差好幾倍,你選了哪個模型,幾乎就決定了要哪一級的 GPU。
對應到 K8s:GPU 資源通常只能以整數個配置(nvidia.com/gpu: 1),不能像 CPU 那樣切 0.5 個。要嘛獨占整張卡,要嘛想辦法讓多個模型共用(有 time-slicing、MIG 這類機制,做法見 NVIDIA GPU Operator 的 Time-Slicing GPUs in Kubernetes,我還沒親手試過)。對一個只跑一個 Whisper 模型的服務來說,這代表:GPU 大部分時間閒著,但你還是得為整張卡付錢。
自架之後第一個讓我意外的是啟動時間。模型要從磁碟載進 VRAM,大一點的模型要等一陣子。這段時間裡,服務「起來了,但不能用」。
在 Docker Compose 上這沒差,反正開了就一直放著。但一旦你開始想「能不能用完就關,省 GPU 錢」,啟動時間就變成核心問題:每次冷啟動都要等一陣子,使用者等得下去嗎?
這個考量直接影響我後來選 Cloud Run 的 GPU(L4)當 backend。Cloud Run 可以 scale-to-zero,閒著不計費,代價就是冷啟動。我目前的做法是接受冷啟動,因為字幕轉換本來就不是即時互動:使用者上傳影片後本來就要等一陣子,冷啟動多出來的那一段還能接受。
但如果換成即時語音轉文字的 agent,這個取捨就完全不成立。同一個模型,在不同使用情境下,部署決策可能完全不同。 這也是 Day 06 為什麼要先分清楚「你的負載是哪一型」。
繞了一大圈才發現,換硬體本身不是重點,後來的調整集中在切段與靜音處理。自架 GPU 只是讓我有權限去調這些東西。
特別提這件事,是想講一個很容易被忽略的體會:很多你以為要靠 infra 解決的問題,本質上是應用層的邏輯問題;但你常常得先拿到 infra 的控制權,才有辦法驗證這件事。 只停在用 API 的階段,我連實驗都做不了。
整理一下 Whisper 服務現在的樣子,以及對應到 K8s 的概念:
resources.limits 設 nvidia.com/gpu: 1,用 node selector 指到有 GPU 的 node pool;readiness probe 要等模型載完才轉 ready,不然 Service 會太早把流量導進還沒準備好的 Pod。最後一點是我認為 GPU 服務搬到 K8s 時最容易漏的:readiness probe 一定要真的反映「模型載入完成」,不能只檢查 port 有沒有開。
明天回到記憶體壓力的主題,用 K8s 的視角重新看 Day 04 那場事件。
GPU 服務的三個硬限制:VRAM 不能留 buffer、啟動不能瞬間完成、整張卡不能隨便切;部署決策全繞著這三件事轉。