已合并
【26.2.0众测】ArgoNorthrop 任务1+任务2 交付件 #39
程康博创建于 9月15日
【26.2.0众测】ArgoNorthrop 任务1+任务2 交付件 #39
已合并
共 54 个文件变更+1985-0
| @@ -0,0 +1,64 @@ | |||
| 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.1 软件安装证明材料(存放于「软件安装/」文件夹) | ||
| 29 | + ----------------------------------------------------------------- | ||
| 30 | + 文件1:1_pip_list截图.png | ||
| 31 | + - 内容:终端执行 pip list 的输出截图,需能看到 torch、torch_npu 等包 | ||
| 32 | + - 要求:截图清晰,包名和版本号可读 | ||
| 33 | + - 文件格式:PNG 或 JPG | ||
| 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 | + - 要求:输出如下类似信息说明安装成功。截图清晰。 | ||
| 39 | + tensor([[-0.6066, 6.3385, 0.0379, 3.3356], | ||
| 40 | + [ 2.9243, 3.3134, -1.5465, 0.1916], | ||
| 41 | + [-2.1807, 0.2008, -1.1431, 2.1523]], device='npu:0') | ||
| 42 | + - 文件格式:PNG 或 JPG | ||
| 43 | + | ||
| 44 | + ★ 4.2 快速入门证明材料(存放于「快速入门/」文件夹) | ||
| 45 | + ----------------------------------------------------------------- | ||
| 46 | + 文件1:1_训练脚本.py | ||
| 47 | + - 内容:您实际运行的训练脚本 | ||
| 48 | + - 要求:脚本可独立运行,包含必要的注释说明 | ||
| 49 | + - 文件命名:1_训练脚本.py | ||
| 50 | + - 文件格式:.py | ||
| 51 | + | ||
| 52 | + 文件2:2_训练成功截图.png | ||
| 53 | + - 内容:训练脚本正常执行完毕的终端截图 | ||
| 54 | + - 要求:能看到训练正常输出(loss 打印、epoch 进度等),无报错中断 | ||
| 55 | + - 文件格式:PNG 或 JPG | ||
| 56 | + | ||
| 57 | + 文件3:3_权重文件截图.png | ||
| 58 | + - 内容:生成的 checkpoint.pth.tar 权重文件的截图 | ||
| 59 | + - 要求:可通过 ls -l 命令展示文件存在及大小,或文件管理器截图 | ||
| 60 | + - 文件格式:PNG 或 JPG | ||
| 61 | + - 注意:权重文件本身较大,无需上传,只需截图证明已生成 | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +===================================================================== | ||
| @@ -0,0 +1,77 @@ | |||
| 1 | +# 引入模块 | ||
| 2 | +import time | ||
| 3 | +import torch | ||
| 4 | +import torch.nn as nn | ||
| 5 | +from torch.utils.data import DataLoader | ||
| 6 | +import torchvision | ||
| 7 | + | ||
| 8 | +# =========【迁移新增:使能自动迁移】========= | ||
| 9 | +import torch_npu | ||
| 10 | +from torch_npu.contrib import transfer_to_npu | ||
| 11 | + | ||
| 12 | +# 初始化运行device(自动迁移方案:GPU代码原样保留cuda:0,不修改!) | ||
| 13 | +device = torch.device('cuda:0') | ||
| 14 | + | ||
| 15 | +# 定义模型网络【GPU原版代码,完全不变】 | ||
| 16 | +class CNN(nn.Module): | ||
| 17 | + def __init__(self): | ||
| 18 | + super(CNN, self).__init__() | ||
| 19 | + self.net = nn.Sequential( | ||
| 20 | + nn.Conv2d(in_channels=1, out_channels=16, | ||
| 21 | + kernel_size=(3, 3), | ||
| 22 | + stride=(1, 1), | ||
| 23 | + padding=1), | ||
| 24 | + nn.MaxPool2d(kernel_size=2), | ||
| 25 | + nn.Conv2d(16, 32, 3, 1, 1), | ||
| 26 | + nn.MaxPool2d(kernel_size=2), | ||
| 27 | + nn.Flatten(), | ||
| 28 | + nn.Linear(32*7*7, 16), | ||
| 29 | + nn.ReLU(), | ||
| 30 | + nn.Linear(16, 10) | ||
| 31 | + ) | ||
| 32 | + def forward(self, x): | ||
| 33 | + return self.net(x) | ||
| 34 | + | ||
| 35 | +# 下载数据集【与官方快速入门文档示例一致】 | ||
| 36 | +train_data = torchvision.datasets.MNIST( | ||
| 37 | + root='mnist', | ||
| 38 | + download=True, | ||
| 39 | + train=True, | ||
| 40 | + transform=torchvision.transforms.ToTensor() | ||
| 41 | +) | ||
| 42 | + | ||
| 43 | +# 定义训练相关参数【GPU原版代码,完全不变】 | ||
| 44 | +batch_size = 64 | ||
| 45 | +model = CNN().to(device) | ||
| 46 | +train_dataloader = DataLoader(train_data, batch_size=batch_size) | ||
| 47 | +loss_func = nn.CrossEntropyLoss().to(device) | ||
| 48 | +optimizer = torch.optim.SGD(model.parameters(), lr=0.1) | ||
| 49 | +epochs = 10 | ||
| 50 | + | ||
| 51 | +# 设置循环【GPU原版代码,完全不变:Atlas A2 可不开启AMP】 | ||
| 52 | +for epoch in range(epochs): | ||
| 53 | + running_loss = 0.0 | ||
| 54 | + for imgs, labels in train_dataloader: | ||
| 55 | + start_time = time.time() # 记录训练开始时间 | ||
| 56 | + imgs = imgs.to(device) # 把img数据放到指定设备上 | ||
| 57 | + labels = labels.to(device) # 把label数据放到指定设备上 | ||
| 58 | + outputs = model(imgs) # 前向计算 | ||
| 59 | + loss = loss_func(outputs, labels) # 损失函数计算 | ||
| 60 | + optimizer.zero_grad() | ||
| 61 | + loss.backward() # 损失函数反向计算 | ||
| 62 | + optimizer.step() # 更新优化器 | ||
| 63 | + running_loss += loss.item() | ||
| 64 | + # 打印每个 epoch 的平均 loss,便于截图确认训练正常推进 | ||
| 65 | + print(f"Epoch [{epoch+1}/{epochs}] loss={running_loss/len(train_dataloader):.4f} " | ||
| 66 | + f"耗时={time.time()-start_time:.2f}s/最后batch", flush=True) | ||
| 67 | + | ||
| 68 | +print("训练完成,开始保存权重 checkpoint.pth.tar ...", flush=True) | ||
| 69 | + | ||
| 70 | +# 定义保存模型【GPU原版代码,完全不变】 | ||
| 71 | +torch.save({ | ||
| 72 | + 'epoch': 10, | ||
| 73 | + 'arch': CNN, | ||
| 74 | + 'state_dict': model.state_dict(), | ||
| 75 | + 'optimizer' : optimizer.state_dict(), | ||
| 76 | +},'checkpoint.pth.tar') | ||
| 77 | +print("权重已保存:checkpoint.pth.tar", flush=True) | ||
Binary files do not support preview
| @@ -0,0 +1,99 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · AOT_Eager(AOT Autograd + Eager,无图融合/算子编译) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 7 | + | ||
| 8 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8): | ||
| 9 | + env -u CI OMP_NUM_THREADS=1 TORCHINDUCTOR_COMPILE_THREADS=1 \ | ||
| 10 | + HF_HOME=/root/.cache/huggingface python 1_改写脚本.py | ||
| 11 | +""" | ||
| 12 | +import os | ||
| 13 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 14 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 15 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 16 | + | ||
| 17 | +import time | ||
| 18 | +import torch | ||
| 19 | +import torch_npu # noqa: F401 导入即注册 NPU 后端 | ||
| 20 | +from transformers import DistilBertForMaskedLM | ||
| 21 | + | ||
| 22 | +BACKEND_NAME = "aot_eager" | ||
| 23 | +BATCH, SEQ_LEN = 4, 128 | ||
| 24 | +WARMUP, ITERS = 3, 20 | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +def sync(): | ||
| 28 | + torch.npu.synchronize() | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 32 | + with torch.no_grad(): | ||
| 33 | + for _ in range(warmup): | ||
| 34 | + fn() | ||
| 35 | + sync() | ||
| 36 | + t0 = time.perf_counter() | ||
| 37 | + for _ in range(iters): | ||
| 38 | + fn() | ||
| 39 | + sync() | ||
| 40 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +def main(): | ||
| 44 | + torch.npu.set_device(0) | ||
| 45 | + print("=" * 72) | ||
| 46 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 47 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 48 | + f"device={torch.npu.get_device_name(0)}") | ||
| 49 | + print("=" * 72) | ||
| 50 | + | ||
| 51 | + # 1) 加载模型与输入 ---------------------------------------------------- | ||
| 52 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 53 | + model.to("npu").eval() | ||
| 54 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 55 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 56 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}") | ||
| 57 | + | ||
| 58 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 59 | + def run_eager(): | ||
| 60 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 61 | + | ||
| 62 | + t_eager = bench(run_eager) | ||
| 63 | + with torch.no_grad(): | ||
| 64 | + out_eager = run_eager() | ||
| 65 | + | ||
| 66 | + # 3) 编译后端 --------------------------------------------------------- | ||
| 67 | + t0 = time.perf_counter() | ||
| 68 | + compiled = torch.compile(model, backend=BACKEND_NAME) | ||
| 69 | + with torch.no_grad(): | ||
| 70 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次触发编译 | ||
| 71 | + sync() | ||
| 72 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 73 | + | ||
| 74 | + def run_compiled(): | ||
| 75 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 76 | + | ||
| 77 | + t_compiled = bench(run_compiled) | ||
| 78 | + with torch.no_grad(): | ||
| 79 | + out_compiled = run_compiled() | ||
| 80 | + | ||
| 81 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 82 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 83 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 84 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 85 | + speedup = t_eager / t_compiled | ||
| 86 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 87 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 88 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 89 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 90 | + print(f" 首次编译耗时 = {t_compile:9.1f} ms") | ||
| 91 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 92 | + print(f" {BACKEND_NAME:14s}= {t_compiled:9.3f} ms/次") | ||
| 93 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 94 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 95 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 96 | + | ||
| 97 | + | ||
| 98 | +if __name__ == "__main__": | ||
| 99 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/AOT_Eager/2_正确性验证截图.png+0-0
| @@ -0,0 +1,156 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · Guard Filter · 示例1:过滤“字典版本 / 键集合”类 guard | ||
| 4 | +================================================================ | ||
| 5 | +被测模型:distilbert-base-uncased(DistilBertForMaskedLM,MLM 头,离线 HF 缓存) | ||
| 6 | +说明:本脚本没有任何自建网络模型——编译对象就是一个普通 Python 函数,函数内部 | ||
| 7 | + 调用真实的 DistilBertForMaskedLM,仅在其 logits 上叠加一个生成配置读取。 | ||
| 8 | + | ||
| 9 | +场景(还原 HuggingFace generation_config 字典的典型用法): | ||
| 10 | + 通过 cfg.get("temperature", 默认值) 读取一个“当前不存在”的键。Dynamo 会对 | ||
| 11 | + 该字典安装 DICT_CONTAINS / DICT_KEYS_MATCH / NOT_PRESENT_IN_GENERIC_DICT 等 | ||
| 12 | + guard;外部给字典补上这个键(字典 version 变化)后,默认行为就是【重新编译】。 | ||
| 13 | + 该变化不影响复用同一份计算图,于是用 guard_filter_fn 把字典类 guard 过滤掉, | ||
| 14 | + 再次补键时直接命中缓存、不再重编译。 | ||
| 15 | + | ||
| 16 | +判定方法:torch.compiler.set_stance("fail_on_recompile") | ||
| 17 | + —— 调用期间若发生重编译会抛 RuntimeError,据此判断。 | ||
| 18 | +运行:env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 19 | + python 1_过滤字典版本guard.py | ||
| 20 | +""" | ||
| 21 | +import os | ||
| 22 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 23 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 24 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 25 | + | ||
| 26 | +import time | ||
| 27 | +import torch | ||
| 28 | +import torch_npu # noqa: F401 导入即把 PyTorch 的后端桥接到 NPU | ||
| 29 | +from transformers import DistilBertForMaskedLM | ||
| 30 | + | ||
| 31 | +torch.npu.set_device(0) | ||
| 32 | +DEV = "npu" | ||
| 33 | +BATCH, SEQ_LEN = 2, 64 | ||
| 34 | + | ||
| 35 | +# 需要过滤的“字典版本/键集合”类 guard | ||
| 36 | +DICT_GUARDS = frozenset({ | ||
| 37 | + "DICT_VERSION", "DICT_KEYS", "DICT_KEYS_MATCH", | ||
| 38 | + "DICT_CONTAINS", "NOT_PRESENT_IN_GENERIC_DICT", | ||
| 39 | +}) | ||
| 40 | + | ||
| 41 | + | ||
| 42 | +def fwd_with_cfg(bert, input_ids, attention_mask, cfg): | ||
| 43 | + """以真实 DistilBert 为核心的前向函数,仅叠加一个生成配置字典读取。""" | ||
| 44 | + logits = bert(input_ids=input_ids, | ||
| 45 | + attention_mask=attention_mask).logits | ||
| 46 | + temperature = cfg.get("temperature", 1.0) # 首次该键缺失 -> DICT_CONTAINS | ||
| 47 | + return logits * temperature | ||
| 48 | + | ||
| 49 | + | ||
| 50 | +def make_filter(keep_rule, store): | ||
| 51 | + """构造 guard_filter_fn,同时把本次安装的 guard 记录到 store 里。""" | ||
| 52 | + | ||
| 53 | + def guard_filter_fn(entries): | ||
| 54 | + store.clear() | ||
| 55 | + store.extend([(str(e.guard_type), str(e.name)) for e in entries]) | ||
| 56 | + return [bool(keep_rule(e)) for e in entries] | ||
| 57 | + | ||
| 58 | + return guard_filter_fn | ||
| 59 | + | ||
| 60 | + | ||
| 61 | +def recompiles_on_changed_call(compiled, bert, ids, mask, cfg): | ||
| 62 | + """在 fail_on_recompile 姿态下调用;抛异常=重编译,未抛=命中缓存。""" | ||
| 63 | + try: | ||
| 64 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 65 | + compiled(bert, ids, mask, cfg) | ||
| 66 | + return False | ||
| 67 | + except RuntimeError: | ||
| 68 | + return True | ||
| 69 | + | ||
| 70 | + | ||
| 71 | +def sync(): | ||
| 72 | + torch.npu.synchronize() | ||
| 73 | + | ||
| 74 | + | ||
| 75 | +def first_change_ms(compiled, bert, ids, mask, cfg): | ||
| 76 | + """状态切换后的“当次调用”耗时(baseline 当次含重编译,过滤后命中缓存)。""" | ||
| 77 | + sync() | ||
| 78 | + t0 = time.perf_counter() | ||
| 79 | + with torch.no_grad(): | ||
| 80 | + compiled(bert, ids, mask, cfg) | ||
| 81 | + sync() | ||
| 82 | + return (time.perf_counter() - t0) * 1e3 | ||
| 83 | + | ||
| 84 | + | ||
| 85 | +def run(bert, ids, mask, use_filter): | ||
| 86 | + torch._dynamo.reset() | ||
| 87 | + cfg = {"do_sample": False} # 注意:此时没有 "temperature" 键 | ||
| 88 | + entries = [] | ||
| 89 | + if use_filter: | ||
| 90 | + rule = lambda e: str(e.guard_type) not in DICT_GUARDS | ||
| 91 | + opt = {"guard_filter_fn": make_filter(rule, entries)} | ||
| 92 | + tag = "过滤后" | ||
| 93 | + else: | ||
| 94 | + opt = {"guard_filter_fn": make_filter(lambda e: True, entries)} | ||
| 95 | + tag = "未过滤" | ||
| 96 | + | ||
| 97 | + compiled = torch.compile(fwd_with_cfg, backend="aot_eager", options=opt) | ||
| 98 | + with torch.no_grad(): | ||
| 99 | + compiled(bert, ids, mask, cfg) # 首次调用 -> 触发编译 | ||
| 100 | + dict_guards = sorted({t for t, _ in entries if t in DICT_GUARDS}) | ||
| 101 | + cfg["temperature"] = 0.9 # 外部补键 -> version 变化 | ||
| 102 | + rec = recompiles_on_changed_call(compiled, bert, ids, mask, cfg) | ||
| 103 | + changed_ms = first_change_ms(compiled, bert, ids, mask, cfg) | ||
| 104 | + return tag, dict_guards, rec, changed_ms | ||
| 105 | + | ||
| 106 | + | ||
| 107 | +def main(): | ||
| 108 | + print("=" * 72) | ||
| 109 | + print("示例1:过滤字典版本 / 键集合类 guard(基于 distilbert-base-uncased)") | ||
| 110 | + print("=" * 72) | ||
| 111 | + | ||
| 112 | + bert = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 113 | + bert.to(DEV).eval() | ||
| 114 | + input_ids = torch.randint(0, bert.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 115 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 116 | + print(f"被测模型:DistilBertForMaskedLM 输入:input_ids{tuple(input_ids.shape)}") | ||
| 117 | + | ||
| 118 | + tag_b, guards_b, rec_b, ms_b = run(bert, input_ids, attention_mask, False) | ||
| 119 | + tag_f, guards_f, rec_f, ms_f = run(bert, input_ids, attention_mask, True) | ||
| 120 | + | ||
| 121 | + # 1) guard 枚举 | ||
| 122 | + print("\n[1] 编译期安装的字典类 guard:") | ||
| 123 | + print(" ", guards_b if guards_b else "(未观察到)") | ||
| 124 | + | ||
| 125 | + # 2) 重编译消除验证 | ||
| 126 | + print("\n[2] 给配置字典补上缺失键 temperature 后,是否触发重编译:") | ||
| 127 | + print(f" {tag_b}:{'发生重编译 ❗' if rec_b else '未重编译 ✅'}") | ||
| 128 | + print(f" {tag_f}:{'发生重编译 ❗' if rec_f else '未重编译 ✅(已命中缓存)'}") | ||
| 129 | + print(" 结论:{}".format( | ||
| 130 | + "过滤字典 guard 成功消除重编译 🎉" if (rec_b and not rec_f) else "请检查环境/脚本")) | ||
| 131 | + | ||
| 132 | + # 3) 正确性:稳定配置下,过滤后编译结果 vs Eager 结果(DistilBert logits) | ||
| 133 | + torch._dynamo.reset() | ||
| 134 | + with torch.no_grad(): | ||
| 135 | + cfg_ok = {"do_sample": False} | ||
| 136 | + eager_out = fwd_with_cfg(bert, input_ids, attention_mask, cfg_ok) | ||
| 137 | + rule = lambda entries: [str(e.guard_type) not in DICT_GUARDS for e in entries] | ||
| 138 | + cf = torch.compile(fwd_with_cfg, backend="aot_eager", | ||
| 139 | + options={"guard_filter_fn": rule}) | ||
| 140 | + compiled_out = cf(bert, input_ids, attention_mask, cfg_ok) | ||
| 141 | + maxdiff = (eager_out - compiled_out).abs().max().item() | ||
| 142 | + ok4 = torch.allclose(eager_out, compiled_out, atol=1e-4) | ||
| 143 | + print(f"\n[3] 正确性:Eager vs 过滤后编译(DistilBert logits)") | ||
| 144 | + print(f" max|Δ| = {maxdiff:.3e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 145 | + | ||
| 146 | + # 4) 性能:配置变化后的稳态调用(未过滤含重编译 vs 过滤后命中缓存) | ||
| 147 | + print("\n[4] 性能:字典补键后的当次调用耗时:") | ||
| 148 | + print(f" {tag_b}(含重编译):{ms_b:8.3f} ms") | ||
| 149 | + print(f" {tag_f}(缓存命中):{ms_f:8.3f} ms") | ||
| 150 | + print(f" 加速比 ≈ {ms_b / max(ms_f, 1e-6):8.2f}x") | ||
| 151 | + print("\n说明:guard_filter_fn 属于 unsafe 能力,请仅在确认这些变化不影响") | ||
| 152 | + print(" 计算图语义时使用(如生成配置、元信息字典等)。") | ||
| 153 | + | ||
| 154 | + | ||
| 155 | +if __name__ == "__main__": | ||
| 156 | + main() | ||
| @@ -0,0 +1,195 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · Guard Filter · 示例2:过滤“运行时状态”类 guard | ||
| 4 | +================================================================ | ||
| 5 | +被测模型:distilbert-base-uncased(DistilBertForMaskedLM,离线 HF 缓存) | ||
| 6 | +说明:本脚本没有任何自建网络模型——部分 A 直接编译 DistilBertForMaskedLM 实例, | ||
| 7 | + 部分 B 编译一个内部调用 DistilBertForMaskedLM 的普通函数。 | ||
| 8 | +文档给出的运行时状态 guard 包括: | ||
| 9 | + GRAD_MODE / TORCH_FUNCTION_STATE / GLOBAL_STATE / DEFAULT_DEVICE / | ||
| 10 | + DETERMINISTIC_ALGORITHMS / AUTOCAST_STATE / FSDP_TRAINING_STATE | ||
| 11 | +本脚本分两部分: | ||
| 12 | + A. 严格按文档复现:编译 DistilBert 后用 torch.no_grad() 切换 GRAD_MODE, | ||
| 13 | + 观察过滤行为(如实记录 torch 2.9.0 + torch_npu 上的现象,作为众测反馈)。 | ||
| 14 | + B. 给出一个可真正消除重编译的“应用级运行时状态”示例(Python 层局部状态, | ||
| 15 | + 对应 CONSTANT_MATCH guard),状态切换作用于 DistilBert logits 的后处理。 | ||
| 16 | + | ||
| 17 | +运行:env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 18 | + python 2_过滤运行时状态guard.py | ||
| 19 | +""" | ||
| 20 | +import os | ||
| 21 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 22 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 23 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 24 | + | ||
| 25 | +import time | ||
| 26 | +import torch | ||
| 27 | +import torch_npu # noqa: F401 | ||
| 28 | +from transformers import DistilBertForMaskedLM | ||
| 29 | + | ||
| 30 | +torch.npu.set_device(0) | ||
| 31 | +DEV = "npu" | ||
| 32 | +BATCH, SEQ_LEN = 2, 64 | ||
| 33 | + | ||
| 34 | +# 文档列出的 7 类运行时状态 guard | ||
| 35 | +RUNTIME_GUARDS = frozenset({ | ||
| 36 | + "GRAD_MODE", "TORCH_FUNCTION_STATE", "GLOBAL_STATE", "DEFAULT_DEVICE", | ||
| 37 | + "DETERMINISTIC_ALGORITHMS", "AUTOCAST_STATE", "FSDP_TRAINING_STATE", | ||
| 38 | +}) | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +def make_filter(keep_rule, store): | ||
| 42 | + def guard_filter_fn(entries): | ||
| 43 | + store.clear() | ||
| 44 | + keeps = [bool(keep_rule(e)) for e in entries] | ||
| 45 | + store.extend([(str(e.guard_type), str(e.name), k) | ||
| 46 | + for e, k in zip(entries, keeps)]) | ||
| 47 | + return keeps | ||
| 48 | + return guard_filter_fn | ||
| 49 | + | ||
| 50 | + | ||
| 51 | +def sync(): | ||
| 52 | + torch.npu.synchronize() | ||
| 53 | + | ||
| 54 | + | ||
| 55 | +def first_change_ms(call): | ||
| 56 | + """状态切换后的“当次调用”耗时(baseline 当次含重编译,过滤后命中缓存)。""" | ||
| 57 | + sync() | ||
| 58 | + t0 = time.perf_counter() | ||
| 59 | + call() | ||
| 60 | + sync() | ||
| 61 | + return (time.perf_counter() - t0) * 1e3 | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +# ---------------------------------------------------------------------- | ||
| 65 | +# 部分 A:GRAD_MODE 文档复现(真实 DistilBert) | ||
| 66 | +# ---------------------------------------------------------------------- | ||
| 67 | +def part_a(bert, ids, mask): | ||
| 68 | + print("-" * 72) | ||
| 69 | + print("部分 A:文档示例复现 —— 过滤 GRAD_MODE(DistilBert,torch.no_grad 切换)") | ||
| 70 | + print("-" * 72) | ||
| 71 | + | ||
| 72 | + def once(use_filter): | ||
| 73 | + torch._dynamo.reset() | ||
| 74 | + store = [] | ||
| 75 | + if use_filter: | ||
| 76 | + rule = lambda e: str(e.guard_type) not in RUNTIME_GUARDS | ||
| 77 | + opt = {"guard_filter_fn": make_filter(rule, store)} | ||
| 78 | + else: | ||
| 79 | + opt = {"guard_filter_fn": make_filter(lambda e: True, store)} | ||
| 80 | + cf = torch.compile(bert, backend="aot_eager", options=opt) | ||
| 81 | + cf(input_ids=ids, attention_mask=mask) # grad-enabled 下首次编译 | ||
| 82 | + in_grad = any(t == "GRAD_MODE" for t, _, _ in store) | ||
| 83 | + kept_grad = any(t == "GRAD_MODE" and k for t, _, k in store) | ||
| 84 | + with torch.no_grad(): | ||
| 85 | + try: | ||
| 86 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 87 | + cf(input_ids=ids, attention_mask=mask) | ||
| 88 | + rec = False | ||
| 89 | + except RuntimeError: | ||
| 90 | + rec = True | ||
| 91 | + return in_grad, kept_grad, len(store), rec | ||
| 92 | + | ||
| 93 | + ib, kb, nb, rb = once(False) | ||
| 94 | + if_, kf, nf, rf = once(True) | ||
| 95 | + print(f" 未过滤:guard 总数={nb:4d},收到 GRAD_MODE={ib},保留 GRAD_MODE={kb}," | ||
| 96 | + f"no_grad 后重编译={rb}") | ||
| 97 | + print(f" 过滤后:guard 总数={nf:4d},收到 GRAD_MODE={if_},保留 GRAD_MODE={kf}," | ||
| 98 | + f"no_grad 后重编译={rf}") | ||
| 99 | + print(" 现象说明:guard_filter_fn 已把 GRAD_MODE 从 guard 列表中移除," | ||
| 100 | + "但在 torch 2.9.0 +") | ||
| 101 | + print(" torch_npu 上,AOTAutograd 仍会按 no_grad / 训练态重新特化前反向图,") | ||
| 102 | + print(" 因此该调用仍被视为重编译。该现象已作为本次众测 Issue 反馈。") | ||
| 103 | + | ||
| 104 | + | ||
| 105 | +# ---------------------------------------------------------------------- | ||
| 106 | +# 部分 B:可真正消除重编译的“应用级运行时状态”(Python 层,CONSTANT_MATCH) | ||
| 107 | +# ---------------------------------------------------------------------- | ||
| 108 | +def part_b(bert, ids, mask): | ||
| 109 | + print("-" * 72) | ||
| 110 | + print("部分 B:应用级运行时状态(局部状态对象,作用于 DistilBert logits)") | ||
| 111 | + print("-" * 72) | ||
| 112 | + | ||
| 113 | + def once(use_filter): | ||
| 114 | + torch._dynamo.reset() | ||
| 115 | + phase = {"v": 1} # 应用运行时状态:1=预热阶段,2=正式阶段(局部对象) | ||
| 116 | + | ||
| 117 | + def f(input_ids, attention_mask): | ||
| 118 | + logits = bert(input_ids=input_ids, | ||
| 119 | + attention_mask=attention_mask).logits | ||
| 120 | + return torch.relu(logits) if phase["v"] == 1 else logits + 1.0 | ||
| 121 | + | ||
| 122 | + store = [] | ||
| 123 | + if use_filter: | ||
| 124 | + rule = lambda e: str(e.guard_type) != "CONSTANT_MATCH" | ||
| 125 | + opt = {"guard_filter_fn": make_filter(rule, store)} | ||
| 126 | + tag = "过滤后" | ||
| 127 | + else: | ||
| 128 | + opt = {"guard_filter_fn": make_filter(lambda e: True, store)} | ||
| 129 | + tag = "未过滤" | ||
| 130 | + cf = torch.compile(f, backend="aot_eager", options=opt) | ||
| 131 | + with torch.no_grad(): | ||
| 132 | + cf(ids, mask) # 阶段1:首次编译 | ||
| 133 | + cm = [f"{k}({n})" for k, n, _ in store if k == "CONSTANT_MATCH"] | ||
| 134 | + phase["v"] = 2 # 运行时切换阶段 | ||
| 135 | + try: | ||
| 136 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 137 | + cf(ids, mask) | ||
| 138 | + rec = False | ||
| 139 | + except RuntimeError: | ||
| 140 | + rec = True | ||
| 141 | + ms = first_change_ms(lambda: cf(ids, mask)) | ||
| 142 | + return tag, cm, rec, ms, f | ||
| 143 | + | ||
| 144 | + tag_b, cm_b, rec_b, ms_b, _ = once(False) | ||
| 145 | + tag_f, cm_f, rec_f, ms_f, f_f = once(True) | ||
| 146 | + | ||
| 147 | + print("\n[1] 编译期与运行时状态相关的 guard:") | ||
| 148 | + print(" ", cm_b[:5], "..." if len(cm_b) > 5 else "") | ||
| 149 | + print("\n[2] 运行时切换阶段后是否重编译:") | ||
| 150 | + print(f" {tag_b}:{'发生重编译 ❗' if rec_b else '未重编译 ✅'}") | ||
| 151 | + print(f" {tag_f}:{'发生重编译 ❗' if rec_f else '未重编译 ✅(命中缓存)'}") | ||
| 152 | + print(" 结论:{}".format( | ||
| 153 | + "过滤应用运行时状态 guard 成功消除重编译 🎉" if (rec_b and not rec_f) else "请检查")) | ||
| 154 | + | ||
| 155 | + # 正确性:阶段1 稳定状态下,Eager 与过滤后编译的 DistilBert logits 一致 | ||
| 156 | + phase = {"v": 1} | ||
| 157 | + | ||
| 158 | + def eager_f(input_ids, attention_mask): | ||
| 159 | + logits = bert(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 160 | + return torch.relu(logits) if phase["v"] == 1 else logits + 1.0 | ||
| 161 | + | ||
| 162 | + torch._dynamo.reset() | ||
| 163 | + rule = lambda entries: [str(e.guard_type) != "CONSTANT_MATCH" for e in entries] | ||
| 164 | + cf = torch.compile(eager_f, backend="aot_eager", | ||
| 165 | + options={"guard_filter_fn": rule}) | ||
| 166 | + with torch.no_grad(): | ||
| 167 | + out_e = eager_f(ids, mask) | ||
| 168 | + out_c = cf(ids, mask) | ||
| 169 | + maxdiff = (out_e - out_c).abs().max().item() | ||
| 170 | + ok4 = torch.allclose(out_e, out_c, atol=1e-4) | ||
| 171 | + print(f"\n[3] 正确性:Eager vs 过滤后编译(DistilBert logits)") | ||
| 172 | + print(f" max|Δ| = {maxdiff:.3e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 173 | + | ||
| 174 | + print("\n[4] 性能:阶段切换当次调用耗时:") | ||
| 175 | + print(f" {tag_b}(含重编译):{ms_b:8.3f} ms") | ||
| 176 | + print(f" {tag_f}(缓存命中):{ms_f:8.3f} ms") | ||
| 177 | + print(f" 加速比 ≈ {ms_b / max(ms_f, 1e-6):8.2f}x") | ||
| 178 | + | ||
| 179 | + | ||
| 180 | +def main(): | ||
| 181 | + print("=" * 72) | ||
| 182 | + print("示例2:过滤运行时状态类 guard(基于 distilbert-base-uncased)") | ||
| 183 | + print("=" * 72) | ||
| 184 | + bert = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 185 | + bert.to(DEV).eval() | ||
| 186 | + ids = torch.randint(0, bert.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 187 | + mask = torch.ones_like(ids).npu() | ||
| 188 | + print(f"模型:DistilBertForMaskedLM 输入:input_ids{tuple(ids.shape)}") | ||
| 189 | + part_a(bert, ids, mask) | ||
| 190 | + print() | ||
| 191 | + part_b(bert, ids, mask) | ||
| 192 | + | ||
| 193 | + | ||
| 194 | +if __name__ == "__main__": | ||
| 195 | + main() | ||
| @@ -0,0 +1,167 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · Guard Filter · 示例3:组合过滤多类 guard | ||
| 4 | +================================================================ | ||
| 5 | +被测模型:distilbert-base-uncased(DistilBertForMaskedLM,离线 HF 缓存) | ||
| 6 | +说明:本脚本没有任何自建网络模型——编译对象是一个普通 Python 函数,内部调用真实 | ||
| 7 | + 的 DistilBertForMaskedLM,在其 logits 上模拟生成式模型 prefill->decode 常见 | ||
| 8 | + 的多类“易变”guard: | ||
| 9 | + 1) 配置字典 cfg.get(...) -> DICT_CONTAINS / DICT_KEYS / DICT_VERSION | ||
| 10 | + 2) 可选缓存 past(None <-> Tensor)-> TYPE_MATCH / OPTIONAL_TENSOR | ||
| 11 | + 3) 全局特性开关 GLOBAL_FLAG -> EQUALS_MATCH(is_global=True) | ||
| 12 | + 4) 运行时状态 -> GRAD_MODE 等 | ||
| 13 | + 5) 内建对象/属性 -> HASATTR / NOT_PRESENT_IN_GENERIC_DICT / CONSTANT_MATCH | ||
| 14 | +分别过滤每一类很繁琐,这里用一个 guard_filter_fn 一次性组合过滤。 | ||
| 15 | + | ||
| 16 | +prefill 阶段:past=None、配置缺键;decode 阶段:past=Tensor(与 logits 同形的 | ||
| 17 | +缓存增量)、配置补键。默认从 prefill 切到 decode 会触发重编译;组合过滤后直接 | ||
| 18 | +复用同一份图。 | ||
| 19 | + | ||
| 20 | +运行:env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 21 | + python 3_组合多类guard过滤.py | ||
| 22 | +""" | ||
| 23 | +import os | ||
| 24 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 25 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 26 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 27 | + | ||
| 28 | +import time | ||
| 29 | +import torch | ||
| 30 | +import torch_npu # noqa: F401 | ||
| 31 | +from transformers import DistilBertForMaskedLM | ||
| 32 | + | ||
| 33 | +torch.npu.set_device(0) | ||
| 34 | +DEV = "npu" | ||
| 35 | +BATCH, SEQ_LEN = 2, 64 | ||
| 36 | + | ||
| 37 | +GLOBAL_FLAG = {"mode": 1} # 全局特性开关(is_global=True) | ||
| 38 | + | ||
| 39 | +_DICT = frozenset({"DICT_VERSION", "DICT_KEYS", "DICT_KEYS_MATCH", "DICT_CONTAINS", | ||
| 40 | + "NOT_PRESENT_IN_GENERIC_DICT"}) | ||
| 41 | +_RUNTIME = frozenset({"GRAD_MODE", "TORCH_FUNCTION_STATE", "GLOBAL_STATE", "DEFAULT_DEVICE", | ||
| 42 | + "DETERMINISTIC_ALGORITHMS", "AUTOCAST_STATE", "FSDP_TRAINING_STATE"}) | ||
| 43 | +_OTHER = frozenset({"TYPE_MATCH", "OPTIONAL_TENSOR", "HASATTR", | ||
| 44 | + "CONSTANT_MATCH", "EQUALS_MATCH"}) | ||
| 45 | + | ||
| 46 | + | ||
| 47 | +def fwd_prefill_decode(bert, input_ids, attention_mask, past=None, cfg=None): | ||
| 48 | + """以真实 DistilBert 为核心:可选缓存增量 + 配置字典 + 全局开关。""" | ||
| 49 | + logits = bert(input_ids=input_ids, | ||
| 50 | + attention_mask=attention_mask).logits | ||
| 51 | + h = logits + past if past is not None else logits # past: None <-> Tensor | ||
| 52 | + if GLOBAL_FLAG["mode"] == 1: # 全局开关 | ||
| 53 | + h = torch.relu(h) | ||
| 54 | + scale = cfg.get("temperature", 1.0) if cfg is not None else 1.0 | ||
| 55 | + return h * scale | ||
| 56 | + | ||
| 57 | + | ||
| 58 | +# 组合规则:命中上述任意一类,或属于全局变量 guard 的,一律过滤掉(返回 False) | ||
| 59 | +def combo_rule(entry): | ||
| 60 | + t = str(entry.guard_type) | ||
| 61 | + return not (t in _DICT or t in _RUNTIME or t in _OTHER or entry.is_global) | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +def make_filter(keep_rule, store): | ||
| 65 | + def guard_filter_fn(entries): | ||
| 66 | + store.clear() | ||
| 67 | + keeps = [bool(keep_rule(e)) for e in entries] | ||
| 68 | + store.extend([(str(e.guard_type), str(e.name), bool(e.is_global), k) | ||
| 69 | + for e, k in zip(entries, keeps)]) | ||
| 70 | + return keeps | ||
| 71 | + return guard_filter_fn | ||
| 72 | + | ||
| 73 | + | ||
| 74 | +def sync(): | ||
| 75 | + torch.npu.synchronize() | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +def first_change_ms(compiled, bert, ids, mask, past, cfg): | ||
| 79 | + """状态切换后的“当次调用”耗时(baseline 当次含重编译,过滤后命中缓存)。""" | ||
| 80 | + sync() | ||
| 81 | + t0 = time.perf_counter() | ||
| 82 | + with torch.no_grad(): | ||
| 83 | + compiled(bert, ids, mask, past, cfg) | ||
| 84 | + sync() | ||
| 85 | + return (time.perf_counter() - t0) * 1e3 | ||
| 86 | + | ||
| 87 | + | ||
| 88 | +def run(bert, ids, mask, past, use_filter): | ||
| 89 | + torch._dynamo.reset() | ||
| 90 | + GLOBAL_FLAG["mode"] = 1 | ||
| 91 | + cfg = {"do_sample": False} # prefill:缺 temperature 键 | ||
| 92 | + store = [] | ||
| 93 | + if use_filter: | ||
| 94 | + opt = {"guard_filter_fn": make_filter(combo_rule, store)} | ||
| 95 | + tag = "组合过滤后" | ||
| 96 | + else: | ||
| 97 | + opt = {"guard_filter_fn": make_filter(lambda e: True, store)} | ||
| 98 | + tag = "不过滤" | ||
| 99 | + cf = torch.compile(fwd_prefill_decode, backend="aot_eager", options=opt) | ||
| 100 | + | ||
| 101 | + with torch.no_grad(): | ||
| 102 | + cf(bert, ids, mask, None, cfg) # prefill:首次编译 | ||
| 103 | + cfg["temperature"] = 0.8 # 字典补键 | ||
| 104 | + try: | ||
| 105 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 106 | + cf(bert, ids, mask, past, cfg) # decode:past 变 Tensor + 字典变化 | ||
| 107 | + rec = False | ||
| 108 | + except RuntimeError: | ||
| 109 | + rec = True | ||
| 110 | + ms = first_change_ms(cf, bert, ids, mask, past, cfg) | ||
| 111 | + return tag, store, rec, ms | ||
| 112 | + | ||
| 113 | + | ||
| 114 | +def main(): | ||
| 115 | + print("=" * 72) | ||
| 116 | + print("示例3:组合过滤多类 guard(基于 distilbert-base-uncased,prefill/decode)") | ||
| 117 | + print("=" * 72) | ||
| 118 | + | ||
| 119 | + bert = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 120 | + bert.to(DEV).eval() | ||
| 121 | + ids = torch.randint(0, bert.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 122 | + mask = torch.ones_like(ids).npu() | ||
| 123 | + with torch.no_grad(): | ||
| 124 | + logits_shape = fwd_prefill_decode(bert, ids, mask).shape | ||
| 125 | + past = torch.randn(*logits_shape, device=DEV) | ||
| 126 | + print(f"被测模型:DistilBertForMaskedLM 输入:input_ids{tuple(ids.shape)} " | ||
| 127 | + f"past:None(prefill) -> Tensor{tuple(past.shape)}(decode)") | ||
| 128 | + | ||
| 129 | + tag_b, store_b, rec_b, ms_b = run(bert, ids, mask, past, False) | ||
| 130 | + tag_f, store_f, rec_f, ms_f = run(bert, ids, mask, past, True) | ||
| 131 | + | ||
| 132 | + # 1) 展示被组合规则过滤掉的 guard 类别 | ||
| 133 | + dropped = sorted({t for t, _, g, k in store_f if not k}) | ||
| 134 | + print("\n[1] 组合规则一次性过滤掉的 guard 类型:") | ||
| 135 | + print(" ", dropped) | ||
| 136 | + | ||
| 137 | + # 2) 重编译消除 | ||
| 138 | + print("\n[2] prefill -> decode(past: None->Tensor、配置补键)是否重编译:") | ||
| 139 | + print(f" {tag_b}:{'发生重编译 ❗' if rec_b else '未重编译 ✅'}") | ||
| 140 | + print(f" {tag_f}:{'发生重编译 ❗' if rec_f else '未重编译 ✅(prefill/decode 复用同一图)'}") | ||
| 141 | + print(" 结论:{}".format( | ||
| 142 | + "组合多类 guard 过滤成功消除重编译 🎉" if (rec_b and not rec_f) else "请检查")) | ||
| 143 | + | ||
| 144 | + # 3) 正确性:prefill 稳定签名下 Eager 与组合过滤后编译一致(DistilBert logits) | ||
| 145 | + torch._dynamo.reset() | ||
| 146 | + GLOBAL_FLAG["mode"] = 1 | ||
| 147 | + with torch.no_grad(): | ||
| 148 | + cfg_ok = {"do_sample": False} | ||
| 149 | + cfc = torch.compile(fwd_prefill_decode, backend="aot_eager", | ||
| 150 | + options={"guard_filter_fn": lambda es: [combo_rule(e) for e in es]}) | ||
| 151 | + out_e = fwd_prefill_decode(bert, ids, mask, None, cfg_ok) | ||
| 152 | + out_c = cfc(bert, ids, mask, None, cfg_ok) | ||
| 153 | + maxdiff = (out_e - out_c).abs().max().item() | ||
| 154 | + ok4 = torch.allclose(out_e, out_c, atol=1e-4) | ||
| 155 | + print(f"\n[3] 正确性(prefill 签名,DistilBert logits):") | ||
| 156 | + print(f" max|Δ| = {maxdiff:.3e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 157 | + | ||
| 158 | + # 4) 性能 | ||
| 159 | + print("\n[4] 性能:prefill->decode 切换的当次调用耗时:") | ||
| 160 | + print(f" {tag_b}(含重编译):{ms_b:8.3f} ms") | ||
| 161 | + print(f" {tag_f}(缓存命中):{ms_f:8.3f} ms") | ||
| 162 | + print(f" 加速比 ≈ {ms_b / max(ms_f, 1e-6):8.2f}x") | ||
| 163 | + print("\n注意:组合过滤属于 unsafe,前提是 prefill/decode 复用语义一致(本例仅演示)。") | ||
| 164 | + | ||
| 165 | + | ||
| 166 | +if __name__ == "__main__": | ||
| 167 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/4_按变量名过滤.py+141-0
| @@ -0,0 +1,141 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · Guard Filter · 示例4:按“变量名”精准过滤 guard(filter_by_name) | ||
| 4 | +================================================================ | ||
| 5 | +被测模型:distilbert-base-uncased(DistilBertForMaskedLM,离线 HF 缓存) | ||
| 6 | +说明:本脚本没有任何自建网络模型——编译对象是一个普通 Python 函数,内部调用 | ||
| 7 | + 真实的 DistilBertForMaskedLM,仅给它增加一个可选偏置参数 y。 | ||
| 8 | +不按 guard 类型“一刀切”,而是根据 entry.name / entry.value 精准定位到某个变量: | ||
| 9 | + 首次调用 y=None,Dynamo 会针对“y 的值是 None”安装 CONSTANT_MATCH(y, None); | ||
| 10 | + 之后传入 y=Tensor 时默认触发重编译。 | ||
| 11 | +我们只过滤 name == "y" 且 value is None 的那一条 guard,其余 guard 全部保留, | ||
| 12 | +是一种最小作用域、相对可控的过滤方式。 | ||
| 13 | + | ||
| 14 | +运行:env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 15 | + python 4_按变量名过滤.py | ||
| 16 | +""" | ||
| 17 | +import os | ||
| 18 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 19 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 20 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 21 | + | ||
| 22 | +import time | ||
| 23 | +import torch | ||
| 24 | +import torch_npu # noqa: F401 | ||
| 25 | +from transformers import DistilBertForMaskedLM | ||
| 26 | + | ||
| 27 | +torch.npu.set_device(0) | ||
| 28 | +DEV = "npu" | ||
| 29 | +BATCH, SEQ_LEN = 2, 64 | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +def make_filter(keep_rule, store): | ||
| 33 | + def guard_filter_fn(entries): | ||
| 34 | + store.clear() | ||
| 35 | + keeps = [bool(keep_rule(e)) for e in entries] | ||
| 36 | + # 只记录类型/变量名/是否为None,不 repr 设备张量值 | ||
| 37 | + store.extend([(str(e.guard_type), str(e.name), getattr(e, "value", None) is None, k) | ||
| 38 | + for e, k in zip(entries, keeps)]) | ||
| 39 | + return keeps | ||
| 40 | + return guard_filter_fn | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +# 仅过滤:变量名为 y 且当前取值为 None 的 guard | ||
| 44 | +def name_rule(e): | ||
| 45 | + return not (e.name == "y" and e.value is None) | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +def sync(): | ||
| 49 | + torch.npu.synchronize() | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +def first_change_ms(call): | ||
| 53 | + """状态切换后的“当次调用”耗时(baseline 当次含重编译,过滤后命中缓存)。""" | ||
| 54 | + sync() | ||
| 55 | + t0 = time.perf_counter() | ||
| 56 | + call() | ||
| 57 | + sync() | ||
| 58 | + return (time.perf_counter() - t0) * 1e3 | ||
| 59 | + | ||
| 60 | + | ||
| 61 | +def run(bert, ids, mask, y, use_filter): | ||
| 62 | + torch._dynamo.reset() | ||
| 63 | + | ||
| 64 | + def f(input_ids, attention_mask, y=None): | ||
| 65 | + """DistilBert 前向 + 可选偏置:y 为 None 时走旁路,否则与 logits 相加。""" | ||
| 66 | + logits = bert(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 67 | + if y is not None: | ||
| 68 | + logits = logits + y | ||
| 69 | + return logits | ||
| 70 | + | ||
| 71 | + store = [] | ||
| 72 | + if use_filter: | ||
| 73 | + opt = {"guard_filter_fn": make_filter(name_rule, store)} | ||
| 74 | + tag = "按名过滤后" | ||
| 75 | + else: | ||
| 76 | + opt = {"guard_filter_fn": make_filter(lambda e: True, store)} | ||
| 77 | + tag = "不过滤" | ||
| 78 | + cf = torch.compile(f, backend="aot_eager", options=opt) | ||
| 79 | + with torch.no_grad(): | ||
| 80 | + cf(ids, mask) # 首次:y=None | ||
| 81 | + target = [(t, n, v) for t, n, v, k in store if n == "y"] | ||
| 82 | + try: | ||
| 83 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 84 | + cf(ids, mask, y) # y 变成 Tensor | ||
| 85 | + rec = False | ||
| 86 | + except RuntimeError: | ||
| 87 | + rec = True | ||
| 88 | + ms = first_change_ms(lambda: cf(ids, mask, y)) | ||
| 89 | + return tag, target, rec, ms, f | ||
| 90 | + | ||
| 91 | + | ||
| 92 | +def main(): | ||
| 93 | + print("=" * 72) | ||
| 94 | + print("示例4:按变量名精准过滤 guard(基于 distilbert-base-uncased,只放过 y=None)") | ||
| 95 | + print("=" * 72) | ||
| 96 | + | ||
| 97 | + bert = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 98 | + bert.to(DEV).eval() | ||
| 99 | + ids = torch.randint(0, bert.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 100 | + mask = torch.ones_like(ids).npu() | ||
| 101 | + with torch.no_grad(): | ||
| 102 | + vocab = bert.config.vocab_size | ||
| 103 | + y = torch.randn(BATCH, SEQ_LEN, vocab, device=DEV) | ||
| 104 | + print(f"模型:DistilBertForMaskedLM 输入:input_ids{tuple(ids.shape)} " | ||
| 105 | + f"y: None -> Tensor{tuple(y.shape)}") | ||
| 106 | + | ||
| 107 | + tag_b, target_b, rec_b, ms_b, _ = run(bert, ids, mask, y, False) | ||
| 108 | + tag_f, target_f, rec_f, ms_f, f_f = run(bert, ids, mask, y, True) | ||
| 109 | + | ||
| 110 | + print("\n[1] 编译期针对变量 y 的 guard:") | ||
| 111 | + shown = [(t, n, ("value=None" if isnone else "value=非None")) for t, n, isnone in target_b] | ||
| 112 | + print(" ", shown if shown else "(未观察到)") | ||
| 113 | + | ||
| 114 | + print("\n[2] y 从 None 变为 Tensor 后是否重编译:") | ||
| 115 | + print(f" {tag_b}:{'发生重编译 ❗' if rec_b else '未重编译 ✅'}") | ||
| 116 | + print(f" {tag_f}:{'发生重编译 ❗' if rec_f else '未重编译 ✅(仅放过 y=None 这一条)'}") | ||
| 117 | + print(" 结论:{}".format( | ||
| 118 | + "按变量名过滤成功消除重编译 🎉" if (rec_b and not rec_f) else "请检查")) | ||
| 119 | + | ||
| 120 | + # 正确性:y=None 稳定签名下,Eager 与过滤后编译的 DistilBert logits 一致 | ||
| 121 | + torch._dynamo.reset() | ||
| 122 | + cfc = torch.compile(f_f, backend="aot_eager", | ||
| 123 | + options={"guard_filter_fn": lambda es: [name_rule(e) for e in es]}) | ||
| 124 | + with torch.no_grad(): | ||
| 125 | + out_e = f_f(ids, mask) | ||
| 126 | + out_c = cfc(ids, mask) | ||
| 127 | + maxdiff = (out_e - out_c).abs().max().item() | ||
| 128 | + ok4 = torch.allclose(out_e, out_c, atol=1e-4) | ||
| 129 | + print(f"\n[3] 正确性(y=None 签名,DistilBert logits):") | ||
| 130 | + print(f" max|Δ| = {maxdiff:.3e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 131 | + | ||
| 132 | + print("\n[4] 性能:y=None -> y=Tensor 切换的当次调用耗时:") | ||
| 133 | + print(f" {tag_b}(含重编译):{ms_b:8.3f} ms") | ||
| 134 | + print(f" {tag_f}(缓存命中):{ms_f:8.3f} ms") | ||
| 135 | + print(f" 加速比 ≈ {ms_b / max(ms_f, 1e-6):8.2f}x") | ||
| 136 | + print("\n说明:按名过滤作用域最小,但仍属 unsafe——复用的是 y=None 分支图,") | ||
| 137 | + print(" 需确认 y 变为 Tensor 后的分支语义不需要重新特化。") | ||
| 138 | + | ||
| 139 | + | ||
| 140 | +if __name__ == "__main__": | ||
| 141 | + main() | ||
| @@ -0,0 +1,146 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · Guard Filter · 示例5:过滤“全局变量”guard | ||
| 4 | +================================================================ | ||
| 5 | +被测模型:distilbert-base-uncased(DistilBertForMaskedLM,离线 HF 缓存) | ||
| 6 | +说明:本脚本没有任何自建网络模型——编译对象是一个普通 Python 函数,内部调用真实 | ||
| 7 | + 的 DistilBertForMaskedLM,并在其 logits 上读取模块级全局配置做激活分支。 | ||
| 8 | + | ||
| 9 | +当函数前向读取模块级全局对象(配置、特性开关、注册表等)时,Dynamo 会给这些 | ||
| 10 | +G['xxx'] 全局读取安装 is_global=True 的 guard(如 EQUALS_MATCH / TYPE_MATCH / | ||
| 11 | +DICT 类 guard)。全局对象一旦被改动(哪怕只是值变化),默认就会重编译。 | ||
| 12 | + | ||
| 13 | +对“整个进程生命周期内不希望其变化触发重编译”的全局对象,用 | ||
| 14 | + [not entry.is_global for entry in entries] | ||
| 15 | +把全部全局 guard 一次性过滤掉,只保留输入张量等局部 guard。 | ||
| 16 | + | ||
| 17 | +运行:env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 18 | + python 5_过滤全局变量guard.py | ||
| 19 | +""" | ||
| 20 | +import os | ||
| 21 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 22 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 23 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 24 | + | ||
| 25 | +import time | ||
| 26 | +import torch | ||
| 27 | +import torch_npu # noqa: F401 | ||
| 28 | +from transformers import DistilBertForMaskedLM | ||
| 29 | + | ||
| 30 | +torch.npu.set_device(0) | ||
| 31 | +DEV = "npu" | ||
| 32 | +BATCH, SEQ_LEN = 2, 64 | ||
| 33 | + | ||
| 34 | +# 模块级全局特性开关(对应工程里的 global config / feature flag) | ||
| 35 | +GLOBAL_CONFIG = {"activation": "relu"} | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +def fwd_global(bert, input_ids, attention_mask): | ||
| 39 | + """以真实 DistilBert 为核心:logits 上的激活分支由模块级全局配置控制。""" | ||
| 40 | + logits = bert(input_ids=input_ids, | ||
| 41 | + attention_mask=attention_mask).logits | ||
| 42 | + if GLOBAL_CONFIG["activation"] == "relu": # 读取全局 -> is_global guard | ||
| 43 | + return torch.relu(logits) | ||
| 44 | + return torch.sigmoid(logits) | ||
| 45 | + | ||
| 46 | + | ||
| 47 | +def make_filter(keep_rule, store): | ||
| 48 | + def guard_filter_fn(entries): | ||
| 49 | + store.clear() | ||
| 50 | + keeps = [bool(keep_rule(e)) for e in entries] | ||
| 51 | + store.extend([(str(e.guard_type), str(e.name), bool(e.is_global), k) | ||
| 52 | + for e, k in zip(entries, keeps)]) | ||
| 53 | + return keeps | ||
| 54 | + return guard_filter_fn | ||
| 55 | + | ||
| 56 | + | ||
| 57 | +def global_rule(e): | ||
| 58 | + return not e.is_global # 过滤掉所有全局 guard | ||
| 59 | + | ||
| 60 | + | ||
| 61 | +def sync(): | ||
| 62 | + torch.npu.synchronize() | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +def first_change_ms(compiled, bert, ids, mask): | ||
| 66 | + """状态切换后的“当次调用”耗时(baseline 当次含重编译,过滤后命中缓存)。""" | ||
| 67 | + sync() | ||
| 68 | + t0 = time.perf_counter() | ||
| 69 | + with torch.no_grad(): | ||
| 70 | + compiled(bert, ids, mask) | ||
| 71 | + sync() | ||
| 72 | + return (time.perf_counter() - t0) * 1e3 | ||
| 73 | + | ||
| 74 | + | ||
| 75 | +def run(bert, ids, mask, use_filter): | ||
| 76 | + torch._dynamo.reset() | ||
| 77 | + GLOBAL_CONFIG["activation"] = "relu" | ||
| 78 | + store = [] | ||
| 79 | + if use_filter: | ||
| 80 | + opt = {"guard_filter_fn": make_filter(global_rule, store)} | ||
| 81 | + tag = "过滤全局guard后" | ||
| 82 | + else: | ||
| 83 | + opt = {"guard_filter_fn": make_filter(lambda e: True, store)} | ||
| 84 | + tag = "不过滤" | ||
| 85 | + cf = torch.compile(fwd_global, backend="aot_eager", options=opt) | ||
| 86 | + with torch.no_grad(): | ||
| 87 | + cf(bert, ids, mask) # 首次编译 | ||
| 88 | + glob_guards = [f"{t}({n})" for t, n, g, k in store if g] | ||
| 89 | + GLOBAL_CONFIG["activation"] = "sigmoid" # 修改全局开关 | ||
| 90 | + try: | ||
| 91 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 92 | + cf(bert, ids, mask) | ||
| 93 | + rec = False | ||
| 94 | + except RuntimeError: | ||
| 95 | + rec = True | ||
| 96 | + ms = first_change_ms(cf, bert, ids, mask) | ||
| 97 | + return tag, glob_guards, rec, ms | ||
| 98 | + | ||
| 99 | + | ||
| 100 | +def main(): | ||
| 101 | + print("=" * 72) | ||
| 102 | + print("示例5:过滤全局变量 guard(基于 distilbert-base-uncased,is_global=True)") | ||
| 103 | + print("=" * 72) | ||
| 104 | + | ||
| 105 | + bert = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 106 | + bert.to(DEV).eval() | ||
| 107 | + ids = torch.randint(0, bert.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 108 | + mask = torch.ones_like(ids).npu() | ||
| 109 | + print(f"被测模型:DistilBertForMaskedLM 输入:input_ids{tuple(ids.shape)}") | ||
| 110 | + | ||
| 111 | + tag_b, gl_b, rec_b, ms_b = run(bert, ids, mask, False) | ||
| 112 | + tag_f, gl_f, rec_f, ms_f = run(bert, ids, mask, True) | ||
| 113 | + | ||
| 114 | + print("\n[1] 编译期安装的全局 guard(is_global=True)示例:") | ||
| 115 | + print(" ", gl_b[:6], "..." if len(gl_b) > 6 else "") | ||
| 116 | + print(f" 共 {len(gl_b)} 条") | ||
| 117 | + | ||
| 118 | + print("\n[2] 修改全局开关 activation: relu -> sigmoid 后是否重编译:") | ||
| 119 | + print(f" {tag_b}:{'发生重编译 ❗' if rec_b else '未重编译 ✅'}") | ||
| 120 | + print(f" {tag_f}:{'发生重编译 ❗' if rec_f else '未重编译 ✅(仅保留局部张量 guard)'}") | ||
| 121 | + print(" 结论:{}".format( | ||
| 122 | + "过滤全局 guard 成功消除重编译 🎉" if (rec_b and not rec_f) else "请检查")) | ||
| 123 | + | ||
| 124 | + # 正确性:全局开关固定 relu 时,Eager 与过滤后编译的 DistilBert logits 一致 | ||
| 125 | + torch._dynamo.reset() | ||
| 126 | + GLOBAL_CONFIG["activation"] = "relu" | ||
| 127 | + with torch.no_grad(): | ||
| 128 | + cfc = torch.compile(fwd_global, backend="aot_eager", | ||
| 129 | + options={"guard_filter_fn": lambda es: [not e.is_global for e in es]}) | ||
| 130 | + out_e = fwd_global(bert, ids, mask) | ||
| 131 | + out_c = cfc(bert, ids, mask) | ||
| 132 | + maxdiff = (out_e - out_c).abs().max().item() | ||
| 133 | + ok4 = torch.allclose(out_e, out_c, atol=1e-4) | ||
| 134 | + print(f"\n[3] 正确性(全局开关固定 relu,DistilBert logits):") | ||
| 135 | + print(f" max|Δ| = {maxdiff:.3e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 136 | + | ||
| 137 | + print("\n[4] 性能:全局开关变化后的当次调用耗时:") | ||
| 138 | + print(f" {tag_b}(含重编译):{ms_b:8.3f} ms") | ||
| 139 | + print(f" {tag_f}(缓存命中):{ms_f:8.3f} ms") | ||
| 140 | + print(f" 加速比 ≈ {ms_b / max(ms_f, 1e-6):8.2f}x") | ||
| 141 | + print("\n说明:官方提供同名内置 helper —— torch.compiler.skip_guard_on_globals_unsafe。") | ||
| 142 | + print(" 仅当你确信这些全局对象的变化不应影响已编译图时再使用。") | ||
| 143 | + | ||
| 144 | + | ||
| 145 | +if __name__ == "__main__": | ||
| 146 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/6_结合内置helper.py+176-0
| @@ -0,0 +1,176 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · Guard Filter · 示例6:结合官方内置 helper 过滤 nn.Module guard | ||
| 4 | +================================================================ | ||
| 5 | +被测模型:distilbert-base-uncased(DistilBertForMaskedLM,离线 HF 缓存) | ||
| 6 | + 编译对象就是该模型实例本身,没有任何自建网络模型。 | ||
| 7 | + DistilBert 内部含大量内建 nn.Linear / nn.Embedding,是观察模块 guard 的好载体。 | ||
| 8 | +torch.compiler 提供两个开箱即用的 guard_filter_fn: | ||
| 9 | + · skip_guard_on_inbuilt_nn_modules_unsafe:跳过 nn.Linear 等“内建模块”上的 guard | ||
| 10 | + · skip_guard_on_all_nn_modules_unsafe :跳过所有 nn.Module(含自定义模块)上的 guard | ||
| 11 | +官方定位(docstring 原文): | ||
| 12 | + "the model code does not modify the nn module attributes. They can benefit | ||
| 13 | + from reduction in guard latency overhead using this API." | ||
| 14 | + | ||
| 15 | +为触发“内建子模块类型”guard,脚本只在验证环节把模型中某一个内建 nn.Linear | ||
| 16 | +临时替换为下面定义的 ReplacementLinear——它不是模型,仅是与 nn.Linear 同语义 | ||
| 17 | +(含同形 weight/bias)但类型不同的替换层,用来检验 TYPE_MATCH 类 guard 是否被 | ||
| 18 | +helper 过滤。 | ||
| 19 | + | ||
| 20 | +本脚本量化三件事: | ||
| 21 | + [1] guard 总数 / 保留数 / 过滤数(按类型统计,DistilBert 上约 1500 条 guard) | ||
| 22 | + [2] 重编译消除:把某个内建 nn.Linear 替换为“同语义但不同类型”的替换层后, | ||
| 23 | + baseline 会因子模块 TYPE_MATCH 失败而重编译;两个 helper 过滤掉该 guard, | ||
| 24 | + 直接复用已编译图(fail_on_recompile 姿态实测)。 | ||
| 25 | + [3] 正确性:helper 只删 guard、不改数学,编译输出与 Eager 一致。 | ||
| 26 | + [4] 性能:稳态缓存命中的端到端平均延迟(NPU 环境算子派发占主导,guard 收益 | ||
| 27 | + 主要体现在 guard 数量上,深层网络 / CPU 派发占比高时收益更明显)。 | ||
| 28 | + | ||
| 29 | +运行:env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 30 | + python 6_结合内置helper.py | ||
| 31 | +""" | ||
| 32 | +import os | ||
| 33 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 34 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 35 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 36 | + | ||
| 37 | +import collections | ||
| 38 | +import time | ||
| 39 | +import torch | ||
| 40 | +import torch_npu # noqa: F401 | ||
| 41 | +import torch.nn as nn | ||
| 42 | +from transformers import DistilBertForMaskedLM | ||
| 43 | + | ||
| 44 | +torch.npu.set_device(0) | ||
| 45 | +DEV = "npu" | ||
| 46 | +BATCH, SEQ_LEN = 2, 64 | ||
| 47 | + | ||
| 48 | +HELPERS = [ | ||
| 49 | + ("baseline(不过滤)", None), | ||
| 50 | + ("inbuilt(仅内建模块)", torch.compiler.skip_guard_on_inbuilt_nn_modules_unsafe), | ||
| 51 | + ("all(全部 nn.Module)", torch.compiler.skip_guard_on_all_nn_modules_unsafe), | ||
| 52 | +] | ||
| 53 | + | ||
| 54 | + | ||
| 55 | +class ReplacementLinear(nn.Linear): | ||
| 56 | + """同语义内建替换层(非模型,仅用于触发内建子模块 TYPE_MATCH guard)。""" | ||
| 57 | + pass | ||
| 58 | + | ||
| 59 | + | ||
| 60 | +def sync(): | ||
| 61 | + torch.npu.synchronize() | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +def make_inputs(bert): | ||
| 65 | + ids = torch.randint(0, bert.config.vocab_size, (BATCH, SEQ_LEN), device=DEV) | ||
| 66 | + return ids, torch.ones_like(ids) | ||
| 67 | + | ||
| 68 | + | ||
| 69 | +def run(helper): | ||
| 70 | + """编译 DistilBert,统计 guard;随后换异型子模块做重编译判定;再测稳态耗时。""" | ||
| 71 | + torch._dynamo.reset() | ||
| 72 | + bert = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 73 | + bert.to(DEV).eval() | ||
| 74 | + ids, mask = make_inputs(bert) | ||
| 75 | + | ||
| 76 | + type_counter = collections.Counter() | ||
| 77 | + dropped_types = collections.Counter() | ||
| 78 | + | ||
| 79 | + def flt(entries): | ||
| 80 | + keeps = [True] * len(entries) if helper is None else list(helper(entries)) | ||
| 81 | + for e, k in zip(entries, keeps): | ||
| 82 | + t = str(e.guard_type) | ||
| 83 | + type_counter[t] += 1 | ||
| 84 | + if not k: | ||
| 85 | + dropped_types[t] += 1 | ||
| 86 | + return keeps | ||
| 87 | + | ||
| 88 | + cf = torch.compile(bert, backend="aot_eager", options={"guard_filter_fn": flt}) | ||
| 89 | + | ||
| 90 | + def invoke(): | ||
| 91 | + return cf(input_ids=ids, attention_mask=mask).logits | ||
| 92 | + | ||
| 93 | + with torch.no_grad(): | ||
| 94 | + for _ in range(5): # warmup,含首次编译(多段图的 guard 会在全部编译后出齐) | ||
| 95 | + invoke() | ||
| 96 | + # —— 重编译消除判定:把第 0 层 attention 的 q_lin 换成同语义异型 Linear —— | ||
| 97 | + attn = bert.distilbert.transformer.layer[0].attention | ||
| 98 | + old = attn.q_lin | ||
| 99 | + new_lin = ReplacementLinear(old.in_features, old.out_features, bias=True).to(DEV) | ||
| 100 | + new_lin.weight.data.copy_(old.weight.data) | ||
| 101 | + new_lin.bias.data.copy_(old.bias.data) | ||
| 102 | + attn.q_lin = new_lin | ||
| 103 | + try: | ||
| 104 | + with torch.compiler.set_stance("fail_on_recompile"): | ||
| 105 | + invoke() | ||
| 106 | + rec = False | ||
| 107 | + except RuntimeError: | ||
| 108 | + rec = True | ||
| 109 | + attn.q_lin = old # 还原,后续计时与正确性使用原模型 | ||
| 110 | + # —— 稳态缓存命中计时 —— | ||
| 111 | + for _ in range(5): | ||
| 112 | + invoke() | ||
| 113 | + sync(); t0 = time.perf_counter() | ||
| 114 | + for _ in range(100): | ||
| 115 | + invoke() | ||
| 116 | + sync() | ||
| 117 | + ms = (time.perf_counter() - t0) / 100 * 1e3 | ||
| 118 | + | ||
| 119 | + total = sum(type_counter.values()) | ||
| 120 | + dropped = sum(dropped_types.values()) | ||
| 121 | + return total, total - dropped, dropped, dict(dropped_types), rec, ms, bert, ids, mask | ||
| 122 | + | ||
| 123 | + | ||
| 124 | +def main(): | ||
| 125 | + print("=" * 72) | ||
| 126 | + print("示例6:内置 helper 过滤 nn.Module guard(基于 distilbert-base-uncased)") | ||
| 127 | + print("=" * 72) | ||
| 128 | + | ||
| 129 | + results = {} | ||
| 130 | + for name, helper in HELPERS: | ||
| 131 | + total, kept, dropped, dropped_types, rec, ms, bert, ids, mask = run(helper) | ||
| 132 | + results[name] = (total, kept, dropped, dropped_types, rec, ms, bert, ids, mask) | ||
| 133 | + print(f"\n[{name}]") | ||
| 134 | + print(f" guard 总数 = {total:4d},保留 = {kept:4d},过滤 = {dropped:4d}" | ||
| 135 | + f"({dropped / max(total,1) * 100:4.1f}%)") | ||
| 136 | + top = sorted(dropped_types.items(), key=lambda kv: -kv[1])[:4] | ||
| 137 | + if top: | ||
| 138 | + print(" 被过滤类型 TOP:", ", ".join(f"{t}×{n}" for t, n in top)) | ||
| 139 | + print(f" 换异型子模块后重编译 = {rec}") | ||
| 140 | + print(f" 稳态缓存命中耗时 = {ms:8.3f} ms/次") | ||
| 141 | + | ||
| 142 | + # [3] 正确性:以 all helper 为例,编译结果与 Eager 完全一致 | ||
| 143 | + _, _, _, _, _, _, bert, ids, mask = results[HELPERS[2][0]] | ||
| 144 | + torch._dynamo.reset() | ||
| 145 | + with torch.no_grad(): | ||
| 146 | + out_e = bert(input_ids=ids, attention_mask=mask).logits | ||
| 147 | + cf = torch.compile( | ||
| 148 | + bert, backend="aot_eager", | ||
| 149 | + options={"guard_filter_fn": | ||
| 150 | + torch.compiler.skip_guard_on_all_nn_modules_unsafe}) | ||
| 151 | + out_c = cf(input_ids=ids, attention_mask=mask).logits | ||
| 152 | + maxdiff = (out_e - out_c).abs().max().item() | ||
| 153 | + ok4 = torch.allclose(out_e, out_c, atol=1e-4) | ||
| 154 | + print("\n[正确性] helper 仅删 guard、不改数学(DistilBert logits):") | ||
| 155 | + print(f" max|Δ| = {maxdiff:.3e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 156 | + | ||
| 157 | + tb = results[HELPERS[0][0]] | ||
| 158 | + ti = results[HELPERS[1][0]] | ||
| 159 | + ta = results[HELPERS[2][0]] | ||
| 160 | + print("\n[重编译消除] 内建 Linear 换成同语义异型子类后:") | ||
| 161 | + print(f" baseline 重编译 = {tb[4]}(TYPE_MATCH 守模块类型,必然重特化)") | ||
| 162 | + print(f" inbuilt 重编译 = {ti[4]}(内建模块 guard 已过滤,复用缓存)") | ||
| 163 | + print(f" all 重编译 = {ta[4]}(全部模块 guard 已过滤,复用缓存)") | ||
| 164 | + | ||
| 165 | + print("\n[性能] 稳态缓存命中平均延迟:") | ||
| 166 | + print(f" baseline = {tb[5]:7.3f} ms/次") | ||
| 167 | + print(f" inbuilt = {ti[5]:7.3f} ms/次(guard {tb[0]} -> {ti[1]},减少 {ti[2]} 条)") | ||
| 168 | + print(f" all = {ta[5]:7.3f} ms/次(guard {tb[0]} -> {ta[1]},减少 {ta[2]} 条)") | ||
| 169 | + print(" 说明:guard 检查发生在算子派发前的 CPU 侧;本机 NPU 单算子派发开销较大,") | ||
| 170 | + print(" 端到端耗时主要由算子派发决定。深层/超深网络或 CPU 派发占比较高的场景下,") | ||
| 171 | + print(" guard 数量的大幅下降可直接转化为每次缓存命中的延迟收益。helper 属于 unsafe,") | ||
| 172 | + print(" 前提是运行期不更换这些 nn.Module 的类型/结构(权重数值作为图输入不受影响)。") | ||
| 173 | + | ||
| 174 | + | ||
| 175 | +if __name__ == "__main__": | ||
| 176 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/7_重编译消除验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/8_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/9_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例2_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例3_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例4_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例4_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例5_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例5_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例6_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Guard Filter/示例6_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-DVM/1_改写脚本.py+107-0
| @@ -0,0 +1,107 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · Inductor + DVM(torch_npu 内置 dvm 后端) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +后端:torch.compile(..., backend="inductor", options={"npu_backend": "dvm"}) | ||
| 7 | + DVM 能力由 torch_npu wheel 内置的 torch_npu._C.dvm 提供,无需额外安装。 | ||
| 8 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 9 | + | ||
| 10 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8): | ||
| 11 | + env -u CI OMP_NUM_THREADS=1 TORCHINDUCTOR_COMPILE_THREADS=1 \ | ||
| 12 | + TORCHINDUCTOR_NPU_BACKEND=dvm HF_HOME=/root/.cache/huggingface \ | ||
| 13 | + python 1_改写脚本.py | ||
| 14 | +说明:TORCHINDUCTOR_NPU_BACKEND=dvm 需在 import torch 前设置(脚本内已兜底)。 | ||
| 15 | +""" | ||
| 16 | +import os | ||
| 17 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 18 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 19 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 20 | +os.environ.setdefault("TORCH_COMPILE_DEBUG", "0") # 不在脚本目录生成 torch_compile_debug | ||
| 21 | +os.environ["TORCHINDUCTOR_NPU_BACKEND"] = "dvm" # 必须在 import torch 前指定 | ||
| 22 | + | ||
| 23 | +import time | ||
| 24 | +import torch | ||
| 25 | +import torch_npu # noqa: F401 导入即注册 NPU 后端与内置 dvm | ||
| 26 | +from transformers import DistilBertForMaskedLM | ||
| 27 | + | ||
| 28 | +BACKEND_NAME = "inductor + DVM" | ||
| 29 | +BATCH, SEQ_LEN = 4, 128 | ||
| 30 | +WARMUP, ITERS = 3, 20 | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def sync(): | ||
| 34 | + torch.npu.synchronize() | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 38 | + with torch.no_grad(): | ||
| 39 | + for _ in range(warmup): | ||
| 40 | + fn() | ||
| 41 | + sync() | ||
| 42 | + t0 = time.perf_counter() | ||
| 43 | + for _ in range(iters): | ||
| 44 | + fn() | ||
| 45 | + sync() | ||
| 46 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +def main(): | ||
| 50 | + torch.npu.set_device(0) | ||
| 51 | + print("=" * 72) | ||
| 52 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 53 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 54 | + f"device={torch.npu.get_device_name(0)}") | ||
| 55 | + print("TORCHINDUCTOR_NPU_BACKEND =", os.environ.get("TORCHINDUCTOR_NPU_BACKEND")) | ||
| 56 | + print("=" * 72) | ||
| 57 | + | ||
| 58 | + # 1) 加载模型与输入 ---------------------------------------------------- | ||
| 59 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 60 | + model.to("npu").eval() | ||
| 61 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 62 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 63 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}") | ||
| 64 | + | ||
| 65 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 66 | + def run_eager(): | ||
| 67 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 68 | + | ||
| 69 | + t_eager = bench(run_eager) | ||
| 70 | + with torch.no_grad(): | ||
| 71 | + out_eager = run_eager() | ||
| 72 | + | ||
| 73 | + # 3) 编译后端(DVM)--------------------------------------------------- | ||
| 74 | + t0 = time.perf_counter() | ||
| 75 | + compiled = torch.compile( | ||
| 76 | + model, backend="inductor", options={"npu_backend": "dvm"}) | ||
| 77 | + with torch.no_grad(): | ||
| 78 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次触发 DVM 编译 | ||
| 79 | + sync() | ||
| 80 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 81 | + | ||
| 82 | + def run_compiled(): | ||
| 83 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 84 | + | ||
| 85 | + t_compiled = bench(run_compiled) | ||
| 86 | + with torch.no_grad(): | ||
| 87 | + out_compiled = run_compiled() | ||
| 88 | + | ||
| 89 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 90 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 91 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 92 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 93 | + speedup = t_eager / t_compiled | ||
| 94 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 95 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 96 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 97 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 98 | + print(f" 首次编译耗时 = {t_compile:9.1f} ms") | ||
| 99 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 100 | + print(f" {BACKEND_NAME:18s}= {t_compiled:9.3f} ms/次") | ||
| 101 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 102 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 103 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 104 | + | ||
| 105 | + | ||
| 106 | +if __name__ == "__main__": | ||
| 107 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-DVM/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-DVM/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-MLIR/1_改写脚本.py+103-0
| @@ -0,0 +1,103 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · Inductor + MLIR(torch-mlir 编译后端) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +后端:torch.compile(..., backend="inductor", options={"npu_backend": "mlir"}) | ||
| 7 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 8 | + | ||
| 9 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8 + torch-mlir 0.0.1): | ||
| 10 | + env -u CI OMP_NUM_THREADS=1 TORCHINDUCTOR_COMPILE_THREADS=1 \ | ||
| 11 | + HF_HOME=/root/.cache/huggingface python 1_改写脚本.py | ||
| 12 | +说明:低内存机器务必 TORCHINDUCTOR_COMPILE_THREADS=1;env -u CI 关闭 CI 严格模式。 | ||
| 13 | +""" | ||
| 14 | +import os | ||
| 15 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 16 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 17 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 18 | +os.environ.setdefault("TORCH_COMPILE_DEBUG", "0") # 不在脚本目录生成 torch_compile_debug | ||
| 19 | + | ||
| 20 | +import time | ||
| 21 | +import torch | ||
| 22 | +import torch_npu # noqa: F401 导入即注册 NPU 后端 | ||
| 23 | +from transformers import DistilBertForMaskedLM | ||
| 24 | + | ||
| 25 | +BACKEND_NAME = "inductor + MLIR" | ||
| 26 | +BATCH, SEQ_LEN = 4, 128 | ||
| 27 | +WARMUP, ITERS = 3, 20 | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +def sync(): | ||
| 31 | + torch.npu.synchronize() | ||
| 32 | + | ||
| 33 | + | ||
| 34 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 35 | + with torch.no_grad(): | ||
| 36 | + for _ in range(warmup): | ||
| 37 | + fn() | ||
| 38 | + sync() | ||
| 39 | + t0 = time.perf_counter() | ||
| 40 | + for _ in range(iters): | ||
| 41 | + fn() | ||
| 42 | + sync() | ||
| 43 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +def main(): | ||
| 47 | + torch.npu.set_device(0) | ||
| 48 | + print("=" * 72) | ||
| 49 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 50 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 51 | + f"device={torch.npu.get_device_name(0)}") | ||
| 52 | + print("=" * 72) | ||
| 53 | + | ||
| 54 | + # 1) 加载模型与输入 ---------------------------------------------------- | ||
| 55 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 56 | + model.to("npu").eval() | ||
| 57 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 58 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 59 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}") | ||
| 60 | + | ||
| 61 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 62 | + def run_eager(): | ||
| 63 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 64 | + | ||
| 65 | + t_eager = bench(run_eager) | ||
| 66 | + with torch.no_grad(): | ||
| 67 | + out_eager = run_eager() | ||
| 68 | + | ||
| 69 | + # 3) 编译后端(MLIR)-------------------------------------------------- | ||
| 70 | + t0 = time.perf_counter() | ||
| 71 | + compiled = torch.compile( | ||
| 72 | + model, backend="inductor", options={"npu_backend": "mlir"}) | ||
| 73 | + with torch.no_grad(): | ||
| 74 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次触发 MLIR 编译 | ||
| 75 | + sync() | ||
| 76 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 77 | + | ||
| 78 | + def run_compiled(): | ||
| 79 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 80 | + | ||
| 81 | + t_compiled = bench(run_compiled) | ||
| 82 | + with torch.no_grad(): | ||
| 83 | + out_compiled = run_compiled() | ||
| 84 | + | ||
| 85 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 86 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 87 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 88 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 89 | + speedup = t_eager / t_compiled | ||
| 90 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 91 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 92 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 93 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 94 | + print(f" 首次编译耗时 = {t_compile:9.1f} ms") | ||
| 95 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 96 | + print(f" {BACKEND_NAME:18s}= {t_compiled:9.3f} ms/次") | ||
| 97 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 98 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 99 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 100 | + | ||
| 101 | + | ||
| 102 | +if __name__ == "__main__": | ||
| 103 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-MLIR/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-MLIR/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-Triton/1_改写脚本.py+104-0
| @@ -0,0 +1,104 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · Inductor + Triton-Ascend(算子融合 + Triton NPU 内核) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +后端:torch.compile(..., backend="inductor"),NPU 内核走 triton-ascend。 | ||
| 7 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 8 | + | ||
| 9 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8 + triton-ascend 3.2.2): | ||
| 10 | + env -u CI OMP_NUM_THREADS=1 TORCHINDUCTOR_COMPILE_THREADS=1 \ | ||
| 11 | + HF_HOME=/root/.cache/huggingface python 1_改写脚本.py | ||
| 12 | +说明:低内存机器务必 TORCHINDUCTOR_COMPILE_THREADS=1,否则编译大模型易 OOM; | ||
| 13 | + 必须 env -u CI 关闭 CI 严格模式,避免 make_fallback 断言中断。 | ||
| 14 | +""" | ||
| 15 | +import os | ||
| 16 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 17 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 18 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 19 | +os.environ.setdefault("TORCH_COMPILE_DEBUG", "0") # 不在脚本目录生成 torch_compile_debug | ||
| 20 | +os.environ.setdefault("TORCHINDUCTOR_NPU_BACKEND", "default") # inductor 默认 NPU 内核=Triton | ||
| 21 | + | ||
| 22 | +import time | ||
| 23 | +import torch | ||
| 24 | +import torch_npu # noqa: F401 导入即注册 NPU 后端 | ||
| 25 | +from transformers import DistilBertForMaskedLM | ||
| 26 | + | ||
| 27 | +BACKEND_NAME = "inductor + triton-ascend" | ||
| 28 | +BATCH, SEQ_LEN = 4, 128 | ||
| 29 | +WARMUP, ITERS = 3, 20 | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +def sync(): | ||
| 33 | + torch.npu.synchronize() | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 37 | + with torch.no_grad(): | ||
| 38 | + for _ in range(warmup): | ||
| 39 | + fn() | ||
| 40 | + sync() | ||
| 41 | + t0 = time.perf_counter() | ||
| 42 | + for _ in range(iters): | ||
| 43 | + fn() | ||
| 44 | + sync() | ||
| 45 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +def main(): | ||
| 49 | + torch.npu.set_device(0) | ||
| 50 | + print("=" * 72) | ||
| 51 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 52 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 53 | + f"device={torch.npu.get_device_name(0)}") | ||
| 54 | + print("=" * 72) | ||
| 55 | + | ||
| 56 | + # 1) 加载模型与输入 ---------------------------------------------------- | ||
| 57 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 58 | + model.to("npu").eval() | ||
| 59 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 60 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 61 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}") | ||
| 62 | + | ||
| 63 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 64 | + def run_eager(): | ||
| 65 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 66 | + | ||
| 67 | + t_eager = bench(run_eager) | ||
| 68 | + with torch.no_grad(): | ||
| 69 | + out_eager = run_eager() | ||
| 70 | + | ||
| 71 | + # 3) 编译后端 --------------------------------------------------------- | ||
| 72 | + t0 = time.perf_counter() | ||
| 73 | + compiled = torch.compile(model, backend="inductor") | ||
| 74 | + with torch.no_grad(): | ||
| 75 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次触发 Triton 编译 | ||
| 76 | + sync() | ||
| 77 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 78 | + | ||
| 79 | + def run_compiled(): | ||
| 80 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 81 | + | ||
| 82 | + t_compiled = bench(run_compiled) | ||
| 83 | + with torch.no_grad(): | ||
| 84 | + out_compiled = run_compiled() | ||
| 85 | + | ||
| 86 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 87 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 88 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 89 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 90 | + speedup = t_eager / t_compiled | ||
| 91 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 92 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 93 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 94 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 95 | + print(f" 首次编译耗时 = {t_compile:9.1f} ms") | ||
| 96 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 97 | + print(f" {BACKEND_NAME:18s}= {t_compiled:9.3f} ms/次") | ||
| 98 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 99 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 100 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 101 | + | ||
| 102 | + | ||
| 103 | +if __name__ == "__main__": | ||
| 104 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/Inductor-Triton/3_性能对比截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/NPUGraph_EX/1_改写脚本.py+101-0
| @@ -0,0 +1,101 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · NPUGraph_EX(NPU Graph 增强模式) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +后端:torch.compile(..., backend="npugraph_ex")(torch_npu 提供,不依赖 torchair) | ||
| 7 | +特点:NPUGraphs 的增强变体,对图捕获/重放路径做了进一步优化;同样要求静态输入, | ||
| 8 | + 首次调用包含图捕获开销,计时前需 warmup。 | ||
| 9 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 10 | + | ||
| 11 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8): | ||
| 12 | + env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface python 1_改写脚本.py | ||
| 13 | +""" | ||
| 14 | +import os | ||
| 15 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 16 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 17 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 18 | + | ||
| 19 | +import time | ||
| 20 | +import torch | ||
| 21 | +import torch_npu # noqa: F401 导入即注册 NPU 后端(含 npugraph_ex) | ||
| 22 | +from transformers import DistilBertForMaskedLM | ||
| 23 | + | ||
| 24 | +BACKEND_NAME = "npugraph_ex" | ||
| 25 | +BATCH, SEQ_LEN = 4, 128 | ||
| 26 | +WARMUP, ITERS = 5, 30 # 多给几次 warmup,确保首次图捕获完成后再计时 | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +def sync(): | ||
| 30 | + torch.npu.synchronize() | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 34 | + with torch.no_grad(): | ||
| 35 | + for _ in range(warmup): | ||
| 36 | + fn() | ||
| 37 | + sync() | ||
| 38 | + t0 = time.perf_counter() | ||
| 39 | + for _ in range(iters): | ||
| 40 | + fn() | ||
| 41 | + sync() | ||
| 42 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +def main(): | ||
| 46 | + torch.npu.set_device(0) | ||
| 47 | + print("=" * 72) | ||
| 48 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 49 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 50 | + f"device={torch.npu.get_device_name(0)}") | ||
| 51 | + print("=" * 72) | ||
| 52 | + | ||
| 53 | + # 1) 加载模型与输入(要求固定形状/静态输入)------------------------------ | ||
| 54 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 55 | + model.to("npu").eval() | ||
| 56 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 57 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 58 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}(固定形状)") | ||
| 59 | + | ||
| 60 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 61 | + def run_eager(): | ||
| 62 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 63 | + | ||
| 64 | + t_eager = bench(run_eager) | ||
| 65 | + with torch.no_grad(): | ||
| 66 | + out_eager = run_eager() | ||
| 67 | + | ||
| 68 | + # 3) 编译后端(npugraph_ex,首次调用完成图捕获)------------------------ | ||
| 69 | + t0 = time.perf_counter() | ||
| 70 | + compiled = torch.compile(model, backend="npugraph_ex") | ||
| 71 | + with torch.no_grad(): | ||
| 72 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次:图捕获 | ||
| 73 | + sync() | ||
| 74 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 75 | + | ||
| 76 | + def run_compiled(): | ||
| 77 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 78 | + | ||
| 79 | + t_compiled = bench(run_compiled) | ||
| 80 | + with torch.no_grad(): | ||
| 81 | + out_compiled = run_compiled() | ||
| 82 | + | ||
| 83 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 84 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 85 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 86 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 87 | + speedup = t_eager / t_compiled | ||
| 88 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 89 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 90 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 91 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 92 | + print(f" 首次图捕获耗时 = {t_compile:9.1f} ms") | ||
| 93 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 94 | + print(f" {BACKEND_NAME:14s}= {t_compiled:9.3f} ms/次(graph replay)") | ||
| 95 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 96 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 97 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 98 | + | ||
| 99 | + | ||
| 100 | +if __name__ == "__main__": | ||
| 101 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/NPUGraph_EX/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/NPUGraph_EX/3_性能对比截图.png+0-0
| @@ -0,0 +1,101 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · NPUGraphs(整图捕获为 NPU Graph 后重放) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +后端:torch.compile(..., backend="npugraphs")(torch_npu 提供,无需 torchair) | ||
| 7 | +特点:把固定形状的计算序列捕获成单张 NPU Graph,之后以 graph replay 执行, | ||
| 8 | + 消除逐算子 launch/调度开销;首次调用包含图捕获开销,计时需 warmup。 | ||
| 9 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 10 | + | ||
| 11 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8): | ||
| 12 | + env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface python 1_改写脚本.py | ||
| 13 | +""" | ||
| 14 | +import os | ||
| 15 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 16 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 17 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 18 | + | ||
| 19 | +import time | ||
| 20 | +import torch | ||
| 21 | +import torch_npu # noqa: F401 导入即注册 NPU 后端(含 npugraphs) | ||
| 22 | +from transformers import DistilBertForMaskedLM | ||
| 23 | + | ||
| 24 | +BACKEND_NAME = "npugraphs" | ||
| 25 | +BATCH, SEQ_LEN = 4, 128 | ||
| 26 | +WARMUP, ITERS = 5, 30 # 多给几次 warmup,确保首次图捕获完成后再计时 | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +def sync(): | ||
| 30 | + torch.npu.synchronize() | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 34 | + with torch.no_grad(): | ||
| 35 | + for _ in range(warmup): | ||
| 36 | + fn() | ||
| 37 | + sync() | ||
| 38 | + t0 = time.perf_counter() | ||
| 39 | + for _ in range(iters): | ||
| 40 | + fn() | ||
| 41 | + sync() | ||
| 42 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +def main(): | ||
| 46 | + torch.npu.set_device(0) | ||
| 47 | + print("=" * 72) | ||
| 48 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 49 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 50 | + f"device={torch.npu.get_device_name(0)}") | ||
| 51 | + print("=" * 72) | ||
| 52 | + | ||
| 53 | + # 1) 加载模型与输入(NPU Graph 要求固定形状/静态输入)--------------------- | ||
| 54 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 55 | + model.to("npu").eval() | ||
| 56 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 57 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 58 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}(固定形状)") | ||
| 59 | + | ||
| 60 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 61 | + def run_eager(): | ||
| 62 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 63 | + | ||
| 64 | + t_eager = bench(run_eager) | ||
| 65 | + with torch.no_grad(): | ||
| 66 | + out_eager = run_eager() | ||
| 67 | + | ||
| 68 | + # 3) 编译后端(npugraphs,首次调用完成图捕获)-------------------------- | ||
| 69 | + t0 = time.perf_counter() | ||
| 70 | + compiled = torch.compile(model, backend="npugraphs") | ||
| 71 | + with torch.no_grad(): | ||
| 72 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次:图捕获 | ||
| 73 | + sync() | ||
| 74 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 75 | + | ||
| 76 | + def run_compiled(): | ||
| 77 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 78 | + | ||
| 79 | + t_compiled = bench(run_compiled) | ||
| 80 | + with torch.no_grad(): | ||
| 81 | + out_compiled = run_compiled() | ||
| 82 | + | ||
| 83 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 84 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 85 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 86 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 87 | + speedup = t_eager / t_compiled | ||
| 88 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 89 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 90 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 91 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 92 | + print(f" 首次图捕获耗时 = {t_compile:9.1f} ms") | ||
| 93 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 94 | + print(f" {BACKEND_NAME:14s}= {t_compiled:9.3f} ms/次(graph replay)") | ||
| 95 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 96 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 97 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 98 | + | ||
| 99 | + | ||
| 100 | +if __name__ == "__main__": | ||
| 101 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/NPUGraphs/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/TorchAir-GE/1_改写脚本.py+108-0
| @@ -0,0 +1,108 @@ | |||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +任务2 · 后端体验 · TorchAir-GE(TorchAir 图引擎 Graph Engine) | ||
| 4 | +================================================================ | ||
| 5 | +模型:distilbert-base-uncased 的 DistilBertForMaskedLM(MLM 头,离线 HF 缓存) | ||
| 6 | +后端:先 import torch_npu,再 import torchair;使用 | ||
| 7 | + torchair.get_npu_backend(compiler_config=torchair.CompilerConfig()) | ||
| 8 | + 交给 GE(Graph Engine)做整图编译与执行。 | ||
| 9 | +验证:编译后输出与 Eager 一致(allclose),并对比平均推理延迟。 | ||
| 10 | + | ||
| 11 | +运行(910B,CANN 9.1.0,torch 2.7.1 + torch_npu 2.7.1.post8 + torchair 0.1): | ||
| 12 | + mkdir -p /root/.tvm_test_data /root/Ascend # GE 首次运行所需缓存目录 | ||
| 13 | + env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface python 1_改写脚本.py | ||
| 14 | +说明:GE 首次会做图编译,耗时明显长于普通后端,属正常现象。 | ||
| 15 | +""" | ||
| 16 | +import os | ||
| 17 | +os.environ.setdefault("OMP_NUM_THREADS", "1") | ||
| 18 | +os.environ.setdefault("HF_HOME", "/root/.cache/huggingface") | ||
| 19 | +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") | ||
| 20 | +os.environ.setdefault("TORCH_COMPILE_DEBUG", "0") # 不在脚本目录生成 torch_compile_debug | ||
| 21 | +os.chdir("/tmp") # GE/torchair 会在当前目录落 fusion_result.json 等产物,统一切到 /tmp 保持交付目录干净 | ||
| 22 | + | ||
| 23 | +import time | ||
| 24 | +import torch | ||
| 25 | +import torch_npu # noqa: F401 必须先导入 torch_npu | ||
| 26 | +import torchair # noqa: E402 再导入 torchair | ||
| 27 | +from transformers import DistilBertForMaskedLM | ||
| 28 | + | ||
| 29 | +BACKEND_NAME = "TorchAir-GE" | ||
| 30 | +BATCH, SEQ_LEN = 4, 128 | ||
| 31 | +WARMUP, ITERS = 3, 20 | ||
| 32 | + | ||
| 33 | + | ||
| 34 | +def sync(): | ||
| 35 | + torch.npu.synchronize() | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +def bench(fn, warmup=WARMUP, iters=ITERS): | ||
| 39 | + with torch.no_grad(): | ||
| 40 | + for _ in range(warmup): | ||
| 41 | + fn() | ||
| 42 | + sync() | ||
| 43 | + t0 = time.perf_counter() | ||
| 44 | + for _ in range(iters): | ||
| 45 | + fn() | ||
| 46 | + sync() | ||
| 47 | + return (time.perf_counter() - t0) / iters * 1e3 # ms/次 | ||
| 48 | + | ||
| 49 | + | ||
| 50 | +def main(): | ||
| 51 | + torch.npu.set_device(0) | ||
| 52 | + print("=" * 72) | ||
| 53 | + print(f"torch.compile 后端体验:{BACKEND_NAME}") | ||
| 54 | + print(f"torch={torch.__version__} torch_npu={torch_npu.__version__} " | ||
| 55 | + f"torchair={getattr(torchair, '__version__', '0.1')} " | ||
| 56 | + f"device={torch.npu.get_device_name(0)}") | ||
| 57 | + print("=" * 72) | ||
| 58 | + | ||
| 59 | + # 1) 加载模型与输入 ---------------------------------------------------- | ||
| 60 | + model = DistilBertForMaskedLM.from_pretrained("distilbert-base-uncased") | ||
| 61 | + model.to("npu").eval() | ||
| 62 | + input_ids = torch.randint(0, model.config.vocab_size, (BATCH, SEQ_LEN)).npu() | ||
| 63 | + attention_mask = torch.ones_like(input_ids).npu() | ||
| 64 | + print(f"输入:input_ids{tuple(input_ids.shape)} attention_mask{tuple(attention_mask.shape)}") | ||
| 65 | + | ||
| 66 | + # 2) Eager 基线 ------------------------------------------------------- | ||
| 67 | + def run_eager(): | ||
| 68 | + return model(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 69 | + | ||
| 70 | + t_eager = bench(run_eager) | ||
| 71 | + with torch.no_grad(): | ||
| 72 | + out_eager = run_eager() | ||
| 73 | + | ||
| 74 | + # 3) 编译后端(TorchAir GE)------------------------------------------- | ||
| 75 | + t0 = time.perf_counter() | ||
| 76 | + ge_backend = torchair.get_npu_backend(compiler_config=torchair.CompilerConfig()) | ||
| 77 | + compiled = torch.compile(model, backend=ge_backend) | ||
| 78 | + with torch.no_grad(): | ||
| 79 | + compiled(input_ids=input_ids, attention_mask=attention_mask) # 首次:GE 图编译 | ||
| 80 | + sync() | ||
| 81 | + t_compile = (time.perf_counter() - t0) * 1e3 | ||
| 82 | + | ||
| 83 | + def run_compiled(): | ||
| 84 | + return compiled(input_ids=input_ids, attention_mask=attention_mask).logits | ||
| 85 | + | ||
| 86 | + t_compiled = bench(run_compiled) | ||
| 87 | + with torch.no_grad(): | ||
| 88 | + out_compiled = run_compiled() | ||
| 89 | + | ||
| 90 | + # 4) 正确性 ----------------------------------------------------------- | ||
| 91 | + maxdiff = (out_eager - out_compiled).abs().max().item() | ||
| 92 | + ok4 = torch.allclose(out_eager, out_compiled, atol=1e-4) | ||
| 93 | + ok5 = torch.allclose(out_eager, out_compiled, atol=1e-5) | ||
| 94 | + speedup = t_eager / t_compiled | ||
| 95 | + print("\n[正确性] Eager vs 编译输出:") | ||
| 96 | + print(f" max|Δ| = {maxdiff:.4e} allclose(atol=1e-4) = {ok4} {'✅' if ok4 else '❌'}") | ||
| 97 | + print(f" allclose(atol=1e-5) = {ok5}") | ||
| 98 | + print("\n[性能] 推理平均延迟(已 warmup,torch.npu.synchronize 计时):") | ||
| 99 | + print(f" 首次 GE 编译耗时 = {t_compile:9.1f} ms") | ||
| 100 | + print(f" Eager = {t_eager:9.3f} ms/次") | ||
| 101 | + print(f" {BACKEND_NAME:16s}= {t_compiled:9.3f} ms/次") | ||
| 102 | + print(f" 加速比 = {speedup:9.2f}x") | ||
| 103 | + print("\n结论:{},相对 Eager {:.2f}x。".format( | ||
| 104 | + "正确性校验通过" if ok4 else "正确性未通过,请检查", speedup)) | ||
| 105 | + | ||
| 106 | + | ||
| 107 | +if __name__ == "__main__": | ||
| 108 | + main() | ||
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/TorchAir-GE/2_正确性验证截图.png+0-0
A01_tasks/2026/torch_npu_public_beta/ArgoNorthrop/任务2-torch.compile体验与实践/TorchAir-GE/3_性能对比截图.png+0-0
| @@ -0,0 +1,140 @@ | |||
| 1 | +===================================================================== | ||
| 2 | + 任务2 交付件存放指南 | ||
| 3 | +===================================================================== | ||
| 4 | + | ||
| 5 | +本指南详细说明任务2所有交付件的存放位置、文件命名和内容要求。 | ||
| 6 | +请严格按照本指南整理交付材料,确保验收顺利通过。 | ||
| 7 | + | ||
| 8 | + | ||
| 9 | +--------------------------------------------------------------------- | ||
| 10 | +一、文件夹结构 | ||
| 11 | +--------------------------------------------------------------------- | ||
| 12 | + | ||
| 13 | +任务2-torch.compile体验与实践/ | ||
| 14 | +├── 交付件说明.txt ← 本文件(您正在阅读的) | ||
| 15 | +├── 链接交付件.xls ← 用于填写 Issue 链接和PR链接 | ||
| 16 | +│ | ||
| 17 | +├── Guard Filter/ | ||
| 18 | +│ ├── 1_过滤字典版本guard.py | ||
| 19 | +│ ├── 2_过滤运行时状态guard.py | ||
| 20 | +│ ├── 3_组合多类guard过滤.py | ||
| 21 | +│ ├── 4_按变量名过滤.py | ||
| 22 | +│ ├── 5_过滤全局变量guard.py | ||
| 23 | +│ ├── 6_结合内置helper.py | ||
| 24 | +│ ├── 7_重编译消除验证截图.png ← 示例1(含模型名与三段证据) | ||
| 25 | +│ ├── 8_正确性验证截图.png ← 示例1 | ||
| 26 | +│ ├── 9_性能对比截图.png ← 示例1 | ||
| 27 | +│ ├── 示例2_重编译消除验证截图.png / 示例2_正确性验证截图.png / 示例2_性能对比截图.png | ||
| 28 | +│ ├── 示例3_重编译消除验证截图.png / 示例3_正确性验证截图.png / 示例3_性能对比截图.png | ||
| 29 | +│ ├── 示例4_重编译消除验证截图.png / 示例4_正确性验证截图.png / 示例4_性能对比截图.png | ||
| 30 | +│ ├── 示例5_重编译消除验证截图.png / 示例5_正确性验证截图.png / 示例5_性能对比截图.png | ||
| 31 | +│ └── 示例6_重编译消除验证截图.png / 示例6_正确性验证截图.png / 示例6_性能对比截图.png | ||
| 32 | +│ (6 个示例均基于真实 DistilBertForMaskedLM;每张整屏含 [2]重编译 [3]正确性 [4]性能) | ||
| 33 | +│ | ||
| 34 | +├── Inductor-Triton/ | ||
| 35 | +│ ├── 1_改写脚本.py | ||
| 36 | +│ ├── 2_正确性验证截图.png | ||
| 37 | +│ └── 3_性能对比截图.png | ||
| 38 | +│ | ||
| 39 | +├── Inductor-MLIR/ | ||
| 40 | +│ ├── 1_改写脚本.py | ||
| 41 | +│ ├── 2_正确性验证截图.png | ||
| 42 | +│ └── 3_性能对比截图.png | ||
| 43 | +│ | ||
| 44 | +├── Inductor-DVM/ | ||
| 45 | +│ ├── 1_改写脚本.py | ||
| 46 | +│ ├── 2_正确性验证截图.png | ||
| 47 | +│ └── 3_性能对比截图.png | ||
| 48 | +│ | ||
| 49 | +├── NPUGraphs/ | ||
| 50 | +│ ├── 1_改写脚本.py | ||
| 51 | +│ ├── 2_正确性验证截图.png | ||
| 52 | +│ └── 3_性能对比截图.png | ||
| 53 | +│ | ||
| 54 | +├── NPUGraph_EX/ | ||
| 55 | +│ ├── 1_改写脚本.py | ||
| 56 | +│ ├── 2_正确性验证截图.png | ||
| 57 | +│ └── 3_性能对比截图.png | ||
| 58 | +│ | ||
| 59 | +├── AOT_Eager/ | ||
| 60 | +│ ├── 1_改写脚本.py | ||
| 61 | +│ ├── 2_正确性验证截图.png | ||
| 62 | +│ └── 3_性能对比截图.png | ||
| 63 | +│ | ||
| 64 | +└── TorchAir-GE/ | ||
| 65 | + ├── 1_改写脚本.py | ||
| 66 | + ├── 2_正确性验证截图.png | ||
| 67 | + └── 3_性能对比截图.png | ||
| 68 | + | ||
| 69 | +*图片要求:截图清晰,能看到验证通过(未抛出重编译异常) | ||
| 70 | + | ||
| 71 | +--------------------------------------------------------------------- | ||
| 72 | +二、统一实验设置(910B3,CANN 9.1.0,Python 3.12/aarch64) | ||
| 73 | +--------------------------------------------------------------------- | ||
| 74 | + 模型:distilbert-base-uncased 的 DistilBertForMaskedLM(离线 HF 缓存,自包含) | ||
| 75 | + 输入:BATCH=4, SEQ_LEN=128;warmup=3,计时 20 次取均值;torch.npu.synchronize() | ||
| 76 | + 正确性判据:与 Eager 输出 allclose,atol=1e-4(实测最大绝对误差 3.8e-5~5.4e-5) | ||
| 77 | + Python 环境:7 个后端用 /root/venv271(torch 2.7.1 + torch_npu 2.7.1.post8 | ||
| 78 | + + triton-ascend 3.2.2+release20260701 + torch-mlir + torchair); | ||
| 79 | + 6 个 Guard Filter 用 /root/.local/share/venv290(torch/torch_npu 2.9.0.post8) | ||
| 80 | + 通用前缀:env -u CI HF_HOME=/root/.cache/huggingface(inductor 再加 | ||
| 81 | + OMP_NUM_THREADS=1 TORCHINDUCTOR_COMPILE_THREADS=1,低内存必须单线程编译) | ||
| 82 | + | ||
| 83 | +--------------------------------------------------------------------- | ||
| 84 | +三、本机实测结果(延迟为稳态平均,加速比相对同脚本 Eager) | ||
| 85 | +--------------------------------------------------------------------- | ||
| 86 | + 后端 最大|Δ| 后端延迟 Eager延迟 加速比 首次编译/图捕获 | ||
| 87 | + AOT_Eager 4.10e-05 17.9 ms 5.6 ms 0.31x 无需编译 | ||
| 88 | + NPUGraphs 4.15e-05 3.274 ms 5.5 ms 1.69x 图捕获约 4.36 s | ||
| 89 | + NPUGraph_EX 3.91e-05 3.000 ms 5.5 ms 2.01x 图捕获约 5.51 s | ||
| 90 | + TorchAir-GE 3.81e-05 2.289 ms 5.4 ms 2.34x GE 编译约 20.3 s | ||
| 91 | + Inductor-Triton 5.34e-05 7.646 ms 5.5 ms 0.68x 编译约 302 s | ||
| 92 | + Inductor-MLIR 5.34e-05 7.066 ms 5.474 ms 0.77x HFusion 编译约 1608 s(约27分) | ||
| 93 | + Inductor-DVM 5.34e-05 25.643 ms 5.570 ms 0.22x 编译约 17 s | ||
| 94 | + 说明:图类后端(NPU Graph / GE)在 batch=4、seq=128 小算子密集的推理上延迟收益 | ||
| 95 | + 最明显;Inductor 三个后端首次编译成本高,在该小规模/小 batch 上稳态延迟暂 | ||
| 96 | + 未超过 Eager,属首版在小模型上的真实表现,此处如实呈现,不做挑选。 | ||
| 97 | + | ||
| 98 | + Guard Filter(6 脚本,2.9.0,统一基于 distilbert-base-uncased / DistilBertForMaskedLM, | ||
| 99 | + batch=2、seq=64;编译对象均为该模型实例本身或内部调用该模型的普通 Python 函数, | ||
| 100 | + 6 个脚本中不含任何自建网络模型): | ||
| 101 | + 示例1 字典 guard 重编译❗→✅ max|Δ|=3.43e-05 切换当次 196.57x | ||
| 102 | + 示例2 运行时状态 Part B 重编译❗→✅ 2.69e-05 205.07x | ||
| 103 | + 示例3 组合(prefill/decode) ❗→✅ 3.78e-05 197.39x | ||
| 104 | + 示例4 按变量名 重编译❗→✅ 3.72e-05 574.26x | ||
| 105 | + 示例5 全局变量 重编译❗→✅ 2.58e-05 199.31x | ||
| 106 | + 示例6 内置 helper guard 1492 → inbuilt 832(过滤44.2%) / all 501(过滤66.4%); | ||
| 107 | + 内建 Linear 换同语义异型子类后 baseline 重编译=True、 | ||
| 108 | + inbuilt/all=False;max|Δ|=3.62e-05;稳态 20.545→20.305 ms/次 | ||
| 109 | + 说明:加速比为“状态切换当次调用”之比(baseline 当次含真实重编译约 3.6~11 s, | ||
| 110 | + 过滤后命中缓存约 19 ms);正确性均为 DistilBert logits 与 Eager allclose。 | ||
| 111 | + 截图:7/8/9 为示例1 的重编译/正确性/性能三段;示例2~6 各附同名三张证据。 | ||
| 112 | + 示例2 Part A 如实复现:GRAD_MODE 等 C++ 全局运行时态即使 filter 返回 False, | ||
| 113 | + 仍会被 AOTAutograd 按前/反向图重新特化(已作为 Issue #4996 反馈)。 | ||
| 114 | + | ||
| 115 | +--------------------------------------------------------------------- | ||
| 116 | +四、逐脚本运行命令(截图时逐条执行) | ||
| 117 | +--------------------------------------------------------------------- | ||
| 118 | + # —— Guard Filter(venv290,进入 Guard Filter 目录)—— | ||
| 119 | + for f in 1_过滤字典版本guard 2_过滤运行时状态guard 3_组合多类guard过滤 \ | ||
| 120 | + 4_按变量名过滤 5_过滤全局变量guard 6_结合内置helper; do | ||
| 121 | + env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 122 | + /root/.local/share/venv290/bin/python "$f.py" | ||
| 123 | + done | ||
| 124 | + | ||
| 125 | + # —— 7 个后端(venv271,分别进入各自目录)—— | ||
| 126 | + # AOT_Eager / NPUGraphs / NPUGraph_EX: | ||
| 127 | + env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 128 | + /root/venv271/bin/python 1_改写脚本.py | ||
| 129 | + # TorchAir-GE(首次先建缓存目录): | ||
| 130 | + mkdir -p /root/.tvm_test_data /root/Ascend | ||
| 131 | + env -u CI OMP_NUM_THREADS=1 HF_HOME=/root/.cache/huggingface \ | ||
| 132 | + /root/venv271/bin/python 1_改写脚本.py | ||
| 133 | + # Inductor-Triton: | ||
| 134 | + env -u CI OMP_NUM_THREADS=1 TORCHINDUCTOR_COMPILE_THREADS=1 \ | ||
| 135 | + HF_HOME=/root/.cache/huggingface /root/venv271/bin/python 1_改写脚本.py | ||
| 136 | + # Inductor-MLIR:(同上,脚本内 options 指定 npu_backend=mlir) | ||
| 137 | + # Inductor-DVM :(同上,脚本内已在 import torch 前置 TORCHINDUCTOR_NPU_BACKEND=dvm) | ||
| 138 | + # 注意 MLIR 首次 HFusion 全图编译约 27 分钟,请耐心等待,属一次性成本。 | ||
| 139 | +===================================================================== | ||
| 140 | + | ||
Binary files do not support preview