Cachinde commited on
Commit
263acfd
·
1 Parent(s): ce9dd03

feat: pausa inteligente do Llama em quota esgotada (fallback imediato)

Browse files
Files changed (2) hide show
  1. README.md +2 -0
  2. 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
  )