- Published on
Day 45: Cost Optimization Cho LLM/RAG Production
- Authors

- Name
- Trần Mạnh Thắng
- @TranManhThang96
1. Mục tiêu bài học
Sau Day 45, bạn cần nhìn một LLM/RAG app và trả lời được:
Request này tốn bao nhiêu tiền?
Stage nào đang làm cost phình lên?
Token budget của từng endpoint là gì?
Nếu vượt budget, hệ thống degrade như thế nào?
Nếu giảm context/model/output tokens, quality có còn đạt release gate không?
Dùng được trong production không, nếu có thì cần điều kiện gì?
Cost optimization không phải là "chọn model rẻ hơn". Với hệ thống thật, cost đến từ toàn pipeline:
- Input tokens và output tokens của LLM.
- Embedding cho query và documents.
- Reranking, query rewrite, classifier, guardrail hoặc citation validation.
- Vector DB, lexical search, Redis, database, queue và object storage.
- Retry, timeout, malformed structured output và fallback.
- Eval, synthetic data, offline batch, observability và log retention.
- GPU/serving utilization nếu self-host model.
Tư duy đúng của Senior SE: cost là một non-functional requirement, giống latency, availability và security. Phải đo, budget, enforce, alert và review theo từng release.
2. Bối cảnh dùng trong bài
Bài này lấy RAG app Day 40 làm ví dụ:
User question
-> normalize
-> optional semantic cache lookup
-> query embedding
-> dense search + lexical search
-> hybrid merge
-> rerank
-> context builder
-> LLM generation
-> citation validation
-> trace/cost logger
Mục tiêu không phải giảm cost bằng mọi giá. Mục tiêu là tìm điểm tối ưu theo context:
- Với FAQ nội bộ: cost thấp và latency thấp thường quan trọng hơn reasoning mạnh.
- Với HR/legal/policy: citation correctness quan trọng hơn vài phần trăm cost.
- Với eval/offline workload: có thể chấp nhận chậm hơn để dùng Batch API.
- Với tenant trả phí cao: có thể route sang model mạnh hơn và quota lớn hơn.
- Với tenant trial/free: có thể giới hạn output, context và retry.
3. Step-by-step cost optimization loop
Không tối ưu khi chưa có số liệu. Quy trình production nên đi theo vòng lặp:
1. Instrument
Log token, model, cache, retry, latency và stage-level trace.
2. Baseline
Tính cost/request, cost/day, cost/month theo tenant, feature và model.
3. Budget
Đặt token budget, request quota, eval budget và retry budget.
4. Optimize
Áp dụng cache, model routing, context pruning, batch hoặc distillation.
5. Verify
Chạy regression eval: retrieval, answer quality, citation, no-answer, latency.
6. Roll out
Canary theo tenant/traffic %, theo dõi cost và quality cùng lúc.
7. Degrade or rollback
Nếu vượt budget hoặc quality giảm, bật degrade mode hoặc rollback config.
Điểm quan trọng: mọi tối ưu cost phải có before/after estimate và quality gate. Nếu cost giảm 40% nhưng citation sai tăng mạnh, đó không phải optimization tốt cho RAG production.
4. Cost model
Công thức tối thiểu cho một request:
cost_per_request =
llm_input_tokens * input_price_per_token
+ llm_cached_input_tokens * cached_input_price_per_token
+ llm_output_tokens * output_price_per_token
+ reasoning_tokens * reasoning_price_per_token
+ embedding_tokens * embedding_price_per_token
+ reranker_units * reranker_price_per_unit
+ tool_calls * tool_price_per_call
+ retry_count * retry_cost
+ infra_cost_allocated_per_request
+ observability_cost_allocated_per_request
Với RAG app, tách theo stage để biết cần tối ưu ở đâu:
| Stage | Cost driver | Metric cần log | Tối ưu thường dùng |
|---|---|---|---|
| Query normalization | CPU hoặc LLM nhỏ | normalization_ms, rewrite_tokens | rule-based, small model |
| Semantic cache lookup | Redis + embedding | cache_lookup_ms, cache_hit | TTL, threshold, tenant scope |
| Query embedding | embedding tokens/request | embedding_tokens, embedding_model | cache query embedding, batch offline |
| Dense search | vector DB CPU/RAM | dense_top_k, dense_ms | metadata filter, index tuning |
| Lexical search | search infra | sparse_top_k, sparse_ms | index field đúng, limit candidate |
| Rerank | model/API call | rerank_candidates, rerank_ms | giảm candidate, dùng reranker nhỏ |
| Context build | input tokens | context_tokens, context_chunks | chunk pruning, compression |
| Generation | input/output tokens | prompt_tokens, completion_tokens | token budget, concise mode |
| Citation validation | CPU hoặc retry | citation_valid, retry_count | schema validation, stricter prompt |
| Eval | nhân số request | eval_items, eval_cost | Batch API, sampling |
| Observability | storage/log volume | trace_size_bytes | retention, sampling, redaction |
Daily/monthly estimate
cost_per_day =
sum(cost_per_request_by_type * requests_per_day_by_type)
+ offline_jobs_cost_per_day
+ fixed_infra_cost_per_day
cost_per_month = cost_per_day * 30
Nên estimate theo request type, không lấy một average mơ hồ:
| Request type | Traffic share | Cost behavior | Ghi chú |
|---|---|---|---|
| Simple FAQ | cao | input/output thấp, cache hit cao | ứng viên tốt cho semantic cache |
| Normal RAG | trung bình | context 3-6 chunks | default path |
| Complex RAG | thấp | context dài, model mạnh | quota riêng |
| Eval | theo batch | nhân golden set | chạy offline |
| Admin/debug | thấp | trace đầy đủ, output dài | chỉ cho internal user |
5. Trace log là nền tảng của cost optimization
Nếu trace không có token và model, bạn không thể tối ưu nghiêm túc. Mỗi request nên log tối thiểu:
{
"trace_id": "tr_20260510_001",
"timestamp": "2026-05-10T10:30:00Z",
"tenant_id": "tenant_a",
"user_tier": "paid",
"feature": "rag_query",
"request_type": "normal_rag",
"pipeline_version": "rag-cost-v2",
"corpus_version": "policy-2026-05",
"prompt_version": "rag-answer.v4",
"models": {
"embedding": "embedding-small",
"reranker": "reranker-base",
"generator": "llm-medium"
},
"usage": {
"prompt_tokens": 4200,
"cached_prompt_tokens": 1800,
"completion_tokens": 520,
"reasoning_tokens": 0,
"embedding_tokens": 32,
"rerank_units": 24
},
"retrieval": {
"dense_top_k": 50,
"sparse_top_k": 50,
"rerank_top_n": 24,
"context_chunks": 5,
"context_tokens": 3100
},
"cache": {
"prompt_cache_hit_tokens": 1800,
"semantic_cache_hit": false,
"semantic_cache_similarity": null
},
"latency_ms": {
"embedding": 42,
"dense_search": 38,
"sparse_search": 21,
"rerank": 170,
"generation": 1460,
"total": 1810
},
"retry": {
"count": 0,
"reason": null
},
"estimated_cost_usd": 0.0031,
"quality_signals": {
"answer_status": "answered",
"citation_valid": true,
"user_feedback": null
}
}
Production note:
- Log cost estimate ngay trong request path hoặc async worker gần realtime.
- Không log raw PII nếu không cần; dùng redaction và retention policy.
- Lưu
pricing_versionđể sau này biết cost được tính theo bảng giá nào. - Cost dashboard nên drill-down theo tenant, feature, route, model, prompt version và cache hit.
6. Token budget
Token budget là contract giữa product, backend và prompt. Nó phải được enforce ở backend, không chỉ ghi trong prompt.
Ví dụ budget cho RAG app:
| Endpoint/request | Max query tokens | Max context chunks | Max context tokens | Max output tokens | Max retries | Ghi chú |
|---|---|---|---|---|---|---|
/query simple FAQ | 120 | 3 | 1,800 | 250 | 1 | ưu tiên cache/latency |
/query normal RAG | 250 | 5 | 3,500 | 600 | 1 | default |
/query complex RAG | 500 | 8 | 7,000 | 900 | 1 | cần quota/tier |
/eval/run | 250 | 5 | 3,500 | 400 | 0 | deterministic, offline |
/documents/ingest | n/a | n/a | n/a | n/a | 3 | budget theo document tokens |
/admin/debug | 1,000 | 10 | 12,000 | 1,200 | 0 | internal only |
Backend enforcement nên có các bước:
receive request
-> classify request_type
-> load tenant/user budget policy
-> reject or trim query if too long
-> cap retrieval top_k and rerank_top_n
-> build candidate context
-> prune until context_tokens <= budget
-> set max_output_tokens on model call
-> cap retry count
-> log budget decision
Pseudo-code:
def enforce_token_budget(request, retrieved_chunks, policy, tokenizer):
query_tokens = tokenizer.count(request.question)
if query_tokens > policy.max_query_tokens:
raise BudgetError("question_too_long")
selected = []
total_context_tokens = 0
for chunk in retrieved_chunks[: policy.max_context_chunks * 2]:
chunk_tokens = tokenizer.count(chunk.text)
if total_context_tokens + chunk_tokens > policy.max_context_tokens:
continue
selected.append(chunk)
total_context_tokens += chunk_tokens
if len(selected) >= policy.max_context_chunks:
break
return GenerationPlan(
context_chunks=selected,
max_output_tokens=policy.max_output_tokens,
max_retries=policy.max_retries,
budget_name=policy.name,
)
Trade-off:
- Budget quá chặt giảm cost nhưng tăng no-answer hoặc answer thiếu bằng chứng.
- Budget quá rộng làm cost, latency và p95 spike.
- Best default cho RAG Day 40: đặt normal budget vừa đủ 4-5 chunks, chỉ mở complex budget khi classifier thấy câu hỏi cần multi-section reasoning.
7. Prompt caching
Prompt caching phù hợp khi phần prefix của prompt lặp lại:
system instruction
+ developer instruction
+ response schema
+ static examples
+ stable tool definitions
+ stable policy block
+ dynamic context
+ user question
Nguyên tắc thiết kế:
- Đặt phần static ở đầu prompt.
- Đặt user-specific data, query và context biến động ở cuối.
- Version hóa prompt:
prompt_version,schema_version,policy_version. - Log
cached_prompt_tokenshoặc field tương đương của provider. - Không trộn dữ liệu permission-sensitive vào cache key dùng chung.
Với provider như OpenAI, prompt caching có thể tự động áp dụng cho prompt đủ dài, usage trả về số cached tokens, và có tham số kiểu prompt_cache_key/retention tùy model. Chi tiết này thay đổi theo provider, nên production code không nên hardcode giả định; hãy log usage thực tế và đưa pricing vào config.
Risk:
- Cache key sai có thể làm giảm hit rate hoặc tạo rủi ro dữ liệu multi-tenant.
- Prefix thay đổi liên tục làm cache vô dụng.
- Caching không giảm output token cost; nếu answer quá dài, cost vẫn cao.
- Cached tokens vẫn có thể tính vào rate limit tùy provider.
Best use trong Day 40 RAG:
- Cache phần system prompt, schema, citation rules và static examples.
- Không cache global phần context chứa tài liệu tenant nếu chưa chắc boundary permission.
- Theo dõi
cached_tokens / prompt_tokens, p95 latency và cost/request trước/sau.
8. Semantic caching với Redis
Semantic cache trả lại answer đã có cho query gần nghĩa nhau, không chỉ query giống hệt nhau.
Flow:
question
-> normalize
-> classify cache eligibility
-> embedding(question)
-> vector search trong cache index
-> kiểm tra similarity threshold
-> kiểm tra tenant_id, permission_hash, corpus_version, prompt_version
-> nếu hit: trả cached answer
-> nếu miss: chạy full RAG pipeline rồi lưu cache entry
Cache entry tối thiểu:
{
"cache_id": "sc:tenant_a:policy-2026-05:rag-answer.v4:8f31",
"tenant_id": "tenant_a",
"permission_hash": "roles:employee",
"corpus_version": "policy-2026-05",
"prompt_version": "rag-answer.v4",
"answer_schema_version": "v1",
"embedding_model": "embedding-small",
"generator_model": "llm-medium",
"normalized_question": "ngay nghi phep nam full time",
"answer": "Nhân viên full-time có 12 ngày nghỉ phép năm [S1].",
"citations": ["doc_hr:v3:chunk_004"],
"source_chunk_hashes": ["sha256:..."],
"created_at": "2026-05-10T10:30:00Z",
"expires_at": "2026-05-17T10:30:00Z"
}
Redis có thể dùng theo hai lớp:
- Redis key-value để lưu answer payload, TTL, metadata và counters.
- Redis vector search hoặc vector DB riêng để search embedding của normalized question.
Pseudo-code:
def get_semantic_cache(question, auth, versions, embedding):
if not is_cacheable(question, auth):
return CacheMiss(reason="not_cacheable")
normalized = normalize_question(question)
query_vector = embedding.embed(normalized)
candidates = cache_index.search(
vector=query_vector,
top_k=5,
filters={
"tenant_id": auth.tenant_id,
"permission_hash": auth.permission_hash,
"corpus_version": versions.corpus,
"prompt_version": versions.prompt,
},
)
best = candidates[0] if candidates else None
if best and best.score >= 0.92 and still_valid(best.metadata):
payload = redis.get(best.cache_id)
return CacheHit(payload=payload, similarity=best.score)
return CacheMiss(reason="low_similarity_or_not_found")
Không dùng semantic cache cho:
- Câu hỏi chứa PII hoặc dữ liệu cá nhân nhạy cảm.
- Dữ liệu realtime: giá, tồn kho, lịch, trạng thái ticket.
- Câu hỏi yêu cầu tính toán theo thời điểm hiện tại.
- Domain có rủi ro cao nếu trả nhầm: legal advice, medical advice, financial decision.
- Multi-tenant/ACL chưa có permission hash chắc chắn.
Trade-off:
- Hit rate cao giúp giảm mạnh cost và latency.
- Similarity threshold thấp có thể trả nhầm answer.
- Invalidation khó nếu corpus thay đổi thường xuyên.
- Best default: chỉ bật cho FAQ có citation ổn định, threshold cao, TTL ngắn, scoped theo tenant + ACL + corpus version.
9. Model routing
Không phải request nào cũng cần model mạnh nhất. Router chọn model theo task, complexity, user tier, quota, latency SLO và risk.
Ví dụ rule table:
| Task | Default model tier | Khi nào nâng cấp | Khi nào hạ cấp |
|---|---|---|---|
| Intent classification | rule/small | intent không chắc | gần hết budget |
| Query rewrite | small | query dài, ambiguous | exact keyword query |
| JSON extraction | small/medium | schema fail nhiều | input đơn giản |
| RAG answer | medium | multi-hop, high-risk, paid tier | simple FAQ, cacheable |
| Citation validation | rule/small | citation conflict | debug off |
| Eval judge | strong | release gate chính | smoke test |
Router input nên rõ:
{
"request_type": "normal_rag",
"tenant_tier": "paid",
"remaining_monthly_budget_usd": 42.5,
"query_complexity": "medium",
"risk_level": "policy",
"latency_slo_ms": 2500,
"cache_hit": false,
"provider_health": "ok"
}
Pseudo-code:
def route_model(features, budgets, eval_scores):
if budgets.tenant_monthly_remaining_usd <= 0:
return Route(model="llm-small", mode="degraded", reason="budget_exhausted")
if features.cache_hit:
return Route(model=None, mode="cache_hit", reason="semantic_cache")
if features.request_type == "simple_faq":
return Route(model="llm-small", mode="normal", reason="simple_faq")
if features.risk_level in {"legal", "finance"} and budgets.user_tier == "paid":
return Route(model="llm-strong", mode="normal", reason="high_risk_paid")
if features.query_complexity == "complex":
return Route(model="llm-strong", mode="normal", reason="complex_query")
return Route(model="llm-medium", mode="normal", reason="default_rag")
Production guardrail:
- Mỗi route phải có eval riêng, không dùng một golden score chung.
- Log route reason để debug bill tăng bất thường.
- Có fallback khi provider lỗi, nhưng fallback cũng phải nằm trong budget.
- Model nhỏ phải qua structured output validation nếu dùng cho extraction.
10. Context compression và chunk pruning
RAG cost thường phình vì context quá dài. Tối ưu context nên làm trước khi đổi model.
Pipeline đề xuất:
retrieve top 50 dense + top 50 sparse
-> RRF merge
-> rerank top 24
-> remove duplicate document/section
-> keep top evidence spans
-> apply max context token budget
-> optional compression
-> build prompt with citations
Kỹ thuật:
- Giảm
context_top_ksau rerank, không nhất thiết giảm retrieval candidate ban đầu. - Dedupe chunk cùng document/heading nếu nội dung trùng ý.
- Prune chunk có score thấp hoặc không chứa entity/query terms quan trọng.
- Trích evidence span trong chunk thay vì đưa cả chunk dài.
- Tách mode
concisevàdetailed. - Dùng metadata filter trước retrieval: tenant, ACL, document type, language, version.
- Dùng compression bằng small model cho context dài, nhưng chỉ khi citation vẫn map được về source span.
Ví dụ chunk pruning:
def prune_context(reranked_hits, budget, tokenizer):
selected = []
used_sections = set()
used_tokens = 0
for hit in reranked_hits:
section_key = (hit.document_id, hit.heading)
if section_key in used_sections and hit.score < budget.duplicate_section_score:
continue
span = extract_relevant_span(hit.text, hit.query_terms, max_tokens=budget.max_span_tokens)
span_tokens = tokenizer.count(span)
if used_tokens + span_tokens > budget.max_context_tokens:
continue
selected.append(ContextBlock(source_id=hit.source_id, text=span, metadata=hit.metadata))
used_sections.add(section_key)
used_tokens += span_tokens
if len(selected) >= budget.max_context_chunks:
break
return selected
Trade-off:
- Pruning mạnh giảm cost nhưng có thể mất evidence.
- Compression có thể tạo paraphrase làm citation khó validate.
- Với policy assistant, ưu tiên giữ nguyên source span ngắn hơn là summary không còn trích dẫn trực tiếp.
Best default cho Day 40:
- Retrieval candidate vẫn rộng: dense 50 + sparse 50.
- Rerank còn 24.
- Context gửi LLM còn 4-5 blocks.
- Mỗi block là source span 250-500 tokens có citation metadata.
11. Batch API và offline workload
Batch API phù hợp với workload không cần trả lời realtime:
- Embedding hoặc re-embedding tài liệu.
- Nightly eval trên golden set.
- Synthetic question generation.
- Classification/summarization large dataset.
- Backfill trace analysis.
- Migration prompt/model version.
Không phù hợp với:
- Chat realtime.
- Request cần streaming.
- User-facing SLA tính bằng giây.
- Action cần kết quả ngay để tiếp tục workflow.
Production design:
Realtime path
-> strict latency SLO
-> small per-request budget
-> limited retry
-> user-facing quota
Offline path
-> queue/batch
-> separate quota
-> lower priority
-> resumable job
-> result audit table
Nhiều provider có Batch API với pricing/rate-limit khác sync API. Ví dụ tài liệu OpenAI hiện mô tả batch là async, thường có discount và completion window rõ. Tuy nhiên, thông số cụ thể là provider-specific; course code nên đọc từ pricing_config và feature flags.
12. Distillation overview
Distillation là dùng model mạnh tạo label/output để train hoặc fine-tune model nhỏ hơn.
Flow:
collect stable task traces
-> filter high-quality examples
-> generate teacher labels bằng strong model
-> human review hoặc rule validation
-> train/fine-tune student model
-> eval theo task
-> canary route một phần traffic
-> monitor quality/cost drift
Nên dùng khi:
- Task lặp lại nhiều: classification, extraction, routing, short answer.
- Format output rõ và có schema.
- Traffic đủ lớn để bù chi phí tạo dataset/eval.
- Có golden set đáng tin cậy.
- Requirement ổn định trong vài tháng.
Không nên dùng khi:
- Product requirement thay đổi liên tục.
- Domain cần reasoning mạnh hoặc context dài.
- Chưa có eval framework.
- Data có rủi ro compliance cao mà chưa xử lý governance.
Trade-off:
- Cost inference dài hạn có thể giảm nhiều.
- Chi phí ban đầu tăng: data labeling, training, eval, deployment.
- Model nhỏ dễ drift khi corpus hoặc policy đổi.
- Best use trong RAG Day 40: distill query classifier, intent router, no-answer detector hoặc extraction task; chưa nên distill toàn bộ answer generator nếu citation correctness là release gate chính.
13. Budget, quota và degrade mode
Production cần phân biệt:
- Budget: số tiền/token dự kiến được dùng trong một khoảng thời gian.
- Quota: giới hạn enforce được, ví dụ requests/day hoặc tokens/month.
- Rate limit: tốc độ request/token trong một cửa sổ ngắn.
- Degrade mode: hành vi khi budget/quota/rate limit sắp hoặc đã vượt.
Ví dụ policy:
| Level | Trigger | Hành động | User impact |
|---|---|---|---|
| Normal | dưới 70% monthly budget | full route | không |
| Watch | 70-85% | alert, tăng sampling logs | không đáng kể |
| Conserve | 85-95% | concise output, giảm context, ưu tiên cache | answer ngắn hơn |
| Degraded | 95-100% | route model nhỏ, tắt complex mode, batch eval | quality có thể giảm |
| Hard stop | vượt 100% hoặc abuse | reject non-critical, admin override | request bị từ chối |
Degrade mode nên có thứ tự:
1. Giảm max output tokens.
2. Bật concise answer mode.
3. Giảm context_top_k nhưng giữ citation validation.
4. Ưu tiên semantic cache cho FAQ.
5. Route simple task sang small model.
6. Tạm dừng eval/synthetic/offline job.
7. Reject complex/debug request không quan trọng.
8. Hard stop tenant hoặc user nếu vượt quota nghiêm trọng.
Không nên degrade bằng cách:
- Bỏ ACL filter.
- Tắt citation validation cho domain cần citation.
- Trả answer không có đủ context.
- Retry vô hạn sang provider khác.
- Chuyển sang model chưa qua eval.
14. Best solution theo context/performance
Không có một kỹ thuật tốt nhất cho mọi hệ thống. Chọn theo shape của workload.
| Context | Best first move | Vì sao | Cần đo |
|---|---|---|---|
| RAG app mới, traffic thấp | instrument + token budget | chưa có data để tối ưu sâu | cost/request, p95 tokens |
| FAQ traffic cao | semantic cache + small model route | query lặp lại nhiều | cache hit, false hit |
| Prompt/schema dài | prompt caching | giảm prefill cost/latency | cached tokens ratio |
| Context quá dài | chunk pruning + context budget | giảm input tokens trực tiếp | citation correctness |
| Eval/nightly job tốn tiền | Batch API | không cần realtime | job success, total cost |
| Nhiều task đơn giản | model routing | không dùng model mạnh cho mọi thứ | quality per route |
| Stable high-volume task | distillation | giảm cost dài hạn | student vs teacher eval |
| Self-host GPU idle thấp | batching + quantization + autoscaling | cost nằm ở utilization | GPU utilization, throughput |
Best default cho RAG Day 40:
- Log đầy đủ token/cost theo trace.
- Enforce token budget cho
/queryvà/eval/run. - Dùng prompt caching cho static prompt/schema.
- Prune context còn 4-5 source spans sau rerank.
- Dùng semantic cache chỉ cho FAQ tenant-scoped.
- Route simple FAQ/query rewrite sang small model.
- Chạy eval/offline workload bằng batch queue.
- Chỉ nghiên cứu distillation sau khi traffic và eval đủ ổn định.
15. Trade-off tổng hợp
| Kỹ thuật | Giảm cost | Tác động latency | Risk chính | Production condition |
|---|---|---|---|---|
| Max output tokens | cao | giảm | answer thiếu | có UX "more detail" hoặc detailed mode |
| Prompt caching | trung bình-cao | giảm prefill | cache miss nếu prefix biến động | static prefix, log cached tokens |
| Semantic cache | rất cao với FAQ | giảm mạnh | trả nhầm context | tenant/ACL/corpus scoped, threshold cao |
| Model routing | cao | tùy route | model nhỏ fail case khó | eval per route, fallback rõ |
| Context pruning | cao | giảm | mất evidence | citation regression test |
| Context compression | trung bình | có thể tăng vì thêm call | summary sai/lost citation | map về source span |
| Batch API | cao cho offline | không realtime | job chậm, retry phức tạp | separate queue/quota |
| Distillation | cao dài hạn | giảm | data/eval cost cao | task ổn định, golden set tốt |
| Local model | tùy utilization | tùy infra | ops/GPU phức tạp | tính TCO, monitor utilization |
16. Dùng được trong production không?
Có, dùng được trong production nếu cost optimization được triển khai như một control plane có đo lường, budget, policy và eval, không phải vài mẹo giảm token rời rạc.
Điều kiện tối thiểu:
- Mọi LLM/embedding/rerank call đều log token usage, model, pricing version và trace id.
- Có dashboard cost theo tenant, feature, endpoint, model và prompt version.
- Token budget được enforce ở backend.
- Cache key có tenant, ACL/permission hash, corpus version, prompt version và schema version.
- Model routing có eval per route và fallback được test.
- Context pruning/compression có regression eval cho retrieval, citation và no-answer.
- Offline jobs dùng quota riêng, không ăn hết quota realtime.
- Có budget alert, degrade mode, hard stop và admin override.
- Có runbook rollback khi cost giảm nhưng quality tụt.
- Không hardcode bảng giá trong business logic; giá nằm trong config/versioned table.
Chưa nên gọi là production-ready nếu:
- Chỉ estimate bằng spreadsheet, không có trace thật.
- Không log cached tokens, retry count hoặc context tokens.
- Semantic cache không scope theo tenant/permission.
- Tất cả request đều dùng cùng một model mạnh.
- Eval không chạy sau khi giảm context hoặc đổi model.
- Không có cách dừng cost spike do retry loop, abuse hoặc batch job lỗi.
17. Checklist trước khi merge cost optimization PR
- Before/after cost estimate có số liệu trace thật hoặc giả định ghi rõ.
- Có budget policy cho endpoint bị ảnh hưởng.
- Có metric quality liên quan: retrieval recall, citation correctness, no-answer accuracy, schema success.
- Có rollout plan: canary tenant hoặc traffic percentage.
- Có rollback plan: config flag, model route fallback, cache disable.
- Có alert cho cost spike và cache false hit.
- Có log
route_reason,budget_decision,cache_decision. - Không thay đổi ACL/security boundary để giảm cost.
- Không xóa trace/log cần cho audit nếu chưa có retention policy.
Tài liệu
1. Mental model nhanh
Một cost plan đáng tin phải trả lời được 5 câu:
1. Mỗi request tốn bao nhiêu tiền?
2. Cost đến từ stage nào?
3. Giới hạn token/quota nằm ở đâu và enforce như thế nào?
4. Khi vượt budget, hệ thống degrade ra sao?
5. Quality có còn đạt release gate sau tối ưu không?
Không hardcode bảng giá vào business logic. Giá model, discount, cached token price và Batch API behavior thay đổi theo provider. Production nên dùng pricing_config có version, effective date và dashboard đối chiếu billing thật.
2. Pricing config template
Ví dụ pricing.config.json:
{
"pricing_version": "provider-pricing-2026-05-10",
"currency": "USD",
"token_unit": 1000000,
"llm_models": {
"llm-small": {
"input_per_1m": "0.1500",
"cached_input_per_1m": "0.0150",
"output_per_1m": "0.6000",
"reasoning_per_1m": "0.6000"
},
"llm-medium": {
"input_per_1m": "0.5000",
"cached_input_per_1m": "0.0500",
"output_per_1m": "2.0000",
"reasoning_per_1m": "2.0000"
},
"llm-strong": {
"input_per_1m": "2.0000",
"cached_input_per_1m": "0.2000",
"output_per_1m": "8.0000",
"reasoning_per_1m": "8.0000"
}
},
"embedding_models": {
"embedding-small": {
"input_per_1m": "0.0200"
}
},
"rerankers": {
"reranker-base": {
"per_1000_units": "0.0800"
}
},
"tool_calls": {
"web_search": {
"per_call": "0.0100"
}
},
"infra_allocation": {
"fixed_daily_usd": "15.0000",
"expected_requests_per_day": 10000
}
}
Ghi chú:
- Các số trên là placeholder để làm bài, không phải giá thật.
cached_input_per_1mphải tách riêng với non-cached input.- Nếu provider không có reasoning tokens hoặc cached input price, để
0hoặc bằng input price theo contract của provider. - Nếu dùng Batch API, có thể thêm
batch_multiplier, ví dụ"batch_multiplier": "0.5", nhưng phải lấy từ provider pricing hiện tại.
3. Trace log schema
Trace log nên là JSONL, mỗi dòng là một request hoặc một job item:
{
"trace_id": "tr_001",
"timestamp": "2026-05-10T10:30:00Z",
"tenant_id": "tenant_a",
"user_id_hash": "sha256:...",
"feature": "rag_query",
"request_type": "normal_rag",
"pipeline_version": "rag-cost-v2",
"pricing_version": "provider-pricing-2026-05-10",
"prompt_version": "rag-answer.v4",
"corpus_version": "policy-2026-05",
"is_batch": false,
"models": {
"generator": "llm-medium",
"embedding": "embedding-small",
"reranker": "reranker-base"
},
"usage": {
"prompt_tokens": 4200,
"cached_prompt_tokens": 1800,
"completion_tokens": 520,
"reasoning_tokens": 0,
"embedding_tokens": 32,
"rerank_units": 24,
"tool_calls": {
"web_search": 0
}
},
"cache": {
"semantic_cache_hit": false,
"prompt_cache_hit_tokens": 1800
},
"retry": {
"count": 0,
"reason": null
},
"latency_ms": {
"total": 1810
},
"quality_signals": {
"answer_status": "answered",
"citation_valid": true
}
}
Validation rules:
cached_prompt_tokens <= prompt_tokens.pricing_versiontrong trace phải match config hoặc được map bằng compatibility table.models.generatorbắt buộc nếu không phải semantic cache hit.- Nếu
semantic_cache_hit=true, generation tokens nên bằng0hoặc trace phải giải thích vì sao vẫn gọi LLM. - Retry phải được log như cost thật, không chỉ log request cuối.
4. Cost estimate table
CSV template:
request_type,requests_per_day,prompt_tokens,cached_prompt_tokens,completion_tokens,embedding_tokens,rerank_units,semantic_cache_hit_rate,retry_rate,model,cost_per_request_usd,cost_per_day_usd
simple_faq,700,1800,900,220,24,8,0.35,0.01,llm-small,,
normal_rag,250,4200,1800,520,32,24,0.05,0.02,llm-medium,,
complex_rag,50,7800,2500,900,48,36,0.00,0.03,llm-strong,,
eval,30,3800,1600,350,32,24,0.00,0.00,llm-medium,,
Nên tạo 3 sheet hoặc 3 scenario:
- 1k requests/day.
- 10k requests/day.
- 100k requests/day.
Mỗi scenario cần ghi rõ:
- Traffic mix.
- Cache hit rate giả định.
- Retry rate giả định.
- Model route ratio.
- Batch/offline jobs.
- Fixed infra allocation.
- Quality risk.
5. Token budget policy template
Ví dụ token-budget.policy.yaml:
version: rag-token-budget-v2
defaults:
max_retries: 1
reject_when_question_tokens_over: true
log_budget_decision: true
policies:
simple_faq:
max_query_tokens: 120
max_context_chunks: 3
max_context_tokens: 1800
max_output_tokens: 250
max_rerank_candidates: 12
allowed_models: ["llm-small", "llm-medium"]
normal_rag:
max_query_tokens: 250
max_context_chunks: 5
max_context_tokens: 3500
max_output_tokens: 600
max_rerank_candidates: 24
allowed_models: ["llm-medium", "llm-strong"]
complex_rag:
max_query_tokens: 500
max_context_chunks: 8
max_context_tokens: 7000
max_output_tokens: 900
max_rerank_candidates: 36
allowed_models: ["llm-strong"]
required_tiers: ["paid", "enterprise"]
eval_run:
max_query_tokens: 250
max_context_chunks: 5
max_context_tokens: 3500
max_output_tokens: 400
max_rerank_candidates: 24
max_retries: 0
force_batch_when_items_over: 100
Budget decision log:
{
"trace_id": "tr_001",
"budget_policy": "normal_rag",
"budget_version": "rag-token-budget-v2",
"query_tokens": 84,
"context_tokens_before_pruning": 6900,
"context_tokens_after_pruning": 3380,
"context_chunks_before": 12,
"context_chunks_after": 5,
"max_output_tokens": 600,
"decision": "allowed"
}
6. Redis semantic cache template
Key format:
sc:{tenant_id}:{permission_hash}:{corpus_version}:{prompt_version}:{schema_version}:{embedding_model}:{cache_hash}
Payload:
{
"cache_id": "sc:tenant_a:roles_employee:policy_2026_05:rag_v4:v1:embedding_small:abc123",
"normalized_question": "ngay nghi phep nam full time",
"answer": "Nhân viên full-time có 12 ngày nghỉ phép năm [S1].",
"citations": [
{
"document_id": "doc_hr",
"chunk_id": "tenant_a:doc_hr:v3:004",
"source_id": "S1"
}
],
"source_chunk_hashes": ["sha256:..."],
"quality": {
"citation_valid": true,
"answer_status": "answered"
},
"created_at": "2026-05-10T10:30:00Z",
"ttl_seconds": 604800
}
Invalidation triggers:
- Document/corpus version thay đổi.
- Prompt/schema version thay đổi.
- ACL/role mapping thay đổi.
- Embedding model thay đổi.
- Citation validator phát hiện cached answer sai.
- User feedback negative có severity cao.
Metrics:
semantic_cache_hit_rate.semantic_cache_false_hit_rate.semantic_cache_saved_cost_usd.semantic_cache_p95_latency_ms.semantic_cache_entries_by_tenant.
7. Model routing policy template
version: rag-routing-v2
routes:
- name: semantic_cache_hit
when:
semantic_cache_hit: true
action:
mode: cache_hit
model: null
- name: simple_faq_low_risk
when:
request_type: simple_faq
risk_level: low
action:
mode: normal
model: llm-small
max_output_tokens: 250
- name: high_risk_paid
when:
risk_level_in: ["legal", "finance", "policy"]
user_tier_in: ["paid", "enterprise"]
query_complexity_in: ["medium", "complex"]
action:
mode: normal
model: llm-strong
max_output_tokens: 900
- name: conserve_budget
when:
tenant_budget_state_in: ["conserve", "degraded"]
action:
mode: degraded
model: llm-small
max_output_tokens: 350
context_top_k_delta: -2
default:
mode: normal
model: llm-medium
max_output_tokens: 600
Mỗi route cần:
- Eval set riêng hoặc tag riêng trong golden set.
- Alert nếu route ratio thay đổi bất thường.
- Rollback bằng config, không cần deploy code.
route_reasontrong trace.
8. Pseudo-code gần production: tính cost từ trace logs
Script dưới đây đọc trace JSONL, pricing config JSON và xuất summary CSV. Đây là pseudo-code gần production: dùng Decimal, validate field quan trọng, không double-count cached input tokens và gom nhóm theo tenant/feature/model/request type.
#!/usr/bin/env python3
import argparse
import csv
import json
from collections import defaultdict
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
MILLION = Decimal("1000000")
THOUSAND = Decimal("1000")
def money(value: Decimal) -> Decimal:
return value.quantize(Decimal("0.000001"), rounding=ROUND_HALF_UP)
def d(value, default="0") -> Decimal:
if value is None:
return Decimal(default)
return Decimal(str(value))
@dataclass(frozen=True)
class CostBreakdown:
llm_input: Decimal = Decimal("0")
llm_cached_input: Decimal = Decimal("0")
llm_output: Decimal = Decimal("0")
llm_reasoning: Decimal = Decimal("0")
embedding: Decimal = Decimal("0")
rerank: Decimal = Decimal("0")
tool: Decimal = Decimal("0")
infra: Decimal = Decimal("0")
@property
def total(self) -> Decimal:
return (
self.llm_input
+ self.llm_cached_input
+ self.llm_output
+ self.llm_reasoning
+ self.embedding
+ self.rerank
+ self.tool
+ self.infra
)
def load_json(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def iter_jsonl(path: Path):
with path.open("r", encoding="utf-8") as f:
for line_no, line in enumerate(f, start=1):
line = line.strip()
if not line:
continue
try:
yield line_no, json.loads(line)
except json.JSONDecodeError as exc:
raise ValueError(f"Invalid JSON at line {line_no}: {exc}") from exc
def require(trace: dict, field: str):
current = trace
for part in field.split("."):
if not isinstance(current, dict) or part not in current:
raise ValueError(f"Missing required field {field} in trace {trace.get('trace_id')}")
current = current[part]
return current
def per_token_cost(tokens: Decimal, price_per_1m: Decimal, multiplier: Decimal) -> Decimal:
return (tokens / MILLION) * price_per_1m * multiplier
def calculate_trace_cost(trace: dict, pricing: dict) -> CostBreakdown:
usage = trace.get("usage", {})
models = trace.get("models", {})
semantic_cache_hit = bool(trace.get("cache", {}).get("semantic_cache_hit", False))
batch_multiplier = Decimal("1")
if trace.get("is_batch"):
batch_multiplier = d(pricing.get("batch_multiplier", "1"))
infra = pricing.get("infra_allocation", {})
expected_requests = d(infra.get("expected_requests_per_day", "0"))
fixed_daily = d(infra.get("fixed_daily_usd", "0"))
infra_per_request = Decimal("0") if expected_requests <= 0 else fixed_daily / expected_requests
llm_input = Decimal("0")
llm_cached_input = Decimal("0")
llm_output = Decimal("0")
llm_reasoning = Decimal("0")
if not semantic_cache_hit:
generator = require(trace, "models.generator")
model_price = pricing["llm_models"][generator]
prompt_tokens = d(usage.get("prompt_tokens"))
cached_tokens = d(usage.get("cached_prompt_tokens"))
if cached_tokens > prompt_tokens:
raise ValueError(f"cached_prompt_tokens > prompt_tokens in trace {trace.get('trace_id')}")
non_cached_tokens = prompt_tokens - cached_tokens
completion_tokens = d(usage.get("completion_tokens"))
reasoning_tokens = d(usage.get("reasoning_tokens"))
llm_input = per_token_cost(
non_cached_tokens,
d(model_price.get("input_per_1m")),
batch_multiplier,
)
llm_cached_input = per_token_cost(
cached_tokens,
d(model_price.get("cached_input_per_1m", model_price.get("input_per_1m"))),
batch_multiplier,
)
llm_output = per_token_cost(
completion_tokens,
d(model_price.get("output_per_1m")),
batch_multiplier,
)
llm_reasoning = per_token_cost(
reasoning_tokens,
d(model_price.get("reasoning_per_1m", model_price.get("output_per_1m"))),
batch_multiplier,
)
embedding_cost = Decimal("0")
embedding_model = models.get("embedding")
if embedding_model and d(usage.get("embedding_tokens")) > 0:
embedding_price = pricing["embedding_models"][embedding_model]
embedding_cost = per_token_cost(
d(usage.get("embedding_tokens")),
d(embedding_price.get("input_per_1m")),
batch_multiplier,
)
rerank_cost = Decimal("0")
reranker = models.get("reranker")
if reranker and d(usage.get("rerank_units")) > 0:
reranker_price = pricing["rerankers"][reranker]
rerank_cost = (d(usage.get("rerank_units")) / THOUSAND) * d(reranker_price.get("per_1000_units"))
tool_cost = Decimal("0")
for tool_name, count in usage.get("tool_calls", {}).items():
tool_price = pricing.get("tool_calls", {}).get(tool_name)
if not tool_price:
raise ValueError(f"Missing pricing for tool call {tool_name}")
tool_cost += d(count) * d(tool_price.get("per_call"))
return CostBreakdown(
llm_input=money(llm_input),
llm_cached_input=money(llm_cached_input),
llm_output=money(llm_output),
llm_reasoning=money(llm_reasoning),
embedding=money(embedding_cost),
rerank=money(rerank_cost),
tool=money(tool_cost),
infra=money(infra_per_request),
)
def group_key(trace: dict) -> tuple[str, str, str, str, str]:
models = trace.get("models", {})
return (
trace.get("tenant_id", "unknown"),
trace.get("feature", "unknown"),
trace.get("request_type", "unknown"),
models.get("generator", "cache_or_unknown"),
trace.get("pricing_version", "unknown"),
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--traces", required=True, type=Path)
parser.add_argument("--pricing", required=True, type=Path)
parser.add_argument("--out", required=True, type=Path)
args = parser.parse_args()
pricing = load_json(args.pricing)
summary = defaultdict(lambda: {
"requests": 0,
"prompt_tokens": Decimal("0"),
"cached_prompt_tokens": Decimal("0"),
"completion_tokens": Decimal("0"),
"cost_total": Decimal("0"),
"cost_llm_input": Decimal("0"),
"cost_llm_cached_input": Decimal("0"),
"cost_llm_output": Decimal("0"),
"cost_embedding": Decimal("0"),
"cost_rerank": Decimal("0"),
"cost_tool": Decimal("0"),
"cost_infra": Decimal("0"),
"semantic_cache_hits": 0,
"retries": 0,
})
for _, trace in iter_jsonl(args.traces):
cost = calculate_trace_cost(trace, pricing)
key = group_key(trace)
row = summary[key]
usage = trace.get("usage", {})
row["requests"] += 1
row["prompt_tokens"] += d(usage.get("prompt_tokens"))
row["cached_prompt_tokens"] += d(usage.get("cached_prompt_tokens"))
row["completion_tokens"] += d(usage.get("completion_tokens"))
row["cost_total"] += cost.total
row["cost_llm_input"] += cost.llm_input
row["cost_llm_cached_input"] += cost.llm_cached_input
row["cost_llm_output"] += cost.llm_output + cost.llm_reasoning
row["cost_embedding"] += cost.embedding
row["cost_rerank"] += cost.rerank
row["cost_tool"] += cost.tool
row["cost_infra"] += cost.infra
row["semantic_cache_hits"] += int(bool(trace.get("cache", {}).get("semantic_cache_hit")))
row["retries"] += int(trace.get("retry", {}).get("count", 0) or 0)
args.out.parent.mkdir(parents=True, exist_ok=True)
with args.out.open("w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow([
"tenant_id",
"feature",
"request_type",
"generator_model",
"pricing_version",
"requests",
"avg_prompt_tokens",
"cached_token_ratio",
"avg_completion_tokens",
"semantic_cache_hit_rate",
"retry_rate",
"cost_total_usd",
"cost_per_request_usd",
"cost_llm_input_usd",
"cost_llm_cached_input_usd",
"cost_llm_output_usd",
"cost_embedding_usd",
"cost_rerank_usd",
"cost_tool_usd",
"cost_infra_usd",
])
for key, row in sorted(summary.items()):
requests = Decimal(row["requests"])
prompt_tokens = row["prompt_tokens"]
cached_tokens = row["cached_prompt_tokens"]
completion_tokens = row["completion_tokens"]
cost_total = money(row["cost_total"])
cached_ratio = Decimal("0") if prompt_tokens == 0 else cached_tokens / prompt_tokens
writer.writerow([
*key,
row["requests"],
money(prompt_tokens / requests),
money(cached_ratio),
money(completion_tokens / requests),
money(Decimal(row["semantic_cache_hits"]) / requests),
money(Decimal(row["retries"]) / requests),
cost_total,
money(cost_total / requests),
money(row["cost_llm_input"]),
money(row["cost_llm_cached_input"]),
money(row["cost_llm_output"]),
money(row["cost_embedding"]),
money(row["cost_rerank"]),
money(row["cost_tool"]),
money(row["cost_infra"]),
])
if __name__ == "__main__":
main()
Chạy script nếu lưu thành scripts/calc_cost_from_traces.py:
python scripts/calc_cost_from_traces.py \
--traces data/traces/day44-query-traces.jsonl \
--pricing config/pricing.config.json \
--out reports/cost-summary.csv
Production hardening cần thêm:
- Map
pricing_versioncũ sang bảng giá lịch sử. - Đối chiếu billing provider mỗi ngày.
- Alert nếu trace thiếu usage hoặc token bất thường.
- Unit test cho cached token, semantic cache hit, batch multiplier và retry.
- Không để script silently bỏ qua trace lỗi.
9. Cost dashboard
Dashboard tối thiểu:
| Chart | Group by | Mục đích |
|---|---|---|
| Cost/day | tenant, feature | phát hiện tenant/feature đốt tiền |
| Cost/request p50/p95 | request_type | biết tail cost |
| Token/request p50/p95 | model, prompt_version | phát hiện prompt/context phình |
| Cache hit rate | tenant, cache type | đánh giá prompt/semantic cache |
| Retry rate | endpoint, model | phát hiện schema/prompt/provider lỗi |
| Route ratio | route_name, model | phát hiện router đổi hành vi |
| Eval cost | eval suite, model | kiểm soát offline workload |
Alert gợi ý:
- Daily cost vượt forecast 20%.
- p95 prompt tokens tăng 30% sau deploy.
- Semantic cache false hit > 0.5% với domain policy.
- Retry rate > 3%.
- Eval/batch job dùng quá 20% daily budget.
- Tenant vượt 85% monthly budget.
10. Runbook: cost spike
Khi cost tăng bất thường:
1. Xác nhận spike
So sánh billing provider, internal cost summary và request volume.
2. Xác định phạm vi
Group by tenant, feature, endpoint, model, prompt_version, route_name.
3. Kiểm tra nguyên nhân phổ biến
- Traffic tăng thật.
- Retry loop.
- Prompt/context dài hơn sau deploy.
- Cache hit giảm.
- Router route nhiều request sang model mạnh.
- Eval/batch job chạy nhầm realtime quota.
4. Bật mitigation
- Disable job/offline workload.
- Bật concise mode.
- Giảm max output tokens.
- Giảm context_top_k.
- Route simple task sang small model.
- Rate limit tenant/user gây spike.
5. Verify quality
Chạy smoke eval cho citation/no-answer trước khi giữ mitigation lâu dài.
6. Postmortem
Cập nhật budget policy, alert threshold, test và dashboard.
Rollback levers nên là config/feature flag:
semantic_cache_enabled=falsemodel_router_version=previouscontext_budget_version=previouseval_jobs_paused=truecomplex_mode_enabled=false
11. PR note template
## Cost Optimization PR
### Problem
- Current cost/request:
- Current cost/day:
- Main driver:
### Change
- Technique:
- Affected endpoint/tenant:
- Config changed:
### Before/After estimate
| Metric | Before | After | Expected delta |
|---|---:|---:|---:|
| Cost/request | | | |
| p95 latency | | | |
| Prompt tokens/request | | | |
| Output tokens/request | | | |
| Cache hit rate | | | |
### Quality guardrail
- Golden set:
- Retrieval recall:
- Citation correctness:
- No-answer accuracy:
- Schema success:
### Risk
- Quality risk:
- Security/privacy risk:
- Operational risk:
### Rollout
- Canary scope:
- Monitoring window:
- Alert:
### Rollback
- Config flag:
- Owner:
- Time to rollback:
### Production readiness answer
Có/không dùng được trong production? Điều kiện còn thiếu là gì?
12. Tài liệu tham khảo provider-specific
- OpenAI Prompt Caching docs: https://platform.openai.com/docs/guides/prompt-caching
- OpenAI Batch API docs: https://platform.openai.com/docs/guides/batch/
Các link trên dùng để kiểm tra behavior hiện tại của OpenAI. Khi triển khai thật, vẫn cần đối chiếu provider đang dùng, model cụ thể, data retention policy và pricing page tại thời điểm release.
Bài tập
Mục tiêu
Bạn sẽ thiết kế một cost plan gần production cho RAG app Day 40. Bài tập không yêu cầu gọi provider thật, nhưng phải có số liệu giả định rõ, công thức rõ, trade-off rõ và production readiness answer rõ.
Thời lượng đề xuất:
- Bản tối thiểu: 60-90 phút.
- Bản tốt cho portfolio: 3-4 giờ.
- Bản gần production: 1 ngày, có script đọc trace logs và report.
0. Acceptance criteria
Hoàn thành bài tập khi bạn có:
- Bảng estimate cho 1k, 10k và 100k requests/day.
- Cost model có input tokens, cached input tokens, output tokens, embedding, rerank, retry, infra và observability.
- Token budget cho
/query,/eval/run,/documents/ingest. - Thiết kế prompt caching và semantic caching với Redis.
- Ít nhất 3 model routing rules.
- Thiết kế context compression/chunk pruning.
- Kế hoạch dùng Batch API cho offline workload.
- Distillation overview: khi nào đáng làm, khi nào không.
- Budget/quota/degrade mode.
- Pseudo-code hoặc script tính cost từ trace logs.
- PR note có before/after estimate, risk, rollout và rollback.
- Trả lời: "Dùng được trong production không? Nếu có thì cần điều kiện gì?"
1. Setup giả định
Giả định traffic ban đầu:
1,000 requests/day
70% simple FAQ
25% normal RAG
5% complex RAG
30 eval questions/day
Request profile:
| Request type | Prompt tokens | Cached prompt tokens | Output tokens | Embedding tokens | Rerank units | Cache hit rate | Retry rate | Model |
|---|---|---|---|---|---|---|---|---|
| simple_faq | 1,800 | 900 | 220 | 24 | 8 | 35% | 1% | llm-small |
| normal_rag | 4,200 | 1,800 | 520 | 32 | 24 | 5% | 2% | llm-medium |
| complex_rag | 7,800 | 2,500 | 900 | 48 | 36 | 0% | 3% | llm-strong |
| eval | 3,800 | 1,600 | 350 | 32 | 24 | 0% | 0% | llm-medium |
Placeholder pricing để làm bài:
llm-small: input 0.15 / 1M, cached input 0.015 / 1M, output 0.60 / 1M
llm-medium: input 0.50 / 1M, cached input 0.050 / 1M, output 2.00 / 1M
llm-strong: input 2.00 / 1M, cached input 0.200 / 1M, output 8.00 / 1M
embedding-small: 0.02 / 1M tokens
reranker-base: 0.08 / 1000 units
fixed infra: 15 USD/day at 10k requests/day baseline
Các số trên là giả định học tập. Khi làm production thật, thay bằng pricing hiện tại của provider.
2. Bài 1: Tính baseline cost
Tạo bảng:
scenario,request_type,requests_per_day,prompt_tokens,cached_prompt_tokens,output_tokens,embedding_tokens,rerank_units,semantic_cache_hit_rate,retry_rate,model,cost_per_request,cost_per_day
1k,simple_faq,700,1800,900,220,24,8,0.35,0.01,llm-small,,
1k,normal_rag,250,4200,1800,520,32,24,0.05,0.02,llm-medium,,
1k,complex_rag,50,7800,2500,900,48,36,0.00,0.03,llm-strong,,
1k,eval,30,3800,1600,350,32,24,0.00,0.00,llm-medium,,
Công thức cho non-cache request:
non_cached_input_tokens = prompt_tokens - cached_prompt_tokens
llm_cost =
non_cached_input_tokens / 1_000_000 * input_price
+ cached_prompt_tokens / 1_000_000 * cached_input_price
+ output_tokens / 1_000_000 * output_price
embedding_cost = embedding_tokens / 1_000_000 * embedding_price
rerank_cost = rerank_units / 1000 * reranker_price
infra_cost = fixed_infra_daily / expected_requests_per_day
raw_cost_per_request = llm_cost + embedding_cost + rerank_cost + infra_cost
effective_cost_per_request =
raw_cost_per_request * (1 - semantic_cache_hit_rate)
+ cache_lookup_cost_estimate
+ raw_cost_per_request * retry_rate
Yêu cầu:
- Tính
cost_per_requestvàcost_per_daycho scenario 1k. - Nhân traffic để có scenario 10k và 100k.
- Tách eval/offline cost khỏi realtime cost.
- Ghi rõ assumption cho cache lookup cost. Nếu bỏ qua, ghi
0và giải thích.
3. Bài 2: Phân tích cost driver
Từ bảng baseline, trả lời:
1. Request type nào tốn nhiều nhất mỗi ngày?
2. Stage nào tốn nhiều nhất: input, output, rerank, infra, retry?
3. Nếu traffic tăng 10 lần, cost có tăng tuyến tính không? Vì sao?
4. Cache hit rate cần đạt bao nhiêu để giảm 20% cost simple FAQ?
5. Retry rate tăng từ 2% lên 8% ảnh hưởng cost/month thế nào?
Deliverable: 1 bảng top cost drivers và 3 nhận xét ngắn.
4. Bài 3: Thiết kế token budget
Thiết kế budget cho:
/querysimple FAQ./querynormal RAG./querycomplex RAG./eval/run./documents/ingest.
Template:
policies:
simple_faq:
max_query_tokens:
max_context_chunks:
max_context_tokens:
max_output_tokens:
max_retries:
allowed_models:
normal_rag:
max_query_tokens:
max_context_chunks:
max_context_tokens:
max_output_tokens:
max_retries:
allowed_models:
documents_ingest:
max_document_tokens_per_file:
max_files_per_job:
max_embedding_batch_tokens:
max_retries:
force_batch_when_tokens_over:
Yêu cầu:
- Ghi rõ policy nào dành cho free/paid/enterprise tier.
- Mô tả backend enforce budget ở stage nào.
- Mô tả UX khi request bị reject vì vượt budget.
5. Bài 4: Prompt caching plan
Thiết kế prompt để tăng cache hit:
Static prefix:
- system instruction
- developer instruction
- response schema
- citation rules
- stable examples
Dynamic suffix:
- tenant-specific context
- retrieved chunks
- user question
- current timestamp nếu thật sự cần
Yêu cầu:
- Xác định phần nào của prompt có thể cache.
- Xác định field cần log:
prompt_tokens,cached_prompt_tokens,cache_key,prompt_version. - Nêu 3 lý do prompt cache hit thấp.
- Nêu cách rollback nếu prompt caching làm latency/cost không như kỳ vọng.
Trade-off cần trả lời:
Prompt caching có giảm output token cost không?
Nếu prompt version đổi mỗi deploy thì chuyện gì xảy ra?
Với dữ liệu multi-tenant, phần nào không nên xem là cache prefix dùng chung?
6. Bài 5: Semantic caching với Redis
Thiết kế semantic cache cho simple FAQ.
Key format bắt buộc có:
tenant_idpermission_hashcorpus_versionprompt_versionschema_versionembedding_modelcache_hash
Template:
sc:{tenant_id}:{permission_hash}:{corpus_version}:{prompt_version}:{schema_version}:{embedding_model}:{cache_hash}
Yêu cầu:
- Chọn similarity threshold, ví dụ
0.92, và giải thích. - Chọn TTL, ví dụ 7 ngày, và giải thích.
- Thiết kế invalidation khi tài liệu thay đổi.
- Thiết kế metric false hit.
- Nêu loại câu hỏi không được cache.
Pseudo-code cần viết:
def should_use_semantic_cache(question, auth, request_type, risk_level):
if request_type not in {"simple_faq", "normal_rag"}:
return False
if risk_level in {"legal", "finance", "medical", "personal_data"}:
return False
if auth.permission_hash is None:
return False
if len(question) > 500:
return False
return True
def build_cache_scope(auth, versions, embedding_model):
return {
"tenant_id": auth.tenant_id,
"permission_hash": auth.permission_hash,
"corpus_version": versions.corpus,
"prompt_version": versions.prompt,
"schema_version": versions.answer_schema,
"embedding_model": embedding_model,
}
def lookup_semantic_cache(question, scope):
normalized = normalize_question(question)
embedding = embed_query(normalized, model=scope["embedding_model"])
hits = redis_vector_search(embedding, scope=scope, top_k=3)
best = hits[0] if hits else None
if best and best.score >= 0.92:
return best.payload
return None
def store_semantic_cache(question, answer, citations, scope):
if not citations:
return
payload = {
"question": normalize_question(question),
"answer": answer,
"citations": citations,
"scope": scope,
"ttl_seconds": 7 * 24 * 60 * 60,
}
redis_store_answer(payload)
7. Bài 6: Model routing rules
Thiết kế ít nhất 3 rules:
Ví dụ:
routes:
- name: simple_faq_small_model
condition: request_type == "simple_faq" and risk_level == "low"
model: llm-small
- name: complex_policy_strong_model
condition: request_type == "complex_rag" and user_tier in ["paid", "enterprise"]
model: llm-strong
- name: budget_conserve_mode
condition: tenant_budget_state in ["conserve", "degraded"]
model: llm-small
Yêu cầu:
- Mỗi route có
route_reason. - Mỗi route có metric kiểm chứng.
- Mỗi route có fallback.
- Nêu risk nếu route nhầm sang model nhỏ.
Metrics gợi ý:
- Answer acceptance rate.
- Citation correctness.
- No-answer accuracy.
- Schema success rate.
- Cost/request.
- p95 latency.
8. Bài 7: Context compression và chunk pruning
Thiết kế pipeline giảm context:
retrieve dense 50 + sparse 50
-> merge RRF
-> rerank top 24
-> dedupe document/heading
-> extract relevant spans
-> enforce context budget
-> build prompt
Yêu cầu:
- Chọn
context_top_kcho simple, normal và complex. - Chọn max tokens/span.
- Nêu khi nào dùng compression bằng small model.
- Nêu cách giữ citation correctness sau compression.
- Nêu metric regression cần chạy.
Trade-off cần trả lời:
Giảm top_k từ 8 xuống 4 có thể làm cost giảm nhưng rủi ro gì?
Compression có thể làm citation sai như thế nào?
Với HR/legal/policy assistant, nên ưu tiên raw evidence span hay summary?
9. Bài 8: Batch API và offline workload
Liệt kê workload nào nên chuyển sang batch:
- Nightly eval.
- Re-embedding tài liệu.
- Synthetic question generation.
- Backfill classification.
- Large summarization offline.
Thiết kế queue:
realtime_queue
- user-facing query
- strict latency SLO
- small retry budget
offline_batch_queue
- eval/reindex/synthetic
- separate quota
- resumable
- lower priority
Yêu cầu:
- Ước lượng cost eval nếu chạy sync mỗi ngày.
- Ước lượng cost eval nếu chạy batch với multiplier giả định.
- Nêu vì sao batch không dùng cho chat realtime.
- Nêu cách tránh offline job ăn hết quota realtime.
10. Bài 9: Distillation overview
Viết một đoạn thiết kế distillation cho một task nhỏ trong RAG app:
Gợi ý chọn một trong các task:
- Intent classification.
- Query complexity classifier.
- No-answer detector.
- JSON extraction.
- Query rewrite.
Template:
Task:
Teacher model:
Student model:
Training data source:
Label quality control:
Eval metrics:
Rollout plan:
Rollback plan:
Expected cost saving:
Risks:
Yêu cầu:
- Nêu vì sao chưa nên distill toàn bộ answer generator nếu citation correctness là critical.
- Nêu điều kiện traffic/eval để distillation đáng làm.
11. Bài 10: Budget/quota/degrade mode
Thiết kế budget policy:
tenant_budget:
monthly_usd: 500
states:
normal:
until_percent: 70
watch:
until_percent: 85
actions: ["alert_owner"]
conserve:
until_percent: 95
actions: ["concise_mode", "reduce_context_top_k"]
degraded:
until_percent: 100
actions: ["small_model_for_simple_tasks", "pause_offline_jobs"]
hard_stop:
actions: ["reject_non_critical_requests", "admin_override_required"]
Yêu cầu:
- Tách monthly budget, daily budget và per-request budget.
- Tách quota cho realtime và offline.
- Nêu thứ tự degrade.
- Nêu hành vi user thấy khi bị degrade.
- Nêu hành vi admin thấy trong dashboard.
Không được degrade bằng cách:
- Bỏ ACL filter.
- Bỏ citation validation ở domain cần citation.
- Trả answer không có context.
- Chạy model chưa qua eval.
12. Bài 11: Script tính cost từ trace logs
Tạo hoặc phác thảo script đọc JSONL:
python scripts/calc_cost_from_traces.py \
--traces data/traces/day44-query-traces.jsonl \
--pricing config/pricing.config.json \
--out reports/cost-summary.csv
Input trace tối thiểu:
{"trace_id":"tr_001","tenant_id":"tenant_a","feature":"rag_query","request_type":"normal_rag","pricing_version":"provider-pricing-2026-05-10","models":{"generator":"llm-medium","embedding":"embedding-small","reranker":"reranker-base"},"usage":{"prompt_tokens":4200,"cached_prompt_tokens":1800,"completion_tokens":520,"embedding_tokens":32,"rerank_units":24},"cache":{"semantic_cache_hit":false},"retry":{"count":0}}
Output CSV tối thiểu:
tenant_id,feature,request_type,generator_model,requests,cost_total_usd,cost_per_request_usd,avg_prompt_tokens,cached_token_ratio,semantic_cache_hit_rate,retry_rate
tenant_a,rag_query,normal_rag,llm-medium,250,0.82,0.00328,4200,0.428,0.05,0.02
Yêu cầu script:
- Không double-count cached prompt tokens.
- Tính semantic cache hit cost khác non-cache request.
- Group by tenant/feature/request_type/model.
- Fail nếu thiếu pricing cho model.
- Có test case cho retry và batch multiplier.
13. Bài 12: Viết optimization plan
Chọn 3 optimization và viết before/after estimate.
Gợi ý:
- Semantic cache cho simple FAQ.
- Giảm context normal RAG từ 8 chunks xuống 5 chunks sau rerank.
- Model routing query rewrite/FAQ sang small model.
- Chạy eval bằng Batch API.
- Giảm max output tokens và thêm UX detailed mode.
Template:
## Optimization 1
### Change
### Expected cost saving
### Quality risk
### Metrics to monitor
### Rollback
Yêu cầu:
- Mỗi optimization có trade-off.
- Mỗi optimization có metric.
- Mỗi optimization có rollback.
- Không chọn optimization phá security boundary.
14. Bài 13: Production readiness answer
Viết câu trả lời cuối trong README/PR:
Dùng được trong production không?
Có, nếu:
- trace log đã có token/model/cache/retry/cost theo request;
- token budget được enforce ở backend;
- cache scoped theo tenant, ACL, corpus version và prompt version;
- model routing có eval per route;
- context pruning có regression eval cho citation/no-answer;
- offline batch có quota riêng;
- budget alert, degrade mode và rollback flag đã sẵn sàng.
Chưa nên production nếu:
- chỉ có estimate thủ công mà không có trace thật;
- semantic cache chưa có permission boundary;
- chưa có quality gate sau khi giảm context/model;
- không có hard stop khi retry hoặc batch job gây cost spike.
15. Nộp bài
Deliverables:
reports/cost-estimate.mdhoặc spreadsheet export.config/pricing.config.json.config/token-budget.policy.yaml.config/model-routing.policy.yaml.docs/semantic-cache-design.md.scripts/calc_cost_from_traces.pyhoặc pseudo-code tương đương.reports/cost-optimization-pr-note.md.
Checklist tự review:
- Có số liệu cho 1k/10k/100k requests/day.
- Có cost/request và cost/month.
- Có ít nhất 3 cách giảm cost.
- Có trade-off quality/latency/security cho từng cách.
- Có best solution theo context.
- Có production readiness answer rõ.