stable-diffusion.cpp 的 FLUX.2 Klein 9B 分步

核心理念

2026/7/7

每次仅加载一个模型,中间结果暂存于系统内存,最终输出才写入磁盘。

与 ComfyUI / sd-cli 一次性加载所有模型的策略不同,分步管线确保每一步骤可独占全部资源(尤其是显存)。


架构图

text

复制代码
Step 1                     Step 2                     Step 3
┌──────────────┐          ┌──────────────┐          ┌──────────────┐
│  加载 Qwen3  │          │   加载 DiT   │          │   加载 VAE   │
│  text encoder│          │  diffusion   │          │  decode      │
│              │          │              │          │              │
│ prompt ─────→│ cond     │ cond ───────→│ latent   │ latent ─────→│ img
│ (CPU 5GB)    │ float[]  │ (GPU 3-4GB)  │ float[]  │ (GPU 0.5GB)  │ png
│              │          │              │          │              │
│ ↓ 释放 Qwen3 │          │ ↓ 释放 DiT   │          │ ↓ 释放 VAE   │
│ VRAM: 0     │          │ VRAM: 0     │          │ 写磁盘完成   │
└──────────────┘          └──────────────┘          └──────────────┘

内存中传递: cond (float array) → latent (float array) → image (sd_image_t)

具体改动:给 DLL 加三个导出函数

1. sd_encode_condition --- 只跑文本编码

将 prompt 编码为 conditioning tensor,并以平铺的 float 数组返回。

c

复制代码
SD_API bool sd_encode_condition(
    sd_ctx_t* sd_ctx,
    const char* prompt,
    const char* negative_prompt,
    const sd_lora_t* loras,
    int lora_count,
    float** cond_data,     // 输出:condition float 数组(调用者 free)
    int64_t* cond_size     // 输出:数组元素个数
);
  • 需要暴露 StableDiffusionGGML::get_learned_condition()(原私有方法)。

  • cond_data 包含 c_crossattn + c_vector + c_concat 的序列化拼接。

  • 返回后调用者需调用 free_sd_ctx() 释放 Qwen3 模型。

2. sd_sample --- 只跑采样

输入 condition,输出 latent。

c

复制代码
SD_API bool sd_sample(
    sd_ctx_t* sd_ctx,
    const float* cond_data,
    int64_t cond_size,
    int width,
    int height,
    int steps,
    float cfg_scale,
    int64_t seed,
    float** latent_data,   // 输出:latent float 数组
    int64_t* latent_size
);
  • 需要暴露 StableDiffusionGGML::sample()(原私有方法)。

  • 内部反序列化 cond_data 还原为 SDCondition 结构。

  • latent_data 为采样结果(例如 FLUX.2 的 latent 尺寸为 64×64×16)。

  • 可通过 --backend diffusion=vulkan1 指定后端。

3. sd_decode_latent --- 只跑 VAE 解码

输入 latent,输出最终图像。

c

复制代码
SD_API sd_image_t sd_decode_latent(
    sd_ctx_t* sd_ctx,
    const float* latent_data,
    int64_t latent_size
);
  • 需要暴露 StableDiffusionGGML::decode_first_stage()(原私有方法)。

  • 可通过 --backend vae=vulkan1 让 VAE 在 GPU 上运行(模型仅 164 MB)。


调用方主程序逻辑

text

复制代码
1. 创建 sd_ctx,仅传入 --llm(Qwen3),其余路径置空
   sd_ctx_params.llm_path = "flux2-klein-9b-uncensored-q4_k_m.gguf"
   sd_ctx_params.diffusion_model_path = NULL
   sd_ctx_params.vae_path = NULL
   ctx = new_sd_ctx(&params)

2. sd_encode_condition(ctx, prompt, loras, &cond_data, &cond_size)
   此时 cond_data 为内存中的 float 数组

3. free_sd_ctx(ctx)    // Qwen3 完全释放,显存归零

4. 新建 sd_ctx,仅传入 --diffusion-model(DiT)
   sd_ctx_params.diffusion_model_path = "flux-2-klein-9b-Q4_0.gguf"
   sd_ctx_params.llm_path = NULL
   sd_ctx_params.backend = "diffusion=vulkan1"
   ctx = new_sd_ctx(&params)

5. sd_sample(ctx, cond_data, cond_size, 512, 512, 4, 1.0, 42, &latent_data, &latent_size)
   此时 latent_data 存于内存中

6. free(cond_data)      // 释放 condition
   free_sd_ctx(ctx)     // DiT 完全释放,显存归零

7. 新建 sd_ctx,仅传入 --vae
   sd_ctx_params.vae_path = "flux2-vae.safetensors"
   sd_ctx_params.backend = "vae=vulkan1"
   ctx = new_sd_ctx(&params)

8. img = sd_decode_latent(ctx, latent_data, latent_size)
   stbi_write_png("output.png", img)

9. free(latent_data)
   free_sd_images(&img, 1)
   free_sd_ctx(ctx)     // VAE 释放

每步内存占用

步骤 加载模型 大小 VRAM RAM 备注
1 Qwen3 文本编码器 5.0 GB 0 GB 5.0 GB CPU 推理,不占显存
释放 --- --- 0 GB 0 GB 回归零
2 DiT (Q4_0) 5.3 GB ~3 GB 0 GB 权重 offload 至 GPU
释放 --- --- 0 GB 0 GB 回归零
3 VAE 164 MB <0.5 GB 0 GB 全量置于 GPU

总 VRAM 峰值:~3 GB(远低于 RX 580 的 8 GB 上限)


对 img2img / inpainting 的支持

在 Step 1 与 Step 2 之间插入一步即可:

text

复制代码
Step 1.5: VAE 编码原图

加载 VAE(可复用 Step 3 逻辑)
原图 → VAE encode → init_latent(内存 float[])

随后 Step 2 的 sd_sample 接收 init_latent + cond 作为输入

此额外步骤耗时约 3~5 秒(VAE 编码走 GPU)。


编译方法

bash

复制代码
git clone https://github.com/leejet/stable-diffusion.cpp
cd stable-diffusion.cpp
mkdir build && cd build
cmake .. -DGGML_VULKAN=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release

编译产出:

  • build/bin/Release/stable-diffusion.dll → 替换 D:\files\FLUX.2\

  • 自行编译调用方 exe,链接该 DLL


需要修改的源文件

文件 改动
include/stable-diffusion.h 增加 3 个 SD_API 函数声明
src/stable-diffusion.cpp 实现这 3 个函数,调用内部私有方法
src/stable-diffusion.h(内部) encode_first_stage()sample()decode_first_stage() 设为 public 或添加 public wrapper

与 ComfyUI / sd-cli 对比

ComfyUI sd-cli 一次加载 本方案
模型加载方式 全加载 全加载 分步加载
中间结果 显存常驻 显存常驻 内存 float array
VRAM 峰值 高(~6-7 GB) 中(~3 GB) 低(~3 GB)
img2img 额外开销 小(均在显存) 小(均在显存) 略大(多一次 VAE 加载)
跨 prompt 复用 condition 节点缓存 不支持 代码层手动缓存
控制粒度 节点级 CLI 参数级 函数调用级
相关推荐
whn19771 小时前
oracle 的ora-19633处理
数据库·oracle
小帅不太帅1 小时前
1.5M 参数的 OCR 模型,我把它跑进了浏览器
前端·javascript·ai编程
阿pin1 小时前
Java随笔-红黑树
java·python·算法·红黑树
小弥儿1 小时前
Firecrawl:把整个网页变成 AI 可查询的数据库
数据库·人工智能·学习
风骏时光牛马1 小时前
AI编程场景下故障根因分析与复盘总结
前端
我命由我123451 小时前
Jetpack Compose - @Preview 注解
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
swordbob1 小时前
mysql数据库,明明有索引为啥不用
数据库·mysql
为美好的生活献上中指1 小时前
Spring AI Advisor 深度实战:构建严谨的 AI Agent 拦截链
java·人工智能·spring·advisor·aiagent
ruofu332 小时前
如何让docker使用提前下载好的镜像,而不是在线拉取
java·spring cloud·docker