Published on

Day 45: Cost Optimization Cho LLM/RAG Production

Authors

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 estimatequality 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:

StageCost driverMetric cần logTối ưu thường dùng
Query normalizationCPU hoặc LLM nhỏnormalization_ms, rewrite_tokensrule-based, small model
Semantic cache lookupRedis + embeddingcache_lookup_ms, cache_hitTTL, threshold, tenant scope
Query embeddingembedding tokens/requestembedding_tokens, embedding_modelcache query embedding, batch offline
Dense searchvector DB CPU/RAMdense_top_k, dense_msmetadata filter, index tuning
Lexical searchsearch infrasparse_top_k, sparse_msindex field đúng, limit candidate
Rerankmodel/API callrerank_candidates, rerank_msgiảm candidate, dùng reranker nhỏ
Context buildinput tokenscontext_tokens, context_chunkschunk pruning, compression
Generationinput/output tokensprompt_tokens, completion_tokenstoken budget, concise mode
Citation validationCPU hoặc retrycitation_valid, retry_countschema validation, stricter prompt
Evalnhân số requesteval_items, eval_costBatch API, sampling
Observabilitystorage/log volumetrace_size_bytesretention, 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 typeTraffic shareCost behaviorGhi chú
Simple FAQcaoinput/output thấp, cache hit caoứng viên tốt cho semantic cache
Normal RAGtrung bìnhcontext 3-6 chunksdefault path
Complex RAGthấpcontext dài, model mạnhquota riêng
Evaltheo batchnhân golden setchạy offline
Admin/debugthấptrace đầy đủ, output dàichỉ 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/requestMax query tokensMax context chunksMax context tokensMax output tokensMax retriesGhi chú
/query simple FAQ12031,8002501ưu tiên cache/latency
/query normal RAG25053,5006001default
/query complex RAG50087,0009001cần quota/tier
/eval/run25053,5004000deterministic, offline
/documents/ingestn/an/an/an/a3budget theo document tokens
/admin/debug1,0001012,0001,2000internal 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ế:

  1. Đặt phần static ở đầu prompt.
  2. Đặt user-specific data, query và context biến động ở cuối.
  3. Version hóa prompt: prompt_version, schema_version, policy_version.
  4. Log cached_prompt_tokens hoặc field tương đương của provider.
  5. 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:

TaskDefault model tierKhi nào nâng cấpKhi nào hạ cấp
Intent classificationrule/smallintent không chắcgần hết budget
Query rewritesmallquery dài, ambiguousexact keyword query
JSON extractionsmall/mediumschema fail nhiềuinput đơn giản
RAG answermediummulti-hop, high-risk, paid tiersimple FAQ, cacheable
Citation validationrule/smallcitation conflictdebug off
Eval judgestrongrelease gate chínhsmoke 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_k sau 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 concisedetailed.
  • 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:

LevelTriggerHành độngUser impact
Normaldưới 70% monthly budgetfull routekhông
Watch70-85%alert, tăng sampling logskhông đáng kể
Conserve85-95%concise output, giảm context, ưu tiên cacheanswer ngắn hơn
Degraded95-100%route model nhỏ, tắt complex mode, batch evalquality có thể giảm
Hard stopvượt 100% hoặc abusereject non-critical, admin overriderequest 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.

ContextBest first moveVì saoCần đo
RAG app mới, traffic thấpinstrument + token budgetchưa có data để tối ưu sâucost/request, p95 tokens
FAQ traffic caosemantic cache + small model routequery lặp lại nhiềucache hit, false hit
Prompt/schema dàiprompt cachinggiảm prefill cost/latencycached tokens ratio
Context quá dàichunk pruning + context budgetgiảm input tokens trực tiếpcitation correctness
Eval/nightly job tốn tiềnBatch APIkhông cần realtimejob success, total cost
Nhiều task đơn giảnmodel routingkhông dùng model mạnh cho mọi thứquality per route
Stable high-volume taskdistillationgiảm cost dài hạnstudent vs teacher eval
Self-host GPU idle thấpbatching + quantization + autoscalingcost nằm ở utilizationGPU utilization, throughput

Best default cho RAG Day 40:

  1. Log đầy đủ token/cost theo trace.
  2. Enforce token budget cho /query/eval/run.
  3. Dùng prompt caching cho static prompt/schema.
  4. Prune context còn 4-5 source spans sau rerank.
  5. Dùng semantic cache chỉ cho FAQ tenant-scoped.
  6. Route simple FAQ/query rewrite sang small model.
  7. Chạy eval/offline workload bằng batch queue.
  8. Chỉ nghiên cứu distillation sau khi traffic và eval đủ ổn định.

15. Trade-off tổng hợp

Kỹ thuậtGiảm costTác động latencyRisk chínhProduction condition
Max output tokenscaogiảmanswer thiếucó UX "more detail" hoặc detailed mode
Prompt cachingtrung bình-caogiảm prefillcache miss nếu prefix biến độngstatic prefix, log cached tokens
Semantic cacherất cao với FAQgiảm mạnhtrả nhầm contexttenant/ACL/corpus scoped, threshold cao
Model routingcaotùy routemodel nhỏ fail case khóeval per route, fallback rõ
Context pruningcaogiảmmất evidencecitation regression test
Context compressiontrung bìnhcó thể tăng vì thêm callsummary sai/lost citationmap về source span
Batch APIcao cho offlinekhông realtimejob chậm, retry phức tạpseparate queue/quota
Distillationcao dài hạngiảmdata/eval cost caotask ổn định, golden set tốt
Local modeltùy utilizationtùy infraops/GPU phức tạptí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_1m phải tách riêng với non-cached input.
  • Nếu provider không có reasoning tokens hoặc cached input price, để 0 hoặ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_version trong trace phải match config hoặc được map bằng compatibility table.
  • models.generator bắt buộc nếu không phải semantic cache hit.
  • Nếu semantic_cache_hit=true, generation tokens nên bằng 0 hoặ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_reason trong 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_version cũ 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:

ChartGroup byMục đích
Cost/daytenant, featurephát hiện tenant/feature đốt tiền
Cost/request p50/p95request_typebiết tail cost
Token/request p50/p95model, prompt_versionphát hiện prompt/context phình
Cache hit ratetenant, cache typeđánh giá prompt/semantic cache
Retry rateendpoint, modelphát hiện schema/prompt/provider lỗi
Route ratioroute_name, modelphát hiện router đổi hành vi
Eval costeval suite, modelkiể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=false
  • model_router_version=previous
  • context_budget_version=previous
  • eval_jobs_paused=true
  • complex_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

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 typePrompt tokensCached prompt tokensOutput tokensEmbedding tokensRerank unitsCache hit rateRetry rateModel
simple_faq1,80090022024835%1%llm-small
normal_rag4,2001,80052032245%2%llm-medium
complex_rag7,8002,50090048360%3%llm-strong
eval3,8001,60035032240%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_requestcost_per_day cho 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 0 và 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:

  • /query simple FAQ.
  • /query normal RAG.
  • /query complex 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_id
  • permission_hash
  • corpus_version
  • prompt_version
  • schema_version
  • embedding_model
  • cache_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_k cho 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 ý:

  1. Semantic cache cho simple FAQ.
  2. Giảm context normal RAG từ 8 chunks xuống 5 chunks sau rerank.
  3. Model routing query rewrite/FAQ sang small model.
  4. Chạy eval bằng Batch API.
  5. 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.md hoặ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.py hoặ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õ.