原文链接:https://www.reddit.com/r/StableDiffusion/comments/1dbasvx/the_gory_details_of_finetuning_sdxl_for_30m/
翻译工具:OpenAI o1-preview
关于如何训练 SDXL 的 lora,有很多细节,但对于大型 SDXL 微调是如何训练的,详细信息少之又少。我最近发布了一个大型 SDXL 微调模型,使用了 150 万张图像、3000 万个训练样本,在一台配备 8 张 H100 显卡的机器上花了 5 天时间。因此,我在这里分享所有的训练细节,以帮助社区。
bigASP 是在大约 1,440,000 张照片上训练的,所有照片的分辨率都大于各自的长宽比桶的尺寸。每张图像在磁盘上约为 1MB,使数据集达到每百万张图像约 1TB。
每张图像都经过以下处理:使用质量模型对其进行评分,范围从 0 到 9;使用 JoyTag 进行标注;使用提示词 “a watermark” 的 OWLv2 来检测图像中的水印。我发现 OWLv2 的性能甚至比微调过的视觉模型更好,并且它还具有为水印提供边界框的额外好处。准确率约为 92%。虽然这次版本没有这样做,但未来有可能使用这些边界框在训练期间进行“损失屏蔽(loss masking)”,这基本上是将水印从 SD 中隐藏起来。现在,如果检测到水印,训练提示中会包含一个 “watermark” 标签。
得分为 0 的图像将被完全丢弃。我专门针对评分模型进行了大量工作,以将某些图像放入这个评分档中。你会对数据集中出现的垃圾数量感到惊讶,甚至它们的一丝存在都会真正扰乱训练。缩略图、视频预览图像、广告等。
bigASP 使用了与 SDXL 论文中定义的相同的长宽比桶。所有图像都被分配到它们最适合的桶中,同时在缩小尺寸时不小于该桶的任何维度。因此,在缩放后,图像会被随机裁剪。原始分辨率和裁剪数据与 VAE 编码的图像一同记录在磁盘上,用于 SDXL 的条件输入,最后将潜变量进行 gzip 压缩。我发现 gzip 可以节省大约 30% 的空间。这将训练数据集减少到每百万张图像约 100GB。
训练使用的是基于 diffusers 库的自定义训练脚本。我使用自定义训练脚本是为了能够完全理解所有的内部机制,并实现我想要的任何调整。而且我有从 SD1.5 训练开始就使用的训练脚本,所以这不是一个巨大的飞跃。缺点是不得不花费大量时间调试在数次有问题的运行后出现的细微问题。这些都是代价高昂的错误。但对我来说,错误是学习的代价。
我认为训练提示对于最终模型在实际使用中的性能非常重要。自定义的 Dataset 类在生成训练提示时承担了大量工作。人们的提示从简短到冗长,包含各种逗号、下划线、拼写错误等。
我提取了一个包含提示的大量 AI 图像样本,以分析典型用户提示的统计数据。提示长度的分布大致呈正态分布,平均为 32 个标签,标准差为 19.8。因此,我的 Dataset 类反映了这一点。对于每个训练样本,它在这个分布中随机选择一个整数,以确定该训练样本应使用多少个标签。它将图像的标签随机打乱,然后截断到该数量。
这意味着在训练期间,模型看到的内容从仅仅是 “1girl” 到长达 224 个标记的巨大提示。因此,希望它能够为用户填充细节。
某些标签,如 “watermark”,被赋予优先级,如果存在,则始终包含在内,因此模型会强烈地学习这些标签。这也有一个副作用,在推理期间将模型条件化,使其除非被要求,否则不生成水印。
使用 Danbooru 的标签别名列表来随机将标签变为同义词,以便 bigASP 理解人们可能用于表示概念的各种不同方式。希望如此。
当然,还有得分标签。就像 Pony XL 一样,bigASP 将训练样本的得分编码为形式为 “score_X” 和 “score_X_up” 的一系列标签。然而,为了避免 Pony XL 遭遇的问题(巨人之肩),只在训练提示中包含随机数量的得分标签。它包含适用于图像的 1 到 3 个随机选择的得分标签。这样,模型在提示中不需要 “score_8, score_7, score_6, score_5...” 才能正常工作。它已经习惯于只存在一个或几个得分标签。
在 10% 的情况下,提示会被完全丢弃,设置为空字符串。UCG(Unconditional Guidance,无条件引导),你懂的。
注意:我注意到在 Stability 的训练脚本,甚至 HuggingFace 的脚本中,他们不是将提示设置为空字符串,而是将其在嵌入空间中设置为 “零”。这与 SD1.5 的训练方式不同,也与大多数 SD 前端在 SD 上进行推理的方式不同。我的理论是,如果 SDXL 使用 “零” 丢弃而不是空提示丢弃进行训练,实际上可能是一个大问题。这意味着在推理期间,如果你使用空提示,你不是在告诉模型远离 “平均图像”,而是远离仅在训练期间恰好没有标题的图像。听起来不对。因此,对于 bigASP,我选择使用空提示丢弃进行训练。
此外,Stability 的训练脚本包括丢弃 SDXL 的其他条件:original_size、crop 和 target_size。我没有在 Kohya 的脚本中看到这种行为,所以我没有使用它。我不太确定它能提供什么好处。
我确保在训练期间,模型获得各种批次的提示长度。我的意思是,每个训练样本的提示本身肯定是不同长度的,但在一个批次中,它们都必须填充到最长的示例长度。所以,重要的是要确保即使在批处理之后,模型仍然看到各种长度,否则它可能过度拟合到特定范围的提示长度。一个快速的 Python Notebook 用于扫描训练批次,帮助验证了良好的分布:25% 的批次为 225 个标记,66% 为 150 个,9% 为 75 个标记。尽管在未来的运行中,我可能会尝试更好地平衡这一点。
训练过程的其余部分相当标准。我在实验中发现 min-snr 损失效果最好。纯 fp16 训练对我不起作用,所以我不得不求助于混合精度,模型使用 fp32。由于潜变量已经编码,不需要加载 VAE,节省了宝贵的内存。为了在训练期间生成样本图像,我使用一台单独的机器,它抓取保存的检查点并生成样本图像。同样,这节省了训练机器的内存和计算。
最终的运行使用了有效批大小为 2048,没有 EMA,没有偏移噪声,使用 PyTorch 的 AMP,仅使用 float16(不是 bfloat16),1e-4 的学习率,AdamW,min-snr 损失,0.1 的权重衰减,余弦退火,线性预热 100,000 个训练样本,10% 的 UCG 率,启用了文本编码器 1 的训练,文本编码器 2 保持冻结,min_snr_gamma=5,PyTorch 的 GradScaler 初始缩放为 65k,beta1=0.9,beta2=0.999,eps=1e-8。一切都从 SDXL 1.0 初始化。
使用了一个包含 2048 张图像的验证数据集。每 50,000 个样本进行一次验证,以确保模型没有过度拟合并帮助指导超参数选择。为了帮助比较使用不同损失函数的运行,验证始终使用基本的损失函数,即使训练中使用了例如 min-snr。而且每 500,000 个样本保存一个检查点。我发现仅在每百万步查看一次示例图像才真正有帮助,因此该过程在每隔一个检查点上运行。
还记录了一个稳定的训练损失(我使用 Wandb 监控我的运行)。稳定的训练损失是在与验证损失相同的时间计算的(一个接一个)。它基本上就像一个验证过程,除了使用训练数据集的前 2048 张图像,并使用固定的种子。这提供了一个,嗯,稳定的训练损失。SD 的训练损失非常嘈杂,因此该指标提供了一个更好的衡量训练损失进展的指标。
我使用的批大小与我在线上看到的用于微调运行的少数值相比相当大。但这是由我训练其他模型的经验所决定的。大型批大小在长期中胜出,但在短期中效果更差,因此其效果在小规模基准测试中可能难以衡量。希望在这里是一个胜利。SDXL 的完整运行对于在这里进行大量实验来说过于昂贵。但大型批大小的一个直接好处是迭代速度更快,因为优化和梯度同步发生得更少。
训练是在云中租用的一台 8xH100 SXM5 机器上进行的。在这台机器上,迭代速度约为 70 张图像/秒。这意味着整个运行花费了大约 5 天的计算时间。对于像我这样的业余爱好者来说,这是一个惊人的数字。请给我拥抱。我很痛苦。
在云中进行训练是使用预计算潜变量的一个重要动机。我需要大约一个小时将数据传输到机器上开始训练。理论上,代码可以设置为在第一次传输训练数据时立即开始训练。即使是 8xH100 也需要四个小时才能处理完一百万张图像,因此数据可以比训练更快地被传输。这样,机器就不会闲置烧钱。
预计算潜变量的一个缺点当然是缺乏各个 epoch 之间潜变量变化所带来的正则化。模型在各个 epoch 之间仍然会看到大量不同的提示,但它不会看到图像的不同裁剪或 VAE 采样的变化。在未来的运行中,我可能会让我的本地 GPU 不断地重新编码潜变量,并将这些更新的潜变量流式传输到云机器。这样,每隔几个 epoch,潜变量都会变化。在这次运行中我没有检测到任何过拟合,因此这可能不是一个大问题。
最后,来看一下损失曲线。我注意到在不同的数据集之间,验证损失有相当大的差异,因此对于他人来说可能很难进行比较,但就其价值而言:
如前所述,我在这个发布之前有许多失败的运行,主要是由于训练脚本中的错误,例如将 original_size 等条件的高度和宽度交换了。不幸的是,这样的小细节没有得到很好的文档记录。还有一些运行是为了校准超参数:尝试不同的损失函数、优化器等。Animagine 的超参数是我能找到的记录最详细的,所以它们是我的起点。向那个团队致敬!
在这次运行中我没有发现任何过拟合,尽管它对数据进行了超过 20 个 epoch 的训练。也就是说,3000 万个训练样本,虽然对我来说已经很大了,但与 Pony XL 相比还是相形见绌,据我所知,后者用 600 万张图像进行了大致相同 epoch 数的训练!因此,至少是我在 bigASP 上投入的训练量的 6 倍。根据我目前对 bigASP 的测试,它已经很好地遵循了提示,并理解了我使用的大部分标签。但在整体图像结构上的不一致性,以及在训练数据中出现不到 1 万次的小众标签上遇到困难,这明显表现出训练不足。我肯定期望这些问题会随着更多训练而改善。
最初,在编码潜变量时,我进行了“混合 VAE”编码。基本上,我加载了几个不同的 VAE:SDXL 的 fp32、SDXL 的 fp16、SDXL 的 bf16,以及 fp16 修复的 VAE。然后每张图像用这个列表中的一个随机 VAE 进行编码。其目的是帮助 UNet 对最终用户可能使用的任何 VAE 版本具有鲁棒性。
在训练期间,我注意到模型生成了许多奇怪的高分辨率图案。很难说根本原因是什么。可能是训练数据中的摩尔纹,因为数据集的分辨率非常高。但我使用了 Lanczos 插值,因此应该将其最小化。也可能是潜变量中的不准确性,因此我在训练过程中部分切换到仅使用 SDXL fp32。很难说这是否有所帮助,或者是否有任何影响。此时,我怀疑 SDXL 的 VAE 并不适合这个任务,其中大多数训练图像包含极大量的细节。bigASP 在生成细致的、近距离的皮肤纹理方面非常出色,但像透明尼龙这样的高频图案会导致 VAE 出现问题。我猜想这里需要更多的研究。或者,天哪,更多的训练……
当然,描述性标题在未来会是一个不错的补充。这可能是我未来版本的下一个重大升级之一。JoyTag 在标注图像方面做得很好,所以我的目标是进行大量的人工标题编写,以训练一个新的 LLaVA 风格模型,其中图像嵌入来自 CLIP 和 JoyTag 的组合。这个组合应该有助于为 LLM 提供 CLIP 的广泛通用理解,以及 JoyTag 的详细、未经过滤的基于标签的知识。希望一切顺利。
最后,我想提到我使用的质量/美学评分模型。我从头开始训练了一个模型,通过在一对一的方式中手动对图像进行评级。然后我训练了一个模型,输入为两张图像的 CLIP-B 嵌入,并根据这个手动评级数据预测赢家。由此,我可以在更大的数据集上运行 ELO 来构建排名数据集,最终训练一个模型,该模型接受单个 CLIP-B 嵌入并输出跨越 10 个等级的对数预测。
鉴于我只对两千多张图像进行了评级,这个方法效果出人意料地好。对于我的任务来说,绝对比 Stability 使用的旧美学模型更好。模糊等图像趋向于较低的等级,而更高质量的摄影类照片趋向于顶部。
话虽如此,我认为这里可以做更多的工作。我想避免的一个大问题是让质量模型使 Unet 偏向于生成特定“风格”的图像,就像许多大型图像生成模型目前所做的那样。我们都知道 DALL-E 的风格。因此,一个好的质量模型的目标是确保它不是基于特定的外观/感觉/风格对图像进行排名,而是基于一个更少偏见的“质量”指标。显然,这是一
个困难而模糊的概念。为此,我认为我的质量模型可以通过更多具有非常不同内容和风格的图像进行比较的评级数据来受益。
我希望所有这些细节能够帮助其他可能走上这条痛苦道路的人。
博观换电柜厂家 2024-10-26
就考年级第一 2024-10-26
爱合发FA选型集采平台 2024-10-26
鼎芯实业 2024-10-26