DAMOYOLO-S实战指南:GPU内存泄漏诊断与系统守护机制优化
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 内存泄漏的典型表现
当出现内存泄漏时,通常观察到以下现象:
- 内存持续增长:即使没有新的推理请求,GPU显存占用仍在缓慢增加
- 增长与请求量相关:处理图片越多,内存增长越快
- 最终导致OOM:当显存耗尽时,进程被系统终止,服务中断
- 重启后重复:重启服务后,同样的增长模式会再次出现
如果服务表现出这些特征,很可能存在内存泄漏问题。
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 核心问题与解决方案
- 内存泄漏排查:使用PyTorch内存分析工具定位泄漏点,重点关注张量释放和回调函数闭包问题
- 代码优化:实现安全的内存管理类,确保推理过程中正确释放GPU资源
- 进程守护:结合Supervisor和自定义监控脚本,实现服务的自动恢复
- 健康检查:建立完整的健康检查体系,包括服务状态、GPU内存、端口监听等多维度监控
6.2 部署建议
对于生产环境部署,我建议采用以下组合方案:
- 基础服务:使用优化后的
web_app_optimized.py作为主程序 - 进程管理:使用增强版的Supervisor配置
- 内存监控:部署
SystemGuardian类进行实时监控 - 健康检查:设置定时运行的
health_check.sh脚本 - 日志收集:配置完整的日志系统,便于问题排查
6.3 持续优化方向
即使解决了当前的内存泄漏问题,服务优化也是一个持续的过程:
- 定期更新:关注ModelScope上DAMOYOLO模型的更新,及时升级
- 性能监控:建立更完善的监控体系,记录响应时间、成功率等指标
- 负载测试:定期进行压力测试,了解服务的真实承载能力
- 备份策略:重要数据定期备份,确保故障时能快速恢复
6.4 最后的建议
在实际部署中,每个环境都有其特殊性。建议你先在测试环境中验证这些方案,观察一段时间确保稳定后再部署到生产环境。同时,保持良好的日志记录习惯,这样当问题出现时,你就能快速定位原因。
记住,稳定的AI服务不是一蹴而就的,而是通过持续监控、优化和迭代构建起来的。希望本文的实践经验能帮助你构建更可靠的DAMOYOLO-S服务。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。