as-partionpeek / utils /cache_manager.py
TopgunM's picture
Upload 26 files
6c8c831 verified
Raw
History Blame Contribute Delete
9.57 kB
"""
Hugging Face キャッシュ管理ユーティリティ
"""
import os
import shutil
import logging
from typing import Optional, Dict, Any
from huggingface_hub import scan_cache_dir, HfApi
from pathlib import Path
class CacheManager:
"""Hugging Face キャッシュを管理するクラス"""
def __init__(self, cache_size_limit_gb: float = 40.0):
"""
キャッシュマネージャーを初期化
Args:
cache_size_limit_gb: キャッシュサイズの制限(GB)
"""
self.cache_size_limit_gb = cache_size_limit_gb
self.cache_size_limit_bytes = int(cache_size_limit_gb * 1024 * 1024 * 1024)
self.logger = logging.getLogger(__name__)
def get_cache_info(self) -> Dict[str, Any]:
"""
キャッシュの現在の状態を取得
Returns:
キャッシュ情報の辞書
"""
try:
cache_info = scan_cache_dir()
total_size = sum(repo.size_on_disk for repo in cache_info.repos)
# キャッシュパスを取得
cache_path = "N/A"
if cache_info.repos:
# 最初のリポジトリのパスから推測
first_repo = next(iter(cache_info.repos))
cache_path = str(first_repo.repo_path.parent.parent)
else:
# デフォルトのキャッシュパス
import os
cache_path = os.getenv("HF_HUB_CACHE",
os.path.expanduser("~/.cache/huggingface/hub"))
return {
"total_size_bytes": total_size,
"total_size_gb": total_size / (1024 * 1024 * 1024),
"num_repos": len(cache_info.repos),
"cache_path": cache_path,
"repos": [
{
"repo_id": repo.repo_id,
"size_on_disk": repo.size_on_disk,
"size_on_disk_gb": repo.size_on_disk / (1024 * 1024 * 1024),
"last_accessed": repo.last_accessed,
"last_modified": repo.last_modified
}
for repo in cache_info.repos
]
}
except Exception as e:
self.logger.error(f"キャッシュ情報の取得に失敗: {e}")
return {"error": str(e)}
def cleanup_cache(self, force: bool = False) -> Dict[str, Any]:
"""
キャッシュをクリーンアップ
Args:
force: 強制的にクリーンアップする場合はTrue
Returns:
クリーンアップ結果の辞書
"""
try:
cache_info = scan_cache_dir()
total_size = sum(repo.size_on_disk for repo in cache_info.repos)
# キャッシュサイズが制限を超えているか、強制実行の場合
if total_size > self.cache_size_limit_bytes or force:
# 最後のアクセス時刻が古い順にソート
sorted_repos = sorted(
cache_info.repos,
key=lambda x: x.last_accessed if x.last_accessed else 0
)
# 削除するリポジトリを選択
to_delete = []
freed_size = 0
for repo in sorted_repos:
if total_size - freed_size <= self.cache_size_limit_bytes * 0.8: # 80%まで削減
break
to_delete.append(repo)
freed_size += repo.size_on_disk
if to_delete:
# 削除実行
revision_hashes = []
for repo in to_delete:
revision_hashes.extend([rev.revision_hash for rev in repo.revisions])
delete_strategy = cache_info.delete_revisions(*revision_hashes)
expected_freed_size = delete_strategy.expected_freed_size
delete_strategy.execute()
self.logger.info(f"キャッシュクリーンアップ完了: {expected_freed_size / (1024 * 1024 * 1024):.2f}GB削除")
return {
"success": True,
"deleted_repos": len(to_delete),
"freed_size_bytes": expected_freed_size,
"freed_size_gb": expected_freed_size / (1024 * 1024 * 1024)
}
else:
return {
"success": True,
"deleted_repos": 0,
"freed_size_bytes": 0,
"freed_size_gb": 0,
"message": "削除するリポジトリがありません"
}
else:
return {
"success": True,
"deleted_repos": 0,
"freed_size_bytes": 0,
"freed_size_gb": 0,
"message": "キャッシュサイズが制限内です"
}
except Exception as e:
self.logger.error(f"キャッシュクリーンアップに失敗: {e}")
return {"success": False, "error": str(e)}
def get_disk_usage(self) -> Dict[str, Any]:
"""
ディスク使用状況を取得
Returns:
ディスク使用状況の辞書
"""
try:
# ワーキングディレクトリの使用状況
working_dir = Path.cwd()
working_dir_usage = shutil.disk_usage(working_dir)
# キャッシュディレクトリの使用状況
cache_info = self.get_cache_info()
return {
"working_directory": {
"path": str(working_dir),
"total_gb": working_dir_usage.total / (1024 * 1024 * 1024),
"used_gb": working_dir_usage.used / (1024 * 1024 * 1024),
"free_gb": working_dir_usage.free / (1024 * 1024 * 1024),
"usage_percent": (working_dir_usage.used / working_dir_usage.total) * 100
},
"cache": cache_info
}
except Exception as e:
self.logger.error(f"ディスク使用状況の取得に失敗: {e}")
return {"error": str(e)}
def should_cleanup(self) -> bool:
"""
クリーンアップが必要かどうかを判定
Returns:
クリーンアップが必要な場合True
"""
try:
disk_usage = self.get_disk_usage()
if "error" in disk_usage:
return False
# ディスク使用量が85%を超えている場合、またはキャッシュサイズが制限を超えている場合
usage_percent = disk_usage["working_directory"]["usage_percent"]
cache_size_gb = disk_usage["cache"].get("total_size_gb", 0)
return usage_percent > 85 or cache_size_gb > self.cache_size_limit_gb
except Exception as e:
self.logger.error(f"クリーンアップ判定に失敗: {e}")
return False
def emergency_cleanup(self) -> Dict[str, Any]:
"""
緊急時のキャッシュクリーンアップ(より積極的な削除)
Returns:
クリーンアップ結果の辞書
"""
try:
cache_info = scan_cache_dir()
# 全てのリポジトリを削除対象とする
revision_hashes = []
for repo in cache_info.repos:
revision_hashes.extend([rev.revision_hash for rev in repo.revisions])
if revision_hashes:
delete_strategy = cache_info.delete_revisions(*revision_hashes)
expected_freed_size = delete_strategy.expected_freed_size
delete_strategy.execute()
self.logger.warning(f"緊急キャッシュクリーンアップ完了: {expected_freed_size / (1024 * 1024 * 1024):.2f}GB削除")
return {
"success": True,
"deleted_repos": len(cache_info.repos),
"freed_size_bytes": expected_freed_size,
"freed_size_gb": expected_freed_size / (1024 * 1024 * 1024)
}
else:
return {
"success": True,
"deleted_repos": 0,
"freed_size_bytes": 0,
"freed_size_gb": 0,
"message": "削除するキャッシュがありません"
}
except Exception as e:
self.logger.error(f"緊急キャッシュクリーンアップに失敗: {e}")
return {"success": False, "error": str(e)}