Area
Desktop experience
User problem
Area
Engine or model management
User problem
PAIR solves much of the difficult infrastructure required for distributed local inference: node discovery, trust, model inventory, routing, workload visibility, and failover.
The remaining limitation for heterogeneous performance-oriented clusters is that inference backends still need to be explicitly supported as PAIR engines.
In practice, different hardware often performs best with different inference runtimes and configurations. Requiring an operator to replace an already optimized backend with a PAIR-supported engine can negate much of the reason for building a heterogeneous cluster.
I have a working three-node example:
- RTX 5090 — vLLM, Qwen3.8-27B NVFP4, primary/high-throughput node
- RTX 3090 — llama.cpp, Qwen3.8-27B Q4_K_M GGUF, secondary node
- DGX Spark / GB10 — large MoE / VLM endpoint, overflow and vision-specialist node
The router currently used for this cluster is open source here:
https://github.com/darkmatter2222/HomeAILab/tree/main/router
Its declarative endpoint configuration is here:
https://github.com/darkmatter2222/HomeAILab/blob/main/router/config/router.yaml
And the request classification / deterministic selection implementation is here:
https://github.com/darkmatter2222/HomeAILab/blob/main/router/internal/routing/classify.go
This is not intended as a competing implementation or a request for PAIR to copy this router. It is a working reference showing the behaviors that currently prevent this deployment from moving entirely to PAIR.
The core issue is broader than adding one more runtime.
PR #9 adds vLLM as another first-class engine, which is useful. But vLLM, SGLang, llama.cpp server, TensorRT-LLM gateways, custom inference servers, and future engines increasingly expose substantially the same OpenAI-compatible inference surface.
Ideally, every compatible runtime should not require a bespoke PAIR engine implementation before it can participate in the cluster.
There is also a heterogeneous routing problem.
Two nodes may expose the same logical model while having radically different:
- hardware
- quantization
- inference runtime
- context capacity
- concurrency
- prefill performance
- generation performance
- modality support
- cold-start / TTFT behavior
A request should therefore be matched against endpoint capabilities before ordinary load-based scheduling occurs.
Real-world reference behavior
The router above currently models endpoints declaratively.
For example, the same logical coding workload can be served by:
RTX 5090
vLLM
Qwen3.8-27B NVFP4
262K context
priority 10
static capacity 1
RTX 3090
llama.cpp
Qwen3.8-27B Q4_K_M
262K context
priority 20
static capacity 1
DGX Spark
large Flash-Next / VLM backend
262K context
priority 30
static capacity 3
vision capable
long-prefill behavior
The router does not query GPU utilization, Prometheus, Docker, model inventory, or health synchronously for every inference request.
Those signals are maintained asynchronously.
The inference hot path approximately does:
classify request
↓
determine required capabilities
↓
determine required context
↓
read in-memory endpoint table
↓
reject incompatible endpoints
↓
sort eligible endpoints deterministically
↓
atomically reserve endpoint capacity
↓
stream directly to backend
Request classification currently includes:
- image/vision requirement
- tool requirement
- streaming requirement
- estimated context requirement
- requested maximum output
- logical model alias
Candidate exclusion includes:
- vision unsupported
- tools unsupported
- streaming unsupported
- insufficient context
- unhealthy
- disabled
- draining
- capacity full
Only after eligibility is established is endpoint preference evaluated.
This distinction is important in heterogeneous clusters: eligibility first, preference/load second.
Desired outcome
PAIR should support an externally managed / bring-your-own OpenAI-compatible engine.
An operator would be able to register an already-running inference endpoint without PAIR needing to understand or control that engine's process lifecycle.
At minimum, an external endpoint could declare:
endpoint:
api_family: openai
base_url: http://127.0.0.1:8201
model:
physical_name: qwen3.8
logical_alias: local-coding
capabilities:
text: true
vision: false
tools: true
streaming: true
context:
max_tokens: 262144
routing:
priority: 10
capacity: 1
health:
path: /health
PAIR would handle discovery, trust, routing, streaming, failover, and cluster visibility, while the external runtime remains under operator control.
- Generic externally managed engines
Support registering an arbitrary OpenAI-compatible server.
Examples include:
- vLLM
- SGLang
- llama.cpp server
- TensorRT-LLM gateways
- other OpenAI-compatible runtimes
PAIR would not need to:
- install it
- launch it
- terminate it
- load its model
- unload its model
- change its runtime configuration
It would simply adopt the endpoint as inference capacity.
First-class managed engines such as Ollama, LM Studio, and vLLM could continue to provide the richer lifecycle experience.
- Logical model aliases
Allow multiple physically different deployments to advertise one logical model.
For example:
PAIR model: local-coding
5090:
upstream model = qwen3.8
3090:
upstream model = qwen3.8
Spark:
upstream model = another physical/model deployment
The client should not need to know runtime-specific model IDs or quantization names.
This is particularly useful when the same logical model is represented differently across Hugging Face, GGUF, TensorRT, or runtime-specific deployments.
- Declarative endpoint capabilities
Allow an endpoint/model combination to advertise optional characteristics such as:
- text
- vision
- tools
- streaming
- reasoning/thinking
- maximum context
- quantization
- runtime type
- GPU class
- configured concurrency/capacity
- optional workload tags
These should act as eligibility constraints.
For example, an image request should never be routed to an endpoint declaring:
vision: false
even if that node currently has the lowest GPU utilization.
Similarly, a request requiring 180K context should not be dispatched to an endpoint characterized for only 128K.
- Static capacity / admission control
Allow an operator to state how many simultaneous requests a particular deployment should accept.
For example:
5090 / vLLM capacity = 4
3090 / llama.cpp capacity = 1
Spark capacity = 3
The runtime may technically allow more requests, but the operator may have benchmarked a lower concurrency as the point that provides acceptable TTFT or throughput.
PAIR should be able to stop assigning work to that endpoint when its configured admission capacity is reached.
This is complementary to GPU-utilization/load signals rather than a replacement for them.
- Deterministic baseline preference
Allow an optional per-endpoint or per-model priority.
For example:
RTX 5090 priority 10
RTX 3090 priority 20
DGX Spark priority 30
Meaning:
use 5090 while eligible and capacity is available
↓
then 3090
↓
then Spark
Dynamic scheduling signals can still operate within or on top of this policy.
This is important when heterogeneous systems have very different performance characteristics.
An idle slower endpoint should not necessarily receive a request merely because it currently has lower GPU utilization than a substantially faster endpoint.
This is related to #7, which requests richer heterogeneous placement signals. The request here is specifically for the endpoint contract and declarative information necessary to express such a deployment.
- Request capability classification
Ideally PAIR would classify enough of the request to establish eligibility before scheduling.
At minimum:
requested model
vision/images?
tools?
streaming?
required context?
More advanced scheduling signals could be added independently.
A simple eligibility pipeline could be:
request
↓
model ownership / alias
↓
API compatibility
↓
modality
↓
tool support
↓
context requirement
↓
health
↓
configured capacity
↓
scheduler / preference
- Per-endpoint timeout characteristics
Different inference platforms can have dramatically different prefill behavior.
For example, the DGX Spark endpoint in the reference deployment can experience a very long cold TTFT while loading/prefilling a large MoE model.
The 5090 and 3090 should fail quickly when genuinely unavailable, while the Spark may legitimately require a much longer first-byte/response-header timeout.
Ideally this could be declared per endpoint:
timeouts:
connect_ms: 250
first_byte_ms: 180000
rather than increasing the timeout globally for every backend.
- Keep the inference hot path lightweight
One aspect of the reference implementation that has worked particularly well is separating the control plane from the inference data plane.
Health checks, discovery, telemetry, model inspection, and GPU statistics happen asynchronously.
The request path reads previously validated state from memory, reserves capacity, and immediately connects to the selected upstream.
A similar architectural property in PAIR would be valuable as clusters and agent concurrency increase.
Why this complements existing PAIR work
This request intentionally does not duplicate several existing efforts.
#7 — External / heterogeneous scheduling hooks and richer placement signals
#7
#7 addresses how placement can become more intelligent.
This request addresses the declarative endpoint/capability layer that makes a heterogeneous externally managed runtime eligible for that scheduler in the first place.
PR #9 — Add vLLM as a third inference engine
#9
PR #9 is valuable and demonstrates that additional runtimes are useful.
The proposed generic external-engine contract would complement first-class vLLM support rather than replace it:
Managed vLLM
→ PAIR owns lifecycle and provides full integration
External vLLM
→ advanced operator owns lifecycle
→ PAIR only adopts/routs it
External SGLang
External llama.cpp
External future runtime
→ same generic contract
This would prevent PAIR from needing a bespoke lifecycle implementation for every inference server before that server can participate in routing.
Anthropic "/v1/messages" support is also already being discussed separately in #16, so this request intentionally does not make that protocol the focus.
Alternatives considered
Replace the optimized runtimes with Ollama or LM Studio
This simplifies cluster management, but different hardware may achieve materially better performance with different inference runtimes, model formats, speculative decoding strategies, KV-cache configurations, and quantizations.
Add every inference runtime individually to PAIR
This provides excellent first-class integrations but creates ongoing maintenance requirements for every new engine.
A generic OpenAI-compatible adoption contract would provide a common denominator while preserving richer integrations where available.
Continue running an independent router in front of PAIR
The current reference router already implements the required heterogeneous placement behavior.
However, doing so duplicates functionality PAIR already provides or is better positioned to provide:
- discovery
- trust/mTLS
- cluster membership
- workload visibility
- node availability
- failover
- model inventory
The ideal outcome would be to use PAIR as the cluster control plane and retire most of the custom router.
Route directly to individual inference servers
This loses the primary benefits of PAIR and forces clients to understand cluster topology.
Compatibility and security implications
The feature could be entirely opt-in.
Existing Ollama, LM Studio, vLLM, and other managed-engine behavior would remain unchanged.
Externally managed endpoints should preferably remain bound to loopback on their host, with PAIR retaining responsibility for trusted inter-node transport.
PAIR should not distribute local engine credentials to peers.
Capability metadata contains operational characteristics only and should contain no prompt or response content.
An external endpoint that becomes unhealthy should simply become ineligible for new routing.
Because it is explicitly marked externally managed, PAIR should never stop, restart, reconfigure, unload, or otherwise mutate that process unless the operator opts into such management.
Validation approach
A useful integration test would use three heterogeneous nodes.
Node A
Runtime: vLLM
Hardware: RTX 5090
Logical model: local-coding
Physical model: qwen3.8
Context: 262144
Vision: false
Tools: true
Capacity: 2
Priority: 10
Node B
Runtime: llama.cpp server
Hardware: RTX 3090
Logical model: local-coding
Physical model: qwen3.8
Context: 262144
Vision: false
Tools: true
Capacity: 1
Priority: 20
Node C
Runtime: arbitrary OpenAI-compatible VLM
Hardware: DGX Spark
Logical model: local-coding
Context: 262144
Vision: true
Tools: true
Capacity: 3
Priority: 30
Long first-token timeout
Validate that:
- All three can be registered without PAIR controlling their process lifecycle.
- One logical model is exposed to clients.
- Normal text requests prefer Node A.
- When Node A reaches configured capacity, routing spills to Node B.
- When A and B are unavailable/full, text can spill to C if allowed.
- Image requests exclude A and B before scheduling and route to C.
- Requests exceeding a node's declared context exclude that node before dispatch.
- Stopping one upstream removes it from eligibility without affecting healthy peers.
- Streaming remains streamed end-to-end.
- Long first-token behavior on C does not require increasing timeouts for A and B.
- No engine-local credential crosses the PAIR trust boundary.
- Existing clients and existing PAIR-managed engines behave exactly as before.
Reference implementation
A working implementation of most of the requested routing semantics is available here:
Router
https://github.com/darkmatter2222/HomeAILab/tree/main/router
Declarative heterogeneous endpoint configuration
https://github.com/darkmatter2222/HomeAILab/blob/main/router/config/router.yaml
Capability-aware deterministic request selection
https://github.com/darkmatter2222/HomeAILab/blob/main/router/internal/routing/classify.go
The project is not presented as the desired PAIR implementation. It is provided as a concrete example of the workload and routing requirements and as testable reference behavior.
If PAIR supported this external endpoint/capability contract, this custom routing layer could largely be replaced by PAIR while retaining the optimized inference runtime on each GPU.
Confirmations
Desired outcome
PAIR should support a generic externally managed OpenAI-compatible endpoint that can participate in cluster routing without requiring PAIR to own or manage that runtime’s lifecycle. An operator should be able to register an existing vLLM, SGLang, llama.cpp, TensorRT-LLM, or other compatible inference server and declare the information PAIR needs for safe routing, including logical model alias, upstream model name, text/vision capability, tool and streaming support, maximum context, configured concurrency/capacity, routing priority, health endpoint, and per-endpoint timeouts.
PAIR should use those declarations as eligibility constraints before scheduling. A vision request should never be sent to a text-only endpoint, a request requiring more context than an endpoint supports should exclude that endpoint, and an endpoint at its configured capacity should not receive additional work. For heterogeneous clusters, operators should also be able to define a deterministic baseline preference such as RTX 5090 → RTX 3090 → DGX Spark while still allowing PAIR to fail over when a preferred endpoint is unavailable or full.
Multiple physical deployments should also be able to advertise the same logical model even when they use different runtimes, quantizations, or upstream model identifiers, so clients only need to request a stable alias such as "local-coding".
The desired result is for PAIR to remain responsible for cluster-level capabilities such as discovery, trust/mTLS, routing, failover, model inventory, telemetry, and workload visibility, while advanced users retain full control over the inference runtime and optimization strategy on each machine. In a heterogeneous deployment like the referenced RTX 5090 + RTX 3090 + DGX Spark cluster, this would allow PAIR to replace most of the custom routing layer without sacrificing the tuned vLLM, llama.cpp, or specialized VLM backends already optimized for each device.
Alternatives considered
The primary alternative is to continue using the custom HomeAILab router I already built for this exact heterogeneous inference use case: https://github.com/darkmatter2222/HomeAILab/tree/main/router. That router already sits in front of my RTX 5090, RTX 3090, and DGX Spark and provides the behaviors I need today, including deterministic priority routing, static per-endpoint capacity, capability-aware eligibility, context-window filtering, vision/tool/streaming awareness, logical model aliasing, asynchronous health checks, direct streaming, endpoint draining, and failover across different inference runtimes. The declarative configuration is here: https://github.com/darkmatter2222/HomeAILab/blob/main/router/config/router.yaml, and the request classification and deterministic endpoint selection logic is here: https://github.com/darkmatter2222/HomeAILab/blob/main/router/internal/routing/classify.go. Continuing with that router is viable and gives me complete control over how different hardware and runtimes are used—for example, preferring a tuned vLLM deployment on the RTX 5090, then a llama.cpp deployment on the RTX 3090, and finally using the DGX Spark as overflow and for specialized VLM workloads. The downside is that maintaining a separate router means continuing to own functionality that PAIR already provides or is better positioned to provide, including node discovery, secure peer trust/mTLS, cluster membership, model inventory aggregation, workload visibility, node availability, failover, and the broader user experience around managing a distributed personal AI cluster. In other words, the custom router proves that these routing semantics are useful and practical, but it also highlights the duplication. The preferred direction would be to let PAIR absorb the cluster-control responsibilities while preserving the ability to attach tuned, externally managed inference runtimes with declarative capabilities, capacity, priority, and timeout characteristics. If PAIR could do that, most of the custom HomeAILab routing layer could be retired rather than maintained in parallel, while still preserving the performance and specialization benefits that motivated the custom router in the first place.
Compatibility and security implications
This should be implemented as an additive, opt-in capability so existing PAIR behavior for Ollama, LM Studio, vLLM, and other managed engines remains unchanged. Externally managed endpoints should participate in routing only when explicitly configured, and PAIR should treat them as inference capacity rather than assuming control over their process lifecycle. Ideally, those runtimes remain bound to loopback or another restricted local interface, with PAIR continuing to provide the trusted inter-node transport and mTLS boundary. Any credentials required by an external engine should remain local to the node hosting that engine and must not be propagated to peers, exposed through discovery, or written to logs. Declarative endpoint metadata such as model aliases, context limits, modality support, capacity, priority, health paths, and timeout characteristics contains operational information only and should never include prompt or response content. Logical model aliases should map only to explicitly configured upstream model identifiers so clients cannot use them to select arbitrary hosts or bypass normal PAIR eligibility and trust checks. If an externally managed endpoint becomes unhealthy or unavailable, it should simply become ineligible for new requests and normal PAIR failover behavior should apply. Because these endpoints are explicitly marked as externally managed, PAIR should not stop, restart, unload, reconfigure, or otherwise mutate the underlying inference server unless the operator separately opts into lifecycle management. Existing clients should continue to use the same PAIR APIs without modification, and deployments that do not configure external endpoints should see no behavioral or security change.
Validation approach
A practical validation approach would use a small heterogeneous three-node cluster that mirrors the reference HomeAILab deployment: an RTX 5090 running vLLM, an RTX 3090 running llama.cpp, and a DGX Spark running a specialized OpenAI-compatible VLM backend. Each endpoint should be registered as externally managed and given explicit metadata for logical model alias, physical model name, context limit, modality support, tool support, streaming support, static capacity, priority, health path, and timeout characteristics. Validation should first confirm that PAIR discovers and routes to all three endpoints without attempting to install, start, stop, unload, or otherwise manage their processes. A single logical model alias should then be exposed to clients even though the underlying runtimes and physical model identifiers differ. Normal text requests should prefer the highest-priority eligible endpoint, then spill to lower-priority nodes only when the preferred endpoint is unavailable or at its configured capacity. Vision requests should exclude text-only endpoints before scheduling, context requirements larger than a node's declared limit should exclude that node, and endpoints marked unhealthy, disabled, draining, or full should not receive new work. Streaming and tool-calling requests should be verified end-to-end to ensure PAIR does not alter protocol behavior. Per-endpoint timeout handling should also be tested by giving the DGX Spark a substantially longer first-token allowance while keeping shorter failure detection on the 5090 and 3090. Failover should be tested by stopping or saturating individual upstreams and confirming that requests move deterministically to the next eligible endpoint without disrupting healthy peers. Finally, validation should confirm that externally managed engine credentials and local runtime configuration never cross the PAIR trust boundary, are not exposed through discovery or telemetry, and are not written to logs. The reference implementation and current routing behavior used for comparison are available at https://github.com/darkmatter2222/HomeAILab/tree/main/router.
Confirmations
Area
Desktop experience
User problem
Area
Engine or model management
User problem
PAIR solves much of the difficult infrastructure required for distributed local inference: node discovery, trust, model inventory, routing, workload visibility, and failover.
The remaining limitation for heterogeneous performance-oriented clusters is that inference backends still need to be explicitly supported as PAIR engines.
In practice, different hardware often performs best with different inference runtimes and configurations. Requiring an operator to replace an already optimized backend with a PAIR-supported engine can negate much of the reason for building a heterogeneous cluster.
I have a working three-node example:
The router currently used for this cluster is open source here:
https://github.com/darkmatter2222/HomeAILab/tree/main/router
Its declarative endpoint configuration is here:
https://github.com/darkmatter2222/HomeAILab/blob/main/router/config/router.yaml
And the request classification / deterministic selection implementation is here:
https://github.com/darkmatter2222/HomeAILab/blob/main/router/internal/routing/classify.go
This is not intended as a competing implementation or a request for PAIR to copy this router. It is a working reference showing the behaviors that currently prevent this deployment from moving entirely to PAIR.
The core issue is broader than adding one more runtime.
PR #9 adds vLLM as another first-class engine, which is useful. But vLLM, SGLang, llama.cpp server, TensorRT-LLM gateways, custom inference servers, and future engines increasingly expose substantially the same OpenAI-compatible inference surface.
Ideally, every compatible runtime should not require a bespoke PAIR engine implementation before it can participate in the cluster.
There is also a heterogeneous routing problem.
Two nodes may expose the same logical model while having radically different:
A request should therefore be matched against endpoint capabilities before ordinary load-based scheduling occurs.
Real-world reference behavior
The router above currently models endpoints declaratively.
For example, the same logical coding workload can be served by:
RTX 5090
vLLM
Qwen3.8-27B NVFP4
262K context
priority 10
static capacity 1
RTX 3090
llama.cpp
Qwen3.8-27B Q4_K_M
262K context
priority 20
static capacity 1
DGX Spark
large Flash-Next / VLM backend
262K context
priority 30
static capacity 3
vision capable
long-prefill behavior
The router does not query GPU utilization, Prometheus, Docker, model inventory, or health synchronously for every inference request.
Those signals are maintained asynchronously.
The inference hot path approximately does:
classify request
↓
determine required capabilities
↓
determine required context
↓
read in-memory endpoint table
↓
reject incompatible endpoints
↓
sort eligible endpoints deterministically
↓
atomically reserve endpoint capacity
↓
stream directly to backend
Request classification currently includes:
Candidate exclusion includes:
Only after eligibility is established is endpoint preference evaluated.
This distinction is important in heterogeneous clusters: eligibility first, preference/load second.
Desired outcome
PAIR should support an externally managed / bring-your-own OpenAI-compatible engine.
An operator would be able to register an already-running inference endpoint without PAIR needing to understand or control that engine's process lifecycle.
At minimum, an external endpoint could declare:
endpoint:
api_family: openai
base_url: http://127.0.0.1:8201
model:
physical_name: qwen3.8
logical_alias: local-coding
capabilities:
text: true
vision: false
tools: true
streaming: true
context:
max_tokens: 262144
routing:
priority: 10
capacity: 1
health:
path: /health
PAIR would handle discovery, trust, routing, streaming, failover, and cluster visibility, while the external runtime remains under operator control.
Support registering an arbitrary OpenAI-compatible server.
Examples include:
PAIR would not need to:
It would simply adopt the endpoint as inference capacity.
First-class managed engines such as Ollama, LM Studio, and vLLM could continue to provide the richer lifecycle experience.
Allow multiple physically different deployments to advertise one logical model.
For example:
PAIR model: local-coding
5090:
upstream model = qwen3.8
3090:
upstream model = qwen3.8
Spark:
upstream model = another physical/model deployment
The client should not need to know runtime-specific model IDs or quantization names.
This is particularly useful when the same logical model is represented differently across Hugging Face, GGUF, TensorRT, or runtime-specific deployments.
Allow an endpoint/model combination to advertise optional characteristics such as:
These should act as eligibility constraints.
For example, an image request should never be routed to an endpoint declaring:
vision: false
even if that node currently has the lowest GPU utilization.
Similarly, a request requiring 180K context should not be dispatched to an endpoint characterized for only 128K.
Allow an operator to state how many simultaneous requests a particular deployment should accept.
For example:
5090 / vLLM capacity = 4
3090 / llama.cpp capacity = 1
Spark capacity = 3
The runtime may technically allow more requests, but the operator may have benchmarked a lower concurrency as the point that provides acceptable TTFT or throughput.
PAIR should be able to stop assigning work to that endpoint when its configured admission capacity is reached.
This is complementary to GPU-utilization/load signals rather than a replacement for them.
Allow an optional per-endpoint or per-model priority.
For example:
RTX 5090 priority 10
RTX 3090 priority 20
DGX Spark priority 30
Meaning:
use 5090 while eligible and capacity is available
↓
then 3090
↓
then Spark
Dynamic scheduling signals can still operate within or on top of this policy.
This is important when heterogeneous systems have very different performance characteristics.
An idle slower endpoint should not necessarily receive a request merely because it currently has lower GPU utilization than a substantially faster endpoint.
This is related to #7, which requests richer heterogeneous placement signals. The request here is specifically for the endpoint contract and declarative information necessary to express such a deployment.
Ideally PAIR would classify enough of the request to establish eligibility before scheduling.
At minimum:
requested model
vision/images?
tools?
streaming?
required context?
More advanced scheduling signals could be added independently.
A simple eligibility pipeline could be:
request
↓
model ownership / alias
↓
API compatibility
↓
modality
↓
tool support
↓
context requirement
↓
health
↓
configured capacity
↓
scheduler / preference
Different inference platforms can have dramatically different prefill behavior.
For example, the DGX Spark endpoint in the reference deployment can experience a very long cold TTFT while loading/prefilling a large MoE model.
The 5090 and 3090 should fail quickly when genuinely unavailable, while the Spark may legitimately require a much longer first-byte/response-header timeout.
Ideally this could be declared per endpoint:
timeouts:
connect_ms: 250
first_byte_ms: 180000
rather than increasing the timeout globally for every backend.
One aspect of the reference implementation that has worked particularly well is separating the control plane from the inference data plane.
Health checks, discovery, telemetry, model inspection, and GPU statistics happen asynchronously.
The request path reads previously validated state from memory, reserves capacity, and immediately connects to the selected upstream.
A similar architectural property in PAIR would be valuable as clusters and agent concurrency increase.
Why this complements existing PAIR work
This request intentionally does not duplicate several existing efforts.
#7 — External / heterogeneous scheduling hooks and richer placement signals
#7
#7 addresses how placement can become more intelligent.
This request addresses the declarative endpoint/capability layer that makes a heterogeneous externally managed runtime eligible for that scheduler in the first place.
PR #9 — Add vLLM as a third inference engine
#9
PR #9 is valuable and demonstrates that additional runtimes are useful.
The proposed generic external-engine contract would complement first-class vLLM support rather than replace it:
Managed vLLM
→ PAIR owns lifecycle and provides full integration
External vLLM
→ advanced operator owns lifecycle
→ PAIR only adopts/routs it
External SGLang
External llama.cpp
External future runtime
→ same generic contract
This would prevent PAIR from needing a bespoke lifecycle implementation for every inference server before that server can participate in routing.
Anthropic "/v1/messages" support is also already being discussed separately in #16, so this request intentionally does not make that protocol the focus.
Alternatives considered
Replace the optimized runtimes with Ollama or LM Studio
This simplifies cluster management, but different hardware may achieve materially better performance with different inference runtimes, model formats, speculative decoding strategies, KV-cache configurations, and quantizations.
Add every inference runtime individually to PAIR
This provides excellent first-class integrations but creates ongoing maintenance requirements for every new engine.
A generic OpenAI-compatible adoption contract would provide a common denominator while preserving richer integrations where available.
Continue running an independent router in front of PAIR
The current reference router already implements the required heterogeneous placement behavior.
However, doing so duplicates functionality PAIR already provides or is better positioned to provide:
The ideal outcome would be to use PAIR as the cluster control plane and retire most of the custom router.
Route directly to individual inference servers
This loses the primary benefits of PAIR and forces clients to understand cluster topology.
Compatibility and security implications
The feature could be entirely opt-in.
Existing Ollama, LM Studio, vLLM, and other managed-engine behavior would remain unchanged.
Externally managed endpoints should preferably remain bound to loopback on their host, with PAIR retaining responsibility for trusted inter-node transport.
PAIR should not distribute local engine credentials to peers.
Capability metadata contains operational characteristics only and should contain no prompt or response content.
An external endpoint that becomes unhealthy should simply become ineligible for new routing.
Because it is explicitly marked externally managed, PAIR should never stop, restart, reconfigure, unload, or otherwise mutate that process unless the operator opts into such management.
Validation approach
A useful integration test would use three heterogeneous nodes.
Node A
Runtime: vLLM
Hardware: RTX 5090
Logical model: local-coding
Physical model: qwen3.8
Context: 262144
Vision: false
Tools: true
Capacity: 2
Priority: 10
Node B
Runtime: llama.cpp server
Hardware: RTX 3090
Logical model: local-coding
Physical model: qwen3.8
Context: 262144
Vision: false
Tools: true
Capacity: 1
Priority: 20
Node C
Runtime: arbitrary OpenAI-compatible VLM
Hardware: DGX Spark
Logical model: local-coding
Context: 262144
Vision: true
Tools: true
Capacity: 3
Priority: 30
Long first-token timeout
Validate that:
Reference implementation
A working implementation of most of the requested routing semantics is available here:
Router
https://github.com/darkmatter2222/HomeAILab/tree/main/router
Declarative heterogeneous endpoint configuration
https://github.com/darkmatter2222/HomeAILab/blob/main/router/config/router.yaml
Capability-aware deterministic request selection
https://github.com/darkmatter2222/HomeAILab/blob/main/router/internal/routing/classify.go
The project is not presented as the desired PAIR implementation. It is provided as a concrete example of the workload and routing requirements and as testable reference behavior.
If PAIR supported this external endpoint/capability contract, this custom routing layer could largely be replaced by PAIR while retaining the optimized inference runtime on each GPU.
Confirmations
Desired outcome
PAIR should support a generic externally managed OpenAI-compatible endpoint that can participate in cluster routing without requiring PAIR to own or manage that runtime’s lifecycle. An operator should be able to register an existing vLLM, SGLang, llama.cpp, TensorRT-LLM, or other compatible inference server and declare the information PAIR needs for safe routing, including logical model alias, upstream model name, text/vision capability, tool and streaming support, maximum context, configured concurrency/capacity, routing priority, health endpoint, and per-endpoint timeouts.
PAIR should use those declarations as eligibility constraints before scheduling. A vision request should never be sent to a text-only endpoint, a request requiring more context than an endpoint supports should exclude that endpoint, and an endpoint at its configured capacity should not receive additional work. For heterogeneous clusters, operators should also be able to define a deterministic baseline preference such as RTX 5090 → RTX 3090 → DGX Spark while still allowing PAIR to fail over when a preferred endpoint is unavailable or full.
Multiple physical deployments should also be able to advertise the same logical model even when they use different runtimes, quantizations, or upstream model identifiers, so clients only need to request a stable alias such as "local-coding".
The desired result is for PAIR to remain responsible for cluster-level capabilities such as discovery, trust/mTLS, routing, failover, model inventory, telemetry, and workload visibility, while advanced users retain full control over the inference runtime and optimization strategy on each machine. In a heterogeneous deployment like the referenced RTX 5090 + RTX 3090 + DGX Spark cluster, this would allow PAIR to replace most of the custom routing layer without sacrificing the tuned vLLM, llama.cpp, or specialized VLM backends already optimized for each device.
Alternatives considered
The primary alternative is to continue using the custom HomeAILab router I already built for this exact heterogeneous inference use case: https://github.com/darkmatter2222/HomeAILab/tree/main/router. That router already sits in front of my RTX 5090, RTX 3090, and DGX Spark and provides the behaviors I need today, including deterministic priority routing, static per-endpoint capacity, capability-aware eligibility, context-window filtering, vision/tool/streaming awareness, logical model aliasing, asynchronous health checks, direct streaming, endpoint draining, and failover across different inference runtimes. The declarative configuration is here: https://github.com/darkmatter2222/HomeAILab/blob/main/router/config/router.yaml, and the request classification and deterministic endpoint selection logic is here: https://github.com/darkmatter2222/HomeAILab/blob/main/router/internal/routing/classify.go. Continuing with that router is viable and gives me complete control over how different hardware and runtimes are used—for example, preferring a tuned vLLM deployment on the RTX 5090, then a llama.cpp deployment on the RTX 3090, and finally using the DGX Spark as overflow and for specialized VLM workloads. The downside is that maintaining a separate router means continuing to own functionality that PAIR already provides or is better positioned to provide, including node discovery, secure peer trust/mTLS, cluster membership, model inventory aggregation, workload visibility, node availability, failover, and the broader user experience around managing a distributed personal AI cluster. In other words, the custom router proves that these routing semantics are useful and practical, but it also highlights the duplication. The preferred direction would be to let PAIR absorb the cluster-control responsibilities while preserving the ability to attach tuned, externally managed inference runtimes with declarative capabilities, capacity, priority, and timeout characteristics. If PAIR could do that, most of the custom HomeAILab routing layer could be retired rather than maintained in parallel, while still preserving the performance and specialization benefits that motivated the custom router in the first place.
Compatibility and security implications
This should be implemented as an additive, opt-in capability so existing PAIR behavior for Ollama, LM Studio, vLLM, and other managed engines remains unchanged. Externally managed endpoints should participate in routing only when explicitly configured, and PAIR should treat them as inference capacity rather than assuming control over their process lifecycle. Ideally, those runtimes remain bound to loopback or another restricted local interface, with PAIR continuing to provide the trusted inter-node transport and mTLS boundary. Any credentials required by an external engine should remain local to the node hosting that engine and must not be propagated to peers, exposed through discovery, or written to logs. Declarative endpoint metadata such as model aliases, context limits, modality support, capacity, priority, health paths, and timeout characteristics contains operational information only and should never include prompt or response content. Logical model aliases should map only to explicitly configured upstream model identifiers so clients cannot use them to select arbitrary hosts or bypass normal PAIR eligibility and trust checks. If an externally managed endpoint becomes unhealthy or unavailable, it should simply become ineligible for new requests and normal PAIR failover behavior should apply. Because these endpoints are explicitly marked as externally managed, PAIR should not stop, restart, unload, reconfigure, or otherwise mutate the underlying inference server unless the operator separately opts into lifecycle management. Existing clients should continue to use the same PAIR APIs without modification, and deployments that do not configure external endpoints should see no behavioral or security change.
Validation approach
A practical validation approach would use a small heterogeneous three-node cluster that mirrors the reference HomeAILab deployment: an RTX 5090 running vLLM, an RTX 3090 running llama.cpp, and a DGX Spark running a specialized OpenAI-compatible VLM backend. Each endpoint should be registered as externally managed and given explicit metadata for logical model alias, physical model name, context limit, modality support, tool support, streaming support, static capacity, priority, health path, and timeout characteristics. Validation should first confirm that PAIR discovers and routes to all three endpoints without attempting to install, start, stop, unload, or otherwise manage their processes. A single logical model alias should then be exposed to clients even though the underlying runtimes and physical model identifiers differ. Normal text requests should prefer the highest-priority eligible endpoint, then spill to lower-priority nodes only when the preferred endpoint is unavailable or at its configured capacity. Vision requests should exclude text-only endpoints before scheduling, context requirements larger than a node's declared limit should exclude that node, and endpoints marked unhealthy, disabled, draining, or full should not receive new work. Streaming and tool-calling requests should be verified end-to-end to ensure PAIR does not alter protocol behavior. Per-endpoint timeout handling should also be tested by giving the DGX Spark a substantially longer first-token allowance while keeping shorter failure detection on the 5090 and 3090. Failover should be tested by stopping or saturating individual upstreams and confirming that requests move deterministically to the next eligible endpoint without disrupting healthy peers. Finally, validation should confirm that externally managed engine credentials and local runtime configuration never cross the PAIR trust boundary, are not exposed through discovery or telemetry, and are not written to logs. The reference implementation and current routing behavior used for comparison are available at https://github.com/darkmatter2222/HomeAILab/tree/main/router.
Confirmations