打开 LoRA 文件看一眼: 前 8 字节写头部长度(168 字节),然后是 JSON(每个张量的起止位置),再后面就是裸二进制。 两个张量、每个 16384 字节,读回来 2/2 一致,整个文件 0.03 MB。 这就是「部署」的全部内容:把文件放到对的目录,再核对三样元信息。
上一节把配置写对了,训练能跑起来。 这一节处理训完的产物——它到底是个什么文件,为什么不是 ckpt,放哪里,怎么验。
一、训完的产物是什么
训练结束,output 目录里会出现若干 *.safetensors。
这就是 LoRA 的全部——一个几 MB 到几百 MB 的文件,里面只有那些低秩因子的权重。
通常还会配一份说明(有的训练器自动生成 json,有的要自己写):
底模版本、触发词、推荐的 LoRA 权重、训练用的参数。这份说明比权重本身更容易被忽略,
也更容易在半年后救你的命。
部署就是把文件放进推理程序认识的目录:SD WebUI 是 models/Lora/,ComfyUI 是 models/loras/,
Forge 与 WebUI 相同。放进去刷新一下,界面里就能选到。
📋 实操清单(在推理程序里做):
第一步,把 xxx.safetensors 复制进对应目录,刷新界面。
第二步,在文生图页的提示词框里写 <lora:xxx:0.8>(WebUI 语法)。
第三步,看提示词框里那一小段有没有变成高亮色。变了说明识别成功。
没高亮就是路径或文件名不对,这时候去查目录,别怀疑模型。
二、safetensors 里到底装了什么
格式简单到可以手写:8 字节(小端无符号整数)写头部多长,紧接着这段长度的 JSON,
再往后就是所有张量首尾相接的裸二进制。
JSON 里每个张量三项:dtype、shape、data_offsets。
data_offsets 是一对数字——这个张量在数据区从第几字节到第几字节。读取时按这对偏移切一刀,
直接按 dtype 解出来就行。
Demo 手写了一个只含两个张量的文件,读回来逐元素比对:
| 张量 | 形状 | 字节数 | 读回来 |
|---|---|---|---|
| lora_unet_a | 8×512 | 16384 | 一致 |
| lora_unet_b | 512×8 | 16384 | 一致 |
| 两行都是「一致」,说明格式理解和写入都是对的。 | |||
| 真实 LoRA 只是张量多几十倍、名字带层号,机制一模一样。 | |||
头部里还常有一段 __metadata__,装的是 ss_network_dim、ss_base_model_version、 |
|||
ss_output_name 这些训练信息。这段不进权重,但它是「这个 LoRA 是用什么训的」的唯一记录。 |
三、为什么不是 ckpt
老格式 .ckpt 是 Python 的 pickle。pickle 在反序列化时会执行代码——这是设计如此,不是漏洞。
所以一个恶意 ckpt 可以在你「加载模型」的瞬间跑任意代码。
safetensors 反过来:它只有数字和偏移量,没有任何可执行内容。加载它等于按偏移量读一段字节,
没有「执行」这一步。
这就是为什么几乎所有训练器和模型站都默认 safetensors。
但它只解决「不执行代码」,不解决「权重本身有问题」——被投毒的权重照样能训出乱七八糟的图,
它只是不会偷你的文件。
所以两条规矩:
① 优先选 safetensors;② 来源不明的权重,先在隔离环境里试跑。 第二条比第一条更重要。
四、部署之后先核对三样
① 底模版本。 SD1.5 的 LoRA 放到 SDXL 上是出不来图的,症状是「全是噪点」或「完全不像」。
元信息里 ss_base_model_version 就是干这个用的。
② dim 与 alpha。 这两个数决定改动量的缩放。
如果下载的 LoRA 说明里写 dim=128,而你按 dim=8 的直觉调权重,出来的效果会差很多。
③ 触发词。 说明里应该有,而且要在提示词里出现。没有触发词的 LoRA,等于一个没有名字的开关。
再加一条工程习惯:
发布和接收都做一次哈希校验。Demo 里那个 SHA256 前 16 位就是给人核对用的——
对不上说明文件传坏了或被人动过,别加载。
命名也值得较真:风格名_v1_r16_20261025.safetensors。
三个月后你有五个版本时,文件名是唯一的线索。
五、动手 Demo:LoRA 权重文件:头部 8 字节定长度,读完逐元素比对
不依赖任何训练框架,用 struct 和 json 手写一个 safetensors 文件:两个张量(8×512 与 512×8),
头部写清各自的 dtype、shape、起止偏移。然后用同样的规则读回来,逐元素比对,并算文件的 SHA256。
meta[name] = {u"dtype": u"F32", u"shape": list(arr.shape),
u"data_offsets": [off, off + arr.nbytes]}
hb = json.dumps(meta, sort_keys=True).encode()
hb += b" " * ((8 - len(hb) % 8) % 8) # 头部补齐到 8 的倍数
with open(p, "wb") as f:
f.write(struct.pack("<Q", len(hb))) # 前 8 字节:头部多长
f.write(hb) # 再一段 JSON
f.write(A.tobytes()); f.write(B.tobytes()) # 再裸二进制
n_head = struct.unpack("<Q", raw[:8])[0]
head = json.loads(raw[8:8 + n_head])
body = raw[8 + n_head:]
实跑输出:
--------------------------------------------------------------------
LoRA 权重文件:头部 8 字节定长度,读完逐元素比对
--------------------------------------------------------------------
文件大小 0.03 MB
头部长度 168 字节
张量个数 2 个
SHA256 前 16 位 4d4e47527cedd070
张量 形状 字节数 读回来
------------------------------------
lora_unet_a 8×512 16384 一致
lora_unet_b 512×8 16384 一致
读回来 **2/2 一致**。这就是 safetensors 的全部秘密:
头部 **8 字节**写长度,后面一段 **JSON** 写每个张量的起止位置,
再后面就是裸的二进制。
相比 pickle 格式(ckpt),它**不会在加载时执行任意代码**——这是它被几乎所有训练器默认采用的原因。
那个 SHA256 是发布时要写进说明里的东西。**用户下载完先校验,对不上就说明文件传坏了或被人动过。**
体积账:这个 512×512 的层,LoRA 两个矩阵一共 0.03 MB;换成整层全量微调要 1.00 MB。
**所以一个画风 LoRA 常见 10 到 100 MB,而底模是 2 到 7 GB。**分发和版本管理都不是一个量级的事。
三行 kv 就是格式的全部:头部 168 字节、2 个张量、文件 0.03 MB。头部长度必须记在文件里,
否则读取时根本不知道 JSON 到哪结束。
struct.pack("<Q", len(hb)) 里的 <Q 是「小端、8 字节无符号整数」。
这 8 个字节就是整个格式的入口——所有 safetensors 读取器第一件事都是读它。
头部要补空格到 8 的倍数。
这不是为了好看:补到 8 的倍数后,数据区的起点也是 8 字节对齐的,np.frombuffer 直接就能用,
不用再拷贝。
读回来 2/2 一致,说明「写进去的字节」和「按偏移切出来的字节」完全对得上。
真实 LoRA 也只是几百个这样的张量拼在一起。
体积那句对比值得记住:
两个 512×512 的因子 0.03 MB,整层全量微调要 1.00 MB——差 32 倍。
这就是为什么网上的 LoRA 动辄几十 MB 而底模动辄几个 GB。
30 秒实验
- 把 dim 从 512 改成 768,重跑,看文件大小怎么变。 线性涨到 0.05 MB 左右——参数量与维度成正比,不是平方。
- 把「补齐到 8 的倍数」那行注释掉,看读回来的结果。
数据区不再对齐,
np.frombuffer会报错或读出乱码——对齐是硬要求。 - 改动 A 矩阵里的一个字节,重算 SHA256。 前 16 位会完全不同——这就是哈希能发现「文件被改过」的原因。
常见误区
- 「ckpt 和 safetensors 一样,只是后缀不同」。前者是 pickle,加载会执行代码;后者只有字节和偏移。
- 「文件小就不用校验」。恰恰相反——小文件更容易被悄悄替换,哈希是唯一的凭据。
- 「LoRA 放哪个底模上都行」。SD1.5 的 LoRA 放到 SDXL 上出不来图,症状是噪点,不是报错。
自测三问(点开对答案)
1. safetensors 的三段结构是什么? —— 8 字节头部长度 + 等长的 JSON(每个张量的 dtype、shape、起止偏移)+ 首尾相接的裸二进制。 2. 为什么不用 ckpt? —— ckpt 是 pickle,反序列化时会执行代码,恶意文件可以在加载时跑任意代码。 safetensors 只有数字和偏移,没有可执行内容。 3. 部署之后要核对哪三样? —— 底模版本、dim 与 alpha、触发词。 底模不对会出噪点,dim 不对权重手感全变,没有触发词等于没有开关。文件放好了、元信息也核过了,最后一节是真正出图——那几个参数里,只有两个是必须自己定的。
- ☐ (本节完,下一节 7.2.2 使用LoRA模型实现文生图)