The translation service now supports automatic eviction of cached models that haven't been used for a configurable period. This feature helps optimize memory usage by automatically unloading models that are no longer actively needed.
Every cached model has its access time tracked:
- Last Access Time: Updated whenever a model is loaded (
put) or used (get) - Idle Duration: Current time minus last access time
- Timeout Threshold: Configurable via
MODEL_IDLE_TIMEOUT
A maintenance task runs periodically to check for idle models:
- Checks every
IDLE_CHECK_INTERVALseconds (default: 60) - Evicts models where
idle_duration > MODEL_IDLE_TIMEOUT - Cleans up GPU memory after evictions
- Logs eviction activities
The idle timeout eviction works alongside existing eviction mechanisms:
- LRU Eviction: When cache reaches
MAX_CACHED_MODELScapacity - Memory Pressure Eviction: When RAM/VRAM exceeds critical thresholds
- Idle Timeout Eviction: When models haven't been used for
MODEL_IDLE_TIMEOUTseconds
All three mechanisms coexist and operate independently.
# Time-based eviction settings
MODEL_IDLE_TIMEOUT=3600 # Evict models idle for 1 hour (0 = disabled)
IDLE_CHECK_INTERVAL=60 # Check for idle models every 60 seconds
# Existing cache settings (still apply)
MAX_CACHED_MODELS=10 # Maximum models in cache (LRU eviction)
ENABLE_MEMORY_MONITOR=1 # Memory-based eviction
MEMORY_CRITICAL_THRESHOLD=90 # Auto-evict at 90% RAM| Variable | Type | Default | Description |
|---|---|---|---|
MODEL_IDLE_TIMEOUT |
int | 0 (disabled) | Seconds before evicting idle models. Set to 0 to disable. |
IDLE_CHECK_INTERVAL |
int | 60 | How often to check for idle models (seconds). Only active if MODEL_IDLE_TIMEOUT > 0. |
docker run --gpus all \
-e USE_GPU=true \
-e MODEL_IDLE_TIMEOUT=1800 \
-e IDLE_CHECK_INTERVAL=300 \
-p 8000:8000 \
mostlylucid-nmt:gpu- Models idle for 30+ minutes will be evicted
- Check runs every 5 minutes
- Useful for services with sporadic usage patterns
docker run --gpus all \
-e USE_GPU=true \
-e MODEL_IDLE_TIMEOUT=300 \
-e IDLE_CHECK_INTERVAL=60 \
-p 8000:8000 \
mostlylucid-nmt:gpu- Models idle for 5+ minutes will be evicted
- Check runs every minute
- Useful for memory-constrained environments
docker run --gpus all \
-e USE_GPU=true \
-e MODEL_IDLE_TIMEOUT=0 \
-p 8000:8000 \
mostlylucid-nmt:gpu- Idle eviction disabled
- Models only evicted by LRU or memory pressure
- Useful when models are frequently reused
When idle eviction is enabled:
⏰ Idle model eviction enabled: 3600s timeout (check every 60s)
Maintenance task started (interval: 60s, CUDA clearing: False, idle eviction: 3600s)
When disabled:
⏰ Idle model eviction disabled (MODEL_IDLE_TIMEOUT=0)
When idle models are evicted:
⏰ Found 2 idle models (timeout: 3600s)
⏰ Evicted idle model: en->fr (idle for 62m 15s)
⏰ Evicted idle model: de->en (idle for 75m 42s)
⏰ Idle eviction complete: 2 models evicted (3/10 remaining)
curl http://localhost:8000/cacheResponse includes current cache state:
{
"capacity": 10,
"size": 3,
"keys": ["en->de", "en->es", "fr->en"],
"utilization": "3/10 (30%)",
"system_memory": {
"percentage": 65.2,
"used_gb": 10.4,
"total_gb": 16.0,
"status": "ok"
}
}Scenario: Service supporting 50+ language pairs, but only 5-10 actively used at any time
Configuration:
MAX_CACHED_MODELS=15
MODEL_IDLE_TIMEOUT=1800 # 30 minutes
IDLE_CHECK_INTERVAL=300 # 5 minutesBenefit: Keeps frequently used models hot, evicts rarely used ones
Scenario: Developer testing multiple translation pairs, switching frequently
Configuration:
MAX_CACHED_MODELS=5
MODEL_IDLE_TIMEOUT=600 # 10 minutes
IDLE_CHECK_INTERVAL=120 # 2 minutesBenefit: Balances quick model switching with memory conservation
Scenario: Service handling EN↔DE and EN↔FR primarily, occasional other pairs
Configuration:
MAX_CACHED_MODELS=10
MODEL_IDLE_TIMEOUT=7200 # 2 hours
IDLE_CHECK_INTERVAL=600 # 10 minutes
PRELOAD_MODELS="en->de,de->en,en->fr,fr->en"Benefit: Core models stay loaded, occasional pairs evicted after 2 hours
Scenario: Running on a system with limited RAM/VRAM
Configuration:
MAX_CACHED_MODELS=3
MODEL_IDLE_TIMEOUT=300 # 5 minutes
IDLE_CHECK_INTERVAL=60 # 1 minute
MEMORY_CRITICAL_THRESHOLD=85 # Aggressive memory managementBenefit: Maximizes available memory, evicts idle models quickly
- Opus-MT models: ~300MB-1GB each
- mBART50: ~2.3GB (single model for all pairs)
- M2M100: ~2.5GB (single model for all pairs)
Example: With 10 idle Opus-MT models evicted:
- Memory freed: 3-10GB RAM/VRAM
- Reload time: 10-30s per model when needed again
| Shorter Timeout (e.g., 5 minutes) | Longer Timeout (e.g., 2 hours) |
|---|---|
| ✅ Maximizes memory availability | ✅ Minimizes model reload overhead |
| ✅ Good for sporadic usage | ✅ Good for bursty traffic patterns |
| ❌ More frequent model reloads | ❌ Higher memory usage |
| ❌ Increased latency on cold requests | ❌ Risk of OOM on memory-constrained systems |
- Lower interval (e.g., 30s): More responsive, slightly higher CPU usage
- Higher interval (e.g., 300s): More efficient, models may stay longer than timeout
Recommended: IDLE_CHECK_INTERVAL = MODEL_IDLE_TIMEOUT / 6 (check ~6 times during timeout period)
The feature is implemented in:
src/core/cache.py:LRUPipelineCache.evict_idle_models()src/app.py:_maintenance_task()background tasksrc/config.py: Configuration variables
# Tracked in LRUPipelineCache
self.last_access_times: dict[str, float] = {}
# Updated on get()
self.last_access_times[key] = time.time()
# Updated on put()
self.last_access_times[key] = time.time()
# Cleaned up on eviction
self.last_access_times.pop(key, None)def evict_idle_models(self, timeout_seconds: int) -> List[str]:
current_time = time.time()
evicted_keys = []
for key in list(self.keys()):
last_access = self.last_access_times.get(key, 0)
idle_duration = current_time - last_access
if idle_duration > timeout_seconds:
# Evict model and clean up GPU memory
val = self.pop(key)
self.last_access_times.pop(key, None)
evicted_keys.append(key)
if hasattr(val, "model"):
val.model.cpu()
del val
if torch.cuda.is_available():
torch.cuda.empty_cache()
return evicted_keysRun the test suite:
pytest tests/test_cache_idle_eviction.py -vTest coverage includes:
- ✅ Disabled timeout (0) does nothing
- ✅ Recently accessed models not evicted
- ✅ Old models evicted correctly
- ✅ Access time tracking cleaned up
- ✅ Getting a model updates its access time
- ✅ Idle and LRU eviction work together
- ✅ Empty cache doesn't error
Symptom: Models stay in cache longer than MODEL_IDLE_TIMEOUT
Possible Causes:
MODEL_IDLE_TIMEOUT=0(feature disabled)- Models are being accessed more frequently than expected
IDLE_CHECK_INTERVALis too large
Solution:
# Check configuration
docker exec <container> env | grep -E '(MODEL_IDLE_TIMEOUT|IDLE_CHECK_INTERVAL)'
# Check logs
docker logs <container> | grep -E '(idle|Idle)'Symptom: High latency, frequent model loading logs
Possible Causes:
MODEL_IDLE_TIMEOUTtoo short for usage patternIDLE_CHECK_INTERVALtoo frequent
Solution:
# Increase timeout
-e MODEL_IDLE_TIMEOUT=3600 # 1 hour instead of 5 minutes
# Reduce check frequency
-e IDLE_CHECK_INTERVAL=300 # 5 minutes instead of 1 minuteSymptom: High memory usage despite idle eviction
Possible Causes:
MAX_CACHED_MODELStoo high- Models are frequently accessed (not idle)
- Memory leak elsewhere
Solution:
# Reduce max cache size
-e MAX_CACHED_MODELS=5
# More aggressive memory monitoring
-e MEMORY_CRITICAL_THRESHOLD=80
# More aggressive idle timeout
-e MODEL_IDLE_TIMEOUT=300 # 5 minutesQ: Does this replace LRU eviction? A: No, both work together. LRU evicts when cache is full, idle timeout evicts based on time.
Q: What happens if a model is evicted while in use? A: Models are only evicted by the background task between requests. Active translations are never interrupted.
Q: Can I disable idle eviction?
A: Yes, set MODEL_IDLE_TIMEOUT=0 (default).
Q: Does this work on CPU? A: Yes, it works on both CPU and GPU.
Q: How does this affect preloaded models? A: Preloaded models are subject to idle eviction like any other model.
Q: Can I manually trigger eviction?
A: Not via API, but the maintenance task runs automatically based on IDLE_CHECK_INTERVAL.
- Start Conservative: Begin with longer timeouts (1-2 hours) and adjust based on monitoring
- Match Usage Patterns: Use shorter timeouts for sporadic usage, longer for steady traffic
- Monitor Logs: Watch for eviction patterns and adjust accordingly
- Balance with LRU: Set
MAX_CACHED_MODELSto handle burst traffic, use idle timeout for cleanup - Test in Development: Experiment with aggressive timeouts in dev to understand reload overhead
- Combine with Memory Monitoring: Use both idle timeout and memory-based eviction for robustness
MAX_CACHED_MODELS: LRU cache capacityENABLE_MEMORY_MONITOR: Enable/disable memory-based evictionMEMORY_CRITICAL_THRESHOLD: Auto-evict when RAM exceeds this %GPU_MEMORY_CRITICAL_THRESHOLD: Auto-evict when VRAM exceeds this %CUDA_CACHE_CLEAR_INTERVAL_SEC: Periodic CUDA cache clearing
- Main README: README.md
- Configuration Guide: CLAUDE.md
- Cache Implementation: src/core/cache.py
- Test Suite: tests/test_cache_idle_eviction.py