AI 模型占了 10G 显存,服务重启却没释放?

IT疑难杂症诊疗室 10w+人浏览 771人参与

在这里插入图片描述

阅读大约需 10 分钟


显存泄漏的噩梦

前端的坑踩得差不多了,小禾开始对付后端。

后端用的是 FastAPI,跑在一台有 24G 显存的 GPU 服务器上。本地开发一切正常,部署上去也没问题。

直到有一天,小禾在服务器上按了 Ctrl+C 停掉服务,准备更新代码。

# 停止服务
^C
INFO: Shutting down...

# 重新启动
$ python run.py

然后,服务就崩了:

torch.cuda.OutOfMemoryError: CUDA out of memory.
Tried to allocate 2.00 GiB (GPU 0; 24.00 GiB total capacity;
22.10 GiB already allocated; 1.43 GiB free)

小禾懵了:我明明停掉服务了,为什么显存还被占着?

他打开 nvidia-smi 一看:

$ nvidia-smi
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|=============================================================================|
|    0   N/A  N/A     12345      C   python                          10240MiB |
+-----------------------------------------------------------------------------+

进程还在!

原来 Ctrl+C 虽然停了 uvicorn,但 Python 进程没有完全退出,模型还在显存里躺着。

小禾只好 kill -9 强制杀掉进程,然后重新启动。

“这也太蠢了吧,每次重启都要手动杀进程?”


FastAPI 的生命周期

小禾查了查文档,发现 FastAPI 有一套生命周期管理机制。

应用启动
startup 事件
加载 AI 模型
初始化数据库连接
预热缓存
处理请求...
shutdown 事件
释放 AI 模型
关闭数据库连接
清理临时文件
应用关闭

原来 FastAPI 提供了 startupshutdown 两个事件钩子,可以在应用启动和关闭时执行特定逻辑。

小禾之前只写了加载模型的代码,但从来没写过释放模型的代码。

难怪显存不释放——根本没人告诉程序要释放。


两种写法

FastAPI 支持两种生命周期管理方式。

写法一:装饰器方式(传统写法)

from fastapi import FastAPI

app = FastAPI()

@app.on_event("startup")
async def startup_event():
    print("应用启动")
    # 加载模型

@app.on_event("shutdown")
async def shutdown_event():
    print("应用关闭")
    # 释放模型

写法二:lifespan 上下文管理器(推荐,FastAPI 0.93+)

from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行
    print("应用启动")
    yield
    # 关闭时执行
    print("应用关闭")

app = FastAPI(lifespan=lifespan)

小禾选了第二种写法,因为它更清晰——启动和关闭的逻辑写在一起,一眼就能看出配对关系。


完整的生命周期管理

小禾开始重构代码:

# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.services.model_manager import ModelManager
from app.core.config import settings
from app.core.logger import logger

# 全局模型管理器
model_manager = ModelManager()

@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期管理"""

    # ===== 启动阶段 =====
    logger.info(f"正在启动 {settings.PROJECT_NAME}")
    logger.info(f"运行环境: {settings.ENVIRONMENT}")
    logger.info(f"设备: {settings.DEVICE}")

    try:
        # 加载 AI 模型
        model_manager.load_model()
        logger.info(f"模型 ({settings.IMAGE_MODEL}) 加载完成")

    except Exception as e:
        logger.error(f"启动失败: {str(e)}")
        raise  # 启动失败要抛出异常,阻止服务启动

    # ===== 运行阶段 =====
    yield  # 服务运行中...

    # ===== 关闭阶段 =====
    logger.info("正在关闭服务...")

    try:
        # 释放模型资源
        model_manager.cleanup()
        logger.info("模型资源已释放")

    except Exception as e:
        logger.error(f"清理时出错: {str(e)}")

    logger.info("服务已关闭")

app = FastAPI(
    title=settings.PROJECT_NAME,
    lifespan=lifespan
)

关键在于 cleanup() 方法,它负责释放 GPU 显存。


模型清理的正确姿势

小禾最初写的清理方法是这样的:

def cleanup(self):
    del self.model
    self.model = None

结果发现:显存还是没释放。

原来 PyTorch 的显存管理比较特殊,单纯 del 是不够的。小禾查了资料,找到了正确的清理流程:

# app/services/model_manager.py
import gc
import torch

class ModelManager:
    """模型管理器"""

    def __init__(self):
        self.pipe = None
        self.device = settings.DEVICE

    def load_model(self):
        """加载模型到 GPU"""
        if self.pipe is not None:
            return  # 已加载,不重复加载

        logger.info("正在加载模型...")
        self.pipe = StableDiffusionXLPipeline.from_pretrained(
            settings.MODEL_PATH,
            torch_dtype=torch.float16,
        ).to(self.device)
        logger.info("模型加载完成")

    def cleanup(self):
        """释放模型资源"""
        if self.pipe is None:
            return

        logger.info("正在释放模型资源...")

        # 第一步:把模型从 GPU 移到 CPU
        self.pipe.to("cpu")

        # 第二步:删除引用
        del self.pipe
        self.pipe = None

        # 第三步:强制垃圾回收
        gc.collect()

        # 第四步:清理 CUDA 缓存
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
            torch.cuda.synchronize()  # 等待 GPU 操作完成

        logger.info("模型资源已释放")

四步缺一不可:

  1. 移到 CPU:先把张量从 GPU 搬走
  2. 删除引用:解除 Python 对象的引用
  3. 垃圾回收:触发 Python 的 GC
  4. 清理缓存:告诉 CUDA 释放已分配但未使用的显存

小禾测试了一下:

# 启动前
$ nvidia-smi
GPU Memory: 2GB / 24GB

# 服务运行中
$ nvidia-smi
GPU Memory: 12GB / 24GB

# Ctrl+C 停止服务
$ nvidia-smi
GPU Memory: 2GB / 24GB  # 释放了!

终于正常了。


但是,Ctrl+C 有时候不管用

小禾高兴了没两天,问题又来了。

有时候用 Ctrl+C 停止服务,shutdown 事件根本没触发,显存还是被占着。

他研究了一下,发现问题出在信号处理上。

当你按 Ctrl+C 时,系统发送的是 SIGINT 信号。正常情况下,uvicorn 会捕获这个信号,优雅地关闭服务。

但如果程序在执行某些阻塞操作(比如 AI 推理),可能来不及处理信号就被强制终止了。

更糟糕的是 kill -9,它发送的是 SIGKILL 信号,根本不给程序任何清理的机会。

小禾的解决方案是:注册信号处理器

import signal
import sys

def signal_handler(signum, frame):
    """处理终止信号"""
    logger.info(f"收到信号 {signum},准备清理...")

    try:
        model_manager.cleanup()
    except Exception as e:
        logger.error(f"清理失败: {e}")

    sys.exit(0)

# 注册信号处理器
signal.signal(signal.SIGINT, signal_handler)   # Ctrl+C
signal.signal(signal.SIGTERM, signal_handler)  # kill 命令

这样即使 FastAPI 的 shutdown 事件没触发,信号处理器也能兜底。


Uvicorn 的优雅关闭配置

小禾还发现 uvicorn 有个配置项可以控制优雅关闭的超时时间:

# run.py
import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "app.main:app",
        host="0.0.0.0",
        port=8000,
        reload=False,  # 生产环境不要开 reload
        workers=1,     # AI 应用一般单进程
        timeout_graceful_shutdown=30,  # 给 30 秒清理时间
    )

timeout_graceful_shutdown 参数告诉 uvicorn:收到停止信号后,等待最多 30 秒让程序完成清理工作。如果 30 秒内没清理完,才强制终止。


调试技巧:监控显存使用

为了方便调试,小禾写了个显存监控函数:

def log_gpu_memory():
    """打印当前 GPU 显存使用"""
    if torch.cuda.is_available():
        allocated = torch.cuda.memory_allocated() / 1024**3
        reserved = torch.cuda.memory_reserved() / 1024**3
        logger.info(f"GPU 显存: 已分配 {allocated:.2f}GB, 已预留 {reserved:.2f}GB")

在关键位置调用:

@asynccontextmanager
async def lifespan(app: FastAPI):
    log_gpu_memory()  # 启动前
    model_manager.load_model()
    log_gpu_memory()  # 加载后

    yield

    log_gpu_memory()  # 清理前
    model_manager.cleanup()
    log_gpu_memory()  # 清理后

输出类似这样:

GPU 显存: 已分配 0.00GB, 已预留 0.00GB
正在加载模型...
模型加载完成
GPU 显存: 已分配 9.87GB, 已预留 10.12GB
...
正在释放模型资源...
模型资源已释放
GPU 显存: 已分配 0.00GB, 已预留 0.00GB

一目了然。


健康检查端点

小禾还加了个健康检查端点,方便运维监控:

@app.get("/health")
async def health_check():
    gpu_info = {}
    if torch.cuda.is_available():
        gpu_info = {
            "allocated_gb": round(torch.cuda.memory_allocated() / 1024**3, 2),
            "reserved_gb": round(torch.cuda.memory_reserved() / 1024**3, 2),
        }

    return {
        "status": "healthy",
        "gpu": gpu_info,
        "model_loaded": model_manager.pipe is not None
    }

请求 /health 就能看到当前状态:

{
    "status": "healthy",
    "gpu": {
        "allocated_gb": 9.87,
        "reserved_gb": 10.12
    },
    "model_loaded": true
}

生命周期清单

经过这番折腾,小禾总结了一份清单:

阶段动作
startup加载模型、初始化连接、预热缓存
运行中处理请求、监控资源
shutdown释放模型、关闭连接、清理缓存

关键点

  1. 使用 lifespan 上下文管理器,启动和关闭逻辑配对
  2. cleanup() 要彻底:移到 CPU、删引用、gc、empty_cache
  3. 注册信号处理器应对异常关闭
  4. 设置 timeout_graceful_shutdown 给清理留时间
  5. 添加健康检查端点方便监控

小禾的感悟

显存不会说谎,
不释放就是不释放。

启动时加载,
关闭时释放,
听起来很简单,
做起来全是坑。

del 不等于释放,
Ctrl+C 不等于关闭,
kill -9 更是不讲武德。

lifespan 是救星,
信号处理是兜底,
四步清理要牢记:
移到 CPU、删引用、gc、empty_cache。

GPU 很贵,
显存更贵,
别让它们白白浪费。

小禾看着 nvidia-smi 显示的 2GB 占用,心情舒畅。

终于可以放心地重启服务了。


下一篇预告:接口报 500 了,日志里却啥都没有

找了三小时的 bug,原来是异常被吞了。

敬请期待。

<think>好的,我现在需要解决用户提出的关于LM Studio加载模型显存不足的问题。首先,我得回想一下用户提供的引用内容,看看有有相关的信息可以借鉴。引用[1]提到调整NVIDIA控制面板的设置来确保LM Studio调用GPU,而引用[2]则建议根据显存大小选择适合的模型,如果显存不够,模型会跑到CPU上,影响速度。 首先,显存不足通常有几个原因:模型太大、显存被其他程序用、或者设置不当。用户已经提到了选择适合显存模型,这可能是一个关键点。我需要先确认用户是否已经选择了合适的模型版本。比如,如果用户的显卡只有8GB显存,而加载了需要更大显存模型,自然会出现问题。这时候应该建议用户查看模型的不同版本,如量化版本(如4-bit或8-bit),这些版本通常用更少显存。 接下来,检查后台程序是否用了显存。用户可能需要关闭不必要的应用程序,释放显存资源。比如,游戏或其他GPU密集型应用可能在后台运行,导致显存不足。此外,使用任务管理器或NVIDIA的监控工具查看显存使用情况会有帮助。 然后,调整LM Studio的设置。引用[1]提到确保软件正确调用GPU,可能需要调整NVIDIA控制面板中的设置,将LM Studio的GPU优先级调高,或者强制使用独立显卡。同时,在LM Studio内部,是否有相关的配置选项,比如显存分配限制?需要建议用户检查软件设置,确保启用GPU加速,并且有错误地限制显存使用。 另外,更新驱动程序也是一个常见步骤。过时的显卡驱动可能导致显存管理不佳或兼容性问题,建议用户更新到最新版本。 如果显存确实无法满足需求,可能需要降低模型精度,比如使用量化模型,或者考虑扩展硬件,比如增加显卡或使用云服务。引用[2]提到显存不够会退回到CPU,但速度受影响,所以用户如果无法升级硬件,可能需要接受较慢的推理速度。 最后,确保LM Studio是最新版本,因为软件更新可能修复了显存管理的bug或优化了资源使用。同时,查阅官方文档或社区是否有其他用户遇到类似问题,可能会有特定的解决方案。 总结解决方案步骤:检查模型大小与显存匹配、关闭显存的程序、调整软件和驱动设置、使用量化模型、升级硬件或软件。需要将这些步骤条理清晰地呈现给用户,并引用提供的参考资料来支持建议。</think>### LM Studio加载模型显存不足解决方案 #### 1. **选择适配显存模型版本** - 查看显卡显存容量(如NVIDIA控制面板或`nvidia-smi`命令) - 根据显存大小选择模型量化版本(例如4-bit/8-bit量化模型)[^2] - 计公式:显存需求 ≈ 模型参数数量 × 每参数字节数(如FP16为2字节) - 示例:DeepSeek 1.5B模型FP16需3GB显存,4-bit量化仅需0.75GB #### 2. **释放显存资源** ```bash # Windows查看显存用 nvidia-smi # Linux查看显存用 watch -n 1 nvidia-smi ``` - 关闭显存的程序(如游戏、视频渲染软件) - 重启LM Studio释放残留显存 #### 3. **调整软件设置** - **NVIDIA控制面板**: 1. 管理3D设置 → 程序设置 → 添加LM Studio 2. 设置首选图形处理器为"高性能NVIDIA处理器" 3. 关闭"节能模式"[^1] - **LM Studio设置**: ```json // settings.json { "compute_type": "auto", // 改为"cuda"强制使用GPU "gpu_memory_utilization": 0.8 // 显存利用率限制 } ``` #### 4. **启用量化加载(示例代码)** ```python from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained( "deepseek-ai/deepseek-1.5b", load_in_4bit=True, # 4-bit量化 device_map="auto" ) ``` #### 5. **硬件升级建议** | 模型规模 | 最小显存需求 | 推荐显卡型号 | |----------|--------------|--------------| | 7B | 16GB | RTX 3090/4090| | 1.5B | 4GB | RTX 3060 | | 500M | 2GB | GTX 1650 | #### 6. **其他优化措施** - 更新NVIDIA驱动至最新版(支持更好的显存压缩) - 使用`--low-vram`启动参数(如有) - 在模型加载时添加`device_map="balanced"`参数
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员义拉冠

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值