Upload utils.py
Browse files
utils.py
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Utility functions for LMCODE (Language Model with Memory CODE).
|
| 3 |
+
|
| 4 |
+
Includes memory visualization, analysis, and helper functions.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import numpy as np
|
| 9 |
+
import matplotlib.pyplot as plt
|
| 10 |
+
from typing import Dict, List, Optional, Tuple
|
| 11 |
+
from collections import defaultdict
|
| 12 |
+
import json
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def analyze_memory_capacity(model, test_sequences: List[torch.Tensor],
|
| 16 |
+
retrieval_threshold: float = 0.8) -> Dict:
|
| 17 |
+
"""
|
| 18 |
+
Analyze the memory capacity and retrieval accuracy of the model.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
model: LMCODE model
|
| 22 |
+
test_sequences: List of test sequences
|
| 23 |
+
retrieval_threshold: Similarity threshold for successful retrieval
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
Dictionary with analysis results
|
| 27 |
+
"""
|
| 28 |
+
results = {
|
| 29 |
+
'total_memories': 0,
|
| 30 |
+
'successful_retrievals': 0,
|
| 31 |
+
'average_similarity': 0,
|
| 32 |
+
'capacity_utilization': 0
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
similarities = []
|
| 36 |
+
|
| 37 |
+
for seq in test_sequences:
|
| 38 |
+
# Store sequence
|
| 39 |
+
model.store_experience(seq)
|
| 40 |
+
|
| 41 |
+
# Try to retrieve
|
| 42 |
+
retrieved, indices = model.query_memory(seq, top_k=5)
|
| 43 |
+
|
| 44 |
+
# Compute similarity
|
| 45 |
+
with torch.no_grad():
|
| 46 |
+
# Simplified similarity computation
|
| 47 |
+
seq_repr = seq.mean(dim=1) if seq.dim() > 2 else seq
|
| 48 |
+
retrieved_repr = retrieved.mean(dim=1) if retrieved.dim() > 2 else retrieved
|
| 49 |
+
|
| 50 |
+
if seq_repr.shape[-1] == retrieved_repr.shape[-1]:
|
| 51 |
+
# Cosine similarity
|
| 52 |
+
seq_norm = torch.nn.functional.normalize(seq_repr, dim=-1)
|
| 53 |
+
retrieved_norm = torch.nn.functional.normalize(retrieved_repr, dim=-1)
|
| 54 |
+
similarity = (seq_norm * retrieved_norm).sum(dim=-1).mean()
|
| 55 |
+
similarities.append(similarity.item())
|
| 56 |
+
|
| 57 |
+
if similarity > retrieval_threshold:
|
| 58 |
+
results['successful_retrievals'] += 1
|
| 59 |
+
|
| 60 |
+
results['total_memories'] += 1
|
| 61 |
+
|
| 62 |
+
if similarities:
|
| 63 |
+
results['average_similarity'] = np.mean(similarities)
|
| 64 |
+
results['similarity_std'] = np.std(similarities)
|
| 65 |
+
|
| 66 |
+
# Compute capacity utilization
|
| 67 |
+
total_slots = sum(
|
| 68 |
+
layer.long_term_memory.num_slots
|
| 69 |
+
for layer in model.layers
|
| 70 |
+
)
|
| 71 |
+
results['capacity_utilization'] = results['total_memories'] / total_slots
|
| 72 |
+
|
| 73 |
+
return results
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def visualize_memory_attention(attention_weights: torch.Tensor,
|
| 77 |
+
save_path: Optional[str] = None) -> plt.Figure:
|
| 78 |
+
"""
|
| 79 |
+
Visualize memory attention patterns.
|
| 80 |
+
|
| 81 |
+
Args:
|
| 82 |
+
attention_weights: Attention weights tensor
|
| 83 |
+
save_path: Optional path to save figure
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
Matplotlib figure
|
| 87 |
+
"""
|
| 88 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
| 89 |
+
|
| 90 |
+
# Short-term memory attention
|
| 91 |
+
if len(attention_weights.shape) == 4:
|
| 92 |
+
# Multi-head attention
|
| 93 |
+
attention_avg = attention_weights.mean(dim=1)
|
| 94 |
+
else:
|
| 95 |
+
attention_avg = attention_weights
|
| 96 |
+
|
| 97 |
+
im1 = axes[0].imshow(attention_avg[0].cpu().numpy(), cmap='hot', aspect='auto')
|
| 98 |
+
axes[0].set_title('Short-Term Memory Attention')
|
| 99 |
+
axes[0].set_xlabel('Memory Slots')
|
| 100 |
+
axes[0].set_ylabel('Sequence Position')
|
| 101 |
+
plt.colorbar(im1, ax=axes[0])
|
| 102 |
+
|
| 103 |
+
# Long-term memory retrieval weights
|
| 104 |
+
# (This would need actual retrieval weights from a forward pass)
|
| 105 |
+
axes[1].text(0.5, 0.5, 'Long-Term Memory\nRetrieval Weights',
|
| 106 |
+
ha='center', va='center', transform=axes[1].transAxes)
|
| 107 |
+
axes[1].set_title('Long-Term Memory Retrieval')
|
| 108 |
+
|
| 109 |
+
plt.tight_layout()
|
| 110 |
+
|
| 111 |
+
if save_path:
|
| 112 |
+
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
| 113 |
+
|
| 114 |
+
return fig
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def plot_training_history(history: Dict, save_path: Optional[str] = None) -> plt.Figure:
|
| 118 |
+
"""
|
| 119 |
+
Plot training history.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
history: Training history dictionary
|
| 123 |
+
save_path: Optional path to save figure
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
Matplotlib figure
|
| 127 |
+
"""
|
| 128 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
|
| 129 |
+
|
| 130 |
+
# Loss plot
|
| 131 |
+
axes[0].plot(history.get('train_loss', []), label='Train Loss', alpha=0.8)
|
| 132 |
+
if 'eval_loss' in history and history['eval_loss']:
|
| 133 |
+
axes[0].plot(history['eval_loss'], label='Eval Loss', alpha=0.8)
|
| 134 |
+
axes[0].set_xlabel('Epoch')
|
| 135 |
+
axes[0].set_ylabel('Loss')
|
| 136 |
+
axes[0].set_title('Training Loss')
|
| 137 |
+
axes[0].legend()
|
| 138 |
+
axes[0].grid(True, alpha=0.3)
|
| 139 |
+
|
| 140 |
+
# Memory statistics
|
| 141 |
+
if 'memory_stats' in history and history['memory_stats']:
|
| 142 |
+
memory_stats = history['memory_stats']
|
| 143 |
+
if isinstance(memory_stats, list) and len(memory_stats) > 0:
|
| 144 |
+
# Extract memory statistics
|
| 145 |
+
if isinstance(memory_stats[0], dict):
|
| 146 |
+
# Get first layer's memory stats
|
| 147 |
+
layer_0_stats = [s.get('layer_0_lt_active_count', 0)
|
| 148 |
+
for s in memory_stats if isinstance(s, dict)]
|
| 149 |
+
if layer_0_stats:
|
| 150 |
+
axes[1].plot(layer_0_stats, label='Active Memories (Layer 0)', alpha=0.8)
|
| 151 |
+
axes[1].set_xlabel('Step')
|
| 152 |
+
axes[1].set_ylabel('Active Memory Count')
|
| 153 |
+
axes[1].set_title('Memory Utilization')
|
| 154 |
+
axes[1].legend()
|
| 155 |
+
axes[1].grid(True, alpha=0.3)
|
| 156 |
+
|
| 157 |
+
plt.tight_layout()
|
| 158 |
+
|
| 159 |
+
if save_path:
|
| 160 |
+
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
| 161 |
+
|
| 162 |
+
return fig
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def compute_memory_efficiency(model, baseline_model=None) -> Dict:
|
| 166 |
+
"""
|
| 167 |
+
Compute memory efficiency metrics.
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
model: LMCODE model
|
| 171 |
+
baseline_model: Optional baseline model for comparison
|
| 172 |
+
|
| 173 |
+
Returns:
|
| 174 |
+
Dictionary with efficiency metrics
|
| 175 |
+
"""
|
| 176 |
+
metrics = {}
|
| 177 |
+
|
| 178 |
+
# Count parameters
|
| 179 |
+
total_params = sum(p.numel() for p in model.parameters())
|
| 180 |
+
memory_params = sum(
|
| 181 |
+
p.numel() for name, p in model.named_parameters()
|
| 182 |
+
if 'memory' in name
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
metrics['total_parameters'] = total_params
|
| 186 |
+
metrics['memory_parameters'] = memory_params
|
| 187 |
+
metrics['memory_parameter_ratio'] = memory_params / total_params
|
| 188 |
+
|
| 189 |
+
# Memory capacity
|
| 190 |
+
total_memory_slots = sum(
|
| 191 |
+
layer.long_term_memory.num_slots
|
| 192 |
+
for layer in model.layers
|
| 193 |
+
)
|
| 194 |
+
metrics['total_memory_slots'] = total_memory_slots
|
| 195 |
+
|
| 196 |
+
# Compute parameter efficiency
|
| 197 |
+
params_per_slot = memory_params / total_memory_slots if total_memory_slots > 0 else 0
|
| 198 |
+
metrics['parameters_per_memory_slot'] = params_per_slot
|
| 199 |
+
|
| 200 |
+
if baseline_model:
|
| 201 |
+
baseline_params = sum(p.numel() for p in baseline_model.parameters())
|
| 202 |
+
metrics['parameter_savings'] = 1 - (total_params / baseline_params)
|
| 203 |
+
|
| 204 |
+
return metrics
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def generate_memory_report(model, dataset, output_path: str = 'memory_report.json'):
|
| 208 |
+
"""
|
| 209 |
+
Generate a comprehensive memory report.
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
model: LMCODE model
|
| 213 |
+
dataset: Evaluation dataset
|
| 214 |
+
output_path: Path to save report
|
| 215 |
+
"""
|
| 216 |
+
report = {
|
| 217 |
+
'model_config': model.config.to_dict() if hasattr(model.config, 'to_dict')
|
| 218 |
+
else vars(model.config),
|
| 219 |
+
'memory_analysis': {},
|
| 220 |
+
'efficiency_metrics': {}
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
# Analyze memory
|
| 224 |
+
test_sequences = [d['input_ids'] for d in dataset[:10]] # Sample
|
| 225 |
+
memory_analysis = analyze_memory_capacity(model, test_sequences)
|
| 226 |
+
report['memory_analysis'] = memory_analysis
|
| 227 |
+
|
| 228 |
+
# Compute efficiency
|
| 229 |
+
efficiency = compute_memory_efficiency(model)
|
| 230 |
+
report['efficiency_metrics'] = efficiency
|
| 231 |
+
|
| 232 |
+
# Save report
|
| 233 |
+
with open(output_path, 'w') as f:
|
| 234 |
+
json.dump(report, f, indent=2, default=str)
|
| 235 |
+
|
| 236 |
+
print(f"Memory report saved to {output_path}")
|
| 237 |
+
return report
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def visualize_memory_flow(model, input_sequence: torch.Tensor,
|
| 241 |
+
save_path: Optional[str] = None) -> plt.Figure:
|
| 242 |
+
"""
|
| 243 |
+
Visualize memory flow through the network.
|
| 244 |
+
|
| 245 |
+
Args:
|
| 246 |
+
model: LMCODE model
|
| 247 |
+
input_sequence: Input sequence
|
| 248 |
+
save_path: Optional path to save figure
|
| 249 |
+
|
| 250 |
+
Returns:
|
| 251 |
+
Matplotlib figure
|
| 252 |
+
"""
|
| 253 |
+
model.eval()
|
| 254 |
+
|
| 255 |
+
with torch.no_grad():
|
| 256 |
+
outputs = model(input_sequence.unsqueeze(0), use_long_term_memory=True)
|
| 257 |
+
|
| 258 |
+
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
|
| 259 |
+
|
| 260 |
+
# Plot 1: Hidden state evolution
|
| 261 |
+
hidden_states = outputs['hidden_states']
|
| 262 |
+
if hidden_states:
|
| 263 |
+
# Average across layers
|
| 264 |
+
avg_hidden = torch.stack([hs.mean(dim=0) for hs in hidden_states])
|
| 265 |
+
im1 = axes[0, 0].imshow(avg_hidden.cpu().numpy(), aspect='auto', cmap='viridis')
|
| 266 |
+
axes[0, 0].set_title('Hidden State Evolution Across Layers')
|
| 267 |
+
axes[0, 0].set_xlabel('Hidden Dimension')
|
| 268 |
+
axes[0, 0].set_ylabel('Layer')
|
| 269 |
+
plt.colorbar(im1, ax=axes[0, 0])
|
| 270 |
+
|
| 271 |
+
# Plot 2: Attention weights (first layer)
|
| 272 |
+
if outputs['attention_weights']:
|
| 273 |
+
attn = outputs['attention_weights'][0]
|
| 274 |
+
if attn.dim() == 4:
|
| 275 |
+
attn = attn.mean(dim=1) # Average heads
|
| 276 |
+
im2 = axes[0, 1].imshow(attn[0].cpu().numpy(), aspect='auto', cmap='plasma')
|
| 277 |
+
axes[0, 1].set_title('Self-Attention Weights (Layer 0)')
|
| 278 |
+
axes[0, 1].set_xlabel('Key Position')
|
| 279 |
+
axes[0, 1].set_ylabel('Query Position')
|
| 280 |
+
plt.colorbar(im2, ax=axes[0, 1])
|
| 281 |
+
|
| 282 |
+
# Plot 3: Memory retrieval weights
|
| 283 |
+
if outputs['long_term_outputs'] and outputs['long_term_outputs'][0]:
|
| 284 |
+
lt_output = outputs['long_term_outputs'][0]
|
| 285 |
+
if 'retrieval_weights' in lt_output:
|
| 286 |
+
weights = lt_output['retrieval_weights']
|
| 287 |
+
im3 = axes[1, 0].imshow(weights[0].cpu().numpy(), aspect='auto', cmap='coolwarm')
|
| 288 |
+
axes[1, 0].set_title('Long-Term Memory Retrieval Weights')
|
| 289 |
+
axes[1, 0].set_xlabel('Memory Slot')
|
| 290 |
+
axes[1, 0].set_ylabel('Sequence Position')
|
| 291 |
+
plt.colorbar(im3, ax=axes[1, 0])
|
| 292 |
+
|
| 293 |
+
# Plot 4: Short-term memory read weights
|
| 294 |
+
if outputs['short_term_outputs']:
|
| 295 |
+
st_output = outputs['short_term_outputs'][0]
|
| 296 |
+
if 'read_weights' in st_output:
|
| 297 |
+
weights = st_output['read_weights']
|
| 298 |
+
im4 = axes[1, 1].imshow(weights[0].cpu().numpy(), aspect='auto', cmap='YlOrRd')
|
| 299 |
+
axes[1, 1].set_title('Short-Term Memory Read Weights')
|
| 300 |
+
axes[1, 1].set_xlabel('Memory Slot')
|
| 301 |
+
axes[1, 1].set_ylabel('Sequence Position')
|
| 302 |
+
plt.colorbar(im4, ax=axes[1, 1])
|
| 303 |
+
|
| 304 |
+
plt.tight_layout()
|
| 305 |
+
|
| 306 |
+
if save_path:
|
| 307 |
+
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
| 308 |
+
|
| 309 |
+
return fig
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
class MemoryMonitor:
|
| 313 |
+
"""
|
| 314 |
+
Monitor memory usage and performance during training.
|
| 315 |
+
"""
|
| 316 |
+
|
| 317 |
+
def __init__(self, model):
|
| 318 |
+
self.model = model
|
| 319 |
+
self.history = defaultdict(list)
|
| 320 |
+
|
| 321 |
+
def record_step(self, step: int, outputs: Dict):
|
| 322 |
+
"""
|
| 323 |
+
Record memory statistics for a training step.
|
| 324 |
+
|
| 325 |
+
Args:
|
| 326 |
+
step: Current training step
|
| 327 |
+
outputs: Model outputs
|
| 328 |
+
"""
|
| 329 |
+
# Record loss
|
| 330 |
+
if 'loss' in outputs and outputs['loss'] is not None:
|
| 331 |
+
self.history['loss'].append((step, outputs['loss'].item()))
|
| 332 |
+
|
| 333 |
+
# Record memory statistics
|
| 334 |
+
for i, lt_output in enumerate(outputs.get('long_term_outputs', [])):
|
| 335 |
+
if lt_output and 'retrieval_weights' in lt_output:
|
| 336 |
+
weights = lt_output['retrieval_weights']
|
| 337 |
+
avg_weight = weights.mean().item()
|
| 338 |
+
self.history[f'layer_{i}_retrieval_weight'].append((step, avg_weight))
|
| 339 |
+
|
| 340 |
+
for i, st_output in enumerate(outputs.get('short_term_outputs', [])):
|
| 341 |
+
if st_output and 'read_weights' in st_output:
|
| 342 |
+
weights = st_output['read_weights']
|
| 343 |
+
avg_weight = weights.mean().item()
|
| 344 |
+
self.history[f'layer_{i}_read_weight'].append((step, avg_weight))
|
| 345 |
+
|
| 346 |
+
def get_statistics(self) -> Dict:
|
| 347 |
+
"""
|
| 348 |
+
Get aggregated memory statistics.
|
| 349 |
+
|
| 350 |
+
Returns:
|
| 351 |
+
Dictionary with statistics
|
| 352 |
+
"""
|
| 353 |
+
stats = {}
|
| 354 |
+
|
| 355 |
+
for key, values in self.history.items():
|
| 356 |
+
if values:
|
| 357 |
+
vals = [v for _, v in values]
|
| 358 |
+
stats[key] = {
|
| 359 |
+
'mean': np.mean(vals),
|
| 360 |
+
'std': np.std(vals),
|
| 361 |
+
'min': np.min(vals),
|
| 362 |
+
'max': np.max(vals),
|
| 363 |
+
'latest': vals[-1]
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
return stats
|
| 367 |
+
|
| 368 |
+
def plot_history(self, save_path: Optional[str] = None) -> plt.Figure:
|
| 369 |
+
"""
|
| 370 |
+
Plot monitoring history.
|
| 371 |
+
|
| 372 |
+
Args:
|
| 373 |
+
save_path: Optional path to save figure
|
| 374 |
+
|
| 375 |
+
Returns:
|
| 376 |
+
Matplotlib figure
|
| 377 |
+
"""
|
| 378 |
+
n_metrics = len(self.history)
|
| 379 |
+
if n_metrics == 0:
|
| 380 |
+
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
|
| 381 |
+
ax.text(0.5, 0.5, 'No data recorded',
|
| 382 |
+
ha='center', va='center', transform=ax.transAxes)
|
| 383 |
+
return fig
|
| 384 |
+
|
| 385 |
+
n_cols = min(2, n_metrics)
|
| 386 |
+
n_rows = (n_metrics + n_cols - 1) // n_cols
|
| 387 |
+
|
| 388 |
+
fig, axes = plt.subplots(n_rows, n_cols, figsize=(6 * n_cols, 4 * n_rows))
|
| 389 |
+
if n_metrics == 1:
|
| 390 |
+
axes = [axes]
|
| 391 |
+
elif n_rows > 1 and n_cols > 1:
|
| 392 |
+
axes = axes.flatten()
|
| 393 |
+
|
| 394 |
+
for idx, (key, values) in enumerate(self.history.items()):
|
| 395 |
+
if idx >= len(axes):
|
| 396 |
+
break
|
| 397 |
+
|
| 398 |
+
steps, vals = zip(*values)
|
| 399 |
+
axes[idx].plot(steps, vals, alpha=0.7)
|
| 400 |
+
axes[idx].set_title(key.replace('_', ' ').title())
|
| 401 |
+
axes[idx].set_xlabel('Step')
|
| 402 |
+
axes[idx].set_ylabel('Value')
|
| 403 |
+
axes[idx].grid(True, alpha=0.3)
|
| 404 |
+
|
| 405 |
+
# Hide unused subplots
|
| 406 |
+
for idx in range(n_metrics, len(axes)):
|
| 407 |
+
axes[idx].axis('off')
|
| 408 |
+
|
| 409 |
+
plt.tight_layout()
|
| 410 |
+
|
| 411 |
+
if save_path:
|
| 412 |
+
plt.savefig(save_path, dpi=150, bbox_inches='tight')
|
| 413 |
+
|
| 414 |
+
return fig
|