背景
由於專案要求的緣故,不能使用閉源模型來進行 PDF OCR,而傳統 OCR 的效果沒有很好,所以只好使用開源模型來處理。在 OCR 領域中,我看排名前面幾乎都是 qwen 的版本,所以就先用 qwen 的最新 3.5 來進行測試。
過程
技術選型
首先我先比較市面上解析 PDF 圖片的幾個方案,傳統 Tesseract / EasyOCR、PaddleOCR、以及多模態模型VLM(Qwen)的方法,然後選取PDF 文件中比較複雜的20頁進行測試:
- Tesseract / EasyOCR:傳統 OCR。但它本身不含中文,要另外裝語言包;裝了繁中包(
tessdata_best)跑下來,還是有點不符合需求——繁中字有些會被切開、字形也有認錯,像 「辯護人」 被解析成 「拓護人」。單純只比較純文字的解析,只有大約 73% 和 VLM 的解析結果一致。 - PaddleOCR:來自百度的OCR 模型,其純文字的解析結果幾乎追平 VLM,但它唯一明顯的軟肋是流程圖、表格、模糊頁——一張審判程序的流程圖,它把箭頭圖元認成
全房品品房品品机、方塊的閱讀順序也亂掉了。 - VLM(Qwen):不只認字,還「看得懂版面」——流程圖、表格能按邏輯順序讀,模糊處也處理得比較好;更關鍵的是它還能夠接收prompt來調整(忽略背面透印、看不清的標【模糊】不要亂猜、保留條號結構等等)
為了 PDF 解析的完整度,所以選擇自己部署 VLM 來對專案的文件進行解析。
計算過程
規劃需要什麼規格的GPU,需要先大致預估一下模型的使用:
模型權重
先查看 Qwen3.5-35B-A3B,其模型權重為 35B:
| 精度 | 每個參數 | 35B 權重 |
|---|---|---|
| FP32(全精度) | 4 bytes | 140GB |
| BF16 / FP16(不量化) | 2 bytes | 70GB |
| FP8 | 1 bytes | 35GB |
| INT4 | 0.5 bytes | ~17.5GB |
所以用 INT4(AWQ/GPTQ)的量化版本,權重需要預留快20GB 的容量。
圖片轉換token
Qwen VL 系列的 vision encoder (ViT) 是以 14x14 pixel 切一個 patch,然後才做 2x2 merge—把相鄰的 4 個 patch 合併成 1 個 token 推送給模型。 因此一張 1024x 1024 的圖片,生成 1300+ token,跟著文字 token 一樣佔入 KV cache。
KV cache
算完權重,還有第二個吃 VRAM 的東西:KV cache。
模型是一個字一個字往外吐的,每吐一個新字,都要「回頭看」前面所有字。如果每次都重算前面每個字,太浪費,所以模型會把每個 token 算過的東西(Key 和 Value)先存起來、後面直接拿來用——這份存起來的東西就是 KV cache。
它和權重最大的不同是:權重是固定的(就是那 ~18GB,跑一頁跑一百頁都一樣),但 KV cache 是會變化的,長度取決於「一次餵多少 token × 同時處理幾個請求」。所以一張卡能同時服務幾個請求、能吃多長的內容,其實是看「權重佔完後,剩多少空間留給 KV cache」。而 KV cache 的算法為:
每個 token = 2(Key + Value)× 層數 × KV 頭數 × 每頭維度 × 每個數字幾 bytes
根據 Qwen 3 的config:
"text_config": {
"head_dim": 128,
"num_hidden_layers": 64,
"num_key_value_heads": 8,
},
代進去(64 層、8 個 KV 頭、每頭 128 維、KV cache 用 FP16 存所以 2 bytes——這是 KV_CACHE_DTYPE 決定的,和權重是不是 INT4 無關):
2 × 64 × 8 × 128 × 2 ≈ 256 KB / 每個 token
所以粗估一下:
| 情境 | token 數 | KV cache |
|---|---|---|
| 一個 OCR 請求(圖 ~1300 + 文字 + 輸出) | ~4000 | ~1 GB |
| 塞滿 16K 的 context | 16384 | ~4 GB |
也就是說,一個 OCR 請求大概吃 1GB 的 KV cache。假設權重佔完後還剩 30GB,那大概能同時跑 30 個請求。
(補充:KV cache 也可以另外量化——把 KV_CACHE_DTYPE 設成 fp8 就變成 1 byte、直接砍一半。這是獨立於權重量化的另一個可調整的地方。)
而前面提到那 ~1300 個 vision token 也一起算在裡面,所以圖片解析度越高、token 越多、KV cache 越大、prefill 也越慢。
GPU 估算
把前面兩塊加起來,大概就知道要租多大的卡了:
- 權重:Int4 量化後 ~18GB,這塊是固定的
- KV cache + 其他:留點空間給並發,再加上激活、vision encoder、框架本身的 overhead,抓個十幾 GB
粗估下來大概要 30GB 以上的 GPU。
運氣問題
一開始部署完成後,我在本地隨機挑選20頁比較複雜的PDF 文檔頁面、呼叫 Runpod endpoint 去解析,都很成功;但當我把服務上雲(upload image -> GPU pod -> callback -> R2 bucket)的時候,發現光是解析5頁,總會有一頁遇到 OOM/ time out 的問題。但我也不想隨便往上調GPU容量,總覺得是哪裡有問題,所以修改一些 vllm 的環境變量:
GPU_MEMORY_UTILIZATION(0.9 → 0.75):一開始以為是顯存留太少,就把 vLLM 佔用的比例調低、想多留點餘量給它。結果 OOM 照樣發生。MAX_NUM_SEQS(8 → 2):接著以為是一次塞太多請求進同一張卡、KV cache 被塞爆,就把 「同時處理幾個序列」 往下調。但翻 log 才發現 KV cache 根本用不完,也不是這條。GDN_PREFILL_BACKEND=triton:這個應該是比較接近真相的原因——這個模型(Qwen3.5 用的是比較新的注意力架構)的核在 worker 冷啟動時要「即時編譯」,而編譯過程本身十分佔用記憶體、結果被系統砍掉。設了之後確實好一些,但還是有些 worker 沒吃到這個設定、繼續OOM/timeout。 vllm 的 log:FlashInfer GDN prefill kernel is JIT-compiled; first run may take a while to compile. Set `--gdn-prefill-backend triton` to avoid JIT compile time
推理導致的,降級
往回看、翻 log 才發現:不同的 worker 跑的核居然不一樣——有的印 Using FlashInfer、有的印 Using Triton。而 FlashInfer 那種要在冷啟動即時編譯(編譯目標是 sm_90a,也就是 Hopper H100/H200 這類卡),編譯本身吃爆記憶體被砍(exit 137)。叫AI 查看 vLLM 源碼才知道,選哪種核是看 GPU 型號決定的——只有 Hopper 卡才會走 FlashInfer,而 RunPod 每次長 worker 給的卡不固定。所以同一批 5 頁,哪一頁不巧落到用 FlashInfer 的 worker 上就掛,落到 Triton 的就正常。失敗看起來隨機,但根源其實在「GPU / worker 不一致」,不是模型自己在選核。
再加上這個模型是推理模型,每次回答前會先跑一大段思考(<think>),一頁要多花好幾倍時間,就算沒 OOM 也很容易撞到執行超時。等於是「不穩的核」+「慢的推理」兩個問題疊在一起。
想清楚之後就決定:與其繼續跟這個新架構硬碰,不如換一個更成熟、更單純的版本。所以測試了上一代的 Qwen3-VL-32B:
- 它是 dense 架構(不是那個要即時編譯的新注意力),不管 worker 落到哪張卡,都不會有那個會 OOM 的核;
- 它是 non-reasoning 版,不會先跑一段
<think>,每頁快很多、也不容易超時。
結果一換上去,OOM 和超時都不見了,速度還快了好幾倍。
利用辨識完成的20頁進行 eval
換了模型,還是要確認一下「降級」有沒有讓辨識品質變差。我拿本文一開始提到的 20 頁測試文本來餵給 Qwen3-VL-32B,結果還不錯:
- 整體字元一致度 ~94%。
- 不一致的地方,多半是排版差異(換行、空白),或者是新模型其實表現更好——比如它用的是標準字形(
為而不是舊體的爲)、會把上標的註腳(⁶)正確標出來、表格也還原得比較整齊。 - 最關鍵的是:法律條文中需要注意幾個地方——條號(第 X 條)、還有「得 / 應」這種一字之差就是「可以」變「必須」的字——兩個模型完全一致。
所以這樣看起來,降級到 Qwen3-VL 並沒有付出品質的代價,因此我就拿去跑全量的PDF OCR。
結論
有了AI coding agent,在做技術選型的時候真的方便很多,叫AI 寫個demo 然後人類評估一下好壞就好;而在部署 VLM 的時候,由於不太懂 Qwen3.5-35B-A3B 導致花了很多時間在處理 OOM 的問題,最後沒想到降了一級、選擇non-reasoning 的版本就可以快速完成。
而且未來有關LLM、VLM 的任務,真的不一定要選擇最新最強的模型,畢竟一切都還在變化。就依照自己業務邏輯所需要的、和目前市面上模型的版本找到局部最優解。
Ref
- Qwen2-VL: Enhancing Vision-Language Model’s Perception of the World at Any Resolution
- Qwen3-VL-32B config
- vLLM 源碼:GDN prefill kernel 後端選擇(
--gdn-prefill-backend、依 GPU 型號決定 FlashInfer/Triton)