Compel
一个用于 transformers 类型文本嵌入系统的文本提示权重和混合库,由 @damian0815 开发。
通过灵活直观的语法,您可以重新调整提示字符串不同部分的权重,从而重新调整从字符串生成的嵌入张量的不同部分权重。
经过 Hugging Face 的 StableDiffusionPipeline
测试和开发,但应该适用于任何使用某种 Tokenizer
和 Text Encoder
的基于 diffusers 的系统。
改编自 InvokeAI 的提示代码(同样由 @damian0815 开发)。
请注意,Compel 目前忽略了交叉注意力控制 .swap()
,但您可以通过自行调用 build_conditioning_tensor_for_prompt_object()
并在扩散循环中实现交叉注意力控制来使用它。
安装
pip install compel
文档
文档在这里。
演示
快速开始
使用 Hugging Face diffusers >=0.12:
from diffusers import StableDiffusionPipeline
from compel import Compel
pipeline = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
compel = Compel(tokenizer=pipeline.tokenizer, text_encoder=pipeline.text_encoder)
# 提高 "ball" 的权重
prompt = "a cat playing with a ball++ in the forest"
conditioning = compel.build_conditioning_tensor(prompt)
# 或者:conditioning = compel([prompt])
# 生成图像
images = pipeline(prompt_embeds=conditioning, num_inference_steps=20).images
images[0].save("image.jpg")
对于批处理输入,使用 compel 的 call 接口:
from diffusers import StableDiffusionPipeline
from compel import Compel
pipeline = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
compel = Compel(tokenizer=pipeline.tokenizer, text_encoder=pipeline.text_encoder)
prompts = ["a cat playing with a ball++ in the forest", "a dog playing with a ball in the forest"]
prompt_embeds = compel(prompts)
images = pipeline(prompt_embeds=prompt_embeds).images
images[0].save("image0.jpg")
images[1].save("image1.jpg")
文本反转支持
如果您想使用 🤗diffusers 文本反转功能,请实例化一个 DiffusersTextualInversionManager
并在 Compel 初始化时传递:
pipeline = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
textual_inversion_manager = DiffusersTextualInversionManager(pipeline)
compel = Compel(tokenizer=pipeline.tokenizer, text_encoder=pipeline.text_encoder,
textual_inversion_manager=textual_inversion_manager)
内存使用/VRAM 泄漏
如果遇到内存问题,请确保在 with torch.no_grad():
块内运行 compel。
如果这不能解决问题,您可以尝试 @kshieh1 提供的建议:
生成图像后,您应该显式取消引用张量对象(即 prompt_embeds = None)并调用 gc.collect()
更多详情请参见 https://github.com/damian0815/compel/issues/24。感谢 @kshieh1!
更新日志
2.0.3 - 包含贡献的修复 #64、#80,并修复 pyproject.toml/pypi 中的许可证
2.0.2 - 修复 SDXL 模型使用 pipeline.enable_sequential_cpu_offloading()
的问题(您需要在 compel 初始化时传递 device='cuda'
)
2.0.1 - 修复 #45 SDXL 非截断提示和 .and()
的填充问题
2.0.0 - SDXL 支持
非常感谢 Hugging Face 的 Patrick von Platen 提供的拉取请求,Compel 现在支持 SDXL。使用方法如下:
from compel import Compel, ReturnedEmbeddingsType
from diffusers import DiffusionPipeline
import torch
pipeline = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0", variant="fp16", use_safetensors=True, torch_dtype=torch.float16).to("cuda")
compel = Compel(tokenizer=[pipeline.tokenizer, pipeline.tokenizer_2] , text_encoder=[pipeline.text_encoder, pipeline.text_encoder_2], returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, requires_pooled=[False, True])
# 提高 "ball" 的权重
prompt = "a cat playing with a ball++ in the forest"
conditioning, pooled = compel(prompt)
# 生成图像
image = pipeline(prompt_embeds=conditioning, pooled_prompt_embeds=pooled, num_inference_steps=30).images[0]
请注意,如果您一直在使用 clip skip,这是一个重大变更:旧的布尔参数 use_penultimate_clip_layer
已被替换为枚举 ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED
。
1.2.1 - 实际应用 .and()
权重
1.2.0 - 使用 .and()
连接嵌入
对于 Stable Diffusion 2.1,我一直在尝试一个新功能:连接嵌入。我注意到,例如,对于更复杂的提示,当提示被分成多个部分并分别输入到 OpenCLIP 时,图像生成质量会大幅提高。
简而言之:您现在可以尝试将提示分成多个段落,这似乎可以改善 SD2.1 生成的图像。语法是 ("提示部分 1", "提示部分 2").and()
。您可以有多个部分,也可以为它们分配权重,例如 ("一个人吃苹果", "坐在车顶上", "高质量,艺术站热门,8K UHD").and(1, 0.5, 0.5)
,这将为 一个人吃苹果
分配权重 1
,为 坐在车顶上
和 高质量,艺术站热门,8K UHD
分配权重 0.5
。
这里有一个来自 InvokeAI discord #garbage-bin 频道的无意义示例,由 gogurt enjoyer 的令人难以置信的噩梦提示生成器创建:
一个潮湿黏腻的 pindlesackboy 邋遢的 hamblin' bogomadong,Clem Fandango 很生气,背景是 Wario's Woods,发出 ga-woink-a 的声音
直接将这个输入到 SD2.1 中,我们得到了这个真的不太好的图像:
然而,如果将提示分成几个部分,分别输入到 OpenCLIP 中作为四个独立的提示,然后连接起来:
一个潮湿黏腻的 pindlesackboy 邋遢的 hamblin' bogomadong
Clem Fandango 很生气
背景是 Wario's Woods
发出 ga-woink-a 的声音
然后使用相同的种子生成的输出图像好多了:
在新的 .and()
语法中,您可以这样提示:
("一个潮湿黏腻的 pindlesackboy 邋遢的 hamblin' bogomadong", "Clem Fandango 很生气", "背景是 Wario's Woods", "发出 ga-woink-a 的声音").and()
效果可能或多或少有所不同。例如,这里是
卡斯帕·大卫·弗里德里希绘制的遥远星系之梦,哑光绘画,艺术站热门,高质量
将其分为两部分:
卡斯帕·大卫·弗里德里希绘制的遥远星系之梦,哑光绘画
艺术站热门,高质量
这个 Compel 提示是:
("卡斯帕·大卫·弗里德里希绘制的遥远星系之梦,哑光绘画", "艺术站热门,高质量").and()
1.1.6 - 杂项小修复
- 添加
DiffusersTextualInversionManager
(感谢 @pdoane) - 修复截断/非截断提示长度的批量嵌入生成问题(#18,感谢 @abassino)
- 添加关于内存泄漏的说明(参考 #24,感谢 @kshieh1)
- 修复逗号后没有空格时的错误解析(#34,感谢 @moono)
1.1.5 - 修复compel将括号内文本中的数字转换为浮点数的问题
1.1.4 - 修复 #23(顺序卸载)和 InvokeAI 问题 #3442(允许LoRA名称中使用连字符)
1.1.3 - 启用获取倒数第二个CLIP隐藏层(又称"clip skip")
使用时,在初始化 Compel
实例时传递 use_penultimate_clip_layer=True
。注意,对于SD2.0/SD2.1,无需传递此标志,因为diffusers在加载SD2.0+文本编码器时已经舍弃了最后一个隐藏层。
1.1.2 - 修复 #21(启用截断时解析长提示词导致崩溃,如果截断边界之外存在加权片段)
1.1.1 - 修复 #22(括号内解析 .
字符的问题)
1.1.0 - 支持在 parse_prompt_string()
中解析 withLora
/useLora
Compel.parse_prompt_string()
现在返回一个Conjunction
- 提示词字符串中任何出现的
withLora(name[, weight])
或useLora(name[, weight])
将被解析为LoraWeight
实例,并在parse_prompt_string()
返回的最外层Conjunction
中返回。
1.0.5 - 修复传递无效(auto1111)语法中包含浮点数时的错误解析
同时修复默认交换参数的测试用例
1.0.4 - 修复禁用截断时空交换目标的嵌入(例如 cat.swap("")
)
1.0.3 - 改进 .swap 的默认值(https://github.com/damian0815/compel/issues/8)
1.0.2 - 修复非截断批量嵌入的填充(https://github.com/damian0815/compel/issues/9)
1.0.1 - 修复 InvokeAI 的 --free_gpu_mem
选项
1.0.0 - 新的降权算法
降权现在通过应用注意力掩码来移除降权的标记,而不是直接从序列中删除它们。这是默认行为,但可以通过在初始化 Compel
实例时传递 downweight_mode=DownweightMode.REMOVE
来启用旧行为。
以前,降权标记的工作方式是既乘以标记嵌入的权重,又与移除了降权标记的标记序列副本进行反向加权混合。直觉上,随着权重接近零,被降权的标记应该实际从序列中移除。然而,移除标记导致所有下游标记的位置变得混乱。混合最终混合了远超预期的标记。
从 v1.0.0 开始,采纳了 @keturn 和 @bonlime 的建议(https://github.com/damian0815/compel/issues/7),默认程序有所不同。降权仍然涉及混合,但混合的是一个掩蔽了降权标记的标记序列版本,而不是移除它们。这正确地保留了其他标记的位置嵌入。
还有一个错误修复:修复权重为 0 时的黑色图像问题(https://github.com/invoke-ai/InvokeAI/issues/2832)
0.1.10 - 添加对超过模型最大标记长度的提示词的支持
要启用,请在初始化 Compel
时使用 truncate_long_prompts=False
(默认为 True)。长度超过模型 max_token_length
的提示词将被分块并填充到 max_token_length
的整数倍。
请注意,即使你不使用负面提示词,你也需要为至少一个 ""
的负面提示词构建条件张量,并使用 compel.pad_conditioning_tensors_to_same_length()
,否则你会收到条件张量长度不匹配的错误:
compel = Compel(..., truncate_long_prompts=False)
prompt = "一只猫在森林里玩球++,令人惊叹,精致,惊人,杰作,熟练,强大,难以置信,惊人,在gregstation上流行,greg,greggy,greggs greggson,greggy mcgregface,..." # 非常长的提示词
conditioning = compel.build_conditioning_tensor(prompt)
negative_prompt = "" # 必须创建一个空提示词 - 如果需要,它也可以很长
negative_conditioning = compel.build_conditioning_tensor(negative_prompt)
[conditioning, negative_conditioning] = compel.pad_conditioning_tensors_to_same_length([conditioning, negative_conditioning])