Reasoning effort trên LLM local là gì?
Reasoning effort chỉ đổi số token suy luận trước câu trả lời, không đổi weights hay quantisation. Tìm hiểu chi phí thật trên CPU, GPU và context window.
Mức độ suy luận thay đổi gì trên LLM chạy local
Mức độ suy luận là một thiết lập cho biết model cần suy nghĩ bao lâu trước khi trả lời. Thiết lập này chỉ thay đổi độ dài của phần suy luận, không thay đổi điều gì khác. Các weight trên disk giống hệt nhau ở mọi mức, quantisation cũng giống nhau, và câu trả lời được tạo từ cùng một forward pass. Thay đổi duy nhất là số token model dùng trước tiên cho scratchpad nội bộ.
Điểm này quan trọng vì các token đó được tính ở đâu. Với hosted API, token suy luận xuất hiện trên hóa đơn. Với VPS do bạn sở hữu, chúng tiêu tốn thời gian generation trên CPU hoặc GPU của bạn, đồng thời chiếm dung lượng trong context window. Nếu để model ở mức effort cao nhất, model có thể dùng phần lớn output cho suy luận trước khi từ đầu tiên của câu trả lời xuất hiện. Trên hardware tự host, điều này có thể tạo ra khác biệt giữa phản hồi trong hai giây và phản hồi trong hai phút.
Mức độ nằm ở đâu: chat template, không phải weights
Model có khả năng suy luận được huấn luyện để xuất một đoạn reasoning, thường được bọc trong tag <think> và </think>, trước khi đưa ra câu trả lời cuối cùng. Mức độ effort là một instruction mà chat template của model ghi vào prompt. Template đó là một file Jinja đi kèm model. Template đọc một variable như reasoning_effort rồi render một dòng ở cấp system khác nhau cho từng giá trị. Model được huấn luyện để rút ngắn hoặc kéo dài scratchpad dựa trên dòng đó.
Có hai hệ quả. Tên các mức độ thuộc về model, không thuộc về runtime của bạn. Vì vậy, một tên trong model card này có thể không có ý nghĩa với model khác. Ngoài ra, nếu bất kỳ thành phần nào trong chuỗi thay chat template của model bằng template generic, variable sẽ không được render và setting sẽ âm thầm không có tác dụng.
Được kiểm tra vào 2026-08-20, model card của Qwen3.8-27B ghi rõ 3 mức độ effort: low, medium và xhigh, trong đó xhigh là mặc định. Không có high. Thinking được bật hoặc tắt bằng enable_thinking; mặc định là bật. Model card cũng ghi rõ preserve_thinking, mặc định bật, để giữ reasoning từ các lượt trước trong conversation history. gpt-oss dùng low, medium và high thay thế. Nhiều họ model khác chỉ nhận một boolean, không có tùy chọn nào khác. Hãy đọc model card của đúng version bạn đã pull, vì những tên này không phải standard. Chạy model 27B trên VPS là bước đầu tiên. Trang này nói về các thiết lập cần dùng sau khi model trả lời.
Vì sao suy luận nhiều tốn kém hơn trên VPS
Số token đầu ra. Token suy luận cũng là token được sinh ra. Chúng đi qua cùng vòng lặp decode như token trong câu trả lời, với tốc độ token mỗi giây mà phần cứng của bạn xử lý được. Giả sử một tác vụ tạo ra 200 token câu trả lời và 4,000 token suy luận. Bạn đã sinh 4,200 token nhưng người đọc chỉ thấy 200 token. Tốc độ decode phụ thuộc vào băng thông bộ nhớ và quantisation bạn đã chọn, nên yếu tố duy nhất còn có thể điều chỉnh là tổng số token.
Thời gian thực tế. Người dùng chờ token đầu tiên của câu trả lời, vì trước đó màn hình vẫn trống hoặc chỉ hiển thị spinner đang thu gọn. Suy luận được xuất trước, nên thời gian chờ gần bằng số token suy luận chia cho tốc độ decode, cộng thêm thời gian xử lý prompt. Tăng gấp đôi độ dài suy luận sẽ làm thời gian chờ tăng gấp đôi.
Context. Token suy luận chiếm chỗ trong context window như mọi token khác. Khi bật preserve_thinking, scratchpad từ lượt đầu vẫn nằm trong prompt ở lượt thứ năm. Vì vậy, việc xử lý prompt chậm dần qua từng lượt trong khi window đầy lên từ cả hai phía. Tăng num_ctx để chứa phần này sẽ tốn thêm bộ nhớ KV cache. Trên VPS không có GPU, bộ nhớ đó là system RAM mà có thể bạn không còn dư.
Khi nào nên tăng mức, và khi nào nên để thấp
Hãy tăng mức cho những việc mà một bước trung gian sai sẽ làm hỏng kết quả: số học nhiều bước và chuyển đổi đơn vị, lập kế hoạch chỉnh sửa trên nhiều file, code phải compile, và các bài toán ràng buộc trong đó một đáp án phải đồng thời thỏa mãn nhiều điều kiện. Với các việc này, scratchpad thực sự có tác dụng. Scratchpad dài hơn là cách ít tốn kém để phát hiện lỗi mà model có thể mắc phải nếu không có nó.
Hãy để mức thấp khi đáp án đã có sẵn trong input và công việc chỉ là chuyển nó sang dạng khác. Các tác vụ trích xuất, phân loại, gắn tag, dịch, viết lại, tóm tắt và định dạng đều thuộc nhóm này. Phần suy luận chủ yếu chỉ nhắc lại yêu cầu. Nó cũng tạo cơ hội để model tự bác bỏ trực giác đầu tiên vốn đã đúng.
Với các tác vụ tương tác, cũng nên để mức thấp. Trong hộp chat hoặc editor, bạn đang tham gia xử lý, nên câu trả lời nhanh để bạn sửa thường tốt hơn câu trả lời chậm mà bạn phải chờ. Đây là đánh đổi thực sự khi trỏ coding agent vào một model local: agent thực hiện nhiều call nhỏ, và chi phí suy luận được tính cho từng call.
Cách thiết lập level trong llama.cpp
llama.cpp ghi biến trực tiếp vào template. Vì vậy, đây là runtime mà bạn có thể xác nhận chắc chắn level đã được truyền đến. Trỏ -m đến file GGUF bạn đã có.
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 sử dụng chat template của chính model và được bật mặc định trong các bản build hiện tại. --reasoning-effort chấp nhận default, minimal, low, medium, high, xhigh hoặc max, trong đó default nghĩa là giữ nguyên giá trị mặc định của template. Danh sách này là vocabulary của llama.cpp, không phải của model, nên chỉ truyền một tên có trong model card: level mà template không định nghĩa có thể gây lỗi template tại thời điểm xử lý request. --reasoning-format deepseek đưa phần reasoning ra khỏi message.content và chuyển vào message.reasoning_content. Nhờ vậy, có thể đo riêng phần này trong section tiếp theo.
Để tắt quá trình suy nghĩ thay vì rút ngắn nó, hãy tự đặt biến của template:
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf --jinja \
--chat-template-kwargs '{"enable_thinking": false}'--reasoning-budget là một cơ chế khác. Nó giới hạn segment reasoning theo số token. 0 kết thúc segment ngay lập tức, còn -1 giữ segment không bị giới hạn, thay vì yêu cầu model lập kế hoạch ngắn hơn. Cả hai flag đều áp dụng trên toàn server. llama-server không nhận reasoning_effort dưới dạng field riêng cho từng request. Vì vậy, để phục vụ đồng thời hai level effort, cần chạy hai process trên hai port.
vLLM cho phép truyền cùng biến này trong từng request, bên trong body tương thích với OpenAI:
{"model": "Qwen/Qwen3.8-27B",
"messages": [{"role": "user", "content": "Summarise this changelog in two lines."}],
"chat_template_kwargs": {"reasoning_effort": "medium"}}Cách đặt level trong Ollama
Ollama có field riêng là think trên /api/chat và /api/generate. Field này nhận true, false hoặc một trong low, medium, high và max, trong đó max yêu cầu level cao nhất mà model cung cấp. Với các model hỗ trợ thinking, tính năng này được bật mặc định.
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}Phần reasoning được trả về trong message.thinking và câu trả lời trong message.content, đã được tách sẵn cho bạn. Trong một session ollama run tương tác, /set think và /set nothink bật hoặc tắt tính năng này mà không cần restart.
Bây giờ hãy chú ý đến điểm không khớp. Ollama dùng các giá trị low, medium, high và max. Template của Qwen3.8 định nghĩa low, medium và xhigh. Cần có cơ chế ánh xạ giá trị này sang giá trị kia. Ngoài ra, model của Ollama chứa template được đóng gói bên trong tag, thay vì file Jinja từ repository gốc. Vì vậy, level bạn đặt có được truyền đến model hay không phụ thuộc vào template đã đóng gói đó. Không được mặc định rằng nó đã hoạt động. Việc đo kiểm chỉ mất khoảng một phút.
Cách đo xem level có thực sự được áp dụng hay không
Gửi cùng một prompt ở nhiều level với temperature bằng 0, rồi so sánh số lượng token. Ở đây jq tạo phần body nên bạn không phải tự escape dấu ngoặc kép.
for level in low medium max; 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))
}'
doneeval_count là tổng số token được tạo, bao gồm cả reasoning, nên chênh lệch giữa hai level gần như hoàn toàn là reasoning. thinking_chars cung cấp trực tiếp phần phân tách. Có hai điều cần đúng: các con số phải thay đổi giữa các level, và câu trả lời vẫn phải đúng ở level thấp hơn. Nếu eval_count gần như không thay đổi trong cả ba lần chạy, level đang bị bỏ qua. Khi đó, cách khắc phục là dùng runtime có truyền level này, không phải đổi sang tên level khác.
Tổng thời gian chỉ phản ánh một nửa vấn đề. Vì vậy, hãy đo khoảng thời gian đến token trả lời đầu tiên bằng cách stream và dừng ở chunk content không rỗng đầu tiên. Cách này cần jq và 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
doneChạy với low, rồi chạy lại với max. Chênh lệch là thời gian chờ bạn phải trả thêm. Trên llama.cpp, các con số tương tự xuất hiện bên trong response, không cần thực hiện phép tính bằng 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
}'Hãy thực hiện trên chính máy chủ của bạn. Một phép so sánh effort được công bố được đo trên phần cứng không phải của bạn, còn tốc độ decode của bạn là yếu tố chuyển số lượng token thành số giây. Đo số token mỗi giây trên máy chủ của bạn cung cấp giá trị đó: số token reasoning chia cho tốc độ decode chính là thời gian chờ bạn vừa thêm vào.
Điều gì có thể xảy ra
Câu trả lời bị cắt, hoặc content rỗng trong khi thinking có nội dung. Giới hạn sinh đã bị phần reasoning sử dụng hết. num_predict của Ollama giới hạn toàn bộ phần sinh, bao gồm cả reasoning, và reasoning được xử lý trước. Vì vậy, ở mức effort cao, giới hạn 512 token có thể khiến response kết thúc trước khi câu trả lời bắt đầu. Ollama báo "done_reason": "length" trong response đó. Hãy tăng giới hạn hoặc giảm effort. Cách num_predict đếm token giải thích chi tiết cách hai yếu tố này tương tác.
Thay đổi level không tạo ra khác biệt. Số lượng token giống nhau ở mọi level. Runtime có thể không truyền biến này, hoặc template không đọc nó. Hãy kiểm tra template mà runtime thực sự đang dùng, thay vì template trong repository gốc. llama.cpp với --jinja và --chat-template-kwargs tự ghi biến vào template, nên đây là cách kiểm tra đối chứng tốt: nếu level hoạt động ở đó nhưng không hoạt động ở nơi khác, model không có vấn đề và runtime còn lại đang bỏ qua biến này.
Tên level bị từ chối. Lỗi template tại thời điểm gửi request, hoặc lỗi ở message đầu tiên trong khi server vẫn hoạt động bình thường, thường có nghĩa là bạn đã truyền một level mà template không định nghĩa. Ví dụ, bạn truyền high cho model chỉ liệt kê các level low, medium và xhigh trong model card.
Chat nhiều lượt chậm dần ở mọi lượt. Reasoning cũ đang được giữ trong history. Đặt preserve_thinking thành false nếu model hỗ trợ, hoặc xóa trường thinking khỏi các message bạn gửi lại. Nếu không, thời gian xử lý prompt sẽ tăng sau mỗi lượt trong khi độ dài câu trả lời vẫn giữ nguyên.
Chất lượng giảm khi dùng effort thấp cho một tác vụ mà bạn nghĩ là đơn giản. Một số tác vụ trích xuất thực ra không chỉ là trích xuất. Nếu input cần đổi đơn vị hoặc áp dụng tuần tự một quy tắc, đó là tác vụ reasoning dù output ngắn. Hãy tăng level cho riêng request đó thay vì tăng cho toàn bộ server.
Chạy đồng thời hai mức
llama.cpp cố định mức ngay khi khởi động. Vì vậy, một máy phục vụ cả editor và batch job chạy hằng đêm cần 2 process trên 2 port, mỗi process có --reasoning-effort riêng. 2 process cũng có nghĩa là 2 bản sao của weights trong memory, trừ khi bạn tách các job theo thời gian. Trên một VPS, cách tiết kiệm hơn thường là dùng server có mức effort thấp cho mọi tác vụ mà người dùng đang chờ, cộng với một lần chạy theo lịch ở mức effort cao hơn cho các tác vụ không có ai theo dõi. Điều gì xảy ra khi nhiều người dùng dùng chung một model local cũng áp dụng ở đây: reasoning tokens là công việc decode, nên tăng effort sẽ giảm concurrency thực tế của bạn khoảng cùng một hệ số với mức tăng của số token.
FAQ
Tôi nên dùng mức độ suy luận nào theo mặc định?
Bắt đầu ở mức thấp nhất mà model cung cấp và chỉ tăng khi bạn đã thấy tác vụ thất bại ở mức đó. Một số model thinking có mức mặc định cao. Tính đến tháng 8 năm 2026, Qwen3.8-27B mặc định dùng xhigh, là mức cao nhất. Mức mặc định này được chọn để cho kết quả tốt trên các bảng benchmark. Bảng benchmark không tính phí theo thời gian. Trên phần cứng của bạn, thời gian được tính bằng giây. Vì vậy, hãy coi mức cao hơn là tùy chọn cho từng tác vụ, không phải thiết lập mà mọi request đều kế thừa.
Các reasoning token có tính vào context window không?
Có. Chúng là các token thông thường trong output và nằm trong context window cùng với mọi nội dung khác. Việc chúng có được giữ lại ở turn tiếp theo hay không phụ thuộc vào runtime và model. Card của Qwen3.8 ghi rõ preserve_thinking, được bật mặc định. Thiết lập này giữ reasoning trước đó trong history, nên một conversation dài sẽ chứa toàn bộ scratchpad đã tạo. Đặt giá trị này thành false hoặc xóa field thinking khỏi các message bạn replay. Khi đó, thời gian xử lý prompt sẽ không tiếp tục tăng.
Tại sao thay đổi mức thinking không làm thay đổi số token của tôi?
Thiết lập này không được truyền vào chat template. Mức suy luận là một biến của template. Vì vậy, nó chỉ hoạt động khi runtime truyền biến đó và template được đóng gói có đọc biến này. Một số runtime đóng gói template riêng cùng model thay vì dùng file Jinja từ repository gốc. Khi đó, biến bị loại bỏ mà không có lỗi nào được in ra. Hãy kiểm tra bằng cách gửi cùng một prompt ở mức thấp nhất và cao nhất, đặt temperature bằng 0, rồi so sánh eval_count. Nếu số lượng gần như giống nhau trong phạm vi sai số, mức suy luận đang bị bỏ qua.
Giảm effort của reasoning có làm model kém chính xác hơn không?
Điều này phụ thuộc vào tác vụ. Bạn nên đo thay vì mặc định giả định. Khi câu trả lời đã có trong input, chẳng hạn như extraction hoặc rewriting, scratchpad ngắn hơn thường không làm thay đổi kết quả. Khi một bước trung gian phải chính xác thì bước cuối mới đúng, chẳng hạn như phép tính nhiều bước hoặc code phải compile, độ chính xác sẽ giảm khi scratchpad ngắn hơn. Hãy tạo một bộ gồm 20 prompt từ workload thực tế của bạn, chạy chúng ở 2 mức với temperature bằng 0, rồi đếm số câu trả lời sai. Con số đó phụ thuộc vào workload của bạn. Không có bảng được công bố nào có thể cung cấp con số này.