feat(cag): CAG integration — memory adapter, warmup semaphore, cache-aware routing (#47)
CI / CI (push) Successful in 3m20s
CI / CI (push) Successful in 3m20s
## Summary Three issues in one PR. Implements Cache-Augmented Generation (CAG) support in the api-gw. --- ### #44 — Memory ServiceAdapter: standing-context resource - Code-registers `memory` adapter with `standing-context` resource (`POST /memory/standing-context`) - Fixes dispatcher bug: method-level `Auth.Capability` override now respected (was always using verb-derived capability) - Auth: `memory:read` required via method-level override; `memory:write` for ingest - `MEMORY_UPSTREAM_URL` env configures upstream ### #45 — Continuous batching: warmup deprioritisation + per-backend semaphore - In-memory bounded queue per backend: `VLLM_MAX_QUEUE_DEPTH` slots (default 20) - Concurrency limiter: `VLLM_MAX_NUM_SEQS` slots (default 4) - `X-Poimen-Warmup: true` → sleep `WARMUP_DELAY_MS` (default 500ms) before queue entry, strip header, add `X-Priority: low` - Queue full → 503 immediately, no goroutine pile-up - Metrics: `warmup_requests_delayed_total`, `queue_full_requests_total` at `/metrics` ### #46 — Cache-aware routing: rendezvous hash + prefix cache hit logging - `addresses: [...]` YAML field for multi-backend models (backward-compat with `address`) - `X-Poimen-Cache-Key` header → rendezvous-hash backend selection (same key = same GPU) - No cache key → round-robin - Failed backend excluded 30s; next-highest-score fallback - `vllm-num-cached-tokens` response header captured → `vllm_prefix_cache_hit_tokens_total` counter + structured log --- ## Verification ``` go build ./... ✅ go vet ./... ✅ go test -race ./... ✅ (all packages, 0 races) ``` ## Reviewer verdicts - reviewer-sre: SIGN-OFF - reviewer-quality: SIGN-OFF ## Non-blocking findings (🟡🔵 only) - `getOrCreateTransport` not goroutine-safe by design; pre-population invariant documented - `time.Sleep(warmupDelay)` holds goroutine; context-cancel alternative left as TODO - OTel trace_id not propagated into CAG log events (existing gap) Closes #44 Closes #45 Closes #46 --------- Co-authored-by: Admin Bot <[email protected]> Reviewed-on: #47
This commit was merged in pull request #47.
This commit is contained in:
@@ -36,6 +36,11 @@ type Metrics struct {
|
||||
itlMs map[string][]int64 // samples for histogram
|
||||
// Token counts
|
||||
tokenCount map[string]int64
|
||||
|
||||
// CAG metrics
|
||||
warmupRequestsDelayed int64
|
||||
prefixCacheHitTokens int64
|
||||
queueFullRequests int64
|
||||
}
|
||||
|
||||
// NewMetrics creates a new Metrics instance.
|
||||
@@ -280,4 +285,61 @@ func (m *Metrics) Reset() {
|
||||
m.ttftMs = make(map[string][]int64)
|
||||
m.itlMs = make(map[string][]int64)
|
||||
m.tokenCount = make(map[string]int64)
|
||||
m.warmupRequestsDelayed = 0
|
||||
m.prefixCacheHitTokens = 0
|
||||
m.queueFullRequests = 0
|
||||
}
|
||||
|
||||
// IncrQueueFull increments queue_full_requests_total.
|
||||
func (m *Metrics) IncrQueueFull() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.queueFullRequests++
|
||||
}
|
||||
|
||||
// GetQueueFull returns queue_full_requests_total.
|
||||
func (m *Metrics) GetQueueFull() int64 {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.queueFullRequests
|
||||
}
|
||||
|
||||
// IncrWarmupDelayed increments warmup_requests_delayed_total.
|
||||
func (m *Metrics) IncrWarmupDelayed() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.warmupRequestsDelayed++
|
||||
}
|
||||
|
||||
// GetWarmupDelayed returns warmup_requests_delayed_total.
|
||||
func (m *Metrics) GetWarmupDelayed() int64 {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.warmupRequestsDelayed
|
||||
}
|
||||
|
||||
// IncrPrefixCacheHitTokens increments vllm_prefix_cache_hit_tokens_total by n.
|
||||
func (m *Metrics) IncrPrefixCacheHitTokens(n int64) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.prefixCacheHitTokens += n
|
||||
}
|
||||
|
||||
// GetPrefixCacheHitTokens returns vllm_prefix_cache_hit_tokens_total.
|
||||
func (m *Metrics) GetPrefixCacheHitTokens() int64 {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.prefixCacheHitTokens
|
||||
}
|
||||
|
||||
// globalMetrics is the singleton Metrics instance for gateway-wide telemetry.
|
||||
var (
|
||||
globalMetrics *Metrics
|
||||
globalMetricsOnce sync.Once
|
||||
)
|
||||
|
||||
// Global returns the singleton Metrics instance, initialising on first call.
|
||||
func Global() *Metrics {
|
||||
globalMetricsOnce.Do(func() { globalMetrics = NewMetrics() })
|
||||
return globalMetrics
|
||||
}
|
||||
|
||||
@@ -36,6 +36,18 @@ func (p *PrometheusExporter) Export() string {
|
||||
lines = append(lines, "# TYPE request_duration_seconds histogram")
|
||||
p.exportRequestDuration(&lines)
|
||||
|
||||
lines = append(lines, "# HELP queue_full_requests_total Requests rejected due to backend queue saturation")
|
||||
lines = append(lines, "# TYPE queue_full_requests_total counter")
|
||||
lines = append(lines, fmt.Sprintf("queue_full_requests_total %d", p.metrics.GetQueueFull()))
|
||||
|
||||
lines = append(lines, "# HELP warmup_requests_delayed_total LLM warmup requests delayed for vLLM slot management")
|
||||
lines = append(lines, "# TYPE warmup_requests_delayed_total counter")
|
||||
lines = append(lines, fmt.Sprintf("warmup_requests_delayed_total %d", p.metrics.GetWarmupDelayed()))
|
||||
|
||||
lines = append(lines, "# HELP vllm_prefix_cache_hit_tokens_total vLLM KV prefix cache hit tokens")
|
||||
lines = append(lines, "# TYPE vllm_prefix_cache_hit_tokens_total counter")
|
||||
lines = append(lines, fmt.Sprintf("vllm_prefix_cache_hit_tokens_total %d", p.metrics.GetPrefixCacheHitTokens()))
|
||||
|
||||
return strings.Join(lines, "\n") + "\n"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user