File size: 10,516 Bytes
6ba6dc7
 
 
 
 
 
 
 
1c5ef91
6ba6dc7
 
 
 
 
1c5ef91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80a3544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ba6dc7
 
80a3544
 
 
 
1c5ef91
80a3544
 
 
 
 
 
 
 
 
 
 
 
1c5ef91
80a3544
 
1c5ef91
80a3544
 
1c5ef91
 
80a3544
 
 
8580232
 
 
 
 
 
1c5ef91
6ba6dc7
 
1c5ef91
 
 
 
 
6ba6dc7
 
 
 
 
 
 
1c5ef91
 
 
 
 
6ba6dc7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8580232
 
 
 
6ba6dc7
 
 
 
8580232
 
fa98542
 
6ba6dc7
 
 
 
 
 
8580232
6ba6dc7
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import psutil
import logging
from typing import Dict, Any, Optional

logger = logging.getLogger(__name__)

def get_memory_usage() -> Dict[str, Any]:
    """Récupère les informations sur l'utilisation de la mémoire système.
    Optimisé pour les environnements conteneurisés (Docker/Hugging Face Spaces).
    
    Returns:
        Dictionnaire contenant les informations de mémoire en Mo et pourcentages
    """
    try:
        # Vérifier d'abord si nous sommes dans un conteneur Docker avec des limites cgroup
        container_limit = None
        try:
            # Essayer de lire la limite de mémoire du cgroup (disponible dans les conteneurs)
            with open('/sys/fs/cgroup/memory/memory.limit_in_bytes', 'r') as f:
                container_limit = int(f.read().strip())
            # Si c'est proche de la valeur maximale d'un entier 64-bit, alors c'est illimité
            if container_limit > 2**60:  # Une valeur très proche de la max
                container_limit = None
        except (IOError, FileNotFoundError):
            # Essayer le chemin cgroup v2
            try:
                with open('/sys/fs/cgroup/memory.max', 'r') as f:
                    content = f.read().strip()
                    if content != 'max':
                        container_limit = int(content)
            except (IOError, FileNotFoundError):
                # Pas de limite cgroup lisible
                pass
                
        # Déterminer la mémoire totale pour Hugging Face Spaces (32GB)
        total_memory = 32 * (1024**3)  # 32 GB en octets fixe pour Hugging Face
        
        # Essayer une approche plus directe pour les conteneurs Docker
        docker_memory_used = None
        memory_source = "unknown"
        
        # Log du début de la tentative de lecture
        logger.info("Tentative de lecture des informations de mémoire Docker...")
        
        try:
            # Lire la mémoire utilisée directement depuis les stats cgroup v1
            cgroup_path = '/sys/fs/cgroup/memory/memory.usage_in_bytes'
            with open(cgroup_path, 'r') as f:
                docker_memory_used = int(f.read().strip())
                memory_source = f"cgroup v1 ({cgroup_path})"
                logger.info(f"Mémoire Docker lue depuis {memory_source}: {docker_memory_used / (1024*1024):.2f} Mo")
        except (IOError, FileNotFoundError) as e:
            logger.info(f"Impossible de lire depuis cgroup v1: {e}")
            try:
                # Essayer le chemin cgroup v2
                cgroup_path = '/sys/fs/cgroup/memory.current'
                with open(cgroup_path, 'r') as f:
                    docker_memory_used = int(f.read().strip())
                    memory_source = f"cgroup v2 ({cgroup_path})"
                    logger.info(f"Mémoire Docker lue depuis {memory_source}: {docker_memory_used / (1024*1024):.2f} Mo")
            except (IOError, FileNotFoundError) as e:
                logger.info(f"Impossible de lire depuis cgroup v2: {e}")
                # Utiliser le RSS du processus Python comme approximation
                docker_memory_used = psutil.Process().memory_info().rss
                memory_source = "RSS du processus Python"
                logger.info(f"Mémoire utilisée approximative depuis {memory_source}: {docker_memory_used / (1024*1024):.2f} Mo")
        
        # Si nous n'avons pas pu lire directement la mémoire du conteneur
        # Utiliser les informations du système mais avec une correction
        virtual_memory = psutil.virtual_memory()
        
        # Utiliser la valeur Docker si disponible, sinon utiliser une approximation
        # Dans Hugging Face Spaces, la vraie mémoire utilisée est généralement plus proche
        # de la mémoire RSS du processus Python
        logger.info(f"Informations psutil - total: {virtual_memory.total / (1024*1024):.2f} Mo, utilisé: {virtual_memory.used / (1024*1024):.2f} Mo")
        
        if docker_memory_used:
            used_memory = docker_memory_used
            logger.info(f"Utilisation de la mémoire depuis {memory_source}")
        else:
            # Approche conservatrice: utiliser 10% du total rapporté par psutil ou la mémoire RSS
            # Cela correspond mieux à ce que HF affiche typiquement
            process_rss = psutil.Process().memory_info().rss
            psutil_adjusted = virtual_memory.used * 0.10
            used_memory = max(process_rss, psutil_adjusted)
            
            logger.info(f"Utilisation de la mémoire estimée - RSS: {process_rss / (1024*1024):.2f} Mo, psutil ajusté: {psutil_adjusted / (1024*1024):.2f} Mo")
            memory_source = "estimation (max de RSS et psutil ajusté)"
        
        # S'assurer que la mémoire utilisée ne dépasse pas le total
        used_memory = min(used_memory, total_memory)
        
        # Calculer la mémoire disponible
        available_memory = total_memory - used_memory
        
        # Calculer les pourcentages
        percent_used = (used_memory / total_memory) * 100 if total_memory > 0 else 0
        
        # Log des résultats finaux
        import datetime
        current_time = datetime.datetime.now().strftime("%H:%M:%S")
        logger.info(f"[{current_time}] Mémoire totale: {total_memory / (1024*1024):.2f} Mo (fixée à 32 Go)")
        logger.info(f"[{current_time}] Mémoire utilisée: {used_memory / (1024*1024):.2f} Mo (source: {memory_source})")
        logger.info(f"[{current_time}] Mémoire disponible: {available_memory / (1024*1024):.2f} Mo")
        logger.info(f"[{current_time}] Pourcentage utilisé: {percent_used:.1f}%")
            
        # Convertir les bytes en Mo pour être plus lisible
        memory_info = {
            "total": round(total_memory / (1024 * 1024), 2),  # Mo
            "available": round(available_memory / (1024 * 1024), 2),  # Mo
            "used": round(used_memory / (1024 * 1024), 2),  # Mo
            "percent_used": round(percent_used, 1),  # %
            "percent_free": round(100 - percent_used, 1)  # %
        }
        
        return memory_info
    except Exception as e:
        logger.error(f"Failed to get memory usage: {e}")
        return {
            "error": f"Failed to get memory usage: {e}",
            "total": 32 * 1024,  # 32 GB par défaut en Mo
            "available": 16 * 1024,  # 16 GB par défaut en Mo
            "used": 16 * 1024,  # 16 GB par défaut en Mo
            "percent_used": 50.0,  # %
            "percent_free": 50.0  # %
        }

def get_loaded_models_memory(model_pipelines: Dict[str, Any]) -> Dict[str, Any]:
    """Estime la mémoire utilisée par les modèles chargés.
    
    Args:
        model_pipelines: Dictionnaire des modèles chargés
        
    Returns:
        Dictionnaire avec le nombre de modèles et l'utilisation mémoire estimée
    """
    try:
        models_count = len(model_pipelines)
        
        # Utilisation mémoire approximative basée sur le nombre de paramètres des modèles
        memory_usage = 0
        model_sizes = {}
        
        for model_id, model_data in model_pipelines.items():
            pipeline = model_data.get('pipeline')
            if pipeline is not None:
                # Calculer le nombre de paramètres
                param_count = sum(p.numel() for p in pipeline.parameters())
                # Estimer la mémoire (4 bytes par paramètre pour les float32)
                size_mb = round(param_count * 4 / (1024 * 1024), 2)
                
                model_sizes[model_id] = {
                    "parameters": param_count,
                    "size_mb": size_mb,
                    "name": model_data.get('metadata', {}).get('hf_filename', 'unknown')
                }
                
                memory_usage += size_mb
        
        return {
            "models_count": models_count,
            "total_memory_mb": round(memory_usage, 2),
            "models": model_sizes
        }
    except Exception as e:
        logger.error(f"Failed to calculate models memory usage: {e}")
        return {
            "error": str(e),
            "models_count": len(model_pipelines),
            "total_memory_mb": 0,
            "models": {}
        }

def get_memory_status(model_pipelines: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Récupère les informations de mémoire système et des modèles.
    
    Args:
        model_pipelines: Dictionnaire des modèles chargés (optionnel)
        
    Returns:
        Dictionnaire contenant les informations de mémoire
    """
    memory_info = {
        "system_memory": get_memory_usage(),
    }
    
    if model_pipelines is not None:
        memory_info["models_memory"] = get_loaded_models_memory(model_pipelines)
    
    return memory_info

def format_memory_status(memory_status: Dict[str, Any]) -> str:
    """Formate les informations de mémoire de manière élégante pour l'affichage.
    
    Args:
        memory_status: Dictionnaire contenant les informations de mémoire
        
    Returns:
        Chaîne formatée pour affichage
    """
    
    # Ajouter un horodatage pour vérifier le rafraîchissement
    import datetime
    current_time = datetime.datetime.now().strftime("%H:%M:%S")
    try:
        system_memory = memory_status.get("system_memory", {})
        models_memory = memory_status.get("models_memory", {})
        
        # Formater les infos système avec horodatage
        formatted_text = f"--- Mémoire Système (maj à {current_time}) ---\n"
        formatted_text += f"Total: {system_memory.get('total', 0):.1f} Mo \n"
        formatted_text += f"Utilisée: {system_memory.get('used', 0):.1f} Mo ({system_memory.get('percent_used', 0):.1f}%) \n"
        formatted_text += f"Disponible: {system_memory.get('available', 0):.1f} Mo ({system_memory.get('percent_free', 0):.1f}%)\n\n"
        
        # Ajouter les infos modèles
        models_count = models_memory.get("models_count", 0)
        total_model_mb = models_memory.get("total_memory_mb", 0)
        
        formatted_text += f"--- Mémoire des Modèles (maj à {current_time}) ---\n"
        formatted_text += f"Nombre de modèles chargés: {models_count}\n"
        formatted_text += f"Mémoire estimée: {total_model_mb:.1f} Mo"
        
        return formatted_text
    except Exception as e:
        logger.error(f"Error formatting memory status: {e}")
        return "Erreur lors de la récupération des informations de mémoire."