今天是雙主軸真正交會的一天。
Day 02 提到 LLMOps 全台只有 19 筆職缺——但那是因為這個詞還沒進入招募用語。實際上做這件事的人,需求很大。
三個階段性的問題,多數團隊會依序遇到:
第一階段(PoC):用 API,很順利,每月幾百塊。
第二階段(上線):使用者變多,帳單變成每月六位數。主管開始問「能不能便宜一點」。
第三階段(規模化):算了一下,自建 GPU 好像划算——但真的划算嗎? 而且法務說某些資料不能出境。
今天回答這三個問題:怎麼省、什麼時候該自建、自建要注意什麼。
📊 職缺訊號
「生成式 AI 部署與 LLMOps」出現在 38.7% 的生成式 AI 職缺中,同時「模型部署與推論服務」出現在 49.2% 的 MLOps 職缺。這兩個數字重疊的部分,就是今天的內容——也是目前市場上最稀缺的組合。

圖 26-1:LLM 成本的歸因、優化成效與單次請求拆解(示範性推估)。左圖顯示導入模型路由與快取後的成本下降;右圖是單次請求的成本組成。
先看右圖(Day 07 談過,今天要動手處理):最貴的是「固定前綴」與「輸出」。所以省錢的順序很明確:
| 順序 | 手段 | 典型效果 | 難度 |
|---|---|---|---|
| 1 | 限制輸出長度(max_tokens + 提示要求精簡) |
20–40% | 極低 |
| 2 | prompt caching(固定前綴前置且逐字穩定) | 輸入端約 50–60% | 低 |
| 3 | 模型路由(簡單任務用小模型) | 30–50% | 中 |
| 4 | 語意快取(高重複性場景) | 10–30% | 中(有誤命中風險) |
| 5 | 精簡檢索脈絡(提高檢索精準度後降 top-k) | 10–20% | 中 |
| 6 | 自建推論 | 規模夠大時 50%+ | 高 |
注意「自建」排在最後。 前五項都是幾天內能做完的軟性優化,自建則是一個需要人力持續維護的決定。
主軸二的讀者可能沒想過:同一張 GPU、同一個模型,換一個推論引擎,吞吐可以差三到四倍。原因在於 KV cache 的管理方式。

圖 26-2:KV cache 記憶體(依公式精算)與批次策略的效益(示範性推估)。左圖:批次 × 長度的乘積效應;右圖:連續批次與 PagedAttention 對吞吐與顯存碎片的影響。
左圖的計算(Day 07 提過,這裡是完整版):
KV cache = 2(K 與 V)× 層數 × KV head 數 × head_dim × 位元組數 × 序列長度 × 批次
8B 模型(32 層、8 KV head、head_dim 128、fp16):
每 token 每序列 = 2 × 32 × 8 × 128 × 2 B = 131,072 B = 128 KiB
批次 16、序列 8,192:128 KiB × 8192 × 16 ≈ 17.2 GB
加上權重 16 GB → 約 33 GB,一張 80GB 卡放得下但要留餘裕
右圖的三個改進:
💡 給主軸二讀者的白話版
你不需要看懂 PagedAttention 的實作。你需要知道的是:如果你現在用 Hugging Face
transformers的generate()直接對外服務,換成 vLLM 通常能立刻獲得數倍吞吐——不用改模型、不用改應用程式碼(因為它提供 OpenAI 相容 API)。
uv add vllm
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--gpu-memory-utilization 0.90 \ # 保留 10% 給碎片與突發,別設 1.0
--max-model-len 8192 \ # 直接決定 KV cache 上限,別設超過實際需求
--max-num-seqs 64 \ # 最大併發序列數
--port 8000
應用端幾乎不用改——因為它是 OpenAI 相容介面:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
# 之後的呼叫方式與雲端 API 完全相同
三個參數的實務意義:
| 參數 | 設太大 | 設太小 |
|---|---|---|
--gpu-memory-utilization |
OOM 崩潰 | 併發數上不去 |
--max-model-len |
KV cache 吃光顯存,併發數暴跌 | 長文件請求被拒絕 |
--max-num-seqs |
排隊變長、TTFT 惡化 | 吞吐上不去 |
"""bench.py —— LLM 服務的壓測,指標與傳統 API 不同。"""
import asyncio, time
async def one_request(client, prompt):
t0 = time.perf_counter()
ttft = None
tokens = 0
stream = await client.chat.completions.create(
model=MODEL, messages=[{"role": "user", "content": prompt}],
stream=True, max_tokens=300)
async for chunk in stream:
if chunk.choices[0].delta.content:
if ttft is None:
ttft = time.perf_counter() - t0 # 首個 token 抵達時間
tokens += 1
total = time.perf_counter() - t0
return {"ttft": ttft, "total": total,
"tpot": (total - ttft) / max(tokens - 1, 1), # 每個後續 token 的時間
"tokens": tokens}
# 三個必看指標:
# TTFT → 使用者的「等待感」(受 prefill 與排隊影響)
# TPOT → 生成的「流暢感」(受 decode 影響)
# 吞吐 → 你的容量(總輸出 token / 秒)
這三個指標要分開訂 SLO。 常見的目標:TTFT P95 < 1 秒(等待感)、TPOT < 50 ms(約每秒 20 字,比多數人閱讀速度快)。只看端到端延遲會看不出問題出在 prefill 還是 decode。
"""llm_cost.py —— 把假設攤開來算(Day 15 的方法,換成 LLM 情境)。"""
# API 方案
API_INPUT, API_OUTPUT = 1.0, 5.0 # 每百萬 token 的相對單價
daily_in, daily_out = 400_000_000, 20_000_000 # 每日 token 量
api_monthly = (daily_in / 1e6 * API_INPUT + daily_out / 1e6 * API_OUTPUT) * 30
# 自建方案(Day 15 的成本模型)
GPU_HOURLY = 3.0 # 雲端 GPU 每小時(或地端攤提)
GPUS_NEEDED = 3 # 由容量規劃決定:尖峰吞吐 ÷ 單卡吞吐 + 冗餘
OPS_MONTHLY = 400 # 維運人力分攤——自建最容易被低估的成本
selfhost_monthly = GPU_HOURLY * 24 * 30 * GPUS_NEEDED + OPS_MONTHLY
print(f"API 方案:{api_monthly:>10.0f} / 月")
print(f"自建方案:{selfhost_monthly:>10.0f} / 月")
print("建議:", "自建" if selfhost_monthly < api_monthly * 0.7 else "續用 API")
# 門檻設 0.7 而非 1.0:自建要明顯划算才值得承擔維運複雜度
📌 那個 0.7 是刻意的
自建除了帳面成本,還有隱形成本:模型升級要自己做、出事要自己修、半夜掛了要有人接。只有在明顯划算(省 30% 以上)時才值得——除非你的決策理由是資料主權,那就不是成本問題。
模型路由是最被低估的成本手段:不是所有請求都需要最強的模型。
"""router.py —— 依任務複雜度分流,簡單任務用便宜模型。"""
async def route_and_answer(question, context):
# 用小模型先判斷複雜度(這次判斷本身很便宜)
complexity = await classify_complexity(question) # simple / standard / complex
model = {"simple": SMALL_MODEL, # 分類、擷取、格式轉換
"standard": MID_MODEL, # 一般問答、摘要
"complex": LARGE_MODEL}[complexity] # 多步推理、長文件分析
try:
return await call_llm(model=model, ...)
except (RateLimitError, APIError):
return await call_llm(model=FALLBACK_MODEL, ...) # 跨供應商冗餘
LLM 服務的發布管理——這是 Day 11 品質門檻的 LLMOps 版本。要注意的是,LLM 服務有四種資產會變動:
| 資產 | 誰會改 | 發布前要做 |
|---|---|---|
| 模型(版本或供應商) | 工程師/供應商自動升版 | 跑全套評測 + 人工抽查 |
| 提示 | 工程師、有時是 PM | 跑全套評測(Day 24) |
| 檢索索引 | 文件更新自動觸發 | 跑檢索端指標 |
| 工具定義與權限 | 工程師 | 跑注入測試(Day 25) |
「模型路由」聽起來很好,但實務上第一個疑問通常是:小模型的品質夠嗎?
答案取決於任務類型。經驗上的分界線是:任務越接近「格式轉換」,小模型越夠用;越接近「開放式推理」,差距越大。
| 任務 | 小模型是否夠用 | 說明 |
|---|---|---|
| 意圖分類、情緒判斷 | ✅ 通常夠 | 選項有限,且可用 few-shot 補強 |
| 結構化欄位擷取 | ✅ 通常夠 | 有明確的 schema 約束 |
| 查詢改寫(Day 19) | ✅ 夠 | 高頻但簡單,用大模型是浪費 |
| 摘要 | ⚠️ 視長度 | 短文可以,長文的重點取捨會明顯變差 |
| 多步推理、複雜問答 | ❌ 差距明顯 | 這才是大模型的價值所在 |
| 程式碼生成 | ❌ 差距明顯 | 錯一個字就不能跑,容錯率低 |
判斷方法還是回到評測(Day 24):把你的評測集用小模型跑一次,看掉多少分。如果只掉 2%、成本降 90%,那顯然值得;如果掉 15%,那就別省這個錢。這個決定不該靠感覺,一小時就能量出來。
⚠️ 一個 LLMOps 特有的風險:供應商自動升版
你沒有改任何東西,但供應商把
model-latest指向了新版本,你的產品行為就變了。正式環境一定要釘住明確的模型版本號,並在自己決定的時間點升級——升級時走完整的評測與金絲雀流程。這是傳統 MLOps 沒有的問題:你的相依項會自己動。
gpu-memory-utilization、max-model-len、max-num-seqs。技術面完成,明天處理制度面:治理。AI 風險矩陣怎麼畫、歐盟 AI Act 的時程對台灣團隊有什麼影響、模型檔案的供應鏈風險(那個很多人不知道的 pickle 問題),以及一套能真正落地的權限與稽核設計。