File size: 6,409 Bytes
5c0ad68
73830e7
 
5c0ad68
 
867e38e
 
9ade958
58221cb
867e38e
58221cb
867e38e
9ade958
dba7f50
 
58221cb
dba7f50
867e38e
5c0ad68
 
9ade958
5c0ad68
73830e7
 
dba7f50
73830e7
 
 
 
2def258
dba7f50
2def258
dba7f50
5e7b481
dba7f50
5c0ad68
 
9ade958
fa376db
dba7f50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73830e7
fa376db
2def258
5e7b481
dba7f50
5e7b481
 
dba7f50
5e7b481
 
 
 
5c0ad68
5e7b481
5c0ad68
dba7f50
5c0ad68
 
2def258
dba7f50
5e7b481
dba7f50
 
 
 
5c0ad68
dba7f50
 
 
5e7b481
dba7f50
2def258
 
dba7f50
 
 
 
 
 
 
 
 
 
 
 
2def258
 
dba7f50
2def258
 
 
dba7f50
 
 
 
2def258
dba7f50
 
2def258
dba7f50
 
 
 
 
 
 
 
 
 
 
2def258
dba7f50
2def258
dba7f50
 
 
 
5e7b481
dba7f50
5c0ad68
73830e7
fa376db
73830e7
 
 
 
5e7b481
fa376db
58221cb
73830e7
2def258
5e7b481
73830e7
 
 
dba7f50
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# modules/treinamento.py
import threading
import time
import json
import os
import shutil
import subprocess
from loguru import logger
from sentence_transformers import SentenceTransformer
from peft import LoraConfig
from .database import Database
from .local_llm import _get_llm

# EMBEDDING MODEL TOP PRA ANGOLANO + PORTUGUÊS
EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
embedding_model = SentenceTransformer(EMBEDDING_MODEL)

FINETUNED_PATH = "/home/user/data/finetuned_hermes"
os.makedirs(FINETUNED_PATH, exist_ok=True)

def gerar_embedding(text: str):
    return embedding_model.encode(text, convert_to_numpy=True)

class Treinamento:
    def __init__(self, db: Database, interval_hours: int = 3):
        self.db = db
        self.interval_hours = interval_hours
        self._thread = None
        self._running = False
        self.llm = _get_llm()
        
        if self.llm:
            logger.info("TREINAMENTO CONECTADO AO TINYLLAMA 1.1B GGUF (LORA COMPATÍVEL!)")
        else:
            logger.warning("LLM não carregado → finetune desativado")

    def registrar_interacao(self, usuario, mensagem, resposta, numero='', is_reply=False, mensagem_original=''):
        self.db.salvar_mensagem(usuario, mensagem, resposta, numero, is_reply, mensagem_original)
        self._aprender_roleplay(numero, mensagem, resposta)
        self._salvar_embedding(usuario, mensagem, resposta)

    def _salvar_embedding(self, usuario, msg, resp):
        """SALVA EMBEDDINGS PRA BUSCA SEMÂNTICA (NLP AVANÇADO)"""
        try:
            msg_emb = gerar_embedding(msg)
            resp_emb = gerar_embedding(resp)
            path = f"{FINETUNED_PATH}/embeddings.jsonl"
            entry = {
                "usuario": usuario,
                "msg": msg,
                "resp": resp,
                "msg_emb": msg_emb.tolist(),
                "resp_emb": resp_emb.tolist(),
                "timestamp": time.time()
            }
            with open(path, "a", encoding="utf-8") as f:
                json.dump(entry, f, ensure_ascii=False)
                f.write("\n")
            logger.debug(f"Embedding salvo: {msg[:30]}...")
        except Exception as e:
            logger.error(f"Erro ao salvar embedding: {e}")

    def _aprender_roleplay(self, numero: str, msg: str, resp: str):
        if not numero or not self.llm:
            return
        dataset_path = f"{FINETUNED_PATH}/roleplay_tinyllama.jsonl"
        entry = {
            "messages": [
                {"role": "system", "content": "Tu és Akira, kota fixe de Luanda. Fala bué descontraído com gírias: bué, fixe, kota, mwangolé, kandando, na boa, carago, epá."},
                {"role": "user", "content": msg},
                {"role": "assistant", "content": resp}
            ]
        }
        with open(dataset_path, "a", encoding="utf-8") as f:
            json.dump(entry, f, ensure_ascii=False)
            f.write("\n")
        logger.debug(f"Roleplay TinyLlama salvo: {msg[:30]}... → {resp[:30]}...")

    def train_once(self):
        if not self.llm:
            logger.warning("TinyLlama não carregado. Pulando finetune.")
            return
        
        dataset_path = f"{FINETUNED_PATH}/roleplay_tinyllama.jsonl"
        if not os.path.exists(dataset_path) or os.path.getsize(dataset_path) < 500:
            logger.info("Poucos dados pro TinyLlama. Esperando mais kandandos...")
            return

        logger.info("INICIANDO FINETUNE LORA TURBO PRO TINYLLAMA 1.1B (3 SEGUNDOS + SOTAQUE LUANDA MELHORADO!)")
        
        try:
            lora_path = f"{FINETUNED_PATH}/temp_lora_tiny"
            os.makedirs(lora_path, exist_ok=True)

            # LoRA CONFIG OTIMIZADA PRA TINYLLAMA
            config = LoraConfig(
                r=64,
                lora_alpha=128,
                target_modules=["q_proj", "v_proj"],
                lora_dropout=0.05,
                bias="none",
                task_type="CAUSAL_LM"
            )
            config.save_pretrained(lora_path)

            # COMANDO CORRETO PRA TREINAR LORA COM llama.cpp (FUNCIONA COM TINYLLAMA!)
            cmd = [
                "python", "-m", "llama_cpp.convert",
                "--model", "/home/user/models/tinyllama-1.1b-chat-v1.0.Q5_K_M.gguf",
                "--outfile", f"{lora_path}/adapter_model.bin",
                "--lora-out", lora_path,
                "--train", dataset_path,
                "--epochs", "2",
                "--lora-r", "64",
                "--lora-alpha", "128",
                "--batch", "8",
                "--threads", "4",
                "--ctx", "2048",
                "--adam-iter", "100"
            ]

            logger.info("Rodando finetune LoRA TinyLlama...")
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)

            if result.returncode == 0 and os.path.exists(f"{lora_path}/adapter_model.bin"):
                # SUBSTITUI O LORA ATUAL
                shutil.move(f"{lora_path}/adapter_model.bin", f"{FINETUNED_PATH}/adapter_model.bin")
                shutil.move(f"{lora_path}/adapter_config.json", f"{FINETUNED_PATH}/adapter_config.json")
                logger.info("LORA ANGOLANO TURBO ATUALIZADO COM SUCESSO! SOTAQUE DE LUANDA NÍVEL MÁXIMO!")
                
                # LIMPA DATASET
                open(dataset_path, 'w').close()
                logger.info("Dataset limpo. TinyLlama tá mais angolano que nunca!")
            else:
                logger.error(f"Erro no finetune: {result.stderr[:500]}")

        except subprocess.TimeoutExpired:
            logger.warning("Finetune demorou → pulando (HF Spaces tem limite)")
        except Exception as e:
            logger.error(f"Erro crítico no finetune TinyLlama: {e}")

    def _run_loop(self):
        interval = self.interval_hours * 3600
        while self._running:
            try:
                self.train_once()
            except Exception as e:
                logger.exception(f"Erro no loop de treino: {e}")
            time.sleep(interval)

    def start_periodic_training(self):
        if self._running or not self.llm:
            return
        self._running = True
        self._thread = threading.Thread(target=self._run_loop, daemon=True)
        self._thread.start()
        logger.info(f"TREINAMENTO PERIÓDICO TINYLLAMA INICIADO (a cada {self.interval_hours}h)")