mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-18 15:06:28 +03:00
Three researched references validated against current API docs: - references/pytorch-integration.md: device management, training loops, AMP, torch.compile, transfer learning, LoRA, distillation, pruning, DDP, debugging (validated against PyTorch 2.12 docs) - references/sklearn-integration.md: pipelines, ColumnTransformer, model selection, ensembles, calibration, imbalanced data, custom estimators, feature selection (validated against sklearn 1.8.0 docs) - references/data-science-coding-workflow.md: project structure, config management, experiment logging (MLflow/TensorBoard/WandB), result serialization, reproducibility, data versioning, unit testing 66/66 validation tests passing. Closes #23
617 lines
19 KiB
Markdown
617 lines
19 KiB
Markdown
# PyTorch Integration Reference
|
||
|
||
**Source validated against:** PyTorch 2.12 documentation (pytorch.org/docs/stable)
|
||
**Last reviewed:** 2026-05-23
|
||
**When to load:** The campaign protocol (Phase 3-7) or any task requires implementing, training, or deploying a PyTorch model.
|
||
|
||
---
|
||
|
||
## Device Management
|
||
|
||
### Canonical Device Pattern
|
||
|
||
Always parameterize the device. Never hardcode `"cuda"`.
|
||
|
||
```python
|
||
import torch
|
||
|
||
device = torch.device(
|
||
"cuda" if torch.cuda.is_available()
|
||
else "mps" if torch.backends.mps.is_available()
|
||
else "cpu"
|
||
)
|
||
# Usage: model.to(device); tensor.to(device)
|
||
```
|
||
|
||
### Checking Device Properties
|
||
|
||
```python
|
||
if torch.cuda.is_available():
|
||
print(f"Device: {torch.cuda.get_device_name(0)}")
|
||
print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
|
||
print(f"CUDA Capability: {torch.cuda.get_device_capability(0)}")
|
||
```
|
||
|
||
**MPS (Apple Silicon):** Available on macOS 12.3+. Not all operations are supported — check `torch.backends.mps.is_available()` and `torch.backends.mps.is_built()`. Some operations fall back to CPU automatically.
|
||
|
||
---
|
||
|
||
## Training Loop Patterns
|
||
|
||
### Basic Supervised Training Loop
|
||
|
||
```python
|
||
model.train()
|
||
for epoch in range(num_epochs):
|
||
running_loss = 0.0
|
||
for batch_x, batch_y in dataloader:
|
||
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
|
||
|
||
optimizer.zero_grad()
|
||
outputs = model(batch_x)
|
||
loss = criterion(outputs, batch_y)
|
||
loss.backward()
|
||
|
||
# Gradient clipping (essential for stability)
|
||
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
||
|
||
optimizer.step()
|
||
|
||
running_loss += loss.item()
|
||
|
||
avg_loss = running_loss / len(dataloader)
|
||
print(f"Epoch {epoch}: loss={avg_loss:.4f}")
|
||
```
|
||
|
||
### Gradient Accumulation (for large models on limited VRAM)
|
||
|
||
```python
|
||
accumulation_steps = 4 # Effective batch_size = physical_batch * accumulation
|
||
|
||
for i, (batch_x, batch_y) in enumerate(dataloader):
|
||
outputs = model(batch_x)
|
||
loss = criterion(outputs, batch_y) / accumulation_steps
|
||
loss.backward()
|
||
|
||
if (i + 1) % accumulation_steps == 0:
|
||
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
||
optimizer.step()
|
||
optimizer.zero_grad()
|
||
```
|
||
|
||
### Validation Loop
|
||
|
||
```python
|
||
model.eval()
|
||
val_loss = 0.0
|
||
correct = 0
|
||
total = 0
|
||
|
||
with torch.no_grad():
|
||
for batch_x, batch_y in val_loader:
|
||
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
|
||
outputs = model(batch_x)
|
||
val_loss += criterion(outputs, batch_y).item()
|
||
_, predicted = torch.max(outputs, 1)
|
||
total += batch_y.size(0)
|
||
correct += (predicted == batch_y).sum().item()
|
||
|
||
print(f"Val Loss: {val_loss/len(val_loader):.4f}, Acc: {100*correct/total:.2f}%")
|
||
```
|
||
|
||
---
|
||
|
||
## Dataset & DataLoader
|
||
|
||
### Custom Dataset Class
|
||
|
||
```python
|
||
from torch.utils.data import Dataset, DataLoader
|
||
|
||
class CustomDataset(Dataset):
|
||
def __init__(self, features, labels, transform=None):
|
||
self.features = torch.tensor(features, dtype=torch.float32)
|
||
self.labels = torch.tensor(labels, dtype=torch.long)
|
||
self.transform = transform
|
||
|
||
def __len__(self):
|
||
return len(self.labels)
|
||
|
||
def __getitem__(self, idx):
|
||
x = self.features[idx]
|
||
y = self.labels[idx]
|
||
if self.transform:
|
||
x = self.transform(x)
|
||
return x, y
|
||
```
|
||
|
||
### DataLoader Configuration
|
||
|
||
```python
|
||
dataloader = DataLoader(
|
||
dataset,
|
||
batch_size=32,
|
||
shuffle=True,
|
||
num_workers=4, # Set to 0 on Windows if multiprocessing issues
|
||
pin_memory=True, # Speeds up GPU transfer (only with CUDA)
|
||
persistent_workers=True if num_workers > 0 else False, # PyTorch 2.0+
|
||
collate_fn=None, # Custom collation for variable-length data
|
||
)
|
||
```
|
||
|
||
**Note on `num_workers`:** On Linux, 4-8 workers is typical. On macOS, keep at 0-2. On Windows, 0 is safest. The optimal value depends on the data loading speed vs GPU speed.
|
||
|
||
### Collate Function for Variable-Length Data
|
||
|
||
```python
|
||
def collate_fn(batch):
|
||
"""Pad sequences in a batch to the same length."""
|
||
inputs, labels = zip(*batch)
|
||
# Pad to max length in this batch
|
||
inputs_padded = torch.nn.utils.rnn.pad_sequence(inputs, batch_first=True)
|
||
return inputs_padded, torch.tensor(labels)
|
||
```
|
||
|
||
---
|
||
|
||
## Model Saving & Loading
|
||
|
||
### Save/Load State Dict (Recommended)
|
||
|
||
```python
|
||
# Save
|
||
torch.save({
|
||
"epoch": epoch,
|
||
"model_state_dict": model.state_dict(),
|
||
"optimizer_state_dict": optimizer.state_dict(),
|
||
"loss": loss,
|
||
"config": {"n_layers": 4, "hidden_dim": 256}, # metadata
|
||
}, "checkpoint.pt")
|
||
|
||
# Load
|
||
checkpoint = torch.load("checkpoint.pt", map_location=device)
|
||
model.load_state_dict(checkpoint["model_state_dict"])
|
||
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
|
||
start_epoch = checkpoint["epoch"] + 1
|
||
```
|
||
|
||
### Save for Inference (weights only)
|
||
|
||
```python
|
||
torch.save(model.state_dict(), "model_weights.pt")
|
||
|
||
# Load for inference
|
||
model = YourModel()
|
||
model.load_state_dict(torch.load("model_weights.pt", map_location="cpu"))
|
||
model.eval()
|
||
```
|
||
|
||
### TorchScript / torch.export (for production deployment)
|
||
|
||
```python
|
||
# torch.export (PyTorch 2.x preferred, producesExportedProgram)
|
||
exported_program = torch.export.export(model, (example_input,))
|
||
torch.export.save(exported_program, "model.pt2")
|
||
|
||
# TorchScript (legacy, still widely supported)
|
||
scripted_model = torch.jit.script(model)
|
||
scripted_model.save("model_scripted.pt")
|
||
```
|
||
|
||
---
|
||
|
||
## Mixed Precision (AMP)
|
||
|
||
Automatic Mixed Precision trains with `float16` (or `bfloat16`) where safe and `float32` where needed. Typically 1.5-2x faster with minimal accuracy loss.
|
||
|
||
```python
|
||
from torch.amp import autocast, GradScaler
|
||
|
||
scaler = GradScaler("cuda") # "cuda" or "cpu"
|
||
|
||
for batch_x, batch_y in dataloader:
|
||
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
|
||
|
||
optimizer.zero_grad()
|
||
|
||
# Autocast context manager
|
||
with autocast(device_type="cuda"): # or "cpu"
|
||
outputs = model(batch_x)
|
||
loss = criterion(outputs, batch_y)
|
||
|
||
# Scale loss, backward, unscale, step
|
||
scaler.scale(loss).backward()
|
||
scaler.unscale_(optimizer)
|
||
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
||
scaler.step(optimizer)
|
||
scaler.update()
|
||
```
|
||
|
||
**Key points:**
|
||
- `autocast` wraps forward pass + loss computation only
|
||
- `GradScaler` prevents underflow in small gradients
|
||
- Use `bfloat16` on Ampere+ GPUs (less numerical issues than `float16`)
|
||
- AMP is most beneficial for large models (CNNs, Transformers) — small models may not see speedup
|
||
|
||
---
|
||
|
||
## torch.compile (PyTorch 2.x JIT Compilation)
|
||
|
||
`torch.compile` compiles model graphs for faster execution with minimal code changes.
|
||
|
||
```python
|
||
# Basic usage (reduces model — returns a compiled wrapper)
|
||
compiled_model = torch.compile(model)
|
||
|
||
# With options
|
||
compiled_model = torch.compile(
|
||
model,
|
||
mode="reduce-overhead", # "default", "reduce-overhead", "max-autotune"
|
||
fullgraph=True, # Fail if graph breaks
|
||
dynamic=True, # Support dynamic tensor shapes
|
||
)
|
||
|
||
# Training is identical:
|
||
output = compiled_model(batch_x) # First call compiles, subsequent calls are fast
|
||
```
|
||
|
||
**When to use:** Models with large tensor operations (CNNs, Transformers). Not beneficial for very small models or heavy data loading bottlenecks.
|
||
|
||
**When not to use:** Dynamic control flow, custom CUDA extensions, models compiled for TorchScript export.
|
||
|
||
**Mode selection:**
|
||
- `"default"` — conservative, works for most models
|
||
- `"reduce-overhead"` — good for inference, reduces Python overhead
|
||
- `"max-autotune"` — benchmarks and picks the best backend (slow first run)
|
||
|
||
---
|
||
|
||
## Loss Functions
|
||
|
||
| Problem Type | Loss Function | Import |
|
||
|---|---|---|
|
||
| Binary classification | `BCEWithLogitsLoss` | `torch.nn.BCEWithLogitsLoss` |
|
||
| Multi-class classification | `CrossEntropyLoss` | `torch.nn.CrossEntropyLoss` |
|
||
| Multi-label classification | `BCEWithLogitsLoss` | Combines sigmoid + BCELoss |
|
||
| Regression (MSE) | `MSELoss` | `torch.nn.MSELoss` |
|
||
| Regression (MAE) | `L1Loss` | `torch.nn.L1Loss` |
|
||
| Regression (Huber) | `HuberLoss` | `torch.nn.HuberLoss` (delta parameter) |
|
||
| Contrastive / Siamese | `TripletMarginLoss` / `ContrastiveLoss` | `torch.nn.TripletMarginLoss` |
|
||
| Imbalanced classes | Weighted `CrossEntropyLoss` | Pass `weight` tensor to constructor |
|
||
| Sequence (CTC) | `CTCLoss` | `torch.nn.CTCLoss` |
|
||
|
||
```python
|
||
# Weighted loss for imbalanced data
|
||
class_weights = torch.tensor([0.2, 0.8]).to(device) # inverse frequency
|
||
criterion = torch.nn.CrossEntropyLoss(weight=class_weights)
|
||
```
|
||
|
||
---
|
||
|
||
## Optimizers & Schedulers
|
||
|
||
| Optimizer | When | Learning Rate |
|
||
|---|---|---|
|
||
| `AdamW` | Default for most models (Transformers, CNNs) | 1e-4 to 3e-4 |
|
||
| `Adam` | Legacy; prefer AdamW (proper weight decay) | 1e-4 to 3e-4 |
|
||
| `SGD` + momentum | When Adam overfits; vision models | 1e-2 to 1e-1 |
|
||
| `AdamW` (LoRA) | Fine-tuning with LoRA | 2e-4 to 5e-4 |
|
||
|
||
```python
|
||
import torch.optim as optim
|
||
|
||
# AdamW — the default
|
||
optimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)
|
||
|
||
# SGD with momentum (for vision fine-tuning)
|
||
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4)
|
||
```
|
||
|
||
### Learning Rate Schedulers
|
||
|
||
```python
|
||
from torch.optim.lr_scheduler import (
|
||
ReduceLROnPlateau, # Reduce when metric plateaus
|
||
CosineAnnealingLR, # Cosine decay
|
||
OneCycleLR, # Warmup + cosine ("super-convergence")
|
||
)
|
||
|
||
# Reduce on plateau (works well for most tasks)
|
||
scheduler = ReduceLROnPlateau(optimizer, mode="min", patience=5, factor=0.5)
|
||
|
||
# One-cycle (fast convergence, needs max_lr)
|
||
scheduler = OneCycleLR(
|
||
optimizer,
|
||
max_lr=1e-3,
|
||
steps_per_epoch=len(train_loader),
|
||
epochs=num_epochs,
|
||
)
|
||
|
||
# Cosine annealing (good for Transformers)
|
||
scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs)
|
||
|
||
# Warmup + cosine (standard for LLM fine-tuning)
|
||
# Implement manually:
|
||
def get_cosine_schedule_with_warmup(optimizer, warmup_steps, total_steps):
|
||
def lr_lambda(current_step):
|
||
if current_step < warmup_steps:
|
||
return float(current_step) / float(max(1, warmup_steps))
|
||
progress = float(current_step - warmup_steps) / float(max(1, total_steps - warmup_steps))
|
||
return 0.5 * (1.0 + math.cos(math.pi * progress))
|
||
return torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
|
||
```
|
||
|
||
---
|
||
|
||
## Transfer Learning
|
||
|
||
### Feature Extraction (Freeze Backbone)
|
||
|
||
```python
|
||
import torchvision.models as models
|
||
|
||
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
|
||
|
||
# Freeze all layers
|
||
for param in model.parameters():
|
||
param.requires_grad = False
|
||
|
||
# Replace classifier
|
||
num_features = model.fc.in_features
|
||
model.fc = torch.nn.Linear(num_features, num_classes)
|
||
|
||
# Only the new layer's params are trainable
|
||
optimizer = optim.AdamW(model.fc.parameters(), lr=1e-3)
|
||
```
|
||
|
||
### Full Fine-Tuning
|
||
|
||
```python
|
||
# Unfreeze all layers
|
||
for param in model.parameters():
|
||
param.requires_grad = True
|
||
|
||
# Use lower learning rate
|
||
optimizer = optim.AdamW(model.parameters(), lr=2e-5)
|
||
|
||
# Often helps to freeze early layers, fine-tune later layers:
|
||
for name, param in model.named_parameters():
|
||
if "layer1" in name or "layer2" in name:
|
||
param.requires_grad = False
|
||
```
|
||
|
||
### LoRA for Transformer Models
|
||
|
||
LoRA (Low-Rank Adaptation) trains small rank-decomposition matrices while keeping the base model frozen. Requires the `peft` library.
|
||
|
||
```python
|
||
from peft import LoraConfig, get_peft_model
|
||
|
||
lora_config = LoraConfig(
|
||
r=8, # Rank — higher = more expressiveness, more params
|
||
lora_alpha=32, # Scaling factor
|
||
target_modules=["q_proj", "v_proj"], # Which modules to apply LoRA to
|
||
lora_dropout=0.1,
|
||
bias="none", # Don't train bias terms
|
||
task_type="SEQ_CLS", # Task type (SEQ_CLS, CAUSAL_LM, TOKEN_CLS, etc.)
|
||
)
|
||
|
||
model = get_peft_model(base_model, lora_config)
|
||
|
||
# Train with slightly higher LR
|
||
optimizer = optim.AdamW(model.parameters(), lr=3e-4)
|
||
|
||
# LoRA adds very few params (< 1% of base model)
|
||
model.print_trainable_parameters() # e.g., "trainable params: 294,912 || all params: 110,080,512 || %: 0.2679"
|
||
```
|
||
|
||
---
|
||
|
||
## Knowledge Distillation
|
||
|
||
```python
|
||
import torch.nn.functional as F
|
||
|
||
def distillation_loss(student_logits, teacher_logits, labels, temperature=4.0, alpha=0.7):
|
||
"""
|
||
Combined distillation + supervised loss.
|
||
|
||
Args:
|
||
temperature: Higher = softer probability distribution (more information from teacher)
|
||
alpha: Weight for distillation loss (vs standard cross-entropy)
|
||
"""
|
||
# Soft target loss (distillation)
|
||
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
|
||
soft_teacher = F.softmax(teacher_logits.detach() / temperature, dim=-1)
|
||
distill_loss = F.kl_div(soft_student, soft_teacher, reduction="batchmean")
|
||
distill_loss *= temperature ** 2 # Scale to keep gradients in right range
|
||
|
||
# Hard target loss
|
||
hard_loss = F.cross_entropy(student_logits, labels)
|
||
|
||
return alpha * distill_loss + (1 - alpha) * hard_loss
|
||
|
||
|
||
# Training loop with distillation
|
||
teacher_model.eval()
|
||
for batch_x, batch_y in dataloader:
|
||
with torch.no_grad():
|
||
teacher_logits = teacher_model(batch_x)
|
||
|
||
student_logits = student_model(batch_x)
|
||
loss = distillation_loss(student_logits, teacher_logits, batch_y)
|
||
|
||
optimizer.zero_grad()
|
||
loss.backward()
|
||
optimizer.step()
|
||
```
|
||
|
||
**Temperature tuning:** Start with T=4.0. Higher values produce softer targets (more small-class information). Lower values (T=1.0) collapse to standard cross-entropy.
|
||
|
||
**Alpha tuning:** α=0.7 (weight on distillation) is a common starting point. Increase α when the teacher is much better than the student.
|
||
|
||
---
|
||
|
||
## Model Pruning
|
||
|
||
```python
|
||
import torch.nn.utils.prune as prune
|
||
|
||
# Apply pruning to specific layers
|
||
prune.l1_unstructured(module=model.fc, name="weight", amount=0.3) # Remove 30% of weights
|
||
|
||
# Make pruning permanent (removes the pruning mask)
|
||
prune.remove(module=model.fc, name="weight")
|
||
|
||
# Structured pruning (removes entire neurons/channels)
|
||
prune.ln_structured(module=model.conv1, name="weight", amount=0.2, n=2, dim=0)
|
||
|
||
# Global pruning (prune all layers together by importance)
|
||
parameters_to_prune = [
|
||
(model.layer1, "weight"),
|
||
(model.layer2, "weight"),
|
||
(model.fc, "weight"),
|
||
]
|
||
prune.global_unstructured(
|
||
parameters_to_prune,
|
||
pruning_method=prune.L1Unstructured,
|
||
amount=0.2, # Remove 20% of weights globally
|
||
)
|
||
```
|
||
|
||
**After pruning:** Fine-tune the pruned model. Pruning then fine-tuning almost always recovers accuracy. Pruning without fine-tuning degrades performance significantly.
|
||
|
||
---
|
||
|
||
## Distributed Data Parallel (DDP)
|
||
|
||
For multi-GPU training. DDP wraps the model and handles gradient synchronization.
|
||
|
||
```python
|
||
import torch.distributed as dist
|
||
import torch.multiprocessing as mp
|
||
from torch.nn.parallel import DistributedDataParallel as DDP
|
||
|
||
def setup(rank, world_size):
|
||
"""Initialize the distributed process group."""
|
||
dist.init_process_group(
|
||
backend="nccl", # "nccl" for NVIDIA, "gloo" for CPU
|
||
init_method="env://", # Use env vars MASTER_ADDR and MASTER_PORT
|
||
rank=rank,
|
||
world_size=world_size,
|
||
)
|
||
|
||
def cleanup():
|
||
dist.destroy_process_group()
|
||
|
||
def train(rank, world_size):
|
||
setup(rank, world_size)
|
||
|
||
# Model must be on the correct device BEFORE wrapping
|
||
model = YourModel().to(rank)
|
||
ddp_model = DDP(model, device_ids=[rank])
|
||
|
||
# DataLoader must use DistributedSampler
|
||
sampler = torch.utils.data.distributed.DistributedSampler(
|
||
dataset, num_replicas=world_size, rank=rank
|
||
)
|
||
dataloader = DataLoader(dataset, batch_size=32, sampler=sampler)
|
||
|
||
# Training loop (same structure, use ddp_model)
|
||
for epoch in range(num_epochs):
|
||
sampler.set_epoch(epoch) # Shuffle each epoch
|
||
for batch_x, batch_y in dataloader:
|
||
batch_x, batch_y = batch_x.to(rank), batch_y.to(rank)
|
||
outputs = ddp_model(batch_x)
|
||
loss = criterion(outputs, batch_y)
|
||
loss.backward()
|
||
optimizer.step()
|
||
|
||
cleanup()
|
||
|
||
# Launch
|
||
if __name__ == "__main__":
|
||
world_size = torch.cuda.device_count()
|
||
mp.spawn(train, args=(world_size,), nprocs=world_size)
|
||
```
|
||
|
||
**When DDP is worth it:** Models that take > 1 hour to train on a single GPU. For short experiments, single-GPU + AMP is often faster due to communication overhead.
|
||
|
||
---
|
||
|
||
## Debugging
|
||
|
||
### Common Failure Patterns
|
||
|
||
| Symptom | Likely Cause | Fix |
|
||
|---|---|---|
|
||
| `loss = nan` | Exploding gradients, bad learning rate | Lower LR, add gradient clipping, check for NaN in input data |
|
||
| `loss = nan` after AMP | Gradient underflow | Increase `GradScaler` init_scale, or use `bfloat16` |
|
||
| Loss doesn't decrease | Wrong LR, wrong loss function | Check LR range, verify loss function matches task |
|
||
| CUDA OOM | Batch size too large | Reduce batch size, enable gradient checkpointing, use AMP |
|
||
| `Expected all tensors to be on...` | Device mismatch | Always `.to(device)` tensors before model forward |
|
||
| `CUDA error: device-side assert` | Wrong label class (out of range) | Check label values are in `[0, num_classes)` |
|
||
| Model doesn't overfit 1 batch | Bug in model architecture | Try overfitting on a single batch (batch of 2-4 samples for 100 steps) |
|
||
|
||
### Overfit on One Batch (Diagnostic)
|
||
|
||
```python
|
||
# If model can't overfit a single batch, something is fundamentally wrong
|
||
single_batch = next(iter(dataloader))
|
||
|
||
for step in range(100):
|
||
outputs = model(single_batch[0].to(device))
|
||
loss = criterion(outputs, single_batch[1].to(device))
|
||
loss.backward()
|
||
optimizer.step()
|
||
|
||
print(f"Loss after 100 steps on 1 batch: {loss.item():.6f}")
|
||
# If loss is not near 0, model has a bug or LR is way off.
|
||
```
|
||
|
||
### Gradient Checking
|
||
|
||
```python
|
||
# Check gradient norms during training
|
||
total_norm = 0.0
|
||
for p in model.parameters():
|
||
if p.grad is not None:
|
||
param_norm = p.grad.data.norm(2)
|
||
total_norm += param_norm.item() ** 2
|
||
total_norm = total_norm ** 0.5
|
||
|
||
if total_norm > 10.0:
|
||
print(f"WARNING: Large gradient norm: {total_norm:.4f}")
|
||
```
|
||
|
||
---
|
||
|
||
## Reproducibility
|
||
|
||
```python
|
||
import random
|
||
import numpy as np
|
||
import torch
|
||
|
||
def set_seed(seed: int = 42):
|
||
random.seed(seed)
|
||
np.random.seed(seed)
|
||
torch.manual_seed(seed)
|
||
torch.cuda.manual_seed_all(seed)
|
||
torch.backends.cudnn.deterministic = True
|
||
torch.backends.cudnn.benchmark = False # Trades off speed for determinism
|
||
|
||
set_seed(42)
|
||
```
|
||
|
||
**Note on `cudnn.deterministic`:** Setting this to `True` may reduce performance (5-15%). For experiments where exact reproducibility isn't critical, leave `benchmark=True` and accept minor stochastic variation.
|
||
|
||
---
|
||
|
||
## See Also
|
||
|
||
- `references/experimental-campaign-protocol.md` — where this reference fits in the campaign workflow
|
||
- `references/sklearn-integration.md` — for non-deep-learning methods
|
||
- `scripts/detect-compute.py` — check your hardware before choosing model size
|
||
- pytorch.org/docs/stable — official API documentation
|