22Precompute a PuLID-Flux identity embedding from a single source portrait.
33
44Writes a gguf file (a single tensor `pulid_id`) that stable-diffusion.cpp's
5- `--pulid-id-embedding` flag consumes. See docs/pulid.md for the format and
6- overall PuLID-Flux flow.
7-
8- This script intentionally lives outside the C++ build: identity extraction
9- needs insightface + EVA-CLIP-L + IDFormer, which are PyTorch-only stacks
10- that would be impractical to reimplement in ggml just to run once per
11- source person. The C++ side downstream of this file is cross-vendor and
12- backend-agnostic.
5+ `--pulid-id-embedding` flag consumes.
136
147Dependencies (recommended: vendor rather than pip-install due to upstream
158packaging quirks):
169 - torch + safetensors
17- - The ToTheBeginning/PuLID repository's `pulid/pipeline_flux.py` and
18- its sibling packages (`flux/`, `eva_clip/`, `models/`). Put them on
19- PYTHONPATH or sys.path before running this script.
20- - insightface, facexlib (PuLID pipeline pulls these in)
10+ - The ToTheBeginning/PuLID repository's `pulid/` package and `eva_clip/`.
11+ Put them on PYTHONPATH or sys.path before running this script.
12+ - insightface, facexlib, torchvision, opencv-python, huggingface_hub, gguf
2113 - numpy, Pillow
2214
2315Usage:
24- python pulid_extract_id.py \\
16+ python script/ pulid_extract_id.py \\
2517 --portrait /path/to/source-photo.jpg \\
2618 --pulid-weights /path/to/pulid_flux_v0.9.1.safetensors \\
2719 --out /path/to/source.pulidembd
3527import argparse
3628import os
3729import sys
38-
39-
40- def _make_minimal_flux_skeleton (device ):
41- """PuLIDPipeline expects a `dit` (Flux transformer) to attach its
42- PerceiverAttentionCA modules to during construction. We never run a
43- forward pass on it -- the encoders alone (which is what we actually
44- need) live on the pipeline object, not the dit. So we instantiate a
45- real Flux skeleton with default params and never load its weights."""
46- import torch
47- from flux .model import Flux
48- from flux .util import configs
49-
50- with torch .device ("cpu" ):
51- model = Flux (configs ["flux-dev" ].params ).to (torch .bfloat16 )
52- return model
30+ from types import SimpleNamespace
5331
5432
5533def extract (portrait_path : str , pulid_weights : str ) -> "torch.Tensor" :
@@ -65,18 +43,17 @@ def extract(portrait_path: str, pulid_weights: str) -> "torch.Tensor":
6543
6644 print (f"device={ device } " , flush = True )
6745
68- print ("constructing minimal Flux skeleton (no weights loaded)" , flush = True )
69- dit = _make_minimal_flux_skeleton (device )
70-
71- print ("instantiating PuLIDPipeline" , flush = True )
72- pulid = PuLIDPipeline (dit = dit , device = device ,
46+ # PuLIDPipeline only attaches pulid_ca attributes to `dit` during
47+ # construction; get_id_embedding() never runs Flux, so a dummy object is
48+ # enough and avoids importing/building a Flux skeleton.
49+ print ("instantiating PuLIDPipeline with a dummy Flux object" , flush = True )
50+ dit = SimpleNamespace ()
51+ pulid = PuLIDPipeline (dit = dit ,
52+ device = device ,
7353 weight_dtype = torch .bfloat16 ,
7454 onnx_provider = onnx_provider )
7555
7656 print (f"loading PuLID weights from { pulid_weights } " , flush = True )
77- # PuLIDPipeline.load_pretrain expects a "version" string used to construct
78- # the default filename when pretrain_path is None. We pass the file
79- # directly so the version string is informational only.
8057 pulid .load_pretrain (pretrain_path = pulid_weights , version = "v0.9.1" )
8158
8259 print (f"extracting ID embedding from { portrait_path } " , flush = True )
@@ -100,10 +77,6 @@ def write_embd(tensor, out_path: str, dtype_choice: str) -> None:
10077
10178 os .makedirs (os .path .dirname (out_path ) or "." , exist_ok = True )
10279
103- # The embedding ships as a standard gguf container holding a single tensor
104- # named "pulid_id". numpy is row-major (num_tokens, token_dim); gguf stores
105- # dims reversed, so stable-diffusion.cpp reads it back as
106- # ne[0]=token_dim, ne[1]=num_tokens (see load_pulid_id_embedding).
10780 writer = gguf .GGUFWriter (out_path , arch = "pulid" )
10881 writer .add_uint32 ("pulid.version" , 1 )
10982
0 commit comments