Skip to content

feat(demo): add --kv-cache flag to llm-d pool-metrics demo - #46

Open
jordigilh wants to merge 3 commits into
praxis-proxy:mainfrom
jordigilh:feat/llmd-pool-metrics-kv-cache-flavor
Open

feat(demo): add --kv-cache flag to llm-d pool-metrics demo#46
jordigilh wants to merge 3 commits into
praxis-proxy:mainfrom
jordigilh:feat/llmd-pool-metrics-kv-cache-flavor

Conversation

@jordigilh

Copy link
Copy Markdown
Contributor

Summary

Adds a --kv-cache flag to grid-llmd-pool-metrics-demo that switches the
demo's routing signal from the default queue-depth scoring strategy to
llm-d's kv-cache-utilization signal, by setting
GridNetwork.spec.scoringPolicy.strategy: kvCachePressure on both sites
instead of the template's default queueDepth. This lets the demo show
either of Grid's two real, already-implemented scoring strategies without
touching the operator itself — ScoringStrategy::KvCachePressure and its
weights are pre-existing, unit-tested operator behavior
(operator/src/crd/grid_network.rs); this PR only wires the demo's config
templating and CLI surface to select it.

Both signals (queue_depth and kv_cache) are always shown in the live
scorecard regardless of the flag; only the strategy that actually produces
the score/rank driving the A→B failover changes.

Design

  • ScoringFlavor enum (QueueDepth default / KvCachePressure) selected
    from the CLI flag, mapping to the scoringPolicy.strategy YAML value and
    to a pressure_phase_active predicate that keys the announced "pressure"
    phase off whichever signal is actually driving the active strategy
    (queue size vs. KV utilization), rather than always narrating queue
    pressure.
  • materialize_config swaps the strategy: queueDepth anchor to
    strategy: kvCachePressure on both pool-a-site and pool-b-site when
    the flavor is selected — same checked_replace-based templating pattern
    already used for the mTLS transport transformations in this function.
  • DemoContext.scoring_flavor threads the selection through setup and the
    live proof-scenario loop; evidence JSON records it as
    scoring_strategy: "kv-cache-pressure" / "queue-depth".

Testing

  • 8 new unit tests: flavor→strategy-yaml mapping, the pressure-trigger
    predicate for both flavors, and both materialize_config outcomes
    (queue-depth leaves the template's default untouched; kv-cache swaps both
    sites, asserted against the same fixture the module's other
    config-templating tests already use).
  • Manually confirmed the exact templating target
    (strategy: queueDepth, occurring exactly twice) matches the real
    tests/e2e/topologies/grid-llmd-pool-metrics/forge.yaml byte-for-byte,
    not just the synthetic test fixture.
  • cargo test -p xtask: 466/466 passing. cargo clippy -p xtask --all-targets: clean. rustfmt: clean.
  • Live-validated on a real 2-cluster Kind deployment (--kv-cache --teardown, full mode): environment converged with the kvCachePressure
    strategy applied on both sites, and the pressure/flip proof scenario
    showed the A→B rank flip driven specifically by the KV-cache signal — the
    queue/capacity pressure ratio stayed pegged at 1.00 for both pools
    throughout, while kv_cache rose 0.00 → 0.02 on pool-a and score
    dropped 1.00 → 0.98, flipping rank to pool-b. Recovery drained cleanly
    back to pool-a. All 4 proof scenarios (provenance, baseline,
    pressure_and_flip, recovery) passed; evidence JSON confirms
    "scoring_strategy": "kv-cache-pressure", "success": true.

Docs

Documented the new --kv-cache flag (and the existing --metrics-mtls
flag, previously undocumented) in the topology's README.

Lets the grid-llmd-pool-metrics-demo drive routing off llm-d's
kv-cache-utilization signal instead of the default queue-depth signal,
by swapping GridNetwork.spec.scoringPolicy.strategy from queueDepth to
kvCachePressure on both sites. Both signals are always shown in the
live scorecard; the flag only changes which one drives the A->B flip.

- ScoringFlavor enum selects the flavor from the flag and maps it to
  the strategy YAML value and a pressure-trigger predicate
  (pressure_phase_active), keyed off the same signal the active
  scoring strategy actually uses.
- materialize_config swaps the strategy anchor on both pool-a-site and
  pool-b-site when kvCachePressure is selected.
- Adds unit tests for the flavor mapping, the pressure predicate, and
  both materialize_config outcomes (asserted against the same fixture
  used by the demo's other config-templating tests).

Signed-off-by: Jordi Gil <jgil@redhat.com>
Closes the one remaining unit-testable gap identified while auditing this
feature's test pyramid: the clap flag itself was previously untested (true
for every flag on this Action variant, not just this one). Parses the real
Cli/Action tree via Cli::try_parse_from and asserts kv_cache defaults to
false and parses to true when passed, so a future clap attribute typo can't
silently disconnect the flag from ScoringFlavor::from_kv_cache_flag.

With this, every pure/decision-making line this feature added (ScoringFlavor,
pressure_phase_active, materialize_config's strategy swap, and now CLI
parsing) has 100% unit line coverage (measured with cargo-llvm-cov); only the
pre-existing, already-untested imperative-shell dispatch (run(), the env::run
match arm) remains uncovered by unit tests, consistent with the rest of this
file and validated instead by the live E2E run already recorded in the PR
description.

Signed-off-by: Jordi Gil <jgil@redhat.com>
@jordigilh

Copy link
Copy Markdown
Contributor Author

Ran a follow-up test-pyramid audit on this PR (per project convention: pyramid invariant, behavioral assurance against acceptance criteria, 100% unit coverage of testable logic):

  • Unit coverage (measured with cargo-llvm-cov): every pure/decision-making line this feature adds — ScoringFlavor (enum + its 3 methods), pressure_phase_active, and materialize_config's new strategy-swap branch — is at 100% line coverage. Added one more unit test module (llmd_pool_metrics_demo_cli_tests, latest commit) to close the one remaining testable gap: the --kv-cache clap flag itself was previously untested (true of every flag on this command, not unique to this PR) — now asserts it defaults to false and parses to true via Cli::try_parse_from against the real Cli/Action tree.
  • Pyramid shape: 9 fast pure-function unit tests (flavor mapping, pressure predicate, CLI parsing) at the base, 2 file-based config-templating tests in the middle (asserting real YAML output against the actual fixture), and the single live full-mode E2E run at the top (already described above) — broad base, narrow top, no inversion.
  • Behavioral assurance: the pressure-predicate tests assert the actual acceptance criterion ("the announced pressure phase must key off whichever signal drives the active strategy, not always queue") rather than just structural coverage; the strategy_yaml test is a standing contract check against the operator's CRD serde rename so the two can't silently drift apart.
  • The only intentionally-uncovered-by-unit-tests lines are the pre-existing imperative-shell dispatch (run(), prepare_setup(), the top-level env::run match arm) — consistent with the rest of this file's shape, and exercised instead by the live E2E run rather than mocked out.

@jordigilh

Copy link
Copy Markdown
Contributor Author

Ran an independent security review of this diff (the --kv-cache flag and its config-templating path). Summary: no medium/high/critical issues — the flag is a boolean mapped through a 2-variant enum to fixed &'static str literals before being spliced into the Forge YAML via the existing checked_replace helper (fails closed on a match-count mismatch), so there's no path for attacker-controlled or free-form text to reach generated config through this flag. No new deserialization, credential handling, or logging of sensitive data on the changed path.

…ic fallback

Bugbot review of this PR found two related defects in the kvCachePressure
path added earlier in this branch:

1. proof_recovery's verification-probe gate was hardcoded to
   `epp_a.queue_size < 3.0` regardless of scoring flavor. Under
   kvCachePressure, rank is driven by KV utilization, not queue depth, so
   pool A could regain rank 0 while queue stayed elevated (or stall below
   the gate while rank was already 0) -- flaky/false recovery-proof
   failures in full-mode --kv-cache runs.

2. scrape_epp_metrics read kv_cache from only
   `inference_pool_average_kv_cache_utilization`, with no fallback to
   `llm_d_router_epp_average_kv_cache_utilization` -- unlike queue_size,
   which already falls back symmetrically. An EPP build exposing only the
   llm_d_router_* series would read kv_cache as a permanent 0.0, so the
   pressure phase would never announce despite real KV pressure driving
   the flip.

Fixes, via TDD (RED: 5 new tests against not-yet-existing functions ->
GREEN: implementation -> REFACTOR: clippy/fmt clean, no duplicated magic
numbers):

- Extracted scrape_epp_metrics's Prometheus-text parsing into a pure
  parse_epp_metrics(text: &str) -> EppMetrics function (functional
  core/imperative shell), and added the missing kv_cache fallback,
  symmetric with the existing queue_size fallback.
- Added recovery_condition_met(flavor, epp), mirroring
  pressure_phase_active's flavor dispatch: QueueDepth keeps the original
  threshold (now a named RECOVERY_QUEUE_THRESHOLD constant instead of an
  inline literal, no behavior change); KvCachePressure reuses the inverse
  of pressure_phase_active rather than inventing a second, uncalibrated KV
  constant.
- Wired recovery_condition_met into proof_recovery in place of the
  hardcoded queue check.

100% line coverage of both new functions confirmed with cargo-llvm-cov
(all branches of both match statements hit). Full xtask suite:
473 passed, 0 failed (was 468 before this fix). make lint: clippy -D
warnings, fmt --check, cargo machete all clean.

Signed-off-by: Jordi Gil <jgil@redhat.com>
@jordigilh

Copy link
Copy Markdown
Contributor Author

Automated review caught two real defects in the kvCachePressure path — pushed a fix:

  1. Recovery proof ignored scoring flavorproof_recovery's verification-probe gate was hardcoded to queue_size < 3.0 regardless of flavor. Under kvCachePressure, rank is driven by KV utilization, not queue depth, so this could produce flaky/false recovery-proof results in full-mode --kv-cache runs.
  2. KV scrape missing metric fallbackscrape_epp_metrics read kv_cache from only inference_pool_average_kv_cache_utilization, unlike queue_size, which already falls back to the llm_d_router_epp_* series. An EPP exposing only that series would read kv_cache as a permanent 0.0.

Fixed via the same TDD discipline as the rest of this PR: 5 new RED tests against not-yet-existing parse_epp_metrics/recovery_condition_met functions → GREEN implementation → REFACTOR (extracted the recovery threshold into a named constant instead of leaving 3.0 as a magic literal). recovery_condition_met mirrors the existing pressure_phase_active flavor dispatch: QueueDepth keeps its original, already-live-validated threshold unchanged; KvCachePressure reuses the inverse of pressure_phase_active rather than inventing a second, uncalibrated KV constant.

100% line coverage of both new functions confirmed with cargo-llvm-cov (all branches of both matches hit). Full suite: 473 passed / 0 failed (was 468). make lint clean (clippy -D warnings, fmt, machete).

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed all three changed files. Clean PR -- no Critical, Large, or Medium issues found.

What looks good:

  • ScoringFlavor enum is well-designed: from_kv_cache_flag / label / strategy_yaml cleanly separate CLI surface, display, and YAML-template concerns.
  • pressure_phase_active and recovery_condition_met correctly dispatch on flavor, and the lack of a hysteresis gap for KvCachePressure recovery is explicitly documented as intentional ("reuses the inverse of pressure_phase_active rather than inventing an untested second KV constant").
  • parse_epp_metrics extraction is a smart refactor -- separating the functional core from the I/O-bound scrape_epp_metrics makes the metric-name-fallback behavior unit-testable.
  • materialize_config strategy swap uses checked_replace with count=2, which will fail loudly if the template ever changes its number of scoringPolicy.strategy occurrences.
  • 10 new tests cover construction, labels, strategy YAML values, pressure detection for both flavors (including cross-signal independence assertions), metric parsing (primary, fallback, absent), recovery conditions with boundary values, and both materialize_config outcomes. Thorough.
  • Existing tests updated to pass the new scoring_flavor parameter without losing coverage.
  • README documents both --kv-cache and the previously-undocumented --metrics-mtls flag.

No changes requested.

@nerdalert nerdalert left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jordigilh LGTM, rebase at your leisure. Ty!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants