SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

本地 LLM 推理運算量設定與效能影響解析

解析本地 LLM 推理運算量(Reasoning effort)機制。說明如何透過聊天模板調整思考長度,並分析高運算量對 VPS 硬體效能、生成時間及上下文佔用的實際成本影響。

本地 LLM 的推理運算量變化機制

推理運算量(Reasoning effort)是一項設定,用於指示模型在回答前需思考的時間長度。此設定僅會改變推理區段的長度,不會影響其他部分。磁碟上的權重在各層級皆相同,量化(quantisation)方式亦無差異,且答案皆源自相同的向前傳遞(forward pass)。變動的僅是模型在正式回答前,於自身暫存區(scratchpad)所消耗的 token 數量。

此區別至關重要,原因在於這些 token 的歸屬。在使用託管 API 時,推理 token 會反映在帳單上。但在您自有的 VPS 上,這些 token 的代價體現在您 CPU 或 GPU 的生成時間,以及上下文視窗(context window)內的空間佔用。若將模型設為最高運算量,模型可能會在輸出第一個字之前,將大部分的輸出額度用於推理;在自架硬體上,這意味著回應時間可能從 2 秒拉長至 2 分鐘。

層級設定的位置:chat template,而不是模型權重

Thinking model 會先輸出推理區段,再輸出最終答案;推理區段通常會包在 <think> 和 </think> 標籤中。effort level 是由模型的 chat template 寫入 prompt 的指示。該 template 是隨模型提供的 Jinja 檔案。它會讀取 reasoning_effort 之類的變數,並針對不同值產生不同的 system-level 行,而模型則是根據這行內容訓練成縮短或延長 scratchpad。

這會帶來兩個結果。level 名稱屬於模型,不屬於 runtime,因此某個模型卡使用的名稱,對另一個模型可能毫無意義。如果處理鏈中的任何一環將模型的 chat template 替換成通用 template,該變數就不會被渲染,設定也會靜默失效。

截至 2026-10-12,Qwen3.8-27B 模型卡記載 3 個 effort level:low、medium 和 xhigh,預設值為 xhigh。沒有 high。Thinking 功能由 enable_thinking 切換,預設為開啟;模型卡也記載 preserve_thinking,同樣預設為開啟,用來保留對話歷史中較早回合的推理內容。gpt-oss 則使用 low、medium 和 high。許多其他模型系列只接受布林值,不支援更多層級。請查閱實際下載版本的模型卡,確認確切設定名稱,因為這些名稱並非標準。請先參閱 在 VPS 上執行 27B 模型。本頁說明模型開始回應後應設定哪些內容。

為什麼高推理成本在 VPS 上更昂貴

輸出 token。 推理會產生 token,這些 token 與回答 token 一樣,都需要經過解碼迴圈,並受硬體可達到的每秒 token 數限制。假設某項工作產生 200 個回答 token 和 4,000 個推理 token,實際產生的就是 4,200 個 token,而讀者只看到其中 200 個。解碼速率取決於記憶體頻寬、所選用的量化方式以及執行緒數量:llama.cpp 自己的調校說明警告,--threads 高於實體核心數量時,可能使 CPU 過度飽和,讓生成速度大幅下降。這些參數調整完成後,剩下可控制的因素就是 token 數量本身。

實際經過時間。 使用者等待的是回答的第一個 token,因為在此之前畫面不是空白,就是只顯示收合的載入指示器。系統會先輸出推理內容,因此等待時間大致等於推理 token 數量除以解碼速率,再加上提示處理時間。推理長度增加 1 倍,等待時間也會增加 1 倍。

內容視窗。 推理 token 與其他 token 一樣,會佔用內容視窗。啟用 preserve_thinking 後,第一輪的暫存內容在第五輪仍會保留於提示中,因此提示處理會逐輪變慢,而內容視窗也會從兩端逐漸填滿。提高 num_ctx 以容納這些內容會增加 KV cache 的記憶體需求;在沒有 GPU 的 VPS 上,這些記憶體來自可能沒有多餘空間的系統 RAM。

何時該提高層級,何時該保持較低層級

若工作內容中,錯誤的中間步驟會導致最終結果失效,則應提高層級。例如:多步驟算術與單位換算、跨多個檔案的編輯規劃、需要編譯的程式碼,以及必須同時滿足多項條件的約束問題。在這些情況下,草稿區(scratchpad)正在執行實際運算,延長草稿區是捕捉模型原本可能犯錯的低成本方式。

若答案已存在於輸入內容中,且任務僅為搬移資料,則應保持較低層級。資料提取、分類、標記、翻譯、改寫、摘要與格式化皆屬此類。此時推理區段多半只是在重述任務,反而會讓模型有空間推翻正確的第一直覺。

對於任何互動式任務,也應保持較低層級。在聊天視窗或編輯器中,您身處於迴圈內,因此能快速獲得並由您修正的答案,優於需要長時間等待的答案。這正是 將程式碼代理指向本機模型 背後真正的權衡考量:代理程式會進行多次小型呼叫,而每一次呼叫都會被課徵推理稅。

如何在 llama.cpp 中設定等級

llama.cpp 會直接將變數寫入範本,因此執行時可確認等級確實已傳入。將 -m 指向現有的 GGUF 檔案。

llama-server -m ./qwen3.8-27b-Q4_K_M.gguf \
  --jinja \
  --reasoning-effort medium \
  --reasoning-format deepseek \
  -c 32768 \
  --host 127.0.0.1 --port 8080

--jinja 使用模型本身的聊天範本,目前的版本預設會啟用此功能。--reasoning-effort 接受 default、minimal、low、medium、high、xhigh 或 max;其中 default 表示保留範本本身的預設值。這份清單是 llama.cpp 使用的詞彙,而不是模型的詞彙,因此只能傳入模型卡列出的名稱:如果範本未定義該等級,請求執行時可能會產生範本錯誤。--reasoning-format deepseek 會將推理內容從 message.content 移至 message.reasoning_content,因此下一節可以測量兩者的區隔。

若要關閉思考,而不是縮短思考內容,請自行設定範本變數:

llama-server -m ./qwen3.8-27b-Q4_K_M.gguf --jinja \
  --chat-template-kwargs '{"enable_thinking": false}'

--reasoning-budget 是不同的機制。它會限制推理區段的 token 數量;0 會立即結束推理,-1 則不設限制,而不是要求模型規劃較短的推理內容。兩個旗標都會設定伺服器的預設值。截至 2026-10-12,llama-server 的 /v1/chat/completions 也會在請求本文中接受 reasoning_effort 和 chat_template_kwargs,將等級傳入範本,僅套用於該次呼叫;none 則會關閉推理。因此,單一程序可以提供兩種推理強度。

vLLM 會在與 OpenAI 相容的本文中,針對每次請求公開相同的變數:

{"model": "Qwen/Qwen3.8-27B",
 "messages": [{"role": "user", "content": "Summarise this changelog in two lines."}],
 "chat_template_kwargs": {"reasoning_effort": "medium"}}

如何在 Ollama 中設定 level

Ollama 在 /api/chat 和 /api/generate 上使用專屬欄位 think。此欄位接受 true、false 或 level 名稱。截至 2026-10-12,對於會發布 thinking metadata 的模型,名稱由模型自行定義:/api/show 會在 thinking.values 下列出這些名稱,並在旁邊標示預設值。如果指定的名稱不在模型列出的清單中,Ollama 會改用該預設值,且不會顯示錯誤。沒有這項 metadata 的模型則使用 Ollama 舊版固定的 low、medium、high 和 max。省略 think 時,會套用模型自己的預設值;Qwen3.8 的預設值是啟用 thinking。

ollama run qwen3.8:27b --think=low "Draft a one line commit message for a README typo fix"
{"model": "qwen3.8:27b",
 "messages": [{"role": "user", "content": "Which HTTP status code means the request body was too large?"}],
 "think": "low",
 "stream": false}

推理內容會回傳在 message.thinking,答案則回傳在 message.content,兩者已替你分開。在互動式 ollama run 工作階段中,/set think 和 /set nothink 可在不重新啟動的情況下切換此功能,而 /set think low 可選取 level。

現在來看這項設定可能無聲失效的情況。Qwen3.8 的 template 定義了 low、medium 和 xhigh。Ollama 模型會在其 tag 內附帶 template,而不是使用原始 repository 中的 Jinja 檔案。只有在該封裝模型列出某個 level 時,Ollama 才會傳遞該 level。請先確認你的 tag 宣告了哪些 level:

curl -s http://localhost:11434/api/show -d '{"model": "qwen3.8:27b"}' | jq .thinking

如果名稱不在 values 中,例如此模型的 high 或 max,模型會改用預設值執行。不要假設設定已生效。測量結果約需 1 分鐘。

如何量測該層級是否確實生效

在多個層級使用相同提示,並將 temperature 設為 0,然後比較 token 數量。以下三個名稱是 Qwen3.8 的模型卡列出的名稱;如果 /api/show 為你的標籤列出不同名稱,請改用那些名稱。此處由 jq 建立本文,因此不必手動跳脫引號。

for level in low medium xhigh; do
  body=$(jq -n --arg lvl "$level" '{
    model: "qwen3.8:27b",
    messages: [{role: "user", content: "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take? Answer in minutes."}],
    think: $lvl,
    stream: false,
    options: {temperature: 0, num_ctx: 8192}
  }')
  echo "== $level"
  curl -s http://localhost:11434/api/chat -d "$body" | jq '{
    thinking_chars: (.message.thinking // "" | length),
    answer_chars: (.message.content | length),
    eval_count: .eval_count,
    seconds: (.total_duration / 1e9),
    tok_per_sec: (.eval_count / (.eval_duration / 1e9))
  }'
done

eval_count 代表產生的所有 token,包含推理內容,因此兩個層級之間的差距幾乎全是推理內容。thinking_chars 會直接提供兩者的拆分結果。應確認兩點:數字會隨層級變動,而較低層級的答案仍然正確。如果三次執行中 eval_count 都落在雜訊範圍內,表示系統忽略了該層級。先透過 /api/show 確認名稱,因為 Ollama 對未列出的名稱會使用預設值;如果名稱相符,問題就在於執行環境沒有傳遞該層級。

總耗時只反映一半情況,因此請透過串流量測第一個答案 token 的間隔,並在第一個非空的 content 區塊處停止。這需要 jq 和 bc。

start=$(date +%s.%N)
curl -sN http://localhost:11434/api/chat -d '{
  "model": "qwen3.8:27b",
  "messages": [{"role": "user", "content": "Explain what a reverse proxy does, in three sentences."}],
  "think": "low",
  "stream": true
}' |
while IFS= read -r line; do
  if [ -n "$(printf '%s' "$line" | jq -r '.message.content // ""')" ]; then
    echo "first answer token after $(echo "$(date +%s.%N) - $start" | bc)s"
    break
  fi
done

在 low 執行一次,再於 xhigh 執行一次。兩者的差值就是你為較高層級付出的等待時間。在 llama.cpp 中,相同數字會包含在回應內,不需要使用 shell 進行算術運算:

curl -s http://localhost:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "local", "temperature": 0,
       "messages": [{"role": "user", "content": "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take?"}]}' | jq '{
  reasoning_chars: (.choices[0].message.reasoning_content // "" | length),
  answer_chars: (.choices[0].message.content | length),
  predicted_n: .timings.predicted_n,
  tok_per_sec: .timings.predicted_per_second
}'

請在自己的主機上執行這項測試。已發布的效能比較是在不屬於你的硬體上量測,且你的解碼速率才是將 token 數量換算為秒數的因素。在自己的伺服器上量測每秒 token 數 會提供這項數值:推理 token 數除以你的解碼速率,就是你新增的等待時間。

發生什麼問題

回答被截斷,或 content 為空而 thinking 有內容。 產生上限被推理消耗了。Ollama 的 num_predict 會限制整個產生流程,也包含推理;而且推理會先執行,因此在高強度下設定 512 tokens 的上限,可能會在回答開始前就結束。Ollama 會在該回應中回報 "done_reason": "length"。請提高上限,或降低推理強度。How num_predict 計算 tokens 詳細說明兩者的互動方式。

調整強度沒有任何變化。 每個強度的 token 數量都相同。可能是 runtime 沒有傳入變數、template 沒有讀取變數,或在 Ollama 中,該名稱不在標籤列出的名稱內,因此回退到預設值。請檢查 runtime 實際使用的 template,不要只檢查原始 repository 中的版本。llama.cpp 搭配 --jinja 與 --chat-template-kwargs 時會手動寫入變數,因此適合作為對照:如果強度在這裡有效、在其他地方無效,表示模型正常,而是其他 runtime 捨棄了該變數。

強度名稱遭到拒絕。 如果請求時出現 template 錯誤,或健康狀態正常的 server 在第一則訊息時失敗,通常表示你傳入了 template 未定義的強度,例如將 high 傳給只列出 low、medium 與 xhigh 的模型。在 Ollama 中,沒有 thinking metadata 的模型只接受 low、medium、high 與 max;其他值會回傳 invalid think value 錯誤。

多輪對話的每一輪都變慢。 舊的推理內容仍保留在歷史記錄中。如果模型支援,請將 preserve_thinking 設為 false,或從回傳給模型的 messages 中移除 thinking 欄位。否則每一輪的 prompt 處理量都會增加,但回答長度維持不變。

在你認為簡單的工作上,低強度會降低品質。 有些擷取工作並不只是擷取。如果輸入需要進行單位轉換,或依序套用規則,這就是輸出很短的推理工作。請只針對該次呼叫提高強度,不要調高整台 server 的設定。

同時執行兩個層級

llama.cpp 啟動時會使用 --reasoning-effort 設定預設層級,但目前的版本也接受每次請求的 reasoning_effort。因此,單一程序和單份權重即可同時服務低層級的編輯器,以及較高層級的夜間批次工作。若要在啟動時固定各自的層級,也可以使用兩個程序和兩個連接埠;但除非將兩項工作錯開執行,否則記憶體中需要保留兩份權重。無論採用哪種方式,這些工作都會共用同一台機器的解碼容量。因此,在單一 VPS 上,較省資源的配置通常是讓需要人工等待的工作使用低層級,並排程執行較高層級的工作,供無人即時監看的任務使用。多位使用者共用單一本機模型時會發生什麼事 同樣適用於此:推理 token 需要解碼處理,因此提高層級後,有效並行處理量大約會依 token 數量增加的相同比例下降。

FAQ

預設應使用哪個推理努力程度?

先從模型提供的最低程度開始,只有在觀察到某些工作失敗時,才提高程度。許多思考模型出貨時都採用較高的預設值;截至 2026 年 10 月,Qwen3.8-27B 的預設值為 xhigh,也就是最高程度。這個預設值是為了在基準測試表現良好,而基準測試不會計算時間成本。在自己的硬體上,時間會以秒數計算,因此應將較高程度設為個別工作才選擇的選項,而不是每個請求都繼承的設定。

推理 token 會計入 context window 嗎?

會。它們是輸出中的一般 token,會和其他內容一併占用 context window。下一輪是否保留這些 token,取決於 runtime 和模型。Qwen3.8 的模型卡記載 preserve_thinking 預設啟用;此設定會將先前的推理保留在歷史記錄中,因此長對話會帶上模型產生的每個暫存推理內容。將它設為 false,或從重新送出的 messages 中移除 thinking 欄位,prompt 處理的內容就不會持續增加。

為什麼變更思考程度後,token 數量沒有變化?

這項設定沒有傳到 chat template。程度是 template 變數,因此只有在 runtime 傳入該變數,且套件中的 template 讀取該變數時才會生效。有些 runtime 會隨模型提供自己的 template,而不是使用原始 repository 中的 Jinja 檔案;此時該變數會在沒有任何錯誤訊息的情況下被捨棄。Ollama 也會以模型預設的程度執行模型未列出的程度名稱,同樣不會顯示錯誤。傳送相同 prompt,分別使用最低和最高程度,並將 temperature 設為 0,再比較 eval_count,即可驗證。如果兩者的數量在誤差範圍內相同,就表示該程度設定被忽略了。

降低推理努力程度會讓模型變得不準確嗎?

這取決於工作內容,應實際測量,而不是直接假設。在答案已存在於輸入中的情況,例如擷取或改寫,較短的暫存推理內容通常不會造成變化。在最終答案必須建立在正確中間步驟上的情況,例如多步驟算術或必須通過編譯的程式碼,暫存推理內容較短時,準確度確實會下降。根據實際工作負載建立 20 個 prompt,使用兩種程度執行,並將 temperature 設為 0,再計算錯誤答案的數量。這個數字取決於你的工作負載,任何已發布的表格都無法替你提供這項數據。