PowerMachine commited on
Commit
6816cc6
·
verified ·
1 Parent(s): 52f7e71

Delete utils/monitoring.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. utils/monitoring.py +0 -729
utils/monitoring.py DELETED
@@ -1,729 +0,0 @@
1
- """monitoring.py — Monitoramento completo e evolução do CNN-BiGRU.
2
-
3
- Rastreia métricas de treinamento, inferência, uso de recursos, evolução
4
- dos componentes (EWC, Medusa, CyclicReasoning, etc.) e exporta relatórios.
5
-
6
- ==============================================================================
7
- FUNCIONALIDADES
8
- ==============================================================================
9
-
10
- 1. Métricas de treinamento:
11
- - Loss total, loss_main, loss_medusa, loss_ewc, loss_hallucination
12
- - Perplexidade (PPL)
13
- - Learning rate (G, V)
14
- - Norma do gradiente
15
- - Hypothesis activations
16
- - Synergy history
17
-
18
- 2. Métricas de inferência:
19
- - Tokens gerados
20
- - Medusa accept rate
21
- - Tempo por token (ms)
22
- - Throughput (tokens/s)
23
- - Memória usada (MB)
24
-
25
- 3. Métricas de evolução:
26
- - Loss/PPL ao longo do tempo (por época)
27
- - Convergência do CyclicReasoning (n_cycles, deltas)
28
- - Codebook usage do VQ-VAE-2
29
- - Quantização stats (memória economizada, etc.)
30
- - EWC penalty evolution
31
-
32
- 4. Métricas de sistema:
33
- - CPU/Memory usage
34
- - Device info
35
- - Runtime info (Xeon AVX512/AMX)
36
-
37
- 5. Exportação:
38
- - JSON report
39
- - CSV time series
40
- - Markdown summary
41
-
42
- ==============================================================================
43
- USO
44
- ==============================================================================
45
-
46
- from cnn_bigru.utils.monitoring import Monitor
47
-
48
- monitor = Monitor(output_dir="/path/to/logs")
49
- monitor.start_training()
50
- for epoch in range(N):
51
- for batch in dataloader:
52
- ...
53
- monitor.log_batch({
54
- "loss": loss.item(),
55
- "ppl": ppl,
56
- "lr": lr,
57
- "grad_norm": grad_norm,
58
- })
59
- monitor.log_epoch({"epoch": epoch, "avg_loss": ...})
60
- monitor.end_training()
61
- monitor.export_report()
62
-
63
- Autor: CNN-BiGRU Project
64
- """
65
- from __future__ import annotations
66
-
67
- import csv
68
- import json
69
- import logging
70
- import os
71
- import time
72
- from collections import defaultdict, deque
73
- from dataclasses import dataclass, field, asdict
74
- from pathlib import Path
75
- from typing import Any, Dict, List, Optional, Union
76
-
77
- import torch
78
-
79
- logger = logging.getLogger(__name__)
80
-
81
-
82
- # ============================================================================
83
- # Data Classes
84
- # ============================================================================
85
-
86
- @dataclass
87
- class BatchMetrics:
88
- """Métricas de um batch."""
89
- step: int
90
- epoch: int
91
- batch_in_epoch: int
92
- timestamp: float
93
- loss: float = 0.0
94
- loss_main: float = 0.0
95
- loss_medusa: float = 0.0
96
- loss_ewc: float = 0.0
97
- loss_hallucination: float = 0.0
98
- loss_total: float = 0.0
99
- ppl: float = 0.0
100
- lr_g: float = 0.0
101
- lr_v: float = 0.0
102
- grad_norm_g: float = 0.0
103
- grad_norm_v: float = 0.0
104
- v_mean: float = 0.0
105
- hypothesis_activations: int = 0
106
- elapsed_ms: float = 0.0
107
- extra: Dict[str, Any] = field(default_factory=dict)
108
-
109
-
110
- @dataclass
111
- class EpochMetrics:
112
- """Métricas agregadas de uma época."""
113
- epoch: int
114
- avg_loss: float = 0.0
115
- avg_ppl: float = 0.0
116
- n_batches: int = 0
117
- n_hypothesis_activations: int = 0
118
- elapsed_s: float = 0.0
119
- best_loss: float = float("inf")
120
- worst_loss: float = 0.0
121
- synergy_history: List[Dict] = field(default_factory=list)
122
- extra: Dict[str, Any] = field(default_factory=dict)
123
-
124
-
125
- @dataclass
126
- class EvolutionMetrics:
127
- """Métricas de evolução dos componentes ao longo do tempo."""
128
- cyclic_reasoning_stats: List[Dict] = field(default_factory=list)
129
- vqvae2_codebook_usage: List[Dict] = field(default_factory=list)
130
- quantization_stats: List[Dict] = field(default_factory=list)
131
- ewc_penalty_evolution: List[Dict] = field(default_factory=list)
132
- medusa_accept_rate: List[Dict] = field(default_factory=list)
133
-
134
-
135
- @dataclass
136
- class SystemMetrics:
137
- """Métricas de sistema."""
138
- timestamp: float
139
- cpu_percent: float = 0.0
140
- memory_mb: float = 0.0
141
- gpu_memory_mb: float = 0.0
142
- n_active_threads: int = 0
143
-
144
-
145
- # ============================================================================
146
- # Monitor
147
- # ============================================================================
148
-
149
- class Monitor:
150
- """Monitor completo de treinamento, inferência e evolução.
151
-
152
- Args:
153
- output_dir: diretório para salvar logs e relatórios
154
- max_history: número máximo de métricas em memória (FIFO)
155
- log_every: intervalo de log (em batches)
156
- track_system: se True, rastreia CPU/memória (requer psutil)
157
- """
158
-
159
- def __init__(
160
- self,
161
- output_dir: Optional[Union[str, Path]] = None,
162
- max_history: int = 10000,
163
- log_every: int = 1,
164
- track_system: bool = True,
165
- ):
166
- self.output_dir = Path(output_dir) if output_dir else None
167
- if self.output_dir:
168
- self.output_dir.mkdir(parents=True, exist_ok=True)
169
- self.max_history = max_history
170
- self.log_every = log_every
171
- self.track_system = track_system and self._psutil_available()
172
-
173
- # Histórico
174
- self.batch_history: deque = deque(maxlen=max_history)
175
- self.epoch_history: List[EpochMetrics] = []
176
- self.evolution = EvolutionMetrics()
177
- self.system_history: deque = deque(maxlen=max_history)
178
-
179
- # Estado
180
- self._training_active = False
181
- self._inference_active = False
182
- self._start_time: Optional[float] = None
183
- self._epoch_start_time: Optional[float] = None
184
- self._current_epoch = 0
185
- self._current_batch = 0
186
- self._global_step = 0
187
-
188
- # Agregados para época atual
189
- self._epoch_losses: List[float] = []
190
- self._epoch_ppls: List[float] = []
191
- self._epoch_hypothesis_activations = 0
192
- self._epoch_synergy_history: List[Dict] = []
193
-
194
- # Inferência
195
- self.inference_stats: Dict[str, Any] = {
196
- "total_tokens": 0,
197
- "total_time_ms": 0.0,
198
- "medusa_accepted": 0,
199
- "n_calls": 0,
200
- "fallback_to_greedy": 0,
201
- }
202
-
203
- # Component tracking
204
- self.component_status: Dict[str, bool] = {
205
- "ewc": False,
206
- "medusa": False,
207
- "cyclic_reasoning": False,
208
- "vqvae2": False,
209
- "quantization": False,
210
- "multimodal_attention": False,
211
- "context_window": False,
212
- }
213
-
214
- def _psutil_available(self) -> bool:
215
- try:
216
- import psutil # noqa: F401
217
- return True
218
- except ImportError:
219
- return False
220
-
221
- # ----------------------------------------------------------------------
222
- # Training lifecycle
223
- # ----------------------------------------------------------------------
224
-
225
- def start_training(self) -> None:
226
- """Inicia uma sessão de monitoramento de treino."""
227
- self._training_active = True
228
- self._start_time = time.time()
229
- self._current_epoch = 0
230
- self._current_batch = 0
231
- self._global_step = 0
232
- logger.info("Monitor: sessão de treino iniciada")
233
-
234
- def start_epoch(self, epoch: int) -> None:
235
- """Inicia uma nova época."""
236
- self._current_epoch = epoch
237
- self._epoch_start_time = time.time()
238
- self._epoch_losses = []
239
- self._epoch_ppls = []
240
- self._epoch_hypothesis_activations = 0
241
- self._epoch_synergy_history = []
242
-
243
- def log_batch(self, metrics: Dict[str, Any]) -> None:
244
- """Loga métricas de um batch.
245
-
246
- Args:
247
- metrics: dict com chaves como loss, ppl, lr, grad_norm, etc.
248
- """
249
- if not self._training_active:
250
- return
251
-
252
- self._global_step += 1
253
- self._current_batch += 1
254
-
255
- ts = time.time()
256
- batch_metrics = BatchMetrics(
257
- step=self._global_step,
258
- epoch=self._current_epoch,
259
- batch_in_epoch=self._current_batch,
260
- timestamp=ts,
261
- loss=metrics.get("loss", 0.0),
262
- loss_main=metrics.get("loss_main", 0.0),
263
- loss_medusa=metrics.get("loss_medusa", 0.0),
264
- loss_ewc=metrics.get("loss_ewc", 0.0),
265
- loss_hallucination=metrics.get("loss_hallucination", 0.0),
266
- loss_total=metrics.get("loss_total", metrics.get("loss", 0.0)),
267
- ppl=metrics.get("ppl", 0.0),
268
- lr_g=metrics.get("lr_g", metrics.get("lr", 0.0)),
269
- lr_v=metrics.get("lr_v", 0.0),
270
- grad_norm_g=metrics.get("grad_norm_g", metrics.get("grad_norm", 0.0)),
271
- grad_norm_v=metrics.get("grad_norm_v", 0.0),
272
- v_mean=metrics.get("v_mean", 0.0),
273
- hypothesis_activations=metrics.get("hypothesis_activations", 0),
274
- elapsed_ms=metrics.get("elapsed_ms", 0.0),
275
- extra={k: v for k, v in metrics.items()
276
- if k not in {"loss", "loss_main", "loss_medusa", "loss_ewc",
277
- "loss_hallucination", "loss_total", "ppl",
278
- "lr_g", "lr_v", "grad_norm_g", "grad_norm_v",
279
- "v_mean", "hypothesis_activations", "elapsed_ms",
280
- "lr", "grad_norm"}},
281
- )
282
- self.batch_history.append(batch_metrics)
283
-
284
- # Acumular para época
285
- if batch_metrics.loss > 0:
286
- self._epoch_losses.append(batch_metrics.loss)
287
- if batch_metrics.ppl > 0:
288
- self._epoch_ppls.append(batch_metrics.ppl)
289
- self._epoch_hypothesis_activations += batch_metrics.hypothesis_activations
290
- if "synergy_history" in metrics:
291
- self._epoch_synergy_history.extend(metrics["synergy_history"])
292
-
293
- # Log
294
- if self._global_step % self.log_every == 0:
295
- logger.info(
296
- "Batch %d (epoch %d) | loss=%.4f | ppl=%.2f | lr_g=%.2e | hyp=%d",
297
- self._global_step, self._current_epoch,
298
- batch_metrics.loss, batch_metrics.ppl, batch_metrics.lr_g,
299
- batch_metrics.hypothesis_activations,
300
- )
301
-
302
- # Track system metrics occasionally
303
- if self.track_system and self._global_step % 50 == 0:
304
- self._track_system()
305
-
306
- def end_epoch(self, extra: Optional[Dict[str, Any]] = None) -> EpochMetrics:
307
- """Finaliza a época atual e retorna métricas agregadas."""
308
- if self._epoch_start_time is None:
309
- logger.warning("end_epoch chamado sem start_epoch")
310
- return EpochMetrics(epoch=self._current_epoch)
311
-
312
- elapsed = time.time() - self._epoch_start_time
313
-
314
- # Agregar
315
- if self._epoch_losses:
316
- avg_loss = sum(self._epoch_losses) / len(self._epoch_losses)
317
- best_loss = min(self._epoch_losses)
318
- worst_loss = max(self._epoch_losses)
319
- else:
320
- avg_loss = best_loss = worst_loss = 0.0
321
-
322
- if self._epoch_ppls:
323
- avg_ppl = sum(self._epoch_ppls) / len(self._epoch_ppls)
324
- else:
325
- avg_ppl = 0.0
326
-
327
- epoch_metrics = EpochMetrics(
328
- epoch=self._current_epoch,
329
- avg_loss=avg_loss,
330
- avg_ppl=avg_ppl,
331
- n_batches=len(self._epoch_losses),
332
- n_hypothesis_activations=self._epoch_hypothesis_activations,
333
- elapsed_s=elapsed,
334
- best_loss=best_loss,
335
- worst_loss=worst_loss,
336
- synergy_history=list(self._epoch_synergy_history),
337
- extra=extra or {},
338
- )
339
- self.epoch_history.append(epoch_metrics)
340
-
341
- logger.info(
342
- "Época %d concluída | avg_loss=%.4f | avg_ppl=%.2f | hyp_act=%d | batches=%d | %.1fs",
343
- self._current_epoch, avg_loss, avg_ppl,
344
- self._epoch_hypothesis_activations, len(self._epoch_losses), elapsed,
345
- )
346
- return epoch_metrics
347
-
348
- def end_training(self) -> Dict[str, Any]:
349
- """Finaliza a sessão de treino e retorna resumo."""
350
- if self._start_time is None:
351
- return {}
352
- total_time = time.time() - self._start_time
353
- self._training_active = False
354
-
355
- summary = {
356
- "total_time_s": total_time,
357
- "total_epochs": len(self.epoch_history),
358
- "total_batches": self._global_step,
359
- "final_loss": self.epoch_history[-1].avg_loss if self.epoch_history else 0.0,
360
- "final_ppl": self.epoch_history[-1].avg_ppl if self.epoch_history else 0.0,
361
- "best_loss": min((e.avg_loss for e in self.epoch_history), default=0.0),
362
- "best_ppl": min((e.avg_ppl for e in self.epoch_history), default=0.0),
363
- "total_hypothesis_activations": sum(e.n_hypothesis_activations for e in self.epoch_history),
364
- }
365
- logger.info("Monitor: sessão de treino finalizada — %s", summary)
366
- return summary
367
-
368
- # ----------------------------------------------------------------------
369
- # Inference tracking
370
- # ----------------------------------------------------------------------
371
-
372
- def log_inference(
373
- self,
374
- n_tokens: int,
375
- elapsed_ms: float,
376
- medusa_accepted: int = 0,
377
- fallback_to_greedy: int = 0,
378
- ) -> None:
379
- """Loga uma chamada de inferência."""
380
- self.inference_stats["total_tokens"] += n_tokens
381
- self.inference_stats["total_time_ms"] += elapsed_ms
382
- self.inference_stats["medusa_accepted"] += medusa_accepted
383
- self.inference_stats["fallback_to_greedy"] += fallback_to_greedy
384
- self.inference_stats["n_calls"] += 1
385
-
386
- def get_inference_throughput(self) -> Dict[str, float]:
387
- """Retorna throughput de inferência."""
388
- s = self.inference_stats
389
- if s["total_time_ms"] == 0:
390
- return {"tokens_per_s": 0.0, "ms_per_token": 0.0, "medusa_accept_rate": 0.0}
391
- total_s = s["total_time_ms"] / 1000.0
392
- tps = s["total_tokens"] / total_s if total_s > 0 else 0.0
393
- mspt = s["total_time_ms"] / s["total_tokens"] if s["total_tokens"] > 0 else 0.0
394
- accept_rate = s["medusa_accepted"] / max(1, s["total_tokens"])
395
- return {
396
- "tokens_per_s": tps,
397
- "ms_per_token": mspt,
398
- "medusa_accept_rate": accept_rate,
399
- "n_calls": s["n_calls"],
400
- "total_tokens": s["total_tokens"],
401
- }
402
-
403
- # ----------------------------------------------------------------------
404
- # Evolution tracking (componentes específicos)
405
- # ----------------------------------------------------------------------
406
-
407
- def log_cyclic_reasoning(self, stats: Dict[str, Any]) -> None:
408
- """Loga estatísticas do CyclicReasoning."""
409
- stats["step"] = self._global_step
410
- stats["timestamp"] = time.time()
411
- self.evolution.cyclic_reasoning_stats.append(stats)
412
-
413
- def log_vqvae2_usage(self, stats: Dict[str, Any]) -> None:
414
- """Loga uso do codebook VQ-VAE-2."""
415
- stats["step"] = self._global_step
416
- stats["timestamp"] = time.time()
417
- self.evolution.vqvae2_codebook_usage.append(stats)
418
-
419
- def log_quantization(self, stats: Dict[str, Any]) -> None:
420
- """Loga estatísticas de quantização W8A8."""
421
- stats["step"] = self._global_step
422
- stats["timestamp"] = time.time()
423
- self.evolution.quantization_stats.append(stats)
424
-
425
- def log_ewc_penalty(self, penalty: float, num_tasks: int) -> None:
426
- """Loga evolução do penalty EWC."""
427
- self.evolution.ewc_penalty_evolution.append({
428
- "step": self._global_step,
429
- "timestamp": time.time(),
430
- "penalty": penalty,
431
- "num_tasks": num_tasks,
432
- })
433
-
434
- def log_medusa_accept(self, accepted: int, total: int) -> None:
435
- """Loga taxa de aceitação Medusa."""
436
- rate = accepted / max(1, total)
437
- self.evolution.medusa_accept_rate.append({
438
- "step": self._global_step,
439
- "timestamp": time.time(),
440
- "accepted": accepted,
441
- "total": total,
442
- "rate": rate,
443
- })
444
-
445
- def register_component(self, name: str, active: bool = True) -> None:
446
- """Registra que um componente está ativo."""
447
- if name in self.component_status:
448
- self.component_status[name] = active
449
-
450
- # ----------------------------------------------------------------------
451
- # System tracking
452
- # ----------------------------------------------------------------------
453
-
454
- def _track_system(self) -> None:
455
- """Rastreia métricas de sistema."""
456
- if not self.track_system:
457
- return
458
- try:
459
- import psutil
460
- cpu = psutil.cpu_percent(interval=None)
461
- mem = psutil.virtual_memory()
462
- sm = SystemMetrics(
463
- timestamp=time.time(),
464
- cpu_percent=cpu,
465
- memory_mb=mem.used / (1024 * 1024),
466
- n_active_threads=psutil.cpu_count() or 0,
467
- )
468
- # GPU memory (se disponível)
469
- if torch.cuda.is_available():
470
- try:
471
- gpu_mem = torch.cuda.memory_allocated() / (1024 * 1024)
472
- sm.gpu_memory_mb = gpu_mem
473
- except Exception:
474
- pass
475
- self.system_history.append(sm)
476
- except Exception as e:
477
- logger.debug("System tracking falhou: %s", e)
478
-
479
- # ----------------------------------------------------------------------
480
- # Exportação
481
- # ----------------------------------------------------------------------
482
-
483
- def export_report(self, output_path: Optional[Union[str, Path]] = None) -> Path:
484
- """Exporta relatório completo em JSON.
485
-
486
- Args:
487
- output_path: caminho do arquivo (default: output_dir/monitor_report.json)
488
-
489
- Returns:
490
- Path do arquivo salvo
491
- """
492
- if output_path is None:
493
- if self.output_dir is None:
494
- raise ValueError("output_dir ou output_path deve ser fornecido")
495
- output_path = self.output_dir / "monitor_report.json"
496
- output_path = Path(output_path)
497
- output_path.parent.mkdir(parents=True, exist_ok=True)
498
-
499
- # Construir relatório
500
- report = {
501
- "metadata": {
502
- "generated_at": time.time(),
503
- "training_active": self._training_active,
504
- "global_step": self._global_step,
505
- "current_epoch": self._current_epoch,
506
- },
507
- "components": dict(self.component_status),
508
- "training_summary": self._get_training_summary(),
509
- "epoch_history": [asdict(e) for e in self.epoch_history],
510
- "inference_stats": {
511
- **self.inference_stats,
512
- "throughput": self.get_inference_throughput(),
513
- },
514
- "evolution": {
515
- "cyclic_reasoning": self.evolution.cyclic_reasoning_stats[-50:],
516
- "vqvae2_codebook_usage": self.evolution.vqvae2_codebook_usage[-50:],
517
- "quantization_stats": self.evolution.quantization_stats[-10:],
518
- "ewc_penalty_evolution": self.evolution.ewc_penalty_evolution[-50:],
519
- "medusa_accept_rate": self.evolution.medusa_accept_rate[-50:],
520
- },
521
- "system_metrics": [asdict(s) for s in list(self.system_history)[-50:]],
522
- }
523
-
524
- with open(output_path, "w", encoding="utf-8") as f:
525
- json.dump(report, f, indent=2, default=str, ensure_ascii=False)
526
- logger.info("Monitor report salvo em: %s", output_path)
527
- return output_path
528
-
529
- def export_csv(self, output_path: Optional[Union[str, Path]] = None) -> Path:
530
- """Exporta métricas de batch em CSV."""
531
- if output_path is None:
532
- if self.output_dir is None:
533
- raise ValueError("output_dir ou output_path deve ser fornecido")
534
- output_path = self.output_dir / "batch_metrics.csv"
535
- output_path = Path(output_path)
536
- output_path.parent.mkdir(parents=True, exist_ok=True)
537
-
538
- if not self.batch_history:
539
- logger.warning("Sem batch history para exportar")
540
- return output_path
541
-
542
- # Pegar chaves do primeiro item + extras
543
- first = self.batch_history[0]
544
- fieldnames = ["step", "epoch", "batch_in_epoch", "timestamp",
545
- "loss", "loss_main", "loss_medusa", "loss_ewc",
546
- "loss_hallucination", "loss_total", "ppl",
547
- "lr_g", "lr_v", "grad_norm_g", "grad_norm_v",
548
- "v_mean", "hypothesis_activations", "elapsed_ms"]
549
-
550
- with open(output_path, "w", newline="", encoding="utf-8") as f:
551
- writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
552
- writer.writeheader()
553
- for m in self.batch_history:
554
- writer.writerow(asdict(m))
555
-
556
- logger.info("CSV metrics salvo em: %s (%d rows)", output_path, len(self.batch_history))
557
- return output_path
558
-
559
- def export_markdown_summary(self, output_path: Optional[Union[str, Path]] = None) -> Path:
560
- """Exporta resumo em Markdown."""
561
- if output_path is None:
562
- if self.output_dir is None:
563
- raise ValueError("output_dir ou output_path deve ser fornecido")
564
- output_path = self.output_dir / "monitor_summary.md"
565
- output_path = Path(output_path)
566
- output_path.parent.mkdir(parents=True, exist_ok=True)
567
-
568
- summary = self._get_training_summary()
569
- throughput = self.get_inference_throughput()
570
-
571
- lines = [
572
- "# Monitor Report — CNN-BiGRU",
573
- "",
574
- f"**Generated at:** {time.strftime('%Y-%m-%d %H:%M:%S')}",
575
- "",
576
- "## Components Status",
577
- "",
578
- ]
579
- for comp, active in self.component_status.items():
580
- icon = "[x]" if active else "[ ]"
581
- lines.append(f"- {icon} {comp}")
582
-
583
- lines.extend([
584
- "",
585
- "## Training Summary",
586
- "",
587
- f"- Total time: {summary.get('total_time_s', 0):.1f}s",
588
- f"- Total epochs: {summary.get('total_epochs', 0)}",
589
- f"- Total batches: {summary.get('total_batches', 0)}",
590
- f"- Final loss: {summary.get('final_loss', 0):.4f}",
591
- f"- Final PPL: {summary.get('final_ppl', 0):.2f}",
592
- f"- Best loss: {summary.get('best_loss', 0):.4f}",
593
- f"- Best PPL: {summary.get('best_ppl', 0):.2f}",
594
- f"- Total hypothesis activations: {summary.get('total_hypothesis_activations', 0)}",
595
- "",
596
- "## Inference Throughput",
597
- "",
598
- f"- Tokens/s: {throughput.get('tokens_per_s', 0):.1f}",
599
- f"- ms/token: {throughput.get('ms_per_token', 0):.2f}",
600
- f"- Medusa accept rate: {throughput.get('medusa_accept_rate', 0):.2%}",
601
- f"- Total tokens generated: {throughput.get('total_tokens', 0)}",
602
- f"- Total inference calls: {throughput.get('n_calls', 0)}",
603
- "",
604
- "## Evolution",
605
- "",
606
- f"- CyclicReasoning events: {len(self.evolution.cyclic_reasoning_stats)}",
607
- f"- VQ-VAE-2 usage events: {len(self.evolution.vqvae2_codebook_usage)}",
608
- f"- Quantization events: {len(self.evolution.quantization_stats)}",
609
- f"- EWC penalty events: {len(self.evolution.ewc_penalty_evolution)}",
610
- f"- Medusa accept events: {len(self.evolution.medusa_accept_rate)}",
611
- "",
612
- "## Epoch History",
613
- "",
614
- "| Epoch | Avg Loss | Avg PPL | Batches | Hyp Act | Time(s) |",
615
- "|-------|----------|---------|---------|---------|---------|",
616
- ])
617
- for e in self.epoch_history:
618
- lines.append(
619
- f"| {e.epoch} | {e.avg_loss:.4f} | {e.avg_ppl:.2f} | "
620
- f"{e.n_batches} | {e.n_hypothesis_activations} | {e.elapsed_s:.1f} |"
621
- )
622
-
623
- with open(output_path, "w", encoding="utf-8") as f:
624
- f.write("\n".join(lines))
625
- logger.info("Markdown summary salvo em: %s", output_path)
626
- return output_path
627
-
628
- def _get_training_summary(self) -> Dict[str, Any]:
629
- """Constrói sumário de treino."""
630
- if not self.epoch_history:
631
- return {"total_time_s": 0, "total_epochs": 0, "total_batches": self._global_step}
632
- return {
633
- "total_time_s": time.time() - (self._start_time or time.time()),
634
- "total_epochs": len(self.epoch_history),
635
- "total_batches": self._global_step,
636
- "final_loss": self.epoch_history[-1].avg_loss,
637
- "final_ppl": self.epoch_history[-1].avg_ppl,
638
- "best_loss": min((e.avg_loss for e in self.epoch_history), default=0.0),
639
- "best_ppl": min((e.avg_ppl for e in self.epoch_history), default=0.0),
640
- "total_hypothesis_activations": sum(
641
- e.n_hypothesis_activations for e in self.epoch_history
642
- ),
643
- }
644
-
645
- # ----------------------------------------------------------------------
646
- # Reset
647
- # ----------------------------------------------------------------------
648
-
649
- def reset(self) -> None:
650
- """Limpa todo o histórico."""
651
- self.batch_history.clear()
652
- self.epoch_history.clear()
653
- self.evolution = EvolutionMetrics()
654
- self.system_history.clear()
655
- self._global_step = 0
656
- self._current_epoch = 0
657
- self._current_batch = 0
658
- self._start_time = None
659
- self._epoch_start_time = None
660
- self.inference_stats = {k: 0 if isinstance(v, int) else 0.0
661
- for k, v in self.inference_stats.items()}
662
-
663
-
664
- # ============================================================================
665
- # Singleton instance (opcional)
666
- # ============================================================================
667
-
668
- _global_monitor: Optional[Monitor] = None
669
-
670
-
671
- def get_monitor(output_dir: Optional[Union[str, Path]] = None) -> Monitor:
672
- """Retorna a instância global do monitor (singleton)."""
673
- global _global_monitor
674
- if _global_monitor is None:
675
- _global_monitor = Monitor(output_dir=output_dir)
676
- return _global_monitor
677
-
678
-
679
- # ============================================================================
680
- # Self-test
681
- # ============================================================================
682
-
683
- def _self_test():
684
- """Teste rápido do monitor."""
685
- import tempfile
686
- with tempfile.TemporaryDirectory() as tmpdir:
687
- mon = Monitor(output_dir=tmpdir, log_every=1, track_system=True)
688
- mon.start_training()
689
- mon.start_epoch(0)
690
- for i in range(5):
691
- mon.log_batch({
692
- "loss": 5.0 - i * 0.5,
693
- "ppl": 100.0 - i * 10,
694
- "lr_g": 1e-3,
695
- "grad_norm": 0.5 + i * 0.1,
696
- "hypothesis_activations": i,
697
- "elapsed_ms": 10 + i,
698
- })
699
- mon.end_epoch()
700
- mon.end_training()
701
-
702
- mon.log_inference(n_tokens=50, elapsed_ms=500, medusa_accepted=10)
703
- mon.log_cyclic_reasoning({"n_cycles": 3, "converged": True})
704
- mon.log_vqvae2_usage({"top_usage": 0.5, "bottom_usage": 0.7})
705
- mon.log_quantization({"reduction_pct": 60.0})
706
- mon.log_ewc_penalty(penalty=0.001, num_tasks=1)
707
- mon.log_medusa_accept(accepted=10, total=50)
708
-
709
- report = mon.export_report()
710
- csv_path = mon.export_csv()
711
- md_path = mon.export_markdown_summary()
712
- print(f"Report: {report}")
713
- print(f"CSV: {csv_path}")
714
- print(f"MD: {md_path}")
715
- print(f"Throughput: {mon.get_inference_throughput()}")
716
-
717
-
718
- if __name__ == "__main__":
719
- _self_test()
720
-
721
-
722
- __all__ = [
723
- "BatchMetrics",
724
- "EpochMetrics",
725
- "EvolutionMetrics",
726
- "SystemMetrics",
727
- "Monitor",
728
- "get_monitor",
729
- ]