本文档详细介绍 Time-MoE 模型在华为昇腾 NPU 环境下的部署、测评、训练及微调流程,同时提供与 NVIDIA H20 GPU 环境的对比参考。
主要内容:
适用对象: 需要在昇腾 NPU 平台部署或训练 Time-MoE 模型的用户
| 驱动固件 | CANN版本 | python版本 | torch版本 | torch_npu版本 |
|---|---|---|---|---|
| 25.5.0 | 8.5.0 | 3.11.14 | 2.8.0 | 2.8.0 |
本指导以 8.5.0-910b-ubuntu22.04-py3.11 镜像为例,获取镜像的命令为:
docker pull quay.io/ascend/cann:8.5.0-910b-ubuntu22.04-py3.11通过 docker images 可以查看是否拉取成功。
docker run -it -d \
--shm-size=500g \
--name TimeMoE \
--privileged \
--entrypoint /bin/bash \
--net=host \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci4 \
--device /dev/davinci5 \
--device /dev/davinci6 \
--device /dev/davinci7 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /opt/:/opt/ \
quay.io/ascend/cann:8.5.0-910b-ubuntu22.04-py3.11docker exec -itu root TimeMoE bash本项目以在 /home 目录下拉取为例,首先 cd /home,然后输入:
cd /home
git clone https://github.com/Time-MoE/Time-MoE.git
cd Time-MoE
git checkout 915bfda4c78a544d62a2bec6ab22948423059236可以看到存在 TimeMoE 文件,即表示拉取成功。为了防止由于 Time-MoE 代码较大更新导致部署失败问题,故需要 checkout 到指定的 commit。
cd /home
git clone https://github.com/zhouhaoyi/ETDataset.git由于 Time-300B 的原始数据集有 1.24T,为了快速验证,故仅下载其中的 sales 部分数据集,操作如下:
mkdir Time-300B
cd Time-300B
pip install huggingface_hub
export HF_ENDPOINT=https://hf-mirror.com
export HF_HOME=/tmp/hf_cache
hf download Maple728/Time-300B --repo-type dataset --include "sales/*"由于上述下载的数据格式是缓存文件夹结构,并非真实可用的数据格式,故需要将其转化,执行数据转化命令:
cd /home/Time-300B
cp -rL /tmp/hf_cache/hub/datasets--Maple728--Time-300B/snapshots/*/sales ./sales转化后的数据格式如下:
sales/
├── dominick/
├── favorita_transactions/
├── favorita_sales/
......cd /home/Time-MoE
HF_ENDPOINT=https://hf-mirror.com python -c "
from huggingface_hub import snapshot_download
snapshot_download(
repo_id='Maple728/TimeMoE-50M',
local_dir='./TimeMoE-50M',
resume_download=True,
local_dir_use_symlinks=False
)
"wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.shchmod +x Miniconda3-latest-Linux-aarch64.shbash Miniconda3-latest-Linux-aarch64.shsource ~/miniconda3/etc/profile.d/conda.shconda create -n Time-MoE python=3.11 -ycd /home/Time-MoE
pip install -r requirements.txt
pip install torch==2.8.0 torch_npu==2.8.0
pip install transformers==4.57.0run_eval.py,在 import torch 下面添加:from torch_npu.contrib import transfer_to_nputorch_dist_run.py,在 import torch 下面添加:from torch_npu.contrib import transfer_to_npu# 执行推理
python run_eval.py -d /home/ETDataset/ETT-small/ETTh1.csv --model ./TimeMoE-50M -p 96修改 time_moe/runner.py,注释第 125 行的代码:
# evaluation_strategy=train_config.get("evaluation_strategy", 'no'),原因:Time-MoE 官方代码问题,在 GPU 和 NPU 环境上测试均发现此问题。原因是 TimeMoETrainingArguments 不支持 evaluation_strategy 这个参数,代码里传了不支持的参数,所以直接崩溃。
修改 time_moe/runner.py,在第 55-59 行添加两行代码:
else:
model = TimeMoeForPrediction.from_pretrained(model_path, **kwargs)
# Guard against corrupted weight restore
import torch.nn as nn
object.__setattr__(model, 'loss_function', nn.HuberLoss(reduction='none', delta=2.0))原因:模型权重(model.safetensors)中 loss_function 字段被错误地存为了 ForCausalLMLoss 函数指针。调用 from_pretrained() 时,权重恢复机制把这个错误的函数加载到了模型上,导致后续 calc_ar_loss 调用 self.loss_function(preds, labels) 时失败。
nohup torchrun --master_addr=127.0.0.1 --master_port=11011 --node_rank=0 --nproc_per_node=8 main.py --data_path /home/Time-300B/sales --model_path /home/Time-MoE/TimeMoE-50M > run.log 2>&1 &注意:上述训练命令用到了 8 张 NPU 卡,所用数量可以通过 nproc_per_node 修改。
{'loss': 0.4114, 'grad_norm': 0.25300517678260803, 'learning_rate': 0.0001, 'epoch': 0.0}
{'loss': 0.4517, 'grad_norm': 0.2968370318412781, 'learning_rate': 9.99999584434885e-05, 'epoch': 0.0}
{'loss': 0.4112, 'grad_norm': 0.1269938200712204, 'learning_rate': 9.999983377409216e-05, 'epoch': 0.0}
{'loss': 0.4163, 'grad_norm': 0.13979601860046387, 'learning_rate': 9.999962599222543e-05, 'epoch': 0.0}
............
{'loss': 0.3931, 'grad_norm': 0.1221962720155716, 'learning_rate': 5.000016622590785e-05, 'epoch': 1.0}
{'loss': 0.3587, 'grad_norm': 0.10206776112318039, 'learning_rate': 5.00000415565115e-05, 'epoch': 1.0}
{'train_runtime': 999.1059, 'train_samples_per_second': 110.309, 'train_steps_per_second': 1.725, 'train_loss': 0.3853655972940176, 'epoch': 1.0}
100%|██████████| 1723/1723 [16:39<00:00, 1.72it/s]| 项目 | NPU 环境 | GPU 环境 (H20) |
|---|---|---|
| 驱动固件 | 25.5.0 | NVIDIA Driver 550+ |
| CANN版本 | 8.5.0 | - |
| python版本 | 3.11.14 | 3.11 |
| torch版本 | 2.8.0 | 2.5.0+ |
| torch_npu版本 | 2.8.0 | - |
| 镜像 | cann:8.5.0-910b-ubuntu22.04-py3.11 | pytorch:25.12-py3 |
GPU 环境使用 NVIDIA H20,本文档以 Docker 容器方式部署。
docker pull nvcr.io/nvidia/pytorch:25.12-py3docker run -itd --runtime nvidia --gpus all --name TimeMoE --shm-size 500g --ulimit memlock=-1 --ulimit stack=67108864 --net host --entrypoint /bin/bash -v /home/:/home/ nvcr.io/nvidia/pytorch:25.12-py3
docker start TimeMoE
docker exec -itu root TimeMoE bashcd /workspace
git clone https://github.com/Time-MoE/Time-MoE.git
cd Time-MoE
git checkout 915bfda4c78a544d62a2bec6ab22948423059236cd /workspace
git clone https://github.com/zhouhaoyi/ETDataset.git由于 Time-300B 的原始数据集有 1.24T,为了快速验证,故仅下载其中的 sales 部分数据集,操作如下:
cd /workspace
mkdir Time-300B
cd Time-300B
pip install huggingface_hub
export HF_ENDPOINT=https://hf-mirror.com
export HF_HOME=/tmp/hf_cache
hf download Maple728/Time-300B --repo-type dataset --include "sales/*"wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.shchmod +x Miniconda3-latest-Linux-x86_64.shbash Miniconda3-latest-Linux-x86_64.shsource ~/miniconda3/etc/profile.d/conda.shconda create -n Time-MoE python=3.11 -ycd /workspace/Time-MoE
pip install -r requirements.txt
pip install transformers==4.57.0# 执行推理
python run_eval.py -d /workspace/ETDataset/ETT-small/ETTh1.csv -p 96nohup torchrun --master_addr=127.0.0.1 --master_port=11011 --node_rank=0 --nproc_per_node=8 main.py --data_path /home/Time-300B/sales --model_path /home/Time-MoE/TimeMoE-50M > run.log 2>&1 &注意:上述训练命令用到了 8 张 GPU 卡,所用数量可以通过 nproc_per_node 修改。
{'loss': 0.4086, 'grad_norm': 0.23242798447608948, 'learning_rate': 0.0001, 'epoch': 0.0}
{'loss': 0.4247, 'grad_norm': 0.31524255871772766, 'learning_rate': 9.99999584434885e-05, 'epoch': 0.0}
{'loss': 0.4202, 'grad_norm': 0.2507534325122833, 'learning_rate': 9.999983377409216e-05, 'epoch': 0.0}
{'loss': 0.4035, 'grad_norm': 0.410408616065979, 'learning_rate': 9.999962599222543e-05, 'epoch': 0.0}
{'loss': 0.4008, 'grad_norm': 0.18601377308368683, 'learning_rate': 9.999933509857908e-05, 'epoch': 0.0}
............
{'loss': 0.3977, 'grad_norm': 0.11204443871974945, 'learning_rate': 5.000037400777459e-05, 'epoch': 1.0}
{'loss': 0.3645, 'grad_norm': 0.09452001005411148, 'learning_rate': 5.000016622590785e-05, 'epoch': 1.0}
{'loss': 0.3586, 'grad_norm': 0.1820800006389618, 'learning_rate': 5.00000415565115e-05, 'epoch': 1.0}
{'train_runtime': 339.0897, 'train_samples_per_second': 325.017, 'train_steps_per_second': 5.081, 'train_loss': 0.3852101174192379, 'epoch': 1.0}
100%|██████████| 1723/1723 [05:39<00:00, 5.08it/s]
2026-04-16 09:44:26,507 - log_util.py[pid:3606;line:52:log_in_local_rank_0] - INFO: Saving model to logs/time_moeETT = Electricity Transformer Temperature
四个数据集的区别:
| 数据集 | 采样频率 | 时间跨度 | 总长度 | 用途 |
|---|---|---|---|---|
| ETTh1 | 1 小时 | 约 2 年 | 17,420 | 最常用的中等难度基准 |
| ETTh2 | 1 小时 | 约 2 年 | 17,420 | 通常比 ETTh1 更难一点,时间模式、噪声分布不同,验证模型泛化能力 |
| ETTm1 | 15 分钟 | 约 2 年 | 69,680 | 高频数据更细粒度周期结构,中短期预测 + 高频建模 |
| ETTm2 | 15 分钟 | 约 2 年 | 69,680 | 与 ETTm1 相比分布变化更明显,高频 + 泛化测试 |
数据格式:
date,HUFL,HULL,MUFL,MULL,LUFL,LULL,OT默认设置:
备注:上述结果均是在单卡上测试所得,运行脚本为:
python run_eval.py -m models/TimeMoE-50M -d dataset/ETT-small/ETTh1.csv -p 96通过改变 -m(模型权重)、-d(数据集)、-p(预测长度)得到不同组合的数据
| prediction_length | 指标 | TimeMOE-base-50M (NPU) | TimeMOE-large-200M (NPU) | TimeMOE-base-50M (GPU) | TimeMOE-large-200M (GPU) |
|---|---|---|---|---|---|
| 96 | MSE | 0.3577 | 0.3504 | 0.3577 | 0.3504 |
| 96 | MAE | 0.3817 | 0.3821 | 0.3817 | 0.3821 |
| 96 | Time | 04:31 | 04:54 | 01:01 | 01:32 |
| 192 | MSE | 0.3845 | 0.3889 | 0.3845 | 0.3890 |
| 192 | MAE | 0.4047 | 0.4121 | 0.4046 | 0.4121 |
| 192 | Time | 08:35 | 09:30 | 02:13 | 03:14 |
| 336 | MSE | 0.4122 | 0.4117 | 0.4123 | 0.4117 |
| 336 | MAE | 0.4343 | 0.4308 | 0.4343 | 0.4307 |
| 336 | Time | 21:45 | 24:01 | 06:41 | 08:55 |
| 720 | MSE | 0.4477 | 0.4278 | 0.4478 | 0.4278 |
| 720 | MAE | 0.4760 | 0.4559 | 0.4760 | 0.4559 |
| 720 | Time | 35:44 | 39:13 | 13:13 | 16:44 |
| Avg | MSE | 0.401 | 0.395 | 0.401 | 0.395 |
| Avg | MAE | 0.424 | 0.420 | 0.424 | 0.420 |
| 论文结果 | MSE | 0.400 | 0.394 | 0.400 | 0.394 |
| 论文结果 | MAE | 0.424 | 0.419 | 0.424 | 0.419 |
| 绝对误差 | MSE | 0.001 | 0.001 | 0.001 | 0.001 |
| 绝对误差 | MAE | 0.000 | 0.001 | 0.000 | 0.001 |
| prediction_length | 指标 | TimeMOE-base-50M (NPU) | TimeMOE-large-200M (NPU) | TimeMOE-base-50M (GPU) | TimeMOE-large-200M (GPU) |
|---|---|---|---|---|---|
| 96 | MSE | 0.3038 | 0.3018 | 0.3022 | 0.3018 |
| 96 | MAE | 0.3576 | 0.3541 | 0.3564 | 0.3542 |
| 96 | Time | 04:31 | 04:58 | 01:01 | 01:31 |
| 192 | MSE | 0.3479 | 0.3641 | 0.3479 | 0.3641 |
| 192 | MAE | 0.3826 | 0.3853 | 0.3827 | 0.3857 |
| 192 | Time | 08:31 | 09:26 | 02:11 | 03:13 |
| 336 | MSE | 0.3928 | 0.4172 | 0.3927 | 0.4171 |
| 336 | MAE | 0.4185 | 0.4250 | 0.4186 | 0.4251 |
| 336 | Time | 21:20 | 23:47 | 06:38 | 08:54 |
| 720 | MSE | 0.4183 | 0.5374 | 0.4182 | 0.5378 |
| 720 | MAE | 0.4533 | 0.4957 | 0.4532 | 0.4958 |
| 720 | Time | 35:58 | 38:53 | 13:09 | 16:46 |
| Avg | MSE | 0.366 | 0.405 | 0.365 | 0.405 |
| Avg | MAE | 0.403 | 0.415 | 0.403 | 0.415 |
| 论文结果 | MSE | 0.366 | 0.405 | 0.366 | 0.405 |
| 论文结果 | MAE | 0.404 | 0.415 | 0.404 | 0.415 |
| 绝对误差 | MSE | 0.000 | 0.000 | 0.001 | 0.000 |
| 绝对误差 | MAE | 0.001 | 0.000 | 0.001 | 0.000 |
| prediction_length | 指标 | TimeMOE-base-50M (NPU) | TimeMOE-large-200M (NPU) | TimeMOE-base-50M (GPU) | TimeMOE-large-200M (GPU) |
|---|---|---|---|---|---|
| 96 | MSE | 0.3385 | 0.3088 | 0.3384 | 0.3090 |
| 96 | MAE | 0.3681 | 0.3567 | 0.3681 | 0.3568 |
| 96 | Time | 18:57 | 20:21 | 04:06 | 06:08 |
| 192 | MSE | 0.3525 | 0.3467 | 0.3526 | 0.3465 |
| 192 | MAE | 0.3877 | 0.3808 | 0.3877 | 0.3808 |
| 192 | Time | 36:11 | 40:20 | 09:12 | 13:33 |
| 336 | MSE | 0.3787 | 0.3735 | 0.3788 | 0.3736 |
| 336 | MAE | 0.4113 | 0.4080 | 0.4114 | 0.4081 |
| 336 | Time | 01:35:26 | 01:57:50 | 29:10:00 | 39:11:00 |
| 720 | MSE | 0.5003 | 0.4750 | 0.5002 | 0.4748 |
| 720 | MAE | 0.4907 | 0.4772 | 0.4906 | 0.4771 |
| 720 | Time | 02:59:15 | 03:13:47 | 01:05:54 | 01:23:42 |
| Avg | MSE | 0.393 | 0.376 | 0.393 | 0.376 |
| Avg | MAE | 0.414 | 0.406 | 0.414 | 0.406 |
| 论文结果 | MSE | 0.394 | 0.376 | 0.394 | 0.376 |
| 论文结果 | MAE | 0.415 | 0.405 | 0.415 | 0.405 |
| 绝对误差 | MSE | 0.001 | 0.000 | 0.001 | 0.000 |
| 绝对误差 | MAE | 0.001 | 0.001 | 0.001 | 0.001 |
| prediction_length | 指标 | TimeMOE-base-50M (NPU) | TimeMOE-large-200M (NPU) | TimeMOE-base-50M (GPU) | TimeMOE-large-200M (GPU) |
|---|---|---|---|---|---|
| 96 | MSE | 0.1976 | 0.1973 | 0.1967 | 0.1970 |
| 96 | MAE | 0.2876 | 0.2862 | 0.2868 | 0.2860 |
| 96 | Time | 18:02 | 19:40 | 04:01 | 06:06 |
| 192 | MSE | 0.2538 | 0.2504 | 0.2538 | 0.2503 |
| 192 | MAE | 0.3302 | 0.3219 | 0.3301 | 0.3218 |
| 192 | Time | 35:08 | 39:57 | 09:11 | 13:27 |
| 336 | MSE | - | 0.3363 | 0.3233 | 0.3362 |
| 336 | MAE | - | 0.3743 | 0.3721 | 0.3742 |
| 336 | Time | - | 01:44:07 | 29:06:00 | 39:00:00 |
| 720 | MSE | - | 0.5512 | 0.4843 | 0.5505 |
| 720 | MAE | - | 0.4883 | 0.4620 | 0.4881 |
| 720 | Time | - | 03:12:04 | 01:05:34 | 01:23:29 |
| Avg | MSE | - | 0.315 | 0.334 | 0.316 |
| Avg | MAE | - | 0.363 | 0.368 | 0.361 |
| 论文结果 | MSE | - | 0.317 | 0.316 | - |
| 论文结果 | MAE | - | 0.365 | 0.361 | - |
| 绝对误差 | MSE | - | 0.002 | 0.018 | - |
| 绝对误差 | MAE | - | 0.002 | 0.007 | - |
注:ETTm2 数据集部分 NPU 测试结果缺失(- 表示无数据)
本方法的核心原则是:
❗不是判断"权重是否完全相同",而是判断 "差异是否符合训练过程中的合理统计分布"
在 GPU 与 NPU 分别进行模型微调的场景中:
即使满足:
由于浮点运算与并行计算差异,最终权重仍然会出现偏差。
传统方法(如 allclose)隐含假设:
❌ 两个权重应逐点相同
但在实际训练中:
都会导致:
❗逐点一致性不成立
因此必须采用统计方法。
1. 统计一致性原则(Statistical Consistency)
关注整体误差统计特征,而非单点误差:
目标是判断:
❓大多数参数误差是否处于合理范围
2. 分布一致性原则(Distribution over Element)
使用分位数评估误差分布:
| 指标 | 含义 |
|---|---|
| P50 | 中位数误差(整体水平) |
| P90 | 高误差区域 |
| P99 | 极端异常点 |
用于判断:
3. 尺度感知原则(Scale-aware Error)
仅使用绝对误差是不够的,需要引入相对误差: 原因:
因此需结合:
4. 结构化分析原则(Structure-aware Analysis)
不同模块对误差的敏感性不同,应分别统计:
典型规律:
| 模块 | 特性 |
|---|---|
| LayerNorm | 极稳定 |
| Attention | 中等敏感 |
| MoE Experts | 高敏感 |
5. 功能一致性优先原则(Function over Parameter)
权重差异并不等价于模型行为差异:
❗权重不同 ≠ 模型性能不同
应进一步验证:
6. MoE 路径敏感性原则(Path Sensitivity)
对于 MoE 模型:
导致:
因此 MoE 模型天然具有更高差异性。
import torch
from safetensors.torch import load_file
from collections import defaultdict
from tqdm import tqdm
# ===== 配置 =====
gpu_path = "/workspace/Time-MoE/logs/time_moe/model.safetensors"
npu_path = "/workspace/weight/logs/time_moe/model.safetensors"
sample_per_tensor = 1000 # 用于quantile采样
rel_eps = 1e-3 # relative error过滤阈值
topk_num = 10
# ===== 加载 =====
print("Loading weights...")
gpu_weights = load_file(gpu_path)
npu_weights = load_file(npu_path)
gpu_keys = set(gpu_weights.keys())
npu_keys = set(npu_weights.keys())
print("参数名称一致:", gpu_keys == npu_keys)
common_keys = gpu_keys & npu_keys
print(f"参数数量: {len(common_keys)}")
# ===== 全局统计 =====
global_max = 0
global_mean = 0
global_rel_mean = 0
count = 0
# ===== TopK =====
topk = []
# ===== 分模块统计 =====
module_stats = defaultdict(list)
# ===== quantile采样 =====
samples = []
print("\nComparing...")
for key in tqdm(common_keys):
g = gpu_weights[key].to(torch.float32).cpu()
n = npu_weights[key].to(torch.float32).cpu()
if g.shape != n.shape:
print(f"[Shape mismatch] {key}: {g.shape} vs {n.shape}")
continue
diff = torch.abs(g - n)
max_d = diff.max().item()
mean_d = diff.mean().item()
# ===== relative error(过滤小值)=====
mask = torch.abs(g) > rel_eps
if mask.sum() > 0:
rel = diff[mask] / torch.abs(g[mask])
rel_mean = rel.mean().item()
else:
rel_mean = 0
# ===== 全局 =====
global_max = max(global_max, max_d)
global_mean += mean_d
global_rel_mean += rel_mean
count += 1
# ===== TopK =====
topk.append((max_d, key))
# ===== 模块分类 =====
if "self_attn" in key:
module_stats["attention"].append(mean_d)
elif "ffn_layer.experts" in key:
module_stats["moe_expert"].append(mean_d)
elif "shared_expert" in key:
module_stats["shared_expert"].append(mean_d)
elif "layernorm" in key:
module_stats["layernorm"].append(mean_d)
else:
module_stats["others"].append(mean_d)
# ===== quantile采样 =====
flat = diff.view(-1)
if flat.numel() > sample_per_tensor:
idx = torch.randint(0, flat.numel(), (sample_per_tensor,))
flat = flat[idx]
samples.append(flat)
# ===== 汇总 =====
print("\n===== 全局统计 =====")
print(f"平均误差: {global_mean / count:.6e}")
print(f"平均相对误差(过滤<{rel_eps}): {global_rel_mean / count:.6e}")
print(f"最大误差: {global_max:.6e}")
# ===== 分位数(采样)=====
samples = torch.cat(samples)
q = torch.quantile(samples, torch.tensor([0.5, 0.9, 0.99]))
print("\n===== 分位数(采样) =====")
print(f"P50: {q[0].item():.6e}")
print(f"P90: {q[1].item():.6e}")
print(f"P99: {q[2].item():.6e}")
# ===== 模块统计 =====
print("\n===== 模块误差 =====")
for k, v in module_stats.items():
if len(v) > 0:
print(f"{k:15s}: mean={sum(v)/len(v):.6e}, count={len(v)}")
# ===== Top-K =====
topk.sort(reverse=True)
print(f"\n===== Top {topk_num} 最大误差参数 =====")
for d, k in topk[:topk_num]:
print(f"{k}: {d:.6e}")下述结果是用同样的微调命令在 GPU 和 NPU 上跑 10 个 epoch 后得到的权重,并对其比较的结果:
Loading weights...
参数名称一致: True
参数数量: 463
Comparing...
100%|████████████████████████████████████████████████████████████████████████████████████| 463/463 [00:00<00:00, 874.70it/s]
===== 全局统计 =====
平均误差: 7.039339e-03
平均相对误差(过滤<0.001): 2.721881e-01
最大误差: 1.691253e-01
===== 分位数(采样) =====
P50: 5.434263e-03
P90: 1.741766e-02
P99: 3.062531e-02
===== 模块误差 =====
moe_expert : mean=8.156686e-03, count=288
attention : mean=4.904049e-03, count=84
others : mean=4.401606e-03, count=19
shared_expert : mean=6.365904e-03, count=48
layernorm : mean=4.539759e-03, count=24
===== Top 10 最大误差参数 =====
model.layers.11.ffn_layer.experts.7.down_proj.weight: 1.691253e-01
model.layers.11.ffn_layer.experts.7.gate_proj.weight: 1.189974e-01
model.layers.11.ffn_layer.shared_expert.up_proj.weight: 1.134196e-01
model.layers.11.ffn_layer.shared_expert.down_proj.weight: 1.080939e-01
model.layers.11.ffn_layer.experts.3.down_proj.weight: 9.563965e-02
model.layers.11.ffn_layer.shared_expert.gate_proj.weight: 9.188609e-02
model.layers.11.ffn_layer.experts.2.down_proj.weight: 9.044161e-02
model.layers.11.ffn_layer.experts.7.up_proj.weight: 8.590467e-02
model.layers.11.ffn_layer.experts.6.down_proj.weight: 8.155429e-02
model.layers.3.ffn_layer.experts.2.gate_proj.weight: 8.127359e-02下图展示了 NPU 和 GPU 在微调过程中的 Loss 曲线对比:

Loss 差异统计分析:
| 指标 | 数值 |
|---|---|
| Step 数量 | 17,230 |
| 平均绝对误差 (MAE) | 1.594050e-02 |
| 最大绝对误差 | 7.950000e-02 |
结果分析:
曲线一致性:从图中可以看出,NPU 和 GPU 的 Loss 曲线整体走势高度一致,均呈现平稳下降趋势,表明两个平台在训练收敛性上表现相似。
误差水平:
1.59e-02,处于较低水平7.95e-02,发生在训练早期阶段(loss 值较高时),属于正常范围收敛行为:两个平台在相同 step 处的 loss 值非常接近,验证了 Time-MoE 模型在昇腾 NPU 和 NVIDIA GPU 上微调的一致性。
结论:NPU 与 GPU 的微调结果在统计意义上高度一致,差异主要来源于浮点运算和并行计算的正常偏差范围。
1e-3 量级,属于合理训练分叉范围,GPU 与 NPU 微调后的模型在权重层面存在可控范围内的统计差异,整体属于"训练分叉"而非"结构性错误"。可能是 pip 源问题,设置成阿里源:
export PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple
export PIP_TRUSTED_HOST=mirrors.aliyun.com问题原因:NPU 环境下 PyTorch 原生的 HuberLoss 可能存在兼容性问题。
解决方案:使用自定义的 NPUHuberLoss 替代。
具体步骤:
time_moe/models/ 目录下添加 HuberLoss.py 文件:import torch
import torch.nn as nn
class NPUHuberLoss(nn.Module):
def __init__(self, delta=2.0, reduction='none'):
super().__init__()
self.delta = delta
self.reduction = reduction
def forward(self, input, target):
diff = input - target
abs_diff = diff.abs()
loss = torch.where(
abs_diff <= self.delta,
0.5 * diff * diff,
self.delta * abs_diff - 0.5 * self.delta * self.delta
)
if self.reduction == 'mean':
return loss.mean()
elif self.reduction == 'sum':
return loss.sum()
else:
return lossmodeling_time_moe.py 开头添加导入:from .HuberLoss import NPUHuberLoss# 替换前
self.loss_function = torch.nn.HuberLoss(reduction='none', delta=2.0)
# 替换后
self.loss_function = NPUHuberLoss()之后照常训练即可。