「轻量」描述的是模型侧的权重体积和单步计算量,不等于你手上这张卡跑得起来,更不等于跑得快。判断顺序建议固定成两条线:先从参数量和精度算出显存下限,再在本机跑一张最低分辨率的图,量出峰值显存和单张耗时。这两组数凑齐,才谈得上「能不能用」。装完就爆显存、或者一张图等到失去耐心,通常不是玄学,而是这两条线里有一条事先没算。
Mage-Flow 能不能在你的机器上跑,取决于两件事:权重体积加运行时固定开销决定显存够不够,步数乘以单步耗时决定一张图等多久。建议先按参数量乘每参数字节数估出权重体积,再给解码器、文本编码器和中间激活留余量;然后在最低分辨率下用 torch.cuda.max_memory_reserved() 与 nvidia-smi 双记录,跑两到三次取热态耗时。峰值显存贴住卡容量、或相邻分辨率档耗时出现陡增,就是这台机器的实际上限,拿到上限再对照可接受等待时间判断用途。
按模型规格反推权重体积和大致显存下限
这一步是为了在下载和安装之前就判断「有没有必要试」。换算只用到两个量:参数量和每参数字节数。fp32 是 4 字节,fp16/bf16 是 2 字节,int8 约 1 字节,int4 约 0.5 字节(另外还有少量 scale/zero 开销)。所以 1B 参数在 bf16 下权重约 2GB,int8 约 1GB,int4 约 0.5GB。Mage-Flow 实际参数量以模型卡或仓库里的 config 为准,这里只给换算方式,不给结论数字。
显存下限不能只算主模型。文本编码器、VAE/解码器往往单独占一份权重,再加上 CUDA context、cuDNN/cuBLAS workspace 这些固定开销(通常几百 MB 起步),以及随分辨率和 batch 增长的中间激活。保守的估法是:所有组件权重之和乘 1.5 到 2,再加 1GB 左右余量,激活部分按 512 长边先粗估,最终以本机实测为准。量化能压权重,但反量化有计算开销,出图质量也要自己对比,别默认量化版一定更快。
先把权重总体积和参数量读出来,在模型目录下执行:
# 1) 先看权重文件有多大
du -sh ./Mage-Flow* 2>/dev/null
ls -lh *.safetensors | awk '{print $5, $9}'
# 2) 读 safetensors header 累加参数量与权重字节数
python - <<'PY'
import glob, json, struct
BYTES = {"F64":8,"F32":4,"F16":2,"BF16":2,"I64":8,"I32":4,"I8":1,"U8":1,"F8_E4M3":1,"F8_E5M2":1}
tot_p = tot_b = 0
for f in sorted(glob.glob("*.safetensors")):
with open(f, "rb") as fh:
hlen = struct.unpack("<Q", fh.read(8))[0]
hdr = json.loads(fh.read(hlen))
for name, meta in hdr.items():
if name == "__metadata__":
continue
n = 1
for d in meta["shape"]:
n *= d
tot_p += n
tot_b += n * BYTES.get(meta["dtype"], 2)
print(f"参数量约 {tot_p/1e9:.2f} B,权重体积约 {tot_b/2**30:.2f} GiB")
PY
如果模型是 fp16 单文件,把上面算出的权重体积和你的卡容量直接对比,差值不到 1.5GB 的基本可以判定「不用装了,先换卡或者换量化版本」。
跑一张最低分辨率图并记录显存峰值与耗时
目标是拿到本机的真实下限数据,所以分辨率先取能跑的最低档(比如 256 或 512 长边),步数固定并写进记录,因为步数变化会直接改变耗时,混淆两个变量后面没法比较。
显存要双记录。nvidia-smi 看到的是整卡占用,包含进程固定开销和其他进程;框架内的统计只算本进程的分配器。判断「这台机器还塞不塞得下别的东西」看前者,判断「模型本身吃多少」看后者。nvidia-smi 轮询可以先开一个采集:
nvidia-smi `--query-gpu`=timestamp,memory.used,memory.total `--format`=csv -lms 200 -f /tmp/vram.csv &
# 跑完一张图后
kill %1
awk -F', ' 'NR>1{gsub(/ MiB/,"",$2); if($2+0>m)m=$2+0} END{print "nvidia-smi 峰值 MiB:", m}' /tmp/vram.csv
框架内用 PyTorch 的峰值统计,注意要先同步再重置,否则统计到的可能是上一个任务残留:
import torch, time
torch.cuda.synchronize()
torch.cuda.reset_peak_memory_stats()
t0 = time.perf_counter()
out = pipe(prompt="a cat on a desk", height=H, width=W, num_inference_steps=N).images[0]
torch.cuda.synchronize() # 必须同步,否则时间戳打在提交时而不是算完时
t1 = time.perf_counter()
print("allocated %.0f MiB" % (torch.cuda.max_memory_allocated()/2**20))
print("reserved %.0f MiB" % (torch.cuda.max_memory_reserved()/2**20))
print("单张耗时 %.2f s" % (t1-t0))
计时起点放在输入张量已就绪、第一次前向之前,终点放在最后一步解码完成、结果张量回到 CPU 之后;只在 pipeline 调用外面包一层计时,往往漏掉解码和拷贝。冷热态要分开:第一次运行含 CUDA context 初始化、权重搬运和 cuDNN autotune,通常明显偏慢,建议同一配置连跑两到三次,用第二次或第三次作为热态耗时,同时保留第一次的数当作启动成本。
逐步抬分辨率和批量,找出这台机器的上限
每次只改一个变量。先固定步数和 batch=1,把长边按 512、640、768、896、1024 逐档往上推;然后把分辨率固定在已知稳定档,抬 batch 到 2、4。每一档都填进同一张表:
- 分辨率(长边)、步数、batch
- allocated 峰值、reserved 峰值、nvidia-smi 峰值
- 单张耗时、总耗时
- 结果:正常 / 能跑但余量小 / 显存不足 / 耗时陡增
判定阈值建议这样写,避免拿极限值当可用值:
- 出现 CUDA out of memory 或分配器报碎片错误,该档直接记为不可用,不写进可用区间。
- 峰值显存距离卡容量只剩 1GB 上下,记为「能跑但没余量」;同一张卡上再开浏览器看显存、再起一个进程都可能挂。
- 耗时陡增用斜率判断,不用固定数字:像素量翻倍时耗时大致同阶增长属正常;如果耗时增长明显超过像素增长,说明已经进入切块、重算或显存换出的低效区,这一档不宜作为日常出图档位。
- batch 往上抬时,如果单张耗时不再下降甚至上升,说明吞吐已经到瓶颈,继续加 batch 只是排队。
把实测上限换算成能不能用的判断
跑得动和跑得好是两件事,区间要用三个维度去卡:单张耗时 t、你能接受的等待时间 T、以及同时或连续出图的数量 n。
- 单张耗时:取热态下的实测值,不要用冷启动数字,也不要用「跑通过的某一档」代替最终要用的分辨率。
- 可接受等待:盯着屏幕调提示词,单张等待在秒级才有意义;批量出素材、跑完就去做别的事,数十秒到分钟级也能接受;只偶尔出一张封面图,分钟级未必是问题。
- 同时出图数量:n 张的总时间约等于 n 乘 t,同时 n 受显存约束——batch 要占激活,多进程并发同理,显存上限会比单张时更早撞墙。
换算方法很直接:t 小于等于 T,才叫这台机器能用于该场景;t 明显大于 T 但任务可以无人值守,就归为「能批量、不能交互」;t 接近 T 同时再降一档分辨率或减步数就明显快很多,优先降档,而不是硬扛最高分辨率。若为了不爆显存上了 CPU offload 或切块,通常只解决跑不动的问题,单张耗时一般会变长,不要把这部分当提速手段;量化降显存同理,需要自己比对出图质量。
最后提醒一点边界:上面的数字都只对当前这张卡、这套驱动、这个框架和量化版本成立,换任意一项,reserved 峰值和单张耗时都要重新量一遍。