本文档记录 OrderAndChaos/controlnet-inpaint-endpoint(ControlNet v1.1 inpaint 权重,等价于
control_v11p_sd15_inpaint)在华为昇腾 NPU(npu:0)上的适配与真机验证结果。
ControlNetModel(361,279,120 参数,cross_attention_dim=768,
block_out_channels=[320,640,1280,1280],in_channels=4),配合 Stable Diffusion 1.5 base
组成 StableDiffusionControlNetPipeline。image-to-image / ControlNet inpainting(图像局部重绘)。float32(正确性对照,未用 fp16)。diffusion_pytorch_model.safetensors + config.json),
base 管线需另取 SD1.5。本次 base 使用非 gated 的 stable-diffusion-v1-5/stable-diffusion-v1-5
(即原 runwayml/stable-diffusion-v1-5 的同权重社区再上传)。适配要点:
trust_remote_code;ControlNetModel.from_pretrained +
StableDiffusionControlNetPipeline.from_pretrained(SD15, controlnet=...) 后 .to("npu:0") 即可。handler.py:把 mask 覆盖(待重绘)区域的像素置为 -1.0,得到 inpaint 控制图,
再传给 StableDiffusionControlNetPipeline(非 Inpaint 专用管线)。safety_checker=None、feature_extractor=None(研究评测,跳过可选组件;本地未取其配置)。UniPCMultistepScheduler(官方 handler 一致)。相关获取地址:
| 组件 | 版本 |
|---|---|
CANN | 8.5.1 |
torch | 2.9.0 |
torch-npu | 2.9.0.post1 |
diffusers | 0.39.0 |
transformers | 4.57.6 |
numpy | 1.26.4 |
Python | 3.11.14 |
Ascend910,逻辑卡 npu:0(物理卡 1,ASCEND_RT_VISIBLE_DEVICES=1,65536 MB HBM)torch_npu + diffusers~/.cache/models/controlnet-inpaint-endpoint(约 1.4 GB),
SD1.5 base ~/.cache/models/sd15-base(约 4 GB)export PATH=/usr/local/python3.11.14/bin:$PATH
pip install -r requirements.txt # diffusers 需 --no-deps,见文件注释
python -c "import torch,torch_npu; assert torch.npu.is_available(); print('NPU OK')"权重自动获取:inference.py 顶部自包含,缺权重时经 HF_ENDPOINT=https://hf-mirror.com
snapshot_download 拉取 ControlNet 权重与 SD1.5 base(只取 safetensors + 必要 config,
忽略 .bin/TF/Flax/ONNX 重复件)。实测经 hf-mirror + Xet 直连可稳定下载(大文件偶发超时会自动续传)。
已验证通过的命令(套卡锁 + 固定物理卡 + 编译缓存进 /tmp):
flock -w 7200 /tmp/npu_card1.lock bash -c '
export PATH=/usr/local/python3.11.14/bin:$PATH
ASCEND_RT_VISIBLE_DEVICES=1 ASCEND_CACHE_PATH=/tmp/ascend_cache_controlnet-inpaint-endpoint \
python inference.py --device npu'脚本一次跑完三道加载门禁、Smoke、Gate-3 对照、HF32 实测与性能采集,结果写
/tmp/controlnet-inpaint-endpoint_npu_results.json,重绘输出图存
/tmp/controlnet-inpaint-endpoint_out.png。
python inference.py --device npu 在昇腾 NPU 上的真实输出(节选):
=== [Gate 1] ControlNet output_loading_info(期望 0/0/0)===
controlnet: missing=0 unexpected=0 mismatched=0 params=361,279,120
[Gate 1] PASS (0/0/0)
=== [Gate 2] ControlNet config 落地审计(原始 config.json vs 生效值)===
cross_attention_dim: file=768 effective=768 OK
block_out_channels: file=[320, 640, 1280, 1280] effective=[320, 640, 1280, 1280] OK
in_channels: file=4 effective=4 OK
conditioning_embedding_out_channels: file=[16, 32, 96, 256] effective=[16, 32, 96, 256] OK
[Gate 2] PASS
=== [§5 Smoke] 合成图+矩形mask inpaint ===
generator on: npu
image size=(512, 512) mode=RGB shape=(512, 512, 3) dtype=uint8
pixel stats: mean=101.60 std=60.72 min=1 max=241
unmasked-region mean|Δ vs orig|=16.59 (lower=structure preserved)
determinism (same seed x2): max|Δpixel|=0 mean|Δpixel|=0.0000
pretrained unmasked-region corr vs orig=0.8758
=== [Gate 3] 随机初始化 ControlNet 对照(N=3 seeds)===
[A structure-control] pretrained unmasked_corr=0.8758 vs random seeds=[-0.0857, -0.0857, -0.0857] mean=-0.0857 -> beats_all(+0.1)=True
[B determinism] same-seed max|Δpixel|=0 (<=5 -> True)
[C sane range] min=1 max=241 std=60.72 -> True
[Gate 3] DECISIVE — pretrained ControlNet conditions output on control image; random-init destroys structure
=== ALL STAGES DONE (Gate1/Gate2/Gate3 PASS, smoke OK, perf OK) ===验证结果:
missing=0 unexpected=0 mismatched=0,361,279,120 参数。config.json 关键结构键(cross_attention_dim/block_out_channels/
conditioning_embedding_out_channels 等)全部落到 model.config。512x512 重绘输出,像素范围合理(1~241)。max|Δpixel|=0。0.876(预训练 ControlNet 保留原图结构、只在 mask 内生成)。测试条件:单图、25 步、512x512、guidance_scale=7.5、CFG(batch=2)、float32;
预热 5 次、正式 20 次、每次前后 torch.npu.synchronize()。
| 指标 | 数值 |
|---|---|
avg_ms | 2956.9 ms |
min_ms / max_ms | 2946.6 / 2996.7 ms |
p50_ms / p90_ms / p95_ms | 2951.5 / 2994.7 / 2996.7 ms |
throughput(p50) | 0.339 img/s |
peak_HBM | 6762 MB |
p95/p50 | 1.02(稳态平滑,无明显尖刺) |
生成任务无参照 GT,采用「预训练 ControlNet 受控生成 vs 随机初始化 ControlNet」三证判别:
| 指标 | 数值 |
|---|---|
| 评测方式 | 未 mask 区域与原图像素相关性 + 决定性 + 数值合理性 |
| 预训练 未mask区域相关性 | 0.8758 |
| 随机初始化 未mask区域相关性 | -0.0857(N=3 seed 均值) |
| 判别 | beats_all(+0.1)=True → 决定性胜出 |
| 决定性 | 同 seed×2 `max |
| 数值范围 | min=1 max=241 std=60.72(sane) |
结论:预训练 ControlNet 令输出受控于控制图(保留原图非重绘区结构、仅在 mask 内合成内容), 随机初始化 ControlNet 破坏图像结构(相关性趋近 0)。三证均通过,Gate 3 决定性 PASS。
HF32 说明:管线 float32;对 ControlNet 一次真实 fp32 前向测 HF32 on/off 偏差,
mid_block max|Δ| = 9.07e-03(相对 1.76e-03)。评测默认关闭 HF32(torch.npu.conv.allow_hf32=False、
torch.npu.matmul.allow_hf32=False)以保证确定性。



该仓库只含 ControlNet 权重,SD1.5 base 需另取,且 base 的 model_index.json 声明了可选的
feature_extractor/safety_checker 组件——只下载核心子目录时会因缺 preprocessor_config.json 加载失败。
实际失败特征如下:
StableDiffusionControlNetPipeline.from_pretrained 抛出 OSError: Can't load image processor。Can't load image processor for '.../sd15-base' ... make sure ... containing a preprocessor_config.json file(其上层为 IndexError: list index out of range)。transformers/image_processing_base.py: get_image_processor_dict。原因不是权重缺失或路径错误,而是 base 管线的可选组件(feature_extractor 的 CLIPImageProcessor
配置)未随核心 safetensors 一起下载。处理方式:加载时显式跳过可选组件——
from_pretrained(..., safety_checker=None, feature_extractor=None, requires_safety_checker=False)。
其余注意事项:
>=5 次
剔除编译,正式段 p95/p50=1.10 稳态平滑。ASCEND_CACHE_PATH 必须 import 前预建:缺该目录会让 GE/AOE 初始化失败,
常伪装成 ACL error 500001。脚本已在顶部 os.makedirs(...)。-1.0,其余保留归一化像素;
这是 control_v11p_sd15_inpaint 的正确用法(配 StableDiffusionControlNetPipeline,非 Inpaint 管线)。TBE Subprocess ... main process disappeared;用 setsid/nohup 完全脱离终端再跑。runwayml/stable-diffusion-v1-5 已从 HF 下架,使用同权重的
stable-diffusion-v1-5/stable-diffusion-v1-5(非 gated)替代。#NPU #Ascend