news 2026/10/1 13:49:15

Stable Diffusion TensorRT转换cpu与cuda设备不一致

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Stable Diffusion TensorRT转换cpu与cuda设备不一致

stable diffusion 的 TensorRT 转换模型报错里,Expected all tensors to be on the same device, but found at least two devices, cpu and cuda:0!这种提示出现的频率极高。它通常不是 TensorRT 本身坏了,也不是显卡驱动突然抽风,而是转换脚本在某个算子前同时接收到了 CPU 张量和 GPU 张量。PyTorch 在即将进入 CUDA 内核时发现设备不一致,于是直接抛错。乍看像 CUDA 版本问题,实际排查下来,九成以上是模型加载方式、启动参数、CPU offload、额外网络或输入张量构造引起的。只要把设备归属捋清楚,转换成功率会立刻上一个台阶。这篇内容适合正在折腾 Stable Diffusion TensorRT 加速、被cpu and cuda:0卡住的人,也适合想把 diffusers 手工导出流程跑通的人。

1. 报错现场还原:cpu 与 cuda:0 混用到底卡在哪里

1.1 从 PyTorch 设备检查机制看懂报错

PyTorch 的算子分两类:一类只认 CPU,一类只认 CUDA,还有一类两者都认但要求参与运算的所有张量在同一设备。像torch.mm、torch.matmul、F.linear、卷积、LayerNorm 等,进入 CUDA 实现后会逐个检查参数。如果self在cuda:0,weight在cpu,或者input在cuda:0,bias在cpu,就会抛出设备不一致错误。这个报错的后半段(when checking argument for argument ...)非常关键,后面的参数名往往直接指向具体算子里的哪个张量出了问题。

比如argument weight,通常说明某个线性层或卷积层的权重还在 CPU,而输入已经在 GPU。argument mat2多半说明矩阵乘法的第二个矩阵在 CPU,常见于 cross attention 里的encoder_hidden_states没有搬到 GPU。argument self则说明输入张量本身在 CPU。argument bias说明偏置没同步。看懂这几个参数名,比盲目改版本有用得多。很多人一看到cpu and cuda:0就去重装 CUDA、降级 PyTorch,其实大多数情况下,模型里某个模块根本没执行.to("cuda")。

TensorRT 转换时,这个问题会被放大,因为转换脚本通常只关心计算图能不能被 trace,而不会帮你自动补齐设备归属。手工写 diffusers 转换脚本时,如果只写了pipe.unet.to("cuda"),却忘了text_encoder、text_encoder_2、vae、safety_checker,那么构造 dummy input 时,文本编码器输出还在 CPU,UNet 的 cross attention 一进 CUDA 就会报错。A1111 WebUI 加 TensorRT 扩展时,启动参数里的--medvram、--lowvram、--use-cpu也会把部分模块放在 CPU,转换脚本抓取模型时正好抓到 CPU 版本。

1.2 TensorRT 转换链路里谁把张量留在了 CPU

一条典型的 Stable Diffusion TensorRT 转换链路是:加载模型权重、把模型组件搬到 GPU、构造 dummy input、trace 或导出 ONNX、调用 TensorRT builder 生成 engine。设备不一致可能发生在第二步,也可能发生在第三步。第二步的常见漏网之鱼包括text_encoder、text_encoder_2、vae、safety_checker、image_encoder、controlnet、ip_adapter、LoRA 注入层。第三步的常见问题是 dummy input 用torch.randn创建时没写device,默认落在 CPU,模型却在 GPU。

SD1.5 和 SDXL 在设备处理上也有区别。SD1.5 通常只有一个text_encoder,cross attention 维度是 768。SDXL 有text_encoder和text_encoder_2,UNet 还需要text_embeds和time_ids这类额外条件输入。如果只移动了 UNet 和 VAE,SDXL 的第二个文本编码器留在 CPU,转换时就会在 cross attention 或 addition embedding 环节报设备错误。ControlNet 和 IP-Adapter 更麻烦,它们会往 UNet 里插入额外模块,这些模块如果不显式.to(device),同样会留在 CPU。

还有一个隐蔽场景是accelerate的 offload。调用过pipe.enable_model_cpu_offload()或pipe.enable_sequential_cpu_offload()后,模块会在推理时按需换入换出,即使你再调用pipe.to("cuda"),accelerate hook 也可能继续把模块搬回 CPU。转换脚本需要的是稳定、静态的设备归属,所以转换前必须关闭 offload,最稳的办法是重新加载 pipeline,而不是在半路强行覆盖。

2. 转换前必须做的环境与设备自检

2.1 版本矩阵与依赖对齐

TensorRT 转换对版本很敏感,尤其是 PyTorch、CUDA、TensorRT、diffusers、transformers、accelerate 这几个。版本错配不一定会直接报cpu and cuda:0,但会引发各种奇怪的图优化失败、算子不支持、dtype 不一致,最后表现成设备错误。下面是一组在 SD1.5 TensorRT 转换里比较稳的组合,不是唯一答案,但适合作为排查基准。

组件推荐版本说明
Python3.10.x3.11 部分扩展兼容性还不稳
PyTorch2.1.2+cu121 或 2.0.1+cu118与 CUDA 对齐
CUDA12.1 或 11.8以 PyTorch 编译版本为准
TensorRT8.6.x对旧扩展兼容更好
diffusers0.24.xAPI 相对稳定
transformers4.36.x避免过新导致 CLIP 输出结构变化
accelerate0.25.xoffload 行为较明确
xformers可选,转换时关闭会改变 attention 实现
onnx1.15.x导出用
onnxruntime-gpu1.17.x对照验证用
polygraphy0.47.x分析 ONNX 与 engine

TensorRT 10 不是不能用,而是很多 Stable Diffusion 扩展、torch2trt、旧版导出脚本还没跟上。你如果已经装了 TensorRT 10,转换失败后可以先用trtexec --version确认,再考虑降级到 8.6。CUDA 版本也要和 PyTorch 对齐,torch.version.cuda输出的版本必须和系统 CUDA 主版本兼容。不要只看nvidia-smi里的 CUDA 版本,那只是驱动支持的最高版本。

2.2 用巡检脚本找出隐藏在 cpu 的模块

转换之前,先跑一段设备巡检。这个动作比改任何脚本都值。无论是 diffusers 手工脚本还是 WebUI 扩展,核心都是找出哪些torch.nn.Module的参数还在 CPU。

import torch def inspect_pipe(pipe): print("torch:", torch.__version__) print("cuda:", torch.version.cuda) print("cuda available:", torch.cuda.is_available()) if torch.cuda.is_available(): print("gpu:", torch.cuda.get_device_name(0)) print("capability:", torch.cuda.get_device_capability(0)) for name, module in pipe.components.items(): if isinstance(module, torch.nn.Module): devices = sorted({str(p.device) for p in module.parameters()}) dtypes = sorted({str(p.dtype) for p in module.parameters()}) print(f"{name:20s} devices={devices} dtypes={dtypes}") if hasattr(pipe, "safety_checker") and pipe.safety_checker is not None: devices = sorted({str(p.device) for p in pipe.safety_checker.parameters()}) print(f"{'safety_checker':20s} devices={devices}")

正常输出应该类似:

unet devices=['cuda:0'] dtypes=['torch.float16'] vae devices=['cuda:0'] dtypes=['torch.float16'] text_encoder devices=['cuda:0'] dtypes=['torch.float16'] tokenizer devices=[] dtypes=[] scheduler devices=[] dtypes=[]

如果你看到text_encoder devices=['cpu'],或者vae devices=['cpu'],那问题已经找到了。注意tokenizer、scheduler不是nn.Module,没有参数,不会参与张量计算,不用管。safety_checker如果存在,也建议一起移动;转换阶段为了减少变量,也可以直接传safety_checker=None把它关掉。

注意:巡检脚本要在模型加载完成、准备转换之前执行。不要在 CPU offload 已启用的情况下巡检,否则你会看到模块在 CPU,但那只是 offload hook 的正常状态,不一定是加载错误。

2.3 启动参数与 offload 设置的坑

A1111 WebUI 用户尤其要检查启动参数。--medvram和--lowvram会把部分模型按需换到 CPU,目的是降低显存占用,但 TensorRT 转换需要稳定的设备归属。转换时最好用干净参数启动,去掉--medvram、--lowvram、--use-cpu、--precision full、--no-half。如果显存实在不够,可以先用--medvram跑普通推理,转换时再换成普通启动,转完 engine 后再恢复。

diffusers 手工脚本里,enable_model_cpu_offload()和enable_sequential_cpu_offload()是重点排查对象。这两个方法会注册 accelerate hook,模块会在 forward 时被临时搬到 GPU,forward 结束又搬回 CPU。这种动态搬运和 TensorRT 导出、ONNX trace 的静态图假设冲突。转换前应该重新加载 pipeline,不调用任何 offload 方法,只调用pipe.to("cuda")。如果你已经调用过 offload,最稳的处理是删掉当前 pipeline,重新from_pretrained,再移动设备。

另外,enable_attention_slicing()、enable_vae_slicing()、enable_vae_tiling()不一定直接导致设备错误,但它们会改变计算图结构,增加导出难度。转换 UNet 时建议先关闭。等 engine 生成后,推理阶段再按需开启切片和分块。不要把推理优化和转换优化混在一起,两者目标不同。

3. 核心修复:把设备对齐落到代码和脚本里

3.1 通用修复模板:加载、移动、关闭 offload

先把通用模板立起来。下面这段代码适合 diffusers 手工加载 SD1.5 或 SDXL,核心是加载后统一 dtype 和 device,并二次强制移动所有子模块。

import torch from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline MODEL_PATH = "/path/to/your/model" DEVICE = torch.device("cuda:0") DTYPE = torch.float16 def load_pipe_sd15(): pipe = StableDiffusionPipeline.from_pretrained( MODEL_PATH, torch_dtype=DTYPE, use_safetensors=True, local_files_only=True, safety_checker=None, ) pipe.disable_attention_slicing() pipe.disable_vae_slicing() pipe.disable_vae_tiling() pipe = pipe.to(DEVICE) for name, module in pipe.components.items(): if isinstance(module, torch.nn.Module): module.to(device=DEVICE, dtype=DTYPE) return pipe def load_pipe_sdxl(): pipe = StableDiffusionXLPipeline.from_pretrained( MODEL_PATH, torch_dtype=DTYPE, use_safetensors=True, local_files_only=True, safety_checker=None, ) pipe.disable_attention_slicing() pipe.disable_vae_slicing() pipe.disable_vae_tiling() pipe = pipe.to(DEVICE) for name, module in pipe.components.items(): if isinstance(module, torch.nn.Module): module.to(device=DEVICE, dtype=DTYPE) return pipe

这里有几个细节。from_pretrained里的torch_dtype=torch.float16决定权重加载后的 dtype,pipe.to(DEVICE)负责移动设备,但某些版本里不会递归修改所有子模块,所以后面再遍历pipe.components做一次强制移动。safety_checker=None在转换阶段可以减少一个变量,推理阶段如果需要再单独加载。disable_*方法用于关闭切片和分块,保证导出图更干净。

如果你用的是 fp32 转换,就把DTYPE改成torch.float32。不要加载时用 fp16,构造输入时用 fp32,那样会报 dtype 错误,虽然错误信息不是cpu and cuda:0,但也是同源问题。设备对齐和 dtype 对齐要一起做。

3.2 针对 sd-webui-tensorrt 的改法与排查点

A1111 WebUI 的 TensorRT 扩展通常通过shared.sd_model访问当前模型。转换前先确认shared.sd_model的设备。可以在扩展脚本里加临时日志:

from modules import shared import torch print("sd_model device:", getattr(shared.sd_model, "device", None)) print("cuda available:", torch.cuda.is_available()) if torch.cuda.is_available(): print("current device:", torch.cuda.current_device())

如果输出cpu,说明 WebUI 启动参数或设置把模型放在了 CPU。常见原因是启动时带了--use-cpu,或者模型还没加载完成就点了转换。另一个原因是--medvram下shared.sd_model可能在 CPU,转换脚本取到时需要显式移动。

一个保守的修复方式是在转换函数入口加:

import torch from modules import shared device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") if shared.sd_model is not None: shared.sd_model.to(device) if hasattr(shared.sd_model, "first_stage_model"): shared.sd_model.first_stage_model.to(device) if hasattr(shared.sd_model, "cond_stage_model"): shared.sd_model.cond_stage_model.to(device)

但不要一上来就大改扩展源码。先确认问题到底出在扩展脚本、WebUI 模型加载,还是启动参数。修改前备份文件,修改后重启完全重新加载。WebUI 的热重载有时不会释放旧模块,容易让你误判。

注意:A1111 的cond_stage_model可能是 CLIP 文本编码器封装,内部有 transformer 和 position embedding。只移动外层不够,最好用巡检脚本看内部参数设备。如果扩展脚本拿不到pipe.components,就递归遍历shared.sd_model.modules(),打印每个子模块的第一个参数设备。

3.3 LoRA、ControlNet、VAE 与文本编码器的特殊处理

LoRA 是转换时最容易忽略的变量。A1111 加载 LoRA 后,它会往 UNet 的线性层里注入低秩矩阵。这些注入层可能没有跟随 UNet 移动,或者 dtype 不一致。转换基础模型时,建议先禁用所有 LoRA,只转换原始 UNet。等 engine 生成并验证通过后,再考虑 LoRA 合并或单独处理。很多 TensorRT 扩展不支持动态 LoRA,合并权重后再导出更稳。

ControlNet 如果也要转 TensorRT,需要单独处理。ControlNet 有自己的controlnet模块和controlnet_cond输入。转换时要确保controlnet.to(device),并且 control image 经过预处理后也在 GPU。常见错误是controlnet_cond还在 CPU,进入 ControlNet 的卷积层时报cpu and cuda:0。可以这样构造:

control_image = control_image.to(device=DEVICE, dtype=DTYPE) controlnet = controlnet.to(device=DEVICE, dtype=DTYPE)

VAE 在只转换 UNet 时不是必需,但如果你要转换 VAE 编码器或完整 pipeline,就必须移动。VAE 还有vae.config.force_upcast之类设置,SDXL 某些版本会在 fp16 下强制 upcast 到 fp32,导致 dtype 和 device 混用。转换 VAE 前先确认版本行为,必要时显式设置pipe.vae.to(device=DEVICE, dtype=DTYPE),并关闭强制 upcast。

文本编码器是 SDXL 转换的重灾区。SDXL 需要text_encoder和text_encoder_2都输出 embedding,并拼接到 UNet 的encoder_hidden_states。如果只移动了 UNet,文本编码器留在 CPU,UNet 的 cross attention 报argument mat2的概率极高。处理方式就是巡检脚本先看设备,再统一.to(device, dtype)。如果你不需要文本编码器参与转换,而是提前算好 embedding,那也要确保传入 UNet 的 embedding 已经在 GPU。

3.4 输入张量构造:别只移动模型忘了输入

模型移动完之后,输入张量也必须同设备、同 dtype。下面是一个 UNet dummy input 构造函数,重点是不要硬编码,而是从模型参数推断设备和 dtype。

def build_unet_inputs(pipe, batch=1, height=512, width=512): device = next(pipe.unet.parameters()).device dtype = next(pipe.unet.parameters()).dtype latent_h = height // 8 latent_w = width // 8 sample = torch.randn(batch, 4, latent_h, latent_w, device=device, dtype=dtype) timestep = torch.tensor([1], device=device, dtype=torch.int64) encoder_hidden_states = torch.randn( batch, 77, pipe.unet.config.cross_attention_dim, device=device, dtype=dtype, ) return { "sample": sample, "timestep": timestep, "encoder_hidden_states": encoder_hidden_states, }

SDXL 的 UNet 还需要额外条件。不同 diffusers 版本参数名略有区别,常见写法是:

def build_unet_inputs_sdxl(pipe, batch=1, height=1024, width=1024): device = next(pipe.unet.parameters()).device dtype = next(pipe.unet.parameters()).dtype latent_h = height // 8 latent_w = width // 8 sample = torch.randn(batch, 4, latent_h, latent_w, device=device, dtype=dtype) timestep = torch.tensor([1], device=device, dtype=torch.int64) encoder_hidden_states = torch.randn( batch, 77, pipe.unet.config.cross_attention_dim, device=device, dtype=dtype, ) text_embeds = torch.randn(batch, 1280, device=device, dtype=dtype) time_ids = torch.randn(batch, 6, device=device, dtype=dtype) return { "sample": sample, "timestep": timestep, "encoder_hidden_states": encoder_hidden_states, "added_cond_kwargs": { "text_embeds": text_embeds, "time_ids": time_ids, }, }

如果你用torch.onnx.export,timestep的 dtype 和 shape 会影响导出图。有些版本期望标量 tensor,有些期望一维 batch tensor。最稳的办法是先跑一次 PyTorch forward,确认能正常输出,再导出。不要一上来就写 ONNX 导出,先把设备问题排除。

4. 完整实操:从零转换一个 Stable Diffusion TensorRT 引擎

4.1 最小化转换脚本与参数选择

这里给一条以 ONNX 为中间格式、再用trtexec转 engine 的路线。它不如一键扩展方便,但排查设备问题最直接。先导出 UNet ONNX:

import torch from pathlib import Path from diffusers import StableDiffusionPipeline MODEL_PATH = "/path/to/model" ONNX_PATH = Path("/path/to/unet.onnx") DEVICE = torch.device("cuda:0") DTYPE = torch.float16 pipe = StableDiffusionPipeline.from_pretrained( MODEL_PATH, torch_dtype=DTYPE, use_safetensors=True, safety_checker=None, ) pipe = pipe.to(DEVICE) for name, module in pipe.components.items(): if isinstance(module, torch.nn.Module): module.to(device=DEVICE, dtype=DTYPE) pipe.unet.eval() inputs = build_unet_inputs(pipe, batch=1, height=512, width=512) with torch.no_grad(): torch.onnx.export( pipe.unet, (inputs["sample"], inputs["timestep"], inputs["encoder_hidden_states"]), ONNX_PATH.as_posix(), input_names=["sample", "timestep", "encoder_hidden_states"], output_names=["out_sample"], opset_version=17, do_constant_folding=True, dynamic_axes={ "sample": {0: "batch", 2: "latent_h", 3: "latent_w"}, "timestep": {0: "batch"}, "encoder_hidden_states": {0: "batch"}, "out_sample": {0: "batch", 2: "latent_h", 3: "latent_w"}, }, )

导出成功后再用trtexec构建 engine:

trtexec \ --onnx=/path/to/unet.onnx \ --saveEngine=/path/to/unet.plan \ --fp16 \ --minShapes=sample:1x4x64x64,timestep:1,encoder_hidden_states:1x77x768 \ --optShapes=sample:2x4x64x64,timestep:2,encoder_hidden_states:2x77x768 \ --maxShapes=sample:4x4x64x64,timestep:4,encoder_hidden_states:4x77x768 \ --workspace=2048

参数选择上,--fp16是提速关键,但会带来精度误差。--workspace是 TensorRT 构建时可用显存,单位 MB。SD1.5 UNet 在 512x512 下,--workspace=2048通常够用。如果显存紧张,降到 1024 或 512,代价是编译时间变长或部分层选不到最优内核。--minShapes、--optShapes、--maxShapes决定动态 shape 范围。分辨率 512x512 对应 latent 64x64,768x768 对应 96x96,1024x1024 对应 128x128。你常用的分辨率要落在 min 和 max 之间,opt 形状越接近实际使用,性能越好。

4.2 显存估算与 batch/分辨率设置

转换时的显存占用不只是模型权重,还包括 ONNX 导出临时张量、TensorRT builder 工作区、CUDA context、cuDNN 工作区。下面是一张粗略估算表,实际因驱动、版本、模型结构浮动。

模型UNet 参数量fp16 权重大小转换额外显存建议显存
SD1.5约 860M约 1.7GB2GB 到 4GB8GB 以上
SD2.1约 865M约 1.7GB2GB 到 4GB8GB 以上
SDXL约 2.6B约 5.2GB4GB 到 6GB16GB 以上
SDXL Refiner约 2.6B约 5.2GB4GB 到 6GB16GB 以上

8GB 显卡转 SD1.5 时,建议关闭浏览器、聊天软件、其他推理进程,--workspace设 1024 或 2048,max_batch_size设 1,分辨率固定 512x512。如果仍然 OOM,先把--maxShapes的 batch 降到 1,把--optShapes也降到 1,再试。SDXL 在 8GB 卡上转 UNet 非常紧张,即使勉强转成功,engine 加载和推理也可能爆显存,实际意义不大。16GB 卡转 SDXL 更稳,24GB 卡可以开更大 workspace 和 batch。

batch 设置也要和实际推理对齐。如果你只做单张图生成,batch 固定 1 能减少编译时间,engine 体积也更小。如果你要批量出图,opt batch 可以设 2 或 4,但 max batch 不要超过显存能承受的范围。动态 batch 会让 TensorRT 为多个 shape 编译内核,引擎体积和编译时间都会增加。

4.3 转换后验证与性能对比

engine 生成后,不要直接上生产。先用同一组输入对比 PyTorch 输出和 TensorRT 输出。可以用polygraphy或onnxruntime做数值对照:

polygraphy run /path/to/unet.onnx \ --trt \ --onnxrt \ --input-shapes sample:1x4x64x64,timestep:1,encoder_hidden_states:1x77x768 \ --fp16 \ --workspace=2048 \ --save-engine=/path/to/unet.plan

如果数值误差在1e-2到1e-3级别,通常可接受。如果误差很大,先检查输入 dtype 和 shape,再看是否某个算子被 TensorRT 用了低精度实现。FP16 引擎在 attention 和 LayerNorm 上可能累积误差,出图表现为局部噪点、色彩偏移或细节丢失。对画质要求高的场景,可以尝试 FP32 转换,或者对敏感层保持 FP32。TensorRT 支持 layer-wise 精度设置,但手动调层比较费时间。

性能对比可以用固定 seed 和固定 prompt,分别跑 PyTorch 和 TensorRT,记录 20 步或 30 步总耗时。下面是一张示例表,数据只代表某张显卡上的相对趋势,不要当成绝对值。

分辨率步数PyTorch fp16TensorRT fp16提升幅度
512x512203.5s1.8s约 48%
512x512305.1s2.6s约 49%
768x768207.8s4.1s约 47%

验证通过后,再把 engine 接回推理流程。A1111 的 TensorRT 扩展通常会自动加载对应 engine 文件,diffusers 手工流程则需要自己写一个包装类,把pipe.unet替换成 TensorRT 推理模块。替换时注意输入输出名称和 dtype 对齐,否则又会绕回设备或 dtype 问题。

5. 常见问题速查与避坑经验

5.1 高频报错对照表

报错关键字真正原因处理动作
Expected all tensors to be on the same device, but found at least two devices, cpu and cuda:0模块或输入在 CPU巡检pipe.components,统一.to(device)
argument weight线性层或卷积层权重在 CPU移动对应模块,检查 LoRA 注入层
argument mat2第二个矩阵在 CPU检查encoder_hidden_states是否在 GPU
argument self输入张量在 CPUdummy input 加device=device
CUDA out of memory转换显存不足降低 workspace、batch、分辨率
Unsupported ONNX op自定义算子或 opset 不匹配换 opset 17,或替换算子
TensorRT engine build failedTensorRT 与 CUDA/PyTorch 不匹配对齐版本,优先 TensorRT 8.6
expected scalar type Half but found Floatdtype 混用模型和输入统一 fp16 或 fp32
engine file not found路径或命名规则不对用绝对路径,检查扩展配置

这张表建议收藏。遇到报错先对关键字,再决定是查设备、查版本还是查显存。不要一上来就重装环境,那样只会把问题搅得更乱。

5.2 我踩过的坑与独家技巧

第一个坑是--medvram。我在 A1111 里用--medvram启动,普通出图正常,一转换 TensorRT 就报cpu and cuda:0。巡检发现text_encoder在 CPU,UNet 在 GPU。去掉--medvram重启后,转换一次通过。后来我养成习惯,转换 TensorRT 一律用干净启动参数,不挂任何显存优化。

第二个坑是 SDXL 的text_encoder_2。我按 SD1.5 的经验只移动了 UNet 和 VAE,结果转换时在 cross attention 报argument weight。巡检脚本一跑,text_encoder_2果然还在 CPU。把两个文本编码器都移动后解决。SDXL 用户一定要记住,文本编码器有两个,不能只处理一个。

第三个坑是 offload 后强行pipe.to("cuda")。我在脚本里先调了enable_model_cpu_offload(),后来又调pipe.to("cuda"),以为就切回 GPU 了。结果转换到一半又报 CPU 张量。原因是 accelerate hook 还在。解决办法是重新加载 pipeline,不调用任何 offload。这个坑很隐蔽,因为巡检时可能看到模块在 GPU,但 forward 时 hook 又把它搬走了。

第四个坑是timestep。我写 dummy input 时用了torch.tensor(1),默认在 CPU。模型在 GPU,forward 时直接报设备不一致。改成torch.tensor([1], device=device)后正常。类似地,torch.randn不写device、torch.zeros不写dtype,都会留下隐患。构造输入时最好统一从一个设备变量和一个 dtype 变量派生。

第五个坑是 TensorRT 版本。我一开始装了 TensorRT 10,扩展和torch2trt都报奇怪的构建错误。降级到 8.6 后,同样的脚本直接跑通。TensorRT 版本不是越新越好,Stable Diffusion 生态里的很多转换工具还停留在 8.x。除非你明确知道自己在用什么新特性,否则优先选兼容组合。

独家技巧方面,我建议把巡检脚本做成一个单独函数,每次转换前自动打印设备表。再准备一个最小化的 UNet forward 测试,不导出,只跑一次 PyTorch 前向。如果最小 forward 都报设备错误,就别急着导出 ONNX。还有,转换前先固定随机种子,记录模型路径、dtype、device、diffusers 版本、TensorRT 版本。转换失败时这些信息能帮你快速复现。转换成功后,把 engine 文件和配置一起备份,换模型或换版本时不要混用。

5.3 转换失败后的回滚与清理

转换失败后,先别反复重试。第一步是重启 Python 进程或 WebUI,释放 CUDA context 和显存。删除中间生成的.onnx、.plan、TensorRT 缓存文件。检查torch.cuda.empty_cache()是否有效,但不要指望它能解决所有显存碎片。第二步是确认当前启动参数,把--medvram、--lowvram、--use-cpu去掉,重启后再试。第三步是回到最小 forward 测试,确认模型本身能在 GPU 上跑通。如果模型 forward 都失败,问题不在 TensorRT,而在加载或设备移动。

如果 A1111 扩展修改过源码,回滚备份文件,再重启。diffusers 手工脚本失败后,把脚本拆成三段:加载并巡检、最小 forward、导出 ONNX。每段单独跑通再进入下一段。很多人把加载、移动、导出、构建 engine 写在一个大脚本里,一报错就不知道哪一步出问题。拆开之后,cpu and cuda:0通常在第一段或第二段就能定位。

我现在遇到这个报错,基本不会再从头猜。先跑巡检脚本,看哪个模块还在 cpu;再看启动参数有没有 medvram、lowvram、use-cpu;最后检查输入张量是不是随手写成了 CPU。设备对齐之后,TensorRT 转换剩下的就是版本兼容和显存调度。能把这两件事分开看,排查速度会快很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 13:49:13

多模态知识图谱与中医辅助诊疗:Python毕设源码实战与避坑指南

简介:基于多模态知识图谱的中医智能辅助诊疗平台毕业设计源码,采用Python与Flask框架构建,覆盖知识图谱构建、智能问诊、症状匹配、用户管理等核心模块,适合计算机相关专业学生用于毕业设计参考、课程大作业实践或项目实战学习。代…

作者头像 李华
网站建设 2026/10/1 13:49:07

Unity Editor打包系统架构设计:构建管线分层与CI/CD落地实践

凌晨两点,项目组微信群里炸了锅。Android包打出来,测试安装后界面资源全是旧的;iOS包倒是新的,但登录模块必现闪退。两边一核对,发现一个人用的是本地构建,另一个人跑了CI机器上的旧脚本,打包参…

作者头像 李华
网站建设 2026/10/1 13:48:30

罗布乐思杀手模拟器开发实战:逆天运气与服务器压力对抗的优化方案

最近又开新坑了,这次是罗布乐思(Roblox)平台上的《杀手模拟器》项目。本以为最费心思的是玩法设计和随机掉落数值,结果真正把我按在地上摩擦的,是“逆天运气”和“拉完了的服务器”之间的对抗。游戏里玩家运气爆棚疯狂…

作者头像 李华
网站建设 2026/10/1 13:48:23

游戏本选购全攻略:从硬件参数到验机避坑指南

上个月帮一个朋友挑游戏本,他拿着一台“i9处理器RTX 4060显卡”的机器问我值不值,价格确实不贵,但我扫了一眼屏幕参数和功耗释放,直接让他退了。这不是第一回了。每次有人把笔记本电脑选购攻略看得太简单,只盯着CPU和显…

作者头像 李华
网站建设 2026/10/1 13:47:55

Lua __index 元方法深度解析:表、函数、nil 三态原理与实战避坑

1. 为什么一个看似简单的__index测试,能暴露你对 Lua 元表机制的真实理解水位?我第一次在项目里写__index的时候,以为就是“找不到字段就去另一个表里找”,三行代码搞定,测试通过,合上电脑就去吃饭。结果三…

作者头像 李华
网站建设 2026/10/1 13:47:28

Edge与Chrome兼容性冲突排查:四层差异与降级实战

浏览器兼容性这个问题,我原以为在 Chromium 一统天下的年代已经快绝迹了,直到上个月连着踩了三个坑:同一套后台管理系统,在谷歌 Chrome 里登录、跳转、导出一切正常,换到 Edge 上登录后弹窗关不掉;另一个数…

作者头像 李华