Skip to content

Instantly share code, notes, and snippets.

@nerdalert
Last active July 28, 2026 01:42
Show Gist options
  • Select an option

  • Save nerdalert/40f337dd827c69ecc07a06a648db5792 to your computer and use it in GitHub Desktop.

Select an option

Save nerdalert/40f337dd827c69ecc07a06a648db5792 to your computer and use it in GitHub Desktop.

IPP and Praxis: A Comparative Analysis

A side-by-side analysis of the llm-d Inference Payload Processor (IPP) and Praxis, covering their architectures, design philosophies, integration models, and how they fit together in the broader llm-d and AI Grid ecosystems.


At a Glance

Identity and Positioning

IPP Praxis
Full name llm-d Inference Payload Processor Praxis Proxy
One-liner Pluggable payload processing service for inference routing High-performance proxy server and framework for AI and cloud-native workloads
Language Go Rust
Foundation Envoy ext_proc gRPC protocol Pingora (Cloudflare's production proxy runtime)
License Apache 2.0 (CNCF llm-d project) MIT
Scope Single-cluster, single-gateway payload decisions Single-cluster to multi-site grid routing
Deployment Standalone gRPC service, one per Gateway Standalone proxy binary, per-listener set
AI awareness AI-first: built for inference payload processing AI-capable: general proxy with AI features in a separate repo

Request Path

IPP Praxis
How it sees traffic Proxy streams events to IPP over gRPC Traffic flows through it natively
Body access Full body via ext_proc FULL_DUPLEX_STREAMED StreamBuffer: peek-then-stream, zero-copy forwarding
Extra network hops 2+ per request (to/from ext_proc per event) 0 (all in-process)
Body mutation Yes (rewrites JSON model field) Possible via buffer manipulation, not the common path
Config hot reload Restart required Atomic pipeline swap, zero downtime

What Each Decides

                                         ┌────────────────────────┐
  "Which model?"                         │   IPP ModelSelector    │
  cost, load, capability                 │   Filter → Score → Pick│
                                         └──────────┬─────────────┘
                                                     │
                                         ┌───────────▼─────────────┐
  "Which pool?"                          │   IPP header injection  │
  base-model mapping, HTTPRoute match    │   → Proxy routing       │
                                         └──────────┬──────────────┘
                                                     │
                                         ┌───────────▼──────────────┐
  "Which pod?"                           │   EPP / llmd_endpoint_   │
  KV cache, queue depth, prefix cache    │   picker (in Praxis)     │
                                         └──────────┬───────────────┘
                                                     │
                                         ┌───────────▼──────────────┐
  "Which site/provider?"                 │   AI Grid / grid_route   │
  locality, cost, capacity, policy       │   (Praxis + Grid Operator│
                                         └──────────────────────────┘

Extension Model

IPP Praxis
Write extensions in Go Rust
Registration Runtime via factory function + YAML type Compile-time via register_filters! macro
Extension interfaces 10+ interfaces (RequestProcessor, ResponseProcessor, Filter, Scorer, Picker, ProfilePicker, Extractor, Collector, DataSource, ResponseChunkProcessor, ResponseHeadersProcessor) 2 traits: HttpFilter (6 hooks) and TcpFilter (2 hooks)
Config reference Plugins declared once, referenced by name with pluginRef Filters declared in named chains, referenced by listener
Body access declaration Implicit via ext_proc event config Explicit via request_body_access() at construction

Key Capabilities Side by Side

Capability IPP Praxis
Payload Processing
Model name extraction from body
LoRA adapter-to-base-model mapping --
Model selection (Filter/Score/Pick) --
JSON-RPC parsing --
Response body mutation Possible
Profile-based traffic splitting -- (use branch chains)
Routing & Selection
Endpoint selection (queue/cache-aware) -- (defers to EPP)
Multi-site routing --
Provider API translation -- ✓ (planned)
Credential injection --
Proxy Infrastructure
TLS termination / mTLS Proxy¹
Load balancing (RR, P2C, consistent hash) Proxy¹
Circuit breaker with fallback Proxy¹
Rate limiting Proxy¹
Guardrails / content filtering ✓²
Operations
Cross-request data layer (async) Partial (KvStoreRegistry)
Dynamic config reload --
TCP/L4 support Proxy¹

¹ Handled by the proxy that IPP sits alongside (typically Envoy) ² IPP can do payload-level content filtering; broader guardrails handled by proxy

Integration Topologies

Current llm-d stack:

Client → Envoy → ext_proc → IPP → ext_proc → EPP → vLLM

Praxis native (primary track):

Client → Praxis → llmd_endpoint_picker (in-process) → vLLM

Praxis + IPP (hybrid):

Client → Praxis → ext_proc → IPP (model selection) → grid_route → Backend

AI Grid (full stack):

Client → Praxis AI Gateway → grid_route → credential_inject → Site/Provider/Model Server

1. Executive Summary

IPP and Praxis are not competing projects. They occupy different layers of the inference data plane and were designed with different constraints. IPP is a payload-aware sidecar service that inspects and mutates inference request and response bodies over Envoy's ext_proc protocol. Praxis is a high-performance proxy server and framework built on Pingora that processes all traffic through composable filter pipelines. When deployed together, IPP handles pool-level model routing decisions while Praxis (or another proxy) handles the actual traffic forwarding, TLS, load balancing, and the broader gateway capabilities.

The key question is not "which one wins" but "where does each one's responsibility begin and end, and what changes when Praxis absorbs IPP-style capabilities natively."

Dimension IPP Praxis
What it is Pluggable payload processing service Proxy server and framework
Language Go Rust
Deployment Standalone gRPC service, one per Gateway Standalone proxy binary, per-listener
Protocol Envoy ext_proc (gRPC) Native HTTP/TCP via Pingora
Primary job Inspect/mutate request/response payloads Route, forward, and secure all traffic
Body access Full request/response bodies via ext_proc streaming StreamBuffer (peek-then-stream) with zero-copy forwarding
Extension model Go plugins via YAML config Rust filters compiled into the binary
Scope Single-cluster, single-gateway payload decisions Single-cluster to multi-site grid routing

2. Origins and Design Philosophy

IPP: Opinion-Free Framework, Opinionated Plugins

IPP was born from a specific need in the llm-d stack: the Envoy proxy that fronts inference pools cannot read request bodies to make routing decisions. Someone needs to parse the OpenAI-compatible JSON, extract the model field, and inject a header so Envoy's HTTPRoute rules can select the right InferencePool. IPP fills that gap.

Its design principles reflect this heritage:

  • The framework is opinion-free; plugins hold the opinions. The core defines the pipeline and extension points. All concrete behavior lives in swappable plugins.
  • Explicit state boundaries. Per-request state is shared through an in-memory "cycle state" for one request's lifetime. Cross-request state lives in a separate data layer.
  • Pluggable everything. Request processing, response processing, model selection, profile selection, and data collection are all swappable via YAML configuration.

Praxis: Filter-First Proxy Framework

Praxis was designed as a general-purpose proxy that happens to be excellent at AI workloads, not as an AI-specific tool. Its design principles are:

  • Fast. Performance is a primary design goal. Built on Pingora (Cloudflare's production proxy runtime), Praxis inherits connection pooling, zero-copy forwarding, and efficient memory management.
  • Secure by default. unsafe_code = "deny" across the workspace, Rustls (no OpenSSL), SSRF protection on health checks, localhost-default listener binds.
  • Composable. Every behavior is a filter. Routing, load balancing, rate limiting, guardrails, body-based routing: all filters, all using the same HttpFilter/TcpFilter traits, all assembled into pipelines through named chains.
  • Extensible. Custom filters implement the same traits as built-in filters and are registered with a single macro.

Summary: Philosophy Differences

Principle IPP Praxis
Core identity Payload processing framework Proxy server framework
Extension mechanism Runtime plugin loading via YAML Compile-time filter registration via Rust traits
Configuration PayloadProcessorConfig (YAML, Kubernetes-style) YAML config with filter chains and listeners
Default posture Does nothing until plugins are configured Ships with 25+ built-in filters covering routing, security, observability
Performance priority Correctness and extensibility first Performance is a primary design constraint
Security model Delegates to proxy and Kubernetes RBAC Security-first with fail-closed defaults

3. Architecture Deep Dive

3.1 IPP Architecture

IPP implements the Envoy External Processing (ext_proc) gRPC API. The proxy streams HTTP lifecycle events to IPP, which replies with mutations.

Client
  --> Proxy (Envoy)
        --> ext_proc gRPC stream --> IPP
                                      --> Plugin Pipeline
                                      <-- Mutations (headers, body edits)
        <-- Apply mutations
  --> Backend (InferencePool / model server)

Processing Pipeline:

PreProcessor Plugins --> ProfilePicker --> Profile Request Plugins
    --> [Model Server] --> Profile Response Plugins --> PostProcessor Plugins

Key concepts:

  • Profile: A named set of request and response plugins. Exactly one profile runs per request, chosen by a profile picker. This enables different processing logic for different traffic classes.
  • ModelSelector: A three-phase Filter -> Score -> Pick pipeline that selects which model serves a request. Filters remove ineligible candidates, scorers rank the rest, and a picker makes the final choice.
  • Data Layer: Background collectors, extractors, and datasources maintain cross-request state (in-flight counts, model configs, cost metadata) that scorers and filters consume without adding latency to the request path.

3.2 Praxis Architecture

Praxis processes traffic natively through Pingora's HTTP/TCP runtime. No external service call is needed for any routing or processing decision.

Client
  --> Praxis Listener (TCP accept, TLS termination)
        --> Filter Pipeline (flat Vec<PipelineFilter>)
              F0 --> F1 --> F2 --> ... --> FN --> Upstream
              FN <-- ... <-- F1 <-- F0 <-- Upstream (response, reverse order)
        --> Upstream (via connection pool)
  --> Backend

Key concepts:

  • Filter Chains: Named, reusable groups of filters defined in YAML. A listener references one or more chains; at startup they are flattened into a single pipeline.
  • Pipeline: The runtime execution list. Filters run forward on request, reverse on response. Any filter can short-circuit.
  • StreamBuffer: Praxis's differentiated body access mode. Chunks are delivered to filters as they arrive (streaming inspection) but not forwarded upstream until a filter releases the buffer. This enables body-based routing with zero post-decision latency.
  • Branch Chains: Conditional branching based on filter results. A filter writes key-value results; branch conditions can skip forward, loop back, terminate, or reject.
  • Dynamic Reload: Pipelines are atomically swapped at runtime via ArcSwap. Config file changes rebuild all pipelines; in-flight requests drain on the old pipeline.

3.3 Architecture Comparison

Aspect IPP Praxis
Execution model Out-of-process gRPC service In-process filter pipeline
Request flow Proxy -> gRPC -> IPP -> gRPC -> Proxy -> Backend Client -> Pipeline -> Backend (single process)
Network hops per request 2+ extra (to/from ext_proc per enabled event) 0 extra (all in-process)
Body access Full body via FULL_DUPLEX_STREAMED ext_proc mode StreamBuffer (peek-then-stream, zero-copy)
Latency overhead gRPC serialization + network hop per event In-process function calls
State model CycleState (per-request) + DataLayer (cross-request) HttpFilterContext (per-request) + KvStoreRegistry (cross-request)
Pipeline shape Linear with profile branching Linear with conditional branch chains
Hot reload Restart required for config changes Atomic pipeline swap, zero downtime
Response processing Separate response plugin phase Reverse-order response filter phase
Streaming responses ResponseChunkProcessor for per-chunk processing on_response_body hook (synchronous, reverse order)
Protocol support HTTP only (via ext_proc) HTTP + TCP + TLS natively

4. Plugin and Filter Systems

4.1 IPP Plugin Taxonomy

IPP organizes plugins into clear categories based on the framework interface they implement:

Category Interface When Executed Examples
Request Handling RequestProcessor Before routing body-field-to-header, base-model-to-header, model-selector
Response Handling ResponseProcessor / ResponseHeadersProcessor / ResponseChunkProcessor After model server reply model-name-to-header
Model Selector - Filter Filter First phase of ModelSelector model-group-name-filter
Model Selector - Scorer Scorer Second phase, weighted combination cost-scorer, inflight-requests-scorer
Model Selector - Picker Picker Final selection max-score-picker, random-picker, weighted-random-picker
Profile Picker ProfilePicker Before profile execution single-profile-picker
Data Layer - Collector Collector Background timer (none in-tree yet)
Data Layer - Extractor Extractor Background event-driven request-metadata-extractor
Data Layer - Datasource DataSource Background watcher model-config-datasource

Plugins are declared once in the plugins list and referenced by name elsewhere. The same type can be instantiated multiple times with different parameters.

4.2 Praxis Built-in Filters

Praxis ships with 25+ built-in filters organized by function:

Category Filters
Observability access_log, request_id
Payload Processing compression, json_body_field, json_rpc
Security cors, credential_injection, csrf, forwarded_headers, guardrails, ip_acl, policy (CPEX)
Traffic Management circuit_breaker, endpoint_selector, grpc_detection, load_balancer, rate_limit, redirect, router, static_response, timeout
Transformation header, path_rewrite, url_rewrite
TCP tcp_access_log, sni_router, tcp_load_balancer
External ext_proc (Envoy-compatible, opt-in crate)

All filters implement the same HttpFilter trait. Custom filters use the same trait and register with register_filters!.

4.3 Extension Model Comparison

Dimension IPP Praxis
Language Go Rust
Registration Factory function registered at startup register_filters! macro at compile time
Discovery Runtime via YAML type field Compile-time via filter registry
Parameters Opaque YAML/JSON passed to factory Typed YAML deserialized via serde
Lifecycle Instantiated per-config-load Instantiated per-pipeline-build
Trait surface Multiple interfaces (RequestProcessor, ResponseProcessor, Filter, Scorer, Picker, etc.) Two traits: HttpFilter (6 hooks) and TcpFilter (2 hooks)
Body access declaration Implicit via ext_proc event config Explicit via request_body_access() / response_body_access()
Custom plugin effort Implement Go interface, register factory, configure in YAML Implement Rust trait, register macro, configure in YAML

5. Payload Processing Capabilities

Both systems exist because traditional proxies route on headers and paths but cannot read the JSON body to extract model names, estimate complexity, or classify task type. This section compares how each solves the payload problem.

5.1 IPP: ext_proc Body Streaming

IPP receives the full request body via ext_proc's FULL_DUPLEX_STREAMED mode. The proxy streams body chunks to IPP over gRPC; IPP's plugins inspect and potentially mutate the body, then return the result.

Strengths:

  • Full body access with mutation capability (can rewrite the model field in the JSON body)
  • Response body processing with the same mechanism
  • Profile-based routing of different processing pipelines

Trade-offs:

  • Every body inspection is a network round-trip
  • gRPC serialization/deserialization overhead on the hot path
  • Body must be fully available before plugins can make decisions (no partial-parse optimization)

5.2 Praxis: StreamBuffer (Peek-Then-Stream)

Praxis's StreamBuffer mode delivers body chunks to filters as they arrive but defers upstream forwarding until a filter returns Release. This enables body inspection before the routing decision with zero post-decision latency.

Strengths:

  • In-process, zero network overhead
  • Chunks inspected as they arrive (streaming inspection)
  • Once released, remaining chunks flow through in stream mode (no full-body buffering required)
  • Pre-computed at pipeline build time, so the protocol layer knows exactly what to expect

Trade-offs:

  • Body mutation is more complex (buffer must be modified before release)
  • Read-only access is the common path; mutation requires explicit buffer manipulation
  • Rust implementation means higher barrier to extending body processing

5.3 Feature Comparison: Body-Based Routing

Capability IPP Praxis
Extract JSON field to header body-field-to-header plugin json_body_field filter
Model name extraction body-field-to-header + base-model-to-header json_body_field (model to X-Model header)
LoRA adapter mapping base-model-to-header with ConfigMap reconciler Not built-in (would be a custom filter)
JSON-RPC parsing Not built-in json_rpc filter (method, id, kind, batch to headers)
Body-based model selection model-selector (Filter -> Score -> Pick) Not built-in as a framework; individual scoring logic in custom filters
Streaming field detection body-field-to-header for stream boolean json_body_field for any top-level field
Body mutation Yes (can rewrite model field) Possible via StreamBuffer but not the common path

6. Model Selection and Routing

6.1 IPP: The ModelSelector Framework

IPP's flagship capability is structured model selection through a three-phase pipeline:

Filter --> Score --> Pick
  • Filters remove ineligible models (e.g., model-group-name-filter restricts by group name or explicit model name)
  • Scorers assign each candidate a score in [0, 1] with configurable weights (e.g., cost-scorer ranks by price, inflight-requests-scorer ranks by current load)
  • Pickers make the final selection (e.g., max-score-picker for deterministic best, weighted-random-picker for exploration)

This is a model-level decision: which model should serve this request. The selected model name is written back into the request body's model field, and routing continues as if the client had requested that model directly.

6.2 Praxis: Filter Pipeline Routing

Praxis does not have a dedicated ModelSelector framework. Instead, routing decisions compose from individual filters in the pipeline:

  1. Signal Extraction: json_body_field extracts model name to header; json_rpc parses JSON-RPC envelopes
  2. Router: Matches path, host, and headers against configured routes; sets ctx.cluster
  3. Load Balancer: Selects an endpoint within the cluster using round-robin, least-connections, P2C, or consistent-hash
  4. Branch Chains: Conditional branching based on filter results enables fallback routing

For llm-d integration specifically, the llmd_endpoint_picker filter (behind the ai-inference feature flag) embeds the EPP scheduling pipeline directly:

Parse OpenAI body --> Model-aware filter --> Load-aware scorer --> Pick endpoint --> Set ctx.upstream

6.3 Routing Scope Comparison

Question IPP Answers Praxis Answers
Which model serves this request? Yes (ModelSelector framework) Partially (json_body_field + router, or custom filter)
Which pool serves this request? Yes (header injection for HTTPRoute matching) Yes (router filter + cluster selection)
Which endpoint/pod within a pool? No (defers to EPP) Yes (load_balancer filter, or llmd_endpoint_picker)
Which site/region serves this request? No (single-cluster only) Yes (via AI Grid's grid_route filter)
Which provider serves this request? No Yes (via AI Grid's backend_scorer + credential_injection)

7. The llm-d Integration

7.1 IPP in the llm-d Stack

IPP sits between the proxy (Envoy) and the EPP (Endpoint Picker Provider) in the current llm-d architecture:

Client -> Envoy -> ext_proc -> IPP (pool selection)
                -> ext_proc -> EPP (endpoint selection)
                -> Model Server (vLLM, SGLang, etc.)

IPP decides which pool. The EPP decides which pod within the pool. They are deployed and configured independently. IPP's base-model-to-header plugin maps LoRA adapter names back to base models, enabling multi-pool routing behind a single OpenAI-compatible endpoint.

7.2 Praxis in the llm-d Stack

Praxis's llm-d integration takes two forms:

Native EPP (primary track): The llmd_endpoint_picker filter embeds EPP-style scheduling directly in the Praxis process. No ext_proc hop is needed. Praxis parses the OpenAI body, filters by model eligibility, scores by queue depth and KV-cache utilization, and sets ctx.upstream to the selected endpoint.

Client -> Praxis -> llmd_endpoint_picker (in-process)
                 -> Model Server (vLLM, llm-d-inference-sim)

ext_proc compatibility (back-pocket track): Praxis replaces Envoy as the proxy but still calls the existing Go EPP via ext_proc. This addresses Envoy's ext_proc connection-limit problem while keeping Go EPP behavior unchanged.

Client -> Praxis -> ext_proc -> Go EPP (endpoint selection)
                 -> Model Server

7.3 Integration Comparison

Aspect IPP + Envoy (current llm-d) Praxis Native EPP Praxis + ext_proc
Proxy Envoy Praxis/Pingora Praxis/Pingora
Pool selection IPP (ext_proc) Praxis filter pipeline IPP or custom ext_proc service
Endpoint selection EPP (ext_proc) llmd_endpoint_picker filter Go EPP (ext_proc)
Network hops 2 ext_proc round-trips 0 (all in-process) 1 ext_proc round-trip
Body parsing IPP parses JSON, injects headers Praxis parses JSON natively Same as IPP path
Metrics source EPP scrapes vLLM Prometheus Praxis background worker scrapes vLLM EPP scrapes vLLM
KV cache awareness EPP (block-level) Praxis (utilization-level, prefix-cache planned) EPP (block-level)
Kubernetes discovery EPP watches InferencePool Praxis discovery worker (planned) EPP watches InferencePool
Config hot reload Restart required Atomic pipeline swap Restart for ext_proc config

8. AI Grid Integration

8.1 IPP: Single-Cluster Scope

IPP is designed for single-cluster, single-gateway deployments. It has no awareness of multi-site topology, cross-cluster routing, or distributed state. Its job is payload processing within one gateway's filter chain.

8.2 Praxis: The AI Grid Data Plane

Praxis serves as the data plane for AI Grid, which is a distributed peer-to-peer network of inference and agentic services. The architecture splits into three layers:

Layer Responsibility Technology
Praxis core Generic proxy runtime, filter pipeline, TLS, connection pooling Rust/Pingora
Praxis AI (praxis-proxy/ai) AI-specific filters: grid_route, grid_credential_inject, llm-d ext_proc, API translation Rust, compiled into praxis-ai-proxy binary
Grid Operator (praxis-proxy/grid) Control plane: SWIM membership, CRDT state propagation, provider inventory, overlay generation Rust, Kubernetes operator

The AI Grid routing pipeline through Praxis follows a multi-stage design:

Stage Filter Purpose
1. Signal Extraction json_body_field, json_rpc Extract model name, streaming flag, tool calls from body
2. Semantic Classification semantic_classifier (planned) Classify request by task type using embedding model
3. Backend Scoring backend_scorer (planned) Score all eligible backends by metrics, cost, locality
4. Policy Enforcement rate_limit, guardrails, budget_enforcer, sovereignty_filter Apply business rules
5. API Translation api_translator (planned) Rewrite request/response for target provider format
6. Credential Injection credential_injection, grid_credential_inject Inject provider-specific auth
7. Resilience circuit_breaker + branch chains Automatic failover to next-best backend

8.3 Grid Integration: Where IPP Fits

IPP can participate in the AI Grid architecture as a standalone filter within the Praxis AI pipeline. The integration model uses Praxis's ext_proc filter to call IPP as an external processing service:

Client -> Praxis AI Gateway
            -> ext_proc -> IPP (model selection / LoRA mapping)
            -> grid_route (multi-site routing)
            -> credential_injection (provider auth)
            -> Backend (local llm-d, remote site, or cloud provider)

In this model, IPP provides model-level selection (choosing between models based on cost, load, and capability) while Praxis handles site-level routing, policy enforcement, and provider abstraction.

However, the Praxis native path is the strategic direction. The AI Grid proposal's routing pipeline embeds all the capabilities IPP provides today as native Praxis filters:

  • json_body_field replaces body-field-to-header
  • json_rpc replaces JSON-RPC parsing
  • backend_scorer replaces the cost-scorer + inflight-requests-scorer combination
  • grid_route replaces pool-level header injection with multi-site-aware routing

The strategic question is whether IPP's pluggable ModelSelector framework (Filter -> Score -> Pick with weighted combination) provides enough value to justify the ext_proc hop, or whether equivalent logic should be implemented as native Praxis filters.


9. Deployment and Operations

Aspect IPP Praxis
Binary Single Go binary Single Rust binary
Deployment unit Kubernetes Deployment + Service Standalone binary or Kubernetes Deployment
Per-gateway ratio One IPP per Gateway One Praxis instance per listener set
Packaging Helm chart with provider-specific integration (Istio/GKE/none) YAML config file; Helm/OLM planned for Grid
Config format PayloadProcessorConfig (Kubernetes-style YAML) Praxis YAML (listeners, filter_chains, clusters)
Config reload Restart required Atomic hot reload via ArcSwap
Health checks gRPC health port (9005) HTTP /ready and /healthy on admin listener
Metrics Prometheus on port 9090 Prometheus on admin listener /metrics
Tracing OpenTelemetry (configurable) tracing crate with structured logging
TLS Self-signed cert for ext_proc gRPC Rustls with SNI, mTLS, cert hot-reload, CRL
Resource footprint Go runtime + gRPC server Rust binary, no GC, lower memory baseline
Kubernetes integration ConfigMap watching, controller-runtime, RBAC Static config (primary), Kubernetes discovery (planned)

10. When to Use Which

Use IPP When:

  • You are running the standard llm-d stack with Envoy and need multi-pool routing
  • You need model-level selection (choosing between models, not just endpoints) with the structured Filter -> Score -> Pick pipeline
  • You need LoRA adapter-to-base-model mapping with runtime ConfigMap reconciliation
  • You want to write custom Go plugins without modifying the proxy binary
  • You need response body processing with per-chunk streaming access
  • Your deployment is single-cluster and the ext_proc overhead is acceptable

Use Praxis When:

  • You need a complete proxy solution (TLS, routing, load balancing, security, observability)
  • You need multi-site or multi-provider routing (AI Grid)
  • You want zero ext_proc overhead on the request path
  • You need hot config reload without restarts
  • You need native llm-d endpoint picking with in-process model-aware, load-aware, KV-cache-aware scheduling
  • You need TCP/L4 support alongside HTTP
  • You need branch chain conditional logic (fallback routing, circuit breaker recovery paths)
  • You are building a custom proxy using the Praxis framework

Use Both When:

  • You want IPP's ModelSelector framework for model-level decisions while Praxis handles site/provider-level routing and all proxy concerns
  • You are migrating from Envoy and want to keep IPP as a stable ext_proc service while Praxis replaces Envoy as the data plane
  • You need independent scaling of payload processing and proxy concerns

11. Convergence and Future Direction

The two projects are converging. Praxis's native EPP filter already provides a subset of what IPP does (model extraction, load-aware scoring, endpoint selection), and the AI Grid roadmap calls for native Praxis filters that cover the remaining IPP capabilities.

What Praxis Already Does Natively That Overlaps with IPP

IPP Capability Praxis Equivalent Status
body-field-to-header (model extraction) json_body_field filter Shipped
base-model-to-header (LoRA mapping) No direct equivalent Gap
JSON-RPC parsing json_rpc filter Shipped
Queue depth scoring llmd_endpoint_picker (queue + KV cache) POC
Cost-aware scoring backend_scorer (planned in AI Grid) Planned
Inflight requests scoring llmd_endpoint_picker POC
Model group filtering Router filter with header matching Indirect
ConfigMap-based model config Static YAML config / Kubernetes discovery (planned) Partial
Response header injection header filter Shipped

What IPP Does That Praxis Does Not (Yet)

  • Structured ModelSelector framework (Filter -> Score -> Pick with weighted combination)
  • LoRA adapter-to-base-model mapping with runtime ConfigMap reconciliation
  • Profile-based traffic splitting (different processing pipelines for different request classes)
  • Data Layer with event-driven extractors (asynchronous cross-request state management)
  • Response body mutation (rewriting response JSON before returning to client)

Strategic Direction

The llm-d integration work has established a clear architectural boundary:

  • llm-d/EPP level: Endpoint selection within a pool (which pod, considering KV cache, queue depth, prefix cache). This is where the llmd_endpoint_picker filter or Go EPP operates.
  • IPP level: Model/pool selection (which model, considering cost, load, capabilities). This is IPP's current domain.
  • AI Grid level: Site/provider selection (which site, which provider, considering locality, cost, capacity, policy, sovereignty). This is Praxis + Grid Operator territory.

The native Praxis path is the primary track. IPP remains a viable integration option through ext_proc, but the long-term direction is absorbing the useful IPP scheduling patterns into native Praxis filters where they execute in-process with zero network overhead.


12. Summary Table

Dimension IPP Praxis
Identity Payload processing sidecar Proxy server and framework
Language Go Rust
Runtime gRPC server (ext_proc) Pingora HTTP/TCP runtime
Latency overhead gRPC round-trip per event Zero (in-process)
Body access Full body via ext_proc streaming StreamBuffer (peek-then-stream)
Model selection Structured Filter -> Score -> Pick Individual filters (or custom)
Endpoint selection Defers to EPP Native llmd_endpoint_picker
Multi-site routing No Yes (AI Grid)
Provider abstraction No Yes (API translation, credential injection)
TLS Self-signed for gRPC Full TLS: SNI, mTLS, CRL, hot-reload
Hot reload Restart Atomic pipeline swap
Extension language Go Rust
Kubernetes native Yes (controller-runtime, ConfigMap watching) Config file primary; Kubernetes discovery planned
CNCF alignment Part of llm-d (CNCF Sandbox) Independent project, upstream Kubernetes alignment
Maturity Production-ready for multi-pool routing Core proxy production-ready; AI features in active development
Grid integration Standalone ext_proc service via Praxis ext_proc filter Native data plane for AI Grid
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment