Field Log · Entry

Reranker Serving: Batching·Quantization·Adaptive Cascade (11/14)

짧고 긴 query-document pair를 token bucket으로 batch하고 작은 reranker에서 불확실한 후보만 큰 reranker로 보내는 production cascade

이번 글의 결론

  • Reranker 용량은 request/s가 아니라 queries × candidates × pair tokens에서 시작합니다. Candidate depth와 length tail이 p95를 만듭니다.
  • Dynamic batching은 throughput을 높이지만 queue time을 추가합니다. Pair count보다 total token budget과 max wait를 함께 제한해야 합니다.
  • Quantization·ONNX·serving engine 변경은 단순한 인프라 교체가 아닙니다. Score·rank parity와 critical slice를 다시 확인합니다.
  • 모든 query를 큰 model에 보내기보다 작은 model의 uncertainty·query difficulty로 일부만 escalate하는 cascade가 품질–비용 Pareto를 개선할 수 있습니다.
  • Timeout은 예외가 아니라 정상 운영 경로입니다. Original retrieval order fallback, circuit breaker, trace와 rollback을 미리 설계합니다.

앞 글에서 in-domain 품질과 license로 model을 골랐습니다. 이제 notebook의 model.predict()를 traffic을 받는 service로 바꿉니다.

request
  → candidate shaping
  → tokenize
  → queue
  → dynamic batch
  → model compute
  → stable sort
  → top-k
  → fallback / trace

Reranker latency는 model forward 하나가 아니라 이 경로의 합입니다.


1. Workload를 Pair와 Token으로 바꾼다

초당 query 수를 Q, query당 candidate 수를 N이라 하면 필요한 pair throughput은:

pairs_per_second = Q × N

예:

Q = 40 queries/s
N = 50 candidates/query

required = 2,000 pairs/s

하지만 2,000개의 80-token pair와 500-token pair는 다릅니다.

token_work_per_second
  ≈ Q × Σ_i tokens(query + candidate_i)

Self-attention model은 token 수와 latency가 완전히 선형도 아닙니다. Capacity는 실제 length distribution에서 측정합니다.

최소 Workload Histogram

traffic:
  qps:
    average: 25
    peak: 65
  candidates:
    p50: 30
    p95: 80
  query_tokens:
    p50: 22
    p95: 110
  pair_tokens:
    p50: 164
    p95: 488
    p99: 512
  long_document_window_count:
    p50: 1
    p95: 4

Windowing을 쓰면 effective pair 수가 늘어납니다.

50 documents
× average 1.8 windows/document
= 90 model pairs

2. SLO를 단계별 Budget으로 나눈다

End-to-end search budget이 400ms라고 해도 reranker가 전부 쓸 수 없습니다.

query rewrite       40 ms
first-stage search  65 ms
rerank              90 ms
context packing     15 ms
generation TTFT    150 ms
network·headroom    40 ms
--------------------------
total              400 ms

Rerank 90ms도 분해합니다.

serialize / fetch   8 ms
tokenize           12 ms
queue              10 ms
GPU compute        50 ms
sort / trace        3 ms
headroom            7 ms

GPU compute가 빨라졌는데 queue가 늘면 사용자는 개선을 못 느낍니다.

3. Batch Size와 Batch Token은 다르다

Fixed pair batch 32의 문제:

31 × 80-token pairs
 1 × 512-token pair
→ padding to 512
→ 대부분 padding work

Length bucket을 둡니다.

bucket S:  <=128 tokens
bucket M: 129..256
bucket L: 257..384
bucket XL:385..512

Batch scheduler의 조건 예시:

batch_policy:
  max_pairs: 64
  max_total_tokens: 8192
  max_sequence_tokens: 512
  max_wait_ms: 4
  buckets: [128, 256, 384, 512]

Total token은 padding 후 shape 기준으로 제한하는 편이 안전합니다.

padded_batch_tokens = batch_size × max_sequence_length_in_batch

4. Dynamic Batching의 Queue Trade-off

Request를 더 모으면 GPU utilization은 오르지만 첫 request가 기다립니다.

small max_wait
  → low queue latency, small batches, lower throughput

large max_wait
  → high throughput, tail queue latency risk

Traffic이 낮은 시간과 peak에 최적 max wait가 다를 수 있습니다. Adaptive batcher를 쓰더라도 upper bound를 둡니다.

평가 grid:

max_wait_ms ∈ {0, 1, 2, 4, 8}
max_batch_tokens ∈ {4k, 8k, 16k}
concurrency ∈ {1, 8, 32, 64}

각 조합에서 goodput, 즉 SLO 안에 완료한 query/s를 봅니다.

5. Query 단위 Fairness

한 query가 long document 200개를 보내면 다른 query가 막힐 수 있습니다.

대응:

  • request당 max candidates
  • request당 max total tokens
  • tenant quota
  • priority queue
  • long-request 별도 lane
  • deadline-aware scheduling

Batch는 pair를 섞더라도 결과를 query별로 정확히 모아야 합니다. Partial batch failure가 한 query의 candidate 일부만 score한 상태를 만들 수 있습니다.

atomic policy:
  all candidate scores valid
  or whole query falls back

Partial score와 retrieval score를 섞는 policy는 별도 validation 없이는 피합니다.

6. CPU·GPU 선택

작은 encoder Cross-Encoder는 CPU에서도 충분한 traffic이 있을 수 있습니다. GPU가 항상 싸거나 빠른 end-to-end 선택은 아닙니다.

CPU가 유리할 수 있는 상황

  • 낮고 불규칙한 QPS
  • 아주 작은 model·candidate depth
  • GPU cold start가 큼
  • existing CPU fleet에 spare capacity가 있음
  • data locality가 중요함

GPU가 유리할 수 있는 상황

  • 높은 지속 QPS와 batch opportunity
  • 긴 sequence·큰 model
  • BF16/FP16·optimized attention 활용
  • 여러 tenant를 모아 utilization 확보

Cost는 device-hour가 아니라 SLO를 만족한 1,000 query당 비용으로 비교합니다.

7. Local Serving Option

Sentence Transformers Direct

작은 service와 실험에 단순합니다. 직접 batching·metrics·health check를 구현해야 합니다.

Hugging Face Text Embeddings Inference, TEI

TEI는 sequence-classification Cross-Encoder reranker를 /rerank endpoint로 serving하고 token-based dynamic batching, tracing·Prometheus 같은 기능을 제공합니다.

개념 예시:

docker run --gpus all -p 8080:80 \
  --pull always \
  ghcr.io/huggingface/text-embeddings-inference:<PINNED_VERSION> \
  --model-id BAAI/bge-reranker-v2-m3

curl http://127.0.0.1:8080/rerank \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "Orion S2 shutdown temperature",
    "texts": ["candidate one", "candidate two"]
  }'

실제 image tag와 model support는 도입 시 공식 repository에서 확인합니다.

vLLM Scoring

vLLM 공식 scoring 문서는 cross-encoder, late-interaction, bi-encoder score type과 rerank-compatible endpoint를 설명합니다. Decoder-only·pooling model을 기존 LLM serving stack에 합칠 때 후보입니다.

Model architecture별 지원과 pooling config가 다르므로 “vLLM에서 load된다”와 “공식 scoring output과 parity가 맞는다”를 구분합니다.

8. Runtime Parity Test

Reference implementation과 serving engine 결과를 비교합니다.

def rank_order(scores: list[float]) -> list[int]:
    return sorted(range(len(scores)), key=lambda i: (-scores[i], i))


def assert_runtime_parity(reference, served, *, atol=1e-3):
    if len(reference) != len(served):
        raise AssertionError("score count mismatch")

    max_error = max(abs(a - b) for a, b in zip(reference, served))
    if max_error > atol:
        raise AssertionError(f"score drift: {max_error}")

    if rank_order(reference) != rank_order(served):
        raise AssertionError("rank order changed")

전체 score tolerance뿐 아니라 top-k overlap, nDCG delta, critical contrast pair의 direction을 봅니다.

9. Quantization은 Rank Flip을 만든다

Weight·activation precision을 줄이면 memory와 throughput이 좋아질 수 있습니다.

FP32 → BF16/FP16 → INT8 → lower-bit weight-only

Score margin이 작은 pair는 작은 numerical 변화로 순서가 바뀝니다.

FP16: d₁=2.401, d₂=2.399
INT8: d₁=2.393, d₂=2.397

검증:

  • score correlation
  • top-k overlap
  • Kendall’s τ
  • rank flip by score margin
  • nDCG·MRR delta
  • 한국어·숫자·long document critical slice
  • actual latency·memory gain

Quantized reasoning LLM은 generation trace까지 바뀔 수 있어 seed 하나만 비교하지 않습니다.

10. Candidate Depth를 줄이는 것이 가장 큰 Optimization일 수 있다

Model을 20% 빠르게 만드는 것보다 candidate 100개를 30개로 줄이면 pair work가 크게 줄 수 있습니다.

하지만 candidate recall ceiling을 함께 낮출 수 있습니다.

Depth sweep:

N = 10, 20, 30, 50, 100
→ candidate recall
→ reranked nDCG@k
→ RAG answer support
→ p95 latency

Query type별 최적 depth가 다를 수 있습니다.

  • Exact ID query: small depth로 충분
  • Ambiguous short query: wider candidates
  • Multi-hop query: wider evidence coverage
  • No-answer query: 넓힌다고 정답이 생기지 않음

11. Adaptive Candidate Depth

First-stage signal로 depth를 조절합니다.

clear exact match + large score gap
→ rerank top-20

ambiguous query + flat scores + multiple channels disagree
→ rerank top-100

가능한 feature:

  • BM25 top1-top2 gap
  • dense score entropy
  • BM25·dense top-k overlap
  • query length·intent
  • exact identifier detection
  • historical query difficulty
  • candidate source diversity

Router도 model이므로 offline regret와 online guardrail을 평가합니다. 잘못 작은 depth를 고르면 reranker가 복구할 수 없는 recall loss가 생깁니다.

12. Small→Large Cascade

모든 candidate를 큰 model로 읽지 않습니다.

hybrid top-100
→ small reranker top-20
→ large reranker top-5

단순 cascade는 candidate를 줄이는 대신 small model의 오류를 permanent하게 만듭니다. Small-stage Recall@20을 확인합니다.

Query-level Escalation

Small reranker가 불확실한 query만 large model로 보냅니다.

small score margin large
  → accept small result

small score distribution flat
  → large reranker

Uncertainty feature:

margin = score_rank1 - score_rank2
entropy of normalized top scores
agreement with first-stage ranks
order stability under small perturbation
calibrated probability of ranking success

Raw margin scale는 query·model에 따라 달라 calibration이 필요합니다.

Candidate-level Escalation

Top 후보의 score가 가까운 cluster만 large model로 비교합니다.

small ranks 1..8 have close scores
small ranks 9..50 clearly lower
→ large rerank only 1..8

Cut boundary의 false exclusion을 evaluation합니다.

13. CoRanking의 시사점

2025년 CoRanking은 efficient small reranker가 후보를 압축하고 stronger large reranker가 작은 top-k를 처리하는 collaborative listwise framework를 제안했습니다. 논문은 평가 설정에서 standalone large reranker보다 latency를 약 70% 줄이면서 effectiveness도 개선했다고 보고했습니다.

이 수치를 그대로 capacity plan에 쓰면 안 됩니다. 중요한 설계 원리는 다음입니다.

large model budget을 모든 후보에 균등하게 쓰지 않는다
→ small model로 search space를 줄인다
→ large model을 high-value comparison에 집중한다

14. Cache 전략

Exact Pair Score Cache

key = query_norm + document_hash + model_revision + template_version

Repeated FAQ query에는 유용합니다. Long-tail에는 hit rate가 낮습니다.

Tokenization Cache

Document side token IDs를 cache할 수 있지만 pair tokenizer가 query와 joint truncation을 하면 단순 결합이 불가능할 수 있습니다. Model tokenizer contract를 확인합니다.

LLM Prefix Cache

System instruction과 query prefix를 여러 candidate call이 공유하면 prefix KV cache가 도움이 될 수 있습니다. Batch scheduler와 model server가 실제로 reuse하는지 측정합니다.

결과 Cache Invalidation

  • document content 변경
  • model·quantization 변경
  • prompt·serializer 변경
  • tokenizer revision 변경
  • relevance instruction 변경

TTL만으로 correctness를 보장하지 않습니다.

15. Timeout과 Fallback

Reranker가 실패해도 first-stage 결과는 있습니다.

success
  → reranked order

timeout / overload / invalid output
  → original fused retrieval order
  → mark fallback=true

Fallback Policy

  • Original candidate IDs를 그대로 유지합니다.
  • 일부 reranker score와 일부 retrieval score를 섞지 않습니다.
  • Timeout deadline을 upstream request deadline보다 짧게 둡니다.
  • Retry는 남은 budget과 idempotency를 확인합니다.
  • Circuit breaker가 open이면 즉시 fallback합니다.

API retry storm은 provider 장애를 악화시킬 수 있습니다. Exponential backoff보다 사용자 deadline이 우선입니다.

16. Load Shedding

Overload 시 품질을 단계적으로 낮춥니다.

normal:
  top-100 → small → top-20 → large

degraded level 1:
  top-50 → small only

degraded level 2:
  top-20 → small only

degraded level 3:
  skip reranker → retrieval order

각 level의 offline quality를 미리 측정합니다. 장애 중 처음 보는 configuration을 즉석에서 만들지 않습니다.

17. Observability

Query trace 예시:

{
  "request_id": "req-91",
  "query_hash": "...",
  "candidate_count": 50,
  "effective_pair_count": 73,
  "input_tokens": 18420,
  "truncated_pairs": 4,
  "small_model": "reranker-small-v7-int8",
  "escalated": true,
  "large_model": "reranker-large-v3-bf16",
  "latency_ms": {
    "tokenize": 7,
    "queue": 5,
    "compute": 41,
    "total": 58
  },
  "fallback": false,
  "rank_changes_at_5": 3
}

Dashboard:

  • QPS·pairs/s·tokens/s
  • batch size·padded token utilization
  • queue·compute·total percentiles
  • truncation·window rate
  • escalation·fallback·timeout rate
  • model score distribution·margin drift
  • rank movement distribution
  • GPU memory·utilization·error

Document text와 raw query를 log하면 privacy risk가 있습니다. Hash·sample·redaction 정책을 둡니다.

18. 간단한 Capacity 계산

Load test에서 target token distribution과 concurrency에서 한 replica가 SLO 내 2,500 pairs/s를 처리했다고 합시다.

안전 utilization을 70%로 두면:

safe capacity ≈ 2,500 × 0.70
              ≈ 1,750 pairs/s per replica

Peak 65 query/s, 평균 effective pair 55라면:

required ≈ 65 × 55
         ≈ 3,575 pairs/s

replicas ≈ ceil(3,575 / 1,750)
         = 3

세 replica는 단순 평균 계산입니다. Failure tolerance, rolling deploy, uneven length, burst를 위해 추가 headroom이 필요합니다.

19. Release와 Rollback

reranker_release:
  release_id: rerank-serving-2026-07-20-03
  model:
    checkpoint: reranker-small-v7
    revision: sha256:...
    quantization: int8
  runtime:
    engine: tei
    version: pinned
  input:
    serializer: document-v5
    max_length: 512
  policy:
    candidate_depth: adaptive-depth-v2
    cascade: small-large-v3
    fallback: original-rrf-order
  calibration: reranker-small-v7-cal-v2
  evaluation: eval-report-884

Rollback은 model weights뿐 아니라 runtime, quantization, serializer, router, calibration을 함께 되돌릴 수 있어야 합니다.

20. Serving 체크리스트

  • QPS를 effective pairs/s와 token distribution으로 변환했다.
  • Queue·tokenize·compute·network latency를 분리한다.
  • Dynamic batch에 max total tokens와 max wait가 있다.
  • Long request와 tenant가 다른 traffic을 독점하지 않는다.
  • Reference runtime과 serving engine의 rank parity를 확인했다.
  • Quantization rank flip과 critical slice를 측정했다.
  • Candidate depth·cascade 단계마다 recall ceiling을 기록했다.
  • Timeout은 original retrieval order로 atomic fallback한다.
  • Load-shedding level별 품질을 미리 평가했다.
  • Model·runtime·policy·calibration을 하나의 release로 versioning한다.

스스로 확인하기

  1. Reranker capacity를 query/s만으로 계산하면 어떤 정보가 빠지는가?
  2. Length bucketing이 padding waste를 줄이는 원리는 무엇인가?
  3. Quantization score error가 작아도 ranking metric이 바뀔 수 있는 이유는 무엇인가?
  4. Small→large cascade에서 small stage의 어떤 metric이 상한을 정하는가?
  5. Reranker timeout 때 partial rerank보다 original retrieval order fallback이 안전한 이유는 무엇인가?

다음 글에서는 검색 relevance와 최종 답변 utility를 구분하고, multi-hop context selection·중복·prompt injection·visual document까지 RAG 경계에서 다룹니다.

참고자료