DDUF

DDUF

DDUF(DDUF's Diffusion Unified Format)는 각종 모델 배포 방식과 가중치 저장 형식을 통합해 모든 모델 컴포넌트를 단일 파일로 묶는, 디퓨전 모델용 단일 파일 형식이에요. 언어에 구애받지 않고, 전체 파일을 다운로드하지 않고 원격 위치에서 파싱될 수 있도록 설계됐어요.

출처: 문서

본문

이 작업은 GGUF 형식에서 영감을 얻었어요.

DDUF 조직을 확인해 가장 인기 있는 디퓨전 모델을 DDUF로 사용해 보세요.

[!TIP] 기여를 환영해요!

널리 채택되는 파일 형식을 만들려면 커뮤니티의 조기 피드백이 필요해요. 정해진 건 없고 모두의 의견을 소중히 여겨요. 사용 사례가 다뤄지지 않았나요? DDUF 조직 discussions에서 알려주세요.

주요 기능은 다음과 같아요.

  1. 단일 파일 패키징
  2. 기존 도구를 활용하는 ZIP 파일 형식 기반
  3. 압축 없음 → 빠른 로딩·저장을 위한 mmap 호환성 보장
  4. 언어 중립적: Python, JavaScript, Rust, C++ 등 어떤 언어로도 도구 구현 가능
  5. HTTP 친화적: HTTP Range 요청으로 메타데이터와 파일 구조를 원격으로 가져올 수 있음
  6. 유연함: 각 모델 컴포넌트가 현재 Diffusers 구조를 따라 자체 디렉토리에 저장
  7. 안전: 가중치 저장 형식으로 Safetensors를 사용하고 ZIP 폭탄을 막기 위해 중첩 디렉토리를 금지

기술 사양

기술적으로 .dduf 파일 .zip 아카이브예요. 보편적으로 지원되는 파일 형식 위에 구축해 견고한 툴링이 이미 존재함을 보장해요. 다만 디퓨전 모델 요구사항을 충족하기 위해 몇 가지 제약이 적용돼요.

  • 데이터는 압축 없이 저장해야 함(플래그 0) → 메모리 매핑을 통한 지연 로딩 가능
  • ZIP64 프로토콜로 저장해야 함 → 4GB 초과 파일 저장 가능
  • 아카이브에는 .json, .safetensors, .model, .txt 파일만 포함 가능
  • model_index.json 파일이 아카이브 루트에 있어야 함. 모델과 그 컴포넌트에 대한 메타데이터의 key-value 매핑이 포함되어야 함
  • 각 컴포넌트는 자체 디렉토리(예: 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 파일 읽기·내보내기 헬퍼가 내장되어 있어요. 이 툴링이 diffusers 통합처럼 Python 생태계에서 채택되는 것이 목표예요. 비슷한 툴링을 다른 언어(JavaScript, Rust, C++ 등)로도 개발할 수 있어요.

DDUF 파일 읽는 법?

read_dduf_file에 경로를 넘기면 DDUF 파일을 읽어요. 메타데이터만 읽으므로 메모리를 폭발시키지 않는 가벼운 호출이에요. 아래 예시에서는 FLUX.1-dev.dduf 파일을 이미 로컬에 다운로드했다고 가정해요.

>>> from huggingface_hub import read_dduf_file

# Read DDUF metadata
>>> dduf_entries = read_dduf_file("FLUX.1-dev.dduf")

read_dduf_file은 DDUF 아카이브의 각 파일에 해당하는 엔트리로 구성된 매핑을 반환해요. 파일은 원본 DDUF 파일에서 엔트리의 파일명, 오프셋, 길이를 포함하는 DDUFEntry dataclass로 표현돼요. 이 정보는 전체 파일을 로드하지 않고 내용을 읽는 데 유용해요. 실제로는 저수준 읽기를 직접 다룰 필요 없이 헬퍼에 의존하면 돼요.

예를 들어 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` is a bytes object

[!TIP] as_mmap은 메모리 매핑 속성을 활용하려면 컨텍스트 매니저 안에서 사용해야 해요.

DDUF 파일 쓰는 법?

export_folder_as_dduf에 폴더 경로를 넘기면 DDUF 파일을 내보내요.

# Export a folder as a DDUF file
>>> 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 파일에 포함할 파일 목록을 명시적으로 지정할 수 있어요.

# Export specific files from the local disk.
>>> from huggingface_hub import export_entries_as_dduf
>>> export_entries_as_dduf(
...     dduf_path="stable-diffusion-v1-4-FP16.dduf",
...     entries=[ # List entries to add to the DDUF file (here, only FP16 weights)
...         ("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"),
...         # ... add more entries here
...     ]
... )

export_entries_as_dduf는 모델을 이미 디스크에 저장했다면 잘 동작해요. 그런데 메모리에 로드된 모델을 DDUF 파일로 직접 직렬화하고 싶다면? 데이터를 반복적으로 직렬화하는 방법을 알려주는 Python generator 제공해 export_entries_as_dduf가 이를 가능하게 해요.

(...)

# Export state_dicts one by one from a loaded pipeline
>>> def as_entries(pipe: DiffusionPipeline) -> Generator[Tuple[str, bytes], None, None]:
...     # Build a generator that yields the entries to add to the DDUF file.
...     # The first element of the tuple is the filename in the DDUF archive. The second element is the content of the file.
...     # Entries will be evaluated lazily when the DDUF file is created (only 1 entry is loaded in memory at a time)
...     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())
...     # ... add more entries here

>>> 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 파일은 불변(immutable)로 설계됐어요. 모델을 업데이트하려면 새 DDUF 파일을 만들어야 해요.

어떤 프레임워크/앱이 DDUF를 지원하나요?

다른 라이브러리와 프레임워크에도 지속적으로 연락하고 있어요. 프로젝트에 지원을 추가하고 싶다면 DDUF 조직에서 Discussion을 열어주세요.

더 알아보기 (Learn more)

DDUF는 디퓨전 모델의 모든 컴포넌트를 압축 없는 단일 ZIP(엄밀히는 .dduf) 파일로 묶는 형식이에요. 메모리 매핑과 HTTP Range 요청을 지원해 원격 파싱과 지연 로딩이 가능해요. read_dduf_file로 메타데이터를, export_folder_as_dduf·export_entries_as_dduf로 내보내기를 하고, Diffusers에서 dduf_file= 인자로 바로 로드할 수 있어요.