akra35567 commited on
Commit
d5a79fd
·
1 Parent(s): 59f862e

Update modules/local_llm.py

Browse files
Files changed (1) hide show
  1. modules/local_llm.py +134 -61
modules/local_llm.py CHANGED
@@ -1,108 +1,181 @@
1
- from llama_cpp import Llama
2
  import os
3
- from loguru import logger
4
  import threading
 
 
 
 
5
 
6
- # CAMINHOS NO HF SPACES (CORRIGIDOS PARA O AMBIENTE ATUAL)
7
- FINETUNED_PATH = "/home/user/data/finetuned_phi3" # LoRA angolano
8
- # CORREÇÃO: O Dockerfile baixa para /home/user/models/
 
 
9
  GGUF_FILENAME = "Phi-3-mini-4k-instruct.Q4_K_M.gguf"
10
  GGUF_PATH = f"/home/user/models/{GGUF_FILENAME}"
 
 
11
 
12
  class Phi3LLM:
 
13
  _llm = None
14
  _available_checked = False
15
- _is_available = False
16
- MODEL_ID = "PHI-3 3.8B"
17
- MODEL_SIZE_RAM_GB = "~7-8GB"
18
-
 
19
  @classmethod
20
  def is_available(cls) -> bool:
21
  """
22
- VERIFICA SE O MODELO GGUF EXISTE CACHEIA O RESULTADO
23
- EVITA CHAMADAS REPETIDAS AO DISCO → MAIS RÁPIDO NO START
24
  """
25
  if not cls._available_checked:
26
- cls._is_available = os.path.isfile(GGUF_PATH)
27
- cls._available_checked = True
28
- if cls._is_available:
29
- logger.info(f"{cls.MODEL_ID} GGUF ENCONTRADO {GGUF_PATH}")
30
- else:
31
- logger.warning(f"{cls.MODEL_ID} GGUF NÃO ENCONTRADO! Caminho: {GGUF_PATH}")
32
- logger.warning("AKIRA VAI USAR MISTRAL/GEMINI COMO FALLBACK")
 
 
 
 
 
 
 
 
 
 
33
  return cls._is_available
34
 
 
35
  @classmethod
36
  def _get_llm(cls):
37
  """
38
- CARREGA O MODELO UMA ÚNICA VEZ → SINGLETON + mlock
39
- PHI-3 é menor (3.8B) Deve usar menos RAM e ser mais rápido.
40
  """
41
  if cls._llm is None and cls.is_available():
 
 
 
 
 
42
  try:
43
- logger.info(f"CARREGANDO {cls.MODEL_ID} GGUF Q4_K_MRAM: {cls.MODEL_SIZE_RAM_GB}, CPU MÍNIMA!")
44
- cls._llm = Llama(
45
- model_path=GGUF_PATH,
46
- n_ctx=4096, # Contexto gigante
47
- n_batch=512, # Batch grande = menos CPU
48
- n_threads=2, # Só 2 threads → CPU em paz!
49
- n_gpu_layers=0, # Tudo na RAM
50
- use_mlock=True, # Trava na RAM física → nunca swap!
51
- verbose=False,
52
- n_parts=1, # Carrega tudo de uma vez
53
- seed=-1,
54
- logits_all=True,
 
 
 
 
 
 
 
 
 
 
 
 
55
  )
56
- logger.success(f"{cls.MODEL_ID} GGUF CARREGADO COM SUCESSO → {cls.MODEL_SIZE_RAM_GB} RAM USADA!")
57
 
58
- # =================================================================
59
- # TENTA CARREGAR LORA SE EXISTIR (funcionalidade depende da versão llama-cpp-python)
60
- # =================================================================
61
  lora_path = os.path.join(FINETUNED_PATH, "lora_leve")
62
  if os.path.isdir(lora_path):
63
- logger.warning(f"LoRA encontrado em {lora_path}, mas 'load_lora' não é suportado pela versão do llama-cpp-python. A carregar modelo base.")
64
  else:
65
- logger.info("Usando modelo GGUF base (sem LoRA).")
66
 
 
 
 
67
  except Exception as e:
68
- logger.error(f"ERRO CRÍTICO AO CARREGAR {cls.MODEL_ID} GGUF: {e}")
69
  import traceback
70
  logger.error(traceback.format_exc())
71
- cls._llm = None # Garante que não tente de novo
 
72
  return cls._llm
73
 
 
74
  @classmethod
75
  def generate(cls, prompt: str, max_tokens: int = 60) -> str:
76
  """
77
- GERA RESPOSTA COM PHI-3 LOCAL
78
- max_tokens=60Deve ser mais rápido que o Hermes (7B)
79
  """
80
- llm = cls._get_llm()
81
- if llm is None:
82
- raise RuntimeError(f"{cls.MODEL_ID} não está disponível ou falhou ao carregar")
83
-
 
 
 
84
  try:
85
  # FORMATO DE CHAT PHI-3: <|user|>PROMPT<|end|><|assistant|>
86
- formatted_prompt = f"<|user|>\n{prompt}<|end|>\n<|assistant|>"
 
 
 
 
 
 
87
 
 
 
 
88
  logger.info(f"[{cls.MODEL_ID} LOCAL] Gerando resposta → {max_tokens} tokens")
89
- output = llm(
90
- formatted_prompt,
91
- max_tokens=max_tokens,
92
- temperature=0.72,
93
- top_p=0.92,
94
- stop=["<|end|>", "<|assistant|>", "<|user|>", "\n\n"],
95
- echo=False,
96
- )
97
- text = output["choices"][0]["text"].strip()
98
 
99
- # Limpeza adicional para garantir que não haja tags residuais
100
- if text.startswith("<|assistant|>"):
101
- text = text[len("<|assistant|>"):].lstrip()
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  logger.success(f"{cls.MODEL_ID} RESPONDEU → {len(text)} chars")
104
  return text
105
 
106
  except Exception as e:
107
- logger.error(f"ERRO NA GERAÇÃO COM {cls.MODEL_ID}: {e}")
108
- raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 HFDEVICE: {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.")