Cost-Quality Optimization
Pareto curves, finding the optimal model mix for a given budget and quality target, and dynamic adjustment under load.
Given a dozen model options (different sizes, quantization levels, providers), which combination minimizes cost while meeting your quality bar? Cost-quality optimization turns this from guesswork into a systematic analysis using Pareto curves and constrained optimization.
1. The Configuration Space
Every model variant has a cost and quality point:
| Configuration | Quality (MT-Bench) | Cost/M tokens | Latency P50 | GPUs |
|---|---|---|---|---|
| Llama-405B BF16, TP=8 | 9.1 | $5.00 | 1200ms | 8× H100 |
| Llama-405B INT4, TP=4 | 8.9 | $2.80 | 900ms | 4× H100 |
| Llama-70B BF16, TP=4 | 8.4 | $1.50 | 600ms | 4× H100 |
| Llama-70B INT4, TP=1 | 8.2 | $0.55 | 400ms | 1× H100 |
| Llama-70B INT4, TP=1 (A100) | 8.2 | $0.40 | 500ms | 1× A100 |
| Llama-8B BF16, 1 GPU | 7.2 | $0.22 | 150ms | 1× H100 |
| Llama-8B INT4, 1 GPU | 7.0 | $0.12 | 100ms | 1× H100 |
| GPT-4o (API) | 9.0 | $2.50 | 800ms | — (API) |
| GPT-4o-mini (API) | 7.8 | $0.15 | 300ms | — (API) |
| Claude 3.5 Sonnet (API) | 8.8 | $3.00 | 700ms | — (API) |
That’s 10 options — and it gets worse when you add caching, batching, and speculative decoding variants.
2. Pareto Frontier
The Pareto frontier is the set of configurations where you can’t improve quality without increasing cost (or vice versa):
def compute_pareto_frontier(configs):
"""Find Pareto-optimal configurations (lower cost, higher quality)."""
# Sort by cost ascending
sorted_configs = sorted(configs, key=lambda c: c.cost)
pareto = []
best_quality = -float('inf')
for config in sorted_configs:
if config.quality > best_quality:
pareto.append(config)
best_quality = config.quality
return pareto
# Example result:
# Pareto frontier: 8B-INT4 ($0.12, 7.0) → 70B-INT4 ($0.55, 8.2) → 405B-INT4 ($2.80, 8.9) → 405B-BF16 ($5.00, 9.1)
#
# NOT on frontier: 70B-BF16 ($1.50, 8.4) — 70B-INT4 is cheaper at similar quality
# NOT on frontier: GPT-4o ($2.50, 9.0) — 405B-INT4 is cheaper at similar quality
Configurations below the Pareto curve are dominated — there exists a cheaper option at equal or better quality. Never use a dominated configuration.
3. Choosing Your Operating Point
Once you have the Pareto frontier, your operating point depends on constraints:
Given a Quality Floor → Minimize Cost
def cheapest_above_quality(pareto, quality_floor):
"""Find cheapest Pareto-optimal config above quality threshold."""
candidates = [c for c in pareto if c.quality >= quality_floor]
if not candidates:
raise ValueError(f"No config meets quality floor {quality_floor}")
return min(candidates, key=lambda c: c.cost)
# "I need MT-Bench ≥ 8.0"
# → 70B-INT4 ($0.55, 8.2) — cheapest option above 8.0
# "I need MT-Bench ≥ 8.5"
# → 405B-INT4 ($2.80, 8.9) — must jump to 405B
Given a Budget → Maximize Quality
def best_quality_under_budget(pareto, budget_per_m_tokens):
"""Find highest-quality config within budget."""
candidates = [c for c in pareto if c.cost <= budget_per_m_tokens]
if not candidates:
raise ValueError(f"No config fits budget ${budget_per_m_tokens}/M tokens")
return max(candidates, key=lambda c: c.quality)
# Budget: $1.00/M tokens
# → 70B-INT4 ($0.55, 8.2) — best quality under $1.00
Routing as a Mixed Strategy
Routing across multiple models creates intermediate operating points between Pareto-optimal configurations:
where is the fraction of traffic to model .
# Example: Route 60% to 8B, 40% to 70B
mixed_quality = 0.60 * 7.0 + 0.40 * 8.2 # = 7.48
mixed_cost = 0.60 * 0.12 + 0.40 * 0.55 # = $0.29/M tokens
# This creates a new point (7.48, $0.29) between the two Pareto configs
# Useful when you need quality between 7.0 and 8.2 at intermediate cost
4. Building the Quality Benchmark
The Pareto curve is only as good as your quality measurement. Generic benchmarks (MMLU, MT-Bench) may not reflect your workload.
Custom Evaluation Set
# Build evaluation set from real production queries
eval_set = {
"customer_support": [
{"prompt": "How do I reset my password?", "reference": "...", "weight": 0.40},
{"prompt": "My order hasn't arrived after 2 weeks", "reference": "...", "weight": 0.30},
# ...
],
"technical_qa": [
{"prompt": "Explain OAuth 2.0 PKCE flow", "reference": "...", "weight": 0.20},
# ...
],
"content_generation": [
{"prompt": "Write a product description for...", "reference": "...", "weight": 0.10},
# ...
],
}
def evaluate_config(config, eval_set):
"""Score a model configuration on your custom benchmark."""
scores = []
for category, items in eval_set.items():
for item in items:
output = config.model.generate(item["prompt"])
score = judge_quality(output, item["reference"]) # LLM-as-judge or custom metric
scores.append(score * item["weight"])
return sum(scores) / sum(item["weight"] for cat in eval_set.values() for item in cat)
Evaluation Dimensions
Quality isn’t one number. Track multiple dimensions:
| Dimension | Metric | Minimum acceptable |
|---|---|---|
| Correctness | Factual accuracy (LLM judge) | 90% |
| Instruction following | IFEval-style format compliance | 85% |
| Safety | Refusal on harmful queries | 99% |
| Latency | P99 TTFT | <2s |
| Consistency | Same question → similar answer | 80% agreement |
A model might score high on correctness but fail on safety — quality is a vector, not a scalar.
5. Dynamic Adjustment Under Load
The optimal operating point shifts with traffic:
class DynamicCostQualityOptimizer:
"""Adjust routing based on real-time conditions."""
def __init__(self, pareto_configs, quality_floor, budget_per_hour):
self.configs = pareto_configs
self.quality_floor = quality_floor
self.budget_per_hour = budget_per_hour
def get_routing_weights(self, current_load, current_spend_rate):
"""Adjust routing based on current conditions."""
budget_remaining_ratio = 1 - (current_spend_rate / self.budget_per_hour)
if budget_remaining_ratio < 0.1:
# Running out of budget — shift traffic to cheaper models
return self._budget_saving_weights()
if current_load > self.peak_threshold:
# High load — route to faster (smaller) models to reduce queue
return self._low_latency_weights()
# Normal operation — optimal cost-quality point
return self._optimal_weights()
def _budget_saving_weights(self):
"""Emergency: maximize 8B usage."""
return {"8b": 0.90, "70b": 0.10, "405b": 0.00}
def _low_latency_weights(self):
"""High load: minimize latency."""
return {"8b": 0.70, "70b": 0.25, "405b": 0.05}
def _optimal_weights(self):
"""Normal: best cost-quality balance."""
return {"8b": 0.40, "70b": 0.35, "405b": 0.25}
Time-of-Day Optimization
Traffic patterns are predictable — adjust proactively:
| Time | Traffic | Strategy | Rationale |
|---|---|---|---|
| Business hours (9am-5pm) | High | More 8B, less 405B | Manage queue depth |
| Evening (5pm-10pm) | Medium | Balanced routing | Normal optimization |
| Night (10pm-6am) | Low | More 405B | GPU capacity is idle anyway |
| Batch processing (weekend) | Variable | All 70B | Best throughput/quality balance |
6. Worked Example: Customer Support Bot
Requirements: Quality ≥ 8.0/10, budget ≤ $500/day, peak 50 req/s.
Step 1: Evaluate all models on your support-specific benchmark:
| Model | Support quality | Cost/M tokens | Throughput |
|---|---|---|---|
| 8B-INT4 | 7.2 | $0.12 | 200 tok/s/GPU |
| 70B-INT4 | 8.5 | $0.55 | 50 tok/s/GPU |
| 405B-INT4 | 9.0 | $2.80 | 12 tok/s/GPU |
Step 2: 8B alone doesn’t meet quality (7.2 < 8.0). 70B alone: 1,100/day (over budget).
Step 3: Use cascading 8B → 70B with 65% hit rate:
- Cost: 0.12 + 0.35 × (0.55) = $0.31/M tokens
- Daily: 620/day — still over budget
Step 4: Add routing to reduce escalation. Train router to identify easy queries:
- 50% → 8B (quality 7.2, but these are easy queries where 8B gets 8.5+)
- 35% → 70B
- 15% → 405B (complex complaints, escalations)
- Cost: 0.12 + 0.35 × 2.80 = $0.67/M tokens… actually worse
Step 5: Cascade 8B → 70B only (no 405B), accept quality dip on hardest 5%:
- Cost: 70% hit → 620/day
- Reduce avg output length by improving prompts: 2M → 1.5M tokens/day → $465/day ✓
Result: Cascading 8B → 70B, prompt optimization to reduce token count, $465/day, quality 8.3/10.
Self-Check
- Plot a Pareto frontier from a set of model configurations
- Find the cheapest configuration above a quality threshold
- Calculate the cost and quality of a mixed routing strategy
- Explain why custom evaluation sets matter more than generic benchmarks
- Design a dynamic optimizer that adjusts routing based on budget remaining