tracemalloc 只能定位 python 堆上由 cpython 分配的对象(如 list、dict、torch.tensor 实例本身),无法追踪 cuda 内存或底层 malloc 分配,适用于发现 python 层重复创建未释放对象的泄漏。

tracemalloc 能定位到哪一层的内存泄漏
tracemalloc 只能追踪 Python 堆上由 CPython 分配的对象(比如 list、dict、torch.Tensor 实例本身),但**不跟踪底层张量数据(tensor.data)的 CUDA 内存或底层 malloc 分配**。它适合查“谁在 Python 层不断 new 出新对象”,比如反复创建未释放的 model 实例、缓存了全量推理结果的 results_cache 全局列表,或者忘了 .detach() / .cpu() 就塞进字典的张量。
常见错误现象:tracemalloc.get_top_locations(10) 显示大量来自 inference.py:42 的 torch.Tensor 构造调用,但实际那行只是 output = model(x)——真正泄漏的是后续没清理的 all_outputs.append(output)。
- 启动前必须调用
tracemalloc.start(),且越早越好(比如if __name__ == "__main__":第一行) - 采样精度影响开销:
tracemalloc.start(256)比默认 1 更准,但内存占用略高;生产环境可临时设为16平衡精度与负载 - 别只看 top 1,泄漏常分散在多个相似路径,要关注重复出现的文件+行号组合
全局变量持有模型或张量时怎么安全清理
服务中常把 model 或 tokenizer 存成模块级变量,本意是复用,但若混入运行时状态(如 model.train() 后没重置)、或被意外赋值新对象(如热重载逻辑里 model = load_model(...)),旧模型不会自动 GC——因为仍有引用链(比如某处闭包捕获了旧 model,或日志装饰器悄悄存了 self.model)。
使用场景:FastAPI/Flask 服务启停之间需稳定复用模型,但每次请求又不能累积中间张量。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 显式切断引用:替换模型前,先对旧模型调用
del old_model,再torch.cuda.empty_cache()(仅限 GPU) - 避免在类实例或闭包里隐式持有:比如
def make_predictor(model): return lambda x: model(x),这个 lambda 会强引用model;改用functools.partial(predict, model=model)更可控 - 检查
gc.get_referrers(old_model),确认没有残留的 logger、cache dict、或 metrics collector 在引用它
PyTorch 张量没释放的典型表现和修复点
最隐蔽的泄漏不是模型本身,而是推理中产生的中间张量:比如 loss.backward() 后没 optimizer.zero_grad(),或 torch.no_grad() 块里仍调用了 .requires_grad_(True);又或者把 output.cpu().numpy() 结果存进全局 history 列表,但忘了 output 本身还连着计算图。
错误信息示例:RuntimeError: unable to open shared memory object ...(多进程 dataloader + 张量未 detach 导致 fork 失败),或 OutOfMemoryError 发生在第 1000 次请求而非首次。
- 所有送入 CPU/Numpy 的张量,必须先
.detach().cpu()(.clone().cpu()也行,但更慢) - 训练模式下,每个 batch 结束后务必
loss.backward()→optimizer.step()→optimizer.zero_grad(),缺一不可 - 用
with torch.no_grad():包裹纯推理逻辑,但注意:该上下文不自动释放张量,只是不建图;仍需手动del output或让它出作用域
排查时容易忽略的“假阳性”和干扰项
tracemalloc 报告里高频出现 torch/nn/modules/module.py 或 torch/autograd/__init__.py,不代表框架有 bug——大概率是你在 forward 中动态创建了 nn.Linear、或用 torch.jit.trace 生成了新 module 并缓存,却没管理生命周期。
另一个干扰:GIL 和垃圾回收时机。Python 不保证 del obj 立即释放内存,尤其当对象被循环引用(比如模型层互相持有 self.parent)时,得靠 gc.collect() 主动触发;但频繁调用 gc.collect() 会影响吞吐,建议只在关键节点(如请求结束 handler 里)条件性调用。
- 排除日志库干扰:某些 logger(如
loguru)会深度序列化异常对象,把整个模型参数张量拖进去;用logger.opt(diagnose=False, backtrace=False)降低序列化深度 - 确认是否真泄漏:对比
psutil.Process().memory_info().rss上升趋势 +tracemalloc分配峰值,二者同步增长才算实锤;单看tracemalloc不能断言 CUDA 内存泄漏 - GPU 张量泄漏必须用
torch.cuda.memory_allocated()单独监控,它和 Python 堆内存完全独立
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










