昨天講注意力怎麼分配,今天講錢怎麼算。這兩件事共用同一個底層:token。你在 API 帳單上看到的每一個數字,背後都有機制可以解釋,知道機制之後,很多省錢手段你自己就想得到。
模型不讀字,讀 token。現在主流的做法是子詞分詞(BPE 這一系):訓練時統計語料裡最常見的字元組合,高頻的詞保留完整,罕見的詞拆成子詞。「the」是一個 token,罕見的長字拆成兩三個。
這個設計解釋了一些模型的怪行為。問「strawberry 有幾個 r」常常答錯,因為模型根本看不到字母:它看到的是一兩個 token 的整體編號,字母層級的資訊在進模型之前就被打包掉了。長數字也一樣,可能被切成不規則的幾段,這是模型算術不穩的原因之一。你以為模型在讀字,它讀的是另一種單位。
成本也跟著這個單位走:API 按 token 計費,同樣的內容,不同語言、不同 tokenizer 切出來的 token 數可以差不少(中文通常比英文多一些,幅度隨模型的詞彙表設計而變)。寫 prompt 前拿官方 tokenizer 數一次,比任何經驗法則都準。
打開任何一家的定價頁,output token 的單價都是 input 的好幾倍。價差來自計算結構。
回到昨天講的機制:生成是自回歸的,一次吐一個 token,每個新 token 都要跟前面所有 token 算注意力。如果每一步都把歷史 token 的 Key 和 Value 重算一遍,生成 n 個 token 的總計算量是 O(n²)。
但這裡有一個關鍵觀察:歷史 token 的 K 和 V 算完就不會變。所以推論引擎把它們快取起來,每一步只算新 token 的 K、V,這就是 KV Cache,計算量降回 O(n)。代價是記憶體:以 LLaMA-3-8B 為例,一個 4K context 的請求,KV Cache 就要吃掉約 2GB,同時服務 10 個請求,光 cache 就是 20GB。long context 貴就貴在這塊記憶體。
這塊記憶體有多值得摳,看 vLLM 就知道。vLLM 是目前最主流的開源推論引擎,它最核心的創新 PagedAttention 解的正是這個問題:傳統做法為每個請求按最大長度預分配連續記憶體,一個實際只用 100 token 的請求,卻占著 4096 token 的空間,利用率可以低到 2.4%。
PagedAttention 借用作業系統的分頁概念,把 KV Cache 切成固定大小的 block、用 block table 映射、按需分配,利用率拉到 95% 以上。vLLM 能撐起更高的併發,靠的主要就是這個借來的概念。你不一定會去自建推論,但整個產業都在為 KV Cache 的記憶體搏鬥,這件事本身就說明它有多貴。
KV Cache 把推論切成兩個性質完全不同的階段。Prefill:一次讀完你的整段 prompt,平行計算所有 K、V,GPU 火力全開。Decode:一個一個 token 生成,每步只算一個新 token,大部分時間在等記憶體搬運 KV Cache,GPU 算力大量閒置。
Input 便宜,因為 prefill 可以平行、效率高;output 貴,因為 decode 被記憶體頻寬卡住,每個 token 都佔著 GPU 慢慢磨。價差是物理的。
第一,控制 output 比控制 input 值錢。 同樣省 1000 個 token,省在 output 上的錢是 input 的好幾倍。要求模型「直接給結論,不要重述問題」,比把 prompt 砍到極簡更有感。
第二,prompt 長度影響的是首字延遲。 Prefill 的耗時跟 prompt 的 token 數成正比,prompt 越長,使用者等第一個字的時間越久。串流輸出救不了 prefill,它只救 decode 的體感。
第三,長對話的成本不是線性的。 每一輪對話都把歷史重新塞進 prompt,input token 隨輪數累積成長。Agent 跑幾十輪之後越來越貴,機制在這裡,之後講 compaction 時會回到這件事。
KV Cache 還藏著一件事:既然歷史 token 的 K、V 算一次就不變,那兩個請求如果共用同一段開頭(比如同一份 system prompt),這段開頭的 K、V 是不是可以只算一次、大家共用?
可以。這就是 大廠的 prompt caching 的機制基礎,也是為什麼它們敢把快取命中的 input token 打到一折。明天講怎麼把這件事用好,以及它的天花板在哪:字串一樣才有得快取,那意思一樣、字不一樣的一萬個問題呢?