GPU Tests / Check GPU Availability (push) Successful in 1s
CI / Build (ubuntu-latest) (push) Failing after 6s
CI / Build (macos-latest) (push) Failing after 11s
Documentation / Build User Guide (push) Successful in 7s
CI / Clippy Check (push) Failing after 21s
CI / Format Check (push) Failing after 6s
GPU Tests / CUDA Tests (11.8) (push) Has been skipped
GPU Tests / CUDA Tests (12.1) (push) Has been skipped
CI / Build CPU-Only (Explicit) (push) Failing after 6s
GPU Tests / Metal Tests (push) Has been skipped
CI / Test (macos-latest) (push) Has been skipped
CI / Test (ubuntu-latest) (push) Has been skipped
CI / Python Bindings (maturin) (macos-latest) (push) Has been skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Has been skipped
CI / WASM Build + Size Check (push) Has been skipped
CI / Distributed Training Tests (push) Has been skipped
CI / CI Success (push) Failing after 0s
Performance Benchmarks / Run Benchmarks (push) Successful in 28s
Documentation / Build API Documentation (push) Failing after 25s
EAGLE (rtx-inference/src/eagle.rs, ~610 lines, mirrors medusa.rs conventions): EagleDraftHead autoregressive FFN with Concat/Add/ Attention feature fusion, EagleHeads draft model with draft/ draft_steps (per-step top-k for candidate trees) and teacher-forced training_loss; implements the speculative::EagleDraftModel trait so it plugs into the orchestration layer. 38 unit tests. Tokenizer (rtx-inference/src/tokenizer.rs): ServingTokenizer enum — Vocab (HuggingFace tokenizers, loadable from tokenizer.json) or ByteLevel fallback preserving previous behavior. rtx-serving-api's AppState and rtx-streaming's token generator now encode/decode through it (with_engine_and_tokenizer / set_tokenizer added; existing signatures unchanged). Also fixes two pre-existing compile errors in rtx-streaming (missing import, stray .await) that blocked its lib tests entirely. Tests: rtx-inference 328 pass, rtx-serving-api 193 pass, rtx-streaming 53 pass (2 pre-existing mock-server connection failures unrelated to these changes). Co-Authored-By: Claude Fable 5 <[email protected]>
149 lines
4.5 KiB
Rust
149 lines
4.5 KiB
Rust
//! HTTP server configuration and startup
|
|
|
|
use axum::{
|
|
Router,
|
|
routing::{get, post},
|
|
};
|
|
use std::time::Duration;
|
|
use tokio::net::TcpListener;
|
|
use tower_http::{cors::CorsLayer, timeout::TimeoutLayer, trace::TraceLayer};
|
|
|
|
use crate::{ApiError, ApiResult, health, inference, models};
|
|
|
|
/// Server configuration
|
|
#[derive(Debug, Clone)]
|
|
pub struct ServerConfig {
|
|
/// Listen address
|
|
pub host: String,
|
|
/// Listen port
|
|
pub port: u16,
|
|
/// Request timeout
|
|
pub timeout_seconds: u64,
|
|
}
|
|
|
|
impl Default for ServerConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
host: "127.0.0.1".to_string(),
|
|
port: 8080,
|
|
timeout_seconds: 30,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Serving server
|
|
pub struct ServingServer {
|
|
config: ServerConfig,
|
|
state: inference::AppState,
|
|
}
|
|
|
|
impl ServingServer {
|
|
/// Create a new serving server (no engine loaded; inference returns 503)
|
|
#[must_use]
|
|
pub fn new(config: ServerConfig) -> Self {
|
|
Self {
|
|
config,
|
|
state: inference::AppState::default(),
|
|
}
|
|
}
|
|
|
|
/// Create a serving server backed by a live inference engine.
|
|
///
|
|
/// Uses byte-level tokenization by default; use
|
|
/// [`ServingServer::with_engine_and_tokenizer`] to attach a real
|
|
/// vocabulary tokenizer.
|
|
#[must_use]
|
|
pub fn with_engine(
|
|
config: ServerConfig,
|
|
engine: std::sync::Arc<tokio::sync::RwLock<rtx_inference::InferenceEngine>>,
|
|
) -> Self {
|
|
Self::with_engine_and_tokenizer(config, engine, rtx_inference::ServingTokenizer::default())
|
|
}
|
|
|
|
/// Create a serving server backed by a live inference engine and an
|
|
/// explicit tokenizer (e.g. loaded via
|
|
/// `rtx_inference::ServingTokenizer::from_file`).
|
|
#[must_use]
|
|
pub fn with_engine_and_tokenizer(
|
|
config: ServerConfig,
|
|
engine: std::sync::Arc<tokio::sync::RwLock<rtx_inference::InferenceEngine>>,
|
|
tokenizer: rtx_inference::ServingTokenizer,
|
|
) -> Self {
|
|
Self {
|
|
config,
|
|
state: inference::AppState {
|
|
engine: Some(engine),
|
|
tokenizer,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Build the Axum router with all routes
|
|
pub fn app(&self) -> Router {
|
|
Router::new()
|
|
// Health endpoints
|
|
.route("/health", get(health::health_check))
|
|
.route("/health/ready", get(health::health_check))
|
|
.route("/health/live", get(health::health_check))
|
|
// Model endpoints
|
|
.route("/v1/models", get(models::list_models))
|
|
// Inference endpoints
|
|
.route("/v1/completions", post(inference::inference))
|
|
.route("/v1/chat/completions", post(inference::inference))
|
|
// Enhanced cached inference endpoints (temporarily disabled)
|
|
// .route("/v1/cached/completions", post(inference_cached::cached_inference))
|
|
// .route("/v1/cached/chat/completions", post(inference_cached::cached_inference))
|
|
// Cache management endpoints (temporarily disabled)
|
|
// .route("/v1/cache/stats", get(inference_cached::cache_stats))
|
|
// .route("/v1/cache/clear", post(inference_cached::clear_cache))
|
|
// .route("/v1/cache/warm", post(inference_cached::warm_cache))
|
|
// Middleware
|
|
.layer(CorsLayer::permissive())
|
|
.layer(TraceLayer::new_for_http())
|
|
.layer(TimeoutLayer::new(Duration::from_secs(
|
|
self.config.timeout_seconds,
|
|
)))
|
|
.with_state(self.state.clone())
|
|
}
|
|
|
|
/// Start the server
|
|
pub async fn serve(&self) -> ApiResult<()> {
|
|
let app = self.app();
|
|
|
|
let addr = format!("{}:{}", self.config.host, self.config.port);
|
|
let listener = TcpListener::bind(&addr)
|
|
.await
|
|
.map_err(|e| ApiError::internal(format!("Failed to bind to {addr}: {e}")))?;
|
|
|
|
tracing::info!("Server listening on {}", addr);
|
|
|
|
axum::serve(listener, app)
|
|
.await
|
|
.map_err(|e| ApiError::internal(format!("Server error: {e}")))?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_server_config_default() {
|
|
let config = ServerConfig::default();
|
|
assert_eq!(config.host, "127.0.0.1");
|
|
assert_eq!(config.port, 8080);
|
|
assert_eq!(config.timeout_seconds, 30);
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_creation() {
|
|
let config = ServerConfig::default();
|
|
let server = ServingServer::new(config);
|
|
|
|
// Should be able to build the app router
|
|
let _app = server.app();
|
|
}
|
|
}
|