乐于分享
好东西不私藏

AI 图片生成工具中的 Pipeline 池设计:让文生图和图生图同时跑

AI 图片生成工具中的 Pipeline 池设计:让文生图和图生图同时跑

AI 图片生成工具中的 Pipeline 池设计:让文生图和图生图同时跑

一次关于并发任务调度与资源管理的技术实践


一、引言:问题的起源

在开发 Stable Diffusion 桌面应用的过程中,我遇到了一个典型的问题:用户希望同时运行文生图和图生图任务,但每次尝试都会导致任务失败,调度器报出奇怪的索引越界错误。

1.1 现象

text

IndexError: index 21 is out of bounds for dimension 0 with size 21

文生图正在生成时,点击图生图,两个任务都挂了。控制台输出的错误信息指向同一个根源:两个任务共享了同一个 Pipeline 实例

1.2 为什么会这样?

GUI 应用和独立脚本有一个本质区别:

场景
Pipeline 实例
结果
独立脚本 A(文生图)
pipe_A = load_model()
✅ 正常工作
独立脚本 B(图生图)
pipe_B = load_model()
✅ 正常工作
GUI 同时跑两个任务
pipe = self.app.pipeline
(共享)
❌ 状态冲突

问题很明显:GUI 中所有任务共享同一个 Pipeline 实例。当文生图正在使用它时,图生图也尝试使用它,调度器的内部状态被覆盖,最终导致索引越界。


二、方案设计

2.1 需求分析

我需要一个机制,让不同任务使用独立的 Pipeline 实例,同时考虑内存限制:

需求
说明
独立实例
每个任务有自己的 Pipeline
资源可控
不能无限创建,否则内存爆炸
自动回收
任务完成后释放资源
任务隔离
相同模型 + 不同任务 = 不同实例
模型复用
相同模型 + 相同任务 = 复用实例

2.2 核心设计:Pipeline 池

我设计了一个 PipelinePool 类,核心思想是:

  1. 引用计数管理:每个 Pipeline 实例记录被多少个任务使用

  2. 最大实例数限制:防止内存溢出(默认 3 个)

  3. LRU 淘汰策略:达到上限时释放最少使用的实例

  4. 任务 ID 隔离:相同模型的不同任务使用不同实例

python

2.3 Key 生成策略

Pipeline 池通过 key 来区分不同的实例:

关键点task_id 让相同模型的不同任务使用不同的实例。

场景
Key
结果
文生图(任务 A)
model|None|txt2img_064536
创建实例 1
图生图(任务 B)
model|None|img2img_064550
创建实例 2
文生图(任务 C)
model|None|txt2img_064600
创建实例 3
文生图(任务 D)
model|None|txt2img_064620
超过上限,淘汰旧实例

三、代码实现

3.1 获取 Pipeline 实例

3.2 释放 Pipeline 实例

3.3 在各 Tab 中使用

文生图标签页的调用方式:

图生图标签页的调用方式类似,只是 task_id 不同:

python

task_id=f"img2img_{datetime.now().strftime('%H%M%S')}"

四、效果对比

4.1 修改前

4.2 修改后

text

4.3 关键指标

指标
修改前
修改后
并发支持
❌ 不支持
✅ 支持
调度器状态
互相覆盖
互相隔离
内存占用
固定 1 个实例
最多 3 个实例
任务隔离
❌ 共享
✅ 独立

五、踩坑记录

坑 1:调度器状态累积

Euler 调度器是有状态的。如果任务失败或取消,调度器可能处于"坏状态",后续任务继续报错。

解决方案:每次使用前重置调度器。

坑 2:引用计数泄漏

如果任务异常退出,finally 块可能不执行,导致 Pipeline 永远不会被释放。

解决方案:在 try-except-finally 中确保释放。

坑 3:Key 冲突

文生图和图生图如果使用相同的 key,会导致复用同一个实例。

解决方案:加入 task_id 区分。

六、总结与思考

6.1 设计原则

原则
实现
资源隔离
每个任务独立 Pipeline
资源复用
相同 Key 的任务复用实例
资源可控
最大实例数限制
自动回收
引用计数 + LRU 淘汰

6.2 适用场景

这个设计模式适用于所有多任务并发 + 资源有限的场景:

  • 多模型推理服务

  • 数据库连接池

  • 线程池

  • HTTP 连接池

6.3 后续优化方向

  1. 动态调整最大实例数:根据内存使用情况自动调整

  2. 预热机制:提前创建 Pipeline 实例

  3. 健康检查:定期检查 Pipeline 状态,自动恢复


写在最后

这次优化让我深刻体会到:看似简单的"让两个任务同时跑",背后涉及资源管理、并发控制、状态隔离等多个层面的设计考量。Pipeline 池虽然只增加了 200 多行代码,但解决了困扰已久的稳定性和并发问题。

如果你也在开发类似的 AI 工具,希望这个方案能给你一些启发。