Claude
Add PyTorch Playground educational web application
07344c1 unverified
"""
Logging utilities for PyTorch Playground demos.
Provides structured logging for demo outputs and results.
"""
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from datetime import datetime
import io
@dataclass
class LogEntry:
"""A single log entry."""
timestamp: datetime
level: str # INFO, WARNING, ERROR, DEBUG
message: str
data: Optional[Dict[str, Any]] = None
@dataclass
class DemoResult:
"""Result from a demo run."""
success: bool
logs: str
metrics: Dict[str, Any]
code: str
figure: Optional[Any] = None # matplotlib figure
error: Optional[str] = None
elapsed_seconds: float = 0.0
class DemoLogger:
"""
Logger for demo execution.
Collects logs and provides formatted output for Gradio display.
"""
def __init__(self, name: str = "Demo"):
self.name = name
self.entries: List[LogEntry] = []
self.metrics: Dict[str, Any] = {}
self._start_time: Optional[datetime] = None
def start(self) -> None:
"""Mark start of demo execution."""
self._start_time = datetime.now()
self.info(f"Starting {self.name}...")
def end(self, success: bool = True) -> None:
"""Mark end of demo execution."""
elapsed = self.elapsed_seconds
status = "completed" if success else "failed"
self.info(f"{self.name} {status} in {elapsed:.2f}s")
@property
def elapsed_seconds(self) -> float:
"""Get elapsed time since start."""
if self._start_time is None:
return 0.0
return (datetime.now() - self._start_time).total_seconds()
def _log(self, level: str, message: str, data: Optional[Dict] = None) -> None:
"""Add a log entry."""
entry = LogEntry(
timestamp=datetime.now(),
level=level,
message=message,
data=data,
)
self.entries.append(entry)
def info(self, message: str, data: Optional[Dict] = None) -> None:
"""Log info message."""
self._log("INFO", message, data)
def warning(self, message: str, data: Optional[Dict] = None) -> None:
"""Log warning message."""
self._log("WARNING", message, data)
def error(self, message: str, data: Optional[Dict] = None) -> None:
"""Log error message."""
self._log("ERROR", message, data)
def debug(self, message: str, data: Optional[Dict] = None) -> None:
"""Log debug message."""
self._log("DEBUG", message, data)
def metric(self, name: str, value: Any) -> None:
"""Record a metric."""
self.metrics[name] = value
self.info(f"{name}: {value}")
def progress(self, current: int, total: int, prefix: str = "Progress") -> None:
"""Log progress."""
pct = (current / total) * 100 if total > 0 else 0
self.info(f"{prefix}: {current}/{total} ({pct:.1f}%)")
def format_logs(self, include_debug: bool = False) -> str:
"""Format logs for display."""
lines = []
for entry in self.entries:
if entry.level == "DEBUG" and not include_debug:
continue
time_str = entry.timestamp.strftime("%H:%M:%S")
level_prefix = {
"INFO": "[INFO]",
"WARNING": "[WARN]",
"ERROR": "[ERROR]",
"DEBUG": "[DEBUG]",
}.get(entry.level, f"[{entry.level}]")
line = f"{time_str} {level_prefix} {entry.message}"
lines.append(line)
if entry.data:
for key, value in entry.data.items():
lines.append(f" {key}: {value}")
return "\n".join(lines)
def get_result(
self,
success: bool = True,
code: str = "",
figure: Optional[Any] = None,
error: Optional[str] = None,
) -> DemoResult:
"""Get a DemoResult from current state."""
return DemoResult(
success=success,
logs=self.format_logs(),
metrics=self.metrics.copy(),
code=code,
figure=figure,
error=error,
elapsed_seconds=self.elapsed_seconds,
)
def clear(self) -> None:
"""Clear all logs and metrics."""
self.entries.clear()
self.metrics.clear()
self._start_time = None
def format_tensor_info(tensor: "torch.Tensor", name: str = "tensor") -> str:
"""
Format tensor information for display.
Args:
tensor: PyTorch tensor
name: Name to display for the tensor
Returns:
Formatted string with tensor info
"""
lines = [
f"{name}:",
f" shape: {tuple(tensor.shape)}",
f" dtype: {tensor.dtype}",
f" device: {tensor.device}",
f" requires_grad: {tensor.requires_grad}",
]
# Add stats for numeric tensors
if tensor.is_floating_point() or tensor.dtype in [
"torch.int32",
"torch.int64",
"torch.int16",
"torch.int8",
]:
try:
lines.append(f" min: {tensor.min().item():.4f}")
lines.append(f" max: {tensor.max().item():.4f}")
lines.append(f" mean: {tensor.float().mean().item():.4f}")
lines.append(f" std: {tensor.float().std().item():.4f}")
except Exception:
pass
# Show memory
try:
nbytes = tensor.nelement() * tensor.element_size()
if nbytes < 1024:
size_str = f"{nbytes} bytes"
elif nbytes < 1024**2:
size_str = f"{nbytes / 1024:.2f} KB"
else:
size_str = f"{nbytes / 1024**2:.2f} MB"
lines.append(f" memory: {size_str}")
except Exception:
pass
return "\n".join(lines)
def format_model_info(model: "torch.nn.Module", name: str = "model") -> str:
"""
Format model information for display.
Args:
model: PyTorch model
name: Name to display
Returns:
Formatted string with model info
"""
import torch
lines = [f"{name}:"]
# Count parameters
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
lines.append(f" total parameters: {total_params:,}")
lines.append(f" trainable parameters: {trainable_params:,}")
# Get device
try:
param = next(model.parameters())
lines.append(f" device: {param.device}")
lines.append(f" dtype: {param.dtype}")
except StopIteration:
lines.append(" (no parameters)")
return "\n".join(lines)
def format_training_step(
epoch: int,
total_epochs: int,
loss: float,
metrics: Optional[Dict[str, float]] = None,
lr: Optional[float] = None,
) -> str:
"""
Format a training step for display.
Args:
epoch: Current epoch
total_epochs: Total number of epochs
loss: Current loss
metrics: Optional additional metrics
lr: Optional current learning rate
Returns:
Formatted string
"""
parts = [f"Epoch {epoch}/{total_epochs}", f"Loss: {loss:.4f}"]
if metrics:
for name, value in metrics.items():
parts.append(f"{name}: {value:.4f}")
if lr is not None:
parts.append(f"LR: {lr:.2e}")
return " | ".join(parts)
class CodeBuilder:
"""Helper to build code strings for display."""
def __init__(self):
self.lines: List[str] = []
self.indent_level = 0
def add(self, line: str) -> "CodeBuilder":
"""Add a line of code."""
indent = " " * self.indent_level
self.lines.append(f"{indent}{line}")
return self
def add_blank(self) -> "CodeBuilder":
"""Add a blank line."""
self.lines.append("")
return self
def indent(self) -> "CodeBuilder":
"""Increase indent level."""
self.indent_level += 1
return self
def dedent(self) -> "CodeBuilder":
"""Decrease indent level."""
self.indent_level = max(0, self.indent_level - 1)
return self
def add_comment(self, comment: str) -> "CodeBuilder":
"""Add a comment line."""
return self.add(f"# {comment}")
def build(self) -> str:
"""Build the final code string."""
return "\n".join(self.lines)