Tutorial 13: Memory Profiling¶
v0.2.0 | Production memory profiling with MemoryTracker, tracemalloc, and psutil
Prerequisites¶
- AdaptShot v0.2.0+ installed:
pip install adaptshot - Optional:
pip install psutilfor system-level memory metrics
Why Memory Profiling?¶
AdaptShot targets < 250 MB RAM even in production. Memory profiling helps you:
- Verify your deployment stays within budget
- Pinpoint which pipeline stage consumes the most memory
- Detect memory leaks in long-running services
- Optimize config (buffer size, backbone, features) for your hardware
Step 1: Quick Memory Snapshot with tracemalloc¶
The simplest way to measure memory:
import tracemalloc
from adaptshot import FewShotLearner, AdaptShotConfig
config = AdaptShotConfig(device="cpu", seed=42)
learner = FewShotLearner(config=config)
learner.load_support_images(
["cat_01.jpg", "cat_02.jpg", "dog_01.jpg"],
["cat", "cat", "dog"],
)
tracemalloc.start()
result = learner.predict("query.jpg")
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"Prediction — current: {current / 1024 / 1024:.1f} MB")
print(f"Prediction — peak: {peak / 1024 / 1024:.1f} MB")
Step 2: Section-Level Profiling with MemoryTracker¶
MemoryTracker breaks down memory usage by pipeline stage:
from adaptshot.profiling import MemoryTracker
tracker = MemoryTracker()
tracker.start()
# Profile support loading
with tracker.section("support_loading"):
learner.load_support_images(image_paths, labels)
# Profile inference
with tracker.section("inference"):
result = learner.predict("query.jpg")
# Profile correction
with tracker.section("correction"):
learner.correct("query.jpg", true_label="cat", confidence_weight=0.9)
# Generate report
report = tracker.get_report()
print(f"Overall peak: {report['peak_memory_mb']:.1f} MB")
print(f"Current: {report['current_memory_mb']:.1f} MB")
for section_name, section_data in report['sections'].items():
print(f"\n{section_name}:")
print(f" Peak: {section_data['peak_mb']:.1f} MB")
print(f" Current: {section_data['current_mb']:.1f} MB")
print(f" Delta: {section_data['delta_mb']:.1f} MB")
Step 3: Profiling a Batch Inference Loop¶
For production services processing many images:
tracker = MemoryTracker()
tracker.start()
results = []
with tracker.section("batch_predict"):
for i, img_path in enumerate(unlabeled_images):
result = learner.predict(img_path)
results.append(result)
# Check memory every 100 predictions
if i % 100 == 0:
snapshot = tracker.snapshot()
if snapshot['current_mb'] > 200:
print(f"⚠️ Memory at {snapshot['current_mb']:.0f} MB after {i} predictions")
learner.clear_backbone_cache()
report = tracker.get_report()
print(f"Batch complete — processed {len(unlabeled_images)} images")
print(f"Peak memory: {report['peak_memory_mb']:.1f} MB")
Step 4: System-Level Profiling with psutil¶
For a complete picture including system overhead:
import psutil
import os
process = psutil.Process(os.getpid())
# Before
mem_before = process.memory_info().rss / 1024 / 1024
learner.load_support_images(image_paths, labels)
# After loading
mem_after = process.memory_info().rss / 1024 / 1024
print(f"RSS before: {mem_before:.1f} MB")
print(f"RSS after: {mem_after:.1f} MB")
print(f"Delta: {mem_after - mem_before:.1f} MB")
Step 5: Memory Budget Enforcement¶
Set a budget and get automatic alerts:
BUDGET_MB = 250
tracker = MemoryTracker()
tracker.start()
# Run your workload...
learner.load_support_images(image_paths, labels)
result = learner.predict("query.jpg")
report = tracker.get_report()
if report['peak_memory_mb'] > BUDGET_MB:
excess = report['peak_memory_mb'] - BUDGET_MB
print(f"⚠️ Memory budget exceeded by {excess:.0f} MB")
print(" Recommended actions:")
print(" 1. Reduce max_buffer_size (current: {config.max_buffer_size})")
print(" 2. Disable explainability if not needed")
print(" 3. Switch to mobilenet_v3_small backbone")
print(" 4. Call clear_backbone_cache() periodically")
else:
headroom = BUDGET_MB - report['peak_memory_mb']
print(f"✅ Within budget — {headroom:.0f} MB headroom")
Memory by Config Choice¶
| Config Setting | Memory Impact | When to Reduce |
|---|---|---|
max_buffer_size=200 |
~40-60 MB for embeddings | Low-RAM devices |
explainability_enabled=True |
~10-15 MB for attribution cache | < 500 MB total RAM |
backbone="resnet18" |
~70 MB (vs ~25 MB for mobilenet) | Mobile/embedded |
uncertainty_mode="ensemble" |
~5-10 MB for ensemble state | Tight budgets |
use_faiss=True |
~15-30 MB for FAISS index | < 200 MB total RAM |
Best Practices¶
- Profile early, profile often — run MemoryTracker on your first deployment
- Set a budget — 250 MB is AdaptShot's target; adjust for your hardware
- Clear backbone cache — call
learner.clear_backbone_cache()every 500-1000 predictions in long-running services - Monitor trend, not just peak — if memory grows 2 MB per 100 predictions, you have a slow leak
- Use section-level profiling — it tells you exactly which stage to optimize, not just that memory is high
Next Steps¶
- Tutorial 18: End-to-End Production Workflow — full production pipeline with profiling
- Real-World Use Cases — profiling integrated into each use case
- Troubleshooting Guide — memory-related issues and fixes