已合并
新增 Qwen3 医学场景全参微调与 NPU 推理接入实战教程 #440
新增 Qwen3 医学场景全参微调与 NPU 推理接入实战教程 #440
已合并
gcw_gpcLb7J8创建于 29 天前
gcw_gpcLb7J8
29 天前

描述

新增 contrib/tutorials/swanlab_example/ 教程:在昇腾 NPU 上参考 SwanLab 官方 Qwen3 医学模型微调教程,完成 Qwen3-1.7B 的 BF16 全参数 SFT 微调(医学问答场景),并将微调产出的权重接入昇腾官方推理框架 cann-recipes-infer 完成 NPU 推理验证。教程覆盖数据准备、模型加载与监督标签构建、全参微调、SwanLab 训练过程记录、Transformer 原生推理验证、以及 cann-recipes-infer 统一执行器部署与性能实测的完整流程。

关联的Issue

测试

  • 依据 qwen3_medical_sft.ipynb 在昇腾 Atlas A3环境完整跑通训练和推理全流程
  • 训练:约650 步,总耗时 569.8s,loss 从 1.93 收敛至 1.07,eval_loss 收敛至约1.16,全程未见过拟合或发散
  • Transformer 原生推理:验证模型正确输出思考过程及最终回答
  • 接入统一执行器完成部署,实测 Prefill 27.22ms、Decode 平均 5.29ms
  • 记录了实测过程中遇到的 6 个环境/依赖问题及对应修正方式

文档更新

类型标签

likedislike
Pull Request已成功合入, 合并人@CANN-robot
(感谢 gcw_gpcLb7J8 的贡献)
Ggcw_gpcLb7J8
29 天前 创建了 pull request,commit 355de36e
atomgit-bot
atomgit-bot
29 天前 评论:

变更摘要

本次 PR 在 contrib/tutorials/swan_lab_example/ 目录下新增一套面向昇腾 NPU 的 Qwen3 医学场景全参数微调与推理接入实战教程,覆盖从数据准备、BF16 全参数 SFT、SwanLab 训练过程记录,到 Transformer 原生推理验证,以及接入 cann-recipes-infer 统一执行器完成 NPU 推理部署的完整流程,并提供配置脚本和运行时 YAML 模板。

主要改动

  • 新增 qwen3_medical_sft.ipynb 交互式教程:以 Jupyter Notebook 形式完整演示 Qwen3-1.7B 在医学问答场景下的数据下载、监督标签构建(MedicalDataset)、BF16 全参数 SFT 微调、SwanLab 实验记录、Transformer 原生推理验证,以及 cann-recipes-infer 统一执行器部署与领域问题验证的全流程。
  • 新增 prepare_config.py 工具脚本:通过正则替换将训练产出的 checkpoint 绝对路径写入统一执行器运行时 YAML 文件的 model_path 字段,简化部署配置步骤。
  • 新增 qwen3_medical_sft.yaml 运行时配置文件:定义模型推理所需的模型配置(model_namemodel_pathexe_mode 等)、数据配置(datasetinput_truncated_len)、并行配置(world_size、各类 TP 大小)以及调度器配置(max_new_tokensbatch_size)。
  • 新增 run_cann_infer.sh 启动脚本:封装 cann-recipes-infer 仓库中统一执行器 executor/scripts/infer.sh 的调用逻辑,支持通过参数指定 YAML 文件名和仓库路径,规范部署执行入口。
likedislike
atomgit-bot
atomgit-bot
29 天前 评论:

代码审查

审查总结

本次审查覆盖了全部 6 个变更文件,共发现 5 个问题

优先级 数量 说明
P1 1 notebook 中 sed 转义错误导致关键环境修正静默失效
P2 2 prepare_config.py 正则静默无匹配 + notebook str.replace 静默无匹配
P3 2 README/validation.md 中 max_steps 文档不准确 + YAML key 疑似拼写错误

各文件审查结果

文件 结论
README.md 1 个 P3(max_steps 文档不准确)
prepare_config.py 1 个 P2(正则静默失败 + 路径含引号破坏 YAML)
qwen3_medical_sft.ipynb 2 个(P1 sed 转义错误 + P2 str.replace 静默失败)
qwen3_medical_sft.yaml 1 个 P3(moe_tp_size 疑似拼写错误)
run_cann_infer.sh 无问题,脚本编写规范(set -euo pipefail${1:?}、变量引号保护、cd 前目录检查)
validation.md 无独立问题(与 README 共享 max_steps 不准确问题,已在 README 处报告)

整体风险评估

中等风险。最值得关注的是 P1 问题:notebook 5.1 节中 sed 命令的 \$ 转义错误会导致 ASCEND_HOME_PATH 冲突修正静默失效,用户在按教程执行后可能遇到 torch_npu 导入失败的 RuntimeError。建议在合并前优先修复此项。两个 P2 问题(prepare_config.py 和 notebook 中的路径替换无匹配检测)在当前 cann-recipes-infer 版本下不会触发,但降低了教程对模板变更的鲁棒性。

类型 数量
🔴 阻塞 1
🟡 建议 4

⛔ 需要修改

likedislike
CANN-robotCANN-robot成员
29 天前 添加了label:stat/needs-squash
CANN-robotCANN-robot成员
29 天前 添加了label:cann-cla/yes
CANN-robot
CANN-robot成员
29 天前 评论:

Thanks for your pull-request.
The full list of commands accepted by me can be found at here
You can get sig-info at here


PR Approval Progress

Congratulations! All modules have met the lgtm and approve requirements.

Module Approval Details

module lgtm status approve status
repo-cann/cann-learning-hub mlewis, waimaidaole (2/2) mlewis (1/1)

💡 Tip:

  • Committer can comment /approve or /lgtm
  • Commenting /approve implies both code review (lgtm) and intent to merge (approve)

CLA Signature Pass

gcw_gpcLb7J8, thanks for your pull request. All authors of the commits have signed the CLA. 👍

likedislike
atomgit-bot
atomgit-bot29 天前进行代码检视2
contrib/tutorials/swan_lab_example/validation.md
已过期
@@ -0,0 +72,4 @@
72+然后,用户提到自己被诊断为糖尿病,所以需要具体的建议。可能需要区分不同类型的碳水化合物,比如简单碳水和复杂碳水。简单碳水比如精制糖、白面包、白米,这些容易被快速吸收,导致血糖迅速升高,所以应该限制。而复杂碳水比如燕麦、糙米、全麦面包、豆类、根茎类蔬菜等,因为它们释放糖更慢,升糖指数(GI)较低,所以更适合糖尿病患者。
73+ 
74+另外,可能需要提到控制摄入量,因为即使选择复杂碳水,也不能过量。
75+```
atomgit-bot
atomgit-bot29 天前评论:

🟡 Medium Priority

affected behavior/contract → 本教程的主题是"医学场景全参微调",读者阅读此验证文档后可能将 AI 生成的医学建议视为经过验证的医学指导。该输出示例中模型以"医生"角色自居,给出的饮食建议(如"简单碳水比如精制糖、白面包、白米应该限制""复杂碳水比如燕麦、糙米更适合糖尿病患者")虽属常识性内容,但在无任何免责声明的情况下呈现在教程文档中,存在被读者当作专业医疗建议的误导风险。

failure mode → 读者(尤其是非医学专业人士)阅读文档后,可能将模型输出的饮食建议当作可靠指导并应用于实际糖尿病管理中,而未咨询真正的医疗专业人员。这对于一个"医学场景"AI 教程而言是显著的文档安全风险。

建议:在输出示例上方添加醒目的免责声明,明确标注该内容为 AI 生成、不构成医疗建议。

likedislike
System
系统消息系统
29 天前 评论:

changed this line on 8a1fa9c0 in version 3 of the diff

atomgit-bot
atomgit-bot29 天前进行代码检视2
contrib/tutorials/swan_lab_example/qwen3_medical_sft.ipynb
已过期
@@ -0,0 +449,4 @@
449+ "with open(yaml_path, \"w\", encoding=\"utf-8\") as f:\n",
450+ " f.write(content)\n",
451+ "\n",
452+ "print(f\"已将 model_path 写入: {checkpoint_path}\")\n"
atomgit-bot
atomgit-bot29 天前评论:

🟡 Medium Priority

变更行: notebook 第 5.2 节(qwen3_medical_sft.ipynb 第 444–452 行):
content = content.replace(
'model_path: "/data/models/origin/Qwen3-8B"',
f'model_path: "{checkpoint_path}"'
)
...
print(f"已将 model_path 写入: {checkpoint_path}")

影响的行为/契约: 与 prepare_config.py 功能相同——将训练产物路径写入推理 YAML 的 model_path 字段。

失效模式: str.replace() 搜索一个硬编码的精确字符串 model_path: "/data/models/origin/Qwen3-8B"。如果 cann-recipes-infer 模板中的路径格式有任何差异(不同路径、不同空白、或模板之前已被修改过),replace 不会匹配,content 保持不变。但第 452 行的 print 无条件输出"已将 model_path 写入",给用户以操作成功的假象。这与 prepare_config.py 构成同类问题——两份代码(notebook cell 与独立脚本)都存在静默失败的风险,且使用的匹配方式不同(精确字符串 vs 正则),进一步增加了维护不一致的可能性。

建议:替换后检查原占位符路径是否仍存在于 content 中,若存在则报错提示用户模板可能已变更。

likedislike
System
系统消息系统
29 天前 评论:

changed this line on 8a1fa9c0 in version 3 of the diff

atomgit-bot
atomgit-bot29 天前进行代码检视2
contrib/tutorials/swan_lab_example/qwen3_medical_sft.ipynb
已过期
@@ -0,0 +400,4 @@
400+ "sed -i 's|cann_path=\"your_cann_pkgs_path\"|cann_path=\"/home/developer/Ascend/cann-9.0.0/aarch64-linux\"|' \\\n",
401+ " executor/scripts/set_env.sh\n",
402+ "\n",
403+ "sed -i '/export ASCEND_HOME_PATH=\\$cann_path/d' \\\n",
atomgit-bot
atomgit-bot29 天前评论:

🟠 High Priority

变更行: notebook 第 5.1 节 (qwen3_medical_sft.ipynb 第 403 行) 的 bash cell 中:

影响的行为/契约: 该 sed 命令的意图是删除 set_env.shexport ASCEND_HOME_PATH=$cann_path 这行(validation.md 第 88 行记录的修正方案),以解决 cann_path 变量身兼两职导致的 ASCEND_RUNTIME_PATH 目录不存在错误。

失效模式: 在 bash 单引号内,\$ 是两个字面字符:反斜杠 \ 后跟美元符 $。sed 接收到的正则模式是 export ASCEND_HOME_PATH=\$cann_path(匹配一个字面反斜杠)。但 set_env.sh 中的实际行是 export ASCEND_HOME_PATH=$cann_path(无反斜杠),因此 sed 永远不会匹配,删除操作静默失败。用户按 notebook 执行后,ASCEND_HOME_PATH 冲突问题未被修复,后续 import torch_npu 仍会触发 validation.md 第 88 行描述的 RuntimeError。

建议:在 JSON 中将 \\$ 改为 $(即 \$$),使 sed 模式匹配实际的无反斜杠行。

改动建议
403
- "sed -i '/export ASCEND_HOME_PATH=\\$cann_path/d' \\\n",
403
+ "sed -i '/export ASCEND_HOME_PATH=$cann_path/d' \\\n",
应用建议
likedislike
System
系统消息系统
29 天前 评论:

changed this line on 8a1fa9c0 in version 3 of the diff

atomgit-bot
atomgit-bot29 天前进行代码检视2
contrib/tutorials/swan_lab_example/prepare_config.py
已过期
@@ -0,0 +38,4 @@
38+ r'model_path:\s*".*"',
39+ f'model_path: "{checkpoint_path}"',
40+ content,
41+ )
atomgit-bot
atomgit-bot29 天前评论:

🟡 Medium Priority

变更行: prepare_config.py 第 37–41 行:
new_content = re.sub(
r'model_path:\s*".*"',
f'model_path: "{checkpoint_path}"',
content,
)

影响的行为/契约: 该函数的目标是将 YAML 中的 model_path 字段替换为训练产出的 checkpoint 路径。

失效模式 1 — 静默无匹配: 正则 model_path:\s*".*" 要求值必须用双引号包裹。如果 cann-recipes-infer 的模板 YAML 使用了单引号、无引号、或不同的空白格式,re.sub 不会匹配任何内容,new_content 等于 content。脚本随后在第 46 行打印"已将...model_path 写入",给用户以操作成功的假象,但实际 YAML 未被修改。

失效模式 2 — YAML 注入: checkpoint_path 来自 sys.argv[1],通过 f-string 直接拼入 f'model_path: "{checkpoint_path}"'。如果路径中包含双引号字符 ",生成的 YAML 行将语法错误,导致推理框架解析配置失败。虽然路径含双引号在实际中罕见,但作为命令行参数未做任何校验是隐患。

  1. checkpoint_path 做基本校验(如禁止包含双引号),或使用 YAML 库安全写入。

建议:增加替换结果校验:若 new_content == content 则警告用户 pattern 未匹配;对 checkpoint_path 过滤或转义双引号。

likedislike
System
系统消息系统
29 天前 评论:

changed this line on 8a1fa9c0 in version 3 of the diff

atomgit-bot
atomgit-bot29 天前进行代码检视2
contrib/tutorials/swan_lab_example/validation.md
已过期
@@ -0,0 +84,4 @@
84+| `model_name` 与实际模型规模不一致 | `qwen3_medical_sft.yaml``model_name``qwen3_8b`,实际权重为 1.7B | 保留原值,未修改:`cann-recipes-infer` 官方仅为 `qwen3_8b``qwen25_7b_instruct` 提供固定的 `model_name` 取值,未见支持自定义规模标签的依据;实测该字段未影响本次加载与推理结果 |
85+| `trust_remote_code=True` 触发隐藏联网校验,卡死不报错 | 加载 Qwen3-1.7B 模型时(notebook 第 2 部分)进程长时间无响应,`npu-smi info` 显示 HBM 占用长期不变,`top` 显示进程 CPU 占用接近 0%(sleeping 状态),怀疑是联网请求超时挂起,而非真实计算 | 在加载模型代码前加入环境变量强制离线模式,跳过网络校验:`os.environ["HF_HUB_OFFLINE"] = "1"``os.environ["TRANSFORMERS_OFFLINE"] = "1"`。该问题是否出现取决于运行环境能否访问 huggingface.co:网络受限(如仅放行 modelscope.cn 等国内域名)的环境下会必现,网络开放的环境下可能感知不到 |
86+| `cann-recipes-infer/models/qwen/requirements.txt` 锁定 `torch==2.8.0` | 执行 `pip install -r requirements.txt` 后覆盖了预装的 `torch 2.7.1+cpu`,导致 `import torch_npu``undefined symbol` ABI 不兼容错误 | 执行完依赖安装后需验证 `python3 -c "import torch, torch_npu"` 是否仍能正常导入且版本为 `2.7.1+cpu` / `2.7.1.post4`;若被覆盖,执行 `pip uninstall torch -y` 卸载多装的版本,回退到镜像预装的 `torch 2.7.1+cpu` |
87+| `torch_npu._C._get_cann_version` 内部读取到非 UTF-8 编码内容 | `source set_env.sh` 设置 `ASCEND_HOME_PATH` 后,`import torch_npu` 触发版本校验逻辑,报 `UnicodeDecodeError: 'utf-8' codec can't decode byte ...`,导致 `RuntimeError: Failed to load the backend extension: torch_npu` | 该函数仅用于版本兼容性提示,非核心推理逻辑必需。对 `torch_npu/npu/utils.py``get_cann_version` 函数打补丁,用 `try/except UnicodeDecodeError` 包裹调用并返回空字符串兜底(该文件路径通常需要 `sudo` 权限修改) |
atomgit-bot
atomgit-bot29 天前评论:

🟡 Medium Priority

changed line → line 87: 该行指导用户直接修改 torch_npu/npu/utils.py(系统安装的 Python 包文件),并注明"该文件路径通常需要 sudo 权限修改"。

affected behavior/contract → 用户按照此修复操作时:(1) 需要以 root 权限修改系统级 Python 包文件,存在误操作破坏系统环境的风险;(2) 该补丁在 pip install --upgrade torch_npu 后会被静默覆盖丢失,修复不具备持久性;(3) 若补丁代码有误,可能导致 torch_npu 模块彻底无法加载,排查困难。

failure mode → 用户以 sudo 修改系统文件时误操作(例如编辑错误、保存不完整),导致 torch_npu 不可用;或后续升级包后问题复现但用户未意识到补丁已丢失,再次陷入同样的 UnicodeDecodeError 故障。

suggested fix → 将修复方式改为运行时 monkey-patch(在 import torch_npu 之后动态替换 get_cann_version 函数),无需修改系统文件也无需 sudo;同时建议向 torch_npu 上游提交 issue/bug report 以从根本上解决该编码兼容性问题。

建议:将修复方式改为运行时 monkey-patch(无需 sudo),而非直接修改系统安装的 Python 包文件;同时建议向上游报告该问题。

likedislike
System
系统消息系统
29 天前 评论:

changed this line on 8a1fa9c0 in version 3 of the diff

Ggcw_gpcLb7J8
29 天前 update merge request[project id: 9260575, iid: 440, commit_id: d7efe4710b622157364e0ab368e10bc67e857216] virtual merging success
此处折叠了24条事件消息 查看更多
Ggcw_gpcLb7J8
29 天前 update merge request[project id: 9260575, iid: 440, commit_id: 3f5a9268bfbaad83e66d10a3ec911ffaf384c0db] virtual merging success
大毛
大毛成员
29 天前 评论:

/lgtm
/approve

likedislike
CANN-robotCANN-robot成员
29 天前 添加了label:approved
meme成员
29 天前 评论:

/lgtm

likedislike
CANN-robotCANN-robot成员
29 天前 添加了label:lgtm
CANN-robotCANN-robot成员
29 天前 解决了最后一个问题
CANN-robotCANN-robot成员
29 天前 合入了pull request