Download src/aurora/dados/cache.py from PowerMachine/aurora-multimodal-ptbr: direct link, hf CLI and curl.
- Browser
- Download file 5.03 kB
-
https://huggingface.co/PowerMachine/aurora-multimodal-ptbr/resolve/main/src/aurora/dados/cache.py
- Command line
-
hf download hf://PowerMachine/aurora-multimodal-ptbr/src/aurora/dados/cache.py
-
curl -L -o cache.py https://huggingface.co/PowerMachine/aurora-multimodal-ptbr/resolve/main/src/aurora/dados/cache.py
5.03 kB
| # -*- coding: utf-8 -*- | |
| """Cache em RAM e disco com orçamento explícito (item 1: otimização de RAM | |
| e armazenamento). RAM: LRU por bytes estimados; Disco: arquivos .pt nomeados | |
| por hash, com índice JSON e limites de total. | |
| Política: prioridade RAM para tensores quentes (lote corrente), disco para | |
| blobs frios (embeddings pré-calculados, amostras do corpus). | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| import os | |
| from collections import OrderedDict | |
| import torch | |
| def _estimar_bytes(obj) -> int: | |
| if isinstance(obj, torch.Tensor): | |
| return obj.element_size() * obj.numel() | |
| if isinstance(obj, (list, tuple)): | |
| return sum(_estimar_bytes(o) for o in obj) + 64 | |
| if isinstance(obj, dict): | |
| return sum(_estimar_bytes(k) + _estimar_bytes(v) for k, v in obj.items()) + 64 | |
| return len(str(obj).encode("utf-8")) + 48 | |
| def _chave_hash(chave: str) -> str: | |
| return hashlib.sha1(chave.encode("utf-8")).hexdigest()[:16] | |
| class CacheRAM: | |
| """LRU com limite de bytes. Evict: menos-recentemente-usado primeiro.""" | |
| def __init__(self, limite_mb: float = 256.0): | |
| self.limite = int(limite_mb * 1024 * 1024) | |
| self._uso = 0 | |
| self._mapa: OrderedDict[str, tuple] = OrderedDict() # chave -> (valor, bytes) | |
| def get(self, chave: str): | |
| item = self._mapa.get(chave) | |
| if item is None: | |
| return None | |
| self._mapa.move_to_end(chave) # toca LRU | |
| return item[0] | |
| def put(self, chave: str, valor) -> None: | |
| if chave in self._mapa: | |
| self._uso -= self._mapa[chave][1] | |
| del self._mapa[chave] | |
| n_bytes = _estimar_bytes(valor) | |
| if n_bytes > self.limite: # indivisível e maior que tudo: descarta | |
| return | |
| while self._uso + n_bytes > self.limite and self._mapa: | |
| _, (_, b) = self._mapa.popitem(last=False) | |
| self._uso -= b | |
| self._mapa[chave] = (valor, n_bytes) | |
| self._uso += n_bytes | |
| def uso_mb(self) -> float: | |
| return self._uso / 1024 / 1024 | |
| def __len__(self): | |
| return len(self._mapa) | |
| class CacheDisco: | |
| """Blobs frios em disco com índice JSON e limite de armazenamento.""" | |
| def __init__(self, diretorio: str, limite_mb: float = 2048.0): | |
| self.dir = diretorio | |
| os.makedirs(diretorio, exist_ok=True) | |
| self.indice_path = os.path.join(diretorio, "indice.json") | |
| self.limite = limite_mb * 1024 * 1024 | |
| self.indice: dict[str, dict] = self._carregar_indice() | |
| def _carregar_indice(self) -> dict: | |
| if os.path.exists(self.indice_path): | |
| with open(self.indice_path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| return {} | |
| def _salvar_indice(self) -> None: | |
| with open(self.indice_path, "w", encoding="utf-8") as f: | |
| json.dump(self.indice, f, ensure_ascii=False) | |
| def _evict_se_necessario(self, novo_bytes: int) -> None: | |
| total = sum(m["bytes"] for m in self.indice.values()) | |
| while total + novo_bytes > self.limite and self.indice: | |
| chave_mais_velha = min(self.indice, key=lambda k: self.indice[k]["t"]) | |
| meta = self.indice.pop(chave_mais_velha) | |
| try: | |
| os.remove(meta["arquivo"]) | |
| except FileNotFoundError: | |
| pass | |
| total -= meta["bytes"] | |
| def put(self, chave: str, tensor: torch.Tensor) -> None: | |
| h = _chave_hash(chave) | |
| caminho = os.path.join(self.dir, h + ".pt") | |
| torch.save(tensor.cpu(), caminho) | |
| n_bytes = os.path.getsize(caminho) | |
| self._evict_se_necessario(n_bytes) | |
| self.indice[chave] = {"arquivo": caminho, "bytes": n_bytes, "t": os.path.getmtime(caminho)} | |
| self._salvar_indice() | |
| def get(self, chave: str) -> torch.Tensor | None: | |
| meta = self.indice.get(chave) | |
| if meta is None or not os.path.exists(meta["arquivo"]): | |
| return None | |
| return torch.load(meta["arquivo"], map_location="cpu", weights_only=True) | |
| class CacheDuplo: | |
| """Fachada: RAM primeiro, disco como respaldo. Métricas p/ telemetria.""" | |
| def __init__(self, limite_ram_mb: float, dir_disco: str, limite_disco_mb: float = 2048.0): | |
| self.ram = CacheRAM(limite_ram_mb) | |
| self.disco = CacheDisco(dir_disco, limite_disco_mb) | |
| self.estatisticas = {"hits_ram": 0, "hits_disco": 0, "misses": 0} | |
| def get(self, chave: str): | |
| v = self.ram.get(chave) | |
| if v is not None: | |
| self.estatisticas["hits_ram"] += 1 | |
| return v | |
| v = self.disco.get(chave) | |
| if v is not None: | |
| self.estatisticas["hits_disco"] += 1 | |
| self.ram.put(chave, v) # promove | |
| return v | |
| self.estatisticas["misses"] += 1 | |
| return None | |
| def put(self, chave: str, valor, persistir: bool = False) -> None: | |
| self.ram.put(chave, valor) | |
| if persistir and isinstance(valor, torch.Tensor): | |
| self.disco.put(chave, valor) | |