SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

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.

Nơi mức độ được xác định: chat template, không phải weights

Thinking model được huấn luyện để xuất một đoạn reasoning, thường được bao quanh bằng các thẻ <think> và </think>, trước câu trả lời cuối cùng. Mức độ suy luận là một instruction do chat template của model ghi vào prompt. Template đó là một file Jinja được đóng gói cùng model. Nó đọc một biến 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ó 2 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, tên từ 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, biến 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-10-12, model card của model Qwen3.8-27B ghi nhận 3 mức độ suy luận: low, medium và xhigh, trong đó xhigh là mặc định. Không có high. Tính năng thinking được bật bằng enable_thinking và mặc định là bật. Model card cũng ghi nhận preserve_thinking, mặc định là bật, để giữ reasoning từ các turn trước trong conversation history. gpt-oss thay vào đó dùng low, medium và high. Nhiều họ model khác chỉ chấp nhận một giá trị boolean và không hỗ trợ thêm gì. Hãy đọc model card của đúng version bạn đã pull, vì những tên này không phải standard. Trước tiên, hãy xem Cách chạy model 27B trên VPS. Trang này nói về các thiết lập cần dùng sau khi model trả lời.

Vì sao effort cao tốn nhiều tài nguyên hơn trên VPS

Số token đầu ra. Token dùng để reasoning cũng là token được sinh ra. Chúng đi qua cùng vòng lặp decode như câu trả lời, với tốc độ token/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 reasoning. 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ớ, mức quantisation bạn chọn và số thread: ghi chú tuning của llama.cpp cảnh báo rằng giá trị --threads cao hơn số core vật lý có thể làm CPU quá tải và khiến quá trình sinh token chậm hẳn. Sau khi đã tuning các yếu tố này, biến số còn lại là chính số lượng token.

Thời gian thực. Người dùng chờ token đầu tiên của câu trả lời, vì mọi thứ trước đó chỉ là màn hình trống hoặc spinner đang thu gọn. Reasoning được xuất trước, nên thời gian chờ xấp xỉ số token reasoning chia cho tốc độ decode, cộng với thời gian xử lý prompt. Tăng gấp đôi độ dài reasoning sẽ làm thời gian chờ đó tăng gấp đôi.

Context. Token reasoning chiếm chỗ trong context window như mọi token khác. Khi bật preserve_thinking, scratchpad từ lượt đầu vẫn còn trong prompt ở lượt thứ năm, nên xử lý prompt chậm hơn sau mỗi lượt trong khi window bị lấp đầy 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, phần bộ nhớ đó lấy từ system RAM, và có thể bạn không còn đủ RAM trống.

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 đặt mức 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 mức đã đượ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. Vì vậy, chỉ truyền tên có trong model card: mức không được template định nghĩa có thể gây lỗi template khi xử lý request. --reasoning-format deepseek chuyển phần reasoning ra khỏi message.content và đưa vào message.reasoning_content. Nhờ đó, phần tách này có thể đo được 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 để 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 đặt giá trị mặc định của server. Tính đến 2026-10-12, /v1/chat/completions của llama-server cũng nhận reasoning_effort và chat_template_kwargs trong request body, rồi truyền mức này cho template chỉ trong lần gọi đó; none sẽ tắt reasoning. Vì vậy, một process có thể phục vụ hai mức effort.

vLLM cung cấp cùng biến này cho 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 tên level. Tính đến 2026-10-12, với model công bố metadata về thinking, các tên này do chính model định nghĩa: /api/show liệt kê chúng dưới thinking.values và hiển thị default bên cạnh. Nếu dùng tên mà model không liệt kê, Ollama sẽ quay về default đó mà không báo lỗi. Model không có metadata này sử dụng bộ cố định cũ của Ollama gồm low, medium, high và max. Nếu bỏ think, Ollama sẽ dùng default của model. Với Qwen3.8, default là bật 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}

Phần reasoning được trả về trong message.thinking, còn câu trả lời nằm trong message.content và đã đượ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 khởi động lại. /set think low dùng để chọn level.

Bây giờ hãy xem trường hợp có thể âm thầm thất bại. Template của Qwen3.8 định nghĩa low, medium và xhigh. Một Ollama model chứa template được đóng gói bên trong tag của nó, thay vì dùng file Jinja từ repository gốc. Ollama chỉ truyền level nếu model đã đóng gói đó liệt kê level này. Trước tiên, hãy kiểm tra tag của bạn đang công bố những gì:

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

Tên không nằm trong values, chẳng hạn high hoặc max đối với model này, sẽ chạy bằng default. Đừng mặc định rằng thiết lập đã có hiệu lực. Việc đo kiểm 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. Ba tên bên dưới là các tên được card của Qwen3.8 liệt kê; nếu /api/show liệt kê tên khác cho tag của bạn, hãy dùng các tên đó. Ở đâ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 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 là mọi token được tạo ra, bao gồm cả reasoning, nên chênh lệch giữa hai level hầu 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 ở level thấp hơn vẫn phải chính xác. Nếu eval_count gần như không thay đổi trong cả ba lần chạy, level đang bị bỏ qua. Trước tiên hãy đối chiếu tên với /api/show, vì Ollama sẽ chạy tên không được liệt kê ở default; nếu tên khớp, vấn đề cần sửa là runtime phải truyền level.

Tổng thời gian chỉ phản ánh một nửa vấn đề. 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
done

Chạy lệnh này với low, rồi chạy lại với xhigh. 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ự được trả về bên trong response, không cần tính toán 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 việc này trên máy của bạn. Một 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à đại lượng chuyển số lượng token thành số giây. Đo token mỗi giây trên server của bạn cung cấp đại lượng đó: số reasoning token chia cho tốc độ decode là thời gian chờ bạn vừa thêm.

Có vấn đề gì

Câu trả lời bị cắt, hoặc content trống trong khi thinking có dữ liệu. Giới hạn sinh đã bị phần suy luận sử dụng hết. num_predict của Ollama giới hạn toàn bộ quá trình sinh, bao gồm cả suy luận. Vì suy luận được thực hiện trước, giới hạn 512 token ở mức effort cao có thể khiến phản hồi kết thúc trước khi phần trả lời bắt đầu. Ollama báo "done_reason": "length" cho phản hồi đó. 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 chúng tương tác.

Mức không làm thay đổi kết quả. Số lượng token giống nhau ở mọi mức. Có thể runtime không truyền biến, template không đọc biến, hoặc trên Ollama, tên đó không nằm trong danh sách của tag nên runtime dùng giá trị mặc định. Hãy kiểm tra template mà runtime thực sự đang dùng, không phải template trong repository gốc. llama.cpp với --jinja và --chat-template-kwargs tự ghi biến này, nên có thể dùng làm đối chứng: nếu mức 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 loại bỏ biến.

Tên mức bị từ chối. Lỗi template khi 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 mức mà template không định nghĩa. Ví dụ, bạn truyền high cho model chỉ liệt kê low, medium và xhigh trong model card. Trên Ollama, model không có metadata về thinking chỉ chấp nhận low, medium, high và max. Mọi giá trị khác sẽ trả về lỗi invalid think value.

Chat nhiều lượt chậm dần sau mỗi lượt. Reasoning cũ đang được giữ trong history. Đặt preserve_thinking thành false nếu model hỗ trợ tùy chọn này, hoặc loại bỏ 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 ở mức effort thấp trong một tác vụ 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 chuyển đổi đơn vị hoặc áp dụng một quy tắc theo thứ tự, đó là tác vụ reasoning có output ngắn. Hãy tăng mức cho riêng request đó thay vì tăng cho toàn bộ server.

Chạy đồng thời hai mức

llama.cpp đặt mức mặc định khi khởi động bằng --reasoning-effort, nhưng các bản build hiện tại cũng nhận reasoning_effort cho từng request. Vì vậy, một process và một bản weights có thể phục vụ editor ở mức thấp và job batch chạy ban đêm ở mức cao hơn. Nếu muốn cố định từng mức khi khởi động, bạn cũng có thể chạy hai process trên hai port. Cách này sẽ dùng hai bản weights trong memory, trừ khi bạn tách thời điểm chạy của các job. Dù chọn cách nào, các job vẫn dùng chung năng lực decode của một máy. Vì vậy, trên một VPS, phương án tiết kiệm hơn thường là dùng effort thấp cho các tác vụ có người chờ kết quả, kèm một lần chạy effort cao theo lịch cho các tác vụ không có người theo dõi. Điều gì xảy ra khi nhiều người dùng chung một model cục bộ cũng áp dụng ở đây: reasoning token là công việc decode. Do đó, tăng effort sẽ làm giảm concurrency hiệu dụng khoảng cùng một hệ số với mức tăng số token.

FAQ

Mặc định nên dùng mức độ suy luận nào?

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ụ bị lỗi ở mức đó. Nhiều model suy luận có mức mặc định cao. Tính đến October 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ả đẹp 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, mỗi giây đều có chi phí. Vì vậy, hãy coi mức cao hơn là tùy chọn cho từng tác vụ, thay vì mức mà mọi request đều kế thừa.

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. Chúng có được giữ lại ở turn tiếp theo hay không còn tùy vào runtime và model. Tài liệu của Qwen3.8 ghi rõ preserve_thinking được bật mặc định. Tùy chọn này giữ phần reasoning trước đó trong history, nên một cuộc hội thoại dài sẽ chứa toàn bộ scratchpad đã tạo. Đặt tùy chọn này thành false hoặc bỏ field thinking khỏi các message bạn replay thì quá trình xử lý prompt sẽ không tiếp tục tăng.

Vì sao thay đổi mức suy nghĩ không làm thay đổi số lượng token?

Thiết lập này không được truyền đến chat template. Mức độ là một biến của template, nên chỉ hoạt động khi runtime truyền biến đó và template đi kèm model đọc được nó. 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. Ollama cũng chạy bằng tên mức mà model không liệt kê ở mức mặc định của model, một lần nữa mà không báo lỗi. 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 thành 0, rồi so sánh eval_count. Nếu số lượng token chỉ chênh lệch trong phạm vi nhiễu, mức độ đang bị bỏ qua.

Mức độ suy luận thấp hơn có làm model kém chính xác không?

Điều này tùy vào tác vụ. Bạn nên đo lường thay vì tự giả định. Khi câu trả lời đã có sẵn trong input, chẳng hạn như tác vụ trích xuất hoặc viết lại, 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 trước khi có thể thực hiện bước cuối, chẳng hạn như phép tính nhiều bước hoặc code phải biên dịch được, độ chính xác sẽ giảm nếu scratchpad ngắn hơn. Hãy tạo một bộ gồm twenty prompt từ workload thực tế của bạn, chạy chúng ở hai mức với temperature đặt thành 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 cho bạn.