MindIE 启动模型开启思考分离说明

目标

让 MindIE 启动的 DeepSeek-V3.1 模型在 OpenAI 接口返回中实现思考分离,即返回类似下面的结构:

json 复制代码
{
  "message": {
    "role": "assistant",
    "reasoning_content": "思考内容...",
    "content": "正式回答..."
  }
}

而不是把思考和正式回答混在一个 content 里。

参考链接


一、结论

MindIE 本身已经支持思考分离,不需要自己额外写后处理。要生效需要同时满足下面几个条件:

  1. 模型本身是 reasoning 模型
  2. tokenizer_config.jsonchat_template 仍然保留 <think> 引导
  3. MindIE 启动配置里显式开启 enable_reasoning
  4. 请求里传入 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

这里有三个条件:

  1. self.llm_config.llm.enable_reasoning == True
  2. self.config.is_reasoning_model == True
  3. 请求层开启 thinking

当前已确认第 2 条没问题,第 3 条也基本没问题。

问题就在第 1 条:MindIE 配置里没有开启 enable_reasoning


四、正确配置方式

1. 确认 tokenizer_config.jsonchat_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_nz
  • llm_config.llm.enable_reasoning

都属于模型专属配置,应该放在:

json 复制代码
ModelConfig[].models.deepseekv2

下。

enable_reasoningkv_cache_options 是同级,不是放在顶层,不是放在 BackendConfig 顶层,也不是放在 ServerConfig


3. 重启 MindIE 服务

改完配置后需要重启服务,否则不会生效。

你当前进程里可见:

  • mindieservice_daemon
  • mindie_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 不支持,而是:

  1. 模型和 parser 都已经支持思考分离
  2. <think> / </think> token 也正确
  3. chat_template 需要保留 <think> 引导
  4. 但 MindIE 启动配置中缺少:
json 复制代码
"models": {
    "deepseekv2": {
        "enable_reasoning": true
    }
}

把这个补上并重启后,MindIE 才会真正走 reasoning_parser,返回 reasoning_contentcontent 分离结果。

补充一点:开启思考分离之后,之前看到的"返回里前面缺少 <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>"的问题
相关推荐
好记忆不如烂笔头abc1 年前
mindie运行Qwen2.5-7B-Instruct正常,量化版Qwen2.5-72B-Instruct-GPTQ-Int4报错
mindie