当前位置:首页 > 技术 > 正文内容

DAMOYOLO-S实战指南:GPU内存泄漏诊断与系统守护机制优化

访客 技术 2026年7月16日 2

DAMOYOLO-S实战指南:GPU内存泄漏诊断与系统守护机制优化

1. 问题背景:从服务中断到解决方案

某工作日下午,系统监控突然发出警报,显示DAMOYOLO-S目标检测服务异常终止。通过服务器检查发现,GPU显存已完全耗尽,而Python进程早已消失不见。这已经是本月第三次发生类似情况。

DAMOYOLO-S作为一款高性能通用目标检测模型,虽然在CSDN星图镜像中基于ModelScope的iic/cv_tinynas_object-detection_damoyolo提供了便捷部署方案,但长期运行时的稳定性问题,特别是GPU内存泄漏和进程意外退出,成为许多开发者面临的共同挑战。

本文将分享一套经过实战验证的解决方案,从内存泄漏的精准定位到进程的可靠守护,确保DAMOYOLO-S服务能够持续稳定运行。无论你是刚接触该镜像的新手,还是正被类似问题困扰的开发者,都能从中获得实用的解决思路。

2. DAMOYOLO-S内存使用模式分析

在开始排查之前,我们需要先了解DAMOYOLO-S运行时的内存行为特征。这类似于医生诊断前需要了解患者的正常生理状态。

2.1 正常内存使用模式

一个健康的DAMOYOLO-S服务,其GPU内存使用通常呈现以下特征:

  • 启动阶段:加载模型时会有一次性的内存峰值,通常占用1.5-2GB显存(具体取决于模型规模和批处理设置)
  • 稳定运行阶段:每次推理完成后,内存应基本回落到基线水平,不会持续累积
  • 空闲时段:即使没有推理请求,模型本身也会占用固定的基础内存

可以通过以下监控脚本观察这一模式:

#!/bin/bash
# gpu_monitor.sh
while true; do
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    memory_info=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits)
    echo "$timestamp - GPU显存占用: ${memory_info}MB"
    sleep 30
done

运行此脚本,让DAMOYOLO-S服务处理一些图片,观察内存变化。正常情况下,内存使用应在一定范围内波动,不会无限增长。

2.2 内存泄漏的典型表现

当出现内存泄漏时,通常观察到以下现象:

  1. 内存持续增长:即使没有新的推理请求,GPU显存占用仍在缓慢增加
  2. 增长与请求量相关:处理图片越多,内存增长越快
  3. 最终导致OOM:当显存耗尽时,进程被系统终止,服务中断
  4. 重启后重复:重启服务后,同样的增长模式会再次出现

如果服务表现出这些特征,很可能存在内存泄漏问题。

3. 内存泄漏源定位实战

定位内存泄漏需要系统性的排查方法。以下是经过验证的"三步定位法"。

3.1 第一步:基础监控与现象确认

首先,我们需要确认内存泄漏确实存在,并收集基础数据。

# 1. 实时监控GPU内存变化(每10秒记录一次)
watch -n 10 nvidia-smi

# 2. 同时监控进程的CPU和内存(RSS)使用情况
top -p $(pgrep -f "python.*damoyolo")

# 3. 记录监控数据到文件,便于分析
nvidia-smi --query-gpu=timestamp,memory.used,memory.total \
           --format=csv -l 10 > gpu_memory_log.csv

运行这些命令后,让服务处理一批测试图片(如50-100张),观察内存变化。如果每次推理后内存都有小幅增长且不释放,泄漏就基本确认了。

3.2 第二步:使用PyTorch内存分析工具

DAMOYOLO-S基于PyTorch,我们可以利用PyTorch自带的内存分析工具进行深入排查。

创建诊断脚本memory_debug.py

import torch
import gc
import gradio as gr
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
import os
import time

class ResourceMonitor:
    def __init__(self):
        self.initial_memory = torch.cuda.memory_allocated()
        
    def print_memory_stats(self, stage=""):
        allocated = torch.cuda.memory_allocated() / 1024**2  # MB
        cached = torch.cuda.memory_reserved() / 1024**2  # MB
        max_allocated = torch.cuda.max_memory_allocated() / 1024**2  # MB
        
        print(f"[{stage}] 当前分配: {allocated:.1f}MB | "
              f"缓存: {cached:.1f}MB | "
              f"峰值: {max_allocated:.1f}MB")
        
    def check_memory_leak(self, before, after, threshold_mb=50):
        increase = after - before
        if increase > threshold_mb:
            print(f"⚠️  疑似内存泄漏: 增加了 {increase:.1f}MB")
            return True
        return False

def test_inference_with_monitor(image_path, num_iterations=10):
    """带内存监控的推理测试"""
    monitor = ResourceMonitor()
    
    # 初始化管道(模拟服务启动)
    print("=== 初始化模型 ===")
    monitor.print_memory_stats("初始化前")
    
    pipe = pipeline(Tasks.image_object_detection, 
                   model='damoyolo_tinynasL20_T')
    
    monitor.print_memory_stats("初始化后")
    
    # 模拟多次推理
    import cv2
    image = cv2.imread(image_path)
    
    print(f"\n=== 开始 {num_iterations} 次推理测试 ===")
    
    for i in range(num_iterations):
        print(f"\n迭代 {i+1}/{num_iterations}")
        monitor.print_memory_stats("推理前")
        
        # 执行推理
        result = pipe(image)
        
        monitor.print_memory_stats("推理后")
        
        # 强制垃圾回收
        gc.collect()
        torch.cuda.empty_cache()
        
        monitor.print_memory_stats("清理后")
        
        # 等待一段时间,模拟真实请求间隔
        time.sleep(1)
    
    print("\n=== 测试完成 ===")
    final_memory = torch.cuda.memory_allocated() / 1024**2
    initial_memory = monitor.initial_memory / 1024**2
    monitor.check_memory_leak(initial_memory, final_memory)
    
    return pipe

if __name__ == "__main__":
    # 使用测试图片路径
    test_image = "/path/to/test/image.jpg"  # 替换为实际路径
    test_inference_with_monitor(test_image)

此脚本会详细记录每个阶段的内存变化,帮助确定泄漏发生在模型加载、推理还是结果处理阶段。

3.3 第三步:针对性排查常见泄漏点

根据经验,DAMOYOLO-S的内存泄漏通常出现在以下几个地方:

3.3.1 张量未释放

推理过程中创建的中间张量如果没有正确释放,会累积占用内存。

# 有问题的代码示例
def detect_objects(image):
    # 处理图像,创建多个中间张量
    processed_tensor = preprocess(image)  # 创建张量1
    features = extract_features(processed_tensor)  # 创建张量2
    predictions = model(features)  # 创建张量3
    
    # 如果没有显式释放,这些张量可能一直留在内存中
    return predictions

# 改进版本
def detect_objects_fixed(image):
    processed_tensor = preprocess(image)
    
    # 使用with torch.no_grad()避免计算图积累
    with torch.no_grad():
        features = extract_features(processed_tensor)
        predictions = model(features)
    
    # 将结果转移到CPU,释放GPU内存
    cpu_predictions = predictions.cpu()
    
    # 显式删除不再需要的张量
    del processed_tensor, features, predictions
    
    # 强制垃圾回收和缓存清理
    import gc
    gc.collect()
    torch.cuda.empty_cache()
    
    return cpu_predictions

3.3.2 Gradio回调函数闭包问题

如果Web界面使用了Gradio,回调函数中可能意外捕获了大型对象。

# 潜在问题:回调函数捕获了大型对象
def create_leaky_callback(model):
    large_data = load_large_dataset()  # 大型数据被捕获
    
    def callback(input_image):
        # 这个闭包会一直引用large_data
        result = model(input_image)
        return process_result(result, large_data)
    
    return callback

# 改进:避免在闭包中捕获不必要的大型对象
def create_fixed_callback(model):
    # 只捕获必要的模型引用
    def callback(input_image):
        result = model(input_image)
        # 按需加载或传递必要数据
        needed_data = load_needed_data_slice()
        return process_result(result, needed_data)
    
    return callback

3.3.3 模型多次重复加载

有时代码逻辑问题会导致模型被重复加载。

# 检查是否有多个模型实例
ps aux | grep python | grep damoyolo

# 检查Python进程中的模型引用数量
# 可以添加日志来记录模型加载次数
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

_model_instance = None

def get_model():
    global _model_instance
    if _model_instance is None:
        logger.info("首次加载模型...")
        _model_instance = pipeline(Tasks.image_object_detection, 
                                  model='damoyolo_tinynasL20_T')
    else:
        logger.info("使用已加载的模型实例")
    return _model_instance

4. 内存泄漏解决方案

找到泄漏点后,我们来实施具体的修复方案。

4.1 修复方案一:完善内存管理代码

基于上面的分析,我们可以优化DAMOYOLO-S的推理代码:

# safe_damoyolo_predictor.py
import torch
import gc
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
import numpy as np

class SecureObjectDetector:
    def __init__(self, model_path='damoyolo_tinynasL20_T'):
        """安全的内存管理版本"""
        self.model_path = model_path
        self.pipeline = None
        self._init_model()
        
    def _init_model(self):
        """初始化模型,确保只加载一次"""
        if self.pipeline is not None:
            return
            
        print(f"初始化模型: {self.model_path}")
        
        # 记录初始内存
        initial_mem = torch.cuda.memory_allocated() / 1024**2
        
        # 加载模型
        self.pipeline = pipeline(
            Tasks.image_object_detection,
            model=self.model_path,
            device='cuda:0' if torch.cuda.is_available() else 'cpu'
        )
        
        # 记录加载后内存
        after_mem = torch.cuda.memory_allocated() / 1024**2
        print(f"模型加载占用: {after_mem - initial_mem:.1f}MB")
        
        # 立即清理缓存
        self._cleanup_memory()
    
    def _cleanup_memory(self):
        """清理GPU内存"""
        gc.collect()
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
    
    def predict_safe(self, image):
        """安全的预测方法,确保内存正确释放"""
        if self.pipeline is None:
            self._init_model()
        
        # 使用no_grad避免计算图积累
        with torch.no_grad():
            try:
                result = self.pipeline(image)
                
                # 将结果转移到CPU
                if isinstance(result, dict):
                    for key in result:
                        if torch.is_tensor(result[key]):
                            result[key] = result[key].cpu()
                elif torch.is_tensor(result):
                    result = result.cpu()
                
                return result
                
            finally:
                # 无论是否异常,都执行清理
                self._cleanup_memory()
    
    def batch_predict_safe(self, images, batch_size=4):
        """安全的批量预测"""
        results = []
        
        for i in range(0, len(images), batch_size):
            batch = images[i:i+batch_size]
            print(f"处理批次 {i//batch_size + 1}/{(len(images)+batch_size-1)//batch_size}")
            
            batch_results = []
            for img in batch:
                result = self.predict_safe(img)
                batch_results.append(result)
            
            results.extend(batch_results)
            
            # 每个批次后都清理
            self._cleanup_memory()
        
        return results

# 使用示例
if __name__ == "__main__":
    import cv2
    
    # 初始化预测器
    detector = SecureObjectDetector()
    
    # 加载测试图片
    test_image = cv2.imread("test.jpg")
    
    # 单次预测
    print("=== 单次预测测试 ===")
    result = detector.predict_safe(test_image)
    print(f"检测到 {len(result['detections'])} 个目标")
    
    # 多次预测测试(模拟长时间运行)
    print("\n=== 多次预测测试(模拟泄漏) ===")
    for i in range(20):
        result = detector.predict_safe(test_image)
        current_mem = torch.cuda.memory_allocated() / 1024**2
        print(f"迭代 {i+1}: 内存使用 {current_mem:.1f}MB")

4.2 修复方案二:添加内存监控和自动重启

对于生产环境,我们可以添加自动监控和恢复机制:

# system_guard.py
import psutil
import torch
import time
import logging
import subprocess
import signal
import os
from threading import Thread

class SystemGuardian:
    def __init__(self, 
                 process_name="damoyolo",
                 gpu_threshold_mb=6000,  # 6GB阈值
                 check_interval=30):     # 30秒检查一次
        self.process_name = process_name
        self.gpu_threshold = gpu_threshold_mb
        self.check_interval = check_interval
        self.running = False
        
        # 设置日志
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('/var/log/damoyolo_system_guard.log'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger("SystemGuardian")
    
    def get_gpu_memory_usage(self):
        """获取GPU内存使用情况"""
        try:
            # 使用nvidia-smi获取内存信息
            result = subprocess.run(
                ['nvidia-smi', '--query-gpu=memory.used', 
                 '--format=csv,noheader,nounits'],
                capture_output=True,
                text=True,
                check=True
            )
            memory_mb = float(result.stdout.strip())
            return memory_mb
        except Exception as e:
            self.logger.error(f"获取GPU内存失败: {e}")
            return 0
    
    def get_process_memory(self, pid):
        """获取进程内存使用"""
        try:
            process = psutil.Process(pid)
            memory_mb = process.memory_info().rss / 1024 / 1024
            return memory_mb
        except psutil.NoSuchProcess:
            return 0
    
    def find_detector_pid(self):
        """查找目标检测进程PID"""
        try:
            # 查找包含damoyolo的Python进程
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    cmdline = proc.info['cmdline']
                    if cmdline and any('damoyolo' in str(arg).lower() 
                                      for arg in cmdline):
                        return proc.info['pid']
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue
        except Exception as e:
            self.logger.error(f"查找进程失败: {e}")
        return None
    
    def restart_service(self):
        """重启目标检测服务"""
        self.logger.warning("检测到内存超限,准备重启服务...")
        
        pid = self.find_detector_pid()
        if pid:
            try:
                # 先尝试优雅终止
                os.kill(pid, signal.SIGTERM)
                time.sleep(5)
                
                # 如果还在运行,强制终止
                if psutil.pid_exists(pid):
                    os.kill(pid, signal.SIGKILL)
                    time.sleep(2)
            except Exception as e:
                self.logger.error(f"终止进程失败: {e}")
        
        # 通过supervisor重启服务
        try:
            subprocess.run(['supervisorctl', 'restart', 'damoyolo'], 
                          check=True)
            self.logger.info("服务重启命令已发送")
            
            # 等待服务启动
            time.sleep(10)
            
            # 检查服务状态
            result = subprocess.run(
                ['supervisorctl', 'status', 'damoyolo'],
                capture_output=True,
                text=True,
                check=True
            )
            self.logger.info(f"服务状态: {result.stdout}")
            
        except subprocess.CalledProcessError as e:
            self.logger.error(f"重启服务失败: {e}")
    
    def monitor_loop(self):
        """监控循环"""
        self.logger.info("系统守护进程启动")
        
        while self.running:
            try:
                # 获取GPU内存使用
                gpu_memory = self.get_gpu_memory_usage()
                
                # 查找进程并获取内存
                pid = self.find_detector_pid()
                process_memory = self.get_process_memory(pid) if pid else 0
                
                self.logger.info(
                    f"监控状态 - GPU内存: {gpu_memory:.1f}MB, "
                    f"进程内存: {process_memory:.1f}MB, "
                    f"PID: {pid if pid else '未找到'}"
                )
                
                # 检查是否超过阈值
                if gpu_memory > self.gpu_threshold:
                    self.logger.warning(
                        f"GPU内存超限: {gpu_memory:.1f}MB > "
                        f"{self.gpu_threshold}MB"
                    )
                    self.restart_service()
                
                # 检查进程是否存在
                if pid is None:
                    self.logger.warning("目标检测进程不存在,尝试重启...")
                    self.restart_service()
                
            except Exception as e:
                self.logger.error(f"监控循环异常: {e}")
            
            # 等待下一次检查
            time.sleep(self.check_interval)
    
    def start(self):
        """启动监控"""
        self.running = True
        self.thread = Thread(target=self.monitor_loop, daemon=True)
        self.thread.start()
        self.logger.info("系统守护进程已启动")
    
    def stop(self):
        """停止监控"""
        self.running = False
        if self.thread:
            self.thread.join(timeout=5)
        self.logger.info("系统守护进程已停止")

# 使用示例
if __name__ == "__main__":
    guardian = SystemGuardian(
        process_name="damoyolo",
        gpu_threshold_mb=6000,  # 6GB
        check_interval=30       # 30秒检查一次
    )
    
    try:
        guardian.start()
        # 主程序继续运行...
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        guardian.stop()

4.3 修复方案三:优化Gradio应用内存使用

如果DAMOYOLO-S通过Gradio提供Web服务,可以这样优化:

# web_app_optimized.py
import gradio as gr
import numpy as np
import torch
import gc
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from functools import lru_cache
import threading

# 全局锁,防止并发问题
model_lock = threading.Lock()

@lru_cache(maxsize=1)
def get_model_pipeline():
    """使用缓存,确保模型只加载一次"""
    print("加载模型(仅第一次调用时执行)...")
    pipe = pipeline(
        Tasks.image_object_detection,
        model='damoyolo_tinynasL20_T',
        device='cuda:0' if torch.cuda.is_available() else 'cpu'
    )
    return pipe

def cleanup_memory():
    """清理GPU内存"""
    gc.collect()
    if torch.cuda.is_available():
        torch.cuda.empty_cache()

def predict_with_memory_management(image, score_threshold=0.3):
    """带内存管理的预测函数"""
    # 获取模型(缓存确保单例)
    pipe = get_model_pipeline()
    
    # 使用锁防止并发访问
    with model_lock:
        try:
            # 设置阈值
            pipe.model.score_thr = score_threshold
            
            # 执行推理
            with torch.no_grad():
                result = pipe(image)
            
            # 提取并处理结果
            detections = []
            if 'detections' in result:
                for det in result['detections']:
                    # 转换为Python原生类型,释放Tensor
                    detection = {
                        'label': str(det['label']),
                        'score': float(det['score']),
                        'box': [float(x) for x in det['box']]
                    }
                    detections.append(detection)
            
            # 构建返回结果
            output = {
                'threshold': score_threshold,
                'count': len(detections),
                'detections': detections
            }
            
            return output
            
        finally:
            # 无论是否异常,都清理内存
            cleanup_memory()

def create_interface():
    """创建Gradio界面"""
    with gr.Blocks(title="DAMOYOLO-S 目标检测(内存优化版)") as demo:
        gr.Markdown("# 🎯 DAMOYOLO-S 通用目标检测")
        gr.Markdown("上传图片进行目标检测,支持80个COCO类别")
        
        with gr.Row():
            with gr.Column():
                input_image = gr.Image(
                    label="输入图片",
                    type="numpy",
                    height=400
                )
                
                score_slider = gr.Slider(
                    minimum=0.01,
                    maximum=1.0,
                    value=0.3,
                    step=0.01,
                    label="置信度阈值"
                )
                
                detect_btn = gr.Button("开始检测", variant="primary")
                
                gr.Markdown("### 使用提示")
                gr.Markdown("""
                - 支持PNG、JPG、JPEG格式
                - 置信度阈值越低,检测到的目标越多(可能包含误检)
                - 首次加载模型需要较长时间,请耐心等待
                """)
            
            with gr.Column():
                output_image = gr.Image(
                    label="检测结果",
                    height=400
                )
                
                output_json = gr.JSON(
                    label="检测结果(JSON)",
                    height=200
                )
        
        # 绑定事件
        detect_btn.click(
            fn=predict_with_memory_management,
            inputs=[input_image, score_slider],
            outputs=[output_image, output_json]
        )
        
        # 示例
        gr.Examples(
            examples=[
                ["example1.jpg", 0.3],
                ["example2.jpg", 0.25],
                ["example3.jpg", 0.4]
            ],
            inputs=[input_image, score_slider],
            label="示例图片"
        )
    
    return demo

if __name__ == "__main__":
    # 启动前清理内存
    cleanup_memory()
    
    # 创建并启动应用
    demo = create_interface()
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False,
        debug=False  # 生产环境关闭debug模式
    )

5. 进程守护与自动恢复方案

解决了内存泄漏问题后,我们还需要确保服务在异常退出时能够自动恢复。这里提供两种方案。

5.1 方案一:使用Supervisor增强配置

Supervisor是CSDN星图镜像默认的进程管理工具,我们可以优化其配置:

; /etc/supervisor/conf.d/damoyolo_enhanced.conf
[program:damoyolo]
; 基本配置
command=/usr/bin/python3 /root/workspace/web_app_optimized.py
directory=/root/workspace
user=root
autostart=true
autorestart=true
startretries=10

; 输出日志
stdout_logfile=/var/log/damoyolo.out.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stderr_logfile=/var/log/damoyolo.err.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=5

; 进程管理
stopasgroup=true
killasgroup=true

; 资源限制(防止内存泄漏失控)
priority=999
startsecs=10
stopwaitsecs=30

; 环境变量
environment=PYTHONUNBUFFERED="1",CUDA_VISIBLE_DEVICES="0"

; 监控配置
[eventlistener:damoyolo_watcher]
command=/usr/bin/python3 /root/workspace/system_guard.py
events=PROCESS_STATE,TICK_60
autostart=true
autorestart=true
stdout_logfile=/var/log/damoyolo_watcher.log
stderr_logfile=/var/log/damoyolo_watcher.err.log

然后更新Supervisor配置:

# 重新加载配置
supervisorctl reread
supervisorctl update

# 启动增强版服务
supervisorctl start damoyolo
supervisorctl start damoyolo_watcher

# 查看状态
supervisorctl status

5.2 方案二:使用Systemd服务(备用方案)

如果Supervisor不能满足需求,可以考虑使用Systemd:

# /etc/systemd/system/damoyolo.service
[Unit]
Description=DAMOYOLO-S Object Detection Service
After=network.target
Wants=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root/workspace
Environment=PYTHONUNBUFFERED=1
Environment=CUDA_VISIBLE_DEVICES=0
ExecStart=/usr/bin/python3 /root/workspace/web_app_optimized.py
Restart=always
RestartSec=10
StartLimitInterval=0

# 资源限制
MemoryLimit=8G
CPUQuota=200%

# 监控和清理
ExecStartPre=/bin/bash -c 'echo "启动前清理内存" && /usr/bin/python3 -c "import torch; torch.cuda.empty_cache()"'
ExecStopPost=/bin/bash -c 'echo "停止后清理内存" && /usr/bin/python3 -c "import torch; torch.cuda.empty_cache()"'

# 日志配置
StandardOutput=journal
StandardError=journal
SyslogIdentifier=damoyolo

[Install]
WantedBy=multi-user.target

使用Systemd管理:

# 启用服务
sudo systemctl daemon-reload
sudo systemctl enable damoyolo.service
sudo systemctl start damoyolo.service

# 查看状态
sudo systemctl status damoyolo.service

# 查看日志
sudo journalctl -u damoyolo.service -f

5.3 方案三:完整的健康检查脚本

创建一个综合的健康检查脚本,定期检查服务状态:

#!/bin/bash
# /root/scripts/health_check.sh

LOG_FILE="/var/log/damoyolo_health.log"
SERVICE_NAME="damoyolo"
MAX_GPU_MEMORY=6000  # 6GB
MAX_RESTARTS=5
RESTART_COUNT_FILE="/tmp/damoyolo_restart_count"

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 获取重启次数
get_restart_count() {
    if [ -f "$RESTART_COUNT_FILE" ]; then
        cat "$RESTART_COUNT_FILE"
    else
        echo "0"
    fi
}

# 增加重启次数
increment_restart_count() {
    local count=$(get_restart_count)
    echo $((count + 1)) > "$RESTART_COUNT_FILE"
}

# 重置重启次数
reset_restart_count() {
    echo "0" > "$RESTART_COUNT_FILE"
}

# 检查服务状态
check_service_status() {
    if supervisorctl status "$SERVICE_NAME" | grep -q "RUNNING"; then
        log "服务状态: 运行中"
        return 0
    else
        log "服务状态: 未运行"
        return 1
    fi
}

# 检查GPU内存
check_gpu_memory() {
    local memory_used=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits)
    log "GPU内存使用: ${memory_used}MB"
    
    if [ "$memory_used" -gt "$MAX_GPU_MEMORY" ]; then
        log "警告: GPU内存超过阈值 (${memory_used}MB > ${MAX_GPU_MEMORY}MB)"
        return 1
    fi
    return 0
}

# 检查端口
check_port() {
    if ss -ltnp | grep -q ":7860"; then
        log "端口检查: 7860端口正常监听"
        return 0
    else
        log "端口检查: 7860端口未监听"
        return 1
    fi
}

# 重启服务
restart_service() {
    local restart_count=$(get_restart_count)
    
    if [ "$restart_count" -ge "$MAX_RESTARTS" ]; then
        log "错误: 重启次数超过最大限制 ($MAX_RESTARTS次),停止重启"
        return 1
    fi
    
    log "尝试重启服务 (第$((restart_count + 1))次)..."
    
    # 先清理GPU内存
    python3 -c "import torch; torch.cuda.empty_cache()" 2>/dev/null || true
    
    # 重启服务
    supervisorctl restart "$SERVICE_NAME"
    
    # 等待服务启动
    sleep 10
    
    # 检查是否重启成功
    if check_service_status; then
        log "服务重启成功"
        increment_restart_count
        return 0
    else
        log "服务重启失败"
        return 1
    fi
}

# 主检查逻辑
main_check() {
    log "=== 开始健康检查 ==="
    
    local need_restart=false
    local failure_reason=""
    
    # 检查服务状态
    if ! check_service_status; then
        need_restart=true
        failure_reason="服务未运行"
    fi
    
    # 检查GPU内存
    if ! check_gpu_memory; then
        need_restart=true
        failure_reason="GPU内存超限"
    fi
    
    # 检查端口
    if ! check_port; then
        need_restart=true
        failure_reason="端口未监听"
    fi
    
    # 执行重启
    if [ "$need_restart" = true ]; then
        log "需要重启服务,原因: $failure_reason"
        restart_service
    else
        log "所有检查通过,服务正常"
        # 如果连续运行超过1小时,重置重启计数
        local uptime=$(ps -o etimes= -p $(pgrep -f "python.*damoyolo" | head -1) 2>/dev/null || echo "0")
        if [ "$uptime" -gt 3600 ]; then
            reset_restart_count
            log "服务稳定运行超过1小时,重置重启计数"
        fi
    fi
    
    log "=== 健康检查完成 ===\n"
}

# 设置定时任务
setup_cron() {
    log "设置定时健康检查(每5分钟一次)"
    (crontab -l 2>/dev/null | grep -v "health_check.sh"; echo "*/5 * * * * /bin/bash /root/scripts/health_check.sh") | crontab -
}

# 主程序
case "$1" in
    "check")
        main_check
        ;;
    "setup")
        setup_cron
        ;;
    "status")
        check_service_status
        check_gpu_memory
        check_port
        echo "重启次数: $(get_restart_count)/$MAX_RESTARTS"
        ;;
    *)
        echo "用法: $0 {check|setup|status}"
        exit 1
        ;;
esac

设置定时检查:

# 给脚本执行权限
chmod +x /root/scripts/health_check.sh

# 运行一次检查
/root/scripts/health_check.sh check

# 设置定时任务
/root/scripts/health_check.sh setup

# 查看当前状态
/root/scripts/health_check.sh status

6. 总结:构建稳定的DAMOYOLO-S服务

通过本文的实践,你应该已经掌握了DAMOYOLO-S服务内存泄漏排查和进程守护的完整方案。让我们回顾一下关键要点:

6.1 核心问题与解决方案

  1. 内存泄漏排查:使用PyTorch内存分析工具定位泄漏点,重点关注张量释放和回调函数闭包问题
  2. 代码优化:实现安全的内存管理类,确保推理过程中正确释放GPU资源
  3. 进程守护:结合Supervisor和自定义监控脚本,实现服务的自动恢复
  4. 健康检查:建立完整的健康检查体系,包括服务状态、GPU内存、端口监听等多维度监控

6.2 部署建议

对于生产环境部署,我建议采用以下组合方案:

  1. 基础服务:使用优化后的web_app_optimized.py作为主程序
  2. 进程管理:使用增强版的Supervisor配置
  3. 内存监控:部署SystemGuardian类进行实时监控
  4. 健康检查:设置定时运行的health_check.sh脚本
  5. 日志收集:配置完整的日志系统,便于问题排查

6.3 持续优化方向

即使解决了当前的内存泄漏问题,服务优化也是一个持续的过程:

  1. 定期更新:关注ModelScope上DAMOYOLO模型的更新,及时升级
  2. 性能监控:建立更完善的监控体系,记录响应时间、成功率等指标
  3. 负载测试:定期进行压力测试,了解服务的真实承载能力
  4. 备份策略:重要数据定期备份,确保故障时能快速恢复

6.4 最后的建议

在实际部署中,每个环境都有其特殊性。建议你先在测试环境中验证这些方案,观察一段时间确保稳定后再部署到生产环境。同时,保持良好的日志记录习惯,这样当问题出现时,你就能快速定位原因。

记住,稳定的AI服务不是一蹴而就的,而是通过持续监控、优化和迭代构建起来的。希望本文的实践经验能帮助你构建更可靠的DAMOYOLO-S服务。

获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。