PowerMachine's picture
AURORA: arquitetura multimodal com 8 SOMs, PDCA, W8A8 e matemática provada
094b8e7 verified
Raw History Blame Contribute Delete
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
@property
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)