ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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:0weight在cpu或者input在cuda:0bias在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 时文本编码器输出还在 CPUUNet 的 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_encodercross attention 维度是 768。SDXL 有text_encoder和text_encoder_2UNet 还需要text_embeds和time_ids这类额外条件输入。如果只移动了 UNet 和 VAESDXL 的第二个文本编码器留在 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.2cu121 或 2.0.1cu118与 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 与 engineTensorRT 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_checkerNone把它关掉。注意巡检脚本要在模型加载完成、准备转换之前执行。不要在 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 时被临时搬到 GPUforward 结束又搬回 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_dtypeDTYPE, use_safetensorsTrue, local_files_onlyTrue, safety_checkerNone, ) 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(deviceDEVICE, dtypeDTYPE) return pipe def load_pipe_sdxl(): pipe StableDiffusionXLPipeline.from_pretrained( MODEL_PATH, torch_dtypeDTYPE, use_safetensorsTrue, local_files_onlyTrue, safety_checkerNone, ) 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(deviceDEVICE, dtypeDTYPE) return pipe这里有几个细节。from_pretrained里的torch_dtypetorch.float16决定权重加载后的 dtypepipe.to(DEVICE)负责移动设备但某些版本里不会递归修改所有子模块所以后面再遍历pipe.components做一次强制移动。safety_checkerNone在转换阶段可以减少一个变量推理阶段如果需要再单独加载。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(deviceDEVICE, dtypeDTYPE) controlnet controlnet.to(deviceDEVICE, dtypeDTYPE)VAE 在只转换 UNet 时不是必需但如果你要转换 VAE 编码器或完整 pipeline就必须移动。VAE 还有vae.config.force_upcast之类设置SDXL 某些版本会在 fp16 下强制 upcast 到 fp32导致 dtype 和 device 混用。转换 VAE 前先确认版本行为必要时显式设置pipe.vae.to(deviceDEVICE, dtypeDTYPE)并关闭强制 upcast。文本编码器是 SDXL 转换的重灾区。SDXL 需要text_encoder和text_encoder_2都输出 embedding并拼接到 UNet 的encoder_hidden_states。如果只移动了 UNet文本编码器留在 CPUUNet 的 cross attention 报argument mat2的概率极高。处理方式就是巡检脚本先看设备再统一.to(device, dtype)。如果你不需要文本编码器参与转换而是提前算好 embedding那也要确保传入 UNet 的 embedding 已经在 GPU。3.4 输入张量构造别只移动模型忘了输入模型移动完之后输入张量也必须同设备、同 dtype。下面是一个 UNet dummy input 构造函数重点是不要硬编码而是从模型参数推断设备和 dtype。def build_unet_inputs(pipe, batch1, height512, width512): 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, devicedevice, dtypedtype) timestep torch.tensor([1], devicedevice, dtypetorch.int64) encoder_hidden_states torch.randn( batch, 77, pipe.unet.config.cross_attention_dim, devicedevice, dtypedtype, ) return { sample: sample, timestep: timestep, encoder_hidden_states: encoder_hidden_states, }SDXL 的 UNet 还需要额外条件。不同 diffusers 版本参数名略有区别常见写法是def build_unet_inputs_sdxl(pipe, batch1, height1024, width1024): 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, devicedevice, dtypedtype) timestep torch.tensor([1], devicedevice, dtypetorch.int64) encoder_hidden_states torch.randn( batch, 77, pipe.unet.config.cross_attention_dim, devicedevice, dtypedtype, ) text_embeds torch.randn(batch, 1280, devicedevice, dtypedtype) time_ids torch.randn(batch, 6, devicedevice, dtypedtype) return { sample: sample, timestep: timestep, encoder_hidden_states: encoder_hidden_states, added_cond_kwargs: { text_embeds: text_embeds, time_ids: time_ids, }, }如果你用torch.onnx.exporttimestep的 dtype 和 shape 会影响导出图。有些版本期望标量 tensor有些期望一维 batch tensor。最稳的办法是先跑一次 PyTorch forward确认能正常输出再导出。不要一上来就写 ONNX 导出先把设备问题排除。4. 完整实操从零转换一个 Stable Diffusion TensorRT 引擎4.1 最小化转换脚本与参数选择这里给一条以 ONNX 为中间格式、再用trtexec转 engine 的路线。它不如一键扩展方便但排查设备问题最直接。先导出 UNet ONNXimport 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_dtypeDTYPE, use_safetensorsTrue, safety_checkerNone, ) pipe pipe.to(DEVICE) for name, module in pipe.components.items(): if isinstance(module, torch.nn.Module): module.to(deviceDEVICE, dtypeDTYPE) pipe.unet.eval() inputs build_unet_inputs(pipe, batch1, height512, width512) 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_version17, do_constant_foldingTrue, 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构建 enginetrtexec \ --onnx/path/to/unet.onnx \ --saveEngine/path/to/unet.plan \ --fp16 \ --minShapessample:1x4x64x64,timestep:1,encoder_hidden_states:1x77x768 \ --optShapessample:2x4x64x64,timestep:2,encoder_hidden_states:2x77x768 \ --maxShapessample:4x4x64x64,timestep:4,encoder_hidden_states:4x77x768 \ --workspace2048参数选择上--fp16是提速关键但会带来精度误差。--workspace是 TensorRT 构建时可用显存单位 MB。SD1.5 UNet 在 512x512 下--workspace2048通常够用。如果显存紧张降到 1024 或 512代价是编译时间变长或部分层选不到最优内核。--minShapes、--optShapes、--maxShapes决定动态 shape 范围。分辨率 512x512 对应 latent 64x64768x768 对应 96x961024x1024 对应 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 或 2048max_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 \ --workspace2048 \ --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是否在 GPUargument self输入张量在 CPUdummy input 加devicedeviceCUDA out of memory转换显存不足降低 workspace、batch、分辨率Unsupported ONNX op自定义算子或 opset 不匹配换 opset 17或替换算子TensorRT engine build failedTensorRT 与 CUDA/PyTorch 不匹配对齐版本优先 TensorRT 8.6expected scalar type Half but found Floatdtype 混用模型和输入统一 fp16 或 fp32engine file not found路径或命名规则不对用绝对路径检查扩展配置这张表建议收藏。遇到报错先对关键字再决定是查设备、查版本还是查显存。不要一上来就重装环境那样只会把问题搅得更乱。5.2 我踩过的坑与独家技巧第一个坑是--medvram。我在 A1111 里用--medvram启动普通出图正常一转换 TensorRT 就报cpu and cuda:0。巡检发现text_encoder在 CPUUNet 在 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。模型在 GPUforward 时直接报设备不一致。改成torch.tensor([1], devicedevice)后正常。类似地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 转换剩下的就是版本兼容和显存调度。能把这两件事分开看排查速度会快很多。
返回列表