news 2026/9/29 6:28:38

sd-scripts 中 OFTv2 与 BOFT 正交微调适配器训练完全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sd-scripts 中 OFTv2 与 BOFT 正交微调适配器训练完全指南
  • 深度学习
  • 计算机视觉
  • 媒体生成
  • 模型训练
  • 微调

【免费下载链接】sd-scripts

项目地址:https://gitcode.com/gh_mirrors/sd/sd-scripts
点击查看免费下载

本文基于 sd-scripts 仓库中的 train_network_oft_boft.md,系统讲解如何在train_network.py(SD1.x / SD2.x)与sdxl_train_network.py(SDXL)中训练 OFTv2 与 BOFT 两类正交微调适配器。与 LoRA 通过低秩矩阵叠加增量不同,正交适配器通过在目标层输入侧乘以学习到的正交(旋转)矩阵来微调模型,具有保留预训练权重范数与夹角结构的独特特性。读完本文你将掌握两种模块的数学原理、全部命令行参数、完整训练命令、权重格式与 PEFT/OMI 兼容加载方式,以及合并与性能优化要点。

1. 概述:正交微调与传统 LoRA 的本质区别

OFTv2(Orthogonal Fine-Tuning v2)与 BOFT(Butterfly Orthogonal Fine-Tuning)都属于正交变换类适配器。它们与 LoRA 的核心差异在于:

  • LoRA:在权重矩阵上叠加低秩增量(W' = W + BA),改变权重本身;
  • OFTv2 / BOFT:不修改权重矩阵本身,而是在目标层输入侧乘以一个学习到的正交(旋转)矩阵(x' = R·x),预训练权重被整体旋转。

由于旋转保持范数与向量间夹角,预训练权重向量的范数(norm)与两两夹角(pairwise angles)均被保留,这被认为能更好地维持预训练模型的特征几何结构。

模块方法每层可训练参数
networks.oft_v2OFTv2(块对角正交变换,Cayley 参数化)oft_R.weight
networks.boftBOFT(蝴蝶分解正交变换 + 每输出通道缩放)boft_R、boft_s

适用模型范围:SD1.x、SD2.x、SDXL(U-Net 与 Text Encoder)。不支持FLUX.1、SD3、Lumina、HunyuanImage 与 Anima。

目标层范围与networks.lora一致:U-Net 中Transformer2DModel块内的全部 Linear 层与 1x1 Conv2d 层,以及 Text Encoder 的 attention / MLP 层;通过enable_conv=true可额外纳入 3x3 Conv2d 层。

说明:这两个模块由 umisetokikaze 在 PR #2357 中贡献,实现遵循 PEFT 的 OFT / BOFT 层规范,因此可以加载 PEFT 格式的权重。传统networks.oft模块(输出侧旋转、不同权重格式)保持原样不变,仍可继续使用。

1.1 源码实现路径

两个模块分别位于 networks/oft_v2.py 与 networks/boft.py,二者共享 networks/orthogonal_common.py 中的公共工具。从源码可以确认的模块结构:

  • 目标替换模块常量(orthogonal_common.py):TEXT_ENCODER_TARGET_REPLACE_MODULE = ["CLIPAttention", "CLIPSdpaAttention", "CLIPMLP"],UNET_TARGET_REPLACE_MODULE = ["Transformer2DModel"],UNET_TARGET_REPLACE_MODULE_CONV2D_3X3 = ["ResnetBlock2D", "Downsample2D", "Upsample2D"];
  • 层筛选:遍历named_modules(),只接受类名恰好为Linear或Conv2d的子模块;Conv2d 默认仅限 1x1(is_conv2d_1x1),3x3 需enable_conv=true(orthogonal_common.py);
  • 限制条件(orthogonal_common.py):正交适配器不支持分组卷积(groups != 1)与 dilation > 1 的 Conv2d 层,遇到会直接抛出ValueError。

2. 命令行参数:模块选择、block size 语义与 dropout

2.1 模块选择与全局参数

  • --network_module:指定训练的网络模块,OFTv2 用networks.oft_v2,BOFT 用networks.boft;
  • --network_alpha:被两个模块完全忽略(源码create_network中直接del network_alpha);
  • --network_dim:语义与 LoRA 及旧版networks.oft均不同。

关键语义提醒:对networks.oft_v2与networks.boft,--network_dim被解释为块大小(block size),即单个正交块的尺寸,而不是块数量(旧版networks.oft中它表示块数)。省略--network_dim时,OFTv2 的默认块大小为32,BOFT 的默认块大小为4。

--network_dropout会被映射为旋转块的乘性 dropout(multiplicative dropout):训练过程中按概率将随机选中的旋转块替换为单位矩阵。

从源码实现看,这一映射发生在两个层面(train_network.py):训练脚本通过neuron_dropout=args.network_dropout与net_kwargs["dropout"]传入,而_create_network_args中 dropout 的取值优先级为「显式--network_dropout>--network_args中的dropout/dropout_probability(OFTv2)或boft_dropout/dropout(BOFT)」。

2.2 OFTv2 专属参数(--network_args)

参数默认值说明
block_size(或oft_block_size)--network_dim(32)每个正交块的大小;优先于--network_dim
coft(或oft_coft)false启用约束 OFT(COFT),每步把旋转投影到 ε 球内
coft_eps6e-5COFT 的约束半径
block_share(或oft_block_share)false层内所有块共享同一个旋转块(参数更少)
dropout(或dropout_probability)0.0块 dropout 概率;若显式给出--network_dropout则以其为准
enable_convfalse同时作用于 3x3 Conv2d 层(ResNet 块、上/下采样器)
auto_adjusttrue当in_features不能被块大小整除时,自动挑选最近的约数;false则直接报错
include_patterns/exclude_patterns无Python 正则列表,对模块名做完整匹配,例如"exclude_patterns=['.*attn2.*']"

2.3 BOFT 专属参数(--network_args)

参数默认值说明
block_size(或boft_block_size)--network_dim(4)每个正交块的大小;与block_num二选一
block_num(或boft_block_num)无每层块数量;块大小由in_features推导
boft_n_butterfly_factor1蝴蝶因子数量。1为带缩放因子的普通块对角 OFT;大于 1 时引入蝴蝶置换。大于 1 要求块大小与块数量均为偶数,且in_features能被block_size * 2^(factor-1)整除
dropout(或boft_dropout)0.0块 dropout 概率;--network_dropout优先
enable_convfalse同时作用于 3x3 Conv2d 层
auto_adjusttrue请求形状不合法时自动选择有效块形状
include_patterns/exclude_patterns无与 OFTv2 相同

2.4 参数解析与形状校验的源码佐证

  • OFTv2 参数解析(oft_v2.py):_create_network_args依次解析block_size(取oft_block_size或block_size,缺省回落network_dim,再缺省 32)、coft、coft_eps、block_share、dropout、enable_conv、auto_adjust、include_patterns/exclude_patterns;
  • BOFT 参数解析(boft.py):支持boft_block_size/boft_block_num互斥指定,block_size缺省为network_dim,再缺省为 4;
  • 形状校验(boft.py):_valid_boft_shape检查block_size * block_num == in_features,当因子数 > 1 时还要求block_num % 2^(factor-1) == 0、block_size与block_num均为偶数、in_features % (block_size * 2^(factor-1)) == 0。若同时给定block_size与block_num会直接抛出ValueError;
  • auto_adjust 实现(orthogonal_common.py):adjust_block_size在给定块大小不能整除in_features时,向上/向下寻找最近的约数;OFTv2 侧若auto_adjust=false且不整除则抛错(oft_v2.py)。

3. 完整训练命令示例

3.1 SDXL + OFTv2(仅 U-Net)

accelerate launch --num_cpu_threads_per_process 1 sdxl_train_network.py \ --pretrained_model_name_or_path="/path/to/sdxl_model.safetensors" \ --dataset_config="/path/to/config.toml" \ --output_dir="./output" --output_name="sdxl_oftv2" --save_model_as=safetensors \ --network_module=networks.oft_v2 --network_dim=32 \ --network_args "coft=false" "block_share=false" \ --network_train_unet_only \ --learning_rate=1e-4 --max_train_steps=1000 --train_batch_size=1 \ --mixed_precision=fp16 --sdpa --gradient_checkpointing --cache_latents

要点说明:

  • --network_dim=32作为 OFTv2 的块大小;
  • --network_train_unet_only仅训练 U-Net 部分(对应脚本中的--network_train_unet_only参数,SD1.x/SD2.x 使用train_network.py时还可结合--network_train_text_encoder_only等);
  • --cache_latents缓存潜在表示以节省显存;--gradient_checkpointing会同时在反向传播阶段重算旋转矩阵(见第 6 节注意事项)。

3.2 BOFT 双蝴蝶因子

--network_module=networks.boft --network_dim=8 \ --network_args "boft_n_butterfly_factor=2"

即块大小 8、蝴蝶因子为 2(2^(2-1) = 2,要求in_features能被 16 整除且块数量为偶数,auto_adjust默认开启可自动适配)。

3.3 其余通用选项

--unet_lr、--text_encoder_lr、--network_weights(加载已有权重继续训练)、采样生成等选项与 LoRA 训练完全一致,详见 train_network.md 与 sdxl_train_network.md。

4. 工作原理:Cayley 参数化、COFT 与蝴蝶置换

4.1 OFTv2:块对角旋转与 Cayley 参数化

OFTv2 将每层的输入特征in_features划分为rank = in_features // block_size个块,每个块使用一个block_size × block_size的正交矩阵。可训练参数oft_R.weight的形状为(rank, n_elements),其中n_elements = block_size * (block_size - 1) / 2,即反对称矩阵的上三角独立元素数——正交矩阵的 Cayley 参数化只需要这么多自由度。

核心实现(oft_v2.py):

  • _pytorch_skew_symmetric:把参数向量组装成反对称矩阵(matrix - matrix.transpose(-2, -1));
  • _cayley_batch:通过Cayley 变换R = (I + Q)^{-1}(I - Q)从反对称矩阵Q生成正交矩阵。默认启用 Neumann 级数近似(use_cayley_neumann=true,默认展开 5 项),可在精度与速度间取舍;关闭时使用精确的torch.linalg.solve;
  • forward:将输入xreshape 为(..., rank, block_size),用torch.einsum("...rk,rkc->...rc", ...)逐块旋转后还原形状;
  • block_share=true时,仅保留 1 个旋转块,按rank次数repeat复用(oft_v2.py),参数量从rank × n_elements降为1 × n_elements。

4.2 COFT:ε 球约束投影

coft=true时,每步前向先将旋转参数投影到以单位矩阵为中心的ε 球内(oft_v2.py):计算参数矩阵到原点的 Frobenius 范数,若超出coft_eps则缩放回边界。这一约束能限制旋转偏离单位阵的程度,防止微调破坏原始权重结构。测试用例 test_orthogonal_networks.py 验证了 COFT 投影后梯度依然正常传播、基础权重保持不变。

4.3 BOFT:蝴蝶分解与输出缩放

BOFT 的可训练参数为boft_R(形状(n_factors, block_num, block_size, block_size))与boft_s(形状(out_features, 1),每输出通道缩放)。其特点(boft.py):

  • 蝴蝶置换:boft_n_butterfly_factor > 1时,通过block_butterfly_perm生成置换矩阵boft_P(非持久 buffer),对块做奇偶交错重排,使不同块之间产生信息交互,突破纯块对角的表达限制;
  • Cayley 变换:cayley_batch对每个block_size × block_size块独立做 Cayley 变换生成正交块;
  • 旋转-缩放:前向时逐因子做P @ blockdiag(R) @ P^T并连乘,再右乘输入权重,最后按输出通道乘以boft_s缩放;
  • dropout 差异:BOFT 的乘性 dropout 在随机选中的单个因子内随机替换p × block_num个块为单位矩阵(boft.py),与 OFTv2 按块整体掩码的实现略有不同。

5. 权重格式与兼容性

5.1 保存格式

权重使用与 LoRA 相同的前缀保存:lora_unet_...、lora_te_...、lora_te1_...、lora_te2_...,后接参数名,例如:

lora_unet_down_blocks_1_attentions_0_transformer_blocks_0_attn1_to_q.oft_R.weight

前缀规则定义在 orthogonal_common.py,native 键名由native_prefix(orthogonal_common.py)把模块名中的.替换为_生成。BOFT 对应...boft_R、...boft_s。

5.2 加载时的多格式识别

使用--network_weights、gen_img.py或合并流程加载时,除 native 格式外还识别以下键格式(orthogonal_common.py):

  • OMI 风格前缀:unet./clip_l./clip_g.加原始点分模块名(omi_prefix);
  • PEFT 适配器格式:base_model.model.<模块名>.oft_R.weight/.boft_R/.boft_s。可以直接传入包含adapter_model.safetensors的 PEFT 适配器目录——load_weights_sd会自动识别目录并从中读取适配器文件(orthogonal_common.py)。

匹配不到的键会发出警告并忽略。测试用例 test_orthogonal_networks.py 验证了 PEFT 风格权重可直接构建网络与训练加载;SDXL 场景下 PEFT 键不区分两个 Text Encoder,加载时需自行注意映射关系(native 格式的lora_te1_/lora_te2_前缀则能精确区分)。

兼容性细节:BOFT 在加载时会对boft_s的形状做自适应转置(_adapt_state_for_local_shape,boft.py),兼容 PEFT 中(out_features, 1)与本地布局的差异;auto_adjust选出的实际块形状在加载时通过_infer_from_state从权重张量形状反推(OFTv2 用三角元素数反推块大小,BOFT 读取boft_R形状),因此不影响自己训练产出的 checkpoint 兼容性。

6. 注意事项与性能优化

  • 速度:旋转矩阵每步前向都要从参数出发通过大量小矩阵运算重建(Cayley 变换 + einsum),因此每步开销高于 LoRA,小 batch 下 GPU 利用率可能偏低。加大 batch size可摊薄该固定开销;--gradient_checkpointing会在反向阶段同样重算旋转,进一步增加计算量;
  • 合并:两个模块均支持merge_to(网络接口is_mergeable()返回True),可在gen_img.py等场景把适配器合并进基础权重,合并结果是原权重被精确旋转后的版本。训练脚本中也可通过--base_weights预合并加载(train_network.py);
  • auto_adjust=true(默认):当请求的块大小不整除in_features时,某些层可能被静默选择不同的块大小。由于加载时从保存权重推断实际形状,这不会影响与你自己 checkpoint 的兼容性;若希望严格报错而非静默调整,请设auto_adjust=false;
  • 前向一致性测试:仓库测试 test_orthogonal_networks.py 对 Linear、1x1 Conv2d、3x3 Conv2d 三种层验证了「hook 前向输出」与「merge 后权重」数值一致(atol/rtol=1e-5),可作为自行验证合并正确性的参考方法。

7. 快速决策:OFTv2 还是 BOFT?

维度OFTv2BOFT
参数语义纯块对角旋转(Cayley)块对角旋转 + 蝴蝶置换 + 输出缩放
默认块大小324
每层参数oft_R.weightboft_R+boft_s
约束变体COFT(ε 球投影)无(依赖缩放因子)
表达力扩展block_share减参boft_n_butterfly_factor增参增表达
适用对象SD1.x / SD2.x / SDXL同左

两者共享同一套目标层筛选、权重前缀与多格式加载逻辑,切换只需更换--network_module并按上表调整--network_dim(块大小)与--network_args。开始训练前,建议先在小步数、小 batch 上验证训练曲线,再根据显存与速度表现决定 batch size 与是否启用--gradient_checkpointing。

  • 深度学习
  • 计算机视觉
  • 媒体生成
  • 模型训练
  • 微调

【免费下载链接】sd-scripts

项目地址:https://gitcode.com/gh_mirrors/sd/sd-scripts
点击查看免费下载

相关推荐

上一篇:AWS CLI 实战:使用 `aws apigateway get-model-template` 获取 API Gateway 模型映射模板
下一篇:终极指南:如何用pdf-lib在浏览器中实现IndexedDB缓存优化PDF性能

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

机器人运动学工程实践:从D-H参数实测到实时IK落地

1. 这不是教科书笔记&#xff0c;是林沛群在实验室白板上擦了七遍才定稿的运动学手稿你手上如果有一本《机器人学导论》或者翻过Craig那本经典教材&#xff0c;大概率会发现&#xff1a;D-H参数表列得工整漂亮&#xff0c;正向运动学推导像解一道线性代数题&#xff0c;逆解公式…

作者头像 李华
网站建设 2026/9/29 6:26:14

NFC 贴卡打开 App:Android AAR 与 iOS 通用链接实战

手上做过好几个带 NFC 交互的线下项目&#xff0c;从门店会员卡到展台打卡&#xff0c;客户的需求描述几乎一模一样&#xff1a;手机贴一下&#xff0c;装了 App 就直接打开&#xff0c;没装就跳到应用市场去下载。这句话说出口只要三秒&#xff0c;但真正落到 Android 和 iOS …

作者头像 李华