news 2026/9/28 8:22:26

stable-diffusion.cpp 中 PuLID-Flux 人脸身份保持(Face Identity Preservation)完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
stable-diffusion.cpp 中 PuLID-Flux 人脸身份保持(Face Identity Preservation)完整指南
  • 人工智能
  • 大模型
  • 本地部署
  • 推理引擎
  • 媒体生成

【免费下载链接】stable-diffusion.cpp

Diffusion model(SD,Flux,Wan,Qwen Image,Z-Image,...) inference in pure C/C++

项目地址:https://gitcode.com/GitHub_Trending/st/stable-diffusion.cpp
点击查看免费下载

导读:本文基于 stable-diffusion.cpp 的 PuLID-Flux 身份保持文档 展开,完整讲解如何在纯 C/C++ 推理栈上为 Flux.1(schnell / dev)叠加 PuLID-Flux 人脸身份注入技术:从架构原理(20 个PerceiverAttentionCA交叉注意力钩子)、身份嵌入(.pulidembd)的预计算与 gguf 容器格式,到三个--pulid-*命令行参数的组合规则、内存预算、后端选型与 SHA-256 验证方法。读完本文,你将能独立完成“单张人像 → 任意场景/姿态/提示词下保持同一人脸”的 Flux 生成流水线搭建,并规避已知的 SLG 冲突、v1.1 权重不兼容、1024 分辨率显存不足等坑。

1. PuLID-Flux 是什么,stable-diffusion.cpp 如何支持它

PuLID-Flux(PuLID: Pure and Lightning ID Customization via Contrastive Alignment)是一种在 Flux.1 扩散模型之上工作的身份注入(identity-injection)技术:给定一张源人像,后续生成的新图可以保留该人物的脸部特征,同时自由改变场景、姿态和提示词。

在 stable-diffusion.cpp 中,该功能面向 Flux.1schnell与dev两个变体实现。它与同为身份保持方案的 PhotoMaker 存在关键区别:

对比维度PhotoMakerPuLID-Flux(本实现)
身份提取时机在推理过程中从一批图像中提取一次性离线预计算,得到固定 embedding
身份提取栈推理期较轻较重:insightface ArcFace + EVA-CLIP-L + IDFormer 编码器
C++ 端负担需处理多图输入只消费一个预计算张量
跨后端能力—提取之后全部是 C++/ggml 计算

从源码结构看,src/extensions/pulid_extension.cpp中PuLIDExtension是一个标准的GenerationExtension扩展组件:它把预计算的id_embedding与id_weight写入FluxDiffusionExtra(见 src/model/diffusion/model.hpp),再由 Flux 图构建阶段真正注入。由于身份提取栈(ArcFace、EVA-CLIP-L、IDFormer)过于庞大,难以移植到 C++/ggml,因此本实现刻意选择了**“消费外部预计算身份嵌入”**的跨厂商(cross-vendor)方案:一次人像只需离线提取一次,之后所有生成环节都运行在纯 C++ 推理栈上,可跑在 Vulkan、CUDA、Metal、ROCm、CPU 等任意后端。

2. 架构:20 个交叉注意力钩子如何注入 Flux 去噪循环

PuLID-Flux 对 Flux 去噪循环的改造,本质上是在 Flux transformer 块之间插入一层小型交叉注意力模块栈——PerceiverAttentionCA。stable-diffusion.cpp 的实现与其对齐:

  • 19 个 double-stream 块:每 2 个块插入 1 个钩子,共 10 个注入点(pulid_double_interval = 2);
  • 38 个 single-stream 块:每 4 个块插入 1 个钩子,共 10 个注入点(pulid_single_interval = 4);

合计 20 个交叉注意力层,对应源码 src/model/diffusion/flux.hpp 中的pulid_enabled、pulid_double_interval、pulid_single_interval配置,权重按pulid_ca.<i>命名加载(见 flux.hpp 与 flux.hpp)。

每个交叉注意力层的数据流为:

  1. 取当前图像 token(image tokens)作为 Query;
  2. 取32 token × 2048 维的身份嵌入作为 Key + Value;
  3. 输出经id_weight(典型值 1.0)缩放后加回图像 token:
img = img + scale(ca_out, pulid_id_weight)

上述加回操作在 flux.hpp(double-stream 分支)与 flux.hpp(single-stream 分支)中均有体现,并伴随ggml_graph_cut标记以配合自动图切分。单层模块PuLIDPerceiverAttentionCA的定义位于 src/model/adapter/pulid.hpp:内部为 norm →to_q(2048 维)→to_kv(3072 维)→ attention →to_out的结构,即一个轻量的感知机式交叉注意力单元。

关键设计:当pulid_id == nullptr或权重为空时,pulid_active为 false,整条路径被跳过(见 flux.hpp),即 PuLID 完全可裁剪、不改变非 PuLID 运行的行为。

3. 所需权重:在三件套之外还需要什么

在标准 Flux 权重集(transformer + VAE + clip_l + t5xxl,配置方式与 docs/flux.md 完全一致)之外,PuLID 还需要三个文件:

  1. Flux 基础权重:transformer、VAE、CLIP-L、T5-XXL,与普通 Flux 生成完全相同;
  2. PuLID 权重:pulid_flux_v0.9.0.safetensors或pulid_flux_v0.9.1.safetensors。本实现针对 v0.9.1 验证通过,推荐使用 v0.9.1;
  3. 身份嵌入(.pulidembd):由下面的预计算工具生成,一次人像只需一份。

⚠️重要兼容性提示:pulid_v1.1.safetensors(v1.1)暂不支持。v1.1 使用了重命名后的键(id_adapter_attn_layers.*而非pulid_ca.*),模块结构也可能不同,属于待实现的未来工作(Future PR)。下载时请务必选择 v0.9.x 版本。

从加载路径看,PuLID 权重路径通过上下文参数pulid_weights_path传入(见 include/stable-diffusion.h),该参数在 CLI 侧由--pulid-weights解析(见 examples/common/common.cpp)。

4. 预计算身份嵌入:一次性离线提取

由于身份提取栈无法移植进 C++,stable-diffusion.cpp 采用“每个源人像运行一次外部 Python 工具”的方式,把提取结果固化成一个(32, 2048)的嵌入张量并写入.pulidembd二进制文件(fp16 存储约 131 KB)。同一个文件可被任意次生成复用。

仓库在 scripts/pulid_extract_id.py 提供了参考 Python 脚本,其运行环境要求:

  • 可用的 CUDA / CPU PyTorch 栈;
  • insightface、facexlib、eva-clip、torchvision、opencv-python、huggingface_hub、gguf等依赖;
  • ToTheBeginning/PuLID 仓库中的pulid/包(含pulid/pipeline_flux.py)与eva_clip/包,需加入PYTHONPATH;flux/包不需要——因为get_id_embedding()不会真正运行 Flux 去噪,脚本内部用SimpleNamespace()构造了一个 dummy Flux 对象(见 pulid_extract_id.py),从而避免引入整套 Flux 骨架。

运行方式:

python pulid_extract_id.py \ --portrait /path/to/source-photo.jpg \ --pulid-weights /path/to/pulid_flux_v0.9.1.safetensors \ --out /path/to/source.pulidembd

脚本逻辑(可对照 pulid_extract_id.py 阅读):

  1. 自动探测 CUDA,选择device = "cuda"/ ONNX provider"gpu",否则回退 CPU;
  2. 用PuLIDPipeline(dit=dummy, device, weight_dtype=bfloat16, onnx_provider)构造管线,并以version="v0.9.1"加载 PuLID 权重;
  3. 读取人像(自动转 RGB),调用get_id_embedding(face_img)得到身份嵌入;若结果带有 batch 维(shape[1, num_tokens, token_dim]),会去掉该维;
  4. 校验形状为二维(num_tokens, token_dim)后写入 gguf。

可选参数--dtype支持fp16(默认,约 131 KB)、bf16、fp32三种存储精度,对应脚本write_embd()中的三个分支(见 pulid_extract_id.py)。

注意:人像必须包含清晰可见的人脸。insightface 的antelopev2检测器在首次运行时会被自动下载。

5. 身份嵌入的文件格式(gguf 容器)

.pulidembd是一个标准gguf容器,仅含单个张量:

tensor name : "pulid_id" shape : [token_dim, num_tokens] (ggml 顺序;典型 [2048, 32]) type : F16 (也接受 F32 / BF16) metadata : general.architecture = "pulid", pulid.version = 1

C++ 端加载逻辑见 src/extensions/pulid_extension.cpp:

  • 使用标准 gguf 读取器gguf_init_from_file打开(没有定制解析器);
  • 查找名为pulid_id的张量;
  • 校验形状合理性:token_dim在 1~65536、num_tokens在 1~1024 且ne[2] == ne[3] == 1;
  • 支持 F32(直接 memcpy)、F16 / BF16(逐元素转换为 fp32)三种类型,其他类型拒绝加载;
  • 加载时统一转为 fp32,供后续 ggml 图使用。

写入侧则由 scripts/pulid_extract_id.py 的GGUFWriter完成:arch="pulid"、pulid.version=1、张量名pulid_id,与读取端完全对应。

6. 命令行用法:让 Flux 生成保持指定人脸

以 Windows 下的sd-cli.exe为例(Linux/macOS 将路径与可执行名替换即可):

.\bin\Release\sd-cli.exe \ --diffusion-model models\flux1-schnell-Q4_K_S.gguf \ --vae models\ae.safetensors \ --clip_l models\clip_l.safetensors \ --t5xxl models\t5xxl_fp16.safetensors \ --pulid-weights models\pulid_flux_v0.9.1.safetensors \ --pulid-id-embedding source.pulidembd \ --pulid-id-weight 1.0 \ -p "candid photograph of a young woman on a beach at sunset" \ --cfg-scale 1.0 --sampling-method euler --steps 4 -W 512 -H 512 \ --seed 42 --clip-on-cpu \ -o out.png

若使用 Flux Dev(而非 Schnell),追加--guidance 3.5并把步数提到--steps 20。

参数解析位于 examples/common/common.cpp(--pulid-weights见 L530-L534,--pulid-id-embedding见 L1116-L1119,--pulid-id-weight见 L1286),最终汇入sd_pulid_params_t(见 include/stable-diffusion.h),再经 src/pipeline/request.cpp 拷贝到扩展上下文。

6.1 三个参数必须同时出现

Flag作用默认值/取值范围
--pulid-weights <path>pulid_flux_v0.9.x.safetensors的路径,随模型一起加载无
--pulid-id-embedding <p>预计算工具产出的.pulidembd二进制路径无
--pulid-id-weight <f>身份注入强度典型 0.7–1.2,默认 1.0

三条规则务必理解:

  • 三个 flag 必须同时设置才会激活 PuLID;
  • 只设--pulid-weights而不设 embedding:权重被加载,但运行时不注入(PuLIDExtension以pulid_weights_path非空作为enabled条件,而prepare_condition在 embedding 为空时不会写入flux_extra,见 pulid_extension.cpp);
  • 设--pulid-id-weight 0:注入贡献被置零——这正是官方推荐的“证伪测试(falsification test)”手段:相同 seed 下输出应与完全不带 PuLID 的跑法逐字节一致。

7. 内存预算与 1024 分辨率下的显存处理

官方在 512×512、4 步(Schnell)、12 GB 消费级显卡(Flux Schnell Q4 GGUF + CPU 卸载的 clip_l / t5xxl + GPU 常驻 VAE)上实测:20 个交叉注意力层对去噪时间的影响约+10%,峰值显存几乎不增加——因为交叉注意力只在 token 序列上做轻量投影,不引入大的中间缓存。

但在1024×1024 + Flux Dev Q4 + 20 步 + PuLID场景下,VAE 解码的计算缓冲区在 12 GB 卡上放不下,即使加了--vae-on-cpu也不行。原因在于:--vae-on-cpu只是把 VAE 权重卸载到 CPU,计算图仍留在默认后端(这是 stable-diffusion.cpp 的既有行为,并非 PuLID 专属问题)。正确解法是把 VAE 的计算也显式路由到 CPU 后端:

--backend "diffusion=vulkan0,vae=cpu"

8. 后端选择:随主扩散模型走

PuLID 交叉注意力层与主扩散模型运行在同一后端,因此复用标准的--backend参数。常见组合:

# AMD Vulkan --backend "diffusion=vulkan0,vae=cpu" # NVIDIA Vulkan --backend "diffusion=vulkan1,vae=cpu" # CUDA --backend "diffusion=cuda0,vae=cpu"

需要说明的边界:该实现是纯 ggml 图构建,理论上可在 CUDA、ROCm、Metal 上工作,但截至文档写作时,仅 Vulkan 与 CPU 后端经过原作者实测,其余后端欢迎用户验证。

9. 验证方法:三路 SHA-256 对照测试

在新组合(模型 + 后端 + 硬件)首次跑通时,官方推荐用三路 SHA-256 对照作为健康检查:

跑法预期哈希关系
A:不带任何--pulid-*flag基线(baseline)
B:带 PuLID flag,但--pulid-id-weight 0.0与 A 逐字节一致
C:带 PuLID flag,--pulid-id-weight 1.0与 A、B 均不同,且输出保持源人脸

判定逻辑:如果 A 与 C 不同,但 A 与 B 也不同,说明注入路径在权重为 0 时仍在分配或计算某些东西——很可能是个 bug。这一约定与第 6.1 节的“置零即无注入”语义互相印证,是排查实现正确性的有力手段。

10. 限制与暂不支持的功能(务必提前知晓)

  • --skip-layers(skip-layer-guidance / SLG)与 PuLID 不可同用。pulid_ca的索引按“未被跳过的块”推进,一旦有块被跳过,交叉注意力权重的分配就会相对训练区间静默错位;参考 PyTorch 实现本身也没有 SLG,不存在可模拟的既定行为。源码层面同样明确:skip_layers非空时pulid_run会被置为 false(见 flux.hpp)。请二者择一使用。
  • PuLID v1.1 权重(pulid_v1.1.safetensors,键名已重排)暂不支持,请使用 v0.9.x。
  • 多张 ID 图:参考 PyTorch 实现可将多张人像融合为更强的单一嵌入;本实现接受的是外部预计算工具从一张或多张图产出的单个嵌入文件。
  • CFG 的 negative-prompt 分支:PuLID 仅在正条件(positive conditioning)路径注入(与已发布参考实现一致)。Flux 的蒸馏式引导(distilled guidance)正常使用时不跑独立的 uncond 分支,因此该限制只影响--true-cfg这类非 Flux 标准工作流。
  • 后端覆盖:除 Vulkan 与 CPU 外未经原作者实测,其他后端依赖用户验证。

11. 快速上手指南:从零到首张保脸图

汇总全文,搭建 PuLID-Flux 生成的最小路径:

  1. 准备 Flux 基础权重:transformer GGUF(如flux1-schnell-Q4_K_S.gguf)、VAE、CLIP-L、T5-XXL,参考 docs/flux.md;
  2. 下载 PuLID 权重:pulid_flux_v0.9.1.safetensors(勿用 v1.1);
  3. 搭建 Python 预计算环境:安装 PyTorch 与insightface、facexlib、eva-clip等依赖,将 ToTheBeginning/PuLID 的pulid/与eva_clip/包加入PYTHONPATH;
  4. 生成.pulidembd:运行python scripts/pulid_extract_id.py --portrait xxx.jpg --pulid-weights pulid_flux_v0.9.1.safetensors --out source.pulidembd,一次完成、永久复用;
  5. 运行sd-cli:三个--pulid-*flag 同时给出;Schnell 用 4 步、Dev 追加--guidance 3.5与 20 步;
  6. 验证:按第 9 节做三路 SHA-256 对照,确认注入真实生效且零权重路径干净。

整个 PuLID 链路中,只有第 3、4 步是 Python/PyTorch 一次性工作;从 embedding 文件加载到去噪注入、再到出图,全部由 src/extensions/pulid_extension.cpp 与 src/model/diffusion/flux.hpp 中的 C++/ggml 实现完成,因而可以自由跑在 Vulkan、CUDA、Metal、ROCm 与纯 CPU 环境下。

  • 人工智能
  • 大模型
  • 本地部署
  • 推理引擎
  • 媒体生成

【免费下载链接】stable-diffusion.cpp

Diffusion model(SD,Flux,Wan,Qwen Image,Z-Image,...) inference in pure C/C++

项目地址:https://gitcode.com/GitHub_Trending/st/stable-diffusion.cpp
点击查看免费下载
上一篇:为什么你的ComfyUI-Impact-Pack安装不完整?完整安装指南与功能解析
下一篇:Irony Mod Manager:Paradox游戏模组管理的终极解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 8:22:22

FAST Element 数据绑定的运行时核心:深入解析 BindingBehavior 类

前端UI组件 【免费下载链接】fast The adaptive interface system for modern web experiences. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/fa/fast 点击查看 免费下载 导读 BindingBehavior 是 FAST Element&#xff08;microsoft/fast-element&#xff09;模板…

作者头像 李华
网站建设 2026/9/28 8:22:10

React脚手架与Hooks实战:从工程化配置到高复用封装

React脚手架及Hooks钩子&#xff0c;这两块东西在圈子里聊的人很多&#xff0c;但大多数讨论都停在了“脚手架怎么搭、Hooks怎么用”的演示层面&#xff0c;真正拿到生产环境、放进团队协作里&#xff0c;你会发现差得不是一星半点。我这篇是这个系列的第三篇&#xff0c;前两篇…

作者头像 李华
网站建设 2026/9/28 8:22:05

Appium实战:移动端输入安全自动化测试体系搭建

做移动端测试这些年&#xff0c;我一直觉得“输入框”是被低估的重灾区。很多人以为输入安全就是加个长度限制、密码掩码&#xff0c;真正拿恶意负载去怼输入框的测试少之又少。直到有一次我在一个金融类App的搜索框里塞了一段XSS payload&#xff0c;后端原样返回并渲染到页面…

作者头像 李华
网站建设 2026/9/28 8:21:42

基于Qt的C++陨石撞击飞机游戏设计与实现:从类设计到碰撞检测

简介&#xff1a;基于QT的陨石撞击飞机游戏设计与实现&#xff0c;是一份C期末大作业的完整源码及文档说明&#xff0c;面向计算机相关专业学生和需要项目实战练习的初学者&#xff0c;可直接用于课程大作业或毕业设计参考。压缩包共79个文件&#xff0c;约34.93MB&#xff0c;…

作者头像 李华
网站建设 2026/9/28 8:21:27

finally为什么不等待异步任务?Java并发执行模型深度解析

开头写Java这么多年&#xff0c;try-catch-finally大概是背得最熟的几行代码之一。但真到生产环境里&#xff0c;很多人栽在“finally不等异步”这个细节上。你辛辛苦苦在try里提交了一个异步任务&#xff0c;想在finally里把线程池关掉、把数据库连接释放、把状态位复位&#…

作者头像 李华
网站建设 2026/9/28 8:20:45

MiMo-V3推理优化:为何24层prefill用25层?HySparse2稀疏化实践

在推理优化圈子里&#xff0c;我最近一段时间基本都泡在 MiMo-V3 的 prefill 性能实验里。项目组决定用 HySparse2 来做稀疏化加速&#xff0c;实验配置单上明确写着“前 25 层 prefill”&#xff0c;不少同事第一反应都是&#xff1a;为什么是 25 层&#xff1f;不是应该跑整个…

作者头像 李华