DDUF
概述
DDUF(DDUF's Diffusion Unified Format)是一种用于扩散模型的单文件格式,旨在通过将所有模型组件打包到单个文件中来统一不同的模型分发方法和权重保存格式。它是语言无关的,并且设计为可以从远程位置解析,而无需下载整个文件。
这项工作从 GGUF 格式中汲取了灵感。
查看 DDUF 组织,开始使用一些最流行的 DDUF 格式扩散模型。
我们热烈欢迎贡献!
要创建一个广泛采用的文件格式,我们需要社区的早期反馈。一切都不是一成不变的,我们重视每个人的意见。你的用例没有被覆盖?请在 DDUF 组织的讨论中告诉我们。
其关键特性包括以下内容。
- 单文件打包。
- 基于 ZIP 文件格式,以利用现有工具。
- 无压缩,确保
mmap兼容性,实现快速加载和保存。 - 语言无关:工具可以用 Python、JavaScript、Rust、C++ 等实现。
- HTTP 友好:可以使用 HTTP Range 请求远程获取元数据和文件结构。
- 灵活:每个模型组件存储在自己的目录中,遵循当前的 Diffusers 结构。
- 安全:使用 Safetensors 作为权重保存格式,并禁止嵌套目录以防止 ZIP 炸弹。
技术规范
从技术上讲,.dduf 文件就是一个 .zip 归档文件。通过构建在普遍支持的文件格式之上,我们确保已有强大的工具可用。但是,为了满足扩散模型的要求,强制执行了一些约束:
- 数据必须未压缩存储(标志
0),允许使用内存映射进行延迟加载。 - 数据必须使用 ZIP64 协议存储,支持保存超过 4GB 的文件。
- 归档文件只能包含
.json、.safetensors、.model和.txt文件。 - 归档文件根目录必须存在
model_index.json文件。它必须包含有关模型及其组件的元数据的键值映射。 - 每个组件必须存储在自己的目录中(例如,
vae/、text_encoder/)。嵌套文件必须使用 UNIX 风格的路径分隔符(/)。 - 每个目录必须对应于
model_index.json索引中的一个组件。 - 每个目录必须包含一个 json 配置文件(
config.json、tokenizer_config.json、preprocessor_config.json、scheduler_config.json之一)。 - 禁止子目录。
想要检查你的文件是否有效?使用此 Space 进行检查:https://huggingface.co/spaces/DDUF/dduf-check。
用法
huggingface_hub 提供了在 Python 中处理 DDUF 文件的工具。它包括用于验证文件完整性的内置规则以及用于读取和导出 DDUF 文件的辅助函数。目标是看到这些工具在 Python 生态系统中被采用,例如在 diffusers 集成中。可以为其他语言(JavaScript、Rust、C++ 等)开发类似的工具。
如何读取 DDUF 文件?
将路径传递给 read_dduf_file 以读取 DDUF 文件。只读取元数据,这意味着这是一个轻量级调用,不会占用大量内存。在下面的示例中,我们假设你已经将 FLUX.1-dev.dduf 文件下载到本地。
>>> from huggingface_hub import read_dduf_file
# 读取 DDUF 元数据
>>> dduf_entries = read_dduf_file("FLUX.1-dev.dduf")
read_dduf_file 返回一个映射,其中每个条目对应于 DDUF 归档文件中的一个文件。文件由 DDUFEntry 数据类表示,该类包含原始 DDUF 文件中条目的文件名、偏移量和长度。此信息对于在不加载整个文件的情况下读取其内容很有用。实际上,你不需要处理低级读取,而是依赖辅助函数。
例如,以下是如何 加载 model_index.json 内容:
>>> import json
>>> json.loads(dduf_entries["model_index.json"].read_text())
{'_class_name': 'FluxPipeline', '_diffusers_version': '0.32.0.dev0', '_name_or_path': 'black-forest-labs/FLUX.1-dev', ...
对于二进制文件,你需要使用 as_mmap 访问原始字节。这会将字节作为原始文件的内存映射返回。内存映射允许你只读取需要的字节,而无需将所有内容加载到内存中。例如,以下是如何加载 safetensors 权重:
>>> import safetensors.torch
>>> with dduf_entries["vae/diffusion_pytorch_model.safetensors"].as_mmap() as mm:
... state_dict = safetensors.torch.load(mm) # `mm` 是一个字节对 象
as_mmap 必须在上下文管理器中使用,才能利用内存映射属性。
如何写入 DDUF 文件?
将文件夹路径传递给 export_folder_as_dduf 以导出 DDUF 文件。
# 将文件夹导出为 DDUF 文件
>>> from huggingface_hub import export_folder_as_dduf
>>> export_folder_as_dduf("FLUX.1-dev.dduf", folder_path="path/to/FLUX.1-dev")
此工具扫描文件夹,添加相关条目,并确保导出的文件有效。如果在此过程中出现任何问题,将引发 DDUFExportError。
为了获得更大的灵活性,使用 [export_entries_as_dduf] 显式指定要包含在最终 DDUF 文件中的文件列表:
# 从本地磁盘导出特定文件。
>>> from huggingface_hub import export_entries_as_dduf
>>> export_entries_as_dduf(
... dduf_path="stable-diffusion-v1-4-FP16.dduf",
... entries=[ # 要添加到 DDUF 文件的条目列表(这里只有 FP16 权重)
... ("model_index.json", "path/to/model_index.json"),
... ("vae/config.json", "path/to/vae/config.json"),
... ("vae/diffusion_pytorch_model.fp16.safetensors", "path/to/vae/diffusion_pytorch_model.fp16.safetensors"),
... ("text_encoder/config.json", "path/to/text_encoder/config.json"),
... ("text_encoder/model.fp16.safetensors", "path/to/text_encoder/model.fp16.safetensors"),
... # ... 在此处添加更多条目
... ]
... )
如果你已经将模型保存到磁盘,export_entries_as_dduf 效果很好。但是,如果你有一个加载到内存中的模型,并希望将其直接序列化为 DDUF 文件怎么办?export_entries_as_dduf 允许你通过提供 Python generator 来实现这一点,该生成器告诉如何迭代地序列化数据:
(...)
# 从加载的管道中逐个导出 state_dicts
>>> def as_entries(pipe: DiffusionPipeline) -> Generator[Tuple[str, bytes], None, None]:
... # 构建一个生成器,生成要添加到 DDUF 文件的条目。
... # 元组的第一个元素是 DDUF 归档文件中的文件名。第二个元素是文件的内容。
... # 条目将在创建 DDUF 文件时延迟评估(一次只在内存中加载 1 个条目)
... yield "vae/config.json", pipe.vae.to_json_string().encode()
... yield "vae/diffusion_pytorch_model.safetensors", safetensors.torch.save(pipe.vae.state_dict())
... yield "text_encoder/config.json", pipe.text_encoder.config.to_json_string().encode()
... yield "text_encoder/model.safetensors", safetensors.torch.save(pipe.text_encoder.state_dict())
... # ... 在此处添加更多条目
>>> export_entries_as_dduf(dduf_path="my-cool-diffusion-model.dduf", entries=as_entries(pipe))
使用 Diffusers 加载 DDUF 文件
Diffusers 具有 DDUF 文件的内置集成。以下是如何从 Hub 上存储的检查点加载管道的示例:
from diffusers import DiffusionPipeline
import torch
pipe = DiffusionPipeline.from_pretrained(
"DDUF/FLUX.1-dev-DDUF", dduf_file="FLUX.1-dev.dduf", torch_dtype=torch.bfloat16
).to("cuda")
image = pipe(
"photo a cat holding a sign that says Diffusers", num_inference_steps=50, guidance_scale=3.5
).images[0]
image.save("cat.png")
常见问题
为什么基于 ZIP 构建?
ZIP 提供了几个优势:
- 普遍支持的文件格式
- 读取时无需额外依赖
- 内置文件索引
- 广泛的语言支持
为什么不使用在归档文件开头带有目录表的 TAR?
请参阅此评论中的解释。
为什么不压缩?
- 支持大文件的直接内存映射
- 确保一致且可预测的远程文件访问
- 防止文件读取期间的 CPU 开销
- 保持与 safetensors 的兼容性
我可以修改 DDUF 文件吗?
不可以。目前,DDUF 文件设计为不可变。要更新模型,请创建新的 DDUF 文件。
哪些框架/应用程序支持 DDUF?
我们正在不断联系其他库和框架。如果你有兴趣为你的项目添加支持,请在 DDUF 组织中开启讨论。