Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -1295,14 +1295,7 @@ def train_model_lora(
|
|
| 1295 |
|
| 1296 |
# ── Dataset: carregar do disco ou tokenizar ────────────────────────
|
| 1297 |
tokenized_path = os.path.join(output_dir, "tokenized_dataset")
|
| 1298 |
-
|
| 1299 |
-
# Depois de tokenizar o dataset
|
| 1300 |
-
if os.path.exists(tokenized_path):
|
| 1301 |
-
HF_BUCKET_DATASET = "hf://buckets/Fedir-Ilina/Train_CPU/tokenized_dataset" # definir nome do bucket
|
| 1302 |
-
upload_to_bucket(tokenized_path, HF_BUCKET_DATASET)
|
| 1303 |
-
log_success("Dataset tokenizado enviado ao Bucket Hugging Face!")
|
| 1304 |
-
training_logs.append("Dataset tokenizado enviado ao Bucket Hugging Face.")
|
| 1305 |
-
#----------------------------------------------------------------------------
|
| 1306 |
if os.path.exists(tokenized_path):
|
| 1307 |
log_info("Dataset tokenizado encontrado no disco. A carregar...")
|
| 1308 |
tokenized_datasets = load_from_disk(tokenized_path)
|
|
@@ -1346,7 +1339,6 @@ def train_model_lora(
|
|
| 1346 |
|
| 1347 |
training_args = TrainingArguments(
|
| 1348 |
output_dir=output_dir,
|
| 1349 |
-
logging_dir="/tmp/tensorboard_logs",
|
| 1350 |
num_train_epochs=_epochs_for_trainer,
|
| 1351 |
per_device_train_batch_size=BASE_BATCH_SIZE,
|
| 1352 |
per_device_eval_batch_size=BASE_EVAL_SIZE,
|
|
@@ -1436,65 +1428,33 @@ def train_model_lora(
|
|
| 1436 |
#-----------------------------------------------------------------------------
|
| 1437 |
try:
|
| 1438 |
log_success("Configuração do Trainer concluída. A iniciar o treino...")
|
| 1439 |
-
train_progress.update({
|
| 1440 |
-
"status": "training",
|
| 1441 |
-
"message": "Iniciando o treinamento do modelo..."
|
| 1442 |
-
})
|
| 1443 |
-
|
| 1444 |
trainer_output = trainer.train(resume_from_checkpoint=resume_from_trainer_checkpoint)
|
| 1445 |
|
| 1446 |
-
# -------------------------------
|
| 1447 |
-
# Salvar adapter LoRA localmente
|
| 1448 |
-
# -------------------------------
|
| 1449 |
lora_model_path = os.path.join(output_dir, "lora_model")
|
| 1450 |
os.makedirs(lora_model_path, exist_ok=True)
|
| 1451 |
-
|
| 1452 |
model.save_pretrained(lora_model_path)
|
| 1453 |
tokenizer.save_pretrained(lora_model_path)
|
| 1454 |
training_logs.append("Adapter LoRA salvo com sucesso!")
|
| 1455 |
|
| 1456 |
-
# ================================================
|
| 1457 |
-
# ENVIAR LoRA PARA BUCKET
|
| 1458 |
-
# ================================================
|
| 1459 |
-
HF_BUCKET_LORA = "hf://buckets/Fedir-Ilina/Train_CPU/lora_adapter"
|
| 1460 |
-
|
| 1461 |
-
try:
|
| 1462 |
-
upload_to_bucket(lora_model_path, HF_BUCKET_LORA)
|
| 1463 |
-
log_success(f"Adapter LoRA enviado ao Bucket: {HF_BUCKET_LORA}")
|
| 1464 |
-
training_logs.append("Adapter LoRA enviado ao Bucket HF com sucesso!")
|
| 1465 |
-
except Exception as bucket_error:
|
| 1466 |
-
log_warning(f"Falha ao enviar LoRA para Bucket: {bucket_error}")
|
| 1467 |
-
training_logs.append(f"Erro no upload para Bucket: {bucket_error}")
|
| 1468 |
-
|
| 1469 |
-
# ---------------------------------
|
| 1470 |
-
# Avaliação final do modelo
|
| 1471 |
-
# ---------------------------------
|
| 1472 |
log_info("A avaliar o modelo final no dataset de validação...")
|
| 1473 |
final_metrics = trainer.evaluate()
|
| 1474 |
-
|
| 1475 |
eval_loss = final_metrics.get("eval_loss")
|
| 1476 |
if eval_loss is not None:
|
| 1477 |
perplexity = math.exp(eval_loss)
|
| 1478 |
log_success("Avaliação Final Concluída:")
|
| 1479 |
log_info(f" -> Eval Loss: {eval_loss:.4f}")
|
| 1480 |
log_info(f" -> Perplexity: {perplexity:.4f}")
|
| 1481 |
-
|
| 1482 |
with open(os.path.join(output_dir, "final_metrics.txt"), "w") as f:
|
| 1483 |
f.write(f"Eval Loss: {eval_loss}\nPerplexity: {perplexity}\n")
|
| 1484 |
-
|
| 1485 |
else:
|
| 1486 |
log_warning("Não foi possível obter 'eval_loss' das métricas finais.")
|
| 1487 |
|
| 1488 |
-
train_progress.update({
|
| 1489 |
-
"status": "awaiting_merge",
|
| 1490 |
-
"percent": 100,
|
| 1491 |
-
"message": "Treino concluído. LoRA salvo. Decida a próxima ação."
|
| 1492 |
-
})
|
| 1493 |
|
|
|
|
| 1494 |
training_logs.append("Treino concluído. Adapter LoRA salvo em " + lora_model_path)
|
| 1495 |
log_success("Treino concluído. Adapter LoRA salvo. Aguardando decisão (merge ou continuar).")
|
| 1496 |
-
|
| 1497 |
-
except RuntimeError as e:
|
| 1498 |
msg = str(e)
|
| 1499 |
training_logs.append(f"Erro durante treino: {msg}")
|
| 1500 |
train_progress["status"] = "error"
|
|
@@ -1506,18 +1466,17 @@ def train_model_lora(
|
|
| 1506 |
train_progress.update({"status": "error", "message": f"Erro crítico no treino: {e}"})
|
| 1507 |
traceback.print_exc()
|
| 1508 |
return
|
|
|
|
| 1509 |
#----------------------------------------------------
|
| 1510 |
-
TENSORBOARD_LOGDIR = "
|
| 1511 |
TENSORBOARD_PORT = 6006
|
| 1512 |
|
| 1513 |
-
os.makedirs(TENSORBOARD_LOGDIR, exist_ok=True)
|
| 1514 |
-
|
| 1515 |
def run_tensorboard():
|
|
|
|
| 1516 |
subprocess.Popen([
|
| 1517 |
"tensorboard",
|
| 1518 |
f"--logdir={TENSORBOARD_LOGDIR}",
|
| 1519 |
-
f"--port={TENSORBOARD_PORT}"
|
| 1520 |
-
"--host=0.0.0.0"
|
| 1521 |
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 1522 |
#-----------------------------------------------------------
|
| 1523 |
#----------------------
|
|
@@ -1546,17 +1505,6 @@ def guess_lora_targets(model):
|
|
| 1546 |
@app.route('/api/train', methods=['POST'])
|
| 1547 |
def handle_train_request():
|
| 1548 |
global all_data
|
| 1549 |
-
# 🟩 INÍCIO — adiciona estas linhas aqui:
|
| 1550 |
-
train_progress.clear()
|
| 1551 |
-
train_progress.update({
|
| 1552 |
-
"status": "running",
|
| 1553 |
-
"percent": 0,
|
| 1554 |
-
"start_time": time.time(),
|
| 1555 |
-
"message": "A preparar treino…"
|
| 1556 |
-
})
|
| 1557 |
-
training_logs.clear()
|
| 1558 |
-
epoch_losses.clear()
|
| 1559 |
-
# 🟩 FIM — estas linhas são obrigatórias para a UI atualizar
|
| 1560 |
try:
|
| 1561 |
model_path = request.form.get('model_path')
|
| 1562 |
epochs = int(request.form.get('epochs'))
|
|
@@ -1569,18 +1517,7 @@ def handle_train_request():
|
|
| 1569 |
if not all([model_path, epochs, uploaded_files]):
|
| 1570 |
return jsonify({"status": "error", "message": "Faltam parâmetros: modelo, épocas ou ficheiros."}), 400
|
| 1571 |
|
| 1572 |
-
#
|
| 1573 |
-
training_logs.clear()
|
| 1574 |
-
epoch_losses.clear()
|
| 1575 |
-
train_progress.update({
|
| 1576 |
-
"status": "running",
|
| 1577 |
-
"percent": 0,
|
| 1578 |
-
"message": "A preparar dados…",
|
| 1579 |
-
"start_time": time.time(), # ⏱️ marca o início real deste treino
|
| 1580 |
-
"run_id": int(time.time()) # opcional: id da sessão
|
| 1581 |
-
})
|
| 1582 |
-
|
| 1583 |
-
# Carregar dados
|
| 1584 |
all_data = []
|
| 1585 |
for file in uploaded_files:
|
| 1586 |
try:
|
|
@@ -1591,7 +1528,7 @@ def handle_train_request():
|
|
| 1591 |
if not line.strip(): continue
|
| 1592 |
if file.filename.endswith('.jsonl'):
|
| 1593 |
all_data.append(json.loads(line))
|
| 1594 |
-
else:
|
| 1595 |
all_data.append({"text": line.strip()})
|
| 1596 |
except Exception as e:
|
| 1597 |
return jsonify({"status": "error", "message": f"Erro ao ler ficheiro {file.filename}: {e}"}), 400
|
|
@@ -1606,11 +1543,8 @@ def handle_train_request():
|
|
| 1606 |
training_logs.append(f"[DEBUG] OUTPUT_DIR: {output_dir}")
|
| 1607 |
training_logs.append(f"[DEBUG] FILES: {os.listdir(os.getcwd())}")
|
| 1608 |
|
| 1609 |
-
#
|
| 1610 |
-
thread = Thread(
|
| 1611 |
-
target=train_model_lora,
|
| 1612 |
-
args=(all_data, epochs, model_path, output_dir, ACCUMULATION_STEPS, DATALOADER_WORKERS, "new_train")
|
| 1613 |
-
)
|
| 1614 |
thread.start()
|
| 1615 |
|
| 1616 |
return jsonify({"status": "started", "message": "Requisição de treino recebida. O processo foi iniciado."})
|
|
@@ -1774,10 +1708,12 @@ def train_status():
|
|
| 1774 |
epoch_list = epoch_losses
|
| 1775 |
logs_tail = training_logs[-200:] if len(training_logs) > 200 else training_logs[:]
|
| 1776 |
|
| 1777 |
-
# Acrescentar
|
| 1778 |
if CURRENT_ACCUM_STEPS is not None:
|
| 1779 |
origem = LAST_ACCUM_ORIGIN or "desconhecido"
|
| 1780 |
logs_tail.append(f"[INFO] gradient_accumulation_steps atual: {CURRENT_ACCUM_STEPS} ({origem})")
|
|
|
|
|
|
|
| 1781 |
if BASE_BATCH_SIZE_EFFECTIVE:
|
| 1782 |
eb = BASE_BATCH_SIZE_EFFECTIVE * CURRENT_ACCUM_STEPS
|
| 1783 |
logs_tail.append(f"[INFO] Effective Batch (base={BASE_BATCH_SIZE_EFFECTIVE}) = {eb}")
|
|
@@ -1785,16 +1721,11 @@ def train_status():
|
|
| 1785 |
if TOTAL_TRAIN_STEPS is not None:
|
| 1786 |
logs_tail.append(f"[INFO] total_train_steps desta execução: {TOTAL_TRAIN_STEPS}")
|
| 1787 |
|
| 1788 |
-
# ⏱ TEMPO DECORRIDO REAL
|
| 1789 |
-
start_time = train_progress.get("start_time")
|
| 1790 |
-
elapsed_seconds = int(time.time() - start_time) if start_time else None
|
| 1791 |
-
|
| 1792 |
return jsonify({
|
| 1793 |
"progress": progress,
|
| 1794 |
"status": status,
|
| 1795 |
"epoch_losses": epoch_list,
|
| 1796 |
-
"logs": logs_tail
|
| 1797 |
-
"elapsed_seconds": elapsed_seconds
|
| 1798 |
})
|
| 1799 |
|
| 1800 |
#---------------------------------------
|
|
@@ -1959,12 +1890,7 @@ def decide_merge():
|
|
| 1959 |
log_warning(f"Erro ao ler trainer_state.json do checkpoint: {e}. Assumindo 0 épocas completadas.")
|
| 1960 |
|
| 1961 |
# Atualiza o status de progresso para indicar que o treino vai continuar
|
| 1962 |
-
train_progress.update({
|
| 1963 |
-
"status": "continuing_training",
|
| 1964 |
-
"message": f"Continuando treino por mais {epochs_to_add} épocas...",
|
| 1965 |
-
"start_time": time.time(), # ⏱️ reinicia o relógio para esta sessão de continuação
|
| 1966 |
-
"run_id": int(time.time()) # opcional
|
| 1967 |
-
})
|
| 1968 |
|
| 1969 |
thread = Thread(
|
| 1970 |
target=train_model_lora,
|
|
@@ -2268,5 +2194,7 @@ def create_model():
|
|
| 2268 |
# O modelo real é carregado no início do treino via train_model_lora
|
| 2269 |
return jsonify({"status": "ok", "message": f"Modelo '{model_name}' será carregado ao iniciar o treino."})
|
| 2270 |
#---------------------------------------------------------------
|
|
|
|
|
|
|
| 2271 |
if __name__ == '__main__':
|
| 2272 |
app.run(host='0.0.0.0', port=7860, debug=False)
|
|
|
|
| 1295 |
|
| 1296 |
# ── Dataset: carregar do disco ou tokenizar ────────────────────────
|
| 1297 |
tokenized_path = os.path.join(output_dir, "tokenized_dataset")
|
| 1298 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1299 |
if os.path.exists(tokenized_path):
|
| 1300 |
log_info("Dataset tokenizado encontrado no disco. A carregar...")
|
| 1301 |
tokenized_datasets = load_from_disk(tokenized_path)
|
|
|
|
| 1339 |
|
| 1340 |
training_args = TrainingArguments(
|
| 1341 |
output_dir=output_dir,
|
|
|
|
| 1342 |
num_train_epochs=_epochs_for_trainer,
|
| 1343 |
per_device_train_batch_size=BASE_BATCH_SIZE,
|
| 1344 |
per_device_eval_batch_size=BASE_EVAL_SIZE,
|
|
|
|
| 1428 |
#-----------------------------------------------------------------------------
|
| 1429 |
try:
|
| 1430 |
log_success("Configuração do Trainer concluída. A iniciar o treino...")
|
| 1431 |
+
train_progress.update({"status": "training", "message": "Iniciando o treinamento do modelo..."})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1432 |
trainer_output = trainer.train(resume_from_checkpoint=resume_from_trainer_checkpoint)
|
| 1433 |
|
|
|
|
|
|
|
|
|
|
| 1434 |
lora_model_path = os.path.join(output_dir, "lora_model")
|
| 1435 |
os.makedirs(lora_model_path, exist_ok=True)
|
|
|
|
| 1436 |
model.save_pretrained(lora_model_path)
|
| 1437 |
tokenizer.save_pretrained(lora_model_path)
|
| 1438 |
training_logs.append("Adapter LoRA salvo com sucesso!")
|
| 1439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1440 |
log_info("A avaliar o modelo final no dataset de validação...")
|
| 1441 |
final_metrics = trainer.evaluate()
|
|
|
|
| 1442 |
eval_loss = final_metrics.get("eval_loss")
|
| 1443 |
if eval_loss is not None:
|
| 1444 |
perplexity = math.exp(eval_loss)
|
| 1445 |
log_success("Avaliação Final Concluída:")
|
| 1446 |
log_info(f" -> Eval Loss: {eval_loss:.4f}")
|
| 1447 |
log_info(f" -> Perplexity: {perplexity:.4f}")
|
|
|
|
| 1448 |
with open(os.path.join(output_dir, "final_metrics.txt"), "w") as f:
|
| 1449 |
f.write(f"Eval Loss: {eval_loss}\nPerplexity: {perplexity}\n")
|
|
|
|
| 1450 |
else:
|
| 1451 |
log_warning("Não foi possível obter 'eval_loss' das métricas finais.")
|
| 1452 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1453 |
|
| 1454 |
+
train_progress.update({"status": "awaiting_merge", "percent": 100, "message": "Treino concluído. LoRA salvo. Decida a próxima ação."})
|
| 1455 |
training_logs.append("Treino concluído. Adapter LoRA salvo em " + lora_model_path)
|
| 1456 |
log_success("Treino concluído. Adapter LoRA salvo. Aguardando decisão (merge ou continuar).")
|
| 1457 |
+
except RuntimeError as e:
|
|
|
|
| 1458 |
msg = str(e)
|
| 1459 |
training_logs.append(f"Erro durante treino: {msg}")
|
| 1460 |
train_progress["status"] = "error"
|
|
|
|
| 1466 |
train_progress.update({"status": "error", "message": f"Erro crítico no treino: {e}"})
|
| 1467 |
traceback.print_exc()
|
| 1468 |
return
|
| 1469 |
+
|
| 1470 |
#----------------------------------------------------
|
| 1471 |
+
TENSORBOARD_LOGDIR = r"C:\Users\ilina\startup\Treinamento\feramenta-treino\trained_model_output\logs"
|
| 1472 |
TENSORBOARD_PORT = 6006
|
| 1473 |
|
|
|
|
|
|
|
| 1474 |
def run_tensorboard():
|
| 1475 |
+
# Lança o TensorBoard em thread separada para não bloquear
|
| 1476 |
subprocess.Popen([
|
| 1477 |
"tensorboard",
|
| 1478 |
f"--logdir={TENSORBOARD_LOGDIR}",
|
| 1479 |
+
f"--port={TENSORBOARD_PORT}"
|
|
|
|
| 1480 |
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 1481 |
#-----------------------------------------------------------
|
| 1482 |
#----------------------
|
|
|
|
| 1505 |
@app.route('/api/train', methods=['POST'])
|
| 1506 |
def handle_train_request():
|
| 1507 |
global all_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1508 |
try:
|
| 1509 |
model_path = request.form.get('model_path')
|
| 1510 |
epochs = int(request.form.get('epochs'))
|
|
|
|
| 1517 |
if not all([model_path, epochs, uploaded_files]):
|
| 1518 |
return jsonify({"status": "error", "message": "Faltam parâmetros: modelo, épocas ou ficheiros."}), 400
|
| 1519 |
|
| 1520 |
+
# Limpa os dados de treinos anteriores
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1521 |
all_data = []
|
| 1522 |
for file in uploaded_files:
|
| 1523 |
try:
|
|
|
|
| 1528 |
if not line.strip(): continue
|
| 1529 |
if file.filename.endswith('.jsonl'):
|
| 1530 |
all_data.append(json.loads(line))
|
| 1531 |
+
else: # Assume .txt ou outros formatos de texto
|
| 1532 |
all_data.append({"text": line.strip()})
|
| 1533 |
except Exception as e:
|
| 1534 |
return jsonify({"status": "error", "message": f"Erro ao ler ficheiro {file.filename}: {e}"}), 400
|
|
|
|
| 1543 |
training_logs.append(f"[DEBUG] OUTPUT_DIR: {output_dir}")
|
| 1544 |
training_logs.append(f"[DEBUG] FILES: {os.listdir(os.getcwd())}")
|
| 1545 |
|
| 1546 |
+
# Passar um flag indicando que é um "novo" treino (ou re-treino)
|
| 1547 |
+
thread = Thread(target=train_model_lora, args=(all_data, epochs, model_path, output_dir, ACCUMULATION_STEPS, DATALOADER_WORKERS, "new_train"))
|
|
|
|
|
|
|
|
|
|
| 1548 |
thread.start()
|
| 1549 |
|
| 1550 |
return jsonify({"status": "started", "message": "Requisição de treino recebida. O processo foi iniciado."})
|
|
|
|
| 1708 |
epoch_list = epoch_losses
|
| 1709 |
logs_tail = training_logs[-200:] if len(training_logs) > 200 else training_logs[:]
|
| 1710 |
|
| 1711 |
+
# Acrescentar as linhas pedidas (apenas texto; simples)
|
| 1712 |
if CURRENT_ACCUM_STEPS is not None:
|
| 1713 |
origem = LAST_ACCUM_ORIGIN or "desconhecido"
|
| 1714 |
logs_tail.append(f"[INFO] gradient_accumulation_steps atual: {CURRENT_ACCUM_STEPS} ({origem})")
|
| 1715 |
+
|
| 1716 |
+
# opcional: mostrar Effective Batch
|
| 1717 |
if BASE_BATCH_SIZE_EFFECTIVE:
|
| 1718 |
eb = BASE_BATCH_SIZE_EFFECTIVE * CURRENT_ACCUM_STEPS
|
| 1719 |
logs_tail.append(f"[INFO] Effective Batch (base={BASE_BATCH_SIZE_EFFECTIVE}) = {eb}")
|
|
|
|
| 1721 |
if TOTAL_TRAIN_STEPS is not None:
|
| 1722 |
logs_tail.append(f"[INFO] total_train_steps desta execução: {TOTAL_TRAIN_STEPS}")
|
| 1723 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1724 |
return jsonify({
|
| 1725 |
"progress": progress,
|
| 1726 |
"status": status,
|
| 1727 |
"epoch_losses": epoch_list,
|
| 1728 |
+
"logs": logs_tail
|
|
|
|
| 1729 |
})
|
| 1730 |
|
| 1731 |
#---------------------------------------
|
|
|
|
| 1890 |
log_warning(f"Erro ao ler trainer_state.json do checkpoint: {e}. Assumindo 0 épocas completadas.")
|
| 1891 |
|
| 1892 |
# Atualiza o status de progresso para indicar que o treino vai continuar
|
| 1893 |
+
train_progress.update({"status": "continuing_training", "message": f"Continuando treino por mais {epochs_to_add} épocas..."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1894 |
|
| 1895 |
thread = Thread(
|
| 1896 |
target=train_model_lora,
|
|
|
|
| 2194 |
# O modelo real é carregado no início do treino via train_model_lora
|
| 2195 |
return jsonify({"status": "ok", "message": f"Modelo '{model_name}' será carregado ao iniciar o treino."})
|
| 2196 |
#---------------------------------------------------------------
|
| 2197 |
+
#if __name__ == "__main__" and os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
| 2198 |
+
# código de inicialização
|
| 2199 |
if __name__ == '__main__':
|
| 2200 |
app.run(host='0.0.0.0', port=7860, debug=False)
|