Cachinde commited on
Commit ·
263acfd
1
Parent(s): ce9dd03
feat: pausa inteligente do Llama em quota esgotada (fallback imediato)
Browse files- README.md +2 -0
- local_llm.py +28 -0
README.md
CHANGED
|
@@ -27,6 +27,8 @@ Cada nível que falha cai para o seguinte; `AI_PROVIDER` força um específico p
|
|
| 27 |
Provedores sem chave são saltados. Cerebras tem free tier generoso (1M tokens/dia,
|
| 28 |
sem cartão; contexto 8K no free): cria a chave em cloud.cerebras.ai e define
|
| 29 |
`CEREBRAS_API_KEY`. Modelos OpenRouter com `:free` também valem como degrau gratuito.
|
|
|
|
|
|
|
| 30 |
O Llama local (`local_llm.py`, `NousResearch/Meta-Llama-3-8B-Instruct` por defeito,
|
| 31 |
LoRA opcional via `LORA_ADAPTER_PATH` — pasta local ou id do Hub, ex. `user/meu-lora`,
|
| 32 |
com download automático e confirmação de adaptadores ativos no log) é carregado
|
|
|
|
| 27 |
Provedores sem chave são saltados. Cerebras tem free tier generoso (1M tokens/dia,
|
| 28 |
sem cartão; contexto 8K no free): cria a chave em cloud.cerebras.ai e define
|
| 29 |
`CEREBRAS_API_KEY`. Modelos OpenRouter com `:free` também valem como degrau gratuito.
|
| 30 |
+
Quota ZeroGPU esgotada? O local entra em pausa até ao reset (lê o "Try again in"
|
| 31 |
+
do erro) e o fallback fica imediato — sem esperar o scheduler recusar.
|
| 32 |
O Llama local (`local_llm.py`, `NousResearch/Meta-Llama-3-8B-Instruct` por defeito,
|
| 33 |
LoRA opcional via `LORA_ADAPTER_PATH` — pasta local ou id do Hub, ex. `user/meu-lora`,
|
| 34 |
com download automático e confirmação de adaptadores ativos no log) é carregado
|
local_llm.py
CHANGED
|
@@ -13,7 +13,9 @@ Regras ZeroGPU respeitadas:
|
|
| 13 |
import spaces # tem de ser o primeiro import com CUDA por perto
|
| 14 |
|
| 15 |
import os
|
|
|
|
| 16 |
import threading
|
|
|
|
| 17 |
|
| 18 |
import torch
|
| 19 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
@@ -29,6 +31,16 @@ _MODEL = None
|
|
| 29 |
_TOKENIZER = None
|
| 30 |
_LOAD_FAILED = ""
|
| 31 |
_LOAD_LOCK = threading.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
def _ensure_loaded() -> bool:
|
|
@@ -90,8 +102,24 @@ def _load_once() -> bool:
|
|
| 90 |
@spaces.GPU(duration=90)
|
| 91 |
def generate_local(messages: list) -> str:
|
| 92 |
"""Gera resposta com o Llama local. Falha rápido se o modelo não carregou."""
|
|
|
|
|
|
|
|
|
|
| 93 |
if not _ensure_loaded():
|
| 94 |
raise RuntimeError(f"Llama local indisponível: {_LOAD_FAILED}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
prompt = _TOKENIZER.apply_chat_template(
|
| 96 |
messages, tokenize=False, add_generation_prompt=True
|
| 97 |
)
|
|
|
|
| 13 |
import spaces # tem de ser o primeiro import com CUDA por perto
|
| 14 |
|
| 15 |
import os
|
| 16 |
+
import re
|
| 17 |
import threading
|
| 18 |
+
import time
|
| 19 |
|
| 20 |
import torch
|
| 21 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
|
|
| 31 |
_TOKENIZER = None
|
| 32 |
_LOAD_FAILED = ""
|
| 33 |
_LOAD_LOCK = threading.Lock()
|
| 34 |
+
_QUOTA_ATE = 0.0
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _segundos_quota(msg: str) -> int:
|
| 38 |
+
"""Extrai 'Try again in H:MM:SS' da mensagem de quota; 1h se ausente."""
|
| 39 |
+
m = re.search(r"Try again in (\d+):(\d+):(\d+)", msg or "")
|
| 40 |
+
if not m:
|
| 41 |
+
return 3600
|
| 42 |
+
h, mi, s = map(int, m.groups())
|
| 43 |
+
return h * 3600 + mi * 60 + s
|
| 44 |
|
| 45 |
|
| 46 |
def _ensure_loaded() -> bool:
|
|
|
|
| 102 |
@spaces.GPU(duration=90)
|
| 103 |
def generate_local(messages: list) -> str:
|
| 104 |
"""Gera resposta com o Llama local. Falha rápido se o modelo não carregou."""
|
| 105 |
+
global _QUOTA_ATE
|
| 106 |
+
if time.time() < _QUOTA_ATE:
|
| 107 |
+
raise RuntimeError("quota ZeroGPU em pausa (reset pendente)")
|
| 108 |
if not _ensure_loaded():
|
| 109 |
raise RuntimeError(f"Llama local indisponível: {_LOAD_FAILED}")
|
| 110 |
+
try:
|
| 111 |
+
return _gerar(messages)
|
| 112 |
+
except Exception as exc:
|
| 113 |
+
msg = str(exc)
|
| 114 |
+
if "quota" in msg.lower() or "exceeded" in msg.lower():
|
| 115 |
+
espera = _segundos_quota(msg)
|
| 116 |
+
_QUOTA_ATE = time.time() + espera
|
| 117 |
+
print(f"[llama] quota esgotada; local em pausa ~{espera}s. "
|
| 118 |
+
f"Fallback imediato até lá.", flush=True)
|
| 119 |
+
raise
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _gerar(messages: list) -> str:
|
| 123 |
prompt = _TOKENIZER.apply_chat_template(
|
| 124 |
messages, tokenize=False, add_generation_prompt=True
|
| 125 |
)
|