目标
让 MindIE 启动的 DeepSeek-V3.1 模型在 OpenAI 接口返回中实现思考分离,即返回类似下面的结构:
json
{
"message": {
"role": "assistant",
"reasoning_content": "思考内容...",
"content": "正式回答..."
}
}
而不是把思考和正式回答混在一个 content 里。
参考链接
- Modelers 文章:https://modelers.cn/models/Modelers_Park/DeepSeek-V3.1-w8a8
- ModelScope 模型页:https://www.modelscope.cn/models/BaojunZhou/DeepSeek-V3.1-Terminus-w8a8-QuaRot/summary
一、结论
MindIE 本身已经支持思考分离,不需要自己额外写后处理。要生效需要同时满足下面几个条件:
- 模型本身是 reasoning 模型
tokenizer_config.json的chat_template仍然保留<think>引导- MindIE 启动配置里显式开启
enable_reasoning - 请求里传入
enable_thinking=true
当前场景里,真正缺的是 MindIE 配置中的 enable_reasoning。
二、已经确认过的事实
1. 模型支持 reasoning
模型目录:
bash
/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot
模型 config.json 已确认:
json
"model_type": "deepseek_v3"
这会命中 config_deepseekv2.py 中的逻辑:
python
if self.model_type in ["deepseek_v3"]:
self.is_reasoning_model = True
self.reasoning_config.start_reasoning_token_id = self.start_reasoning_token_id
self.reasoning_config.end_reasoning_token_id = self.end_reasoning_token_id
说明 MindIE 会把这个模型当成 reasoning 模型处理。
2. <think> / </think> token 正确
已验证:
python
<think> -> 128798
</think> -> 128799
也就是:
bash
think token id: [128798]
end think token id: [128799]
token 128798: <think>
token 128799: </think>
说明 reasoning 分隔 token 没问题。
3. MindIE 内部已经有 reasoning parser
关键文件:
/usr/local/Ascend/atb-models/atb_llm/models/base/reasoning_parser.py/usr/local/Ascend/atb-models/atb_llm/runner/tokenizer_wrapper.py/usr/local/Ascend/atb-models/atb_llm/models/deepseekv2/router_deepseekv2.py
其中 TokenizerWrapper.decode() 里已经有字段分离逻辑:
python
return {
"reasoning_content": ...,
"content": ...
}
所以 MindIE 是支持原生思考分离的。
三、为什么之前没有分离成功
根因
TokenizerWrapper 中是否启用思考分离,取决于 _is_use_reasoning_parser():
python
def _is_use_reasoning_parser(self, metadata: Dict) -> bool:
if not self.llm_config.llm.enable_reasoning:
return False
if not self.config.is_reasoning_model:
return False
if metadata and "req_enable_thinking" in metadata:
return metadata.get("req_enable_thinking")
return self.enable_thinking
这里有三个条件:
self.llm_config.llm.enable_reasoning == Trueself.config.is_reasoning_model == True- 请求层开启 thinking
当前已确认第 2 条没问题,第 3 条也基本没问题。
问题就在第 1 条:MindIE 配置里没有开启 enable_reasoning。
四、正确配置方式
1. 确认 tokenizer_config.json 的 chat_template 已恢复
模型目录下文件:
bash
/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json
需要确保 chat_template 末尾仍然保留 <think> 引导逻辑。
正确版本末尾应包含
jinja
{%- if add_generation_prompt and ns.is_last_user and not ns.is_tool %}
{{'<|Assistant|>'}}
{%- if not thinking %}{{'</think>'}}{%- else %}{{'<think>'}}{%- endif %}
{% endif %}
核心是这一段必须存在:
jinja
{%- else %}{{'<think>'}}
检查命令
bash
python3 -c "
import json
with open('/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json') as f:
config = json.load(f)
template = config.get('chat_template', '')
print(template[-200:])
"
结果判断
如果输出末尾包含:
jinja
{%- if not thinking %}{{'</think>'}}{%- else %}{{'<think>'}}{%- endif %}
说明是正确的。
如果只有:
jinja
{%- if not thinking %}{{'</think>'}}{%- endif %}
说明之前删掉过 <think> 分支,需要还原。
如果有备份
bash
ls -la /home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json*
cp /home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json.bak /home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json
2. 在 MindIE 配置里开启 enable_reasoning
当前服务配置文件:
bash
/usr/local/Ascend/mindie/2.1.RC1/mindie-service/conf/config.json
当前模型配置片段大致是:
json
"ModelConfig" : [
{
"modelInstanceType" : "Standard",
"modelName" : "DSV3.1",
"modelWeightPath" : "/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot",
"worldSize" : 8,
"cpuMemSize" : 5,
"npuMemSize" : -1,
"backendType" : "atb",
"trustRemoteCode" : false,
"async_scheduler_wait_time": 120,
"kv_trans_timeout": 10,
"kv_link_timeout": 1080
}
]
需要改成:
json
"ModelConfig" : [
{
"modelInstanceType" : "Standard",
"modelName" : "DSV3.1",
"modelWeightPath" : "/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot",
"worldSize" : 8,
"cpuMemSize" : 5,
"npuMemSize" : -1,
"backendType" : "atb",
"trustRemoteCode" : false,
"async_scheduler_wait_time": 120,
"kv_trans_timeout": 10,
"kv_link_timeout": 1080,
"models": {
"deepseekv2": {
"enable_reasoning": true
}
}
}
]
为什么要放这里
从 MindIE 示例配置和 validator 可确认:
llm_config.llm.kv_cache_options.enable_nzllm_config.llm.enable_reasoning
都属于模型专属配置,应该放在:
json
ModelConfig[].models.deepseekv2
下。
enable_reasoning 与 kv_cache_options 是同级,不是放在顶层,不是放在 BackendConfig 顶层,也不是放在 ServerConfig。
3. 重启 MindIE 服务
改完配置后需要重启服务,否则不会生效。
你当前进程里可见:
mindieservice_daemonmindie_llm_backend_connector
重启方式按你当前环境已有方式处理。
如果是容器部署,通常是重启容器或重新拉起服务进程。
4. 请求里仍然要传 enable_thinking=true
调用示例:
bash
curl -H "Accept: application/json" \
-H "Content-type: application/json" \
-X POST -d '{
"model": "DSV3.1",
"messages": [
{
"role": "user",
"content": "What is DeepLearning"
}
],
"chat_template_kwargs": {"enable_thinking": true},
"stream": false
}' http://10.33.15.46:1025/v1/chat/completions
五、修改后预期效果
如果一切生效,返回应从之前这种:
json
{
"message": {
"role": "assistant",
"content": "Hmm ... </think>Of course ..."
}
}
变成这种分离后的结构:
json
{
"message": {
"role": "assistant",
"reasoning_content": "Hmm ...",
"content": "Of course ..."
}
}
六、排查顺序建议
按下面顺序排查最稳:
第一步:检查 tokenizer 是否还原
bash
python3 -c "
import json
with open('/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json') as f:
config = json.load(f)
print(config.get('chat_template', '')[-200:])
"
第二步:修改 config.json
在:
bash
/usr/local/Ascend/mindie/2.1.RC1/mindie-service/conf/config.json
加入:
json
"models": {
"deepseekv2": {
"enable_reasoning": true
}
}
第三步:重启 MindIE
第四步:重新发请求测试
bash
curl -H "Accept: application/json" \
-H "Content-type: application/json" \
-X POST -d '{
"model": "DSV3.1",
"messages": [
{"role": "user", "content": "What is DeepLearning"}
],
"chat_template_kwargs": {"enable_thinking": true},
"stream": false
}' http://10.33.15.46:1025/v1/chat/completions
七、关键信息汇总
模型侧关键文件
/usr/local/Ascend/atb-models/atb_llm/models/base/reasoning_parser.py/usr/local/Ascend/atb-models/atb_llm/runner/tokenizer_wrapper.py/usr/local/Ascend/atb-models/atb_llm/models/deepseekv2/router_deepseekv2.py/usr/local/Ascend/atb-models/atb_llm/models/deepseekv2/config_deepseekv2.py
服务侧关键配置
/usr/local/Ascend/mindie/2.1.RC1/mindie-service/conf/config.json
模型目录
/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot
tokenizer 文件
/home/DeepSeek-V3.1-Terminus-w8a8-QuaRot/tokenizer_config.json
八、最终结论
这次问题不是模型不支持,也不是 MindIE 不支持,而是:
- 模型和 parser 都已经支持思考分离
<think>/</think>token 也正确- chat_template 需要保留
<think>引导 - 但 MindIE 启动配置中缺少:
json
"models": {
"deepseekv2": {
"enable_reasoning": true
}
}
把这个补上并重启后,MindIE 才会真正走 reasoning_parser,返回 reasoning_content 和 content 分离结果。
补充一点:开启思考分离之后,之前看到的"返回里前面缺少 <think>"这个现象就不再需要单独处理了。原因不是模型补回了 <think>,而是返回结构已经变化了:思考内容会被直接拆到 reasoning_content 字段里,正式回答放在 content 字段里。也就是说,分离模式下不再依赖在 content 中看到完整的 <think>...</think> 包裹,因此这个问题等价于被正确规避并解决。
九、备用方案:如果原生思考分离暂时无法生效
如果当前环境下无法修改 MindIE 启动配置,或者 enable_reasoning 暂时无法生效,那么可以退回到一个备用方案:在 MindIE 前面加一层轻量 Python 代理,对返回结果做后处理。
这个方案不是首选,因为 MindIE 已经原生支持思考分离;但它可以作为临时兜底方案。
1. 这个方案解决什么问题
在未开启原生思考分离时,常见现象是:
- 返回的
content前面没有<think> - 但中间会出现
</think> - 思考内容和正式回答混在同一个
content字段里
例如:
json
{
"content": "Hmm, the user is asking...思考内容...</think>Of course! 正式回答..."
}
根因是:<think> 来自 prompt,不属于模型生成 token;</think> 是模型自己生成的 token,所以只看返回内容时就会看到"前面缺失 <think>"。
2. 代理方案的作用
代理可以在返回时把内容拆开:
reasoning_content:放思考内容content:放正式回答
或者补成:
json
"content": "<think>思考内容...</think>正式回答..."
3. 代理脚本示例
python
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.request
MINDIE_URL = "http://10.33.15.46:1025"
class ProxyHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
request_data = json.loads(body)
enable_thinking = False
chat_kwargs = request_data.get("chat_template_kwargs", {})
if chat_kwargs.get("enable_thinking"):
enable_thinking = True
req = urllib.request.Request(
MINDIE_URL + self.path,
data=body,
headers={"Content-Type": "application/json", "Accept": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as resp:
response_data = json.loads(resp.read())
if enable_thinking and "choices" in response_data:
for choice in response_data["choices"]:
msg = choice.get("message", {})
content = msg.get("content", "")
if "</think>" in content:
parts = content.split("</think>", 1)
reasoning = parts[0].strip()
actual = parts[1].strip() if len(parts) > 1 else ""
msg["reasoning_content"] = reasoning
msg["content"] = actual
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(response_data, ensure_ascii=False).encode())
def log_message(self, format, *args):
print(f"[Proxy] {args[0]}")
if __name__ == "__main__":
port = 1026
server = HTTPServer(("0.0.0.0", port), ProxyHandler)
print(f"Think proxy running on port {port}, forwarding to {MINDIE_URL}")
server.serve_forever()
4. 运行方式
bash
python3 /home/appadmin/think_proxy.py
或后台运行:
bash
nohup python3 /home/appadmin/think_proxy.py > /home/appadmin/think_proxy.log 2>&1 &
5. 调用方式
把请求从原始端口改到代理端口,例如从 1025 改成 1026:
bash
curl -H "Accept: application/json" \
-H "Content-type: application/json" \
-X POST -d '{
"model": "DSV3.1",
"messages": [
{"role": "user", "content": "What is DeepLearning"}
],
"chat_template_kwargs": {"enable_thinking": true},
"stream": false
}' http://10.33.15.46:1026/v1/chat/completions
6. 适用结论
- 能开原生思考分离时,优先开
models.deepseekv2.enable_reasoning = true - 代理方案只作为临时兜底
- 一旦原生思考分离生效,就不再需要处理"前面缺少
<think>"的问题