feat(cache): 添加 LRU 淘汰机制和缓存大小限制以优化内存使用
This commit is contained in:
@@ -437,7 +437,13 @@ class StyleLearner:
|
||||
|
||||
|
||||
class StyleLearnerManager:
|
||||
"""多聊天室表达风格学习管理器"""
|
||||
"""多聊天室表达风格学习管理器
|
||||
|
||||
添加 LRU 淘汰机制,限制最大活跃 learner 数量
|
||||
"""
|
||||
|
||||
# 🔧 最大活跃 learner 数量
|
||||
MAX_ACTIVE_LEARNERS = 50
|
||||
|
||||
def __init__(self, model_save_path: str = "data/expression/style_models"):
|
||||
"""
|
||||
@@ -445,6 +451,7 @@ class StyleLearnerManager:
|
||||
model_save_path: 模型保存路径
|
||||
"""
|
||||
self.learners: dict[str, StyleLearner] = {}
|
||||
self.learner_last_used: dict[str, float] = {} # 🔧 记录最后使用时间
|
||||
self.model_save_path = model_save_path
|
||||
|
||||
# 确保保存目录存在
|
||||
@@ -452,6 +459,30 @@ class StyleLearnerManager:
|
||||
|
||||
logger.debug(f"StyleLearnerManager初始化成功, 模型保存路径: {model_save_path}")
|
||||
|
||||
def _evict_if_needed(self) -> None:
|
||||
"""🔧 内存优化:如果超过最大数量,淘汰最久未使用的 learner"""
|
||||
if len(self.learners) < self.MAX_ACTIVE_LEARNERS:
|
||||
return
|
||||
|
||||
# 按最后使用时间排序,淘汰最旧的 20%
|
||||
evict_count = max(1, len(self.learners) // 5)
|
||||
sorted_by_time = sorted(
|
||||
self.learner_last_used.items(),
|
||||
key=lambda x: x[1]
|
||||
)
|
||||
|
||||
evicted = []
|
||||
for chat_id, last_used in sorted_by_time[:evict_count]:
|
||||
if chat_id in self.learners:
|
||||
# 先保存再淘汰
|
||||
self.learners[chat_id].save(self.model_save_path)
|
||||
del self.learners[chat_id]
|
||||
del self.learner_last_used[chat_id]
|
||||
evicted.append(chat_id)
|
||||
|
||||
if evicted:
|
||||
logger.info(f"StyleLearner LRU淘汰: 释放了 {len(evicted)} 个不活跃的学习器")
|
||||
|
||||
def get_learner(self, chat_id: str, model_config: dict | None = None) -> StyleLearner:
|
||||
"""
|
||||
获取或创建指定chat_id的学习器
|
||||
@@ -463,7 +494,13 @@ class StyleLearnerManager:
|
||||
Returns:
|
||||
StyleLearner实例
|
||||
"""
|
||||
# 🔧 更新最后使用时间
|
||||
self.learner_last_used[chat_id] = time.time()
|
||||
|
||||
if chat_id not in self.learners:
|
||||
# 🔧 检查是否需要淘汰
|
||||
self._evict_if_needed()
|
||||
|
||||
# 创建新的学习器
|
||||
learner = StyleLearner(chat_id, model_config)
|
||||
|
||||
|
||||
@@ -168,15 +168,22 @@ class ImageManager:
|
||||
image_bytes = base64.b64decode(image_base64)
|
||||
image_hash = hashlib.md5(image_bytes).hexdigest()
|
||||
|
||||
# 如果缓存命中,可以提前释放 image_bytes
|
||||
# 但如果需要保存表情包,则需要保留 image_bytes
|
||||
|
||||
# 2. 优先查询已注册表情的缓存(Emoji表)
|
||||
if full_description := await emoji_manager.get_emoji_description_by_hash(image_hash):
|
||||
logger.info("[缓存命中] 使用已注册表情包(Emoji表)的完整描述")
|
||||
del image_bytes # 缓存命中,不再需要
|
||||
del image_base64
|
||||
refined_part = full_description.split(" Keywords:")[0]
|
||||
return f"[表情包:{refined_part}]"
|
||||
|
||||
# 3. 查询通用图片描述缓存(ImageDescriptions表)
|
||||
if cached_description := await self._get_description_from_db(image_hash, "emoji"):
|
||||
logger.info("[缓存命中] 使用通用图片缓存(ImageDescriptions表)中的描述")
|
||||
del image_bytes # 缓存命中,不再需要
|
||||
del image_base64
|
||||
refined_part = cached_description.split(" Keywords:")[0]
|
||||
return f"[表情包:{refined_part}]"
|
||||
|
||||
@@ -209,7 +216,11 @@ class ImageManager:
|
||||
await self._save_description_to_db(image_hash, full_description, "emoji")
|
||||
logger.info(f"新生成的表情包描述已存入通用缓存 (Hash: {image_hash[:8]}...)")
|
||||
|
||||
# 6. 返回新生成的描述中用于显示的“精炼描述”部分
|
||||
# 内存优化:处理完成后主动释放大型二进制数据
|
||||
del image_bytes
|
||||
del image_base64
|
||||
|
||||
# 6. 返回新生成的描述中用于显示的"精炼描述"部分
|
||||
refined_part = full_description.split(" Keywords:")[0]
|
||||
return f"[表情包:{refined_part}]"
|
||||
|
||||
@@ -248,11 +259,17 @@ class ImageManager:
|
||||
existing_image = result.scalar()
|
||||
if existing_image and existing_image.description:
|
||||
logger.debug(f"[缓存命中] 使用Images表中的图片描述: {existing_image.description[:50]}...")
|
||||
# 缓存命中,释放 base64 和 image_bytes
|
||||
del image_bytes
|
||||
del image_base64
|
||||
return f"[图片:{existing_image.description}]"
|
||||
|
||||
# 3. 其次查询 ImageDescriptions 表缓存
|
||||
if cached_description := await self._get_description_from_db(image_hash, "image"):
|
||||
logger.debug(f"[缓存命中] 使用ImageDescriptions表中的描述: {cached_description[:50]}...")
|
||||
# 缓存命中,释放 base64 和 image_bytes
|
||||
del image_bytes
|
||||
del image_base64
|
||||
return f"[图片:{cached_description}]"
|
||||
|
||||
# 4. 如果都未命中,则同步调用VLM生成新描述
|
||||
@@ -301,6 +318,10 @@ class ImageManager:
|
||||
|
||||
logger.info(f"新生成的图片描述已存入缓存 (Hash: {image_hash[:8]}...)")
|
||||
|
||||
# 内存优化:处理完成后主动释放大型二进制数据
|
||||
del image_bytes
|
||||
del image_base64
|
||||
|
||||
return f"[图片:{description}]"
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user