已合并
feat: Support GLM-4.5V on the pure-FSDP2 backend (issue #370) #2875
SuperKoalaKing创建于 7月23日
feat: Support GLM-4.5V on the pure-FSDP2 backend (issue #370) #2875
已合并
共 9 个文件变更+1786-0
| @@ -13,6 +13,7 @@ mm-convert moe_expert --style split --hf_dir "merged_weight" --save_dir "splited | |||
| 13 | """ | 13 | """ |
| 14 | 14 | ||
| 15 | import json | 15 | import json |
| 16 | +import os | ||
| 16 | from pathlib import Path | 17 | from pathlib import Path |
| 17 | from typing import Dict, Literal | 18 | from typing import Dict, Literal |
| 18 | from enum import Enum | 19 | from enum import Enum |
| @@ -37,6 +38,7 @@ class ConfigType(Enum): | |||
| 37 | DEFAULT = 0 | 38 | DEFAULT = 0 |
| 38 | QWEN3_OMNI = 1 | 39 | QWEN3_OMNI = 1 |
| 39 | QWEN3_5 = 2 | 40 | QWEN3_5 = 2 |
| 41 | + GLM4V = 3 | ||
| 40 | 42 | ||
| 41 | 43 | ||
| 42 | def get_config_type_from_class_name(save_dir: Path) -> ConfigType: | 44 | def 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_OMNI | 52 | 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.DEFAULT | 56 | 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 | + | ||
🟡 Medium Priority 变更行: 证据链:
失败模式:训练失败后脚本继续运行并尝试解析不存在的日志 → 输出误导性性能数字或 awk 报错,给用户错误的成功/失败信号。 建议:在脚本开头(shebang 之后)添加 ![]() ![]() 不准确? | |||
| 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 | ||
🟡 Medium Priority 变更行: 问题: 脚本使用 后果:
建议:在脚本的第 2 行( ![]() ![]() 不准确? | |||
| 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 | ||
The file is empty
| @@ -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 | |||
| 8 | from transformers.utils import SAFE_WEIGHTS_INDEX_NAME | 8 | from transformers.utils import SAFE_WEIGHTS_INDEX_NAME |
| 9 | 9 | ||
| 10 | from checkpoint.vlm_model.converters.moe_expert import ( | 10 | from 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 | + | ||
| 102 | def test_save_sharded_state_dict(setup_test_files): | 141 | def test_save_sharded_state_dict(setup_test_files): |
| 103 | test_dir = setup_test_files | 142 | test_dir = setup_test_files |
| 104 | state_dict = { | 143 | state_dict = { |


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