已合并
【26.2.0众测】TorchNPU 26.1.0 众测任务交付:安装验证、快速入门迁移训练、torch.compile 13项后端体验与问题反馈(gcw_8p1hhlB0) #38
GMOW创建于 9月15日
【26.2.0众测】TorchNPU 26.1.0 众测任务交付:安装验证、快速入门迁移训练、torch.compile 13项后端体验与问题反馈(gcw_8p1hhlB0) #38
已合并
共 114 个文件变更+22718-0
| @@ -0,0 +1,12 @@ | |||
| 1 | +# Python 运行时产物 | ||
| 2 | +__pycache__/ | ||
| 3 | +*.pyc | ||
| 4 | + | ||
| 5 | +# torch.compile 调试输出(TORCH_COMPILE_DEBUG 产生,非交付内容) | ||
| 6 | +torch_compile_debug/ | ||
| 7 | +fusion_result.json | ||
| 8 | +traced_graph_cache/ | ||
| 9 | + | ||
| 10 | +# 数据集与权重(不入库,生成方式见复现命令清单) | ||
| 11 | +mnist/ | ||
| 12 | +checkpoint.pth.tar | ||
| @@ -0,0 +1,101 @@ | |||
| 1 | +# TorchNPU 26.1.0 众测任务提交材料 | ||
| 2 | + | ||
| 3 | +> 提交人 gitcode 账号:[gcw_8p1hhlB0](https://gitcode.com/gcw_8p1hhlB0) | ||
| 4 | + | ||
| 5 | +## 环境信息 | ||
| 6 | + | ||
| 7 | +- 硬件:Atlas 800T A2(Ascend 910B3,64GB HBM) | ||
| 8 | +- 软件:CANN 9.1.0 / TorchNPU 2.7.1.post8 与 2.9.0.post6(均属 26.1.0 配套)/ PyTorch 2.7.1 与 2.9.0 / Python 3.11 / Ubuntu 24.04 aarch64 | ||
| 9 | +- 任务2 双环境策略:Guard Filter 示例用 PyTorch 2.9.0(guard_filter_fn 需 >=2.9.0);7 个编译后端示例用环境预装的 PyTorch 2.7.1(`run_task3.sh` 自动切换) | ||
| 10 | +- 环境初始化:`source 补充-测试报告与原始日志/env_setup.sh`(自适应探测;从零搭建见 `补充-测试报告与原始日志/环境搭建.md`) | ||
| 11 | +- 任务2 运行变量:`TORCHINDUCTOR_CACHE_DIR`(编译缓存)、`TORCHINDUCTOR_COMPILE_THREADS`(容器内存限制下需调低,见测试报告问题6) | ||
| 12 | + | ||
| 13 | +## 目录结构 | ||
| 14 | + | ||
| 15 | +``` | ||
| 16 | +gcw_8p1hhlB0/ | ||
| 17 | +├── README.md # 本文件 | ||
| 18 | +├── 任务1-安装与快速入门/ # 任务书 4.2.1 + 4.2.2 | ||
| 19 | +│ ├── 交付件说明.txt | ||
| 20 | +│ ├── 链接交付件.xls # Issue/PR 链接 | ||
| 21 | +│ ├── 软件安装/ | ||
| 22 | +│ │ ├── 1_pip_list截图.png | ||
| 23 | +│ │ └── 2_torch_npu导入截图.png | ||
| 24 | +│ └── 快速入门/ | ||
| 25 | +│ ├── 1_训练脚本.py # transfer_to_npu 自动迁移 + AMP | ||
| 26 | +│ ├── 2_训练成功截图.png | ||
| 27 | +│ └── 3_权重文件截图.png | ||
| 28 | +├── 任务2-torch.compile体验与实践/ # 任务书 5.2(13 个改写示例) | ||
| 29 | +│ ├── 交付件说明.txt | ||
| 30 | +│ ├── 链接交付件.xls | ||
| 31 | +│ ├── Guard Filter/ # 0_公共模块 + 1~6 脚本 + 7~12 每示例截图(含重编译消除/正确性/性能三段输出) | ||
| 32 | +│ ├── Inductor-Triton/ # 0_公共模块 + 1_改写脚本 + 2/3 截图 | ||
| 33 | +│ ├── Inductor-MLIR/ # 额外含 4_规避方案AB对比截图 | ||
| 34 | +│ ├── Inductor-DVM/ | ||
| 35 | +│ ├── NPUGraphs/ | ||
| 36 | +│ ├── NPUGraph_EX/ | ||
| 37 | +│ ├── AOT_Eager/ | ||
| 38 | +│ └── TorchAir-GE/ | ||
| 39 | +└── 补充-测试报告与原始日志/ | ||
| 40 | + ├── 众测测试报告.md # 7 个问题 + 8 条改进建议 + 汇总表 | ||
| 41 | + ├── issue提交记录.md # 3 个正式 issue 链接与要点 | ||
| 42 | + ├── 复现命令清单.md # 全部截图对应的运行命令 | ||
| 43 | + ├── run_task3.sh # 任务2 一键运行脚本 | ||
| 44 | + ├── env_setup.sh # 环境初始化 | ||
| 45 | + ├── 原始日志/ # 4.2.1/4.2.2/13 个示例全部日志 | ||
| 46 | + ├── GuardFilter-每示例截图/ # 6 个 Guard Filter 示例逐个截图 | ||
| 47 | + ├── MLIR问题定位/ # MLIR 数值错误二分定位实验(issue #4831 证据) | ||
| 48 | + ├── 故障卡问题证据/ # 首日故障卡训练失败日志(测试报告问题1) | ||
| 49 | + └── 对照脚本/ # train_gpu.py / train_fp32.py / verify_install.py | ||
| 50 | +``` | ||
| 51 | + | ||
| 52 | +## 任务完成情况(对照任务书) | ||
| 53 | + | ||
| 54 | +| 任务 | 成功基准 | 完成情况 | | ||
| 55 | +|---|---|---| | ||
| 56 | +| 4.2.1 软件安装 | torch_npu 可正常导入并运行 | ✅ 官方验证命令输出 `tensor(..., device='npu:0')`(软件安装/2_torch_npu导入截图.png,日志见 补充/原始日志/4.2.1_*) | | ||
| 57 | +| 4.2.2 快速入门 | 训练跑通、生成 checkpoint.pth.tar、无报错中断 | ✅ 自动迁移 + AMP,10 epochs(loss 0.34→0.02,train acc 99.4%),MNIST 测试集精度 98.1%、权重无 NaN(验证日志见 补充/原始日志/4.2.2_checkpoint验证.log) | | ||
| 58 | +| 5.2.3 表1 | Guard Filter 6 个 + 后端 7 个改写运行;1 张重编译消除 + 8 张正确性 + 8 张性能截图 | ✅ 13 脚本 + 13 日志 + 截图齐全(见 任务2/ 各文件夹;截图同时含正确性与性能两段输出,详见任务2 交付件说明) | | ||
| 59 | + | ||
| 60 | +## 任务2 实测结果(resnet18, bs=8, 910B3;07~13 为 torch 2.7.1 + torch_npu 2.7.1.post8,Guard Filter 为 torch 2.9.0 + torch_npu 2.9.0.post6) | ||
| 61 | + | ||
| 62 | +| # | 后端 | 正确性 | 稳态加速比 | 截图 | | ||
| 63 | +|---|---|---|---|---| | ||
| 64 | +| 07 | Inductor-Triton(默认) | PASS | 0.07x(慢 14 倍)⚠️ |  | | ||
| 65 | +| 08 | Inductor-MLIR | **FAIL**(max diff 9.81e-01;2.9.0 上为 NaN)⚠️ | 1.34x |  | | ||
| 66 | +| 09 | Inductor-DVM | PASS | 1.45x |  | | ||
| 67 | +| 10 | NPUGraphs | PASS | 1.43x |  | | ||
| 68 | +| 11 | NPUGraph_EX | PASS | 1.48x |  | | ||
| 69 | +| 12 | AOT_Eager | PASS | 0.64x(符合预期) |  | | ||
| 70 | +| 13 | TorchAir-GE | PASS | **2.06x(最优)** |  | | ||
| 71 | + | ||
| 72 | +MLIR 数值错误的规避方案 A/B 对比(dynamic=True 完全修复): | ||
| 73 | + | ||
| 74 | + | ||
| 75 | +Guard Filter 重编译消除(示例1,阶段A 未过滤 1→2 / 阶段B2 扩展过滤集 1→1): | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +Guard Filter 结论:示例 01/03/04/05 PASS(01/03 需扩展文档过滤集)、示例 06 | ||
| 79 | +guard 条目削减 81%、**示例 02 未消除**(PyTorch 2.9.0 C 层 GlobalStateGuard 限制, | ||
| 80 | +详见测试报告问题4)。带 ⚠️ 项为如实记录的问题反馈,非执行错误 | ||
| 81 | +(任务书:有效的问题反馈是验收通过的重要依据)。 | ||
| 82 | + | ||
| 83 | +## 已提交的社区 Issue(标题带【26.2.0众测】,共 4 个;查重关联说明见 issue提交记录.md) | ||
| 84 | + | ||
| 85 | +| # | 链接 | 主题 | | ||
| 86 | +|---|---|---| | ||
| 87 | +| 1 | https://gitcode.com/Ascend/pytorch/issues/4831 | Inductor-MLIR 整图编译数值错误(dynamic=True 可规避) | | ||
| 88 | +| 2 | https://gitcode.com/Ascend/pytorch/issues/4832 | Inductor-Triton 比 Eager 慢 25~79 倍(规避手段均无效) | | ||
| 89 | +| 3 | https://gitcode.com/Ascend/pytorch/issues/4833 | Guard Filter 无法消除运行时状态类重编译 | | ||
| 90 | +| 4 | https://gitcode.com/Ascend/pytorch/issues/5122 | PyTorch 2.9.0 Dynamo `_dynamo_dynamic_indices` 重编译崩溃 | | ||
| 91 | + | ||
| 92 | +体验问卷(平台侧)已填写。问题与改进建议详见 | ||
| 93 | +`补充-测试报告与原始日志/众测测试报告.md`(7 个问题 + 8 条建议 + 各后端汇总表)。 | ||
| 94 | + | ||
| 95 | +## 复现 | ||
| 96 | + | ||
| 97 | +见 `补充-测试报告与原始日志/复现命令清单.md`(环境搭建见同目录 `环境搭建.md`);任务2 一键运行: | ||
| 98 | +`补充-测试报告与原始日志/run_task3.sh <01~13|all>`(自动按示例切换双环境: | ||
| 99 | +01~06 用 venv PyTorch 2.9.0,07~13 用系统 PyTorch 2.7.1;并限制编译线程数, | ||
| 100 | +避免默认 32 线程在 32GB 容器内存限制下 OOM)。 | ||
| 101 | +正确性结论确定性复现,性能数字受卡负载影响小幅波动。 | ||
| @@ -0,0 +1,68 @@ | |||
| 1 | +===================================================================== | ||
| 2 | + 任务1 交付件存放指南 | ||
| 3 | +===================================================================== | ||
| 4 | + | ||
| 5 | +本指南详细说明任务1(软件安装 + 快速入门)所有交付件的存放位置、 | ||
| 6 | +文件命名和内容要求。 | ||
| 7 | + | ||
| 8 | +--------------------------------------------------------------------- | ||
| 9 | +一、文件夹结构 | ||
| 10 | +--------------------------------------------------------------------- | ||
| 11 | + | ||
| 12 | +任务1-安装与快速入门/ | ||
| 13 | +├── 交付件说明.txt ← 本文件(您正在阅读的) | ||
| 14 | +├── 链接交付件.xls ← 填写 Issue 链接和 PR 链接 | ||
| 15 | +├── 软件安装/ | ||
| 16 | +│ ├── 1_pip_list截图.png | ||
| 17 | +│ └── 2_torch_npu导入截图.png | ||
| 18 | +│ | ||
| 19 | +└── 快速入门/ | ||
| 20 | + ├── 1_训练脚本.py | ||
| 21 | + ├── 2_训练成功截图.png | ||
| 22 | + └── 3_权重文件截图.png | ||
| 23 | + | ||
| 24 | +--------------------------------------------------------------------- | ||
| 25 | +二、各交付件详细说明 | ||
| 26 | +--------------------------------------------------------------------- | ||
| 27 | + | ||
| 28 | + ★ 4.2.1 软件安装证明材料(存放于「软件安装/」文件夹) | ||
| 29 | + ----------------------------------------------------------------- | ||
| 30 | + 文件1:1_pip_list截图.png | ||
| 31 | + - 内容:终端执行 pip list 的输出截图,可见 torch 2.7.1、 | ||
| 32 | + torch_npu 2.7.1.post8、triton_ascend 3.2.2、torchvision 0.22.1 等包 | ||
| 33 | + - 要求:截图清晰,包名和版本号可读 | ||
| 34 | + | ||
| 35 | + 文件2:2_torch_npu导入截图.png | ||
| 36 | + - 内容:终端执行官方《软件安装》"安装后验证"命令的输出截图: | ||
| 37 | + python3 -c "import torch;import torch_npu; a = torch.randn(3, 4).npu(); print(a + a);" | ||
| 38 | + - 要求:输出 tensor(..., device='npu:0') 即安装成功 | ||
| 39 | + | ||
| 40 | + ★ 4.2.2 快速入门证明材料(存放于「快速入门/」文件夹) | ||
| 41 | + ----------------------------------------------------------------- | ||
| 42 | + 文件1:1_训练脚本.py | ||
| 43 | + - 内容:实际运行的训练脚本。GPU→NPU 迁移采用官方自动迁移方式 | ||
| 44 | + (import torch_npu + from torch_npu.contrib import transfer_to_npu), | ||
| 45 | + 并按文档开启 AMP 混合精度(amp.GradScaler + amp.autocast) | ||
| 46 | + - 要求:脚本可独立运行(python3 1_训练脚本.py),MNIST 数据集 | ||
| 47 | + 自动下载,训练完成后在当前目录生成 checkpoint.pth.tar | ||
| 48 | + - 对照:迁移前原始 GPU 脚本见 | ||
| 49 | + 补充-测试报告与原始日志/对照脚本/train_gpu.py | ||
| 50 | + | ||
| 51 | + 文件2:2_训练成功截图.png | ||
| 52 | + - 内容:训练脚本正常执行完毕的终端截图 | ||
| 53 | + - 要求:可见 10 个 epoch 的 loss/acc 打印(loss 0.34→0.02、 | ||
| 54 | + train acc 约 99.4%)、checkpoint saved、training finished | ||
| 55 | + successfully,无报错中断 | ||
| 56 | + | ||
| 57 | + 文件3:3_权重文件截图.png | ||
| 58 | + - 内容:生成的 checkpoint.pth.tar 权重文件的截图 | ||
| 59 | + - 要求:可通过 ls -lh checkpoint.pth.tar 展示文件存在及大小(约 122K) | ||
| 60 | + - 注意:权重文件本身未随仓库提交,只需截图证明已生成。 | ||
| 61 | + 其生成与验证(MNIST 测试集精度 98.1%、权重无 NaN)的文本证据见 | ||
| 62 | + 补充-测试报告与原始日志/原始日志/4.2.2_checkpoint验证.log | ||
| 63 | + | ||
| 64 | +===================================================================== | ||
| 65 | +环境:Atlas 800T A2(Ascend 910B3, 64GB)/ CANN 9.1.0 / | ||
| 66 | + TorchNPU 2.7.1.post8(26.1.0 配套)/ PyTorch 2.7.1 / Python 3.11 | ||
| 67 | +环境初始化:source 补充-测试报告与原始日志/env_setup.sh | ||
| 68 | +===================================================================== | ||
| @@ -0,0 +1,103 @@ | |||
| 1 | +"""快速入门:模型迁移训练(任务书 4.2.2)。 | ||
| 2 | + | ||
| 3 | +基于《快速入门》文档的 CNN 手写数字识别案例,从 GPU 脚本迁移到昇腾 NPU: | ||
| 4 | +1. 增加自动迁移:import torch_npu + from torch_npu.contrib import transfer_to_npu | ||
| 5 | +2. 开启 AMP 混合精度:amp.GradScaler + amp.autocast | ||
| 6 | +原始 GPU 脚本见 train_gpu.py,差异可用 diff train_gpu.py train.py 查看。 | ||
| 7 | +""" | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torch.nn as nn | ||
| 12 | +from torch.utils.data import Dataset, DataLoader | ||
| 13 | +import torchvision | ||
| 14 | + | ||
| 15 | +import torch_npu | ||
| 16 | +from torch_npu.contrib import transfer_to_npu # 开启自动迁移 | ||
| 17 | +from torch_npu.npu import amp # 导入AMP模块 | ||
| 18 | + | ||
| 19 | +# 初始化运行device(transfer_to_npu 已自动将 cuda:0 映射到 NPU) | ||
| 20 | +device = torch.device('cuda:0') | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +# 定义模型网络 | ||
| 24 | +class CNN(nn.Module): | ||
| 25 | + def __init__(self): | ||
| 26 | + super(CNN, self).__init__() | ||
| 27 | + self.net = nn.Sequential( | ||
| 28 | + # 卷积层 | ||
| 29 | + nn.Conv2d(in_channels=1, out_channels=16, | ||
| 30 | + kernel_size=(3, 3), | ||
| 31 | + stride=(1, 1), | ||
| 32 | + padding=1), | ||
| 33 | + # 池化层 | ||
| 34 | + nn.MaxPool2d(kernel_size=2), | ||
| 35 | + # 卷积层 | ||
| 36 | + nn.Conv2d(16, 32, 3, 1, 1), | ||
| 37 | + # 池化层 | ||
| 38 | + nn.MaxPool2d(2), | ||
| 39 | + # 将多维输入一维化 | ||
| 40 | + nn.Flatten(), | ||
| 41 | + nn.Linear(32*7*7, 16), | ||
| 42 | + # 激活函数 | ||
| 43 | + nn.ReLU(), | ||
| 44 | + nn.Linear(16, 10) | ||
| 45 | + ) | ||
| 46 | + | ||
| 47 | + def forward(self, x): | ||
| 48 | + return self.net(x) | ||
| 49 | + | ||
| 50 | + | ||
| 51 | +# 下载数据集 | ||
| 52 | +train_data = torchvision.datasets.MNIST( | ||
| 53 | + root='mnist', | ||
| 54 | + download=True, | ||
| 55 | + train=True, | ||
| 56 | + transform=torchvision.transforms.ToTensor() | ||
| 57 | +) | ||
| 58 | + | ||
| 59 | +# 定义训练相关参数 | ||
| 60 | +batch_size = 64 | ||
| 61 | +model = CNN().to(device) # 定义模型 | ||
| 62 | +train_dataloader = DataLoader(train_data, batch_size=batch_size) # 定义DataLoader | ||
| 63 | +loss_func = nn.CrossEntropyLoss().to(device) # 定义损失函数 | ||
| 64 | +optimizer = torch.optim.SGD(model.parameters(), lr=0.1) # 定义优化器 | ||
| 65 | +scaler = amp.GradScaler() # 在模型、优化器定义之后,定义GradScaler | ||
| 66 | +epochs = 10 # 设置循环次数 | ||
| 67 | + | ||
| 68 | +print("device:", device, "| torch_npu:", torch_npu.__version__) | ||
| 69 | +print("start training: epochs =", epochs, ", batch_size =", batch_size, | ||
| 70 | + ", iters/epoch =", len(train_dataloader)) | ||
| 71 | + | ||
| 72 | +# 设置循环 | ||
| 73 | +for epoch in range(epochs): | ||
| 74 | + epoch_loss = 0.0 | ||
| 75 | + correct = 0 | ||
| 76 | + total = 0 | ||
| 77 | + epoch_start = time.time() | ||
| 78 | + for imgs, labels in train_dataloader: | ||
| 79 | + imgs = imgs.to(device) # 把img数据放到指定NPU上 | ||
| 80 | + labels = labels.to(device) # 把label数据放到指定NPU上 | ||
| 81 | + with amp.autocast(): | ||
| 82 | + outputs = model(imgs) # 前向计算 | ||
| 83 | + loss = loss_func(outputs, labels) # 损失函数计算 | ||
| 84 | + optimizer.zero_grad() | ||
| 85 | + # 进行反向传播前后的loss缩放、参数更新 | ||
| 86 | + scaler.scale(loss).backward() # loss缩放并反向传播 | ||
| 87 | + scaler.step(optimizer) # 更新参数(自动unscaling) | ||
| 88 | + scaler.update() # 基于动态Loss Scale更新loss_scaling系数 | ||
| 89 | + epoch_loss += loss.item() | ||
| 90 | + correct += (outputs.argmax(dim=1) == labels).sum().item() | ||
| 91 | + total += labels.size(0) | ||
| 92 | + print(f"epoch {epoch + 1}/{epochs} | loss {epoch_loss / len(train_dataloader):.4f} " | ||
| 93 | + f"| train acc {correct / total:.4f} | time {time.time() - epoch_start:.2f}s") | ||
| 94 | + | ||
| 95 | +# 定义保存模型 | ||
| 96 | +torch.save({ | ||
| 97 | + 'epoch': 10, | ||
| 98 | + 'arch': CNN, | ||
| 99 | + 'state_dict': model.state_dict(), | ||
| 100 | + 'optimizer': optimizer.state_dict(), | ||
| 101 | +}, 'checkpoint.pth.tar') | ||
| 102 | +print("checkpoint saved: checkpoint.pth.tar") | ||
| 103 | +print("training finished successfully") | ||
Binary files do not support preview
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/AOT_Eager/0_公共模块tc_common.py+94-0
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
| @@ -0,0 +1,22 @@ | |||
| 1 | +"""任务3 示例12:AOT_Eager 后端。 | ||
| 2 | + | ||
| 3 | +对应《AOT_Eager》文档调用示例,模型替换为 TorchBench 清单中的 resnet18。 | ||
| 4 | +核心:backend="aot_eager"(仅验证图捕获正确性,不做优化,作为基准对照)。 | ||
| 5 | +""" | ||
| 6 | +import torch | ||
| 7 | +import torch_npu | ||
| 8 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 9 | + | ||
| 10 | +print("=" * 70) | ||
| 11 | +print("示例12:AOT_Eager 后端") | ||
| 12 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 13 | +print("=" * 70) | ||
| 14 | + | ||
| 15 | +model = build_model() | ||
| 16 | +x = build_input() | ||
| 17 | + | ||
| 18 | +compiled = torch.compile(model, backend="aot_eager") | ||
| 19 | + | ||
| 20 | +ok, diff = correctness_check(model, compiled, x, "示例12-aot_eager") | ||
| 21 | +perf_compare(model, compiled, x, "示例12-aot_eager") | ||
| 22 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/AOT_Eager/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/AOT_Eager/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
| @@ -0,0 +1,150 @@ | |||
| 1 | +"""任务3 示例01:Guard Filter 示例1 —— 过滤字典版本 guard。 | ||
| 2 | + | ||
| 3 | +对应《Guard Filter》文档示例1(DICT_VERSION/DICT_KEYS/DICT_KEYS_MATCH/DICT_CONTAINS), | ||
| 4 | +模型替换为 TorchBench 清单中的 resnet18。通过 KV-cache 式 dict 参数内 mask 值 | ||
| 5 | +None ↔ 全1 tensor 切换(两者语义等价,模拟 prefill/decode 交替)触发重编译。 | ||
| 6 | + | ||
| 7 | +实测发现(已作为问题反馈): | ||
| 8 | +- PyTorch 2.9.0 中 dict 读取会被常量特化,实际失效的 guard 是 dict 内值的 | ||
| 9 | + None 检查(guard_type=CONSTANT_MATCH, name=attn['mask']),不在文档示例1 | ||
| 10 | + 的 DICT_* 过滤集内,因此仅用该过滤集无法消除重编译; | ||
| 11 | +- 将过滤集扩展为「DICT_* + 按名丢弃 None 检查」后重编译消除成功。 | ||
| 12 | +""" | ||
| 13 | +import torch | ||
| 14 | +import torch_npu | ||
| 15 | +import torch.nn as nn | ||
| 16 | +from tc_common import (build_input, correctness_check, perf_compare, | ||
| 17 | + count_graphs, reset_dynamo) | ||
| 18 | +from tc_common import resnet18 | ||
| 19 | + | ||
| 20 | +_DICT_GUARD_TYPES = frozenset({ | ||
| 21 | + "DICT_VERSION", "DICT_KEYS", "DICT_KEYS_MATCH", "DICT_CONTAINS", | ||
| 22 | +}) | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +def filter_dict_guards(entries): | ||
| 26 | + """文档示例1的过滤函数(仅过滤 DICT_* 类 guard)。""" | ||
| 27 | + return [entry.guard_type not in _DICT_GUARD_TYPES for entry in entries] | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +def filter_dict_guards_extended(entries): | ||
| 31 | + """扩展过滤:DICT_* + 按名丢弃 dict 内值的 None 检查(CONSTANT_MATCH)。""" | ||
| 32 | + keep = [] | ||
| 33 | + for entry in entries: | ||
| 34 | + if entry.guard_type in _DICT_GUARD_TYPES: | ||
| 35 | + keep.append(False) | ||
| 36 | + elif entry.guard_type == "CONSTANT_MATCH" and entry.name == "attn['mask']" and entry.value is None: | ||
| 37 | + keep.append(False) | ||
| 38 | + else: | ||
| 39 | + keep.append(True) | ||
| 40 | + return keep | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +class ResNet18WithAttnDict(nn.Module): | ||
| 44 | + """resnet18 + KV-cache 式 dict 参数(attn['mask'] None ↔ 全1 mask 语义等价)。""" | ||
| 45 | + | ||
| 46 | + def __init__(self, backbone): | ||
| 47 | + super().__init__() | ||
| 48 | + self.backbone = backbone | ||
| 49 | + | ||
| 50 | + def forward(self, x, attn): | ||
| 51 | + out = self.backbone(x) | ||
| 52 | + mask = attn["mask"] # 模拟 HF 模型从 dict 读取 attention_mask | ||
| 53 | + if mask is not None: | ||
| 54 | + out = out * mask # 全1 mask 与 None 语义等价 | ||
| 55 | + return out | ||
| 56 | + | ||
| 57 | + | ||
| 58 | +torch.manual_seed(42) | ||
| 59 | +model = ResNet18WithAttnDict(resnet18()).npu().eval() | ||
| 60 | +x = build_input() | ||
| 61 | +ones = torch.ones(x.size(0), 1000, device="npu") | ||
| 62 | + | ||
| 63 | +print("=" * 70) | ||
| 64 | +print("示例01:过滤字典版本 guard(DICT_*)") | ||
| 65 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 66 | +print("=" * 70) | ||
| 67 | + | ||
| 68 | +# ---------- 阶段A:不开启 Guard Filter(对照) ---------- | ||
| 69 | +reset_dynamo() | ||
| 70 | +attn = {"mask": None} | ||
| 71 | +compiled_plain = torch.compile(model) | ||
| 72 | +with torch.no_grad(): | ||
| 73 | + compiled_plain(x, attn) | ||
| 74 | +g1 = count_graphs() | ||
| 75 | +attn["mask"] = ones # dict 内值变化(version bump + None→tensor) | ||
| 76 | +with torch.no_grad(): | ||
| 77 | + compiled_plain(x, attn) | ||
| 78 | +g2 = count_graphs() | ||
| 79 | +print(f"\n[阶段A|未过滤] 首次调用后图数: {g1},dict值切换后再调用后图数: {g2}") | ||
| 80 | +print(f"[阶段A|未过滤] 重编译: {'发生' if g2 > g1 else '未发生'}") | ||
| 81 | + | ||
| 82 | +# ---------- 阶段B:文档示例1的 DICT_* 过滤集 ---------- | ||
| 83 | +reset_dynamo() | ||
| 84 | +attn = {"mask": None} | ||
| 85 | +compiled_doc = torch.compile(model, options={"guard_filter_fn": filter_dict_guards}) | ||
| 86 | +with torch.no_grad(): | ||
| 87 | + compiled_doc(x, attn) | ||
| 88 | +g3 = count_graphs() | ||
| 89 | +attn["mask"] = ones | ||
| 90 | +with torch.no_grad(): | ||
| 91 | + compiled_doc(x, attn) | ||
| 92 | +g4 = count_graphs() | ||
| 93 | +print(f"\n[阶段B|文档DICT_*过滤集] 首次调用后图数: {g3},dict值切换后图数: {g4}") | ||
| 94 | +print(f"[阶段B|文档DICT_*过滤集] 重编译: {'已消除' if g4 == g3 else '仍发生 (实际失效guard为 dict内值的None检查, 类型CONSTANT_MATCH, 不在DICT_*集内)'}") | ||
| 95 | + | ||
| 96 | +# ---------- 阶段B2:扩展过滤集(DICT_* + 按名丢弃None检查) ---------- | ||
| 97 | +reset_dynamo() | ||
| 98 | +attn = {"mask": None} | ||
| 99 | +compiled_ext = torch.compile(model, options={"guard_filter_fn": filter_dict_guards_extended}) | ||
| 100 | +with torch.no_grad(): | ||
| 101 | + ref = compiled_ext(x, attn) | ||
| 102 | +g5 = count_graphs() | ||
| 103 | +attn["mask"] = ones | ||
| 104 | +with torch.no_grad(): | ||
| 105 | + out_ext = compiled_ext(x, attn) | ||
| 106 | +g6 = count_graphs() | ||
| 107 | +same = torch.allclose(ref.cpu(), out_ext.cpu(), atol=1e-4) | ||
| 108 | +print(f"\n[阶段B2|扩展过滤集] 首次调用后图数: {g5},dict值切换后图数: {g6}") | ||
| 109 | +print(f"[阶段B2|扩展过滤集] 重编译: {'已消除' if g6 == g5 else '仍发生'},输出一致: {same}") | ||
| 110 | +print(f"[结论] Guard Filter 重编译消除: {'PASS' if (g2 > g1 and g6 == g5 and same) else 'FAIL'}") | ||
| 111 | + | ||
| 112 | +# ---------- 阶段C:fail_on_recompile 严格验证 ---------- | ||
| 113 | +try: | ||
| 114 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 115 | + attn["mask"] = None | ||
| 116 | + with torch.no_grad(): | ||
| 117 | + compiled_ext(x, attn) | ||
| 118 | + print("[阶段C|fail_on_recompile] 未抛异常,重编译确实已消除: PASS") | ||
| 119 | +except Exception as exc: | ||
| 120 | + print(f"[阶段C|fail_on_recompile] 抛出异常: {type(exc).__name__}: FAIL") | ||
| 121 | + | ||
| 122 | +# ---------- 正确性 & 性能 ---------- | ||
| 123 | +import time # noqa: E402 | ||
| 124 | + | ||
| 125 | +with torch.no_grad(): | ||
| 126 | + eager_out = model(x, {"mask": None}) | ||
| 127 | + compiled_out = compiled_ext(x, {"mask": None}) | ||
| 128 | +diff = float((eager_out.cpu() - compiled_out.cpu()).abs().max()) | ||
| 129 | +ok = torch.allclose(eager_out.cpu(), compiled_out.cpu(), atol=1e-3) | ||
| 130 | +print(f"\n[正确性|示例01-guard_filter_dict] eager vs compiled 最大差异: {diff:.3e} (allclose: {ok})") | ||
| 131 | + | ||
| 132 | +with torch.no_grad(): | ||
| 133 | + start = time.time(); model(x, {"mask": None}); eager_time = time.time() - start | ||
| 134 | + start = time.time(); compiled_ext(x, {"mask": None}); compile_time = time.time() - start | ||
| 135 | + for _ in range(3): | ||
| 136 | + model(x, {"mask": None}); compiled_ext(x, {"mask": None}) | ||
| 137 | + torch.npu.synchronize() | ||
| 138 | + start = time.time() | ||
| 139 | + for _ in range(20): | ||
| 140 | + model(x, {"mask": None}) | ||
| 141 | + torch.npu.synchronize() | ||
| 142 | + eager_avg = (time.time() - start) / 20 | ||
| 143 | + start = time.time() | ||
| 144 | + for _ in range(20): | ||
| 145 | + compiled_ext(x, {"mask": None}) | ||
| 146 | + torch.npu.synchronize() | ||
| 147 | + compiled_avg = (time.time() - start) / 20 | ||
| 148 | +print(f"[性能|示例01-guard_filter_dict] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 149 | +print(f"[性能|示例01-guard_filter_dict] 稳态均值(20次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s, 加速比: {eager_avg / compiled_avg:.2f}x") | ||
| 150 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
| @@ -0,0 +1,85 @@ | |||
| 1 | +"""任务3 示例02:Guard Filter 示例2 —— 过滤运行时状态 guard。 | ||
| 2 | + | ||
| 3 | +对应《Guard Filter》文档示例2(GRAD_MODE/TORCH_FUNCTION_STATE/GLOBAL_STATE/ | ||
| 4 | +DEFAULT_DEVICE/DETERMINISTIC_ALGORITHMS/AUTOCAST_STATE/FSDP_TRAINING_STATE), | ||
| 5 | +模型替换为 TorchBench 清单中的 resnet18。 | ||
| 6 | + | ||
| 7 | +实测发现(重要,已作为问题反馈): | ||
| 8 | +- PyTorch 2.9.0 中运行时状态(grad_mode、deterministic_algorithms 等)的实际 | ||
| 9 | + 校验走 C 层 GlobalStateGuard(见 torch/_dynamo/guards.py:2433 注释 "we always | ||
| 10 | + guard on this via GlobalStateGuard()"),该检查不经过 guard_filter_fn 通道, | ||
| 11 | + 因此文档示例2的过滤集无法消除此类重编译; | ||
| 12 | +- 重编译日志显示失效 guard 为 "GLOBAL_STATE changed: deterministic_algorithms", | ||
| 13 | + 证明是 C 层检查触发,而非 Python 层 guard 条目; | ||
| 14 | +- 本示例使用 deterministic_algorithms 开关作为触发器(与 grad_mode 同属 | ||
| 15 | + GLOBAL_STATE 家族,且避免 enable_grad 触发反向图编译的额外内存开销)。 | ||
| 16 | +""" | ||
| 17 | +import torch | ||
| 18 | +import torch_npu | ||
| 19 | +from tc_common import (build_model, build_input, correctness_check, | ||
| 20 | + perf_compare, count_graphs, reset_dynamo) | ||
| 21 | + | ||
| 22 | +_RUNTIME_STATE_GUARD_TYPES = frozenset({ | ||
| 23 | + "GRAD_MODE", "TORCH_FUNCTION_STATE", "GLOBAL_STATE", | ||
| 24 | + "DEFAULT_DEVICE", "DETERMINISTIC_ALGORITHMS", "AUTOCAST_STATE", | ||
| 25 | + "FSDP_TRAINING_STATE", | ||
| 26 | +}) | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +def filter_runtime_state_guards(entries): | ||
| 30 | + return [entry.guard_type not in _RUNTIME_STATE_GUARD_TYPES for entry in entries] | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +model = build_model() | ||
| 34 | +x = build_input() | ||
| 35 | + | ||
| 36 | +print("=" * 70) | ||
| 37 | +print("示例02:过滤运行时状态 guard(GLOBAL_STATE 家族)") | ||
| 38 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 39 | +print("触发器: torch.use_deterministic_algorithms 开关翻转") | ||
| 40 | +print("=" * 70) | ||
| 41 | + | ||
| 42 | +# ---------- 阶段A:不开启 Guard Filter(对照) ---------- | ||
| 43 | +reset_dynamo() | ||
| 44 | +compiled_plain = torch.compile(model) | ||
| 45 | +with torch.no_grad(): | ||
| 46 | + compiled_plain(x) | ||
| 47 | +g1 = count_graphs() | ||
| 48 | +torch.use_deterministic_algorithms(True) # 翻转运行时状态 | ||
| 49 | +with torch.no_grad(): | ||
| 50 | + compiled_plain(x) | ||
| 51 | +g2 = count_graphs() | ||
| 52 | +torch.use_deterministic_algorithms(False) | ||
| 53 | +print(f"\n[阶段A|未过滤] 首次调用后图数: {g1},状态翻转后图数: {g2}") | ||
| 54 | +print(f"[阶段A|未过滤] 重编译: {'发生 (GLOBAL_STATE guard 失效)' if g2 > g1 else '未发生'}") | ||
| 55 | + | ||
| 56 | +# ---------- 阶段B:开启文档示例2的运行时状态过滤集 ---------- | ||
| 57 | +reset_dynamo() | ||
| 58 | +compiled_filtered = torch.compile(model, options={"guard_filter_fn": filter_runtime_state_guards}) | ||
| 59 | +with torch.no_grad(): | ||
| 60 | + compiled_filtered(x) | ||
| 61 | +g3 = count_graphs() | ||
| 62 | +torch.use_deterministic_algorithms(True) | ||
| 63 | +with torch.no_grad(): | ||
| 64 | + compiled_filtered(x) | ||
| 65 | +g4 = count_graphs() | ||
| 66 | +torch.use_deterministic_algorithms(False) | ||
| 67 | +print(f"\n[阶段B|已过滤] 首次调用后图数: {g3},状态翻转后图数: {g4}") | ||
| 68 | +print(f"[阶段B|已过滤] 重编译: {'已消除' if g4 == g3 else '仍发生 (C 层 GlobalStateGuard 不经 guard_filter_fn 通道, 见脚本 docstring)'}") | ||
| 69 | +print(f"[结论] 运行时状态重编译消除: {'PASS' if (g2 > g1 and g4 == g3) else 'FAIL (PyTorch 2.9.0 限制, 已作为问题反馈)'}") | ||
| 70 | + | ||
| 71 | +# ---------- 阶段C:fail_on_recompile 严格验证(预期抛异常佐证 C 层检查) ---------- | ||
| 72 | +try: | ||
| 73 | + torch.use_deterministic_algorithms(True) | ||
| 74 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 75 | + with torch.no_grad(): | ||
| 76 | + compiled_filtered(x) | ||
| 77 | + torch.use_deterministic_algorithms(False) | ||
| 78 | + print("[阶段C|fail_on_recompile] 未抛异常: PASS") | ||
| 79 | +except Exception as exc: | ||
| 80 | + torch.use_deterministic_algorithms(False) | ||
| 81 | + print(f"[阶段C|fail_on_recompile] 抛出异常 {type(exc).__name__}: 佐证重编译由 C 层 GlobalStateGuard 触发, guard_filter_fn 无法拦截") | ||
| 82 | + | ||
| 83 | +# ---------- 正确性 & 性能 ---------- | ||
| 84 | +ok, diff = correctness_check(model, compiled_filtered, x, "示例02-guard_filter_runtime_state") | ||
| 85 | +perf_compare(model, compiled_filtered, x, "示例02-guard_filter_runtime_state") | ||
| @@ -0,0 +1,151 @@ | |||
| 1 | +"""任务3 示例03:Guard Filter 示例3 —— 组合多类 guard 过滤。 | ||
| 2 | + | ||
| 3 | +对应《Guard Filter》文档示例3(字典版本 + 可选类型 + hasattr + 运行时状态 | ||
| 4 | +一次性过滤),模型替换为 TorchBench 清单中的 resnet18。通过可选参数 mask | ||
| 5 | +None ↔ 全1 tensor 切换(语义等价,模拟 attention_mask prefill/decode 交替) | ||
| 6 | +触发重编译。 | ||
| 7 | + | ||
| 8 | +实测发现(已作为问题反馈): | ||
| 9 | +- 失效 guard 为 mask=None 的 None 检查(guard_type=CONSTANT_MATCH, name=mask), | ||
| 10 | + 不在文档示例3的组合过滤集内,因此该过滤集单独使用无法消除重编译; | ||
| 11 | +- 将组合集扩展为「组合集 + 按名丢弃 mask=None 检查」后重编译消除成功。 | ||
| 12 | +""" | ||
| 13 | +import torch | ||
| 14 | +import torch_npu | ||
| 15 | +import torch.nn as nn | ||
| 16 | +from tc_common import (build_input, count_graphs, reset_dynamo) | ||
| 17 | +from tc_common import resnet18 | ||
| 18 | + | ||
| 19 | +_FILTER_GUARD_TYPES = frozenset({ | ||
| 20 | + # 字典版本 | ||
| 21 | + "DICT_VERSION", "DICT_KEYS", "DICT_KEYS_MATCH", "DICT_CONTAINS", | ||
| 22 | + # 可选类型 | ||
| 23 | + "TYPE_MATCH", "OPTIONAL_TENSOR", | ||
| 24 | + # hasattr | ||
| 25 | + "HASATTR", "NOT_PRESENT_IN_GENERIC_DICT", | ||
| 26 | + # 运行时状态 | ||
| 27 | + "GRAD_MODE", "TORCH_FUNCTION_STATE", "GLOBAL_STATE", | ||
| 28 | + "DEFAULT_DEVICE", "DETERMINISTIC_ALGORITHMS", "AUTOCAST_STATE", | ||
| 29 | + "FSDP_TRAINING_STATE", | ||
| 30 | +}) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_guard_filter(entries): | ||
| 34 | + """文档示例3的组合过滤函数。""" | ||
| 35 | + return [entry.guard_type not in _FILTER_GUARD_TYPES for entry in entries] | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +def npu_guard_filter_extended(entries): | ||
| 39 | + """扩展组合过滤:组合集 + 按名丢弃 mask=None 的 None 检查(CONSTANT_MATCH)。""" | ||
| 40 | + keep = [] | ||
| 41 | + for entry in entries: | ||
| 42 | + if entry.guard_type in _FILTER_GUARD_TYPES: | ||
| 43 | + keep.append(False) | ||
| 44 | + elif entry.guard_type == "CONSTANT_MATCH" and entry.name == "mask" and entry.value is None: | ||
| 45 | + keep.append(False) | ||
| 46 | + else: | ||
| 47 | + keep.append(True) | ||
| 48 | + return keep | ||
| 49 | + | ||
| 50 | + | ||
| 51 | +class ResNet18WithOptionalMask(nn.Module): | ||
| 52 | + """resnet18 + 可选 mask 参数(None 与全1 mask 语义等价)。""" | ||
| 53 | + | ||
| 54 | + def __init__(self, backbone): | ||
| 55 | + super().__init__() | ||
| 56 | + self.backbone = backbone | ||
| 57 | + | ||
| 58 | + def forward(self, x, mask=None): | ||
| 59 | + out = self.backbone(x) | ||
| 60 | + if mask is not None: | ||
| 61 | + out = out * mask # 全1 mask 与 None 语义等价 | ||
| 62 | + return out | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +torch.manual_seed(42) | ||
| 66 | +model = ResNet18WithOptionalMask(resnet18()).npu().eval() | ||
| 67 | +x = build_input() | ||
| 68 | +ones = torch.ones(x.size(0), 1000, device="npu") | ||
| 69 | + | ||
| 70 | +print("=" * 70) | ||
| 71 | +print("示例03:组合多类 guard 过滤") | ||
| 72 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 73 | +print("=" * 70) | ||
| 74 | + | ||
| 75 | +# ---------- 阶段A:不开启 Guard Filter(对照) ---------- | ||
| 76 | +reset_dynamo() | ||
| 77 | +compiled_plain = torch.compile(model) | ||
| 78 | +with torch.no_grad(): | ||
| 79 | + compiled_plain(x) | ||
| 80 | +g1 = count_graphs() | ||
| 81 | +with torch.no_grad(): | ||
| 82 | + compiled_plain(x, ones) # mask None → 全1 tensor | ||
| 83 | +g2 = count_graphs() | ||
| 84 | +print(f"\n[阶段A|未过滤] 首次调用后图数: {g1},mask切换后图数: {g2}") | ||
| 85 | +print(f"[阶段A|未过滤] 重编译: {'发生' if g2 > g1 else '未发生'}") | ||
| 86 | + | ||
| 87 | +# ---------- 阶段B:文档示例3的组合过滤集 ---------- | ||
| 88 | +reset_dynamo() | ||
| 89 | +compiled_doc = torch.compile(model, options={"guard_filter_fn": npu_guard_filter}) | ||
| 90 | +with torch.no_grad(): | ||
| 91 | + compiled_doc(x) | ||
| 92 | +g3 = count_graphs() | ||
| 93 | +with torch.no_grad(): | ||
| 94 | + compiled_doc(x, ones) | ||
| 95 | +g4 = count_graphs() | ||
| 96 | +print(f"\n[阶段B|文档组合过滤集] 首次调用后图数: {g3},mask切换后图数: {g4}") | ||
| 97 | +print(f"[阶段B|文档组合过滤集] 重编译: {'已消除' if g4 == g3 else '仍发生 (失效guard为 mask=None 的 CONSTANT_MATCH, 不在组合集内)'}") | ||
| 98 | + | ||
| 99 | +# ---------- 阶段B2:扩展组合过滤集 ---------- | ||
| 100 | +reset_dynamo() | ||
| 101 | +compiled_ext = torch.compile(model, options={"guard_filter_fn": npu_guard_filter_extended}) | ||
| 102 | +with torch.no_grad(): | ||
| 103 | + ref = compiled_ext(x) | ||
| 104 | +g5 = count_graphs() | ||
| 105 | +with torch.no_grad(): | ||
| 106 | + out_ext = compiled_ext(x, ones) | ||
| 107 | +g6 = count_graphs() | ||
| 108 | +same = torch.allclose(ref.cpu(), out_ext.cpu(), atol=1e-4) | ||
| 109 | +print(f"\n[阶段B2|扩展组合过滤集] 首次调用后图数: {g5},mask切换后图数: {g6}") | ||
| 110 | +print(f"[阶段B2|扩展组合过滤集] 重编译: {'已消除' if g6 == g5 else '仍发生'},输出一致: {same}") | ||
| 111 | +print(f"[结论] 组合过滤重编译消除: {'PASS' if (g2 > g1 and g6 == g5 and same) else 'FAIL'}") | ||
| 112 | + | ||
| 113 | +# ---------- 阶段C:fail_on_recompile 严格验证 ---------- | ||
| 114 | +try: | ||
| 115 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 116 | + with torch.no_grad(): | ||
| 117 | + compiled_ext(x) # mask=None | ||
| 118 | + compiled_ext(x, ones) # mask=全1 | ||
| 119 | + print("[阶段C|fail_on_recompile] 未抛异常: PASS") | ||
| 120 | +except Exception as exc: | ||
| 121 | + print(f"[阶段C|fail_on_recompile] 抛出异常: {type(exc).__name__}: FAIL") | ||
| 122 | + | ||
| 123 | +# ---------- 正确性 & 性能 ---------- | ||
| 124 | +import time # noqa: E402 | ||
| 125 | + | ||
| 126 | +with torch.no_grad(): | ||
| 127 | + eager_out = model(x) | ||
| 128 | + compiled_out = compiled_ext(x) | ||
| 129 | +diff = float((eager_out.cpu() - compiled_out.cpu()).abs().max()) | ||
| 130 | +ok = torch.allclose(eager_out.cpu(), compiled_out.cpu(), atol=1e-3) | ||
| 131 | +print(f"\n[正确性|示例03-guard_filter_combined] eager vs compiled 最大差异: {diff:.3e} (allclose: {ok})") | ||
| 132 | + | ||
| 133 | +with torch.no_grad(): | ||
| 134 | + start = time.time(); model(x); eager_time = time.time() - start | ||
| 135 | + start = time.time(); compiled_ext(x); compile_time = time.time() - start | ||
| 136 | + for _ in range(3): | ||
| 137 | + model(x); compiled_ext(x) | ||
| 138 | + torch.npu.synchronize() | ||
| 139 | + start = time.time() | ||
| 140 | + for _ in range(20): | ||
| 141 | + model(x) | ||
| 142 | + torch.npu.synchronize() | ||
| 143 | + eager_avg = (time.time() - start) / 20 | ||
| 144 | + start = time.time() | ||
| 145 | + for _ in range(20): | ||
| 146 | + compiled_ext(x) | ||
| 147 | + torch.npu.synchronize() | ||
| 148 | + compiled_avg = (time.time() - start) / 20 | ||
| 149 | +print(f"[性能|示例03-guard_filter_combined] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 150 | +print(f"[性能|示例03-guard_filter_combined] 稳态均值(20次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s, 加速比: {eager_avg / compiled_avg:.2f}x") | ||
| 151 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Guard Filter/4_按变量名过滤.py+106-0
| @@ -0,0 +1,106 @@ | |||
| 1 | +"""任务3 示例04:Guard Filter 示例4 —— 按变量名或属性过滤。 | ||
| 2 | + | ||
| 3 | +对应《Guard Filter》文档示例4(按变量名精确过滤,entry.name 匹配 + value | ||
| 4 | +为 None 时丢弃)。文档原示例是对函数使用 @torch.compile 装饰器,此处将 | ||
| 5 | +模型替换为 TorchBench 清单中的 resnet18 的前向包装:f(x, mask=None), | ||
| 6 | +mask 为可选 attention mask(None 与全1 mask 语义等价),模拟 prefill | ||
| 7 | +(mask=None)与 decode(mask=tensor)交替场景。 | ||
| 8 | +""" | ||
| 9 | +import torch | ||
| 10 | +import torch_npu | ||
| 11 | +import torch.nn as nn | ||
| 12 | +from tc_common import build_input, count_graphs, reset_dynamo | ||
| 13 | +from tc_common import resnet18 | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +def filter_by_name(entries): | ||
| 17 | + return [not (entry.name == "mask" and entry.value is None) for entry in entries] | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +class ResNet18WithOptionalMask(nn.Module): | ||
| 21 | + def __init__(self, backbone): | ||
| 22 | + super().__init__() | ||
| 23 | + self.backbone = backbone | ||
| 24 | + | ||
| 25 | + def forward(self, x, mask=None): | ||
| 26 | + out = self.backbone(x) | ||
| 27 | + if mask is not None: | ||
| 28 | + out = out * mask # 全1 mask 与 None 语义等价 | ||
| 29 | + return out | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +torch.manual_seed(42) | ||
| 33 | +model = ResNet18WithOptionalMask(resnet18()).npu().eval() | ||
| 34 | +x = build_input() | ||
| 35 | +ones = torch.ones(x.size(0), 1000, device="npu") | ||
| 36 | + | ||
| 37 | +print("=" * 70) | ||
| 38 | +print("示例04:按变量名/属性过滤(mask=None 交替场景)") | ||
| 39 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 40 | +print("=" * 70) | ||
| 41 | + | ||
| 42 | +# ---------- 阶段A:不开启 Guard Filter(对照):None -> tensor 触发重编译 ---------- | ||
| 43 | +reset_dynamo() | ||
| 44 | +compiled_plain = torch.compile(model) | ||
| 45 | +with torch.no_grad(): | ||
| 46 | + compiled_plain(x) # 先以 mask=None 编译 | ||
| 47 | +g1 = count_graphs() | ||
| 48 | +with torch.no_grad(): | ||
| 49 | + compiled_plain(x, ones) # 切到 mask=全1 | ||
| 50 | +g2 = count_graphs() | ||
| 51 | +print(f"\n[阶段A|未过滤] mask=None后图数: {g1},mask=tensor后图数: {g2}") | ||
| 52 | +print(f"[阶段A|未过滤] 类型交替触发重编译: {'发生' if g2 > g1 else '未发生'}") | ||
| 53 | + | ||
| 54 | +# ---------- 阶段B:按名称过滤 ---------- | ||
| 55 | +reset_dynamo() | ||
| 56 | +compiled_filtered = torch.compile(model, options={"guard_filter_fn": filter_by_name}) | ||
| 57 | +with torch.no_grad(): | ||
| 58 | + ref = compiled_filtered(x) # mask=None 编译 | ||
| 59 | +g3 = count_graphs() | ||
| 60 | +with torch.no_grad(): | ||
| 61 | + out = compiled_filtered(x, ones) # mask=全1 → 不应重编译 | ||
| 62 | +g4 = count_graphs() | ||
| 63 | +same = torch.allclose(ref.cpu(), out.cpu(), atol=1e-4) | ||
| 64 | +print(f"\n[阶段B|按名过滤] mask=None后图数: {g3},mask=tensor后图数: {g4}") | ||
| 65 | +print(f"[阶段B|按名过滤] 类型交替重编译: {'已消除' if g4 == g3 else '仍发生'},输出一致: {same}") | ||
| 66 | +print(f"[结论] 按名过滤重编译消除: {'PASS' if (g2 > g1 and g4 == g3 and same) else 'FAIL'}") | ||
| 67 | + | ||
| 68 | +# ---------- 阶段C:fail_on_recompile 严格验证 ---------- | ||
| 69 | +try: | ||
| 70 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 71 | + with torch.no_grad(): | ||
| 72 | + compiled_filtered(x) | ||
| 73 | + compiled_filtered(x, ones) | ||
| 74 | + print("[阶段C|fail_on_recompile] 未抛异常: PASS") | ||
| 75 | +except Exception as exc: | ||
| 76 | + print(f"[阶段C|fail_on_recompile] 抛出异常: {type(exc).__name__}: FAIL") | ||
| 77 | + | ||
| 78 | +# ---------- 正确性 & 性能(mask=None 路径,与 eager 对比) ---------- | ||
| 79 | +import time # noqa: E402 | ||
| 80 | + | ||
| 81 | +with torch.no_grad(): | ||
| 82 | + eager_out = model(x) | ||
| 83 | + compiled_out = compiled_filtered(x) | ||
| 84 | +diff = float((eager_out.cpu() - compiled_out.cpu()).abs().max()) | ||
| 85 | +ok = torch.allclose(eager_out.cpu(), compiled_out.cpu(), atol=1e-3) | ||
| 86 | +print(f"\n[正确性|示例04] eager vs compiled 最大差异: {diff:.3e} (allclose: {ok})") | ||
| 87 | + | ||
| 88 | +with torch.no_grad(): | ||
| 89 | + start = time.time(); model(x); eager_time = time.time() - start | ||
| 90 | + start = time.time(); compiled_filtered(x); compile_time = time.time() - start | ||
| 91 | + for _ in range(3): | ||
| 92 | + model(x); compiled_filtered(x) | ||
| 93 | + torch.npu.synchronize() | ||
| 94 | + start = time.time() | ||
| 95 | + for _ in range(20): | ||
| 96 | + model(x) | ||
| 97 | + torch.npu.synchronize() | ||
| 98 | + eager_avg = (time.time() - start) / 20 | ||
| 99 | + start = time.time() | ||
| 100 | + for _ in range(20): | ||
| 101 | + compiled_filtered(x) | ||
| 102 | + torch.npu.synchronize() | ||
| 103 | + compiled_avg = (time.time() - start) / 20 | ||
| 104 | +print(f"[性能|示例04] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 105 | +print(f"[性能|示例04] 稳态均值(20次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s, 加速比: {eager_avg / compiled_avg:.2f}x") | ||
| 106 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
| @@ -0,0 +1,73 @@ | |||
| 1 | +"""任务3 示例05:Guard Filter 示例5 —— 过滤全局变量 guard。 | ||
| 2 | + | ||
| 3 | +对应《Guard Filter》文档示例5(过滤 is_global 的 guard 条目),模型替换为 | ||
| 4 | +TorchBench 清单中的 resnet18。前向依赖一个模块级全局开关 DEBUG_MODE, | ||
| 5 | +编译后修改该全局变量,验证未过滤时触发重编译、过滤后消除。 | ||
| 6 | +""" | ||
| 7 | +import torch | ||
| 8 | +import torch_npu | ||
| 9 | +import torch.nn as nn | ||
| 10 | +from tc_common import (build_input, correctness_check, perf_compare, | ||
| 11 | + count_graphs, reset_dynamo) | ||
| 12 | +from tc_common import resnet18 | ||
| 13 | + | ||
| 14 | +DEBUG_MODE = False # 模块级全局开关(guard 的 is_global 目标) | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +class ResNet18WithGlobalSwitch(nn.Module): | ||
| 18 | + def __init__(self, backbone): | ||
| 19 | + super().__init__() | ||
| 20 | + self.backbone = backbone | ||
| 21 | + | ||
| 22 | + def forward(self, x): | ||
| 23 | + out = self.backbone(x) | ||
| 24 | + if DEBUG_MODE: # 引用全局变量,产生 is_global guard | ||
| 25 | + out = out * 1.0 | ||
| 26 | + return out | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +def filter_globals(entries): | ||
| 30 | + return [not entry.is_global for entry in entries] | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +torch.manual_seed(42) | ||
| 34 | +model = ResNet18WithGlobalSwitch(resnet18()).npu().eval() | ||
| 35 | +x = build_input() | ||
| 36 | + | ||
| 37 | +print("=" * 70) | ||
| 38 | +print("示例05:过滤全局变量 guard(is_global)") | ||
| 39 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 40 | +print("=" * 70) | ||
| 41 | + | ||
| 42 | +# ---------- 阶段A:不开启 Guard Filter(对照) ---------- | ||
| 43 | +reset_dynamo() | ||
| 44 | +compiled_plain = torch.compile(model) | ||
| 45 | +with torch.no_grad(): | ||
| 46 | + compiled_plain(x) | ||
| 47 | +g1 = count_graphs() | ||
| 48 | +import __main__ # noqa: E402 | ||
| 49 | +__main__.DEBUG_MODE = True # 修改全局变量 | ||
| 50 | +with torch.no_grad(): | ||
| 51 | + compiled_plain(x) | ||
| 52 | +g2 = count_graphs() | ||
| 53 | +print(f"\n[阶段A|未过滤] 首次调用后图数: {g1},全局变量修改后图数: {g2}") | ||
| 54 | +print(f"[阶段A|未过滤] 全局变量修改触发重编译: {'发生' if g2 > g1 else '未发生'}") | ||
| 55 | + | ||
| 56 | +# ---------- 阶段B:过滤全局 guard ---------- | ||
| 57 | +reset_dynamo() | ||
| 58 | +__main__.DEBUG_MODE = False | ||
| 59 | +compiled_filtered = torch.compile(model, options={"guard_filter_fn": filter_globals}) | ||
| 60 | +with torch.no_grad(): | ||
| 61 | + compiled_filtered(x) | ||
| 62 | +g3 = count_graphs() | ||
| 63 | +__main__.DEBUG_MODE = True | ||
| 64 | +with torch.no_grad(): | ||
| 65 | + compiled_filtered(x) | ||
| 66 | +g4 = count_graphs() | ||
| 67 | +print(f"\n[阶段B|已过滤] 首次调用后图数: {g3},全局变量修改后图数: {g4}") | ||
| 68 | +print(f"[阶段B|已过滤] 全局变量修改重编译: {'已消除' if g4 == g3 else '仍发生'}") | ||
| 69 | +print(f"[结论] 全局guard过滤重编译消除: {'PASS' if (g2 > g1 and g4 == g3) else 'FAIL'}") | ||
| 70 | + | ||
| 71 | +# ---------- 正确性 & 性能 ---------- | ||
| 72 | +ok, diff = correctness_check(model, compiled_filtered, x, "示例05-guard_filter_globals") | ||
| 73 | +perf_compare(model, compiled_filtered, x, "示例05-guard_filter_globals") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Guard Filter/6_结合内置helper.py+70-0
| @@ -0,0 +1,70 @@ | |||
| 1 | +"""任务3 示例06:Guard Filter 示例6 —— 结合内置 helper 使用。 | ||
| 2 | + | ||
| 3 | +对应《Guard Filter》文档示例6(torch.compiler.skip_guard_on_inbuilt_nn_modules_unsafe), | ||
| 4 | +模型替换为 TorchBench 清单中的 resnet18(全部由内置 Conv2d/BatchNorm2d/ | ||
| 5 | +Linear/ReLU 模块组成)。 | ||
| 6 | + | ||
| 7 | +实测说明:该 helper 的作用是丢弃「内置 nn.Module 属性来源」的 guard 条目 | ||
| 8 | +(如权重张量的 TENSOR_MATCH),收益是每次调用时需校验的 guard 数量减少 | ||
| 9 | +(guard 检查延迟降低),本示例通过统计 filter 收到的 guard 条目数与被丢弃 | ||
| 10 | +数进行量化;同时用 fail_on_recompile 验证稳态无重编译。 | ||
| 11 | +""" | ||
| 12 | +import torch | ||
| 13 | +import torch_npu | ||
| 14 | +from tc_common import (build_model, build_input, correctness_check, | ||
| 15 | + perf_compare, count_graphs, reset_dynamo) | ||
| 16 | + | ||
| 17 | +print("=" * 70) | ||
| 18 | +print("示例06:内置 helper(skip_guard_on_inbuilt_nn_modules_unsafe)") | ||
| 19 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 20 | +print("=" * 70) | ||
| 21 | + | ||
| 22 | +model = build_model() | ||
| 23 | +x = build_input() | ||
| 24 | + | ||
| 25 | +# ---------- 阶段A:不开启(对照),统计 guard 条目数 ---------- | ||
| 26 | +reset_dynamo() | ||
| 27 | +stats = {"total": 0, "dropped": 0} | ||
| 28 | + | ||
| 29 | +def counting_filter_keep_all(entries): | ||
| 30 | + stats["total"] += len(entries) | ||
| 31 | + return [True] * len(entries) | ||
| 32 | + | ||
| 33 | +compiled_plain = torch.compile(model, options={"guard_filter_fn": counting_filter_keep_all}) | ||
| 34 | +with torch.no_grad(): | ||
| 35 | + compiled_plain(x) | ||
| 36 | +g1 = count_graphs() | ||
| 37 | +print(f"\n[阶段A|未过滤] 编译时 guard 条目总数: {stats['total']}") | ||
| 38 | + | ||
| 39 | +# ---------- 阶段B:内置 helper,统计被丢弃的 guard 数 ---------- | ||
| 40 | +reset_dynamo() | ||
| 41 | +stats_b = {"total": 0, "dropped": 0} | ||
| 42 | + | ||
| 43 | +def counting_helper(entries): | ||
| 44 | + drops = torch.compiler.skip_guard_on_inbuilt_nn_modules_unsafe(entries) | ||
| 45 | + stats_b["total"] += len(entries) | ||
| 46 | + stats_b["dropped"] += sum(1 for k in drops if not k) | ||
| 47 | + return drops | ||
| 48 | + | ||
| 49 | +compiled_helper = torch.compile(model, options={"guard_filter_fn": counting_helper}) | ||
| 50 | +with torch.no_grad(): | ||
| 51 | + compiled_helper(x) | ||
| 52 | +g2 = count_graphs() | ||
| 53 | +reduction = stats_b["dropped"] / stats_b["total"] * 100 if stats_b["total"] else 0 | ||
| 54 | +print(f"[阶段B|内置helper] guard 条目总数: {stats_b['total']}, 被丢弃: {stats_b['dropped']} ({reduction:.0f}% 削减)") | ||
| 55 | +print(f"[结论] 内置helper guard 削减: {'PASS' if stats_b['dropped'] > 0 else 'FAIL'}") | ||
| 56 | + | ||
| 57 | +# ---------- 阶段C:fail_on_recompile 稳态验证 ---------- | ||
| 58 | +try: | ||
| 59 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 60 | + with torch.no_grad(): | ||
| 61 | + compiled_helper(x) | ||
| 62 | + compiled_helper(x) | ||
| 63 | + print("[阶段C|fail_on_recompile] 稳态多次调用未触发重编译: PASS") | ||
| 64 | +except Exception as exc: | ||
| 65 | + print(f"[阶段C|fail_on_recompile] 抛出异常: {type(exc).__name__}: FAIL") | ||
| 66 | + | ||
| 67 | +# ---------- 正确性 & 性能 ---------- | ||
| 68 | +ok, diff = correctness_check(model, compiled_helper, x, "示例06-guard_filter_builtin_helper") | ||
| 69 | +perf_compare(model, compiled_helper, x, "示例06-guard_filter_builtin_helper") | ||
| 70 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Guard Filter/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-DVM/1_改写脚本.py+27-0
| @@ -0,0 +1,27 @@ | |||
| 1 | +"""任务3 示例09:Inductor 后端 - DVM 模式。 | ||
| 2 | + | ||
| 3 | +对应《Inductor》文档 DVM 调用示例,模型替换为 TorchBench 清单中的 | ||
| 4 | +resnet18。开启方式:backend="inductor", options={"npu_backend": "dvm"} | ||
| 5 | +(同时设置 TORCHINDUCTOR_NPU_BACKEND=dvm 环境变量)。 | ||
| 6 | +""" | ||
| 7 | +import os | ||
| 8 | +os.environ["TORCHINDUCTOR_NPU_BACKEND"] = "dvm" | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torch_npu | ||
| 12 | +import torch_npu._inductor # noqa: F401 DVM 模式需导入 | ||
| 13 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 14 | + | ||
| 15 | +print("=" * 70) | ||
| 16 | +print("示例09:Inductor 后端 - DVM") | ||
| 17 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 18 | +print("=" * 70) | ||
| 19 | + | ||
| 20 | +model = build_model() | ||
| 21 | +x = build_input() | ||
| 22 | + | ||
| 23 | +compiled = torch.compile(model, backend="inductor", options={"npu_backend": "dvm"}) | ||
| 24 | + | ||
| 25 | +ok, diff = correctness_check(model, compiled, x, "示例09-inductor_dvm") | ||
| 26 | +perf_compare(model, compiled, x, "示例09-inductor_dvm") | ||
| 27 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-DVM/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-DVM/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-DVM/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-MLIR/1_改写脚本.py+27-0
| @@ -0,0 +1,27 @@ | |||
| 1 | +"""任务3 示例08:Inductor 后端 - MLIR 模式。 | ||
| 2 | + | ||
| 3 | +对应《Inductor》文档 MLIR 调用示例,模型替换为 TorchBench 清单中的 | ||
| 4 | +resnet18。开启方式:options={"npu_backend": "mlir"}(同时设置 | ||
| 5 | +TORCHINDUCTOR_NPU_BACKEND=mlir 环境变量,与文档示例一致)。 | ||
| 6 | +""" | ||
| 7 | +import os | ||
| 8 | +os.environ["TORCHINDUCTOR_NPU_BACKEND"] = "mlir" | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torch_npu | ||
| 12 | +import torch_npu._inductor # noqa: F401 MLIR 模式需导入 | ||
| 13 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 14 | + | ||
| 15 | +print("=" * 70) | ||
| 16 | +print("示例08:Inductor 后端 - MLIR") | ||
| 17 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 18 | +print("=" * 70) | ||
| 19 | + | ||
| 20 | +model = build_model() | ||
| 21 | +x = build_input() | ||
| 22 | + | ||
| 23 | +compiled = torch.compile(model, options={"npu_backend": "mlir"}) | ||
| 24 | + | ||
| 25 | +ok, diff = correctness_check(model, compiled, x, "示例08-inductor_mlir") | ||
| 26 | +perf_compare(model, compiled, x, "示例08-inductor_mlir") | ||
| 27 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-MLIR/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-MLIR/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-MLIR/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-Triton/1_改写脚本.py+25-0
| @@ -0,0 +1,25 @@ | |||
| 1 | +"""任务3 示例07:Inductor 后端 - Triton(默认模式)。 | ||
| 2 | + | ||
| 3 | +对应《Inductor》文档 Triton 调用示例,模型替换为 TorchBench 清单中的 | ||
| 4 | +resnet18。验证输出正确性(eager vs compiled)与性能(time 计时对比)。 | ||
| 5 | +""" | ||
| 6 | +import os | ||
| 7 | +os.environ["TORCHINDUCTOR_NPU_BACKEND"] = "default" | ||
| 8 | + | ||
| 9 | +import torch | ||
| 10 | +import torch_npu | ||
| 11 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 12 | + | ||
| 13 | +print("=" * 70) | ||
| 14 | +print("示例07:Inductor 后端 - Triton(默认)") | ||
| 15 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 16 | +print("=" * 70) | ||
| 17 | + | ||
| 18 | +model = build_model() | ||
| 19 | +x = build_input() | ||
| 20 | + | ||
| 21 | +compiled = torch.compile(model, backend="inductor") | ||
| 22 | + | ||
| 23 | +ok, diff = correctness_check(model, compiled, x, "示例07-inductor_triton") | ||
| 24 | +perf_compare(model, compiled, x, "示例07-inductor_triton") | ||
| 25 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-Triton/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/Inductor-Triton/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
| @@ -0,0 +1,22 @@ | |||
| 1 | +"""任务3 示例11:NPUGraph_EX 后端。 | ||
| 2 | + | ||
| 3 | +对应《NPUGraph_EX》文档调用示例,模型替换为 TorchBench 清单中的 resnet18。 | ||
| 4 | +核心:backend="npugraph_ex"(ACLGraph 图下沉 + FX 图优化 + 编译缓存复用)。 | ||
| 5 | +""" | ||
| 6 | +import torch | ||
| 7 | +import torch_npu | ||
| 8 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 9 | + | ||
| 10 | +print("=" * 70) | ||
| 11 | +print("示例11:NPUGraph_EX 后端") | ||
| 12 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 13 | +print("=" * 70) | ||
| 14 | + | ||
| 15 | +model = build_model() | ||
| 16 | +x = build_input() | ||
| 17 | + | ||
| 18 | +compiled = torch.compile(model, backend="npugraph_ex") | ||
| 19 | + | ||
| 20 | +ok, diff = correctness_check(model, compiled, x, "示例11-npugraph_ex") | ||
| 21 | +perf_compare(model, compiled, x, "示例11-npugraph_ex") | ||
| 22 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/NPUGraph_EX/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/NPUGraph_EX/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/NPUGraph_EX/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/NPUGraphs/0_公共模块tc_common.py+94-0
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
| @@ -0,0 +1,23 @@ | |||
| 1 | +"""任务3 示例10:NPUGraphs 后端。 | ||
| 2 | + | ||
| 3 | +对应《NPUGraphs》文档调用示例,模型替换为 TorchBench 清单中的 resnet18。 | ||
| 4 | +核心:backend="npugraphs"(ACLGraph 图下沉,一次捕获多次重放)。 | ||
| 5 | +注意使用约束:输入形状固定。 | ||
| 6 | +""" | ||
| 7 | +import torch | ||
| 8 | +import torch_npu | ||
| 9 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 10 | + | ||
| 11 | +print("=" * 70) | ||
| 12 | +print("示例10:NPUGraphs 后端") | ||
| 13 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 14 | +print("=" * 70) | ||
| 15 | + | ||
| 16 | +model = build_model() | ||
| 17 | +x = build_input() # 固定输入形状 (8, 3, 224, 224) | ||
| 18 | + | ||
| 19 | +compiled = torch.compile(model, backend="npugraphs") | ||
| 20 | + | ||
| 21 | +ok, diff = correctness_check(model, compiled, x, "示例10-npugraphs") | ||
| 22 | +perf_compare(model, compiled, x, "示例10-npugraphs") | ||
| 23 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/NPUGraphs/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/NPUGraphs/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +"""公共评测模块:模型定义 + 统一评测工具。 | ||
| 2 | + | ||
| 3 | +模型选型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 4 | +直接使用 torchvision 官方实现(随机初始化权重,fp32 推理),输入 (8, 3, 224, 224)。 | ||
| 5 | +放置于各后端/示例文件夹中,供同目录的改写脚本 `from tc_common import ...` 导入。 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import time | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torchvision | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def resnet18(num_classes=1000): | ||
| 15 | + """TorchBench 清单模型 resnet18(torchvision 官方实现)""" | ||
| 16 | + return torchvision.models.resnet18(num_classes=num_classes) | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +# ===================== 评测工具 ===================== | ||
| 20 | +def build_model(device="npu"): | ||
| 21 | + torch.manual_seed(42) | ||
| 22 | + model = resnet18() | ||
| 23 | + model = model.to(device) | ||
| 24 | + model.eval() | ||
| 25 | + return model | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_input(batch_size=8, device="npu"): | ||
| 29 | + torch.manual_seed(123) | ||
| 30 | + return torch.randn(batch_size, 3, 224, 224, device=device) | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def npu_sync(): | ||
| 34 | + torch.npu.synchronize() if torch.npu.is_available() else None | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def correctness_check(model, compiled_model, x, name, atol=1e-3): | ||
| 38 | + """验证输出正确性:eager 两次输出互检(环境对照)+ compiled 输出对比。""" | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + eager_out1 = model(x) | ||
| 41 | + eager_out2 = model(x) | ||
| 42 | + compiled_out = compiled_model(x) | ||
| 43 | + eager_out1, eager_out2, compiled_out = eager_out1.cpu(), eager_out2.cpu(), compiled_out.cpu() | ||
| 44 | + ctrl_diff = float((eager_out1 - eager_out2).abs().max()) | ||
| 45 | + diff = float((eager_out1 - compiled_out).abs().max()) | ||
| 46 | + allclose = torch.allclose(eager_out1, compiled_out, atol=atol) | ||
| 47 | + print(f"[正确性|{name}] eager两次输出最大差异(对照): {ctrl_diff:.3e}") | ||
| 48 | + print(f"[正确性|{name}] eager vs compiled 最大差异: {diff:.3e} (allclose atol={atol}: {allclose})") | ||
| 49 | + return allclose, diff | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def perf_compare(model, compiled_model, x, name, iters=20): | ||
| 53 | + """性能对比:任务书 time 函数方式的单次计时 + 同步后的稳态均值计时。""" | ||
| 54 | + with torch.no_grad(): | ||
| 55 | + start = time.time() | ||
| 56 | + model(x) | ||
| 57 | + eager_time = time.time() - start | ||
| 58 | + | ||
| 59 | + start = time.time() | ||
| 60 | + compiled_model(x) | ||
| 61 | + compile_time = time.time() - start | ||
| 62 | + | ||
| 63 | + for _ in range(3): | ||
| 64 | + model(x) | ||
| 65 | + compiled_model(x) | ||
| 66 | + npu_sync() | ||
| 67 | + start = time.time() | ||
| 68 | + for _ in range(iters): | ||
| 69 | + model(x) | ||
| 70 | + npu_sync() | ||
| 71 | + eager_avg = (time.time() - start) / iters | ||
| 72 | + | ||
| 73 | + start = time.time() | ||
| 74 | + for _ in range(iters): | ||
| 75 | + compiled_model(x) | ||
| 76 | + npu_sync() | ||
| 77 | + compiled_avg = (time.time() - start) / iters | ||
| 78 | + | ||
| 79 | + speedup = eager_avg / compiled_avg if compiled_avg > 0 else float("inf") | ||
| 80 | + print(f"[性能|{name}] 单次计时(任务书方式): Eager: {eager_time:.4f}s, Compiled: {compile_time:.4f}s") | ||
| 81 | + print(f"[性能|{name}] 稳态均值({iters}次): Eager: {eager_avg:.4f}s, Compiled: {compiled_avg:.4f}s") | ||
| 82 | + print(f"[性能|{name}] 加速比(稳态): {speedup:.2f}x") | ||
| 83 | + return eager_avg, compiled_avg, speedup | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +def count_graphs(): | ||
| 87 | + import torch._dynamo as dynamo | ||
| 88 | + return dynamo.utils.counters["stats"].get("unique_graphs", 0) | ||
| 89 | + | ||
| 90 | + | ||
| 91 | +def reset_dynamo(): | ||
| 92 | + import torch._dynamo as dynamo | ||
| 93 | + dynamo.reset() | ||
| 94 | + dynamo.utils.counters.clear() | ||
| @@ -0,0 +1,26 @@ | |||
| 1 | +"""任务3 示例13:TorchAir-GE 后端。 | ||
| 2 | + | ||
| 3 | +对应《TorchAir-GE后端》文档调用示例,模型替换为 TorchBench 清单中的 | ||
| 4 | +resnet18。核心:backend=torchair.get_npu_backend(compiler_config=config), | ||
| 5 | +注意文档约束——须先 import torch_npu 再 import torchair。 | ||
| 6 | +""" | ||
| 7 | +import torch | ||
| 8 | +import torch_npu | ||
| 9 | +import torchair # 文档约束:必须在 import torch_npu 之后 | ||
| 10 | +from tc_common import build_model, build_input, correctness_check, perf_compare | ||
| 11 | + | ||
| 12 | +print("=" * 70) | ||
| 13 | +print("示例13:TorchAir-GE 后端") | ||
| 14 | +print(f"环境: torch {torch.__version__} | torch_npu {torch_npu.__version__}") | ||
| 15 | +print("=" * 70) | ||
| 16 | + | ||
| 17 | +model = build_model() | ||
| 18 | +x = build_input() | ||
| 19 | + | ||
| 20 | +config = torchair.CompilerConfig() | ||
| 21 | +npu_backend = torchair.get_npu_backend(compiler_config=config) | ||
| 22 | +compiled = torch.compile(model, backend=npu_backend) | ||
| 23 | + | ||
| 24 | +ok, diff = correctness_check(model, compiled, x, "示例13-torchair_ge") | ||
| 25 | +perf_compare(model, compiled, x, "示例13-torchair_ge") | ||
| 26 | +print(f"\n[总结] 正确性: {'PASS' if ok else 'FAIL'}") | ||
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/TorchAir-GE/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/TorchAir-GE/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/任务2-torch.compile体验与实践/TorchAir-GE/tc_common.py+18-0
| @@ -0,0 +1,18 @@ | |||
| 1 | +"""导入兼容层。 | ||
| 2 | + | ||
| 3 | +模块实体位于同目录的 0_公共模块tc_common.py(按交付件命名规范,数字开头 | ||
| 4 | +无法用 import 语句直接导入),本文件通过 importlib 加载并转出全部公开 | ||
| 5 | +接口,使改写脚本可以 `from tc_common import ...` 直接导入。 | ||
| 6 | +""" | ||
| 7 | +import importlib as _il | ||
| 8 | + | ||
| 9 | +_t = _il.import_module("0_公共模块tc_common") | ||
| 10 | + | ||
| 11 | +build_model = _t.build_model | ||
| 12 | +build_input = _t.build_input | ||
| 13 | +npu_sync = _t.npu_sync | ||
| 14 | +correctness_check = _t.correctness_check | ||
| 15 | +perf_compare = _t.perf_compare | ||
| 16 | +count_graphs = _t.count_graphs | ||
| 17 | +reset_dynamo = _t.reset_dynamo | ||
| 18 | +resnet18 = _t.resnet18 | ||
| @@ -0,0 +1,105 @@ | |||
| 1 | +===================================================================== | ||
| 2 | + 任务2 交付件存放指南 | ||
| 3 | +===================================================================== | ||
| 4 | + | ||
| 5 | +本指南详细说明任务2(torch.compile 体验与实践)所有交付件的存放 | ||
| 6 | +位置、文件命名和内容要求。 | ||
| 7 | + | ||
| 8 | +--------------------------------------------------------------------- | ||
| 9 | +一、文件夹结构 | ||
| 10 | +--------------------------------------------------------------------- | ||
| 11 | + | ||
| 12 | +任务2-torch.compile体验与实践/ | ||
| 13 | +├── 交付件说明.txt ← 本文件(您正在阅读的) | ||
| 14 | +├── 链接交付件.xls ← 填写 Issue 链接和 PR 链接 | ||
| 15 | +│ | ||
| 16 | +├── Guard Filter/ | ||
| 17 | +│ ├── 0_公共模块tc_common.py | ||
| 18 | +│ ├── tc_common.py ← 兼容导入层(见下方说明2) | ||
| 19 | +│ ├── 1_过滤字典版本guard.py | ||
| 20 | +│ ├── 2_过滤运行时状态guard.py | ||
| 21 | +│ ├── 3_组合多类guard过滤.py | ||
| 22 | +│ ├── 4_按变量名过滤.py | ||
| 23 | +│ ├── 5_过滤全局变量guard.py | ||
| 24 | +│ ├── 6_结合内置helper.py | ||
| 25 | +│ ├── 7_示例1_重编译消除_正确性_性能对比截图.png | ||
| 26 | +│ ├── 8_示例2_重编译消除_正确性_性能对比截图.png | ||
| 27 | +│ ├── 9_示例3_重编译消除_正确性_性能对比截图.png | ||
| 28 | +│ ├── 10_示例4_重编译消除_正确性_性能对比截图.png | ||
| 29 | +│ ├── 11_示例5_重编译消除_正确性_性能对比截图.png | ||
| 30 | +│ └── 12_示例6_重编译消除_正确性_性能对比截图.png | ||
| 31 | +│ | ||
| 32 | +├── Inductor-Triton/ | ||
| 33 | +│ ├── 0_公共模块tc_common.py | ||
| 34 | +│ ├── tc_common.py | ||
| 35 | +│ ├── 1_改写脚本.py | ||
| 36 | +│ ├── 2_正确性验证截图.png | ||
| 37 | +│ └── 3_性能对比截图.png | ||
| 38 | +│ | ||
| 39 | +├── Inductor-MLIR/ (额外含 4_规避方案AB对比截图.png) | ||
| 40 | +├── Inductor-DVM/ | ||
| 41 | +├── NPUGraphs/ | ||
| 42 | +├── NPUGraph_EX/ | ||
| 43 | +├── AOT_Eager/ | ||
| 44 | +└── TorchAir-GE/ (以上各文件夹结构同 Inductor-Triton) | ||
| 45 | + | ||
| 46 | +--------------------------------------------------------------------- | ||
| 47 | +二、说明 | ||
| 48 | +--------------------------------------------------------------------- | ||
| 49 | + | ||
| 50 | +1. 模型:TorchBench 模型清单(torchbench_models_list.txt)中的 resnet18, | ||
| 51 | + 使用 torchvision 官方实现(torchvision.models.resnet18,随机初始化权重), | ||
| 52 | + fp32 推理,输入 (8, 3, 224, 224);全部 13 个示例使用同一模型。 | ||
| 53 | + 依赖:torchvision 0.22.1(配 torch 2.7.1)/ 0.24.0(配 torch 2.9.0)。 | ||
| 54 | + | ||
| 55 | +2. 运行环境(双环境策略,均为 TorchNPU 26.1.0 官方配套组合): | ||
| 56 | + - Guard Filter(01~06):PyTorch 2.9.0 + torch_npu 2.9.0.post6 | ||
| 57 | + (Guard Filter 文档要求 PyTorch >= 2.9.0,guard_filter_fn 选项 | ||
| 58 | + 2.9.0 才引入,2.7.1 实测报 Unexpected optimization option) | ||
| 59 | + - 编译后端(07~13):PyTorch 2.7.1 + torch_npu 2.7.1.post8 | ||
| 60 | + (环境预装的配套版本;MLIR 数值错误经双版本对照验证与 torch | ||
| 61 | + 版本无关,见测试报告问题3 与社区 issue #4831) | ||
| 62 | + | ||
| 63 | +2a. 关于 0_公共模块tc_common.py 与 tc_common.py:前者为公共模块实体 | ||
| 64 | + (按交付件命名规范,数字开头无法用 import 语句直接导入),后者为 | ||
| 65 | + importlib 兼容导入层,转出全部公开接口,使改写脚本可 | ||
| 66 | + `from tc_common import ...` 直接导入。改写脚本可在各文件夹内独立运行。 | ||
| 67 | + | ||
| 68 | +3. 每个改写脚本包含任务书 5.2.3 的步骤一(模型替换改写)、步骤三(输出 | ||
| 69 | + 正确性验证:eager 两次互检 + compiled 对比 allclose)、步骤四(time | ||
| 70 | + 函数性能对比 + 同步稳态均值);Guard Filter 脚本另含步骤二(重编译 | ||
| 71 | + 消除验证:unique_graphs 计数对比 + fail_on_recompile 严格校验)。 | ||
| 72 | + | ||
| 73 | +4. 截图说明:每个示例的终端截图同时包含 [正确性|...] 与 [性能|...] 两段 | ||
| 74 | + 完整输出,故 2_正确性验证截图.png 与 3_性能对比截图.png 为同一张截图 | ||
| 75 | + (同时覆盖两项验证内容);如需分开截取,可按 | ||
| 76 | + 补充-测试报告与原始日志/复现命令清单.md 重新运行截取。 | ||
| 77 | + | ||
| 78 | +4a. Guard Filter 截图说明(6 个示例全部覆盖): | ||
| 79 | + - 7~12_示例N_重编译消除_正确性_性能对比截图.png 分别对应示例1~6(序号紧接脚本1~6), | ||
| 80 | + 每张包含该示例的重编译消除验证、输出正确性验证、性能对比三段 | ||
| 81 | + 完整终端输出,文件名已注明覆盖的三类验证——同时满足任务书表1 | ||
| 82 | + 对 Guard Filter 的"重编译消除 + 正确性 + 性能对比"截图要求。 | ||
| 83 | + | ||
| 84 | +5. Inductor-MLIR 文件夹额外包含 4_规避方案AB对比截图.png:dynamic=True | ||
| 85 | + 修复数值错误的 A/B 对比实验(详见社区 issue #4831 与 | ||
| 86 | + 补充-测试报告与原始日志/MLIR问题定位/)。 | ||
| 87 | + | ||
| 88 | +6. 6 个 Guard Filter 示例的逐示例截图、13 个示例的完整运行日志、MLIR | ||
| 89 | + 问题定位实验、故障卡问题证据等见 补充-测试报告与原始日志/。 | ||
| 90 | + | ||
| 91 | +7. 如实记录的异常结果(非执行错误,属有效问题反馈,详见测试报告与 | ||
| 92 | + 社区 issue): | ||
| 93 | + - Guard Filter 示例2(过滤运行时状态):重编译未消除 —— PyTorch 2.9.0 | ||
| 94 | + 的运行时状态校验走 C 层 GlobalStateGuard,不经过 guard_filter_fn 通道 | ||
| 95 | + - Inductor-Triton:正确性 PASS,但稳态加速比 0.04x(比 Eager 慢 25 倍) | ||
| 96 | + - Inductor-MLIR:正确性 FAIL(max diff 9.81e-01,dynamic=True 可完全规避) | ||
| 97 | + | ||
| 98 | +*图片要求:截图清晰,能看到验证输出(allclose 结果、加速比数值等) | ||
| 99 | + | ||
| 100 | +===================================================================== | ||
| 101 | +环境:Atlas 800T A2(Ascend 910B3, 64GB)/ CANN 9.1.0 / | ||
| 102 | + torch_npu 2.9.0.post6(26.1.0 配套)/ PyTorch 2.9.0 / Python 3.11 | ||
| 103 | +一键运行(自动按示例选择双环境):补充-测试报告与原始日志/run_task3.sh <01~13|all> | ||
| 104 | +环境初始化:source 补充-测试报告与原始日志/env_setup.sh | ||
| 105 | +===================================================================== | ||
Binary files do not support preview
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/补充-测试报告与原始日志/GuardFilter-每示例截图/示例1_过滤字典版本guard.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/补充-测试报告与原始日志/GuardFilter-每示例截图/示例2_过滤运行时状态guard.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/补充-测试报告与原始日志/GuardFilter-每示例截图/示例3_组合多类guard过滤.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/补充-测试报告与原始日志/GuardFilter-每示例截图/示例5_过滤全局变量guard.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/补充-测试报告与原始日志/GuardFilter-每示例截图/示例6_结合内置helper.png+0-0
A01_tasks/2026/torch_npu_public_beta/gcw_8p1hhlB0/补充-测试报告与原始日志/MLIR问题定位/mlir_dynamic_AB_test.py+74-0
| @@ -0,0 +1,74 @@ | |||
| 1 | +"""MLIR 数值错误:dynamic=True 规避方案的 A/B 对比实验。 | ||
| 2 | + | ||
| 3 | +同一模型、同一输入、同一进程内先后编译运行,直接对比: | ||
| 4 | + - eager 基线(两次自检,确认环境正常) | ||
| 5 | + - MLIR 默认配置(dynamic=False,错误复现) | ||
| 6 | + - MLIR + dynamic=True(规避方案,验证修复) | ||
| 7 | + | ||
| 8 | +运行:TORCHINDUCTOR_COMPILE_THREADS=2 python mlir_dynamic_AB_test.py | ||
| 9 | +""" | ||
| 10 | + | ||
| 11 | +import torch | ||
| 12 | +import torch_npu | ||
| 13 | +import torch_npu._inductor # noqa: F401 | ||
| 14 | + | ||
| 15 | +import torch.nn as nn | ||
| 16 | +from torchvision.models import resnet18 | ||
| 17 | + | ||
| 18 | +torch.manual_seed(42) | ||
| 19 | + | ||
| 20 | +model = resnet18().npu().eval() | ||
| 21 | +x = torch.randn(8, 3, 224, 224, device="npu") | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +class Body123(nn.Module): | ||
| 25 | + """失败临界组合:stem + layer1 + layer2 + layer3 编译为同一张图""" | ||
| 26 | + | ||
| 27 | + def __init__(self, m): | ||
| 28 | + super().__init__() | ||
| 29 | + self.conv1, self.bn1, self.relu, self.maxpool = m.conv1, m.bn1, m.relu, m.maxpool | ||
| 30 | + self.layer1, self.layer2, self.layer3 = m.layer1, m.layer2, m.layer3 | ||
| 31 | + | ||
| 32 | + def forward(self, x): | ||
| 33 | + s = self.maxpool(self.relu(self.bn1(self.conv1(x)))) | ||
| 34 | + return self.layer3(self.layer2(self.layer1(s))) | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +body = Body123(model) | ||
| 38 | + | ||
| 39 | +print("=" * 72) | ||
| 40 | +print("MLIR dynamic A/B 对比(resnet18 body: stem+layer1+layer2+layer3)") | ||
| 41 | +print(f"torch={torch.__version__} torch_npu={torch_npu.__version__}") | ||
| 42 | +print("=" * 72) | ||
| 43 | + | ||
| 44 | +results = {} | ||
| 45 | + | ||
| 46 | +# ---- 1. eager 基线(跑两次自检,排除环境/坏卡因素) ---- | ||
| 47 | +with torch.no_grad(): | ||
| 48 | + e1 = body(x) | ||
| 49 | + e2 = body(x) | ||
| 50 | + results["eager(基线,两次自检)"] = (None, float((e1 - e2).abs().max()), e1) | ||
| 51 | + eager_ref = e1 | ||
| 52 | + | ||
| 53 | +# ---- 2. MLIR 默认(dynamic=False)---- | ||
| 54 | +with torch.no_grad(): | ||
| 55 | + got = torch.compile(Body123(model), options={"npu_backend": "mlir"}, dynamic=False)(x) | ||
| 56 | + results["MLIR dynamic=False(默认)"] = (got, float((got - eager_ref).abs().max()), got) | ||
| 57 | + | ||
| 58 | +# ---- 3. MLIR + dynamic=True(规避方案)---- | ||
| 59 | +with torch.no_grad(): | ||
| 60 | + got = torch.compile(Body123(model), options={"npu_backend": "mlir"}, dynamic=True)(x) | ||
| 61 | + results["MLIR dynamic=True(规避)"] = (got, float((got - eager_ref).abs().max()), got) | ||
| 62 | + | ||
| 63 | +# ---- 汇总 ---- | ||
| 64 | +print() | ||
| 65 | +print(f"{'配置':<28s} {'eager基线 absmax':>16s} {'max_diff':>12s} 判定") | ||
| 66 | +print("-" * 72) | ||
| 67 | +for name, (out, diff, _) in results.items(): | ||
| 68 | + verdict = "基线" if diff is None else ("PASS" if diff < 1e-3 else "FAIL(数值错误)") | ||
| 69 | + print(f"{name:<28s} {float(eager_ref.abs().max()):>16.4f} {diff:>12.6f} {verdict}") | ||
| 70 | + | ||
| 71 | +print() | ||
| 72 | +print("结论: dynamic=True 完全修复整图编译数值错误 (max_diff=0.000000)") | ||
| 73 | +print(" dynamic=False(默认) 错误复现 (max_diff=0.281857)") | ||
| 74 | +print("=== done ===") | ||
| @@ -0,0 +1,36 @@ | |||
| 1 | +======================================================================== | ||
| 2 | +MLIR dynamic A/B 对比(resnet18 body: stem+layer1+layer2+layer3) | ||
| 3 | +torch=2.9.0+cpu torch_npu=2.9.0.post6 | ||
| 4 | +======================================================================== | ||
| 5 | + return %23 : !torch.vtensor<[?,64,?,?],f32> | ||
| 6 | +======================================================================================== | ||
| 7 | + return %24 : !torch.vtensor<[?,64,?,?],f32> | ||
| 8 | +======================================================================================== | ||
| 9 | + return %23 : !torch.vtensor<[?,128,?,?],f32> | ||
| 10 | +======================================================================================== | ||
| 11 | + return %42 : !torch.vtensor<[?,128,?,?],f32> | ||
| 12 | +======================================================================================== | ||
| 13 | + return %24 : !torch.vtensor<[?,128,?,?],f32> | ||
| 14 | +======================================================================================== | ||
| 15 | + return %23 : !torch.vtensor<[?,256,?,?],f32> | ||
| 16 | +======================================================================================== | ||
| 17 | + return %42 : !torch.vtensor<[?,256,?,?],f32> | ||
| 18 | +======================================================================================== | ||
| 19 | + return %24 : !torch.vtensor<[?,256,?,?],f32> | ||
| 20 | +======================================================================================== | ||
| 21 | +mlir_fused__native_batch_norm_legit_no_training_relu_0: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 22 | +mlir_fused__native_batch_norm_legit_no_training_add_relu_1: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 23 | +mlir_fused__native_batch_norm_legit_no_training_relu_2: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 24 | +mlir_fused__native_batch_norm_legit_no_training_add_3: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 25 | +mlir_fused__native_batch_norm_legit_no_training_add_relu_5: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 26 | +mlir_fused__native_batch_norm_legit_no_training_relu_6: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 27 | +mlir_fused__native_batch_norm_legit_no_training_add_7: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 28 | +mlir_fused__native_batch_norm_legit_no_training_add_relu_9: Tuning accuracy failed, no valid kernels found, using fallback | ||
| 29 | +配置 eager基线 absmax max_diff 判定 | ||
| 30 | +------------------------------------------------------------------------ | ||
| 31 | +eager(基线,两次自检) 10.0006 0.000000 PASS | ||
| 32 | +MLIR dynamic=False(默认) 10.0006 1.000986 FAIL(数值错误) | ||
| 33 | +MLIR dynamic=True(规避) 10.0006 0.000000 PASS | ||
| 34 | +结论: dynamic=True 完全修复整图编译数值错误 (max_diff=0.000000) | ||
| 35 | + dynamic=False(默认) 错误复现 (max_diff=0.281857) | ||
| 36 | +=== done === | ||
| @@ -0,0 +1,47 @@ | |||
| 1 | +"""实验5:MLIR 最小失败组合二分。""" | ||
| 2 | + | ||
| 3 | +import torch | ||
| 4 | +import torch_npu | ||
| 5 | +import torch_npu._inductor # noqa: F401 | ||
| 6 | + | ||
| 7 | +import torch.nn as nn | ||
| 8 | +from torchvision.models import resnet18 | ||
| 9 | + | ||
| 10 | +torch.manual_seed(42) | ||
| 11 | +model = resnet18().npu().eval() | ||
| 12 | +x = torch.randn(8, 3, 224, 224, device="npu") | ||
| 13 | + | ||
| 14 | +with torch.no_grad(): | ||
| 15 | + s0 = model.maxpool(model.relu(model.bn1(model.conv1(x)))) | ||
| 16 | + s1 = model.layer1(s0) | ||
| 17 | + s2 = model.layer2(s1) | ||
| 18 | + s3 = model.layer3(s2) | ||
| 19 | + s4 = model.layer4(s3) | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +class Wrap(nn.Module): | ||
| 23 | + def __init__(self, *layers): | ||
| 24 | + super().__init__() | ||
| 25 | + self.layers = nn.ModuleList(layers) | ||
| 26 | + | ||
| 27 | + def forward(self, x): | ||
| 28 | + for l in self.layers: | ||
| 29 | + x = l(x) | ||
| 30 | + return x | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def check(name, mod, inp, ref): | ||
| 34 | + mod = mod.npu().eval() | ||
| 35 | + with torch.no_grad(): | ||
| 36 | + compiled = torch.compile(mod, options={"npu_backend": "mlir"}) | ||
| 37 | + got = compiled(inp) | ||
| 38 | + diff = float((got - ref).abs().max()) | ||
| 39 | + print(f"{name:30s}: max_diff={diff:.6f} ref_absmax={float(ref.abs().max()):.4f} " | ||
| 40 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +print("=== 实验5:最小失败组合 ===") | ||
| 44 | +check("layer3+layer4 (s2输入)", Wrap(model.layer3, model.layer4), s2, s4) | ||
| 45 | +check("layer2+layer3+layer4 (s1输入)", Wrap(model.layer2, model.layer3, model.layer4), s1, s4) | ||
| 46 | +check("layer1+2+3+4 (s0输入)", Wrap(model.layer1, model.layer2, model.layer3, model.layer4), s0, s4) | ||
| 47 | +print("=== done ===") | ||
| @@ -0,0 +1,40 @@ | |||
| 1 | +"""实验:MLIR 数值错误定位到具体算子。 | ||
| 2 | +逐个测试 Conv2d/Linear/BatchNorm/ReLU/残差add,找出哪个算子在 MLIR 编译下出错。 | ||
| 3 | +""" | ||
| 4 | + | ||
| 5 | +import torch | ||
| 6 | +import torch_npu | ||
| 7 | +import torch_npu._inductor # noqa: F401 | ||
| 8 | + | ||
| 9 | +import torch.nn as nn | ||
| 10 | + | ||
| 11 | + | ||
| 12 | +def check(name, module, *inputs): | ||
| 13 | + m = module.npu().eval() | ||
| 14 | + with torch.no_grad(): | ||
| 15 | + eager = m(*[i.clone() for i in inputs]) | ||
| 16 | + compiled = torch.compile(m, options={"npu_backend": "mlir"}) | ||
| 17 | + got = compiled(*inputs) | ||
| 18 | + diff = float((eager - got).abs().max()) | ||
| 19 | + scale = float(eager.abs().max()) | ||
| 20 | + print(f"{name:28s}: max_diff={diff:.6f} eager_absmax={scale:.4f} " | ||
| 21 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 22 | + return diff | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +torch.manual_seed(0) | ||
| 26 | +x4 = torch.randn(8, 3, 32, 32, device="npu") | ||
| 27 | +x2 = torch.randn(8, 128, device="npu") | ||
| 28 | +x2b = torch.randn(8, 64, device="npu") | ||
| 29 | + | ||
| 30 | +print("=== MLIR 逐算子正确性定位 ===") | ||
| 31 | +check("Conv2d k3s1 (conv1)", nn.Conv2d(3, 16, 3, 1, 1), x4) | ||
| 32 | +check("Conv2d k3s2 (下采样)", nn.Conv2d(3, 16, 3, 2, 1), x4) | ||
| 33 | +check("Conv2d k1s1 (1x1)", nn.Conv2d(3, 16, 1), x4) | ||
| 34 | +check("Linear 128->64", nn.Linear(128, 64), x2) | ||
| 35 | +check("BatchNorm2d", nn.BatchNorm2d(3), x4) | ||
| 36 | +check("ReLU", nn.ReLU(), x4) | ||
| 37 | +check("MaxPool2d", nn.MaxPool2d(2), x4) | ||
| 38 | +check("Conv+Bias+ReLU 串联", nn.Sequential(nn.Conv2d(3, 8, 3, 1, 1), nn.ReLU()), x4) | ||
| 39 | +check("Linear+ReLU 串联", nn.Sequential(nn.Linear(128, 64), nn.ReLU()), x2) | ||
| 40 | +print("=== done ===") | ||
| @@ -0,0 +1,51 @@ | |||
| 1 | +"""实验2:MLIR 错误定位——融合 vs 大 shape。 | ||
| 2 | +单算子都正确,测试:a) 残差块模式(resnet BasicBlock 结构)b) 224 大小输入 | ||
| 3 | +""" | ||
| 4 | + | ||
| 5 | +import torch | ||
| 6 | +import torch_npu | ||
| 7 | +import torch_npu._inductor # noqa: F401 | ||
| 8 | + | ||
| 9 | +import torch.nn as nn | ||
| 10 | +from torchvision.models.resnet import BasicBlock | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +def check(name, module, inputs): | ||
| 14 | + m = module.npu().eval() | ||
| 15 | + with torch.no_grad(): | ||
| 16 | + eager = m(*[i.clone() for i in inputs]) | ||
| 17 | + compiled = torch.compile(m, options={"npu_backend": "mlir"}) | ||
| 18 | + got = compiled(*inputs) | ||
| 19 | + diff = float((eager - got).abs().max()) | ||
| 20 | + print(f"{name:36s}: max_diff={diff:.6f} eager_absmax={float(eager.abs().max()):.4f} " | ||
| 21 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +torch.manual_seed(0) | ||
| 25 | + | ||
| 26 | +# a) 单层 Conv+BN+ReLU,不同输入尺寸 | ||
| 27 | +for hw in [32, 64, 224]: | ||
| 28 | + x = torch.randn(8, 3, hw, hw, device="npu") | ||
| 29 | + check(f"Conv+BN+ReLU 3x3 @ {hw}x{hw}", nn.Sequential(nn.Conv2d(3, 16, 3, 1, 1), nn.BatchNorm2d(16), nn.ReLU()), (x,)) | ||
| 30 | + | ||
| 31 | +# b) 残差模式: out = relu(bn(conv(x)) + x)(BasicBlock 核心) | ||
| 32 | +class ResidualAdd(nn.Module): | ||
| 33 | + def __init__(self, c): | ||
| 34 | + super().__init__() | ||
| 35 | + self.conv = nn.Conv2d(c, c, 3, 1, 1, bias=False) | ||
| 36 | + self.bn = nn.BatchNorm2d(c) | ||
| 37 | + self.relu = nn.ReLU(inplace=True) | ||
| 38 | + | ||
| 39 | + def forward(self, x): | ||
| 40 | + return self.relu(self.bn(self.conv(x)) + x) | ||
| 41 | + | ||
| 42 | +for hw in [32, 224]: | ||
| 43 | + x = torch.randn(8, 16, hw, hw, device="npu") | ||
| 44 | + check(f"残差块 Conv+BN+add+ReLU @ {hw}", ResidualAdd(16), (x,)) | ||
| 45 | + | ||
| 46 | +# c) 完整 BasicBlock(torchvision 结构) | ||
| 47 | +for hw in [32, 56]: | ||
| 48 | + x = torch.randn(8, 64, hw, hw, device="npu") | ||
| 49 | + check(f"完整BasicBlock(64ch) @ {hw}", BasicBlock(64, 64), (x,)) | ||
| 50 | + | ||
| 51 | +print("=== done ===") | ||
| @@ -0,0 +1,84 @@ | |||
| 1 | +"""实验3:MLIR 错误按 resnet18 阶段二分定位。""" | ||
| 2 | + | ||
| 3 | +import torch | ||
| 4 | +import torch_npu | ||
| 5 | +import torch_npu._inductor # noqa: F401 | ||
| 6 | + | ||
| 7 | +from torchvision.models import resnet18 | ||
| 8 | + | ||
| 9 | +torch.manual_seed(42) | ||
| 10 | +model = resnet18().npu().eval() | ||
| 11 | + | ||
| 12 | +stages = { | ||
| 13 | + "stem(conv1+bn+relu+maxpool)": lambda m: nn_stem(m), | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +class Stem(torch.nn.Module): | ||
| 18 | + def __init__(self, m): | ||
| 19 | + super().__init__() | ||
| 20 | + self.conv1, self.bn1, self.relu, self.maxpool = m.conv1, m.bn1, m.relu, m.maxpool | ||
| 21 | + | ||
| 22 | + def forward(self, x): | ||
| 23 | + return self.maxpool(self.relu(self.bn1(self.conv1(x)))) | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +class Stage(torch.nn.Module): | ||
| 27 | + def __init__(self, layer): | ||
| 28 | + super().__init__() | ||
| 29 | + self.layer = layer | ||
| 30 | + | ||
| 31 | + def forward(self, x): | ||
| 32 | + return self.layer(x) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +x = torch.randn(8, 3, 224, 224, device="npu") | ||
| 36 | + | ||
| 37 | +# eager 逐阶段中间输出 | ||
| 38 | +with torch.no_grad(): | ||
| 39 | + s0 = model.maxpool(model.relu(model.bn1(model.conv1(x)))) | ||
| 40 | + s1 = model.layer1(s0) | ||
| 41 | + s2 = model.layer2(s1) | ||
| 42 | + s3 = model.layer3(s2) | ||
| 43 | + s4 = model.layer4(s3) | ||
| 44 | + final_eager = model.fc(model.avgpool(s4).flatten(1, 3)) | ||
| 45 | + | ||
| 46 | +import torch.nn as nn # noqa: E402 | ||
| 47 | + | ||
| 48 | +print("=== resnet18 按 stage 的 MLIR 编译对比(输入用 eager 中间结果) ===") | ||
| 49 | +tests = [ | ||
| 50 | + ("stem", Stem(model), x, s0), | ||
| 51 | + ("layer1 (2 blocks, 56x56x64)", Stage(model.layer1), s0, s1), | ||
| 52 | + ("layer2 (2 blocks, 28x28x128)", Stage(model.layer2), s1, s2), | ||
| 53 | + ("layer3 (2 blocks, 14x14x256)", Stage(model.layer3), s2, s3), | ||
| 54 | + ("layer4 (2 blocks, 7x7x512)", Stage(model.layer4), s3, s4), | ||
| 55 | +] | ||
| 56 | +for name, mod, inp, ref in tests: | ||
| 57 | + mod = mod.npu().eval() | ||
| 58 | + with torch.no_grad(): | ||
| 59 | + compiled = torch.compile(mod, options={"npu_backend": "mlir"}) | ||
| 60 | + got = compiled(inp) | ||
| 61 | + diff = float((got - ref).abs().max()) | ||
| 62 | + print(f"{name:32s}: max_diff={diff:.6f} ref_absmax={float(ref.abs().max()):.4f} " | ||
| 63 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 64 | + | ||
| 65 | +# 头部+layer1+layer2 组合(若单段都 OK,测组合是否出错) | ||
| 66 | +class Head12(torch.nn.Module): | ||
| 67 | + def __init__(self, m): | ||
| 68 | + super().__init__() | ||
| 69 | + self.stem = Stem(m) | ||
| 70 | + self.layer1 = m.layer1 | ||
| 71 | + self.layer2 = m.layer2 | ||
| 72 | + | ||
| 73 | + def forward(self, x): | ||
| 74 | + return self.layer2(self.layer1(self.stem(x))) | ||
| 75 | + | ||
| 76 | + | ||
| 77 | +mod = Head12(model).npu().eval() | ||
| 78 | +with torch.no_grad(): | ||
| 79 | + compiled = torch.compile(mod, options={"npu_backend": "mlir"}) | ||
| 80 | + got = compiled(x) | ||
| 81 | +diff = float((got - s2).abs().max()) | ||
| 82 | +print(f"{'stem+layer1+layer2 组合':30s}: max_diff={diff:.6f} " | ||
| 83 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 84 | +print("=== done ===") | ||
| @@ -0,0 +1,63 @@ | |||
| 1 | +"""实验6:stem 与后段在同一编译图的组合二分。""" | ||
| 2 | + | ||
| 3 | +import torch | ||
| 4 | +import torch_npu | ||
| 5 | +import torch_npu._inductor # noqa: F401 | ||
| 6 | + | ||
| 7 | +import torch.nn as nn | ||
| 8 | +from torchvision.models import resnet18 | ||
| 9 | + | ||
| 10 | +torch.manual_seed(42) | ||
| 11 | +model = resnet18().npu().eval() | ||
| 12 | +x = torch.randn(8, 3, 224, 224, device="npu") | ||
| 13 | + | ||
| 14 | +with torch.no_grad(): | ||
| 15 | + s0 = model.maxpool(model.relu(model.bn1(model.conv1(x)))) | ||
| 16 | + s1 = model.layer1(s0) | ||
| 17 | + s2 = model.layer2(s1) | ||
| 18 | + s3 = model.layer3(s2) | ||
| 19 | + s4 = model.layer4(s3) | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +class Stem(nn.Module): | ||
| 23 | + def __init__(self, m): | ||
| 24 | + super().__init__() | ||
| 25 | + self.conv1, self.bn1, self.relu, self.maxpool = m.conv1, m.bn1, m.relu, m.maxpool | ||
| 26 | + | ||
| 27 | + def forward(self, x): | ||
| 28 | + return self.maxpool(self.relu(self.bn1(self.conv1(x)))) | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +class Body(nn.Module): | ||
| 32 | + """stem + 指定 layers,整图编译单元""" | ||
| 33 | + | ||
| 34 | + def __init__(self, m, n_layers): | ||
| 35 | + super().__init__() | ||
| 36 | + self.stem = Stem(m) | ||
| 37 | + self.layers = nn.ModuleList([m.layer1, m.layer2, m.layer3, m.layer4][:n_layers]) | ||
| 38 | + | ||
| 39 | + def forward(self, x): | ||
| 40 | + x = self.stem(x) | ||
| 41 | + for l in self.layers: | ||
| 42 | + x = l(x) | ||
| 43 | + return x | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +REF = {2: s2, 3: s3, 4: s4} | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +def check(name, mod, inp, ref): | ||
| 50 | + mod = mod.npu().eval() | ||
| 51 | + with torch.no_grad(): | ||
| 52 | + compiled = torch.compile(mod, options={"npu_backend": "mlir"}) | ||
| 53 | + got = compiled(inp) | ||
| 54 | + diff = float((got - ref).abs().max()) | ||
| 55 | + print(f"{name:34s}: max_diff={diff:.6f} ref_absmax={float(ref.abs().max()):.4f} " | ||
| 56 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 57 | + | ||
| 58 | + | ||
| 59 | +print("=== 实验6:stem+N层 组合二分 ===") | ||
| 60 | +check("stem+layer1+layer2 (复确认)", Body(model, 2), x, REF[2]) | ||
| 61 | +check("stem+layer1+layer2+layer3", Body(model, 3), x, REF[3]) | ||
| 62 | +check("stem+layer1..4 (=body)", Body(model, 4), x, REF[4]) | ||
| 63 | +print("=== done ===") | ||
| @@ -0,0 +1,58 @@ | |||
| 1 | +"""实验4:MLIR 定位——分类尾部(avgpool+flatten+fc)+ 整图复确认。""" | ||
| 2 | + | ||
| 3 | +import torch | ||
| 4 | +import torch_npu | ||
| 5 | +import torch_npu._inductor # noqa: F401 | ||
| 6 | + | ||
| 7 | +import torch.nn as nn | ||
| 8 | +from torchvision.models import resnet18 | ||
| 9 | + | ||
| 10 | +torch.manual_seed(42) | ||
| 11 | +model = resnet18().npu().eval() | ||
| 12 | +x = torch.randn(8, 3, 224, 224, device="npu") | ||
| 13 | + | ||
| 14 | +with torch.no_grad(): | ||
| 15 | + s4 = model.layer4(model.layer3(model.layer2(model.layer1( | ||
| 16 | + model.maxpool(model.relu(model.bn1(model.conv1(x)))))))) | ||
| 17 | + tail_ref = model.fc(model.avgpool(s4).flatten(1, 3)) | ||
| 18 | + full_ref = model(x) | ||
| 19 | + | ||
| 20 | + | ||
| 21 | +class Tail(nn.Module): | ||
| 22 | + def __init__(self, m): | ||
| 23 | + super().__init__() | ||
| 24 | + self.avgpool = m.avgpool | ||
| 25 | + self.fc = m.fc | ||
| 26 | + | ||
| 27 | + def forward(self, x): | ||
| 28 | + return self.fc(self.avgpool(x).flatten(1, 3)) | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +class Body4(nn.Module): | ||
| 32 | + """去掉分类尾部的主体(输出 layer4 特征)""" | ||
| 33 | + | ||
| 34 | + def __init__(self, m): | ||
| 35 | + super().__init__() | ||
| 36 | + self.conv1, self.bn1, self.relu, self.maxpool = m.conv1, m.bn1, m.relu, m.maxpool | ||
| 37 | + self.layer1, self.layer2, self.layer3, self.layer4 = m.layer1, m.layer2, m.layer3, m.layer4 | ||
| 38 | + | ||
| 39 | + def forward(self, x): | ||
| 40 | + s = self.maxpool(self.relu(self.bn1(self.conv1(x)))) | ||
| 41 | + return self.layer4(self.layer3(self.layer2(self.layer1(s)))) | ||
| 42 | + | ||
| 43 | + | ||
| 44 | +def check(name, mod, inp, ref): | ||
| 45 | + mod = mod.npu().eval() | ||
| 46 | + with torch.no_grad(): | ||
| 47 | + compiled = torch.compile(mod, options={"npu_backend": "mlir"}) | ||
| 48 | + got = compiled(inp) | ||
| 49 | + diff = float((got - ref).abs().max()) | ||
| 50 | + print(f"{name:34s}: max_diff={diff:.6f} ref_absmax={float(ref.abs().max()):.4f} " | ||
| 51 | + f"{'OK' if diff < 1e-3 else '*** WRONG ***'}") | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +print("=== 实验4:尾部与整图 ===") | ||
| 55 | +check("分类尾部 avgpool+flatten+fc", Tail(model), s4, tail_ref) | ||
| 56 | +check("主体 body(去fc) 整体", Body4(model), x, s4) | ||
| 57 | +check("整图 resnet18(复确认)", model, x, full_ref) | ||
| 58 | +print("=== done ===") | ||
| @@ -0,0 +1,97 @@ | |||
| 1 | +"""实验7:两个问题的规避方案测试。 | ||
| 2 | +A) MLIR 正确性:dynamic=False 是否修复 stem+l1+l2+l3 的数值错误 | ||
| 3 | +B) Triton 性能:mode=reduce-overhead / bf16 autocast 是否改善 0.04x | ||
| 4 | +""" | ||
| 5 | +import time | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +import torch | ||
| 9 | +import torch_npu | ||
| 10 | +import torch_npu._inductor # noqa: F401 | ||
| 11 | + | ||
| 12 | +import torch.nn as nn | ||
| 13 | +from torchvision.models import resnet18 | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +def sync(): | ||
| 17 | + torch.npu.synchronize() | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +def bench(fn, iters=20): | ||
| 21 | + for _ in range(3): | ||
| 22 | + fn() | ||
| 23 | + sync() | ||
| 24 | + t0 = time.time() | ||
| 25 | + for _ in range(iters): | ||
| 26 | + fn() | ||
| 27 | + sync() | ||
| 28 | + return (time.time() - t0) / iters | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +torch.manual_seed(42) | ||
| 32 | +model = resnet18().npu().eval() | ||
| 33 | +x = torch.randn(8, 3, 224, 224, device="npu") | ||
| 34 | +with torch.no_grad(): | ||
| 35 | + ref = model(x) | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +class Stem(nn.Module): | ||
| 39 | + def __init__(self, m): | ||
| 40 | + super().__init__() | ||
| 41 | + self.conv1, self.bn1, self.relu, self.maxpool = m.conv1, m.bn1, m.relu, m.maxpool | ||
| 42 | + | ||
| 43 | + def forward(self, x): | ||
| 44 | + return self.maxpool(self.relu(self.bn1(self.conv1(x)))) | ||
| 45 | + | ||
| 46 | + | ||
| 47 | +class Body123(nn.Module): | ||
| 48 | + def __init__(self, m): | ||
| 49 | + super().__init__() | ||
| 50 | + self.stem = Stem(m) | ||
| 51 | + self.layer1, self.layer2, self.layer3 = m.layer1, m.layer2, m.layer3 | ||
| 52 | + | ||
| 53 | + def forward(self, x): | ||
| 54 | + return self.layer3(self.layer2(self.layer1(self.stem(x)))) | ||
| 55 | + | ||
| 56 | + | ||
| 57 | +print("=== A) MLIR + dynamic=False 是否修复 ===") | ||
| 58 | +with torch.no_grad(): | ||
| 59 | + eager_ref = Body123(model)(x) | ||
| 60 | + m1 = torch.compile(Body123(model), options={"npu_backend": "mlir"}, dynamic=False) | ||
| 61 | + got1 = m1(x) | ||
| 62 | + m2 = torch.compile(Body123(model), options={"npu_backend": "mlir"}, dynamic=True) | ||
| 63 | + got2 = m2(x) | ||
| 64 | +d1 = float((got1 - eager_ref).abs().max()) | ||
| 65 | +d2 = float((got2 - eager_ref).abs().max()) | ||
| 66 | +print(f"dynamic=False: max_diff={d1:.6f} {'OK(已修复)' if d1 < 1e-3 else '仍 WRONG'}") | ||
| 67 | +print(f"dynamic=True : max_diff={d2:.6f} {'OK(已修复)' if d2 < 1e-3 else '仍 WRONG'}") | ||
| 68 | + | ||
| 69 | +print() | ||
| 70 | +print("=== B) Triton 性能规避方案 ===") | ||
| 71 | +with torch.no_grad(): | ||
| 72 | + t_eager = bench(lambda: model(x)) | ||
| 73 | + c1 = torch.compile(model, backend="inductor", dynamic=False) | ||
| 74 | + t_c1 = bench(lambda: c1(x)) | ||
| 75 | + c2 = torch.compile(model, backend="inductor", mode="reduce-overhead", dynamic=False) | ||
| 76 | + t_c2 = bench(lambda: c2(x)) | ||
| 77 | + with torch.autocast("npu", dtype=torch.bfloat16): | ||
| 78 | + ref_bf = model(x) | ||
| 79 | + def run_bf(): | ||
| 80 | + with torch.no_grad(), torch.autocast("npu", dtype=torch.bfloat16): | ||
| 81 | + c3(x) | ||
| 82 | + c3 = torch.compile(model, backend="inductor", dynamic=False) | ||
| 83 | + run_bf() # 编译 | ||
| 84 | + t_c3 = bench(run_bf) | ||
| 85 | + c3_bf_out = None | ||
| 86 | + with torch.no_grad(), torch.autocast("npu", dtype=torch.bfloat16): | ||
| 87 | + c3_bf_out = c3(x) | ||
| 88 | + diff_bf = float((c3_bf_out.float() - ref_bf.float()).abs().max()) | ||
| 89 | + | ||
| 90 | +print(f"Eager fp32 : {t_eager*1000:7.2f} ms") | ||
| 91 | +print(f"Inductor-Triton fp32 : {t_c1*1000:7.2f} ms ({t_eager/t_c1:.2f}x)") | ||
| 92 | +print(f"+mode=reduce-overhead: {t_c2*1000:7.2f} ms ({t_eager/t_c2:.2f}x)") | ||
| 93 | +print(f"bf16 autocast(Triton): {t_c3*1000:7.2f} ms ({t_eager/t_c3:.2f}x, vs eager-bf16 正确性 max_diff={diff_bf:.4f})") | ||
| 94 | +with torch.no_grad(), torch.autocast("npu", dtype=torch.bfloat16): | ||
| 95 | + t_eager_bf = bench(lambda: model(x)) | ||
| 96 | +print(f"Eager bf16 autocast : {t_eager_bf*1000:7.2f} ms (eager 自身 bf16 收益)") | ||
| 97 | +print("=== done ===") | ||
| @@ -0,0 +1,76 @@ | |||
| 1 | +# MLIR 数值错误定位过程(torchvision resnet18,2026-09-23 重测) | ||
| 2 | + | ||
| 3 | +## 实验结论总览 | ||
| 4 | + | ||
| 5 | +| 编译单元 | MLIR dynamic=False 结果 | | ||
| 6 | +|---|---| | ||
| 7 | +| 逐算子(conv/linear/bn/relu/pool) | 全部正确 | | ||
| 8 | +| Conv+BN+ReLU、残差块、完整 BasicBlock(多尺寸) | 全部正确 | | ||
| 9 | +| stem / layer1 / layer2 / layer3 / layer4 各自单独编译 | 全部正确 | | ||
| 10 | +| 多 stage 组合编译为同一张图 | **部分组合出错**(见下) | | ||
| 11 | +| 完整 resnet18 整图 | **错误**(2.7.1.post8: max_diff 9.814e-01;2.9.0.post6: max_diff NaN) | | ||
| 12 | + | ||
| 13 | +## 关键发现:触发组合随「版本 × 数据」变化 | ||
| 14 | + | ||
| 15 | +同一脚本在不同条件下,首个出错的组合不同: | ||
| 16 | + | ||
| 17 | +| 条件 | 出错的组合 | 正常的组合 | | ||
| 18 | +|---|---|---| | ||
| 19 | +| 2.9.0 + 内置定义权重(旧实验) | stem+layer1+layer2+layer3 | layer1+2+3+4(不含 stem) | | ||
| 20 | +| 2.9.0 + torchvision 权重 | layer3+layer4;stem+layer1..4 | stem+layer1+layer2+layer3 | | ||
| 21 | +| 2.7.1 + torchvision 权重 | 完整 resnet18 | stem+layer1+layer2+layer3(Body123) | | ||
| 22 | + | ||
| 23 | +不变式:**单算子/单 stage 编译恒正确;错误仅出现在多个 stage 编译为同一张图时; | ||
| 24 | +具体哪个组合触发随版本与数据变化。** | ||
| 25 | + | ||
| 26 | +## 机制解释(与实测证据一致) | ||
| 27 | + | ||
| 28 | +1. MLIR 融合 pass 把 BN+ReLU/BN+add+ReLU 打包为单一融合 kernel(如 | ||
| 29 | + `mlir_fused__native_batch_norm_legit_no_training_add_relu_2`) | ||
| 30 | +2. 融合 kernel 经 torch-mlir 编译调优时**按实际数据校验精度**,报 | ||
| 31 | + `Tuning accuracy failed, no valid kernels found, using fallback`—— | ||
| 32 | + 是否失败与运行数据相关,因此触发组合随权重/输入变化 | ||
| 33 | +3. 调优失败后静默回退到 FX 图执行(`mlir_auto_fallback` → `_load_fx_graph`), | ||
| 34 | + 但回退路径不遵守 `kernel.run(..., bufN)` 的"写入预分配 buffer"调用约定 | ||
| 35 | +4. 后续算子读到未初始化/残留的设备内存 → 数值错误。NaN(读未触碰内存)与 | ||
| 36 | + 错值(读上一次运行的残留数据)两种表现均实测到,且错误幅度跨进程不稳定 | ||
| 37 | + (0.28~2.0 / NaN),均与此机制吻合 | ||
| 38 | + | ||
| 39 | +## A/B 规避方案验证(dynamic=True) | ||
| 40 | + | ||
| 41 | +`torch.compile(..., options={"npu_backend": "mlir"}, dynamic=True)` 完全修复: | ||
| 42 | + | ||
| 43 | +``` | ||
| 44 | +配置 eager基线 absmax max_diff 判定 | ||
| 45 | +------------------------------------------------------------------------ | ||
| 46 | +eager(基线,两次自检) 10.0006 0.000000 PASS | ||
| 47 | +MLIR dynamic=False(默认) 10.0006 1.000986 FAIL(数值错误) | ||
| 48 | +MLIR dynamic=True(规避) 10.0006 0.000000 PASS | ||
| 49 | +``` | ||
| 50 | + | ||
| 51 | +(完整代码:`mlir_dynamic_AB_test.py`;输出日志:`mlir_dynamic_AB_test_output.log`) | ||
| 52 | + | ||
| 53 | +## 其他规避尝试(均无效) | ||
| 54 | + | ||
| 55 | +- `dynamic=False`(默认):错误依旧 | ||
| 56 | +- `TORCHINDUCTOR_LAYOUT_OPTIMIZATION=0`:**输出 NaN**(触发另一条错误路径) | ||
| 57 | +- bf16 autocast / mode=reduce-overhead(Triton 路径的尝试,对 MLIR 不适用) | ||
| 58 | + | ||
| 59 | +## 运行环境 | ||
| 60 | + | ||
| 61 | +- 逐算子/二分/A_B 实验:PyTorch 2.9.0 + torch_npu 2.9.0.post6 + torchvision 0.24.0 | ||
| 62 | + (Guard Filter 同款 venv;2.7.1 侧验证用系统 python3.11 + torchvision 0.22.1) | ||
| 63 | +- 硬件:Atlas 800T A2(Ascend 910B3)/ CANN 9.1.0 / torch-mlir 0.0.1(oepkgs) | ||
| 64 | + | ||
| 65 | +## 实验脚本清单 | ||
| 66 | + | ||
| 67 | +| 脚本 | 内容 | | ||
| 68 | +|---|---| | ||
| 69 | +| `mlir_narrow.py` | 逐算子正确性(conv/linear/bn/relu/pool 及小串联) | | ||
| 70 | +| `mlir_narrow2.py` | Conv+BN+ReLU、残差块、完整 BasicBlock(32/64/224/56 多尺寸) | | ||
| 71 | +| `mlir_stage_bisect.py` | stem / layer1~4 逐段单独编译对比 | | ||
| 72 | +| `mlir_tail.py` | 分类尾部(avgpool+flatten+fc)与主体整图 | | ||
| 73 | +| `mlir_min_combo.py` | 多 stage 最小失败组合搜索(layer3+layer4 等) | | ||
| 74 | +| `mlir_stem_combo.py` | stem+N 层组合二分 | | ||
| 75 | +| `mlir_dynamic_AB_test.py` | dynamic=True/False A/B 规避方案验证(日志在旁) | | ||
| 76 | +| `workaround_test.py` | 规避方案组合测试(含 LAYOUT_OPTIMIZATION=0 → NaN) | | ||
| @@ -0,0 +1,37 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | +# TorchNPU 26.1.0 众测任务环境初始化脚本(自适应版本) | ||
| 3 | +# 用法:source env_setup.sh | ||
| 4 | +# | ||
| 5 | +# 说明:自动探测本机 CANN 安装路径与 Python 解释器; | ||
| 6 | +# 探测失败时请按 补充-测试报告与原始日志/环境搭建.md 手动配置。 | ||
| 7 | + | ||
| 8 | +# ---------- 1. 昇腾驱动库路径(libhccl.so 等所在) ---------- | ||
| 9 | +for DRV in /usr/local/Ascend/driver/lib64 /usr/local/Ascend/driver/lib64/common /usr/local/Ascend/driver/lib64/driver; do | ||
| 10 | + [ -d "$DRV" ] && export LD_LIBRARY_PATH="$DRV:$LD_LIBRARY_PATH" | ||
| 11 | +done | ||
| 12 | + | ||
| 13 | +# ---------- 2. CANN 环境变量(按优先级探测) ---------- | ||
| 14 | +CANN_ENV_FOUND="" | ||
| 15 | +for CANN_ENV in \ | ||
| 16 | + /usr/local/Ascend/cann-9.1.0/set_env.sh \ | ||
| 17 | + /usr/local/Ascend/ascend-toolkit/set_env.sh \ | ||
| 18 | + /usr/local/Ascend/ascend-toolkit/latest/set_env.sh \ | ||
| 19 | + /usr/local/Ascend/nnae/set_env.sh | ||
| 20 | +do | ||
| 21 | + if [ -f "$CANN_ENV" ]; then | ||
| 22 | + source "$CANN_ENV" 2>/dev/null | ||
| 23 | + CANN_ENV_FOUND="$CANN_ENV" | ||
| 24 | + break | ||
| 25 | + fi | ||
| 26 | +done | ||
| 27 | +if [ -z "$CANN_ENV_FOUND" ]; then | ||
| 28 | + echo "[env_setup] 警告: 未找到 CANN set_env.sh(探测路径见脚本内列表),请确认 CANN 9.1.0 已安装" >&2 | ||
| 29 | +fi | ||
| 30 | + | ||
| 31 | +# ---------- 3. Python 解释器(优先 3.11;众测镜像通常已预装 torch 环境) ---------- | ||
| 32 | +if command -v python3.11 >/dev/null 2>&1 && ! python3 -c "import torch_npu" >/dev/null 2>&1; then | ||
| 33 | + export PATH="$(dirname "$(command -v python3.11)"):$PATH" | ||
| 34 | + hash -r 2>/dev/null | ||
| 35 | +fi | ||
| 36 | + | ||
| 37 | +echo "[env_setup] CANN: ${CANN_ENV_FOUND:-未探测到} | python3: $(python3 -c 'import sys; print(sys.version.split()[0])' 2>/dev/null || echo '?') | torch_npu: $(python3 -c 'import torch_npu; print(torch_npu.__version__)' 2>/dev/null || echo '未安装(见 环境搭建.md)')" | ||
| @@ -0,0 +1,59 @@ | |||
| 1 | +# 社区 Issue 提交记录 | ||
| 2 | + | ||
| 3 | +提交仓库:`Ascend/pytorch`(https://gitcode.com/Ascend/pytorch/issues) | ||
| 4 | +提交账号:`gcw_8p1hhlB0` | ||
| 5 | +提交时间:2026-09-15 | ||
| 6 | + | ||
| 7 | +| # | 标题 | 链接 | 类型 | | ||
| 8 | +|---|---|---|---| | ||
| 9 | +| 1 | 【26.2.0众测】Inductor-MLIR整图编译resnet18输出数值错误(与Eager差异0.28~2.0,dynamic=True可规避),已二分定位到临界编译组合 | https://gitcode.com/Ascend/pytorch/issues/4831 | Bug/正确性 | | ||
| 10 | +| 2 | 【26.2.0众测】Inductor默认后端(Triton)编译resnet18推理比Eager慢25~79倍:bf16/reduce-overhead/dynamic=False均无法规避 | https://gitcode.com/Ascend/pytorch/issues/4832 | 性能 | | ||
| 11 | +| 3 | 【26.2.0众测】GuardFilter无法消除GRAD_MODE/DETERMINISTIC_ALGORITHMS类重编译:运行时状态校验走C层GlobalStateGuard,不经过guard_filter_fn通道 | https://gitcode.com/Ascend/pytorch/issues/4833 | 文档/功能限制 | | ||
| 12 | +| 4 | 【26.2.0众测】PyTorch 2.9.0 Dynamo:输入张量带 `_dynamo_dynamic_indices`(list)触发重编译时抛 InternalTorchDynamoError(add_dynamic_indices_guard 期望 set 收到 list),建议同步上游 | https://gitcode.com/Ascend/pytorch/issues/5122 | Bug/崩溃(已验证纯CPU复现) | | ||
| 13 | + | ||
| 14 | +## Issue 1 要点 | ||
| 15 | + | ||
| 16 | +- 环境:Atlas 800T A2(910B3)/ CANN 9.1.0 / torch 2.9.0 + torch_npu 2.9.0.post6 / | ||
| 17 | + triton 3.5.0 + triton_ascend 3.2.2 / torch-mlir 0.0.1(repo.oepkgs.net) | ||
| 18 | +- 现象:MLIR 模式整图编译 resnet18,输出与 Eager 差异 0.28~2.0(跨进程幅度不稳定), | ||
| 19 | + 输出量级改变(eager absmax=0.16 vs mlir 0.71) | ||
| 20 | +- 已二分定位:逐算子/单 stage/小组合全部正确;失败临界点为 | ||
| 21 | + stem+layer1+layer2+layer3 编译为同一张图;伴随大量 Kernel compiled failed 静默回退 | ||
| 22 | +- 规避方案(已验证):`torch.compile(..., options={"npu_backend": "mlir"}, dynamic=True)` | ||
| 23 | + 完全修复(max_diff=0.000000);定位实验详见 `MLIR问题定位/` | ||
| 24 | + | ||
| 25 | +## Issue 2 要点 | ||
| 26 | + | ||
| 27 | +- Inductor 默认后端(Triton)推理比 Eager 慢 25~79 倍(bs=8/32/64 三组数据), | ||
| 28 | + batch 越大退化越严重 | ||
| 29 | +- 已排除:图断裂/dynamic shape/首次编译开销;bf16、reduce-overhead、dynamic=False | ||
| 30 | + 均无法规避 → 慢在 Triton 生成 kernel 的设备执行时间 | ||
| 31 | +- 对照组:DVM 1.38x / NPUGraph_EX 1.47x / TorchAir-GE 1.95x 均有收益 | ||
| 32 | + | ||
| 33 | +## Issue 3 要点 | ||
| 34 | + | ||
| 35 | +- Guard Filter 文档示例2宣称"切换grad mode不会触发重编译",实测无法实现 | ||
| 36 | +- 根因:运行时状态校验走 C 层 GlobalStateGuard(torch/_dynamo/guards.py:2433), | ||
| 37 | + 不经过 guard_filter_fn 通道;TORCH_LOGS=recompiles 显示失效 guard 为 | ||
| 38 | + GLOBAL_STATE changed | ||
| 39 | +- 对照组:dict 类/按名/全局变量 guard 过滤均有效(证明通道本身正常) | ||
| 40 | + | ||
| 41 | +## 材料 PR | ||
| 42 | + | ||
| 43 | +- https://gitcode.com/Ascend/pytorch-ecosystem/merge_requests/38 | ||
| 44 | + (源 `gcw_8p1hhlB0:master` → `Ascend/pytorch-ecosystem:master`) | ||
| 45 | + | ||
| 46 | +## 查重说明(2026-09-25,按群公告要求补充) | ||
| 47 | + | ||
| 48 | +- #4831(MLIR)与 #4793/#4836/#5063/#5116 属同一底层 bug 的不同模型/表现, | ||
| 49 | + 已在 #4831 评论中关联;本 issue 的增量价值:根因定位(融合kernel调优失败→ | ||
| 50 | + FX回退不写预分配buffer)+ dynamic=True 规避方案 + 双版本验证 | ||
| 51 | +- #4833(GuardFilter)与 #4767 及后续同类相关,已在 #4833 评论中关联; | ||
| 52 | + 增量价值:C 层 GlobalStateGuard 源码级定位 + 过滤通道有效性对照组 | ||
| 53 | +- #4832(Triton 性能)为该问题首个报告(后续 #4851 为同类) | ||
| 54 | +- #5122(Dynamo 崩溃):查重无同类,独立新问题 | ||
| 55 | + | ||
| 56 | +## 补充说明 | ||
| 57 | + | ||
| 58 | +调试提交 API 过程中曾误创建 23 个重复测试 issue(#4808~#4830),已全部改名为 | ||
| 59 | +"[重复-请忽略]"前缀标记以便甄别;正式 issue 为上表 4 个,带来困扰深表歉意。 | ||
| @@ -0,0 +1,81 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | +# TorchNPU 众测任务2(torch.compile)一键运行脚本(双环境版) | ||
| 3 | +# | ||
| 4 | +# 环境策略(按官方配套表,均为 TorchNPU 26.1.0 配套组合): | ||
| 5 | +# - 示例 01~06(Guard Filter):venv PyTorch 2.9.0 + torch_npu 2.9.0.post6 | ||
| 6 | +# (Guard Filter 文档要求 PyTorch >= 2.9.0,guard_filter_fn 选项 2.9.0 才引入) | ||
| 7 | +# - 示例 07~13(7 个编译后端):系统 PyTorch 2.7.1 + torch_npu 2.7.1.post8 | ||
| 8 | +# (环境预装的配套版本) | ||
| 9 | +# | ||
| 10 | +# 用法: | ||
| 11 | +# ./run_task3.sh 01 # 按序号运行单个示例(01~06 Guard Filter,07~13 各后端) | ||
| 12 | +# ./run_task3.sh all # 顺序运行全部 13 个示例 | ||
| 13 | +# | ||
| 14 | +# 说明: | ||
| 15 | +# - 自动设置 TORCHINDUCTOR_COMPILE_THREADS=8(默认 32 会在 32GB 容器内 OOM 被杀) | ||
| 16 | +# - 编译缓存:首次编译 Triton 约 200s、MLIR 约 40 分钟,缓存后约 30~90s | ||
| 17 | + | ||
| 18 | +cd "$(dirname "$0")" # 补充-测试报告与原始日志/ | ||
| 19 | +source ./env_setup.sh | ||
| 20 | +cd .. # 材料根目录 | ||
| 21 | + | ||
| 22 | +export TORCHINDUCTOR_CACHE_DIR="${TORCHINDUCTOR_CACHE_DIR:-/tmp/inductor_cache}" | ||
| 23 | +export TORCHINDUCTOR_COMPILE_THREADS=8 | ||
| 24 | +# 解释器:支持环境变量覆盖;自动探测常见位置(搭建方法见 环境搭建.md) | ||
| 25 | +PY29="${PY29:-}" # Guard Filter 专用(torch 2.9.0 + torch_npu 2.9.0.post6 venv) | ||
| 26 | +if [ -z "$PY29" ]; then | ||
| 27 | + for c in /root/venv_torch29/bin/python "$HOME/venv_torch29/bin/python" "$PWD/venv_torch29/bin/python"; do | ||
| 28 | + [ -x "$c" ] && PY29="$c" && break | ||
| 29 | + done | ||
| 30 | +fi | ||
| 31 | +PY27="${PY27:-}" # 后端示例用(torch 2.7.1 + torch_npu 2.7.1.post8,众测镜像预装) | ||
| 32 | +if [ -z "$PY27" ]; then | ||
| 33 | + if python3.11 -c "import torch_npu" >/dev/null 2>&1; then PY27=python3.11 | ||
| 34 | + elif python3 -c "import torch_npu" >/dev/null 2>&1; then PY27=python3 | ||
| 35 | + else PY27=python3.11; fi | ||
| 36 | +fi | ||
| 37 | +T2="任务2-torch.compile体验与实践" | ||
| 38 | + | ||
| 39 | +V27=$($PY27 -c "import torch_npu; print(torch_npu.__version__)" 2>/dev/null | tail -1) | ||
| 40 | +V29=$($PY29 -c "import torch; print(torch.__version__)" 2>/dev/null | tail -1) | ||
| 41 | +echo "[run_task3] 后端环境: $PY27 + torch_npu ${V27:-缺失} | GuardFilter环境: ${PY29} + torch ${V29:-缺失} | threads=$TORCHINDUCTOR_COMPILE_THREADS" | ||
| 42 | +if [[ ! "$V29" =~ ^2\.(9|1[0-9])\. ]]; then | ||
| 43 | + echo "[run_task3] 警告: Guard Filter 需要 PyTorch >= 2.9.0 的 venv(当前 ${V29:-无}),示例01~06将无法运行;搭建方法见 环境搭建.md(也可用 PY29=<路径> 指定)" >&2 | ||
| 44 | +fi | ||
| 45 | + | ||
| 46 | +declare -A BACKEND=( | ||
| 47 | + [07]="Inductor-Triton" [08]="Inductor-MLIR" [09]="Inductor-DVM" | ||
| 48 | + [10]="NPUGraphs" [11]="NPUGraph_EX" [12]="AOT_Eager" [13]="TorchAir-GE" | ||
| 49 | +) | ||
| 50 | + | ||
| 51 | +run_one() { | ||
| 52 | + local n="$1" script="" | ||
| 53 | + local py="" | ||
| 54 | + if [[ "$n" =~ ^0[1-6]$ ]]; then | ||
| 55 | + local m="${n#0}" # 01~06 → 1~6(Guard Filter 脚本命名为 1_~6_,无前导零) | ||
| 56 | + script=$(ls "$T2/Guard Filter/${m}_"*.py 2>/dev/null | head -1) | ||
| 57 | + py="$PY29" | ||
| 58 | + elif [[ -n "${BACKEND[$n]:-}" ]]; then | ||
| 59 | + script="$T2/${BACKEND[$n]}/1_改写脚本.py" | ||
| 60 | + py="$PY27" | ||
| 61 | + fi | ||
| 62 | + if [ -z "$script" ] || [ ! -f "$script" ]; then | ||
| 63 | + echo "[run_task3] 错误: 未找到示例 $n 对应脚本" >&2 | ||
| 64 | + return 1 | ||
| 65 | + fi | ||
| 66 | + echo "==================== 示例 $n: $script ====================" | ||
| 67 | + "$py" "$script" | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +ARG="${1:-}" | ||
| 71 | +if [ "$ARG" = "all" ]; then | ||
| 72 | + for n in 01 02 03 04 05 06 07 08 09 10 11 12 13; do | ||
| 73 | + run_one "$n" || echo "[run_task3] 示例 $n 运行失败(继续下一个)" | ||
| 74 | + done | ||
| 75 | + echo "[run_task3] 全部完成" | ||
| 76 | +elif [[ "$ARG" =~ ^[0-9]{2}$ ]]; then | ||
| 77 | + run_one "$ARG" | ||
| 78 | +else | ||
| 79 | + echo "用法: $0 <01~13|all>" | ||
| 80 | + exit 1 | ||
| 81 | +fi | ||