会。celery多进程默认各自加载模型并调用model.to('cuda'),导致显存重复分配;应预加载模型至worker启动阶段,配合torch.cuda.synchronize()和正确设备管理。

PyTorch模型在Celery中直接调用会爆显存?
会。Celery worker 默认用多进程(prefork)启动,每个子进程都独立加载一次模型——哪怕只有一份 torch.load(),GPU 显存也会被重复分配。常见现象是:第一个任务跑完,第二个直接报 cuda out of memory,哪怕模型本身只有 1.2GB。
根本原因不是模型大,而是多个进程各自调用 model.to('cuda'),触发了独立的 CUDA 上下文和显存分配。PyTorch 的 CUDA 上下文不跨进程共享,也没法“池化”。
- 别在
@task函数里做torch.load()+.to('cuda') - 别依赖 Celery 的
max_tasks_per_child=1来“释放显存”——它只回收进程,但 CUDA 上下文残留常导致下次启动仍卡住 - 显存没被真正释放,是因为 PyTorch 在子进程中未显式调用
torch.cuda.empty_cache(),且进程退出时 CUDA 上下文清理不可靠
Celery worker 进程启动前预加载模型到 GPU
把模型加载逻辑提到 worker 启动阶段,让每个 worker 进程只 load 一次、只占一份显存。关键是在 celery.py 或 worker 入口处完成初始化,而非在 task 函数内。
示例结构:
# celery_app.py
from celery import Celery
import torch
<p>app = Celery('tasks')
app.config_from_object('celeryconfig')</p><h1>✅ 在模块顶层加载模型(worker fork 前执行)</h1><p>model = None
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')</p><p>@app.on_after_configure.connect
def setup_model(sender, **kwargs):
global model
if model is None:
model = torch.jit.load('model.pt') # 或 torch.load + .eval()
model.to(device)
model.eval()</p><h1>⚠️ 必须加这句,否则首次推理可能隐式分配额外显存</h1><pre class="brush:php;toolbar:false;"><code> torch.cuda.synchronize()</code>
- 用
@app.on_after_configure.connect而非@app.task内部加载,确保只执行一次 - 必须调用
torch.cuda.synchronize(),避免异步核函数延迟占用显存 - 若用
torch.compile()或torch._dynamo,需额外加torch._dynamo.reset()防止多进程缓存冲突
单 worker 多并发任务仍卡住?检查 CUDA 流与推理线程安全
即使模型只加载一次,多个任务并发调用 model(input) 也可能出错:PyTorch 默认使用全局默认 CUDA 流,多线程/协程混用时易发生同步异常或梯度覆盖(哪怕只是推理)。
典型错误信息:RuntimeError: Expected all tensors to be on the same device 或静默返回错误结果。
- 所有输入
tensor必须显式调用.to(device),不能依赖模型所在设备自动广播 - 禁用
torch.set_grad_enabled(False)不够——要包裹在with torch.no_grad():块里,否则 Dynamo 可能漏优化 - 如用
torch.jit.script模型,确保输入 shape 一致;动态 batch 推理建议用torch.jit.trace+example_inputs固定 shape - 不要在 task 中启新线程调用模型——CUDA 不支持跨线程上下文切换;如需并发处理多个请求,请用 asyncio + 单线程队列,或改用
concurrent.futures.ProcessPoolExecutor配合模型序列化(代价高)
想复用 GPU 显存池?得绕开 Celery 的 prefork 模式
Celery 的 prefork 是为 CPU 密集型任务设计的,天生不适合 GPU 场景。所谓“显存池”,本质是让多个请求复用同一个模型实例和同一块显存,这就要求:只有一个长期存活的 GPU 进程负责推理,其他 worker 只做协议转发。
更可行的做法是剥离 Celery 的 GPU 职责:
- 用 FastAPI / Flask 启一个专用推理服务(绑定特定 GPU ID,如
CUDA_VISIBLE_DEVICES=0),启用torch.compile()和nvtx标记优化 - Celery task 改为 HTTP 调用该服务(用
httpx.AsyncClient),或走 Unix domain socket 提速 - 若必须保留在 Celery 生态,可改用
gevent+eventlet模式启动 worker(-P eventlet),再配合单进程内 asyncio +asyncio.Semaphore(1)强制串行调用模型——虽牺牲并发吞吐,但杜绝显存竞争
真正稳定的“池”,不是靠 Celery 配置,而是靠进程边界隔离:一个 GPU 对应一个长驻推理进程,其余全是无 GPU 的轻量 worker。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











