聊一聊Qwen3思考模式實(shí)現(xiàn)以及背后原理探討
硬開關(guān)
我們先通過官方的示例代碼來體驗(yàn)一下,如何實(shí)現(xiàn)在思考模式和非思考模式之間切換
通過tokenizer.apply_chat_template的enable_thinking參數(shù)來實(shí)現(xiàn)
默認(rèn)情況下,Qwen3 啟用了思考功能,類似于 QwQ-32B。這意味著模型將運(yùn)用其推理能力來提升生成響應(yīng)的質(zhì)量。例如,當(dāng)在tokenizer.apply_chat_template明確設(shè)置enable_thinking=True或保留其默認(rèn)值時,模型將啟動其思考模式。代碼如下:
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True # True is the default value for enable_thinking
)
在這種模式下,模型會生成包裹在...塊中的思考內(nèi)容,然后是最終的響應(yīng)。
對于思考模式,官方提示請使用Temperature=0.6、TopP=0.95、TopK=20和MinP=0( 中的默認(rèn)設(shè)置generation_config.json)。請勿使用貪婪解碼,因?yàn)樗鼤?dǎo)致性能下降和無休止的重復(fù)。https://huggingface.co/Qwen/Qwen3-32B
Qwen3提供了一個硬開關(guān),可以嚴(yán)格禁用模型的思考行為,使其功能與之前的 Qwen2.5-Instruct 模型保持一致。此模式在需要禁用思考以提高效率的場景中尤為有用。
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False # Setting enable_thinking=False disables thinking mode
)
在此模式下,模型不會生成任何思考內(nèi)容,也不會包含...塊。
對于非思考模式,我們建議使用Temperature=0.7、TopP=0.8、TopK=20和MinP=0。
軟開關(guān)
Qwen3示例給了一種高級用法: 通過用戶輸入在思考模式和非思考模式之間切換,具體來說就是通過用戶輸入層控制是否思考,提供了一種軟切換機(jī)制,允許用戶在enable_thinking=True時動態(tài)控制模型的行為。實(shí)現(xiàn)方式就是可以在用戶提示或系統(tǒng)消息中添加/think和/no_think,以便在不同回合之間切換模型的思維模式。
在多回合對話中,模型將遵循最后一條的指令。
以下是多輪對話的示例:
from transformers import AutoModelForCausalLM, AutoTokenizer
class QwenChatbot:
def __init__(self, model_name="Qwen/Qwen3-32B"):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(model_name)
self.history = []
def generate_response(self, user_input):
messages = self.history + [{"role": "user", "content": user_input}]
text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
inputs = self.tokenizer(text, return_tensors="pt")
response_ids = self.model.generate(**inputs, max_new_tokens=32768)[0][len(inputs.input_ids[0]):].tolist()
response = self.tokenizer.decode(response_ids, skip_special_tokens=True)
# Update history
self.history.append({"role": "user", "content": user_input})
self.history.append({"role": "assistant", "content": response})
return response
# Example Usage
if __name__ == "__main__":
chatbot = QwenChatbot()
# First input (without /think or /no_think tags, thinking mode is enabled by default)
user_input_1 = "How many r's in strawberries?"
print(f"User: {user_input_1}")
response_1 = chatbot.generate_response(user_input_1)
print(f"Bot: {response_1}")
print("----------------------")
# Second input with /no_think
user_input_2 = "Then, how many r's in blueberries? /no_think"
print(f"User: {user_input_2}")
response_2 = chatbot.generate_response(user_input_2)
print(f"Bot: {response_2}")
print("----------------------")
# Third input with /think
user_input_3 = "Really? /think"
print(f"User: {user_input_3}")
response_3 = chatbot.generate_response(user_input_3)
print(f"Bot: {response_3}")
思考模式軟硬開關(guān)如何兼容
我們會自然想到一種情況,就是用戶即在tokenizer.apply_chat_template中設(shè)置了enable_thinking參數(shù),又在用戶輸入的時候可能加入了/think和/no_think符號,這種情況軟硬開關(guān)如何兼容呢?
為了實(shí)現(xiàn) API 兼容性,當(dāng)enable_thinking=True時,無論用戶使用/think還是/no_think,模型都會始終輸出一個包裹在<think>...</think>中的塊。
但是,如果禁用思考功能,此塊內(nèi)的內(nèi)容可能為空。當(dāng)enable_thinking=False時,軟開關(guān)無效。無論用戶輸入任何/think或/no_think符號,模型都不會生成思考內(nèi)容,也不會包含...塊。
我們可以看到enable_thinking的優(yōu)先級是要大于/think或/no_think的優(yōu)先級。
接下來我們通過Qwen3模型中的tokenizer_config.json來分析如何通過對話模板來如何實(shí)現(xiàn)快慢思考的?下面截圖就是:
圖片
多說一句,Chat template 是大語言模型(LLM)中的一個關(guān)鍵組件,它定義了如何將對話轉(zhuǎn)換為模型可以理解的格式化輸入。我將詳細(xì)解釋其原理并提供具體示例。
Qwen3的 Chat Template 原理詳解
Chat template 是大語言模型(LLM)中的一個關(guān)鍵組件,它定義了如何將對話轉(zhuǎn)換為模型可以理解的格式化輸入。我將詳細(xì)解釋其原理并提供具體示例。
基本原理
Chat template 本質(zhì)上是一個文本模板,使用特定語法(通常是 Jinja2 模板語法)來處理和格式化對話數(shù)據(jù)。它的主要功能是:
- 角色區(qū)分:明確標(biāo)識不同參與者(系統(tǒng)、用戶、助手等)的消息
- 特殊標(biāo)記插入:添加模型訓(xùn)練時使用的特殊標(biāo)記
- 結(jié)構(gòu)化表示:將多輪對話組織成模型預(yù)期的格式
- 功能支持:處理工具調(diào)用、思考過持等高級功能
關(guān)鍵組件解析
在 Qwen3-8B 的 chat_template 中:
- <|im_start|> 和 <|im_end|>:標(biāo)記消息的開始和結(jié)束
- role:標(biāo)識消息發(fā)送者(system、user、assistant、tool)
- <think></think>:圍繞助手的思考過程
- <tool_call></tool_call>:包含工具調(diào)用信息
- <tool_response></tool_response>:包含工具響應(yīng)信息
實(shí)例說明
讓我通過幾個具體例子來說明 chat_template 如何工作:
例子1:簡單對話
假設(shè)有以下對話:
系統(tǒng):你是一個有用的助手
用戶:你好
助手:你好!有什么我可以幫助你的?
使用 Qwen3-8B 的 chat_template 處理后,會變成:
<|im_start|>system
你是一個有用的助手
<|im_end|>
<|im_start|>user
你好
<|im_end|>
<|im_start|>assistant
你好!有什么我可以幫助你的?
<|im_end|>
例子2:包含思考過程的對話
系統(tǒng):你是一個有用的助手
用戶:42+28等于多少?
助手:(思考:我需要計算42+28,42+28=70)
70
處理后:
<|im_start|>system
你是一個有用的助手
<|im_end|>
<|im_start|>user
42+28等于多少?
<|im_end|>
<|im_start|>assistant
<think>
我需要計算42+28,42+28=70
</think>
70
<|im_end|>
例子3:工具調(diào)用
系統(tǒng):你是一個有用的助手
用戶:查詢北京今天的天氣
助手:我將調(diào)用天氣API
(調(diào)用天氣工具)
工具響應(yīng):北京今天晴朗,溫度22-28度
助手:根據(jù)查詢,北京今天天氣晴朗,溫度在22-28度之間。
處理后:
<|im_start|>system
你是一個有用的助手
<|im_end|>
<|im_start|>user
查詢北京今天的天氣
<|im_end|>
<|im_start|>assistant
我將調(diào)用天氣API
<tool_call>
{"name": "weather_api", "arguments": {"city": "北京", "date": "today"}}
</tool_call>
<|im_end|>
<|im_start|>user
<tool_response>
北京今天晴朗,溫度22-28度
</tool_response>
<|im_end|>
<|im_start|>assistant
根據(jù)查詢,北京今天天氣晴朗,溫度在22-28度之間。
<|im_end|>
Chat template 在實(shí)際處理中利用了 Jinja2 模板引擎的多種功能:
- 條件處理:{%- if ... %} 和 {%- endif %} 用于根據(jù)消息類型選擇不同的格式化方式
- 循環(huán)遍歷:{%- for message in messages %} 用于處理多輪對話
- 變量替換:{{- message.content }} 插入實(shí)際消息內(nèi)容
- 命名空間:{%- set ns = namespace(...) %} 跟蹤狀態(tài)信息
Chat template 對模型使用至關(guān)重要:
- 訓(xùn)練一致性:確保推理時的格式與訓(xùn)練時一致
- 功能支持:使模型能識別并正確處理特殊功能(工具調(diào)用、思考過程)
- 性能優(yōu)化:正確的格式化可以顯著提高模型輸出質(zhì)量
下面一個代碼可以看到enable_thinking為False/True時不同的分詞結(jié)果
prompt = "42+28等于多少?"
messages = [
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True # Setting enable_thinking=False disables thinking mode
)
圖片
可以看到上面分詞之后的唯一差別的地方就是,enable_thinking=False時添加了一個<think>\n\n</think>\n,添加了一個空白思考,用來告訴模型已經(jīng)思考結(jié)束。
那么好奇問了,如果enable_thinking=True,然后我在對話的后面添加一個<think>\n\n</think>\n會怎么樣呢,就是下面:
prompt = "42+28等于多少?"+“<think>\n\n</think>\n”
messages = [
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True # Setting enable_thinking=False disables thinking mode
)
這個時候其實(shí)還是會輸出思考內(nèi)容的,大家可以試試看看
思考:Qwen3模型如何實(shí)現(xiàn)快慢思考
通過軟開關(guān),我們可以看到Qwen3是可以通過從輸入上來控制的快慢思考的,自然而然想到的是不是在訓(xùn)練的時候加了一些策略,直覺就是:
有一部分?jǐn)?shù)據(jù)帶有思維鏈一部分沒有帶有思維鏈
- no_think:問題+答案
- think:問題+【思考內(nèi)容+答案】
在第三階段,Qwen3在一份包括長思維鏈數(shù)據(jù)和常用的指令微調(diào)數(shù)據(jù)的組合數(shù)據(jù)上對模型進(jìn)行微調(diào),將非思考模式整合到思考模型中。確保了推理和快速響應(yīng)能力的無縫結(jié)合。
這個地方可以做到軟硬的實(shí)現(xiàn),一個是硬開關(guān)優(yōu)先級的控制,可以通過強(qiáng)化學(xué)習(xí)來實(shí)現(xiàn),比如模板部分是否存在<think>\n\n</think>\n,另一個就是軟開關(guān),可能就是不同訓(xùn)練數(shù)據(jù)混在一起訓(xùn)練,讓模型面臨簡單或者復(fù)雜問題自主決定思考的自由度。
有同學(xué)說,針對Qwen3有些模型師Moe,是不是可以:
把解碼頭改成多專家的形式,一個think一個不think,這樣可行嗎
這個是可以的,不過Qwen3 8B不是Moe
也有同學(xué)說deepseek之前文章也是這么搞的,r1通過注入思考空指令如何不進(jìn)行思考,
或者存在一種情況
類似于在R1應(yīng)用中sys_prompt寫成:
think>用戶說我是一個端水大師,要從aaa bbb cc方面考慮問題,現(xiàn)在我要先做…… </think>
以期實(shí)現(xiàn)減少、甚至截斷模型原本reasoning的部分
一些控制思考深度的研究方法
筆者收集了一些資料,在深度學(xué)習(xí)與自然語言處理公眾號看到一些關(guān)于大模型“過度思考”解讀的一些文章
讓模型學(xué)會「偷懶」
論文:Towards Thinking-Optimal Scaling of Test-Time Compute for LLM Reasoning 鏈接:https://arxiv.org/pdf/2502.18080
解決方案:讓模型自己決定「想多久」論文提出Thinking-Optimal Scaling(TOPS)策略,核心思想是:學(xué)會「偷懶」。
三步走實(shí)現(xiàn):
- 模仿學(xué)習(xí):用少量樣例教模型「不同難度用不同思考長度」;
- 動態(tài)生成:對同一問題生成短/中/長三種思考鏈;
- 自我改進(jìn):自動選擇最短的正確答案作為訓(xùn)練數(shù)據(jù)。
LightThinker的“思考壓縮”
論文:LightThinker: Thinking Step-by-Step Compression 鏈接:https://arxiv.org/pdf/2502.15589 項(xiàng)目:https://github.com/zjunlp/LightThinker
LightThinker的秘籍分為兩大招:何時壓縮和如何壓縮
何時壓縮?
LightThinker提供了兩種策略:
- Token級壓縮:每生成固定數(shù)量的token就壓縮一次,簡單粗暴但可能把一句話“腰斬”。
- 思考級壓縮:等模型寫完一個完整段落(比如一段分析或一次試錯)再壓縮,保留語義完整性,但需要模型學(xué)會“分段”。
如何壓縮?
這里有個關(guān)鍵操作:
- 把隱藏狀態(tài)壓縮成“要點(diǎn)token”!數(shù)據(jù)重構(gòu):訓(xùn)練時在數(shù)據(jù)中插入特殊標(biāo)記(比如觸發(fā)壓縮,[C]存儲壓縮內(nèi)容),教模型學(xué)會“記重點(diǎn)”。
- 注意力掩碼:設(shè)計專屬注意力規(guī)則,讓壓縮后的token只能關(guān)注問題描述和之前的壓縮內(nèi)容,避免“翻舊賬”。
在思考中提前預(yù)判答案
論文:Reasoning Models Know When They’re Right: Probing Hidden States for Self-Verification 鏈接:https://arxiv.org/pdf/2504.05419v1
通過訓(xùn)練一個簡單的“探針”(類似體檢儀器),研究者發(fā)現(xiàn):
- 高準(zhǔn)確率:探針預(yù)測中間答案正確性的準(zhǔn)確率超過80%(ROC-AUC >0.9)。
- 提前預(yù)判:模型甚至在答案完全生成前,隱藏狀態(tài)就已暴露正確性信號!
研究團(tuán)隊設(shè)計了一套流程:
- 切分推理鏈:將長推理過程按關(guān)鍵詞(如“再檢查”“另一種方法”)分割成多個片段。
- 標(biāo)注正確性:用另一個LLM(Gemini 2.0)自動判斷每個片段的答案是否正確。
- 訓(xùn)練探針:用兩層神經(jīng)網(wǎng)絡(luò)分析模型隱藏狀態(tài),預(yù)測答案正確性。
總結(jié)
圖片
混合推理模型已經(jīng)有不少了,例如 Claude 3.7 Sonnet 和 Gemini 2.5 Flash, Qwen3 應(yīng)該是開源且效果好的典例。未來這可能也是一個趨勢,不需要特意區(qū)分普通模型和思考模型,而是同一個模型按需使用。
Claude 3.7 sonnet 的混合推理模型
Claude 3.7 sonnet 的混合推理模型(Hybrid Reasoning Model)是 LLM 和 reasoning model 的結(jié)合的新范式,之后大概率所有 AI labs 的模型發(fā)布模型都會以類似形式,社區(qū)也不會再單獨(dú)比較 base model 和 reasoning model 的能力。
使用 Claude 3.7 Sonnet 時,用戶可以通過“extended thinking” 的設(shè)置選擇是否需要輸出長 CoT:
? 打開 extended thinking,則輸出 CoT step-by-step 思考,類似開啟了人類的 Slow thinking 并且其思考長度是可以選擇的,因此 extended thinking 并不是 0 或 1,而是一個可以拖動的光譜,
? 關(guān)閉 extended thinking,則和 LLM 一樣直接輸出。
圖片
這個設(shè)計其實(shí) Dario 很早就暗示過,在他看來:base model 與 reasoning model應(yīng)該是個連續(xù)光譜。Claude System Card 中提到,extended thinking 的開關(guān)與長短是通過定義 system prompt 來實(shí)現(xiàn)的。我們推測要實(shí)現(xiàn)這樣的融合模型,應(yīng)該需要在 RL 訓(xùn)練之后通過 post training 讓模型學(xué)會什么時候應(yīng)該 step by step thinking,如何控制推理長度。
對于這個新范式,我們的預(yù)測是:
- 之后的 hybrid reasoning model 需要在 fast thinking 和 slow thinking 的選擇上更加智能,模型自己具備 dynamic computing 能力,能規(guī)劃并分配解決一個問題的算力消耗和 token 思考量。Claude 3.7 Sonnet 目前還是將 inference time 的打開和長短交由用戶自己來決定,AI 還無法判斷 query 復(fù)雜度、無法根據(jù)用戶意圖自行選擇。
- 之后所有頭部 research lab 發(fā)布模型都會以類似形式,不再只是發(fā) base model。
其實(shí)現(xiàn)在打開 ChatGPT 上方的模型選擇,會彈出五六個模型,其中有 4o 也有 o3,用戶需要自行選擇是用 LLM 還是 reasoning model,使用體驗(yàn)非?;靵y。因此,hybrid reasoning model 從智能能力和用戶體驗(yàn)看都是下一步的必然選擇。
圖片
Gemini 2.5 Flash精細(xì)化的思維管理控制
我們知道,不同案例會以不同的方式權(quán)衡質(zhì)量、成本和延遲。為了讓開發(fā)者具備靈活性,Gemini 2.5 Flash研發(fā)出了可供設(shè)置的思考預(yù)算功能,開發(fā)者可借此在模型進(jìn)行思考的同時,對其可以生成的最大令牌數(shù)實(shí)現(xiàn)精細(xì)控制。預(yù)算更高意味著,模型可以更深入地進(jìn)行推理,進(jìn)而提高質(zhì)量。值得注意的是,雖然預(yù)算使 2.5 Flash 的思考深度存在上限,但若提示無需深度思考,模型便不會耗盡全部預(yù)算。
圖片
低推理需求提示:
示例 1:“謝謝”用西班牙語怎么說?
示例 2:加拿大有多少個省份?
中等推理需求提示:
示例 1:投擲兩個骰子,點(diǎn)數(shù)之和為 7 的概率是多少?
示例 2:我所在的健身房會于周一、周三和周五的上午 9 點(diǎn)至下午 3 點(diǎn),以及周二、周六的下午 2 點(diǎn)至晚上 8 點(diǎn)開放籃球活動。我每周工作 5 天,工作時間為上午 9 點(diǎn)至下午 6 點(diǎn),我想在平日打 5 個小時籃球,請為我制定可行的時間表。
高推理需求提示:
示例 1:長度 L=3m 的懸臂梁有一個矩形截面(寬度 b=0.1m,高度 h=0.2m),且由鋼 (E=200 GPa) 制成。其整個長度承受 w=5 kN/m 的均勻分布載荷,自由端則承受 P=10 kN 的集中載荷。計算最大彎曲應(yīng)力 (σ_max)。
示例 2:編寫函數(shù) evaluate_cells(cells: Dict[str, str]) -> Dict[str, float],用以計算電子表格單元格值。
每個單元格包含:
一個數(shù)字(例如“3”) 或一個使用 +、-、*、/ 和其他單元格的公式,如“=A1 + B1 * 2”。 要求:
解析單元格之間的依賴關(guān)系。 遵守運(yùn)算符優(yōu)先級(*/ 優(yōu)于 +-)。 檢測循環(huán)并指出 ValueError(“在<單元格>檢測到循環(huán)”)。 無 eval()。僅使用內(nèi)置庫。