作者​:昇腾实战派
知识地图​:https://blog.csdn.net/Lumos_Lovegood/article/details/161601003

一、背景概述

在大型语言模型(LLM)的训练与推理实践中,框架生态的兼容性是一个关键挑战。我们基于 MindFormers 框架中的 DeepSeek-V3 模型,面临一个实际需求:在无 MindSpore 环境的情况下,实现单卡 PyNative(动态图)模式的预训练闭环。目标平台为 Ascend NPU(通过 torch_npu),同时需向下兼容 CUDA 和 CPU。

更长远的设计约束是:本次单卡改造必须为后续平滑放大到多卡并行训练铺平道路,避免未来大规模重构。因此,所有设计决策都以“单卡即多卡 world_size==1 的特例”为出发点,确保分布式语义在单卡阶段就已正确接入。


二、总体设计原则

1. 单卡即多卡的特例

不编写“单卡专用”的假分支,也不插入占位符式的恒等操作。所有集合通信均走真正的 torch.distributed 接口,在 world_size==1 时天然退化为恒等操作。后续放大到多卡时,本层代码零改动,仅需在 YAML 配置中调整并行度。

2. 保留分布式张量抽象

不剥离 DTensor / DeviceMesh 等分布式抽象。即使各轴 size=1,依然构建完整的 DeviceMesh 网格,确保并行语义在单卡阶段就已“接线正确”。

3. 就地改造,而非另起炉灶

直接在 mindformers/pynative/ 目录及其依赖的共享基础设施上原地修改。当 PyNative 的 import 链拉入 MindSpore 重度依赖的共享文件(如 transformer_configmodels/utilsregistercontext 等)时,一并就地迁移。接受“图模式路径可能因此暂时不可用”的代价。

4. 框架后端集中收口

新增 pynative/_backend.py 作为唯一的后端选择点与分布式引导点。业务代码仅依赖此文件,切换 NPU/CUDA/CPU 只需修改环境变量 MF_DEVICE_TYPE,无需散落在约 50 个文件中逐一编辑。


三、MindSpore → PyTorch 机械映射规则

以下为逐文件改造时反复使用的对照表:

MindSporePyTorch备注
nn.Celltorch.nn.Module
construct(self, ...)forward(self, ...)
mindspore.mint.* / ops.*torch.*mint 基本一一对应 torch
ops.cast(x, dt) / x.astype(dt)x.to(dt)
mstype.float32torch.float32dtype 常量替换
Parameter(mint.empty(...), name=)torch.nn.Parameter(torch.empty(...))name= 无对应(见下)
param.name(可写)param._mf_name(旁挂可写属性)torch.nn.Parameter.name 只读
enable_mindspore_backward_compatPyTorch 原生 autograd
手动反传 senseloss.backward(gradient=sense)
FlashAttentionScoreF.scaled_dot_product_attention可插拔后端
mint.distributed.*torch.distributed.*(经 _backend
hyper_parallel.DTensortorch.distributed.tensor.DTensor版本容错
hyper_parallel.DeviceMeshtorch.distributed.device_mesh.*
grouped_matmul(图算子)torch_npu.npu_grouped_matmul + 纯 torch 分段兜底

关键坑:Parameter.name

PyTorch 的 Tensor.name 是只读的。MindFormers 中大量按 param.name 做参数分组、优化器状态命名、fnmatch 匹配。统一改为旁挂 p._mf_name = pname,读取处一律使用:

getattr(param, "name", None) or getattr(param, "_mf_name", None)

四、分层改造思路

1. 后端收口层 —— pynative/_backend.py

  • 通过 MF_DEVICE_TYPE 环境变量选择设备:npu/cuda/cpu,对应通信后端 hccl/nccl/gloo
  • 统一导出:current_deviceset_deviceinit_process_groupdestroy_process_groupget_rankget_world_sizenew_groupall_reduceall_gather_into_tensorreduce_scatter_tensorall_to_all_singlebroadcastbroadcast_object_listDeviceMeshinit_device_meshReduceOp
  • init_process_group()无 launcher 的裸单进程自动补齐 RANK/WORLD_SIZE/LOCAL_RANK/MASTER_ADDR/MASTER_PORT 默认值,使得不依赖 msrun/torchrun 也能直接 python run_pynative.py 起训。
  • 版本容错的 rms_norm(x, normalized_shape, weight, eps):优先使用 torch.nn.functional.rms_norm(torch ≥ 2.4),否则手写 x * rsqrt(mean(x^2) + eps) * weight,数值等价。

核心思路:将“跟框架、硬件、torch 版本相关的一切不确定性”压进这一个文件,上层业务永远只 import 它。

2. 分布式张量兼容层 —— pynative/dtensor_compat.py

DTensor 公共 API 在 torch 2.5+ 才稳定。做版本容错解析:

  • 先尝试 torch.distributed.tensor(DTensor/Shard/Replicate/Partial/distribute_tensor/distribute_module + placement_types.Placement)
  • 回退到 torch.distributed._tensor
  • 另提供 inplace_copySkipDTensorDispatch = nullcontext

3. FSDP / 并行维度层 —— pynative/distributed/*

  • parallel_dims.py:从 TorchTitan 移植,DeviceMesh 从 _backend 取设备类型。
  • fsdp.pyFSDPModule 导入路径随 torch 版本漂移(≥2.6 公共 torch.distributed.fsdp;2.4/2.5 私有 torch.distributed._composable.fsdp),三级 try/except 兜底到 Nonedisable_fsdp_gradient_divisionFSDPModule is None 时直接 early-return。单卡不触发 FSDP,因此兜底为 None 也不影响闭环。

4. 基础算子层 —— pynative/layers/*

linearlayer_normactivationdropoutflash_attentionmask_generateidentity_opmc2 逐个按映射表转换。

  • layer_norm.pyFusedRMSNorm / FusedLayerNorm 改为 nn.Module,RMSNorm 走 _backend.rms_norm(版本容错)。
  • flash_attention.py_run_attention 使用 SDPA,按 input_layout 处理张量排布。

5. Transformer 结构层 —— pynative/transformers/*

attentionmulti_latent_attention(MLA)mlptransformer_layertransformer_blockmulti_token_prediction(MTP)hyper_connection 转换。

  • MoE 子模块 moe/{router, moe_layer, moe_utils, experts, shared_experts}
    • experts.py:模块级 grouped_matmul = torch_npu.npu_grouped_matmul 融合算子 + 纯 torch 分段 matmul 兜底。
    • router.pytorch.histc 对 Long 类型报“not implemented”→ 改为 torch.bincount(idx.flatten().to(int64), minlength=num_experts)

6. 模型主干 —— pynative/base_models/gpt/*

  • gpt_model.py_update_expert_bias 使用 module.tokens_per_expert.zero_()in-place,不能给 Parameter 赋新张量)。
  • parallelize.py:FSDP2 导入全部 guard;ms.DeviceCtx("meta")torch.device("meta");PP 梯度同步使用 torch.distributed.all_gather

7. 训练编排 —— pynative/trainer/*

  • trainer.py:删除 enable_mindspore_backward_compat_forward_backward 使用 loss.backward(gradient=sense)_optimizer_update 调用 optimizer.step(grads);建模循环里给参数挂 p._mf_name、模块挂 _mf_param_namesm.to_empty(device=current_device())get_batch 把张量搬到 current_device()_create_dataset_iterator 支持普通可迭代对象。
  • utils.py:梯度范数/参数分组改为读取 param.gradmodel.named_parameters();新增 _SyntheticTokenDataset + _build_synthetic_dataloader_build_datasetRandomDataset/SyntheticDataset 早分支返回 torch DataLoader;mindspore.dataset 相关 import 全部 guard。

8. 优化器 / Loss / LR —— pynative/optimizer/*pynative/loss/*core/lr/*

  • AdamW 继承 torch.optim.Optimizer,入口 step(gradients),fp32 master/state 键使用 _mf_name
  • Muon:仍按 param.name 做 fnmatch(遗留待办,需比照 AdamW 改为 _mf_name)。
  • ConstantWarmUpLR 等:mstype.float32 在无 mindspore 时为 None → 改为纯 Python 实现,__call__/__float__ 返回 base lr。

9. 共享基础设施的“就地迁移”—— 约 50+ 文件

PyNative import 链会拖入大量 MindSpore 重度依赖的共享文件,处理策略分两类:

  • 能纯 torch 化的transformer_config.py(dtype 全映射到 torch、convert_str_to_mstype 返回 torch.dtype)、init_method.py(返回 torch 闭包产 torch.empty(...).normal_())、models/utils.pyconfiguration_utils.py 等直接迁移。
  • 图模式专用、单卡 PyNative 用不到的:把顶层 import mindsporetry/exceptif _HAS_MINDSPORE: guard 起来,None 时对相关分支 early-return / 跳过。mindformers/__init__.py 把 legacy 图模式导入整体包进 if _HAS_MINDSPORE:

10. 模型分发与配置 —— models/deepseek3/*

  • modeling_deepseek_v3.pyDeepseekV3ForCausalLM 分发器做成 mindspore-tolerant,__new__ 在 ms is None 时默认走 pynative。
  • config_converter_deepseek_v3.py_pre_process 设置 moe_grouped_gemm 默认 True(否则 moe_grouped_gemm=False 抛 NotImplementedError)。

11. 入口与启动方式 —— 替换 msrun

  • 新增 run_pynative.py:先设置 MF_DEVICE_TYPE 再 import trainer,PynativeTrainer(config=args.config).train()
  • 单卡不需要 msrun/torchrun_backend.init_process_group() 自补 launcher 默认值即可裸跑;多卡时再用 torchrun --nproc_per_node=N
  • 新增最小配置 configs/deepseek3/pretrain_deepseek3_single_card.yaml:PyNative TrainConfig schema,小维度(hidden 256 / 4 层 / 8 专家 / vocab 1024),train_dataset.dataloader.type: RandomDatasetnum_nextn_predict_layers: 0(先关闭 MTP)。

五、关键错误与解决方案

现象根因解决方案
torch.histc not implemented for 'Long'histc 不支持整型改为 torch.bincount(...minlength=...)
TensorBase.name not writabletorch Parameter.name 只读旁挂 _mf_name,读取处双取
moe_grouped_gemm=False NotImplementedError纯 torch 未实现非分组路径配置默认 True
mtp_loss_scaling_factor None最小配置没配 MTPnum_nextn_predict_layers: 0
__init__ 拉进 mindspore顶层导入未 guardif _HAS_MINDSPORE: 包裹
cannot import DTensor from torch.distributed.tensor(torch < 2.5)DTensor 公共 API 版本差异dtensor_compat 版本容错,扫 18 文件
cannot import FSDPModule from torch.distributed.fsdp(torch 2.4.0)FSDP2 公共/私有路径漂移三级 try/except 兜底 None + early-return
LR mstype.float32 None无 mindspore 时 dtype 常量为 NoneLR 调度器纯 Python 化
_update_expert_bias 赋值报错给 Parameter 赋新张量改为 .zero_() in-place

统一的 torch 版本兼容套路:凡跨版本漂移的 API,一律“公共路径 → 私有路径 → None/手写”三级兜底,单卡用不到的能力兜底为 None 不影响闭环。


七、权重转换说明

训练过程中,MindSpore 与 PyTorch 加载同一份权重。在预训练场景下,可以使用 PyTorch 保存一个初始化权重后,转换为 MindSpore 权重。由于 MindSpore 的权重名称与 PyTorch 存在差异,权重转换的本质是将 PyTorch 权重 dict 中的名称改为 MindSpore 权重名称,以支持 MindSpore 加载。权重转换可参考权重转换指导。MindSpore 与 PyTorch 均支持 bin 格式数据,加载相同的数据集进行训练,可保证每个 step 一致。


相关代码已上传至 https://gitcode.com/sabijun/mindformers/tree/torch_porting

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐