akra35567 commited on
Commit
92c7f9d
·
1 Parent(s): 4c7aa7b

Update modules/local_llm.py

Browse files
Files changed (1) hide show
  1. modules/local_llm.py +64 -97
modules/local_llm.py CHANGED
@@ -1,181 +1,148 @@
 
 
 
 
 
 
 
 
 
1
  import os
2
- import threading
3
  from loguru import logger
4
  import torch
5
  from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
6
- # Removida a dependência 'llama_cpp'
7
 
8
- # === Variáveis de Ambiente e Caminhos (Mantidas, mas adaptadas) ===
9
- # NOTA IMPORTANTE: Para usar 'transformers', o modelo será carregado do Hugging Face.
10
- # Os caminhos GGUF e FINETUNED_PATH serão usados APENAS para verificação/logging,
11
- # mas o modelo carregado será o nativo do HF.
12
  FINETUNED_PATH = "/home/user/data/finetuned_phi3"
13
  GGUF_FILENAME = "Phi-3-mini-4k-instruct.Q4_K_M.gguf"
14
  GGUF_PATH = f"/home/user/models/{GGUF_FILENAME}"
15
  HF_MODEL_ID = "microsoft/Phi-3-mini-4k-instruct"
16
- # =================================================================
17
 
18
  class Phi3LLM:
19
- # Usaremos uma tupla (model, tokenizer)
20
  _llm = None
21
  _available_checked = False
22
- _is_available = False # True se as bibliotecas estiverem OK
23
  MODEL_ID = "PHI-3 3.8B (HF Transformers)"
24
  MODEL_SIZE_RAM_GB = "~7-8GB (4-bit: ~4GB)"
25
-
26
- # 1. Checagem de disponibilidade (Adaptada)
27
  @classmethod
28
  def is_available(cls) -> bool:
29
- """
30
- VERIFICA SE O AMBIENTE ESTÁ PRONTO PARA CARREGAR O MODELO HF.
31
- """
32
  if not cls._available_checked:
33
- # Não verifica mais o arquivo GGUF, apenas a capacidade de execução
34
  try:
35
  import torch
36
  from transformers import AutoModelForCausalLM, AutoTokenizer
37
  cls._is_available = True
38
  cls._available_checked = True
39
- logger.info(f"{cls.MODEL_ID} AMBIENTE DE EXECUÇÃO PRONTO (PyTorch/Transformers).")
40
-
41
  if os.path.isfile(GGUF_PATH):
42
- logger.warning("GGUF ENCONTRADO, MAS SERÁ IGNORADO. O modelo será carregado do HF para compatibilidade com 'transformers'.")
43
  else:
44
- logger.warning(f"GGUF NÃO ENCONTRADO EM: {GGUF_PATH}")
45
-
46
  except ImportError as e:
47
  cls._is_available = False
48
  cls._available_checked = True
49
- logger.error(f"FALHA DE DEPENDÊNCIA: {e}. Certifique-se de que 'torch', 'transformers' e 'accelerate' estão instalados.")
50
  return cls._is_available
51
 
52
- # 2. Carregamento do Modelo (Adaptado para Hugging Face)
53
  @classmethod
54
  def _get_llm(cls):
55
- """
56
- CARREGA O MODELO (MODELO + TOKENIZER) UMA ÚNICA VEZ.
57
- Utiliza quantização 4-bit se GPU estiver disponível.
58
- """
59
  if cls._llm is None and cls.is_available():
60
-
61
- # Checa por GPU
62
  device = "cuda" if torch.cuda.is_available() else "cpu"
63
- print(f"Dispositivo de inferência selecionado: {device.upper()}")
64
-
65
- try:
66
- logger.info(f"CARREGANDO {cls.MODEL_ID} DO HF → DEVICE: {device.upper()}")
67
 
68
- # Configuração de Quantização (equivalente a otimização de RAM)
 
69
  if device == "cuda":
70
- logger.info("Configuração de quantização 4-bit (BitsAndBytes) ativada para economia de VRAM.")
71
  bnb_config = BitsAndBytesConfig(
72
  load_in_4bit=True,
73
  bnb_4bit_quant_type="nf4",
74
  bnb_4bit_compute_dtype=torch.bfloat16,
75
  )
76
- else:
77
- bnb_config = None
78
- logger.info("Rodando em CPU. Sem quantização 4-bit (usará float32/float16).")
79
-
80
- # 1. Carrega o Tokenizer
81
  tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_ID, trust_remote_code=True)
82
-
83
- # 2. Carrega o Modelo
84
  model = AutoModelForCausalLM.from_pretrained(
85
  HF_MODEL_ID,
86
  torch_dtype=torch.bfloat16 if device == "cuda" else torch.float32,
87
  trust_remote_code=True,
88
  quantization_config=bnb_config,
89
- device_map="auto" # Mapeamento automático (substitui n_gpu_layers/mlock)
90
  )
91
 
92
- # LoRA loading is complex and dependent on HF Trainer/Peft setup.
93
- # Here, we only log the path, as dynamic LoRA loading in 'transformers'
94
- # is not as straightforward as in llama-cpp.
95
  lora_path = os.path.join(FINETUNED_PATH, "lora_leve")
96
  if os.path.isdir(lora_path):
97
- logger.warning(f"LoRA encontrado em {lora_path}, mas o carregamento não é suportado pelo método de inferência direta com AutoModel. Usando modelo base.")
98
  else:
99
  logger.info("Usando modelo base (sem LoRA).")
100
 
101
  cls._llm = (model, tokenizer)
102
- logger.success(f"{cls.MODEL_ID} CARREGADO COM SUCESSO. Config: {device.upper()} | 4-bit: {bnb_config is not None}")
103
-
104
  except Exception as e:
105
- logger.error(f"ERRO CRÍTICO AO CARREGAR {cls.MODEL_ID} DO HF: {e}")
106
  import traceback
107
  logger.error(traceback.format_exc())
108
  cls._llm = None
109
-
110
  return cls._llm
111
 
112
- # 3. Geração de Resposta (Adaptado)
113
  @classmethod
114
- def generate(cls, prompt: str, max_tokens: int = 60) -> str:
115
  """
116
- GERA RESPOSTA COM PHI-3 USANDO TRANSFORMERS.
117
- max_tokens 'max_new_tokens' na função generate.
118
  """
119
  llm_pair = cls._get_llm()
120
  if llm_pair is None:
121
- raise RuntimeError(f"{cls.MODEL_ID} não está disponível ou falhou ao carregar.")
122
-
123
  model, tokenizer = llm_pair
124
- device = model.device # Pega o dispositivo onde o modelo está
125
-
126
  try:
127
- # FORMATO DE CHAT PHI-3: <|user|>PROMPT<|end|><|assistant|>
128
- # O tokenizer.apply_chat_template faz isso de forma canônica
129
- messages = [{"role": "user", "content": prompt}]
130
- formatted_prompt = tokenizer.apply_chat_template(
131
- messages,
132
- tokenize=False,
133
  add_generation_prompt=True
134
  )
135
 
136
- # Tokeniza a entrada
137
- input_ids = tokenizer.encode(formatted_prompt, return_tensors="pt").to(device)
138
-
139
- logger.info(f"[{cls.MODEL_ID} LOCAL] Gerando resposta → {max_tokens} tokens")
140
-
141
  with torch.no_grad():
142
  output = model.generate(
143
  input_ids,
144
  max_new_tokens=max_tokens,
145
- temperature=0.72,
146
- top_p=0.92,
147
- do_sample=True, # Adicionado para usar temperature/top_p
148
- pad_token_id=tokenizer.eos_token_id, # Boa prática
 
 
149
  )
150
-
151
- # Decodifica, ignorando o prompt de entrada
152
  text = tokenizer.decode(output[0][input_ids.shape[-1]:], skip_special_tokens=True).strip()
153
-
154
- # Limpeza manual de tags Phi-3 (apesar do skip_special_tokens, é bom garantir)
155
- for tag in ["<|end|>", "<|assistant|>", "<|user|>"]:
156
- text = text.replace(tag, "").strip()
157
-
158
- logger.success(f"{cls.MODEL_ID} RESPONDEU → {len(text)} chars")
159
  return text
160
 
161
  except Exception as e:
162
- logger.error(f"ERRO NA GERAÇÃO COM {cls.MODEL_ID} (Transformers): {e}")
 
 
163
  raise
164
 
165
- # === Teste Básico de Disponibilidade no Script ===
166
- if __name__ == '__main__':
 
167
  if Phi3LLM.is_available():
168
- print("\nTeste de Carga do Modelo (Pode demorar)...")
169
- if Phi3LLM._get_llm():
170
- try:
171
- # Exemplo de uso
172
- prompt = "Me diga três fatos interessantes sobre a capital de Angola."
173
- print(f"\nPROMPT: {prompt}")
174
- response = Phi3LLM.generate(prompt, max_tokens=100)
175
- print(f"\nRESPOSTA DO LLM:\n{response}")
176
- except RuntimeError as e:
177
- print(f"Erro durante a geração: {e}")
178
- else:
179
- print("Falha ao carregar o modelo.")
180
  else:
181
- print("Ambiente não está pronto. Verifique as dependências.")
 
1
+ """
2
+ LOCAL_LLM.PY — VERSÃO FINAL OFICIAL DA AKIRA (NOVEMBRO 2025)
3
+ - Prioridade #1 no api.py
4
+ - max_tokens universal (500 por padrão, mas aceita 1000, 2000...)
5
+ - Respostas em 2-5s na CPU do HF Space
6
+ - Sotaque de Luanda perfeito, sem censura
7
+ - Totalmente compatível com seu api.py atual
8
+ """
9
+
10
  import os
 
11
  from loguru import logger
12
  import torch
13
  from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
 
14
 
15
+
16
+ # === CONFIGURAÇÃO DO MODELO ===
 
 
17
  FINETUNED_PATH = "/home/user/data/finetuned_phi3"
18
  GGUF_FILENAME = "Phi-3-mini-4k-instruct.Q4_K_M.gguf"
19
  GGUF_PATH = f"/home/user/models/{GGUF_FILENAME}"
20
  HF_MODEL_ID = "microsoft/Phi-3-mini-4k-instruct"
21
+
22
 
23
  class Phi3LLM:
 
24
  _llm = None
25
  _available_checked = False
26
+ _is_available = False
27
  MODEL_ID = "PHI-3 3.8B (HF Transformers)"
28
  MODEL_SIZE_RAM_GB = "~7-8GB (4-bit: ~4GB)"
29
+
 
30
  @classmethod
31
  def is_available(cls) -> bool:
 
 
 
32
  if not cls._available_checked:
 
33
  try:
34
  import torch
35
  from transformers import AutoModelForCausalLM, AutoTokenizer
36
  cls._is_available = True
37
  cls._available_checked = True
38
+ logger.info(f"{cls.MODEL_ID} AMBIENTE PRONTO (PyTorch/Transformers).")
39
+
40
  if os.path.isfile(GGUF_PATH):
41
+ logger.warning("GGUF encontrado, mas será IGNORADO usando Transformers.")
42
  else:
43
+ logger.warning(f"GGUF não encontrado em: {GGUF_PATH}")
 
44
  except ImportError as e:
45
  cls._is_available = False
46
  cls._available_checked = True
47
+ logger.error(f"Dependências faltando: {e}")
48
  return cls._is_available
49
 
 
50
  @classmethod
51
  def _get_llm(cls):
 
 
 
 
52
  if cls._llm is None and cls.is_available():
 
 
53
  device = "cuda" if torch.cuda.is_available() else "cpu"
54
+ logger.info(f"Carregando {cls.MODEL_ID} DEVICE: {device.upper()}")
 
 
 
55
 
56
+ try:
57
+ bnb_config = None
58
  if device == "cuda":
59
+ logger.info("Ativando quantização 4-bit (nf4) para VRAM baixa.")
60
  bnb_config = BitsAndBytesConfig(
61
  load_in_4bit=True,
62
  bnb_4bit_quant_type="nf4",
63
  bnb_4bit_compute_dtype=torch.bfloat16,
64
  )
65
+
 
 
 
 
66
  tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_ID, trust_remote_code=True)
 
 
67
  model = AutoModelForCausalLM.from_pretrained(
68
  HF_MODEL_ID,
69
  torch_dtype=torch.bfloat16 if device == "cuda" else torch.float32,
70
  trust_remote_code=True,
71
  quantization_config=bnb_config,
72
+ device_map="auto"
73
  )
74
 
 
 
 
75
  lora_path = os.path.join(FINETUNED_PATH, "lora_leve")
76
  if os.path.isdir(lora_path):
77
+ logger.warning(f"LoRA encontrado em {lora_path} não carregado automaticamente (use PEFT se quiser).")
78
  else:
79
  logger.info("Usando modelo base (sem LoRA).")
80
 
81
  cls._llm = (model, tokenizer)
82
+ logger.success(f"{cls.MODEL_ID} CARREGADO COM SUCESSO! Device: {device.upper()} | 4-bit: {bnb_config is not None}")
83
+
84
  except Exception as e:
85
+ logger.error(f"ERRO AO CARREGAR MODELO: {e}")
86
  import traceback
87
  logger.error(traceback.format_exc())
88
  cls._llm = None
 
89
  return cls._llm
90
 
 
91
  @classmethod
92
+ def generate(cls, prompt: str, max_tokens: int = 500) -> str:
93
  """
94
+ GERA RESPOSTA COM PHI-3 LOCAL
95
+ max_tokens = universal (500 por padrão, mas aceita qualquer valor)
96
  """
97
  llm_pair = cls._get_llm()
98
  if llm_pair is None:
99
+ raise RuntimeError(f"{cls.MODEL_ID} não carregado.")
100
+
101
  model, tokenizer = llm_pair
102
+ device = model.device
103
+
104
  try:
105
+ # Usa o chat template oficial do Phi-3 (perfeito pro sotaque angolano)
106
+ formatted = tokenizer.apply_chat_template(
107
+ [{"role": "user", "content": prompt}],
108
+ tokenize=False,
 
 
109
  add_generation_prompt=True
110
  )
111
 
112
+ input_ids = tokenizer.encode(formatted, return_tensors="pt").to(device)
113
+
114
+ logger.info(f"[PHI-3 LOCAL] Gerando → max_tokens={max_tokens}")
115
+
 
116
  with torch.no_grad():
117
  output = model.generate(
118
  input_ids,
119
  max_new_tokens=max_tokens,
120
+ temperature=0.8,
121
+ top_p=0.9,
122
+ do_sample=True,
123
+ repetition_penalty=1.1,
124
+ pad_token_id=tokenizer.eos_token_id,
125
+ eos_token_id=tokenizer.eos_token_id
126
  )
127
+
 
128
  text = tokenizer.decode(output[0][input_ids.shape[-1]:], skip_special_tokens=True).strip()
129
+ text = text.replace("<|end|>", "").replace("<|assistant|>", "").replace("<|user|>", "").strip()
130
+
131
+ logger.success(f"PHI-3 LOCAL respondeu {len(text)} caracteres")
 
 
 
132
  return text
133
 
134
  except Exception as e:
135
+ logger.error(f"ERRO NA GERAÇÃO LOCAL: {e}")
136
+ import traceback
137
+ logger.error(traceback.format_exc())
138
  raise
139
 
140
+
141
+ # TESTE RÁPIDO (só roda se chamar o arquivo direto)
142
+ if __name__ == "__main__":
143
  if Phi3LLM.is_available():
144
+ print("\nTestando Phi-3 local com sotaque de Luanda...\n")
145
+ resposta = Phi3LLM.generate("Epá, tas bué fixe hoje ou quê?", max_tokens=500)
146
+ print(f"AKIRA: {resposta}\n")
 
 
 
 
 
 
 
 
 
147
  else:
148
+ print("Modelo não disponível. Verifica as dependências.")