Fedir-Ilina commited on
Commit
08dcd28
·
verified ·
1 Parent(s): 02fba49

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -355
app.py CHANGED
@@ -91,6 +91,7 @@ def detect_core_types():
91
  Retorna (P_IDS, E_IDS, meta_dict).
92
  """
93
  logical = psutil.cpu_count(logical=True) or os.cpu_count() or 1
 
94
  # 1) Windows EfficiencyClass
95
  if platform.system() == "Windows":
96
  try:
@@ -106,7 +107,6 @@ def detect_core_types():
106
  ("EfficiencyClass", ct.c_ubyte),
107
  ("Reserved", ct.c_ubyte * 20),
108
  ("GroupCount", ct.c_ushort)]
109
- # seguido inline por GROUP_AFFINITY[GroupCount]
110
 
111
  class SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_HEADER(ct.Structure):
112
  _fields_ = [("Relationship", ct.c_int),
@@ -135,13 +135,11 @@ def detect_core_types():
135
  ga = GROUP_AFFINITY.from_buffer(buf, ga_offset + i * ct.sizeof(GROUP_AFFINITY))
136
  mask = ga.Mask
137
  if single_group:
138
- # Mapear bits 0..63 para logical ids 0..63
139
  for bit in range(64):
140
  if (mask >> bit) & 1:
141
  eff_by_logical[bit] = eff
142
  else:
143
- # Em sistemas multi-grupo (>64 lógicos), mapa global é mais complexo.
144
- # Como alteração mínima, ignoramos (cai para fallback se não mapear nada).
145
  pass
146
  offset += size
147
 
@@ -149,9 +147,8 @@ def detect_core_types():
149
  p_ids = sorted([i for i in range(logical) if eff_by_logical.get(i, 0) == 0])
150
  e_ids = sorted([i for i in range(logical) if eff_by_logical.get(i, 0) > 0])
151
  return p_ids, e_ids, {"method": "windows_efficiencyclass", "notes": []}
152
- except Exception as e:
153
- # continua para Linux/fallback
154
- pass
155
 
156
  # 2) Linux sysfs core_type
157
  if platform.system() == "Linux":
@@ -160,12 +157,11 @@ def detect_core_types():
160
  for cpu in range(logical):
161
  path = f"/sys/devices/system/cpu/cpu{cpu}/topology/core_type"
162
  try:
163
- with open(path, "r") as f:
164
  val = f.read().strip()
165
  except FileNotFoundError:
166
  val = None
167
  if val is None:
168
- # sysfs não disponível → sai para fallback
169
  p_ids = e_ids = []
170
  break
171
  try:
@@ -183,8 +179,7 @@ def detect_core_types():
183
  notes = []
184
  if unknown:
185
  notes.append(f"{len(unknown)} CPUs com core_type=Unknown (tratados como P).")
186
- p_ids.extend(unknown) # escolha conservadora: desconhecidos como P
187
- p_ids = sorted(p_ids)
188
  return sorted(p_ids), sorted(e_ids), {"method": "linux_core_type", "notes": notes}
189
  except Exception:
190
  pass
@@ -233,7 +228,6 @@ def get_core_siblings():
233
  if not GetLPIEx(RELATION_PROCESSOR_CORE, ct.byref(buf), ct.byref(buf_size)):
234
  return []
235
 
236
- # Cada entrada RelationProcessorCore descreve UM core físico e a(s) máscara(s) dos seus logical processors.
237
  siblings = []
238
  offset = 0
239
  single_group = (logical <= 64)
@@ -247,36 +241,27 @@ def get_core_siblings():
247
  for i in range(pr.GroupCount):
248
  ga = GROUP_AFFINITY.from_buffer(buf, ga_offset + i * ct.sizeof(GROUP_AFFINITY))
249
  mask = ga.Mask
250
- if single_group:
251
- for bit in range(64):
252
- if (mask >> bit) & 1:
253
- core_logicals.append(bit)
254
- else:
255
- # Multi-grupo: mapping global não é trivial; manter simples
256
- for bit in range(64):
257
- if (mask >> bit) & 1:
258
- core_logicals.append(bit) # aproximação
259
  if core_logicals:
260
  siblings.append(sorted(set(core_logicals)))
261
  offset += size
262
 
263
- # Ordena por menor logical id para estabilidade
264
- siblings = sorted(siblings, key=lambda s: min(s) if s else 1e9)
265
- return siblings
266
  except Exception:
267
  return []
268
 
269
  elif platform.system() == "Linux":
270
- sibs = []
271
  try:
 
272
  for cpu in range(logical):
273
  path = f"/sys/devices/system/cpu/cpu{cpu}/topology/thread_siblings_list"
274
  try:
275
- with open(path, "r") as f:
276
  txt = f.read().strip()
277
  except FileNotFoundError:
278
  return []
279
- # Ex.: "0,6" ou "0-1,6-7"
280
  items = []
281
  for part in txt.split(","):
282
  if "-" in part:
@@ -286,172 +271,125 @@ def get_core_siblings():
286
  items.append(int(part))
287
  sibs.append(sorted(set(items)))
288
  # Deduplica sublistas iguais
289
- uniq = []
290
  seen = set()
 
291
  for s in sibs:
292
  t = tuple(s)
293
  if t not in seen:
294
  seen.add(t)
295
  uniq.append(s)
296
- uniq = sorted(uniq, key=lambda s: min(s))
297
- return uniq
298
  except Exception:
299
  return []
300
 
301
  return []
302
 
 
303
  def order_by_physical_first(candidates, siblings_map):
304
  """
305
- Reordena 'candidates' para usar primeiro 1 logical por core físico (evita usar siblings logo de início).
306
  Se 'siblings_map' estiver vazio, retorna candidatos ordenados naturalmente.
307
  """
308
  if not siblings_map:
309
  return sorted(candidates)
310
 
311
  cand_set = set(candidates)
312
- # passagem: 1 por core
313
- first_pass = []
314
- for group in siblings_map:
315
- pick = next((x for x in group if x in cand_set), None)
316
- if pick is not None:
317
- first_pass.append(pick)
318
-
319
- # 2ª passagem: restantes (siblings), preservando ordem por grupo
320
- others = []
321
- for group in siblings_map:
322
- for x in group:
323
- if x in cand_set and x not in first_pass:
324
- others.append(x)
325
-
326
- # Mantém qualquer candidato que não esteja no mapa (ex.: detection parcial)
327
  leftovers = [x for x in sorted(candidates) if x not in first_pass and x not in others]
328
  return first_pass + others + leftovers
329
 
330
  # =========================================================================
331
- # 1. Configuração base de hardware, Otimização e Hiperparâmetros
332
  # =========================================================================
 
333
  # --- 1.1 Hardware ---
334
- LOGICAL_CPUS = psutil.cpu_count(logical=True) or os.cpu_count() or 16
335
  PHYSICAL_CPUS = psutil.cpu_count(logical=False) or max(1, LOGICAL_CPUS // 2)
336
- TOTAL_RAM_GB = psutil.virtual_memory().total / (1024 ** 3)
337
- #--------------------------------------------------------------------------------------------------------------------------------------
338
  # ===============================================================
339
  # Ajuste de performance (auto/manual)
340
  # ===============================================================
341
  OMP_THREADS_UTILIZATION = 0.85
342
- DETECTION_PERFORMANCE = "auto" # "auto" | "manual"
343
- CORES_UTILIZATION = 1.0
344
-
345
- # --------------------
346
- def compute_effective_cores(cpu_count: int, utilization: float) -> int:
347
- eff = int(math.floor(cpu_count * utilization))
348
- return max(1, min(eff, cpu_count))
349
 
 
 
 
350
 
351
- EFFECTIVE_LOGICAL_CPUS = compute_effective_cores(LOGICAL_CPUS, CORES_UTILIZATION)
 
352
 
353
- # --------------------
354
- # --- Detectar núcleos P/E ---
355
- _DET_P_full, _DET_E_full, _META = detect_core_types() # Renomeado para indicar que são os full detections
356
 
357
- # NOVO: Filtrar P/E-cores para estarem dentro do limite de EFFECTIVE_LOGICAL_CPUS
 
358
  _DET_P = [i for i in _DET_P_full if i < EFFECTIVE_LOGICAL_CPUS]
359
  _DET_E = [i for i in _DET_E_full if i < EFFECTIVE_LOGICAL_CPUS]
360
 
361
-
362
-
363
  # ===============================================================
364
- # Ajuste entre modo auto/manual (apenas meta-informação de arranque)
365
  # ===============================================================
366
- _AUTO_META = "manual"
367
- OMP_THREADS_PLACEHOLDER = compute_effective_cores(EFFECTIVE_LOGICAL_CPUS, OMP_THREADS_UTILIZATION)
368
  if DETECTION_PERFORMANCE.lower() == "auto":
369
- omp_threads = OMP_THREADS_PLACEHOLDER
370
- total_detected_filtered = (len(_DET_P or []) + len(_DET_E or []))
371
- if total_detected_filtered == 0:
372
  _AUTO_META = "auto (fallback homogéneo)"
 
 
373
  else:
374
- p_ratio = len(_DET_P) / total_detected_filtered if total_detected_filtered > 0 else 0
375
- if p_ratio >= 0.5:
376
- _AUTO_META = f"auto (balanceado P/E → OMP={omp_threads})"
377
- else:
378
- _AUTO_META = f"auto (balanceado E-heavy → OMP={omp_threads})"
379
  else:
380
  _AUTO_META = "manual"
381
 
382
- # Log informativo (mantém o teu estilo)
383
  print(f"[INFO] [{_ts()}] Modo de desempenho: {DETECTION_PERFORMANCE} ({_AUTO_META})")
384
 
385
-
386
- # --- 1.2 Otimização de Threads/Processos ---
387
- def compute_omp_threads(cpu_count, utilization):
388
- threads = int(math.floor(cpu_count * utilization))
389
- return max(1, min(threads, cpu_count))
390
-
391
-
392
- # OMP passa a respeitar o teto de núcleos permitidos por CORES_UTILIZATION
393
- OMP_THREADS = compute_omp_threads(EFFECTIVE_LOGICAL_CPUS, OMP_THREADS_UTILIZATION)
394
-
395
- # --------------------
396
- # Seleção para tensores (OMP): usar P primeiro; se faltar, completar com E (com ordenação física primeiro)
397
- siblings = get_core_siblings()
398
  ordered_P = order_by_physical_first(_DET_P, siblings)
399
  ordered_E = order_by_physical_first(_DET_E, siblings)
400
 
401
- needed = OMP_THREADS
402
  P_CORE_IDS = []
 
403
  if ordered_P:
404
- takeP = ordered_P[:min(needed, len(ordered_P))]
405
- P_CORE_IDS.extend(takeP)
406
- needed -= len(takeP)
407
 
408
- # Completar com E-cores se necessário
409
  if needed > 0 and ordered_E:
410
- takeE = [i for i in ordered_E if i not in P_CORE_IDS][:needed]
411
- P_CORE_IDS.extend(takeE)
412
- needed -= len(takeE)
413
 
414
- # Fallback homogéneo: se nada acima preencheu (ex.: sem deteção ou P/E fora do range), usa os primeiros N
415
  if not P_CORE_IDS:
416
- # Agora limitado ao espaço permitido (0..EFFECTIVE_LOGICAL_CPUS-1)
417
  P_CORE_IDS = list(range(min(OMP_THREADS, EFFECTIVE_LOGICAL_CPUS)))
418
- # Isso garante que P_CORE_IDS nunca exceda EFFECTIVE_LOGICAL_CPUS
419
 
420
- # Secundários (DataLoader): 1) E não usados, 2) P não usados, 3) quaisquer outros restantes
421
  used = set(P_CORE_IDS)
422
- secondary = []
423
-
424
-
425
- # IDs disponíveis dentro do limite efetivo
426
- available_ids_in_effective_range = set(range(EFFECTIVE_LOGICAL_CPUS))
427
-
428
- # 1) E que sobraram e estão dentro do range efetivo
429
- secondary.extend([i for i in (_DET_E or []) if i not in used and i in available_ids_in_effective_range])
430
- # 2) P que sobraram e estão dentro do range efetivo
431
- secondary.extend([i for i in (_DET_P or []) if i not in used and i in available_ids_in_effective_range])
432
- # 3) Restantes (caso deteção seja homogénea/fallback)
433
- secondary.extend([i for i in available_ids_in_effective_range
434
- if i not in used
435
- and (i not in (_DET_P or []))
436
- and (i not in (_DET_E or []))])
437
-
438
- # Remover duplicados preservando ordem
439
  _seen = set()
440
  REMAINING_CORE_IDS = [x for x in secondary if not (x in _seen or _seen.add(x))]
441
 
 
442
 
443
- # O número de workers deve refletir exatamente os cores sobrantes e nunca exceder o que sobra dos efetivos
444
- max_workers_possible = max(0, EFFECTIVE_LOGICAL_CPUS - len(P_CORE_IDS))
445
- DATALOADER_WORKERS = min(len(REMAINING_CORE_IDS), max_workers_possible)
446
-
447
- # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
448
- # Variáveis de ambiente (mantidas)
449
- os.environ["OMP_NUM_THREADS"] = str(OMP_THREADS)
450
- os.environ["MKL_NUM_THREADS"] = str(OMP_THREADS)
451
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
452
 
453
 
454
- # --- Afinidade (mantida) ---
455
  def set_affinity(core_ids):
456
  try:
457
  if platform.system() == "Linux":
@@ -462,148 +400,117 @@ def set_affinity(core_ids):
462
  print(f"[WARN] [{_ts()}] Não foi possível definir afinidade: {e}")
463
 
464
 
465
-
466
- # Mantém a ordem: P usados primeiro (prioridade), depois secundários
467
- # Garante que os core_ids são únicos e estão dentro do limite.
468
- # O conjunto final de IDs passados para afinidade deve ser <= EFFECTIVE_LOGICAL_CPUS
469
- final_affinity_cores = sorted(list(set(P_CORE_IDS + REMAINING_CORE_IDS)))
470
- # Filtra para garantir que nenhum ID exceda EFFECTIVE_LOGICAL_CPUS
471
- final_affinity_cores = [i for i in final_affinity_cores if i < EFFECTIVE_LOGICAL_CPUS]
472
  set_affinity(final_affinity_cores)
473
 
474
- # --------------------
475
- # Evita duplicação de logs quando Flask recarrega
476
- if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
 
477
  _LOGS_PRINTED = True
 
478
 
479
- SEC_E = []
480
- SEC_P = []
481
- SEC_OTHER = []
482
- # --------------------
483
- # Logs informativos detalhados (simplificados, evita repetições)
484
- if not globals().get("_LOGS_PRINTED", False):
485
- _LOGS_PRINTED = True # marca que já imprimimos uma vez
486
  print(f"[INFO] [{_ts()}] Método de deteção: {_META.get('method')}")
487
- if _META.get("notes"):
488
- for n in _META["notes"]:
489
- print(f"[INFO] [{_ts()}] Nota: {n}")
490
-
491
- has_e = bool(_DET_E)
492
- # Aqui, _DET_P e _DET_E já estão filtrados para serem <= EFFECTIVE_LOGICAL_CPUS
493
  print(f"[INFO] [{_ts()}] Detetados (filtrados) → P: {len(_DET_P)} | E: {len(_DET_E)}")
494
 
495
- # --- Breakdown do que foi realmente usado
496
- P_USED_FROM_P = [i for i in P_CORE_IDS if i in (_DET_P or [])]
497
- P_USED_FROM_E = [i for i in P_CORE_IDS if i in (_DET_E or [])]
498
- SEC_E = [i for i in REMAINING_CORE_IDS if i in (_DET_E or [])]
499
- SEC_P = [i for i in REMAINING_CORE_IDS if i in (_DET_P or [])]
500
- SEC_OTHER = [i for i in REMAINING_CORE_IDS if (i not in (_DET_P or [])) and (i not in (_DET_E or []))]
501
-
502
  # Tensores
 
 
503
  if has_e:
504
- # P_USED_FROM_P e P_USED_FROM_E estão corretos devido ao filtro de _DET_P e _DET_E
505
- print(
506
- f"[INFO] [{_ts()}] Tensores (OMP={OMP_THREADS}) → "
507
- f"P: {len(P_USED_FROM_P)} {sorted(P_USED_FROM_P)}"
508
- + (f", +E: {len(P_USED_FROM_E)} {sorted(P_USED_FROM_E)}" if P_USED_FROM_E else "")
509
- )
510
  else:
511
  print(f"[INFO] [{_ts()}] Tensores (OMP={OMP_THREADS}) [homogéneo] → {len(P_CORE_IDS)} {sorted(P_CORE_IDS)}")
512
 
513
-
514
- # Secundários (DataLoader)
 
 
515
  if has_e:
516
- if SEC_E:
517
- print(f"[INFO] [{_ts()}] Secundário/DataLoader → E: {len(SEC_E)} {sorted(SEC_E)}")
518
- if SEC_P:
519
- print(f"[INFO] [{_ts()}] Secundário/DataLoader → P: {len(SEC_P)} {sorted(SEC_P)}")
520
- if SEC_OTHER:
521
- print(f"[INFO] [{_ts()}] Secundário/DataLoader → Outros: {len(SEC_OTHER)} {sorted(SEC_OTHER)}")
522
  else:
523
  print(f"[INFO] [{_ts()}] Cores restantes (homogéneo) → {len(REMAINING_CORE_IDS)} {sorted(REMAINING_CORE_IDS)}")
524
- print("-" * 80)
525
- print(
526
- f"[INFO] [{_ts()}] LOGICAL_CPUS: {LOGICAL_CPUS} | PHYSICAL_CPUS: {PHYSICAL_CPUS} | "
527
- f"TOTAL_RAM_GB: {TOTAL_RAM_GB:.2f}"
528
- )
529
- print(f"[INFO] [{_ts()}] Cores Utilization (global): {CORES_UTILIZATION:.2%} → efetivos: {EFFECTIVE_LOGICAL_CPUS}")
530
  print(f"[INFO] [{_ts()}] OMP Threads Utilization: {OMP_THREADS_UTILIZATION:.2%}")
531
- print(f"[INFO] [{_ts()}] OMP Threads: {OMP_THREADS}, Dataloader Workers: {DATALOADER_WORKERS}")
532
  if DATALOADER_WORKERS == 0:
533
  print(f"[INFO] [{_ts()}] Sem cores sobrantes para DataLoader (todos {EFFECTIVE_LOGICAL_CPUS} alocados a OMP).")
534
-
535
- # Verificação final da soma
536
  total_allocated = len(set(P_CORE_IDS + REMAINING_CORE_IDS))
537
- print(
538
- f"[INFO] [{_ts()}] Total de núcleos alocados (OMP + DataLoader): {total_allocated} "
539
- f"(de {EFFECTIVE_LOGICAL_CPUS} efetivos)"
540
- )
541
- print("-" * 80)
542
  print(f"[INFO] [{_ts()}] Iniciando processo de treino...\n")
543
  print("-" * 80)
544
-
545
  #--------------------------------------------------------------------------------------------------
546
- #--------------------------------------------------------------------------------------------------
547
- # --- 1.3 Parâmetros base do treino ---
548
- BASE_BATCH_SIZE = 1 # Valor base para o batch size (reduzido para CPU)
549
- INITIAL_ACCUMULATION_MIN_STEPS = 2
550
- INITIAL_ACCUMULATION_MAX_STEPS = 4
551
- BASE_EVAL_SIZE = 5 # Batch size para avaliação (reduzido para CPU)
552
- BASE_LEARNING_RATE = 1e-4 # 2e-5
553
- LR_SCHEDULER_TYPE = "constant_with_warmup" # (HF: cosine + warmap) cosine
554
- WARMUP_RATIO = 0.03 # ~3% dos 4012 steps ≈ 120
555
- LOGGIN_STEPS = 5
556
- SAVE_STRATEGY = "steps"
557
- SAVE_STEPS = 50
558
- EVAL_STEPS = 5
559
- #MAX_GRAD_NORM = 1.0
560
- WEIGHT_DECAY = 0.1
561
- OPTIM = "adamw_torch"
562
-
563
- # --- 1.4 Utilização de RAM ---
564
- TARGET_RAM_UTILIZATION = 0.95 # Limite máximo de uso da RAM total
565
-
566
- # --- 1.5 Parâmetros para Ajuste Dinâmico do Batch Size (ajustar_accumulation_steps) ---
567
- # Estimação: custo empírico de RAM por batch (ajuste este valor)
568
- ESTIMATED_BATCH_GB = 0.30 #0.4 llama
569
-
570
- # Utilização de RAM alvo para o cálculo de acumulação de gradientes
571
- TARGET_ACCUMULATION_RAM_UTILIZATION = TARGET_RAM_UTILIZATION # Reutiliza a RAM geral
572
-
573
- # --- 1.6 Parâmetros para MAX_LEN Dinâmico (ajustar_max_len) ---
574
- BASE_MAX_LEN = 256
575
- TARGET_MAX_LEN_UTILIZATION = 0.75 #0.65
576
- MAX_LEN_INCREMENT = 256
577
- MAX_LEN_CAP = 256
578
-
579
- # Valores padrão ajustáveis
580
- DEFAULT_FALLBACK_CAP = 8192 # ou MAX_LEN_CTX, se preferires
581
- TOKENIZER_SENTINEL_CAP = 10_000_000
582
-
583
- # Custo empírico de RAM para o dataset (ajuste estes valores)
584
- ESTIMATED_BASE_DATASET_RAM_GB = 1.6
585
- COST_PER_INCREMENT_GB = 0.30 #0.45 llama
586
-
587
- # --- 1.7 Parâmetros para DynamicAccumulationCallback ---
588
- DYNAMIC_ACCUMULATION_MAX_STEPS = 256 # Limite superior para gradient_accumulation_steps (usado na função e no callback)
589
- DYNAMIC_ACCUMULATION_TARGET_UTIL = 0.99 # Target de RAM no callback (não usado diretamente, mas mantido para clareza)
590
- DYNAMIC_ACCUMULATION_HIGH_RAM_LIMIT = 98.5 # Porcentagem de RAM para começar a reduzir o step
591
- DYNAMIC_ACCUMULATION_LOW_RAM_LIMIT = 40.0 # Porcentagem de RAM para começar a aumentar o step
592
-
593
- # --- 1.8 Ganchos opcionais para PyTorch (se estiver disponível) ---
594
- from contextlib import suppress
595
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  with suppress(ImportError):
597
  import torch
598
- # Alinhar threads de Torch com OMP para evitar oversubscription
599
  if hasattr(torch, "set_num_threads"):
600
  torch.set_num_threads(max(1, OMP_THREADS))
601
  if hasattr(torch, "set_num_interop_threads"):
602
- # Interop baixo ajuda a estabilidade — normalmente 1 ou 2 é suficiente.
603
  torch.set_num_interop_threads(max(1, min(2, DATALOADER_WORKERS)))
604
 
605
- # --- 1.9 Outros parâmetros -
606
- OPTIMIZER_REFRESH_INTERVAL = 300 # segundos
 
 
 
607
  # =========================================================================
608
  # 2️.Configuração Inicial e Cálculos
609
  # =========================================================================
@@ -675,7 +582,6 @@ def ajustar_max_len(
675
  )
676
 
677
  return final_max_len
678
-
679
  # ==============================
680
  # 3.Optimizer Refresh Callback
681
  # ==============================
@@ -1044,41 +950,41 @@ class DynamicAccumulationCallback(TrainerCallback):
1044
 
1045
  return control
1046
 
1047
- #--------------------------------------------------
1048
  # Variáveis Globais
1049
- model = None # variável global
1050
- tokenizer = None
 
 
 
1051
 
1052
  train_progress = {
1053
- "current": 0,
1054
- "total": 1,
1055
- "percent": 0,
1056
- "status": "not started",
1057
- "message": "Aguardando início do treino."
1058
  }
1059
- training_logs = [] # se já usas este array, mantém; (ver #3 para acrescentar linhas nele)
1060
- epoch_losses = []
1061
- all_data = [] # Armazena os dados do último treino para poder continuar
1062
-
1063
- chat_model = None
1064
- chat_tokenizer = None
1065
- CURRENT_ACCUM_STEPS = None
1066
- #CURRENT_ACCUM_STEPS = 1 # Valor inicial padrão
1067
-
1068
- # --- NOVOS CAMPOS (para interface mostrar info de accumulation e nº total de passos) ---
1069
- LAST_ACCUM_ORIGIN = None # "manual" | "auto" | None
1070
- TOTAL_TRAIN_STEPS = None # total de passos desta execução (após tokenização/Trainer)
1071
- BASE_BATCH_SIZE_EFFECTIVE = 4 # (opcional) mantém aqui para calcular Effective Batch no /status
1072
-
1073
- # -------------------------------------------------
1074
- training_logs.append(f"[INFO] accumulation inicial = {CURRENT_ACCUM_STEPS} (auto)")
1075
- # --- Execução dos Cálculos Iniciais ---
1076
- ACCUMULATION_STEPS = ajustar_accumulation_steps()
1077
- EFFECTIVE_BATCH_SIZE = BASE_BATCH_SIZE * ACCUMULATION_STEPS
1078
-
1079
- LAST_ACCUM_ORIGIN = "auto" # indica que veio do ajuste automático
1080
  BASE_BATCH_SIZE_EFFECTIVE = EFFECTIVE_BATCH_SIZE
1081
 
 
 
1082
  #----------------------------
1083
  # Função principal de treino
1084
  #----------------------------
@@ -1088,46 +994,34 @@ def train_model_lora(
1088
  initial_epochs_completed=0, train_mode="new_train"
1089
  ):
1090
  """
1091
- Função principal de treino LoRA (Low-Rank Adaptation)
1092
- Corrigido para evitar IndentationError e melhorar a legibilidade.
1093
  """
1094
  # ---------------- VARIÁVEIS GLOBAIS ----------------
1095
- global training_logs, train_progress, epoch_losses
 
1096
  training_logs.clear()
1097
- train_progress.update({
1098
- "status": "loading",
1099
- "percent": 0,
1100
- "message": "Iniciando processo de treino..."
1101
- })
1102
- log_info("Iniciando processo de treino...")
1103
  all_data = file_data
1104
 
1105
- # ---------------- LOGGING INICIAL ----------------
1106
- # log_info(f"CPU: {CPU_COUNT} núcleos, {TOTAL_RAM_GB:.1f} GB RAM")
 
 
 
 
 
 
 
 
 
 
 
 
1107
  log_info(f"OMP Threads: {OMP_THREADS}, Dataloader Workers: {DATALOADER_WORKERS}")
1108
  log_info(f"Batch base: {BASE_BATCH_SIZE}, Steps iniciais: {ACCUMULATION_STEPS}")
1109
  log_info(f"Batch efetivo inicial: {EFFECTIVE_BATCH_SIZE}")
1110
 
1111
- # ----------------------------------------------------
1112
- # O status inicial é "starting" ou "resuming"
1113
- initial_status_message = "Iniciando processo de treino..."
1114
- if lora_adapter_to_load:
1115
- initial_status_message = "Retomando treino a partir de adaptador LoRA salvo..."
1116
-
1117
- train_progress = {
1118
- "current": 0,
1119
- "total": 1,
1120
- "percent": 0,
1121
- "status": "starting",
1122
- "message": initial_status_message
1123
- }
1124
- log_info(initial_status_message)
1125
-
1126
- # Guarda os dados para a função 'continuar'
1127
- all_data = file_data # Atualiza a global all_data com os dados atuais do treino
1128
-
1129
- # ----------------------------------------------------
1130
- # Função auxiliar: detectar camadas LoRA automaticamente
1131
  def guess_lora_targets(model):
1132
  names = {name for name, _ in model.named_modules()}
1133
  if any("q_proj" in n and "v_proj" in n for n in names):
@@ -1141,26 +1035,18 @@ def train_model_lora(
1141
  log_warning("Não foi possível detetar os target_modules. Usando ['q_proj', 'v_proj'] por defeito.")
1142
  return ["q_proj", "v_proj"]
1143
 
1144
- # ----------------------------------------------------
1145
- # BLOCO NOVO PARA XPU
1146
- """
1147
- Seleção de dispositivo (CPU / CUDA / XPU)
1148
- Este patch substitui o bloco onde escolhes device_map/dtype durante o carregamento do modelo.
1149
- Adiciona a opção "xpu" antes de cair para CUDA/CPU.
1150
- """
1151
-
1152
- # ----------------------------------------------------
1153
- # Bloco 1: Carregar Modelo e Tokenizer
1154
  try:
1155
  log_step("A carregar modelo e tokenizer base...")
1156
-
1157
  os.makedirs("offload", exist_ok=True)
1158
-
1159
  global tokenizer
1160
  tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=True)
1161
 
1162
- # Garantir tokens especiais e consistência
1163
- log_info(f"Tokens antes do ajuste: PAD={tokenizer.pad_token}, EOS={tokenizer.eos_token}, BOS={tokenizer.bos_token}")
 
 
1164
  if tokenizer.pad_token is None:
1165
  tokenizer.add_special_tokens({'pad_token': '<PAD>'})
1166
  if tokenizer.eos_token is None:
@@ -1175,53 +1061,26 @@ def train_model_lora(
1175
  f"UNK={tokenizer.unk_token}"
1176
  )
1177
 
1178
- # ------- Substituído para XPU ----------
1179
- # Seleção de dispositivo (CPU / CUDA / XPU)
1180
- use_xpu = hasattr(torch, "xpu") and torch.xpu.is_available()
1181
- if use_xpu:
1182
- # Em Intel, BF16 costuma ser a melhor escolha
1183
- model_dtype = torch.bfloat16
1184
- model_device_map = {"": "xpu"}
1185
- print(f"[INFO] [{_ts()}] Device selecionado: XPU (Intel iGPU) | dtype={model_dtype}")
1186
- elif torch.cuda.is_available():
1187
- model_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
1188
- model_device_map = "auto"
1189
- print(f"[INFO] [{_ts()}] Device selecionado: CUDA | dtype={model_dtype}")
1190
- else:
1191
- model_dtype = torch.bfloat16 # bf16 em CPU é ~2x mais rápido que float32
1192
- model_device_map = "cpu"
1193
- print(f"[INFO] [{_ts()}] Device selecionado: CPU | dtype={model_dtype}")
1194
-
1195
- # Carregar modelo base
1196
  model = AutoModelForCausalLM.from_pretrained(
1197
  model_path,
1198
- device_map=model_device_map,
1199
  offload_folder="offload",
1200
- dtype=model_dtype,
1201
- low_cpu_mem_usage=True
1202
  )
1203
-
1204
- # Fallback: se o device_map não moveu o modelo (versões antigas)
1205
- if use_xpu:
1206
- try:
1207
- model.to("xpu")
1208
- except Exception as e:
1209
- print(f"[WARN] [{_ts()}] .to('xpu') falhou (Transformers antigo?). "
1210
- f"A continuar com device_map… Detalhe: {e}")
1211
-
1212
  model.resize_token_embeddings(len(tokenizer))
1213
  model.config.use_cache = False
1214
- model.gradient_checkpointing_enable() # economiza memória e acelera CPU
1215
- # ------- Fim do bloco novo XPU ----------
1216
-
1217
- #-------------------------------------------------
1218
- # --- Variáveis para controle de fluxo ---
1219
- resume_from_trainer_checkpoint = None
1220
- lora_adapter_path_final = os.path.join(output_dir, "lora_model")
1221
- lora_adapter_to_load_for_training = None # Variável para controlar qual LoRA carregar para o treino
1222
- lora_model_loaded_for_initial_eval = False # Adicione esta variável para controle
1223
-
1224
- #---------------------------------------------------------------------
1225
  # --- Lógica de 4 opções ---
1226
  # Prioridade:
1227
  #1. Checkpoint do Trainer
 
91
  Retorna (P_IDS, E_IDS, meta_dict).
92
  """
93
  logical = psutil.cpu_count(logical=True) or os.cpu_count() or 1
94
+
95
  # 1) Windows EfficiencyClass
96
  if platform.system() == "Windows":
97
  try:
 
107
  ("EfficiencyClass", ct.c_ubyte),
108
  ("Reserved", ct.c_ubyte * 20),
109
  ("GroupCount", ct.c_ushort)]
 
110
 
111
  class SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX_HEADER(ct.Structure):
112
  _fields_ = [("Relationship", ct.c_int),
 
135
  ga = GROUP_AFFINITY.from_buffer(buf, ga_offset + i * ct.sizeof(GROUP_AFFINITY))
136
  mask = ga.Mask
137
  if single_group:
 
138
  for bit in range(64):
139
  if (mask >> bit) & 1:
140
  eff_by_logical[bit] = eff
141
  else:
142
+ # Multi-grupo (>64 lógicos): mapping global complexo, cai para fallback.
 
143
  pass
144
  offset += size
145
 
 
147
  p_ids = sorted([i for i in range(logical) if eff_by_logical.get(i, 0) == 0])
148
  e_ids = sorted([i for i in range(logical) if eff_by_logical.get(i, 0) > 0])
149
  return p_ids, e_ids, {"method": "windows_efficiencyclass", "notes": []}
150
+ except Exception:
151
+ pass # continua para Linux/fallback
 
152
 
153
  # 2) Linux sysfs core_type
154
  if platform.system() == "Linux":
 
157
  for cpu in range(logical):
158
  path = f"/sys/devices/system/cpu/cpu{cpu}/topology/core_type"
159
  try:
160
+ with open(path) as f:
161
  val = f.read().strip()
162
  except FileNotFoundError:
163
  val = None
164
  if val is None:
 
165
  p_ids = e_ids = []
166
  break
167
  try:
 
179
  notes = []
180
  if unknown:
181
  notes.append(f"{len(unknown)} CPUs com core_type=Unknown (tratados como P).")
182
+ p_ids = sorted(p_ids + unknown) # conservador: desconhecidos como P
 
183
  return sorted(p_ids), sorted(e_ids), {"method": "linux_core_type", "notes": notes}
184
  except Exception:
185
  pass
 
228
  if not GetLPIEx(RELATION_PROCESSOR_CORE, ct.byref(buf), ct.byref(buf_size)):
229
  return []
230
 
 
231
  siblings = []
232
  offset = 0
233
  single_group = (logical <= 64)
 
241
  for i in range(pr.GroupCount):
242
  ga = GROUP_AFFINITY.from_buffer(buf, ga_offset + i * ct.sizeof(GROUP_AFFINITY))
243
  mask = ga.Mask
244
+ for bit in range(64):
245
+ if (mask >> bit) & 1:
246
+ core_logicals.append(bit) # single/multi-grupo: aproximação razoável
 
 
 
 
 
 
247
  if core_logicals:
248
  siblings.append(sorted(set(core_logicals)))
249
  offset += size
250
 
251
+ return sorted(siblings, key=lambda s: min(s) if s else 1e9)
 
 
252
  except Exception:
253
  return []
254
 
255
  elif platform.system() == "Linux":
 
256
  try:
257
+ sibs = []
258
  for cpu in range(logical):
259
  path = f"/sys/devices/system/cpu/cpu{cpu}/topology/thread_siblings_list"
260
  try:
261
+ with open(path) as f:
262
  txt = f.read().strip()
263
  except FileNotFoundError:
264
  return []
 
265
  items = []
266
  for part in txt.split(","):
267
  if "-" in part:
 
271
  items.append(int(part))
272
  sibs.append(sorted(set(items)))
273
  # Deduplica sublistas iguais
 
274
  seen = set()
275
+ uniq = []
276
  for s in sibs:
277
  t = tuple(s)
278
  if t not in seen:
279
  seen.add(t)
280
  uniq.append(s)
281
+ return sorted(uniq, key=lambda s: min(s))
 
282
  except Exception:
283
  return []
284
 
285
  return []
286
 
287
+
288
  def order_by_physical_first(candidates, siblings_map):
289
  """
290
+ Reordena 'candidates' para usar primeiro 1 logical por core físico (evita siblings logo de início).
291
  Se 'siblings_map' estiver vazio, retorna candidatos ordenados naturalmente.
292
  """
293
  if not siblings_map:
294
  return sorted(candidates)
295
 
296
  cand_set = set(candidates)
297
+ first_pass = [next((x for x in g if x in cand_set), None) for g in siblings_map]
298
+ first_pass = [x for x in first_pass if x is not None]
299
+
300
+ others = [x for g in siblings_map for x in g if x in cand_set and x not in first_pass]
 
 
 
 
 
 
 
 
 
 
 
301
  leftovers = [x for x in sorted(candidates) if x not in first_pass and x not in others]
302
  return first_pass + others + leftovers
303
 
304
  # =========================================================================
305
+ # 1. Configuração base de hardware, Otimização e Hiperparâmetros
306
  # =========================================================================
307
+
308
  # --- 1.1 Hardware ---
309
+ LOGICAL_CPUS = psutil.cpu_count(logical=True) or os.cpu_count() or 16
310
  PHYSICAL_CPUS = psutil.cpu_count(logical=False) or max(1, LOGICAL_CPUS // 2)
311
+ TOTAL_RAM_GB = psutil.virtual_memory().total / (1024 ** 3)
312
+
313
  # ===============================================================
314
  # Ajuste de performance (auto/manual)
315
  # ===============================================================
316
  OMP_THREADS_UTILIZATION = 0.85
317
+ DETECTION_PERFORMANCE = "auto" # "auto" | "manual"
318
+ CORES_UTILIZATION = 1.0
 
 
 
 
 
319
 
320
+ def compute_cores(cpu_count: int, utilization: float) -> int:
321
+ """Aplica uma utilização fracional a um contagem de CPUs com clamp [1, cpu_count]."""
322
+ return max(1, min(int(math.floor(cpu_count * utilization)), cpu_count))
323
 
324
+ # NOTA: compute_effective_cores e compute_omp_threads faziam exactamente o mesmo;
325
+ # ambos substituídos por compute_cores acima. Usos em baixo são drop-in equivalentes.
326
 
327
+ EFFECTIVE_LOGICAL_CPUS = compute_cores(LOGICAL_CPUS, CORES_UTILIZATION)
 
 
328
 
329
+ # --- Detectar núcleos P/E e filtrar para o limite efectivo ---
330
+ _DET_P_full, _DET_E_full, _META = detect_core_types()
331
  _DET_P = [i for i in _DET_P_full if i < EFFECTIVE_LOGICAL_CPUS]
332
  _DET_E = [i for i in _DET_E_full if i < EFFECTIVE_LOGICAL_CPUS]
333
 
 
 
334
  # ===============================================================
335
+ # Meta-informação de arranque (modo auto/manual)
336
  # ===============================================================
337
+ OMP_THREADS = compute_cores(EFFECTIVE_LOGICAL_CPUS, OMP_THREADS_UTILIZATION)
338
+
339
  if DETECTION_PERFORMANCE.lower() == "auto":
340
+ total_det = len(_DET_P) + len(_DET_E)
341
+ if total_det == 0:
 
342
  _AUTO_META = "auto (fallback homogéneo)"
343
+ elif len(_DET_P) / total_det >= 0.5:
344
+ _AUTO_META = f"auto (balanceado P/E → OMP={OMP_THREADS})"
345
  else:
346
+ _AUTO_META = f"auto (balanceado E-heavy OMP={OMP_THREADS})"
 
 
 
 
347
  else:
348
  _AUTO_META = "manual"
349
 
 
350
  print(f"[INFO] [{_ts()}] Modo de desempenho: {DETECTION_PERFORMANCE} ({_AUTO_META})")
351
 
352
+ # --- 1.2 Alocação de cores para tensores (OMP) e DataLoader ---
353
+ siblings = get_core_siblings()
 
 
 
 
 
 
 
 
 
 
 
354
  ordered_P = order_by_physical_first(_DET_P, siblings)
355
  ordered_E = order_by_physical_first(_DET_E, siblings)
356
 
357
+ needed = OMP_THREADS
358
  P_CORE_IDS = []
359
+
360
  if ordered_P:
361
+ take = ordered_P[:min(needed, len(ordered_P))]
362
+ P_CORE_IDS.extend(take)
363
+ needed -= len(take)
364
 
 
365
  if needed > 0 and ordered_E:
366
+ take = [i for i in ordered_E if i not in P_CORE_IDS][:needed]
367
+ P_CORE_IDS.extend(take)
368
+ needed -= len(take)
369
 
370
+ # Fallback homogéneo
371
  if not P_CORE_IDS:
 
372
  P_CORE_IDS = list(range(min(OMP_THREADS, EFFECTIVE_LOGICAL_CPUS)))
 
373
 
374
+ # Secundários (DataLoader): E sobrantes P sobrantes restantes
375
  used = set(P_CORE_IDS)
376
+ available = set(range(EFFECTIVE_LOGICAL_CPUS))
377
+ secondary = (
378
+ [i for i in _DET_E if i not in used and i in available] +
379
+ [i for i in _DET_P if i not in used and i in available] +
380
+ [i for i in available if i not in used and i not in _DET_P and i not in _DET_E]
381
+ )
 
 
 
 
 
 
 
 
 
 
 
382
  _seen = set()
383
  REMAINING_CORE_IDS = [x for x in secondary if not (x in _seen or _seen.add(x))]
384
 
385
+ DATALOADER_WORKERS = min(len(REMAINING_CORE_IDS), max(0, EFFECTIVE_LOGICAL_CPUS - len(P_CORE_IDS)))
386
 
387
+ # Variáveis de ambiente
388
+ os.environ["OMP_NUM_THREADS"] = str(OMP_THREADS)
389
+ os.environ["MKL_NUM_THREADS"] = str(OMP_THREADS)
 
 
 
 
 
390
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
391
 
392
 
 
393
  def set_affinity(core_ids):
394
  try:
395
  if platform.system() == "Linux":
 
400
  print(f"[WARN] [{_ts()}] Não foi possível definir afinidade: {e}")
401
 
402
 
403
+ final_affinity_cores = sorted(i for i in set(P_CORE_IDS + REMAINING_CORE_IDS) if i < EFFECTIVE_LOGICAL_CPUS)
 
 
 
 
 
 
404
  set_affinity(final_affinity_cores)
405
 
406
+ # ---------------------------------------------------------------
407
+ # Logs de arranque (apenas uma vez, evita duplicação no reload do Flask)
408
+ # ---------------------------------------------------------------
409
+ if os.environ.get("WERKZEUG_RUN_MAIN") != "true" and not globals().get("_LOGS_PRINTED", False):
410
  _LOGS_PRINTED = True
411
+ has_e = bool(_DET_E)
412
 
 
 
 
 
 
 
 
413
  print(f"[INFO] [{_ts()}] Método de deteção: {_META.get('method')}")
414
+ for note in _META.get("notes", []):
415
+ print(f"[INFO] [{_ts()}] Nota: {note}")
 
 
 
 
416
  print(f"[INFO] [{_ts()}] Detetados (filtrados) → P: {len(_DET_P)} | E: {len(_DET_E)}")
417
 
 
 
 
 
 
 
 
418
  # Tensores
419
+ P_from_P = [i for i in P_CORE_IDS if i in _DET_P]
420
+ P_from_E = [i for i in P_CORE_IDS if i in _DET_E]
421
  if has_e:
422
+ e_str = f", +E: {len(P_from_E)} {sorted(P_from_E)}" if P_from_E else ""
423
+ print(f"[INFO] [{_ts()}] Tensores (OMP={OMP_THREADS}) → P: {len(P_from_P)} {sorted(P_from_P)}{e_str}")
 
 
 
 
424
  else:
425
  print(f"[INFO] [{_ts()}] Tensores (OMP={OMP_THREADS}) [homogéneo] → {len(P_CORE_IDS)} {sorted(P_CORE_IDS)}")
426
 
427
+ # DataLoader
428
+ SEC_E = [i for i in REMAINING_CORE_IDS if i in _DET_E]
429
+ SEC_P = [i for i in REMAINING_CORE_IDS if i in _DET_P]
430
+ SEC_OTHER = [i for i in REMAINING_CORE_IDS if i not in _DET_P and i not in _DET_E]
431
  if has_e:
432
+ if SEC_E: print(f"[INFO] [{_ts()}] Secundário/DataLoader → E: {len(SEC_E)} {sorted(SEC_E)}")
433
+ if SEC_P: print(f"[INFO] [{_ts()}] Secundário/DataLoader → P: {len(SEC_P)} {sorted(SEC_P)}")
434
+ if SEC_OTHER: print(f"[INFO] [{_ts()}] Secundário/DataLoader → Outros: {len(SEC_OTHER)} {sorted(SEC_OTHER)}")
 
 
 
435
  else:
436
  print(f"[INFO] [{_ts()}] Cores restantes (homogéneo) → {len(REMAINING_CORE_IDS)} {sorted(REMAINING_CORE_IDS)}")
437
+
438
+ print("-" * 80)
439
+ print(f"[INFO] [{_ts()}] LOGICAL_CPUS: {LOGICAL_CPUS} | PHYSICAL_CPUS: {PHYSICAL_CPUS} | TOTAL_RAM_GB: {TOTAL_RAM_GB:.2f}")
440
+ print(f"[INFO] [{_ts()}] Cores Utilization: {CORES_UTILIZATION:.2%} → efetivos: {EFFECTIVE_LOGICAL_CPUS}")
 
 
441
  print(f"[INFO] [{_ts()}] OMP Threads Utilization: {OMP_THREADS_UTILIZATION:.2%}")
442
+ print(f"[INFO] [{_ts()}] OMP Threads: {OMP_THREADS} | Dataloader Workers: {DATALOADER_WORKERS}")
443
  if DATALOADER_WORKERS == 0:
444
  print(f"[INFO] [{_ts()}] Sem cores sobrantes para DataLoader (todos {EFFECTIVE_LOGICAL_CPUS} alocados a OMP).")
 
 
445
  total_allocated = len(set(P_CORE_IDS + REMAINING_CORE_IDS))
446
+ print(f"[INFO] [{_ts()}] Total alocados (OMP + DataLoader): {total_allocated} (de {EFFECTIVE_LOGICAL_CPUS} efetivos)")
447
+ print("-" * 80)
 
 
 
448
  print(f"[INFO] [{_ts()}] Iniciando processo de treino...\n")
449
  print("-" * 80)
 
450
  #--------------------------------------------------------------------------------------------------
451
+ # =========================================================================
452
+ # 1.3 Parâmetros base do treino
453
+ # =========================================================================
454
+ BASE_BATCH_SIZE = 1
455
+ INITIAL_ACCUMULATION_MIN_STEPS = 2
456
+ INITIAL_ACCUMULATION_MAX_STEPS = 4
457
+ BASE_EVAL_SIZE = 5
458
+ BASE_LEARNING_RATE = 1e-4
459
+ LR_SCHEDULER_TYPE = "constant_with_warmup"
460
+ WARMUP_RATIO = 0.03
461
+ LOGGIN_STEPS = 5
462
+ SAVE_STRATEGY = "steps"
463
+ SAVE_STEPS = 50
464
+ EVAL_STEPS = 5
465
+ WEIGHT_DECAY = 0.1
466
+ OPTIM = "adamw_torch"
467
+ # =========================================================================
468
+ # 1.4 1.6 RAM e MAX_LEN
469
+ # =========================================================================
470
+ TARGET_RAM_UTILIZATION = 0.95 # Limite máximo de uso da RAM total
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
 
472
+ # Custo empírico de RAM por batch (ajustar conforme modelo)
473
+ ESTIMATED_BATCH_GB = 0.30 # 0.4 para LLaMA
474
+
475
+ # NOTA: TARGET_ACCUMULATION_RAM_UTILIZATION foi removido — era um alias de
476
+ # TARGET_RAM_UTILIZATION e não acrescentava nada. Usa TARGET_RAM_UTILIZATION directamente.
477
+
478
+ BASE_MAX_LEN = 256
479
+ TARGET_MAX_LEN_UTILIZATION = 0.75
480
+ MAX_LEN_INCREMENT = 256
481
+ MAX_LEN_CAP = 256
482
+ DEFAULT_FALLBACK_CAP = 8192
483
+ TOKENIZER_SENTINEL_CAP = 10_000_000
484
+
485
+ # Custo empírico de RAM para o dataset (ajustar conforme modelo)
486
+ ESTIMATED_BASE_DATASET_RAM_GB = 1.6
487
+ COST_PER_INCREMENT_GB = 0.30 # 0.45 para LLaMA
488
+
489
+ # =========================================================================
490
+ # 1.7 DynamicAccumulationCallback
491
+ # =========================================================================
492
+ DYNAMIC_ACCUMULATION_MAX_STEPS = 256
493
+ DYNAMIC_ACCUMULATION_TARGET_UTIL = 0.99 # mantido para referência futura
494
+ DYNAMIC_ACCUMULATION_HIGH_RAM_LIMIT = 98.5
495
+ DYNAMIC_ACCUMULATION_LOW_RAM_LIMIT = 40.0
496
+
497
+ # =========================================================================
498
+ # 1.8 Ganchos opcionais para PyTorch
499
+ # =========================================================================
500
+ from contextlib import suppress
501
  with suppress(ImportError):
502
  import torch
 
503
  if hasattr(torch, "set_num_threads"):
504
  torch.set_num_threads(max(1, OMP_THREADS))
505
  if hasattr(torch, "set_num_interop_threads"):
506
+ # Interop baixo ajuda a estabilidade — normalmente 1 ou 2 é suficiente
507
  torch.set_num_interop_threads(max(1, min(2, DATALOADER_WORKERS)))
508
 
509
+ # =========================================================================
510
+ # 1.9 Outros parâmetros
511
+ # =========================================================================
512
+ OPTIMIZER_REFRESH_INTERVAL = 600 # segundos
513
+
514
  # =========================================================================
515
  # 2️.Configuração Inicial e Cálculos
516
  # =========================================================================
 
582
  )
583
 
584
  return final_max_len
 
585
  # ==============================
586
  # 3.Optimizer Refresh Callback
587
  # ==============================
 
950
 
951
  return control
952
 
953
+ # =========================================================================
954
  # Variáveis Globais
955
+ # =========================================================================
956
+ model = None
957
+ tokenizer = None
958
+ chat_model = None
959
+ chat_tokenizer = None
960
 
961
  train_progress = {
962
+ "current": 0,
963
+ "total": 1,
964
+ "percent": 0,
965
+ "status": "not started",
966
+ "message": "Aguardando início do treino.",
967
  }
968
+
969
+ training_logs = [] # linhas de log acumuladas durante o treino
970
+ epoch_losses = []
971
+ all_data = [] # dados do último treino (permite continuar)
972
+
973
+ CURRENT_ACCUM_STEPS = None # None = sem override manual activo
974
+ LAST_ACCUM_ORIGIN = None # "manual" | "auto" | None
975
+ TOTAL_TRAIN_STEPS = None # preenchido após tokenização/Trainer
976
+ BASE_BATCH_SIZE_EFFECTIVE = 4 # usado em /api/train_status para Effective Batch
977
+
978
+ # =========================================================================
979
+ # Cálculos iniciais de acumulação
980
+ # =========================================================================
981
+ ACCUMULATION_STEPS = ajustar_accumulation_steps()
982
+ EFFECTIVE_BATCH_SIZE = BASE_BATCH_SIZE * ACCUMULATION_STEPS
983
+ LAST_ACCUM_ORIGIN = "auto"
 
 
 
 
 
984
  BASE_BATCH_SIZE_EFFECTIVE = EFFECTIVE_BATCH_SIZE
985
 
986
+ # Log após o cálculo — agora ACCUMULATION_STEPS tem valor real
987
+ training_logs.append(f"[INFO] accumulation inicial = {ACCUMULATION_STEPS} (auto)")
988
  #----------------------------
989
  # Função principal de treino
990
  #----------------------------
 
994
  initial_epochs_completed=0, train_mode="new_train"
995
  ):
996
  """
997
+ Função principal de treino LoRA (Low-Rank Adaptation).
998
+ CPU-only: XPU/CUDA removidos por decisão de arquitectura.
999
  """
1000
  # ---------------- VARIÁVEIS GLOBAIS ----------------
1001
+ global training_logs, train_progress, epoch_losses, all_data
1002
+
1003
  training_logs.clear()
 
 
 
 
 
 
1004
  all_data = file_data
1005
 
1006
+ # ---------------- ESTADO INICIAL ----------------
1007
+ initial_status_message = (
1008
+ "Retomando treino a partir de adaptador LoRA salvo..."
1009
+ if lora_adapter_to_load else
1010
+ "Iniciando processo de treino..."
1011
+ )
1012
+ train_progress.update({
1013
+ "current": 0,
1014
+ "total": 1,
1015
+ "percent": 0,
1016
+ "status": "starting",
1017
+ "message": initial_status_message,
1018
+ })
1019
+ log_info(initial_status_message)
1020
  log_info(f"OMP Threads: {OMP_THREADS}, Dataloader Workers: {DATALOADER_WORKERS}")
1021
  log_info(f"Batch base: {BASE_BATCH_SIZE}, Steps iniciais: {ACCUMULATION_STEPS}")
1022
  log_info(f"Batch efetivo inicial: {EFFECTIVE_BATCH_SIZE}")
1023
 
1024
+ # ---------------- AUXILIAR: detetar camadas LoRA ----------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1025
  def guess_lora_targets(model):
1026
  names = {name for name, _ in model.named_modules()}
1027
  if any("q_proj" in n and "v_proj" in n for n in names):
 
1035
  log_warning("Não foi possível detetar os target_modules. Usando ['q_proj', 'v_proj'] por defeito.")
1036
  return ["q_proj", "v_proj"]
1037
 
1038
+ # ---------------- BLOCO 1: Carregar Modelo e Tokenizer ----------------
 
 
 
 
 
 
 
 
 
1039
  try:
1040
  log_step("A carregar modelo e tokenizer base...")
 
1041
  os.makedirs("offload", exist_ok=True)
1042
+
1043
  global tokenizer
1044
  tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=True)
1045
 
1046
+ log_info(
1047
+ f"Tokens antes do ajuste: PAD={tokenizer.pad_token}, "
1048
+ f"EOS={tokenizer.eos_token}, BOS={tokenizer.bos_token}"
1049
+ )
1050
  if tokenizer.pad_token is None:
1051
  tokenizer.add_special_tokens({'pad_token': '<PAD>'})
1052
  if tokenizer.eos_token is None:
 
1061
  f"UNK={tokenizer.unk_token}"
1062
  )
1063
 
1064
+ # CPU-only: bf16 é ~2× mais rápido que float32 em CPU moderno
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1065
  model = AutoModelForCausalLM.from_pretrained(
1066
  model_path,
1067
+ device_map="cpu",
1068
  offload_folder="offload",
1069
+ dtype=torch.bfloat16,
1070
+ low_cpu_mem_usage=True,
1071
  )
 
 
 
 
 
 
 
 
 
1072
  model.resize_token_embeddings(len(tokenizer))
1073
  model.config.use_cache = False
1074
+ model.gradient_checkpointing_enable()
1075
+ log_info("Modelo e tokenizer carregados (CPU / bfloat16).")
1076
+
1077
+ # --- Variáveis de controlo de fluxo (usadas mais abaixo) ---
1078
+ resume_from_trainer_checkpoint = None
1079
+ lora_adapter_path_final = os.path.join(output_dir, "lora_model")
1080
+ lora_adapter_to_load_for_training = None
1081
+ lora_model_loaded_for_initial_eval = False
1082
+
1083
+ #----------------------------------------------------------------------------------
 
1084
  # --- Lógica de 4 opções ---
1085
  # Prioridade:
1086
  #1. Checkpoint do Trainer