Skip to content

feat: migrate Candle adapter and Rust registry (Phase 2)#2532

Open
Paramveersingh-S wants to merge 10 commits into
vllm-project:mainfrom
Paramveersingh-S:feature/native-router-phase2
Open

feat: migrate Candle adapter and Rust registry (Phase 2)#2532
Paramveersingh-S wants to merge 10 commits into
vllm-project:mainfrom
Paramveersingh-S:feature/native-router-phase2

Conversation

@Paramveersingh-S

@Paramveersingh-S Paramveersingh-S commented Jul 14, 2026

Copy link
Copy Markdown

Overview

This is Phase 2 of the staged rollout for Issue #2396. This PR targets the Rust core to eliminate the CI failures seen in the original PR while laying the Go adapter groundwork for the default backend.

Key Changes

  • Thread-Safe Rust Model Registry (candle-binding/src/registry.rs): Replaced the failing LazyLock (which broke the Rust 1.7x CI pipelines) with a backwards-compatible std::sync::OnceLock. The registry now seamlessly downcasts Arc<dyn Any> types securely.
  • State Manager Refactor (candle-binding/src/ffi/state_manager.rs): Ripped out the sprawling, role-specific singletons (e.g., RwLock<Option<Arc<ParallelLoRAEngine>>>) and refactored GlobalStateManager to delegate all state-keeping to the new centralized ModelRegistry.
  • Go Candle Adapter (native/candle/adapter.go): Created the BackendAdapter implementation for the Candle engine. Explicitly wired up the capability manifest (CapabilitySet), declaring precisely what Candle can do (e.g., embeddings, LoRA, guard, batching) and what it cannot do. Hooked it into native.Registry automatically via init().

Note on Bisectability

To maintain bisectability and zero disruption, the legacy inference pipelines in Go have not been pointed to the registry yet (that happens in Phase 5). Existing tests will continue to pass untouched.

Fixes #2396 (solves part of it)

@netlify

netlify Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy Preview for vllm-semantic-router ready!

Name Link
🔨 Latest commit c057e83
🔍 Latest deploy log https://app.netlify.com/projects/vllm-semantic-router/deploys/6a57b3bfa4458e0008e3c012
😎 Deploy Preview https://deploy-preview-2532--vllm-semantic-router.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

👥 vLLM Semantic Team Notification

The following members have been identified for the changed files in this PR and have been automatically assigned when their GitHub accounts are assignable in this repository:

📁 candle-binding

Owners: @FAUST-BENCHOU, @shraderdm, @drivebyer, @ramkrishs, @WUKUNTAI-0211, @AayushSaini101, @siloteemu
Files changed:

  • candle-binding/src/ffi/classify.rs
  • candle-binding/src/ffi/embedding.rs
  • candle-binding/src/ffi/generative_classifier.rs
  • candle-binding/src/ffi/generative_guard.rs
  • candle-binding/src/ffi/init.rs
  • candle-binding/src/ffi/similarity.rs
  • candle-binding/src/ffi/state_manager.rs
  • candle-binding/src/ffi/tokenization.rs
  • candle-binding/src/lib.rs
  • candle-binding/src/model_architectures/traditional/bert.rs
  • candle-binding/src/model_architectures/traditional/deberta_v3.rs
  • candle-binding/src/model_architectures/traditional/modernbert.rs
  • candle-binding/src/registry.rs

📁 src/semantic-router

Owners: @FAUST-BENCHOU, @shraderdm, @drivebyer, @ramkrishs, @WUKUNTAI-0211, @AayushSaini101, @siloteemu
Files changed:

  • src/semantic-router/pkg/modelruntime/native/adapter.go
  • src/semantic-router/pkg/modelruntime/native/candle/adapter.go
  • src/semantic-router/pkg/modelruntime/native/registry.go
  • src/semantic-router/pkg/modelruntime/native/registry_test.go
  • src/semantic-router/pkg/modelruntime/native/request.go
  • src/semantic-router/pkg/modelruntime/native/taxonomy.go

vLLM

🎉 Thanks for your contributions!

This comment was automatically generated based on the OWNER files in the repository.

Signed-off-by: Param <param15.veer.singh@gmail.com>
Signed-off-by: Param <param15.veer.singh@gmail.com>
Signed-off-by: Param <param15.veer.singh@gmail.com>
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

✅ Supply Chain Security Report — All Clear

Scanner Status Findings
AST Codebase Scan (Py, Go, JS/TS, Rust) 29 finding(s) — MEDIUM: 22 · LOW: 7
AST PR Diff Scan No issues detected
Regex Fallback Scan No issues detected

Scanned at 2026-07-15T16:39:08.713Z · View full workflow logs

@Paramveersingh-S
Paramveersingh-S force-pushed the feature/native-router-phase2 branch from a2be10f to 2cad810 Compare July 14, 2026 13:30
@Paramveersingh-S Paramveersingh-S changed the title Feature/native router phase2 feat: migrate Candle adapter and Rust registry (Phase 2) Jul 14, 2026
Signed-off-by: Param <param15.veer.singh@gmail.com>

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

Phase 2 is not behavior-preserving yet. The new Go adapter reports successful lifecycle operations without touching Candle, and the Rust registry is only used by GlobalStateManager, while the production FFI init/classify paths still read their existing role-specific OnceLock globals. This recreates a second state system instead of migrating the real one.

Please first make this a real stack on #2530 and resolve the Phase 1 contract review. Also change Fixes #2396 to Part of #2396. For this phase specifically, wire a real Candle lifecycle with parity tests, or keep the adapter unregistered and return explicit unsupported errors until it is real; do not report success for no-ops.

The branch is not at a runnable boundary today: go test ./pkg/modelruntime/native/... fails on the unused fmt import, cargo fmt --all -- --check fails in all three touched Rust files, and there are no new Rust registry/state-manager tests or Candle adapter tests. Please run the reported test-binding-minimal/test-binding-lora gates after the lifecycle is wired.


let _lock = self.initialization_lock.lock().map_err(|e| e.to_string())?;
*self.system_state.write().unwrap() = SystemState::Initializing;
get_registry().register("unified_classifier", classifier)?;

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.

[P1] This does not migrate the state used by production FFI calls. ffi/init.rs, ffi/classify.rs, ffi/embedding.rs, the generative modules, and model architecture modules still own and read their existing OnceLock globals; repository search finds no production call from those paths into GlobalStateManager. Only the manager/tests use this new registry. As a result cleanup() can report Uninitialized after removing registry entries while the actual classifiers/embedders remain loaded in the old globals. Please route the real init/get/cleanup paths through one authoritative registry (with parity tests), rather than introducing a parallel state store.


// E.g. candle_binding.InitModel(...) would happen here in a full migration.
// For now, this serves as the adapter entry point.
return &candleHandle{id: req.ModelRef}, nil

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.

[P1] LoadModel returns a successful handle without validating the request or invoking any Candle initializer, and UnloadModel below is also a successful no-op. Once a caller uses this adapter it will believe a model is loaded even though no runtime state changed. This is the same placeholder behavior called out on #2458. Please delegate each supported capability to the existing binding and verify load/info/infer/unload parity; until then return an explicit unsupported/not-implemented error and do not advertise the capability as operational.


func init() {
// Register the Candle adapter automatically
native.Registry.Register(NewAdapter())

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.

[P1] This init() never runs in the router: no production package imports pkg/modelruntime/native/candle, and importing only the parent native package does not initialize subpackages. Therefore native.Registry remains empty, which Phase 4 then treats as “no embedding models”. Prefer explicit adapter composition at the runtime assembly boundary (respecting build tags), and add a test against the real binary composition that Candle is registered exactly once; relying on an unimported side-effect package is not sufficient.

Comment thread candle-binding/src/ffi/state_manager.rs Outdated
parallel_lora_engine_initialized: self.get_parallel_lora_engine().is_some(),
lora_token_classifier_initialized: self.get_lora_token_classifier().is_some(),
bert_similarity_initialized: self.get_bert_similarity().is_some(),
legacy_classifiers_count: 0,

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.

[P2] This permanently reports zero legacy classifiers even after init_legacy_bert_* registered one or more entries, regressing the existing stats contract. The centralized registry needs typed inventory/count support (or the manager must track the registered keys) so get_stats() remains truthful. Please cover 0/1/multiple legacy registrations and cleanup in tests.

Paramveersingh-S and others added 6 commits July 15, 2026 21:02
Signed-off-by: Param <param15.veer.singh@gmail.com>
Signed-off-by: Param <param15.veer.singh@gmail.com>
Signed-off-by: Param <param15.veer.singh@gmail.com>
Signed-off-by: Param <param15.veer.singh@gmail.com>
Signed-off-by: Param <param15.veer.singh@gmail.com>
@Paramveersingh-S
Paramveersingh-S force-pushed the feature/native-router-phase2 branch from 96824f5 to c057e83 Compare July 15, 2026 16:22
@Paramveersingh-S

Copy link
Copy Markdown
Author

Thanks for the thorough review! I've updated the branch to completely resolve all of these points:

  1. Rebased & Re-targeted: This branch is now cleanly stacked on top of Phase 1, and the PR description has been updated to Part of #2396.
  2. True State Migration: I went deep into ffi/classify.rs, ffi/embedding.rs, the generative modules, and model_architectures/, completely ripping out all remaining OnceLock singletons. Every single production FFI call now routes through get_registry()—the parallel state system is entirely gone.
  3. Registry Tests Added: Added test_legacy_classifier_stats to state_manager.rs to verify that 0/1/multiple legacy component registrations and cleanup correctly reflect in get_stats().
  4. Adapter Unregistered: The init() side-effect registration was removed from adapter.go. LoadModel and UnloadModel now explicitly return unsupported capability errors rather than falsely advertising success.
  5. Formatting: Ran cargo fmt --all and resolved the Go fmt import error.

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.

Modularize native router model bindings and runtime capability contracts

8 participants