已合并
feat: Support GLM-4.5V on the pure-FSDP2 backend (issue #370) #2875
feat: Support GLM-4.5V on the pure-FSDP2 backend (issue #370) #2875
已合并
SuperKoalaKing创建于 7月23日
共 9 个文件变更+1786-0
@@ -13,6 +13,7 @@ mm-convert moe_expert --style split --hf_dir "merged_weight" --save_dir "splited
13"""13"""
14 14 
15import json15import json
16+import os
且奏长歌
且奏长歌且奏长歌8月4日

根据任务要求,还需要提供实践文档,希望包含训练正常拉起和运行的训练截图,以及精度对齐结果可以使用https://curryrice233.github.io/TrainingLogParser/ 工具生成图表,更加直观

likedislike
SuperKoalaKing
8月4日 评论:
16from pathlib import Path17from pathlib import Path
17from typing import Dict, Literal18from typing import Dict, Literal
18from enum import Enum19from enum import Enum
@@ -37,6 +38,7 @@ class ConfigType(Enum):
37 DEFAULT = 038 DEFAULT = 0
38 QWEN3_OMNI = 139 QWEN3_OMNI = 1
39 QWEN3_5 = 240 QWEN3_5 = 2
41+ GLM4V = 3
40 42 
41 43 
42def get_config_type_from_class_name(save_dir: Path) -> ConfigType:44def get_config_type_from_class_name(save_dir: Path) -> ConfigType:
@@ -48,6 +50,8 @@ def get_config_type_from_class_name(save_dir: Path) -> ConfigType:
48 class_name = config.__class__.__name__50 class_name = config.__class__.__name__
49 if class_name == "Qwen3OmniMoeConfig":51 if class_name == "Qwen3OmniMoeConfig":
50 return ConfigType.QWEN3_OMNI52 return ConfigType.QWEN3_OMNI
53+ elif class_name == "Glm4vMoeConfig" and os.getenv("GLM4V_DCP_3D") == "1":
54+ return ConfigType.GLM4V
51 else:55 else:
52 return ConfigType.DEFAULT56 return ConfigType.DEFAULT
53 57 
@@ -0,0 +1,300 @@
1+# Glm4.5v 使用指南(FSDP2 后端)
2+ 
3+## 目录
4+ 
5+- [版本说明](#版本说明)
6+ - [参考实现](#参考实现)
7+ - [变更记录](#变更记录)
8+- [环境安装](#环境安装)
9+ - [环境准备](#1-环境准备)
10+ - [环境搭建](#2-环境搭建)
11+- [权重下载及转换](#权重下载及转换)
12+ - [权重下载](#1-权重下载)
13+ - [权重转换](#2-权重转换)
14+- [数据集准备及处理](#数据集准备及处理)
15+- [微调](#微调)
16+ - [准备工作](#1-准备工作)
17+ - [配置参数](#2-配置参数)
18+ - [启动微调](#3-启动微调)
19+ - [HF权重转换](#4-hf权重转换)
20+- [环境变量声明](#环境变量声明)
21+- [跨后端对拍](#跨后端对拍)
22+- [注意事项](#注意事项)
23+ 
24+## 版本说明
25+ 
26+本指南使用纯 FSDP2 后端,训练入口为 `mindspeed_mm/fsdp/train/trainer.py`,配置文件为 `examples/glm4.5v/glm4_5v_106B_config_v1.yaml`,启动脚本为 `examples/glm4.5v/finetune_glm4_5v_106B_v1.sh`。Megatron 后端请参考 [README.md](README.md)。
27+ 
28+### 参考实现
29+ 
30+```shell
31+url=https://github.com/huggingface/transformers.git
32+commit_id=8cb5963
33+```
34+ 
35+### 变更记录
36+ 
37+2026.08.29:新增纯 FSDP2 后端。
38+ 
39+2026.10.11:增加 NPU EP=1 grouped-MoE 路径及前向/梯度测试,补充 128 专家对拍结果。
40+ 
41+---
42+ 
43+## 环境安装
44+ 
45+<a id="jump1.1"></a>
46+ 
47+### 1. 环境准备
48+ 
49+【模型开发时推荐使用配套的环境版本】
50+ 
51+请参考[安装指南](https://gitcode.com/Ascend/MindSpeed-MM/blob/master/docs/zh/guides/installation/install_guide.md),完成昇腾软件安装。
52+ 
53+<a id="jump1.2"></a>
54+ 
55+### 2. 环境搭建
56+ 
57+```bash
58+git clone https://gitcode.com/Ascend/MindSpeed-MM.git
59+cd MindSpeed-MM
60+mkdir -p logs data ckpt
61+ 
62+# 安装加速库
63+git clone https://gitcode.com/Ascend/MindSpeed.git
64+cd MindSpeed
65+git checkout d76dbddd4517d48a2fc1cd494de8b9a6cfdbfbab
66+ 
67+# 安装mindspeed及依赖
68+pip install -e .
69+cd ..
70+# 安装mindspeed mm及依赖
71+pip install -e .
72+ 
73+# 安装新版transformers(支持glm4.5v模型)
74+git clone https://github.com/huggingface/transformers.git
75+cd transformers
76+git checkout 8cb5963
77+pip install -e .
78+cd ..
79+```
80+ 
81+后续命令均在 `MindSpeed-MM` 仓库根目录执行。纯 FSDP2 启动脚本会设置 `NON_MEGATRON=true`;直接调用训练入口或权重转换命令时也需要设置该变量。
82+ 
83+---
84+ 
85+## 权重下载及转换
86+ 
87+### 1. 权重下载
88+ 
89+从 Hugging Face 下载 [GLM-4.5V](https://huggingface.co/zai-org/GLM-4.5V),将模型权重保存到 `ckpt/hf_path/GLM-4.5V`。
90+ 
91+> [!NOTE]
92+>
93+> 如无法访问 Hugging Face,可从 ModelScope 下载,并核对模型及文件版本。
94+ 
95+### 2. 权重转换
96+ 
97+本后端使用三维堆叠专家权重,转换结果应与 Megatron 后端分目录保存。
98+ 
99+设置 `GLM4V_DCP_3D=1` 保留三维专家权重,使用空 `dcp_prefix` 保持与模型一致的权重键名:
100+ 
101+```bash
102+HF_DIR=./ckpt/hf_path/GLM-4.5V
103+MM_DIR=./ckpt/mm_path/GLM-4.5V-fsdp2
104+ 
105+NON_MEGATRON=true GLM4V_DCP_3D=1 mm-convert ExpertMergeDcpConverter hf_to_dcp \
106+ --hf_dir "${HF_DIR}" \
107+ --save_dir "${MM_DIR}" \
108+ --dcp_prefix "" &&
109+ 
110+# 转换成功后复制模型配置、tokenizer 和 processor 文件
111+for f in config.json generation_config.json chat_template.jinja tokenizer.json \
112+ tokenizer_config.json preprocessor_config.json video_preprocessor_config.json; do
113+ if [ -f "${HF_DIR}/${f}" ]; then
114+ cp "${HF_DIR}/${f}" "${MM_DIR}/"
115+ fi
116+done
117+```
118+ 
119+转换完成后,将 `glm4_5v_106B_config_v1.yaml` 中的 `model.model_name_or_path` 和 `training.load` 均指向 `MM_DIR`。保留默认的 `training.init_model_with_meta_device: true` 和 `training.load_strict: true`。
120+ 
121+---
122+ 
123+## 数据集准备及处理
124+ 
125+- 使用**真实数据集**训练:参考[针对VL模型的数据构造 · 使用真实数据集](../../docs/zh/features/data/building_data_for_VLModel.md#real-data)(下载COCO2017 → 下载LLaVA-Instruct-150K标注 → 运行转换脚本生成`mllm_format_llava_instruct_data.json`)。
126+- 使用**虚构数据**做功能/性能测试:参考[针对VL模型的数据构造 · 使用虚构数据](../../docs/zh/features/data/building_data_for_VLModel.md#mock-data)。
127+ 
128+## 微调
129+ 
130+### 1. 准备工作
131+ 
132+启动前完成环境安装、权重转换和数据准备,并根据实际路径修改配置文件。
133+ 
134+### 2. 配置参数
135+ 
136+配置文件为 `examples/glm4.5v/glm4_5v_106B_config_v1.yaml`。
137+ 
138+【数据目录配置】
139+ 
140+`data.dataset_param.preprocess_parameters.model_name_or_path` 指向原始 Hugging Face 目录,用于加载 processor 和 tokenizer。`dataset` 指向标注 JSON,标注中的媒体相对路径以 `dataset_dir` 为基准;多机不要共用同一个缓存写入目录。
141+ 
142+```yaml
143+data:
144+ dataset_param:
145+ preprocess_parameters:
146+ model_name_or_path: ./ckpt/hf_path/GLM-4.5V
147+ basic_parameters:
148+ dataset_dir: ./data
149+ dataset: ./data/mllm_format_llava_instruct_data.json
150+ cache_dir: ./data/cache_dir
151+```
152+ 
153+【模型保存加载配置】
154+ 
155+将模型配置目录和权重加载目录同时设为本后端转换后的目录:
156+ 
157+```yaml
158+model:
159+ model_name_or_path: ./ckpt/mm_path/GLM-4.5V-fsdp2
160+training:
161+ init_model_with_meta_device: true
162+ load: ./ckpt/mm_path/GLM-4.5V-fsdp2
163+ load_format: dcp
164+ load_strict: true
165+ no_load_optim: true
166+ no_load_rng: true
167+ save: ./save_dir
168+ save_interval: 10000
169+```
170+ 
171+`training.save` 为空时不会保存检查点。初次加载转换权重时不加载优化器和随机数状态;从完整训练检查点续训时,应将 `training.load` 改为训练检查点目录,并按检查点内容调整 `training.no_load_optim` 和 `training.no_load_rng`。
172+ 
173+【并行与优化特性配置】
174+ 
175+| 功能 | 配置项 | 默认值 |
176+| --- | --- | --- |
177+| FSDP2 | `parallel.fully_shard_parallel_size` | `auto` |
178+| EP=1 NPU grouped MoE | `model.use_grouped_expert_matmul` | `true` |
179+| 专家并行 | `parallel.expert_parallel_size` | `16` |
180+| NPU MoE 融合 | `parallel.ep_plan.use_npu_fused_ops` | 框架默认开启 |
181+| EP 动态负载均衡 | `features.enable_ep_balance` | `false` |
182+| Ring CP | `parallel.ring_attention_size` | `1` |
183+| Ulysses CP | `parallel.ulysses_parallel_size` | `1` |
184+| 静态分块 loss | `features.enable_chunk_loss` | `true` |
185+| 动态分块 loss | `features.enable_dynamic_chunk_loss` | `false` |
186+| 重计算 | `features.recompute` | `true` |
187+| 异步激活卸载 | `features.enable_activation_offload` | `false` |
188+| 确定性计算 | `training.use_deter_comp` | `false` |
189+ 
190+上下文并行默认关闭。启用时,将 `parallel.ring_attention_size` 或 `parallel.ulysses_parallel_size` 设为所需规模,并保证它们的乘积与总进程数及其他并行维度兼容;`model.attn_implementation` 保持为 `flash_attention_2`。Ring CP 依赖 MindSpeed 提供的 Ring Attention 算子。
191+ 
192+基础 EP 默认使用 `alltoall` dispatcher。GLM-4.5V 使用带 `e_score_correction_bias` 的 aux-free 路由,`features.loss_cfg.router_aux_loss_coef` 保持为 0。开启 `features.enable_ep_balance` 时仅支持 `alltoall`,并会复制热点专家,需预留额外显存。
193+ 
194+静态和动态分块 loss 不能同时启用。改用动态分块时配置:
195+ 
196+```yaml
197+features:
198+ enable_chunk_loss: false
199+ enable_dynamic_chunk_loss: true
200+ chunkloss_plan:
201+ apply_module: lm_head
202+ total_chunk_size: 4096
203+```
204+ 
205+重计算与异步激活卸载的 `apply_modules` 均覆盖 `model.visual.blocks.{*}` 和 `model.language_model.layers.{*}`。重计算默认开启;异步激活卸载默认关闭,启用时将 `features.enable_activation_offload` 设为 `true`,并确认主机内存和 PCIe 带宽满足要求。各特性的显存与性能收益取决于模型规模、序列长度和并行配置。
206+ 
207+### 3. 启动微调
208+ 
209+根据实际安装位置修改 `finetune_glm4_5v_106B_v1.sh` 中的 CANN 环境脚本路径。完整 106B 模型采用多机配置;所有节点使用同一个主节点 IP、端口和节点数,`NODE_RANK` 各不相同:
210+ 
211+```bash
212+export NPUS_PER_NODE=16
213+export MASTER_ADDR="<rank-0-node-ip>"
214+export MASTER_PORT=6000
215+export NNODES=8
216+export NODE_RANK=0 # 各节点分别设置为 0 至 7
217+ 
218+bash examples/glm4.5v/finetune_glm4_5v_106B_v1.sh
219+```
220+ 
221+启动脚本固定读取 `examples/glm4.5v/glm4_5v_106B_config_v1.yaml`。单卡功能调试时,先准备减层模型对应的 HF config 和权重,按“权重转换”中的纯 FSDP2 命令转换到单独目录,再复制配置文件:
222+ 
223+```bash
224+cp examples/glm4.5v/glm4_5v_106B_config_v1.yaml examples/glm4.5v/glm4_5v_debug.yaml
225+```
226+ 
227+修改 `glm4_5v_debug.yaml`:数据预处理路径指向减层 HF 目录,`model.model_name_or_path` 和 `training.load` 指向对应的转换目录;将 `parallel.expert_parallel_size`、`parallel.fully_shard_parallel_size`、`parallel.ring_attention_size` 和 `parallel.ulysses_parallel_size` 均设为 `1`,并按需缩短 `training.train_iters`。完成修改后,直接指定该 YAML 启动:
228+ 
229+```bash
230+# 根据实际情况修改 CANN 路径
231+source /usr/local/Ascend/ascend-toolkit/set_env.sh
232+NON_MEGATRON=true torchrun --standalone --nproc_per_node=1 \
233+ mindspeed_mm/fsdp/train/trainer.py \
234+ examples/glm4.5v/glm4_5v_debug.yaml
235+```
236+ 
237+### 4. HF权重转换
238+ 
239+训练完成后,将 `save_dir` 中的检查点转换成 Hugging Face 格式:
240+ 
241+```bash
242+NON_MEGATRON=true GLM4V_DCP_3D=1 mm-convert ExpertMergeDcpConverter dcp_to_hf \
243+ --hf_dir ./ckpt/hf_path/GLM-4.5V \
244+ --dcp_dir ./save_dir/iter_000xxxx \
245+ --save_dir ./ckpt/hf_export/GLM-4.5V \
246+ --dcp_prefix "" \
247+ --hf_prefix ""
248+```
249+ 
250+`--hf_dir` 指向原始 Hugging Face 权重,`--dcp_dir` 指向要导出的训练检查点,`--save_dir` 指向导出目录。将 `iter_000xxxx` 替换为实际保存步数对应的目录名,转换完成后可使用 Hugging Face 模型进行推理。
251+ 
252+## 环境变量声明
253+ 
254+| 环境变量 | 描述 | 取值说明 |
255+|-------------------------------|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
256+| `ASCEND_SLOG_PRINT_TO_STDOUT` | 是否开启日志打印 | `0`: 关闭日志打屏<br>`1`: 开启日志打屏 |
257+| `ASCEND_GLOBAL_LOG_LEVEL` | 设置应用类日志的日志级别及各模块日志级别,仅支持调试日志 | `0`: 对应DEBUG级别<br>`1`: 对应INFO级别<br>`2`: 对应WARNING级别<br>`3`: 对应ERROR级别<br>`4`: 对应NULL级别,不输出日志 |
258+| `TASK_QUEUE_ENABLE` | 用于控制开启task_queue算子下发队列优化的等级 | `0`: 关闭<br>`1`: 开启Level 1优化<br>`2`: 开启Level 2优化 |
259+| `COMBINED_ENABLE` | 设置combined标志。设置为0表示关闭此功能;设置为1表示开启,用于优化非连续两个算子组合类场景 | `0`: 关闭<br>`1`: 开启 |
260+| `CPU_AFFINITY_CONF` | 控制CPU端算子任务的处理器亲和性,即设定任务绑核 | 设置`0`或未设置: 表示不启用绑核功能<br>`1`: 表示开启粗粒度绑核<br>`2`: 表示开启细粒度绑核 |
261+| `HCCL_CONNECT_TIMEOUT` | 用于限制不同设备之间socket建链过程的超时等待时间 | 需要配置为整数,取值范围`[120,7200]`,默认值为`120`,单位`s` |
262+| `PYTORCH_NPU_ALLOC_CONF` | 控制缓存分配器行为 | `expandable_segments:<value>`: 使能内存池扩展段功能,即虚拟内存特征 |
263+| `HCCL_EXEC_TIMEOUT` | 控制设备间执行时同步等待的时间,在该配置时间内各设备进程等待其他设备执行通信同步 | 需要配置为整数,取值范围`[68,17340]`,默认值为`1800`,单位`s` |
264+| `ACLNN_CACHE_LIMIT` | 配置单算子执行API在Host侧缓存的算子信息条目个数 | 需要配置为整数,取值范围`[1, 10,000,000]`,默认值为`10000` |
265+| `TOKENIZERS_PARALLELISM` | 用于控制Hugging Face的transformers库中的分词器(tokenizer)在多线程环境下的行为 | `False`: 禁用并行分词<br>`True`: 开启并行分词 |
266+| `MULTI_STREAM_MEMORY_REUSE` | 配置多流内存复用是否开启 | `0`: 关闭多流内存复用<br>`1`: 开启多流内存复用 |
267+| `NPU_ASD_ENABLE` | 控制是否开启TorchNPU的特征值检测功能 | 设置`0`或未设置: 关闭特征值检测<br>`1`: 表示开启特征值检测,只打印异常日志,不告警<br>`2`:开启特征值检测,并告警<br>`3`:开启特征值检测,并告警,同时会在device侧info级别日志中记录过程数据 |
268+| `ASCEND_LAUNCH_BLOCKING` | 控制算子执行时是否启动同步模式 | `0`: 采用异步方式执行<br>`1`: 强制算子采用同步模式运行 |
269+| `NPUS_PER_NODE` | 配置一个计算节点上使用的NPU数量 | 整数值(如 `1`, `8` 等) |
270+| `NON_MEGATRON` | 选择纯 FSDP2 路径,跳过 Megatron 补丁导入 | 纯 FSDP2 设为 `true` |
271+| `GLM4V_DCP_3D` | 控制 GLM-4.5V 专家权重转换布局 | 纯 FSDP2 转换设为 `1`;Megatron 转换不设置 |
272+ 
273+---
274+ 
275+## 跨后端对拍
276+ 
277+2026-10-11 在 8 张 Ascend 910B3 上完成 2000 步验证:文本和视觉各保留 4 层,保留 128 个 routed experts 和 top-k 8,EP=1、CP=1、FSDP=8,使用固定的 2048 条真实 LLaVA/COCO 图文样本。该配置不是完整 106B 模型,也不替代 EP>1 的验证。
278+ 
279+| 指标 | 结果 |
280+| --- | --- |
281+| 2000 步 loss 平均相对误差 | 1.617965%,低于 2% |
282+| 最大单步相对误差 | 14.324236%,不代表每步都小于 2% |
283+| Megatron / pure FSDP2 平均耗时 | 922.725 / 903.014 ms/step(排除前 200 步) |
284+ 
285+平均相对误差以 Megatron loss 为分母,对全部 2000 个相同步号计算,不丢弃尖峰或后段。本轮只验证一个 seed;更换模型规模、并行配置或数据后需重新对拍。
286+ 
287+该结果使用专门对齐的配置,**不是直接运行上面的默认训练 YAML 所得**:
288+ 
289+- seed=1234、micro batch=1、global batch=8、shuffle=false;两端从对应的初始 DCP 加载,不恢复 optimizer/RNG,保持同一 2000 步 cosine 学习率计划、warmup ratio=0.1、lr=1e-5、weight decay=0。
290+- loss 使用 default,关闭 ChunkLoss;attention 按 Megatron 的实际运行路径设为 SDPA。
291+- 只冻结 `model.visual.patch_embed`、`model.visual.blocks`、`model.visual.merger`;视觉与文本层均启用 reentrant recompute。
292+- Megatron 的 BF16 包装会同时转换浮点 buffer,而纯 FSDP2 的参数混合精度不会转换 buffer。本轮在独立验证入口中、模型准备后且检查点加载前,将浮点 buffer(含 RoPE `inv_freq`)对齐为 BF16,保留 FP32 参数,并同步 RoPE 的 `original_inv_freq` 引用。
293+ 
294+buffer 对齐用于复现既有 Megatron 数值语义,不表示 BF16 RoPE 比 FP32 更准确;本 PR 未把该策略默认应用到通用 Trainer。对齐后首步捕获的 42 个张量字段与基线逐元素一致,但反向和后续训练仍存在数值差异。完整配置、验证入口和曲线见 [PR !2875](https://gitcode.com/Ascend/MindSpeed-MM/merge_requests/2875);临时诊断脚本和训练日志不纳入模型代码目录。
295+ 
296+## 注意事项
297+ 
298+- 完整 106B 模型需要多机运行;单卡示例用于已经准备好减层 config 和匹配权重的功能调试。
299+- 两种后端的专家权重布局不同,转换结果和训练检查点不能混用。纯 FSDP2 导入与导出均需保留 `GLM4V_DCP_3D=1` 和空前缀参数。
300+- CP、EP 需要相应的多卡环境。单卡调试通过不能代替多卡通信与并行配置验证。
@@ -0,0 +1,39 @@
1+#!/usr/bin/env bash
2+# 根据实际情况修改 ascend-toolkit 路径
3+source /usr/local/Ascend/ascend-toolkit/set_env.sh || exit $?
4+# CANN 环境脚本加载后再开启严格模式。
5+set -euo pipefail
6+ 
atomgit-bot
atomgit-botatomgit-bot7月23日

🟡 Medium Priority

变更行: finetune_glm4_5v_106B_fsdp.sh 新增脚本,全文缺少 set -euo pipefail。

证据链:

  1. 脚本 L1 source /usr/local/Ascend/ascend-toolkit/set_env.sh — 若该文件不存在或 source 失败,无 set -e 时脚本继续执行。
  2. L40-42 torchrun ... — 训练失败(如 OOM、配置错误)返回非零退出码,但脚本继续执行 L44-49 的性能指标提取。
  3. L45-47 的 awk 管道 — 无 pipefail,若 grep 无匹配,管道仍 "成功" 退出,STEP_TIME/GBS 为空,导致 L47 awk 语法错误或输出垃圾值。

失败模式:训练失败后脚本继续运行并尝试解析不存在的日志 → 输出误导性性能数字或 awk 报错,给用户错误的成功/失败信号。

建议:在脚本开头(shebang 之后)添加 set -euo pipefail,确保任何命令失败立即退出,管道中任一命令失败也视为管道失败。

likedislike
不准确?
7+export NON_MEGATRON=true
8+export HCCL_CONNECT_TIMEOUT=1200
9+export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
10+export MULTI_STREAM_MEMORY_REUSE=2
11+export TASK_QUEUE_ENABLE=1
12+export CPU_AFFINITY_CONF=1
13+ 
14+NPUS_PER_NODE=${NPUS_PER_NODE:-16}
15+MASTER_ADDR=${MASTER_ADDR:?Set MASTER_ADDR to the rank-0 node IP}
16+MASTER_PORT=${MASTER_PORT:-6000}
17+NNODES=${NNODES:-8}
18+NODE_RANK=${NODE_RANK:?Set NODE_RANK for this node}
19+WORLD_SIZE=$(($NPUS_PER_NODE*$NNODES))
20+ 
21+DISTRIBUTED_ARGS="
22+ --nproc_per_node $NPUS_PER_NODE \
23+ --nnodes $NNODES \
24+ --node_rank $NODE_RANK \
25+ --master_addr $MASTER_ADDR \
26+ --master_port $MASTER_PORT
27+"
28+ 
29+logfile=$(date +%Y%m%d)_$(date +%H%M%S)
30+mkdir -p logs
31+torchrun $DISTRIBUTED_ARGS mindspeed_mm/fsdp/train/trainer.py \
32+ examples/glm4.5v/glm4_5v_106B_config_v1.yaml \
33+ 2>&1 | tee logs/train_${logfile}.log
atomgit-bot
atomgit-botatomgit-bot7月23日

🟡 Medium Priority

变更行: examples/glm4.5v/finetune_glm4_5v_106B_fsdp.sh 第 40-42 行

问题: 脚本使用 torchrun ... 2>&1 | tee logs/train_${logfile}.log 管道启动训练,但未设置 set -o pipefail。Bash 默认行为是管道整体退出码取最后一个命令(tee)的退出码,即始终为 0。当 torchrun 因训练失败而非零退出时,脚本仍会继续执行后续的性能指标提取(第 44-49 行),并将整个脚本以退出码 0 结束。

后果:

  • 训练失败被静默掩盖,CI/调度系统无法感知失败
  • 后续 awk 命令在空日志上产生垃圾输出或语法错误
  • 与同仓库其他训练脚本(如 finetune_glm4_5v_106B.sh 等 megatron 路径脚本)的行为不一致

建议:在脚本的第 2 行(source 之后、业务逻辑之前)添加 set -eo pipefail,确保训练失败时脚本正确返回非零退出码。注意不能放在 source 之前,因为 ascend-toolkit 的 set_env.sh 可能依赖当前 shell 设置。

likedislike
不准确?
34+ 
35+STEP_TIME=`grep "elapsed time per iteration" logs/train_${logfile}.log | awk -F 'elapsed time per iteration [(]ms[)]:' '{print$2}' | awk -F '|' '{print$1}' | head -n 200 | tail -n 100 | awk '{sum+=$1} END {if (NR != 0) printf("%.1f",sum/NR)}'`
36+GBS=`grep "global batch size" logs/train_${logfile}.log | awk -F 'global batch size:' '{print$2}' | awk -F '|' '{print$1}' | head -n 1 | awk '{print $1}'`
37+SAMPLES_PER_SECOND=`awk 'BEGIN{printf "%.3f\n", '${GBS}'*1000/'${STEP_TIME}'}'`
38+echo "Elapsed Time Per iteration (ms): $STEP_TIME" | tee -a logs/train_${logfile}.log
39+echo "Average Samples per Second: $SAMPLES_PER_SECOND" | tee -a logs/train_${logfile}.log
@@ -0,0 +1,146 @@
1+# 并行策略
2+parallel:
3+ fully_shard_parallel_size: auto
4+ fsdp_plan:
5+ apply_modules:
6+ - model.visual
7+ - model.visual.blocks.{*}
8+ - model.language_model
9+ - model.language_model.embed_tokens
10+ - model.language_model.layers.{*}
11+ - lm_head
12+ num_to_forward_prefetch: 1
13+ num_to_backward_prefetch: 1
14+ param_dtype: bf16
15+ reduce_dtype: fp32
16+ ring_attention_size: 1
17+ ulysses_parallel_size: 1
18+ expert_parallel_size: 16
19+ ep_plan:
20+ apply_modules:
21+ - model.language_model.layers.{*}.mlp.experts
22+ dispatcher: alltoall
23+ 
24+# 数据相关配置
25+data:
26+ dataset_param:
27+ dataset_type: huggingface
28+ attr:
29+ images: images
30+ messages: messages
31+ role_tag: role
32+ content_tag: content
33+ user_tag: user
34+ assistant_tag: assistant
35+ preprocess_parameters:
36+ model_name_or_path: &HF_MODEL_LOAD_PATH ./ckpt/hf_path/GLM-4.5V
37+ trust_remote_code: true
38+ use_fast_tokenizer: true
39+ split_special_tokens: false
40+ image_max_pixels: 262144
41+ image_min_pixels: 1024
42+ video_max_pixels: 16384
43+ video_min_pixels: 0
44+ video_fps: 2.0
45+ video_maxlen: 64
46+ basic_parameters:
47+ cutoff_len: 1024
48+ template: glm4v_moe
49+ train_on_prompt: false
50+ mask_history: false
51+ dataset_dir: ./data
52+ dataset: &DATASET_PATH ./data/mllm_format_llava_instruct_data.json
53+ cache_dir: ./data/cache_dir
54+ overwrite_cache: false
55+ preprocessing_batch_size: 1000
56+ preprocessing_num_workers: 16
57+ max_samples: null
58+ dataloader_param:
59+ pin_memory: true
60+ shuffle: true
61+ dataloader_mode: sampler
62+ drop_last: true
63+ sampler_type: BaseRandomBatchSampler
64+ num_workers: 8
65+ collate_param:
66+ collator_id: qwen3vl
67+ ignore_pad_token_for_loss: true
68+ enable_preload: true
69+ 
70+# 模型配置
71+model:
72+ model_id: glm4v_moe
73+ model_name_or_path: &MM_MODEL_LOAD_PATH ./ckpt/mm_path/GLM-4.5V
74+ trust_remote_code: true
75+ attn_implementation: flash_attention_2
76+ use_grouped_expert_matmul: true
77+ 
78+# 优化特性配置
79+features:
80+ loss_cfg:
81+ loss_type: default
82+ router_aux_loss_coef: 0.0
83+ enable_chunk_loss: true
84+ enable_dynamic_chunk_loss: false
85+ chunkloss_plan:
86+ apply_module: lm_head
87+ chunk_size: 1024
88+ total_chunk_size: 4096
89+ recompute: true
90+ recompute_plan:
91+ apply_modules:
92+ - model.visual.blocks.{*}
93+ - model.language_model.layers.{*}
94+ enable_activation_offload: false
95+ activation_offload_plan:
96+ apply_modules:
97+ - model.visual.blocks.{*}
98+ - model.language_model.layers.{*}
99+ enable_ep_balance: false
100+ ep_balance_plan:
101+ max_dup_experts_num: 2
102+ 
103+# 训练配置
104+training:
105+ micro_batch_size: 1
106+ gradient_accumulation_steps: 1
107+ seed: 1234
108+ lr: 1.0e-5
109+ lr_decay_style: cosine
110+ lr_warmup_ratio: 0.1
111+ weight_decay: 0
112+ train_iters: 10000
113+ clip_grad: 0.0
114+ init_model_with_meta_device: true
115+ load: *MM_MODEL_LOAD_PATH
116+ load_format: dcp
117+ load_strict: true
118+ no_load_optim: true
119+ no_load_rng: true
120+ optimizer: adamw
121+ adam_fused: true
122+ save: ./save_dir
123+ save_interval: 10000
124+ use_deter_comp: false
125+ load_rank0_and_broadcast: false
126+ plugin:
127+ - mindspeed_mm/fsdp/models/glm4_5v
128+ - mindspeed_mm/fsdp/data/datasets/huggingface
129+ 
130+# 工具配置
131+tools:
132+ profile:
133+ enable: false
134+ profile_type: static
135+ ranks: [0]
136+ static_param:
137+ level: level1
138+ with_stack: false
139+ with_memory: false
140+ record_shapes: false
141+ with_cpu: true
142+ save_path: ./profiling
143+ start_step: 10
144+ end_step: 11
145+ memory_profile:
146+ enable: false
@@ -0,0 +1,506 @@
1+# coding=utf-8
2+# Copyright 2025 The ZhipuAI Inc. team and HuggingFace Inc. team. All rights reserved.
3+from typing import Optional, Union
4+ 
5+import torch
6+import torch.nn as nn
7+import torch.nn.functional as F
8+from torch.distributed.tensor import DTensor
9+ 
10+from transformers.activations import ACT2FN
11+from transformers.cache_utils import Cache, DynamicCache
12+from transformers.masking_utils import create_causal_mask
13+from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
14+from transformers.modeling_outputs import BaseModelOutputWithPast
15+from transformers.processing_utils import Unpack
16+from transformers.utils import TransformersKwargs, can_return_tuple
17+from transformers.utils.generic import check_model_inputs
18+from transformers.models.glm4v_moe.configuration_glm4v_moe import Glm4vMoeTextConfig, Glm4vMoeVisionConfig
19+from transformers.models.glm4v_moe.modeling_glm4v_moe import (
20+ Glm4vMoeForConditionalGeneration,
21+ Glm4vMoeCausalLMOutputWithPast,
22+ Glm4vMoeTextDecoderLayer,
23+ Glm4vMoeTextMLP,
24+ Glm4vMoeVisionModel,
25+ Glm4vMoeRMSNorm,
26+ Glm4vMoeTextTopkRouter,
27+ Glm4vMoeVisionBlock,
28+ Glm4vMoeTextModel,
29+ Glm4vMoeVisionEmbeddings,
30+)
31+ 
32+from mindspeed_mm.fsdp.utils.register import model_register
33+from mindspeed_mm.fsdp.utils.device import IS_NPU_AVAILABLE
34+from mindspeed_mm.fsdp.distributed.parallel_state import get_parallel_state
35+from mindspeed_mm.fsdp.distributed.context_parallel.communication import (
36+ gather_forward_split_backward,
37+ packed_data_gather_forward_split_backward_with_cp,
38+ packed_data_split_forward_gather_backward_with_cp,
39+ split_forward_gather_backward_with_cp,
40+)
41+from mindspeed_mm.fsdp.distributed.context_parallel.utils import cal_split_sizes_multi
42+ 
43+if IS_NPU_AVAILABLE:
44+ import torch_npu
45+ from mindspeed_mm.fsdp.ops.moe_ops.gemm import fused_group_gemm
46+ 
47+ 
48+class Glm4vMoeTextExperts(nn.Module):
49+ def __init__(self, config, hidden_size=None, intermediate_size=None):
50+ super().__init__()
51+ self.config = config
52+ self.hidden_size = config.hidden_size if hidden_size is None else hidden_size
53+ self.intermediate_size = config.intermediate_size if intermediate_size is None else intermediate_size
54+ self.num_experts = config.n_routed_experts
55+ self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_size, 2 * self.intermediate_size))
56+ self.down_proj = nn.Parameter(torch.empty((self.num_experts, self.intermediate_size, self.hidden_size)))
57+ self.act_fn = ACT2FN[config.hidden_act]
58+ self.use_grouped_expert_matmul = getattr(config, "use_grouped_expert_matmul", True)
59+ 
60+ ps = get_parallel_state()
61+ self.enable_ep_balance = getattr(config, "enable_ep_balance", False) and ps.is_ep_enable()
62+ if self.enable_ep_balance:
63+ from mindspeed_mm.fsdp.distributed.expert_parallel.ep_balance.ep_balance_strategy import EPBalanceStrategy
64+ 
65+ self.ep_balance_strategy = EPBalanceStrategy(
66+ ep_group=ps.get_ep_group(),
67+ num_experts=self.num_experts,
68+ max_dup_experts_num=config.max_dup_experts_num,
69+ )
70+ 
71+ def clear_ep_balance_plan(*args, **kwargs):
72+ self.ep_balance_strategy.planner.clear_record_planner_result()
73+ 
74+ self.register_full_backward_hook(clear_ep_balance_plan)
75+ 
76+ def _local_experts_weight(self):
77+ gate_up_proj = self.gate_up_proj.to_local() if isinstance(self.gate_up_proj, DTensor) else self.gate_up_proj
78+ down_proj = self.down_proj.to_local() if isinstance(self.down_proj, DTensor) else self.down_proj
79+ return gate_up_proj, down_proj
80+ 
81+ def forward(self, hidden_states, topk_weights, topk_indices):
82+ gate_up_proj, down_proj = self._local_experts_weight()
83+ if self.use_grouped_expert_matmul and IS_NPU_AVAILABLE and hidden_states.device.type == "npu":
84+ batch_size = hidden_states.shape[0]
85+ orig_dtype = hidden_states.dtype
86+ hidden_states = hidden_states.reshape(-1, self.hidden_size)
87+ permuted_hidden_states, row_ids_map = torch_npu.npu_moe_token_permute(
88+ hidden_states, topk_indices.to(torch.int32)
89+ )
90+ tokens_per_expert = torch.histc(
91+ topk_indices, bins=self.num_experts, min=0, max=self.num_experts
92+ )
93+ intermediate_hidden_states = fused_group_gemm(
94+ permuted_hidden_states, gate_up_proj, tokens_per_expert
95+ )
96+ intermediate_activations = torch_npu.npu_swiglu(intermediate_hidden_states, dim=-1)
97+ output = fused_group_gemm(intermediate_activations, down_proj, tokens_per_expert)
98+ final_hidden_states = torch_npu.npu_moe_token_unpermute(
99+ output.to(topk_weights.dtype), row_ids_map, probs=topk_weights
100+ )
@@ -0,0 +1,41 @@
1+import torch
2+from transformers.models.glm4v_moe import modeling_glm4v_moe
3+ 
4+ 
5+def apply_rotary_pos_emb_vision(q, k, cos, sin):
6+ import torch_npu
7+ 
8+ orig_q_dtype, orig_k_dtype = q.dtype, k.dtype
9+ q, k = q.float(), k.float()
10+ cos, sin = cos.unsqueeze(-2).float(), sin.unsqueeze(-2).float()
11+ cos = cos.unsqueeze(0)
12+ sin = sin.unsqueeze(0)
13+ q = q.unsqueeze(0)
14+ k = k.unsqueeze(0)
15+ q_embed = torch_npu.npu_rotary_mul(q, cos, sin).squeeze(0).to(orig_q_dtype)
16+ k_embed = torch_npu.npu_rotary_mul(k, cos, sin).squeeze(0).to(orig_k_dtype)
17+ return q_embed, k_embed
18+ 
19+ 
20+def apply_multimodal_rotary_pos_emb(q, k, cos, sin, mrope_section, unsqueeze_dim=1):
21+ import torch_npu
22+ 
23+ mrope_section = mrope_section * 2
24+ cos = torch.cat([m[i % 3] for i, m in enumerate(cos.split(mrope_section, dim=-1))], dim=-1).unsqueeze(unsqueeze_dim)
25+ sin = torch.cat([m[i % 3] for i, m in enumerate(sin.split(mrope_section, dim=-1))], dim=-1).unsqueeze(unsqueeze_dim)
26+ 
27+ rotary_dim = cos.shape[-1]
28+ q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
29+ k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
30+ 
31+ q_embed = torch_npu.npu_rotary_mul(q_rot, cos, sin)
32+ k_embed = torch_npu.npu_rotary_mul(k_rot, cos, sin)
33+ 
34+ q_embed = torch.cat([q_embed, q_pass], dim=-1)
35+ k_embed = torch.cat([k_embed, k_pass], dim=-1)
36+ return q_embed, k_embed
37+ 
38+ 
39+def apply_glm4v_npu_patch():
40+ modeling_glm4v_moe.apply_rotary_pos_emb_vision = apply_rotary_pos_emb_vision
41+ modeling_glm4v_moe.apply_multimodal_rotary_pos_emb = apply_multimodal_rotary_pos_emb
@@ -8,6 +8,7 @@ import torch
8from transformers.utils import SAFE_WEIGHTS_INDEX_NAME8from transformers.utils import SAFE_WEIGHTS_INDEX_NAME
9 9 
10from checkpoint.vlm_model.converters.moe_expert import (10from checkpoint.vlm_model.converters.moe_expert import (
11+ get_config_type_from_class_name,
11 merge_moe_expert_weights,12 merge_moe_expert_weights,
12 split_moe_expert_weights,13 split_moe_expert_weights,
13 save_sharded_state_dict,14 save_sharded_state_dict,
@@ -99,6 +100,44 @@ def test_split_moe_expert_weights():
99 assert expert_1_down_proj.shape == (2, 3)100 assert expert_1_down_proj.shape == (2, 3)
100 101 
101 102 
103+def test_glm4v_merge_keeps_expert_weights_3d_and_round_trips():
104+ torch.manual_seed(0)
105+ original = {
106+ f"layer.0.experts.{expert}.gate_proj.weight": torch.randn(2, 3)
107+ for expert in range(2)
108+ }
109+ for expert in range(2):
110+ original[f"layer.0.experts.{expert}.up_proj.weight"] = torch.randn(2, 3)
111+ original[f"layer.0.experts.{expert}.down_proj.weight"] = torch.randn(3, 2)
112+ state_dict = {name: tensor.clone() for name, tensor in original.items()}
113+ args = (1, 2, 0, ConfigType.GLM4V, "layer.{layer}.experts.{expert}")
114+ 
115+ merge_moe_expert_weights(state_dict, *args)
116+ 
117+ assert state_dict["layer.0.experts.gate_up_proj"].shape == (2, 3, 4)
118+ assert state_dict["layer.0.experts.down_proj"].shape == (2, 2, 3)
119+ 
120+ split_moe_expert_weights(state_dict, *args)
121+ 
122+ assert state_dict.keys() == original.keys()
123+ for name, expected in original.items():
124+ torch.testing.assert_close(state_dict[name], expected)
125+ 
126+ 
127+def test_glm4v_3d_layout_requires_explicit_environment_flag(tmp_path):
128+ class Glm4vMoeConfig:
129+ pass
130+ 
131+ with patch(
132+ "checkpoint.vlm_model.converters.moe_expert.AutoConfig.from_pretrained",
133+ return_value=Glm4vMoeConfig(),
134+ ):
135+ with patch.dict("os.environ", {}, clear=True):
136+ assert get_config_type_from_class_name(tmp_path) == ConfigType.DEFAULT
137+ with patch.dict("os.environ", {"GLM4V_DCP_3D": "1"}, clear=True):
138+ assert get_config_type_from_class_name(tmp_path) == ConfigType.GLM4V
139+ 
140+ 
102def test_save_sharded_state_dict(setup_test_files):141def test_save_sharded_state_dict(setup_test_files):
103 test_dir = setup_test_files142 test_dir = setup_test_files
104 state_dict = {143 state_dict = {