Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a3d9797
feat(router)!: make worker-selection plugins thread-safe and runnable…
karen-sy Oct 30, 2026
f659360
refactor(router)!: rename dynamo-custom-policy-builtin to dynamo-rout…
karen-sy Oct 30, 2026
a1be7e1
refactor(router)!: run the router modes as builtin plugins
karen-sy Oct 30, 2026
9d6c6ce
refactor(router): give each router mode its own file
karen-sy Oct 30, 2026
8a62d01
trim doc changes
karen-sy Oct 30, 2026
8b58fb0
minor refactor, deslop
karen-sy Oct 30, 2026
51be950
deslop comment
karen-sy Oct 30, 2026
ad301c2
refactor(router): address review nits
karen-sy Nov 2, 2026
23068e8
refactor(router): trim the hosted picker's unused surface
karen-sy Nov 2, 2026
b2337db
nit doc
karen-sy Nov 2, 2026
a369170
refactor(router)!: run PushRouter's routing modes through plugin inst…
karen-sy Nov 2, 2026
a7afeb0
fix(router): resolve the router modes from every installed catalog
karen-sy Nov 3, 2026
f1b4e76
refactor(router): shrink the hosted selection request and return the …
karen-sy Nov 3, 2026
b92e621
Merge main into karenc/router-modes-1-hosted-plugins
karen-sy Nov 3, 2026
1c00e9c
Merge karenc/router-modes-1-hosted-plugins into karenc/router-modes-2…
karen-sy Nov 3, 2026
add51b7
Update docs/fern/pages/reference/general/releases/deprecations.mdx
karen-sy Nov 3, 2026
82e1340
fix(router): track occupancy when a selector reads in-flight counts
karen-sy Nov 3, 2026
02a9e66
docs(router): note the PushRouter migration for custom Rust callers
karen-sy Nov 3, 2026
464d176
Merge karenc/router-modes-1-hosted-plugins into karenc/router-modes-2…
karen-sy Nov 3, 2026
d0362f6
Merge main up to the PR 1 squash into karenc/router-modes-2-plugin-modes
karen-sy Nov 3, 2026
45eb54f
Merge main into karenc/router-modes-2-plugin-modes
karen-sy Nov 3, 2026
067808e
Merge main into karenc/router-modes-2-plugin-modes
karen-sy Nov 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ repository = "https://github.com/ai-dynamo/dynamo.git"
keywords = ["llm", "genai", "inference", "nvidia", "distributed"]

[workspace.dependencies]
dynamo-custom-policy-builtin = { path = "lib/router-plugins/builtin", version = "1.7.0" }
dynamo-router-policies = { path = "lib/router-plugins/builtin", version = "1.7.0" }
dynamo-generate-format-catalog = { path = "lib/generate-format-catalog", version = "1.7.0" }
# Published separately by https://github.com/ai-dynamo/aisimulate.
aisimulate-core = { version = "=0.13.0-dev.202610290000000087" }
Expand Down
2 changes: 1 addition & 1 deletion deploy/inference-gateway/ext-proc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ name = "dynamo-ext-proc"
path = "src/bin/dynamo-ext-proc.rs"

[dependencies]
dynamo-custom-policy-builtin = { workspace = true }
dynamo-router-policies = { workspace = true }
async-trait = { workspace = true }
dynamo-kv-router = { workspace = true, features = [
"metrics",
Expand Down
2 changes: 1 addition & 1 deletion deploy/inference-gateway/ext-proc/src/peer_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ mod tests {
SelectionServiceBuilder::new(
kv_router_config_from_dynamo_env(),
WorkerType::Aggregated,
dynamo_custom_policy_builtin::default_registry(),
dynamo_router_policies::default_registry(),
)
.indexer_threads(1)
.replica_sync(listener_port, Vec::new())
Expand Down
6 changes: 3 additions & 3 deletions deploy/inference-gateway/ext-proc/src/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl Selector {
let mut builder = SelectionServiceBuilder::new(
kv_router_config,
WorkerType::Aggregated,
policy_registry.with_default_factory(dynamo_custom_policy_builtin::default_factory()),
dynamo_router_policies::with_defaults(policy_registry),
)
.indexer_threads(cfg.selector_threads);
if let Some(peer_replication) = peer_replication {
Expand Down Expand Up @@ -288,7 +288,7 @@ mod tests {

impl WorkerPicker for ExpectedOutputRecorder {
fn pick(
&mut self,
&self,
context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down Expand Up @@ -459,7 +459,7 @@ models:

impl WorkerPicker for FirstEligiblePicker {
fn pick(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl WorkerFilter for MinimumDeviceOverlapFilter {
}

fn keep(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidate: WorkerCandidate<'_>,
) -> Result<bool, WorkerSelectionPolicyError> {
Expand Down Expand Up @@ -159,7 +159,7 @@ impl WorkerScorer for ActiveRequestsScorer {
}

fn score(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down Expand Up @@ -201,7 +201,7 @@ struct LowestCostPicker;

impl WorkerPicker for LowestCostPicker {
fn pick(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down Expand Up @@ -451,6 +451,7 @@ If a component needs no optional worker data, return `WorkerInputs::NONE`. Combi
| `LOAD` | `decode_cost_blocks()` | Projected active decode footprint, including this request's additional active blocks |
| `LOAD` | `active_requests()` | Requests currently active on the worker |
| `PREFERRED_TAINT` | `preferred_taint_multiplier()` | Optional cost multiplier from matching preferred routing taints |
| `ACTIVE_REQUESTS` | `WorkerInputView::active_requests(row)` | Picker only. Requests in flight on the row's worker; `None` only when the picker did not declare the group or the row is out of range |
| `KV_TRANSFER` | `kv_transfer()` | `None` if the host has no transfer data for this request: no worker in the partition consumes router hints, or the indexer cannot retain the matched block chain |
| `KV_TRANSFER` | `source_prefix_blocks()` | Prefix blocks held by the source this worker would fetch from, and so held by the worker after the fetch; `None` when no eligible source holds more than the worker's device-resident plus host-pinned prefix |

Expand All @@ -460,6 +461,8 @@ The GPU, CPU, and disk overlap accessors return raw tier counts. A missing tier

A filter or scorer reads groups through `WorkerCandidate::cache()` or `load()`. A picker reads index-aligned views through `WorkerInputView`; its cache view supports `get(row)` and `iter()`. Each component must declare every group it reads; another component requesting a group does not grant access.

Declare `ACTIVE_REQUESTS` instead of `LOAD` when a request count is all the picker needs. It is cheaper, and `LOAD` needs the KV scheduler, which tracks each worker's active tokens and blocks.

A scorer reads `KV_TRANSFER` through `WorkerCandidate::kv_transfer()`. Filters and pickers do not receive it, and declaring it there has no effect. The host picks each worker's source with the [KV fetch hint](topology-aware-kv-transfer.md#kv-fetch-hints) rules, including topology islands, and booking a worker attaches a fetch hint for that same source. Fetches are best effort: the source may no longer hold the blocks when the request runs.

This scorer charges each worker for the prompt blocks it can neither reuse nor fetch. A worker without transfer data still counts its own cache:
Expand All @@ -477,7 +480,7 @@ impl WorkerScorer for UncoveredPromptScorer {
}

fn score(
&mut self,
&self,
context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down Expand Up @@ -634,6 +637,7 @@ impl RequestClassifier for InspectPrefixes {
- Treat candidate order as unspecified.
- Request only the signal groups that the component reads.
- Keep blocking I/O and panics out of `keep`, `score`, and `pick`.
- Keep any state your policy shares across requests correct under concurrent updates. Dynamo reuses one policy for every request to a model and worker role and handles many requests at once, so two selections can read and change that state at the same time. Policies without such state need nothing extra.
- Keep policy state local to the factory-created policy unless cross-partition sharing is a deliberate requirement.
- Build the policy against the same Dynamo revision as the frontend or EPP.
- If a signal adds work, storage, allocation, or another scan, run the worker-selection benchmark.
Expand Down Expand Up @@ -691,8 +695,9 @@ patch, and reset semantics.

Updates run between selection calls. A replacement cannot add worker-input groups beyond those
declared at startup, change the worker label, or change exclusive-affinity behavior. Queues,
worker discovery, cache indexes, and host accounting are retained. Scorer/picker-local scratch
state is rebuilt. A cancelled control request still waiting in the actor queue is skipped.
worker discovery, cache indexes, and host accounting are retained. The replacement's filters,
scorers, and picker are new instances, so state the previous instances held, such as a cursor,
does not carry over. A cancelled control request still waiting in the actor queue is skipped.

The [simple-filter-score-pick example](https://github.com/ai-dynamo/dynamo/blob/main/examples/router/custom-policy-example/simple-filter-score-pick/src/lib.rs)
opts in for its `min_device_overlap_blocks` parameter. The built-in default policy opts in for
Expand Down
4 changes: 4 additions & 0 deletions docs/fern/pages/reference/general/releases/deprecations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ Upgrading? Pick the version you run today to see the dependency migration and th

> **Migrate:** Set `DYN_ENABLE_FRONTEND_ADMIN_API=1` on frontends that need these routes and call port `8002` (`DYN_FRONTEND_ADMIN_PORT`). The listener binds to `127.0.0.1` (`DYN_FRONTEND_ADMIN_HOST`), so on Kubernetes use `kubectl port-forward pod/<frontend-pod> 8002:8002`. Remove `DYN_DISABLE_FRONTEND_ADMIN_API`; it is ignored.

<span className="dynref-badge dynref-badge--amber">Deprecated</span> **Deprecated the `WorkerInputs::OCCUPANCY` worker-selection input group** in favor of `WorkerInputs::ACTIVE_REQUESTS`, which custom pickers read through `WorkerInputView::active_requests(row)`. The alias still compiles with a deprecation warning and is slated for removal in Dynamo v1.9.0.

> **Migrate:** Replace `WorkerInputs::OCCUPANCY` with `WorkerInputs::ACTIVE_REQUESTS` in custom worker-selection policies.

<a id="v160"></a>

## v1.6.0 - Oct 22, 2026
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl WorkerFilter for MinimumDeviceOverlapFilter {
}

fn keep(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidate: WorkerCandidate<'_>,
) -> Result<bool, WorkerSelectionPolicyError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub(crate) struct PrefillPicker;
impl WorkerPicker for PrefillPicker {
/// Returns the prefill candidate row with the lowest cost.
fn pick(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand All @@ -38,7 +38,7 @@ pub(crate) struct DecodePicker;
impl WorkerPicker for DecodePicker {
/// Returns the decode candidate row with the lowest cost.
fn pick(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand All @@ -59,7 +59,7 @@ impl UnsupportedWorkerTypePicker {

impl WorkerPicker for UnsupportedWorkerTypePicker {
fn pick(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
_input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl WorkerScorer for PrefillLoadScorer {
}

fn score(
&mut self,
&self,
context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down Expand Up @@ -50,7 +50,7 @@ impl WorkerScorer for DecodeLoadScorer {

/// Returns the projected decode cost as a lower-is-better cost.
fn score(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl WorkerFilter for MinimumDeviceOverlapFilter {

/// Keeps a candidate when its device overlap meets the minimum.
fn keep(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidate: WorkerCandidate<'_>,
) -> Result<bool, WorkerSelectionPolicyError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl WorkerPicker for RequestAwarePicker {
}

fn pick(
&mut self,
&self,
context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl WorkerScorer for ActiveRequestsScorer {

/// Find the batch minimum and write one relative load cost per worker.
fn score(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) struct LowestCostPicker;

impl WorkerPicker for LowestCostPicker {
fn pick(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl WorkerScorer for ActiveRequestsScorer {
}

fn score(
&mut self,
&self,
_context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl WorkerScorer for UncachedBlocksScorer {
}

fn score(
&mut self,
&self,
context: &WorkerSelectionContext<'_>,
candidates: WorkerCandidates<'_>,
costs: &mut [f64],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl WorkerPicker for SoftPinRepinPicker {
}

fn pick(
&mut self,
&self,
context: &WorkerSelectionContext<'_>,
input: WorkerInputView<'_>,
) -> Result<usize, WorkerSelectionPolicyError> {
Expand Down
Loading
Loading