MindRouter Scheduler
Mirrored from docs/scheduler.md in the MindRouter repository. Example hostnames are placeholders — substitute your own deployment.
Overview
MindRouter implements a Weighted Deficit Round Robin (WDRR) scheduler with backend scoring to achieve fair resource allocation across users while maximizing cluster throughput.
Goals
- Maximize token throughput - Process as many tokens as possible
- Minimize token latency - Keep response times low
- Allow bursting - Single user can use full cluster when idle
- Fair rebalancing - When others arrive, quickly rebalance
- Handle heterogeneity - Different backends, GPUs, models
Backend Scoring
Hard Constraints
Backends must pass all hard constraints to be considered:
- Model Available: Backend has the requested model
- Modality Support: Backend supports vision/embeddings if needed
- Structured Output: Backend supports JSON mode if requested
- Capacity:
current + queued < max_concurrent - Memory Fit: Estimated VRAM ≤ available VRAM
Soft Scores
Eligible backends are ranked by total score:
| Factor | Points | Description |
|---|---|---|
| Model Loaded | +100 | Model already in GPU memory |
| Low Utilization | +50 | GPU utilization < 50% |
| Low Latency | +40 | Low real-world latency (EMA) |
| Short Queue | +30 | Few pending requests |
| High Throughput | +20 | Recent tokens/second processed |
| Priority | +N×10 | Admin-configured preference |
Backend Selection
The backend with the highest total score is selected:
selected_backend = argmax(backends, key=lambda b: b.total_score)
Example Scenarios
Scenario 1: Single User, Idle Cluster
Time 0: Student submits request
- Burst credits available
- Routed immediately to best backend
- Full cluster available for burst
Scenario 2: Heavy User, Light User Arrives
Time 0: Faculty starts heavy load (many requests)
Time 5: Student submits single request
- Student has higher relative priority (less recent usage)
- Student request gets served quickly
- Faculty requests continue at lower priority
Scenario 3: Multiple Users Competing
Faculty group (weight=3), Staff group (weight=2), Student group (weight=1) all active
Distribution over time approaches:
- Faculty group: 50% of resources (3/6)
- Staff group: 33% of resources (2/6)
- Student group: 17% of resources (1/6)
Configuration
Group Configuration (Preferred)
Weights are configured per-group in the admin UI at /admin/groups. Each group has a scheduler_weight field that controls fair-share allocation. Individual users can have a weight_override on their quota record.
Environment Variables
# Deprecated weight env vars — still read as fallbacks when a user has
# no group or the group has no scheduler_weight set, but the admin UI
# group settings take precedence.
SCHEDULER_WEIGHT_STUDENT=1
SCHEDULER_WEIGHT_STAFF=2
SCHEDULER_WEIGHT_FACULTY=3
SCHEDULER_WEIGHT_ADMIN=10
# Fairness
SCHEDULER_FAIRNESS_WINDOW=300 # 5 minute rolling window
SCHEDULER_DEPRIORITIZE_THRESHOLD=0.5 # 50%
# Scoring
SCHEDULER_SCORE_MODEL_LOADED=100
SCHEDULER_SCORE_LOW_UTILIZATION=50
SCHEDULER_SCORE_LATENCY=40
SCHEDULER_SCORE_SHORT_QUEUE=30
SCHEDULER_SCORE_HIGH_THROUGHPUT=20
Monitoring
Queue Statistics
{
"queue": {
"total": 5,
"by_user": {"1": 3, "2": 2},
"by_model": {"llama3.2": 4, "mistral": 1},
"average_wait_seconds": 2.5
},
"fair_share": {
"total_users": 2,
"global_recent_tokens": 50000,
"user_stats": [
{"user_id": 1, "weight": 3, "deficit": -1000, "recent_tokens": 30000, "group": "faculty"},
{"user_id": 2, "weight": 1, "deficit": 500, "recent_tokens": 20000, "group": "student"}
]
}
}
Scheduler Decision Records
Each routing decision is logged:
{
"request_id": "abc123",
"selected_backend_id": 1,
"candidate_backends": [1, 2, 3],
"scores": {"1": 150, "2": 80, "3": 60},
"user_deficit": -1000,
"user_weight": 3,
"user_group": "faculty",
"user_recent_usage": 30000
}