Skip to content

feat(server): configure prefill-probe routing - #186

Open
anniesurla wants to merge 3 commits into
NVIDIA-NeMo:mainfrom
anniesurla:asurla/feat-prefill-probe-server
Open

feat(server): configure prefill-probe routing#186
anniesurla wants to merge 3 commits into
NVIDIA-NeMo:mainfrom
anniesurla:asurla/feat-prefill-probe-server

Conversation

@anniesurla

@anniesurla anniesurla commented Jul 29, 2026

Copy link
Copy Markdown

What

Why

Completes the server integration needed to configure and run learned prefill routing end to end.

Depends on #174 and #185. Until those PRs merge, GitHub will also show their commits in this stacked PR.

How tested

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • uv run ruff check .
  • uv run mypy switchyard
  • uv run pytest tests/ -v -m "not integration" — 1718 passed, 12 skipped, 28 deselected
  • cd docs && make publish
  • No live provider calls

Checklist

  • Unit tests cover route construction, defaults, and configuration errors.
  • Customer-facing server configuration and MkDocs documentation are updated.
  • Commit is DCO signed off.

Notes for reviewers

The intended delta is the single feat(server) commit on top of #185. Token-aware pricing remains a separate follow-up.

Summary by CodeRabbit

  • New Features

    • Added a learned prefill-probe route that selects between strong and weak completion targets using prompt hidden-state analysis.
    • Added vLLM hidden-state extraction with feature pooling, artifact validation, timeouts, cleanup, and fallback handling.
    • Added checkpoint-based routing with cost-aware decisions and configurable result caching.
  • Documentation

    • Added setup and configuration guidance for deploying the vLLM hidden-state probe route.
    • Updated supported algorithm and operations documentation.

asurla1998 added 3 commits July 29, 2026 17:19
Signed-off-by: asurla1998 <asurla1998@users.noreply.huggingface.co>
Signed-off-by: asurla1998 <asurla1998@users.noreply.huggingface.co>
Signed-off-by: asurla1998 <asurla1998@users.noreply.huggingface.co>
@anniesurla
anniesurla requested a review from a team as a code owner July 29, 2026 18:57
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Learned prefill probe

Layer / File(s) Summary
vLLM hidden-state extraction
crates/libsy-llm-client/...
Adds the public probe client, synchronized safetensors artifact reading, feature pooling for F32/BF16 tensors, cleanup, typed errors, and comprehensive tests.
Checkpoint validation and routing math
crates/libsy/.../prefill_probe/artifact.rs, crates/libsy/.../prefill_probe/policy.rs, crates/libsy/Cargo.toml
Adds validated router artifact loading, PCA and ensemble inference, probability calculation, cost-aware tier selection, and related tests.
Prefill probe classifier
crates/libsy/src/algorithms.rs, crates/libsy/src/algorithms/prefill_probe.rs, crates/libsy/src/lib.rs
Adds the transport-independent probe trait, classifier configuration, request parsing, caching, fallback behavior, public exports, and classifier tests.
Server route construction
crates/switchyard-server/src/config.rs, crates/switchyard-server/Cargo.toml
Adds RouteConfig::PrefillProbe, vLLM probe adaptation, classifier wiring, checkpoint test helpers, bounded defaults, and route validation tests.
Configuration and operations documentation
crates/switchyard-server/CONFIGURATION.md, crates/switchyard-server/README.md, docs/operations/vllm_hidden_state_probe.md, mkdocs.yml
Documents route configuration, vLLM setup, checkpoint requirements, policy behavior, artifact lifecycle, and operations navigation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Poem

A rabbit hops where hidden states gleam,
Through locks and tensors, soft as a dream.
Weak or strong, the routes now choose,
Cached little hops avoid reuse.
“Safely cleaned!” the bunny sings,
While docs grow whiskers and wings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.19% 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 is concise and matches the main change: adding server-side prefill-probe routing configuration.
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.

Actionable comments posted: 3

🧹 Nitpick comments (4)
crates/libsy-llm-client/src/vllm_hidden_state_probe.rs (2)

441-442: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Validate the artifact path before opening it.

open_synchronized_artifact opens the reported path (and its .lock companion) before validate_hidden_states_path confirms the canonical target is inside root and is a regular file. Reordering keeps the containment check strictly ahead of any file handle on an attacker-influenced path.

♻️ Proposed reordering
-    let (mut artifact, synchronization_lock) = open_synchronized_artifact(path, timeout)?;
-    let artifact_path = validate_hidden_states_path(root, path)?;
+    let (mut artifact, synchronization_lock) = open_synchronized_artifact(path, timeout)?;
+    // Re-validate after the writer released the lock: the file must still
+    // canonicalize inside `root` and be a regular file before it is read.
+    let artifact_path = validate_hidden_states_path(root, path)?;

Note the current order is already safe for reads (validation precedes read_to_end), so this is hardening only.

🤖 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-llm-client/src/vllm_hidden_state_probe.rs` around lines 441 -
442, Reorder the hidden-state artifact flow so validate_hidden_states_path(root,
path) completes before open_synchronized_artifact(path, timeout) is called.
Preserve the existing artifact and synchronization_lock bindings and subsequent
read behavior after validation.

168-169: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Stale sweep runs on every extraction and its failure aborts the probe.

reap_stale_artifacts does a full read_dir plus per-entry metadata/lock attempts before each HTTP call, and any single I/O error (e.g. one unreadable entry another writer owns) propagates and fails the whole extraction. Consider decoupling the sweep from the request path (interval-gated or background) and logging sweep errors instead of failing the probe.

♻️ Sketch: don't fail extraction on sweep errors
-        self.reap_stale_artifacts().await?;
+        // A best-effort sweep must not fail routing; the artifact read below is
+        // what the caller actually depends on.
+        if let Err(error) = self.reap_stale_artifacts().await {
+            tracing::debug!(%error, "hidden-state stale sweep failed");
+        }
🤖 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-llm-client/src/vllm_hidden_state_probe.rs` around lines 168 -
169, Update VllmHiddenStateProbe::extract so stale-artifact cleanup is not
performed on every extraction and cleanup I/O failures do not abort the probe
request. Gate reap_stale_artifacts by an appropriate interval or move it to
background maintenance, and log any sweep error while allowing extraction to
continue to the HTTP call.
crates/libsy/src/algorithms/prefill_probe.rs (1)

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

No request coalescing: concurrent identical tasks each issue a probe call.

The cache is only consulted before and written after the await, so N in-flight requests for the same task produce N vLLM prefill calls plus N inference jobs. If duplicate concurrent tasks are expected (retries, fan-out agents), a per-key in-flight map or a single-flight helper would cut probe load without changing behavior.

🤖 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 - 226, Update
select_for_task to coalesce concurrent requests sharing the same cache_key by
introducing or reusing a per-key in-flight future/task map. Ensure only the
first caller runs probe.extract and routing.select, while concurrent callers
await the same result; preserve existing cache reads, result caching, and error
behavior, and remove the in-flight entry when processing completes.
crates/libsy/src/algorithms/prefill_probe/artifact.rs (1)

19-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Hard-pinned architecture/output constants make every retrained checkpoint a code change.

PCA_DIM, TRUNK_HIDDEN, ENSEMBLE_SIZE, and especially the literal OUTPUT_NAMES model ids are enforced with strict equality, so a checkpoint trained with a different ensemble size or a renamed head fails to load even though the loader could derive those values from router.json (pca_dim, trunk_hidden, ensemble_size, output_names are all present in metadata). Consider validating self-consistency (shapes vs. metadata) plus format_version, and keeping only the structural invariants as constants.

Also applies to: 312-334

🤖 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 19 - 24,
Replace the hard-pinned PCA_DIM, TRUNK_HIDDEN, ENSEMBLE_SIZE, and OUTPUT_NAMES
checks in the artifact loader with values parsed from router.json metadata
(pca_dim, trunk_hidden, ensemble_size, and output_names). Validate checkpoint
tensor shapes and metadata self-consistency, while retaining only genuine
structural invariants as constants and validating format_version.
🤖 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.

Inline comments:
In `@crates/switchyard-server/CONFIGURATION.md`:
- Around line 24-26: Update the checklist step for constructing the algorithm to
reference the appropriate target resolver, either resolve_target or
resolve_targets, instead of naming only resolve_targets. Keep the guidance
applicable to RouteConfig::PrefillProbe and other implementations.

In `@docs/operations/vllm_hidden_state_probe.md`:
- Around line 27-31: Update the artifact-directory creation command in the probe
setup to apply explicit restrictive permissions rather than relying on the
process umask. Use mode 0700 for same-user processes, or 0770 with an
appropriate shared group when the processes run under different users.
- Around line 33-59: Update the vllm serve example command to include the
--no-enable-chunked-prefill option, preserving the existing hidden-state
extraction and KV connector configuration.

---

Nitpick comments:
In `@crates/libsy-llm-client/src/vllm_hidden_state_probe.rs`:
- Around line 441-442: Reorder the hidden-state artifact flow so
validate_hidden_states_path(root, path) completes before
open_synchronized_artifact(path, timeout) is called. Preserve the existing
artifact and synchronization_lock bindings and subsequent read behavior after
validation.
- Around line 168-169: Update VllmHiddenStateProbe::extract so stale-artifact
cleanup is not performed on every extraction and cleanup I/O failures do not
abort the probe request. Gate reap_stale_artifacts by an appropriate interval or
move it to background maintenance, and log any sweep error while allowing
extraction to continue to the HTTP call.

In `@crates/libsy/src/algorithms/prefill_probe.rs`:
- Around line 210-226: Update select_for_task to coalesce concurrent requests
sharing the same cache_key by introducing or reusing a per-key in-flight
future/task map. Ensure only the first caller runs probe.extract and
routing.select, while concurrent callers await the same result; preserve
existing cache reads, result caching, and error behavior, and remove the
in-flight entry when processing completes.

In `@crates/libsy/src/algorithms/prefill_probe/artifact.rs`:
- Around line 19-24: Replace the hard-pinned PCA_DIM, TRUNK_HIDDEN,
ENSEMBLE_SIZE, and OUTPUT_NAMES checks in the artifact loader with values parsed
from router.json metadata (pca_dim, trunk_hidden, ensemble_size, and
output_names). Validate checkpoint tensor shapes and metadata self-consistency,
while retaining only genuine structural invariants as constants and validating
format_version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 949d5b25-2c93-4bed-9621-2e707916fb5e

📥 Commits

Reviewing files that changed from the base of the PR and between 1f5d173 and 3048298.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (15)
  • crates/libsy-llm-client/Cargo.toml
  • crates/libsy-llm-client/src/lib.rs
  • crates/libsy-llm-client/src/vllm_hidden_state_probe.rs
  • 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
  • crates/switchyard-server/CONFIGURATION.md
  • crates/switchyard-server/Cargo.toml
  • crates/switchyard-server/README.md
  • crates/switchyard-server/src/config.rs
  • docs/operations/vllm_hidden_state_probe.md
  • mkdocs.yml

Comment on lines +24 to 26
2. Add its TOML fields as a `RouteConfig` variant in `src/config.rs`.
3. Construct it in the `build_algorithm` match, resolving target names with `resolve_targets`.
4. Add a parsing test and an end-to-end server test when the algorithm makes LLM calls.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make target-resolution guidance match the implementation.

The checklist names resolve_targets, but RouteConfig::PrefillProbe resolves strong_target and weak_target individually with resolve_target in crates/switchyard-server/src/config.rs (Lines 295–399). Say “the appropriate target resolver (resolve_target/resolve_targets)” to avoid misleading future implementations.

🤖 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/switchyard-server/CONFIGURATION.md` around lines 24 - 26, Update the
checklist step for constructing the algorithm to reference the appropriate
target resolver, either resolve_target or resolve_targets, instead of naming
only resolve_targets. Keep the guidance applicable to RouteConfig::PrefillProbe
and other implementations.

Comment on lines +27 to +31
Create a directory used only for probe artifacts:

```bash
mkdir -p /dev/shm/switchyard-prefill
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Create the artifact directory with explicit restrictive permissions.

mkdir -p relies on the process umask and can leave the /dev/shm directory readable by other local users, while the guide correctly identifies hidden-state artifacts as sensitive. Use 0700 when both processes share a user, or a dedicated group with 0770 when they do not.

Suggested setup
- mkdir -p /dev/shm/switchyard-prefill
+ install -d -m 0700 /dev/shm/switchyard-prefill
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Create a directory used only for probe artifacts:
```bash
mkdir -p /dev/shm/switchyard-prefill
```
Create a directory used only for probe artifacts:
🤖 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 `@docs/operations/vllm_hidden_state_probe.md` around lines 27 - 31, Update the
artifact-directory creation command in the probe setup to apply explicit
restrictive permissions rather than relying on the process umask. Use mode 0700
for same-user processes, or 0770 with an appropriate shared group when the
processes run under different users.

Comment on lines +33 to +59
Start vLLM with the hidden-state extraction method and disk connector. Replace the model and layer
IDs with the values used to train the checkpoint:

```bash
vllm serve Qwen/Qwen3-8B \
--speculative_config '{
"method": "extract_hidden_states",
"num_speculative_tokens": 1,
"draft_model_config": {
"hf_config": {
"eagle_aux_hidden_state_layer_ids": [1, 2, 3, 4]
}
}
}' \
--kv_transfer_config '{
"kv_connector": "ExampleHiddenStatesConnector",
"kv_role": "kv_producer",
"kv_connector_extra_config": {
"shared_storage_path": "/dev/shm/switchyard-prefill",
"use_synchronization_lock": true
}
}'
```

Keep `use_synchronization_lock` enabled. Switchyard acquires the companion `.lock` file before
reading, so it cannot parse a partially written safetensors artifact. Chunked prefill is
incompatible with vLLM hidden-state extraction and must be disabled.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced documentation section with line numbers.
sed -n '1,120p' docs/operations/vllm_hidden_state_probe.md | cat -n

Repository: NVIDIA-NeMo/Switchyard

Length of output: 4682


🌐 Web query:

vLLM extract_hidden_states chunked prefill disabled no-enable-chunked-prefill hidden-state extraction documentation

💡 Result:

In vLLM, the "hidden state extraction" feature allows you to capture intermediate layer activations during inference [1][2]. Historically, this feature was incompatible with chunked prefill, requiring users to explicitly disable chunked prefill using the --no-enable-chunked-prefill flag to ensure correct operation and prevent errors [1][3][4]. However, recent improvements (notably in PR #43805, dated May 2026) have addressed this limitation by modifying how hidden states are saved [5]. By moving the save operation to the get_finished stage—which occurs only once per request after generation completes—vLLM is now able to support hidden state extraction without strictly requiring that chunked prefill be disabled [5]. Regarding the --no-enable-chunked-prefill flag: - In older versions of vLLM or specific configurations, this flag was the standard way to disable chunked prefill [6][7][4]. - In the newer V1 engine architecture, chunked prefill is enabled by default [8]. While some versions allowed the --no-enable-chunked-prefill argument to override this, vLLM may issue warnings that disabling this feature is not officially supported and could lead to performance issues or crashes [8][9]. - If you are using a version of vLLM that predates the improvements mentioned in PR #43805, you must continue to use --no-enable-chunked-prefill (or enable_chunked_prefill=False in the Python API) when using extract_hidden_states [1][4]. If you are on a current version, this manual override may no longer be necessary [5]. It is recommended to consult the documentation for your specific vLLM version, as the requirement for this flag depends on the underlying engine implementation and version [8][10].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' docs/operations/vllm_hidden_state_probe.md | cat -n

Repository: NVIDIA-NeMo/Switchyard

Length of output: 4682


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search the repository for chunked prefill references around hidden-state extraction docs.
rg -n --hidden --glob '!**/.git/**' -C 2 'chunked prefill|no-enable-chunked-prefill|extract_hidden_states|hidden-state extraction' docs .

Repository: NVIDIA-NeMo/Switchyard

Length of output: 12703


Disable chunked prefill in the example launch command. Hidden-state extraction is incompatible with chunked prefill here, so add --no-enable-chunked-prefill to the vllm serve example.

Suggested command change
 vllm serve Qwen/Qwen3-8B \
+  --no-enable-chunked-prefill \
   --speculative_config '{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Start vLLM with the hidden-state extraction method and disk connector. Replace the model and layer
IDs with the values used to train the checkpoint:
```bash
vllm serve Qwen/Qwen3-8B \
--speculative_config '{
"method": "extract_hidden_states",
"num_speculative_tokens": 1,
"draft_model_config": {
"hf_config": {
"eagle_aux_hidden_state_layer_ids": [1, 2, 3, 4]
}
}
}' \
--kv_transfer_config '{
"kv_connector": "ExampleHiddenStatesConnector",
"kv_role": "kv_producer",
"kv_connector_extra_config": {
"shared_storage_path": "/dev/shm/switchyard-prefill",
"use_synchronization_lock": true
}
}'
```
Keep `use_synchronization_lock` enabled. Switchyard acquires the companion `.lock` file before
reading, so it cannot parse a partially written safetensors artifact. Chunked prefill is
incompatible with vLLM hidden-state extraction and must be disabled.
Start vLLM with the hidden-state extraction method and disk connector. Replace the model and layer
IDs with the values used to train the checkpoint:
🤖 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 `@docs/operations/vllm_hidden_state_probe.md` around lines 33 - 59, Update the
vllm serve example command to include the --no-enable-chunked-prefill option,
preserving the existing hidden-state extraction and KV connector configuration.

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.

1 participant