Skip to content

feat(libsy): add prefill-probe classifier - #174

Open
anniesurla wants to merge 1 commit into
NVIDIA-NeMo:mainfrom
anniesurla:asurla/feat-prefill-probe-libsy
Open

feat(libsy): add prefill-probe classifier#174
anniesurla wants to merge 1 commit into
NVIDIA-NeMo:mainfrom
anniesurla:asurla/feat-prefill-probe-libsy

Conversation

@anniesurla

@anniesurla anniesurla commented Jul 28, 2026

Copy link
Copy Markdown

Summary

  • implement learned prefill-probe routing directly in libsy as a Classifier
  • load and validate the v1 safetensors checkpoint, then run scaler, PCA-200, ensemble, and cost-aware inference
  • define a transport-independent PrefillProbe boundary for hidden-state extraction
  • bound successful task decisions in a process-randomized hash-keyed LRU cache
  • route probe and inference failures to strong without caching the failure

Context

This is the first of three smaller PRs replacing #140 in response to maintainer review:

  1. libsy classifier and checkpoint inference (this PR)
  2. vLLM hidden-state transport
  3. server configuration and deployment documentation

This PR intentionally contains no HTTP client, filesystem hidden-state transport, Python binding, or server configuration.

Implementation notes

  • CPU inference runs through tokio::task::spawn_blocking.
  • Raw task text is neither cached nor logged.
  • Artifact metadata, tensor names, dtypes, shapes, and finite values are validated at construction.
  • Routing uses an explicit weak/strong tier rather than a numeric decision threshold.

Test plan

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p switchyard-libsy
  • cargo test --workspace

The live VPN-dependent test remains ignored by the workspace suite.

Summary by CodeRabbit

  • New Features
    • Added intelligent prefill routing that selects between strong and weak completion tiers based on prompt features and configurable cost trade-offs.
    • Added support for loading and validating routing artifacts, including checkpoint metadata and tensor data.
    • Added caching for routing decisions to improve repeated-task performance.
    • Added safe fallback to the strong completion tier when probing or inference fails.
    • Exposed configuration options for probe models, routing targets, costs, and cache capacity.

@anniesurla
anniesurla requested a review from a team as a code owner July 28, 2026 23:00
Signed-off-by: asurla1998 <asurla1998@users.noreply.huggingface.co>
@anniesurla
anniesurla force-pushed the asurla/feat-prefill-probe-libsy branch from e1e415f to caed4cd Compare July 28, 2026 23:03
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a transport-independent PrefillProbeClassifier that extracts prompt features, runs validated safetensors-based inference, applies cost-aware weak/strong routing, caches decisions, and falls back to strong completion on failures.

Changes

Prefill Probe Routing

Layer / File(s) Summary
Validated artifact inference
crates/libsy/src/algorithms/prefill_probe/artifact.rs
Loads and validates router metadata and tensors, then performs standardization, PCA projection, ensemble inference, and sigmoid probability calculation with comprehensive tests.
Cost-aware tier policy
crates/libsy/src/algorithms/prefill_probe/policy.rs
Adds validated weak/strong tier selection using normalized costs, lambda weighting, deterministic tie handling, and unit tests.
Feature extraction and cached routing
crates/libsy/src/algorithms/prefill_probe.rs
Adds probe feature contracts, configuration validation, task parsing, LRU caching, artifact inference, strong fallback behavior, classifier integration, and async tests.
Public module and dependency wiring
crates/libsy/Cargo.toml, crates/libsy/src/algorithms.rs, crates/libsy/src/lib.rs
Adds lru and safetensors, exports the prefill-probe API, and documents PrefillProbeClassifier.

Estimated code review effort: 4 (Complex) | ~60 minutes

Poem

I’m a rabbit routing prompts through the hay,
Weak or strong, I choose the right way.
PCA hops and tensors align,
Cached little choices save time,
When probes stumble, strong steps in—
A carrot-powered fallback win!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a prefill-probe classifier to libsy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (5)
crates/libsy/src/algorithms/prefill_probe/artifact.rs (1)

588-604: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider accumulating dense dot products in f64 for consistency with project_pca.

project_pca (Line 547) widens to f64 before summing, while dense_layer sums 200-term products in f32. Not a correctness bug at these widths, but the mixed precision strategy is inconsistent and slightly more prone to rounding drift versus the training-time (float32/float64 numpy) reference.

♻️ Optional: widen accumulation
         .map(|(row, bias)| {
             let value = row
                 .iter()
                 .zip(input)
-                .map(|(weight, input)| *weight * *input)
-                .sum::<f32>()
-                + *bias;
+                .map(|(weight, input)| f64::from(*weight) * f64::from(*input))
+                .sum::<f64>() as f32
+                + *bias;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libsy/src/algorithms/prefill_probe/artifact.rs` around lines 588 -
604, Update the dense_layer dot-product accumulation to widen each weight/input
product to f64 and sum in f64, matching project_pca’s accumulation strategy;
apply the bias consistently before converting the result back to the expected
f32 output and retain the existing non-finite and ReLU handling.
crates/libsy/src/algorithms/prefill_probe.rs (3)

210-225: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

No in-flight deduplication: concurrent identical tasks each trigger a full probe extraction.

The cache is only consulted before and populated after the await, so a burst of requests for the same task all miss and each performs the probe round-trip plus inference. Once PrefillProbe is backed by an HTTP client (later in the stack), this amplifies load on the probe endpoint proportional to burst concurrency. A single-flight map keyed by cache_key (e.g. per-key broadcast/notify) would collapse duplicates.

Worth deciding whether the added complexity is justified at your expected traffic shape — flagging so it is a conscious choice rather than an oversight.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libsy/src/algorithms/prefill_probe.rs` around lines 210 - 225, Update
PrefillProbe::select_for_task to add per-cache_key in-flight deduplication, so
concurrent identical tasks await one shared probe extraction and inference
operation instead of starting duplicates. Reuse the existing decision_cache key
and ensure the in-flight entry is removed after completion while preserving the
current cache lookup and result behavior.

236-245: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Sustained probe outage will emit one warn per request.

When the probe endpoint or checkpoint inference is persistently unavailable, this logs at warn on every single classification, flooding logs at request rate with no added diagnostic value. Consider emitting a counter/metric for the fallback and rate-limiting the warning (or logging the first failure at warn and subsequent ones at debug until recovery).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libsy/src/algorithms/prefill_probe.rs` around lines 236 - 245, Adjust
the error branch around the prefill probe fallback to avoid emitting a warn log
for every request during sustained outages. Keep recording each fallback with a
counter/metric, and rate-limit the warning or emit it at warn only for the first
failure, lowering subsequent failures to debug until probe recovery; preserve
the existing strong_target fallback behavior.

256-265: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Debug acquires the cache mutex; keep it out of any lock-holding path.

parking_lot::Mutex is not reentrant, so formatting a PrefillProbeClassifier while the decision_cache lock is held self-deadlocks. No current call site does this, but a future tracing field like classifier = ?self inside select_for_task's locked scopes would deadlock rather than fail loudly. try_lock with a placeholder would make Debug unconditionally safe.

🛡️ Optional defensive change
     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
         formatter
             .debug_struct("PrefillProbeClassifier")
             .field("strong_target", &self.strong_target)
             .field("weak_target", &self.weak_target)
-            .field("cache_capacity", &self.decision_cache.lock().cap())
+            .field(
+                "cache_capacity",
+                &self.decision_cache.try_lock().map(|cache| cache.cap()),
+            )
             .finish_non_exhaustive()
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libsy/src/algorithms/prefill_probe.rs` around lines 256 - 265, Update
PrefillProbeClassifier’s fmt implementation to avoid blocking on decision_cache:
use try_lock and emit a placeholder when the mutex is already held, while
preserving the capacity output when the lock is available. Keep the existing
strong_target and weak_target debug fields unchanged.
crates/libsy/src/algorithms/prefill_probe/policy.rs (1)

106-150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add coverage for an intermediate lambda where both terms matter.

Tests exercise only the degenerate endpoints (lambda 0.0 and 1.0), where one term of the utility is zeroed out. A case like lambda = 0.5 with unequal costs would pin the actual tradeoff arithmetic and catch a future sign or weight inversion in select.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/libsy/src/algorithms/prefill_probe/policy.rs` around lines 106 - 150,
Add a test alongside lambda_zero_uses_cost_and_weak_wins_equal_utility and
lambda_one_uses_correctness_probabilities covering
CostAwareRoutingPolicy::select with lambda = 0.5 and unequal weak/strong costs.
Use probabilities and costs where the weighted correctness-cost tradeoff
produces distinct expected PrefillTier choices, validating both routing
directions and the intermediate utility arithmetic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/libsy/src/algorithms/prefill_probe.rs`:
- Around line 210-225: Update PrefillProbe::select_for_task to add per-cache_key
in-flight deduplication, so concurrent identical tasks await one shared probe
extraction and inference operation instead of starting duplicates. Reuse the
existing decision_cache key and ensure the in-flight entry is removed after
completion while preserving the current cache lookup and result behavior.
- Around line 236-245: Adjust the error branch around the prefill probe fallback
to avoid emitting a warn log for every request during sustained outages. Keep
recording each fallback with a counter/metric, and rate-limit the warning or
emit it at warn only for the first failure, lowering subsequent failures to
debug until probe recovery; preserve the existing strong_target fallback
behavior.
- Around line 256-265: Update PrefillProbeClassifier’s fmt implementation to
avoid blocking on decision_cache: use try_lock and emit a placeholder when the
mutex is already held, while preserving the capacity output when the lock is
available. Keep the existing strong_target and weak_target debug fields
unchanged.

In `@crates/libsy/src/algorithms/prefill_probe/artifact.rs`:
- Around line 588-604: Update the dense_layer dot-product accumulation to widen
each weight/input product to f64 and sum in f64, matching project_pca’s
accumulation strategy; apply the bias consistently before converting the result
back to the expected f32 output and retain the existing non-finite and ReLU
handling.

In `@crates/libsy/src/algorithms/prefill_probe/policy.rs`:
- Around line 106-150: Add a test alongside
lambda_zero_uses_cost_and_weak_wins_equal_utility and
lambda_one_uses_correctness_probabilities covering
CostAwareRoutingPolicy::select with lambda = 0.5 and unequal weak/strong costs.
Use probabilities and costs where the weighted correctness-cost tradeoff
produces distinct expected PrefillTier choices, validating both routing
directions and the intermediate utility arithmetic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d56e1e4e-22a0-4f52-8246-7bf63869f9be

📥 Commits

Reviewing files that changed from the base of the PR and between 61f41ff and e1e415f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (6)
  • crates/libsy/Cargo.toml
  • crates/libsy/src/algorithms.rs
  • crates/libsy/src/algorithms/prefill_probe.rs
  • crates/libsy/src/algorithms/prefill_probe/artifact.rs
  • crates/libsy/src/algorithms/prefill_probe/policy.rs
  • crates/libsy/src/lib.rs

@ryan-lempka

Copy link
Copy Markdown
Collaborator

@anniesurla can you rebase?

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.

2 participants