use std::collections::{HashMap, VecDeque};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use anyhow::{Context, Result};
use axum::Json;
use axum::extract::State;
use axum::response::IntoResponse;
use axum::response::sse::{Event, Sse};
use axum::routing::{get, post};
use inferencelayer::encoder_weights::{EncBatch, PosKind};
use inferencelayer::serve::chat::{self, ChatTurn, ToolCall};
use inferencelayer::serve::error::ApiError;
use inferencelayer::serve::stop;
use inferencelayer::serve::tools;
use inferencelayer::serve::types::{
ChatReq, CommonParams, CompletionReq, Usage, created_epoch, response_id,
};
use inferencelayer::weights::Arch;
use inferencelayer::{
EmbedEngine, EmbedOut, FinishReason, GpuCtx, Lfm2Gpu, RequestParams, SamplingParams, Scheduler,
ServeStats, Weights, pooling,
};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc as amp;
const EMBED_MAX_TOKENS: usize = 8192;
#[derive(Clone, Copy, PartialEq, Eq)]
enum RespKind {
Completion,
Chat,
}
impl RespKind {
fn object(self) -> &'static str {
match self {
RespKind::Completion => "text_completion",
RespKind::Chat => "chat.completion",
}
}
fn chunk_object(self) -> &'static str {
match self {
RespKind::Completion => "text_completion.chunk",
RespKind::Chat => "chat.completion.chunk",
}
}
fn id_prefix(self) -> &'static str {
match self {
RespKind::Completion => "cmpl",
RespKind::Chat => "chatcmpl",
}
}
}
#[derive(Clone)]
struct Finish {
reason: &'static str,
stop_reason: Option<serde_json::Value>,
}
impl Finish {
fn from_scheduler(fr: FinishReason) -> Self {
match fr {
FinishReason::Eos => Finish {
reason: "stop",
stop_reason: None,
},
FinishReason::StopTokenId(id) => Finish {
reason: "stop",
stop_reason: Some(serde_json::json!(id)),
},
FinishReason::Length => Finish {
reason: "length",
stop_reason: None,
},
}
}
fn from_stop_string(which: &str) -> Self {
Finish {
reason: "stop",
stop_reason: Some(serde_json::json!(which)),
}
}
}
#[allow(dead_code)]
fn fail(r: &EngineRequest, msg: &str) {
let _ = r.reply.send(EngineEvent::Rejected(msg.to_string()));
}
static VISION_US: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static VISION_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static VISION_PRE_US: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static EMIT_US: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
enum Tower {
Qwen(inferencelayer::vision_gpu::VisionGpu),
Glm(
inferencelayer::vision_glm::GlmVisionTower,
Option<inferencelayer::vision_glm_gpu::GlmVisionGpu>,
),
}
struct VisionCtx {
tower: Tower,
#[cfg(feature = "cudarc")]
cuda: Option<inferencelayer::cuda_tower::CudaGlmTower>,
image_token_id: u32,
vctx: Option<GpuCtx>,
}
impl VisionCtx {
fn load(ctx: &GpuCtx, dir: &std::path::Path) -> Option<Self> {
let cfg: serde_json::Value =
serde_json::from_slice(&std::fs::read(dir.join("config.json")).ok()?).ok()?;
let image_token_id = cfg.get("image_token_id")?.as_u64()? as u32;
let arch = cfg
.get("architectures")
.and_then(|a| a.get(0))
.and_then(|x| x.as_str())
.unwrap_or("");
let vctx = if std::env::var("OSFKB_VISION_DEVICE").as_deref() == Ok("2") {
match GpuCtx::new() {
Ok(c) => {
eprintln!("vision tower: own device/queue (OSFKB_VISION_DEVICE=2)");
Some(c)
}
Err(e) => {
eprintln!("vision tower: no second device ({e}); sharing the engine queue");
None
}
}
} else {
None
};
let tctx = vctx.as_ref().unwrap_or(ctx);
if arch == "GlmOcrForConditionalGeneration" {
let tower = inferencelayer::vision_glm::GlmVisionTower::load(dir).ok()?;
#[cfg(feature = "cudarc")]
let cuda = if std::env::var("OSFKB_CUDA_TOWER").as_deref() == Ok("1") {
match inferencelayer::cuda_tower::CudaGlmTower::new(&tower) {
Ok(c) => {
eprintln!("GLM vision tower: CUDA arm active (tensor cores)");
Some(c)
}
Err(e) => {
eprintln!("GLM vision tower: CUDA arm unavailable ({e:#}); using wgpu");
None
}
}
} else {
None
};
let gpu = if std::env::var("OSFKB_GLM_VISION_GPU").as_deref() == Ok("0") {
None
} else {
match inferencelayer::vision_glm_gpu::GlmVisionGpu::new(tctx, &tower) {
Ok(g) => Some(g),
Err(e) => {
eprintln!("GLM vision tower: GPU build failed ({e}); using the CPU tower");
None
}
}
};
eprintln!(
"GLM vision tower loaded (image_token_id={image_token_id}, {}) — images accepted",
if gpu.is_some() { "GPU" } else { "CPU" }
);
return Some(Self {
tower: Tower::Glm(tower, gpu),
#[cfg(feature = "cudarc")]
cuda,
image_token_id,
vctx,
});
}
let cpu = inferencelayer::vision::VisionTower::load(dir).ok()?;
let tower = inferencelayer::vision_gpu::VisionGpu::new(tctx, cpu).ok()?;
eprintln!("vision tower loaded (image_token_id={image_token_id}) — images accepted");
Some(Self {
#[cfg(feature = "cudarc")]
cuda: None,
tower: Tower::Qwen(tower),
image_token_id,
vctx,
})
}
fn prepare(
&self,
engine_ctx: &GpuCtx,
prompt_ids: &[u32],
images: &[Vec<u8>],
) -> anyhow::Result<(Vec<u32>, inferencelayer::server::VisionPrompt)> {
let ctx = self.vctx.as_ref().unwrap_or(engine_ctx);
let cfg = match &self.tower {
Tower::Qwen(t) => t.config().clone(),
Tower::Glm(t, _) => t.cfg.clone(),
};
let cfg = &cfg;
let pads = prompt_ids
.iter()
.filter(|&&t| t == self.image_token_id)
.count();
anyhow::ensure!(
pads == images.len(),
"prompt has {pads} image placeholder(s) but {} image(s) were sent",
images.len()
);
let _t_pre = std::time::Instant::now();
let patches: Vec<_> = images
.iter()
.map(|b| match &self.tower {
Tower::Qwen(_) => inferencelayer::vision::preprocess_bytes(b, cfg),
Tower::Glm(..) => inferencelayer::vision_glm::glm_preprocess_bytes(b, cfg),
})
.collect::<anyhow::Result<_>>()?;
VISION_PRE_US.fetch_add(
_t_pre.elapsed().as_micros() as u64,
std::sync::atomic::Ordering::Relaxed,
);
let mut expanded = Vec::with_capacity(
prompt_ids.len() + patches.iter().map(|p| p.num_tokens(cfg)).sum::<usize>(),
);
let mut next = 0usize;
for &t in prompt_ids {
if t == self.image_token_id {
let n = patches[next].num_tokens(cfg);
expanded.extend(std::iter::repeat_n(self.image_token_id, n));
next += 1;
} else {
expanded.push(t);
}
}
let vp = match &self.tower {
Tower::Qwen(t) => inferencelayer::vision::prepare_prompt_gpu(
ctx,
t,
&expanded,
self.image_token_id,
&patches,
)?,
Tower::Glm(t, gpu) => {
#[cfg(feature = "cudarc")]
if let Some(c) = &self.cuda {
let vp = inferencelayer::vision_glm::glm_prepare_prompt_cuda(
c,
&t.cfg,
&expanded,
self.image_token_id,
&patches,
)?;
return Ok((expanded, vp));
}
match gpu {
Some(g) => inferencelayer::vision_glm::glm_prepare_prompt_gpu(
ctx,
g,
&expanded,
self.image_token_id,
&patches,
)?,
None => inferencelayer::vision_glm::glm_prepare_prompt(
t,
&expanded,
self.image_token_id,
&patches,
)?,
}
}
};
Ok((expanded, vp))
}
}
struct EngineRequest {
prompt_ids: Vec<u32>,
vprompt: Option<inferencelayer::server::VisionPrompt>,
max_tokens: usize,
n: usize,
params: RequestParams,
stop_strings: Vec<String>,
reply: amp::UnboundedSender<EngineEvent>,
}
enum EngineEvent {
Delta {
choice: usize,
text: String,
},
Done {
choice: usize,
text: String,
completion_tokens: usize,
prompt_tokens: usize,
cached_tokens: usize,
finish: Finish,
logprobs: Vec<TokenLp>,
},
Error(String),
Rejected(String),
}
enum EmbedJob {
Embed(EmbedReq),
Rerank(RerankReq),
}
struct EmbedReq {
texts: Vec<String>,
dimensions: Option<usize>,
reply: tokio::sync::oneshot::Sender<Result<EmbedDone, String>>,
}
struct EmbedDone {
vectors: Vec<Vec<f32>>,
prompt_tokens: usize,
}
struct RerankReq {
query: String,
documents: Vec<String>,
top_n: Option<usize>,
reply: tokio::sync::oneshot::Sender<Result<RerankDone, String>>,
}
struct RerankDone {
ranked: Vec<(usize, f32)>,
prompt_tokens: usize,
}
#[derive(Clone)]
struct GenState {
submit: std::sync::mpsc::Sender<EngineRequest>,
tokenizer: Arc<tokenizers::Tokenizer>,
arch: Arch,
model_name: String,
gctx: Arc<GpuCtx>,
vision: Option<Arc<VisionCtx>>,
vision_gate: Arc<tokio::sync::Semaphore>,
}
#[derive(Clone)]
struct EmbedState {
submit: std::sync::mpsc::Sender<EmbedJob>,
model_name: String,
dimension: usize,
is_per_token: bool,
}
#[derive(Clone)]
struct AppState {
generation: Option<GenState>,
embed: Option<EmbedState>,
stats: Arc<Mutex<(ServeStats, usize, usize)>>,
}
fn main() -> Result<()> {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
default_hook(info);
std::process::abort();
}));
if std::env::var_os("OSFKB_SPIN_POLL").is_none() {
unsafe { std::env::set_var("OSFKB_SPIN_POLL", "1") };
}
let mut model = None;
let mut embed_model = None;
let mut port = 8210u16;
let mut batch = 8usize;
let mut embed_batch = 32usize;
let mut vram_gb: Option<f64> = None;
let mut kv_fraction = 0.35f64;
let mut max_batched: Option<usize> = None;
let mut served_name: Option<String> = None;
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
match a.as_str() {
"--model" => model = args.next().map(PathBuf::from),
"--embed-model" => embed_model = args.next().map(PathBuf::from),
"--port" => port = args.next().context("--port value")?.parse()?,
"--batch" => batch = args.next().context("--batch value")?.parse()?,
"--embed-batch" => embed_batch = args.next().context("--embed-batch value")?.parse()?,
"--vram-gb" => vram_gb = Some(args.next().context("--vram-gb value")?.parse()?),
"--kv-fraction" => kv_fraction = args.next().context("--kv-fraction value")?.parse()?,
"--max-batched-tokens" => {
max_batched = Some(args.next().context("--max-batched-tokens value")?.parse()?)
}
"--served-model-name" => {
served_name = Some(args.next().context("--served-model-name value")?)
}
other => anyhow::bail!("unknown argument {other}"),
}
}
anyhow::ensure!(
kv_fraction > 0.0 && kv_fraction <= 1.0,
"--kv-fraction must be in (0, 1]"
);
anyhow::ensure!(
model.is_some() || embed_model.is_some(),
"at least one of --model <dir> / --embed-model <dir> is required"
);
let stats: Arc<Mutex<(ServeStats, usize, usize)>> = Arc::new(Mutex::new((ServeStats::default(), 0, 0)));
let embed = match embed_model {
Some(dir) => Some(spawn_embed_engine(dir, embed_batch)?),
None => None,
};
let generation = match model {
Some(model) => Some(spawn_gen_engine(
model,
served_name,
batch,
max_batched,
vram_gb,
kv_fraction,
stats.clone(),
)?),
None => None,
};
let app_state = AppState {
generation,
embed,
stats,
};
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
rt.block_on(async move {
let app = axum::Router::new()
.route("/v1/completions", post(completions))
.route("/v1/chat/completions", post(chat_completions))
.route("/v1/embeddings", post(embeddings))
.route("/v1/rerank", post(rerank))
.route("/v1/models", get(models))
.route("/health", get(health))
.route("/metrics", get(metrics))
.with_state(app_state);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = tokio::net::TcpListener::bind(addr).await?;
eprintln!("lfm2-serve listening on {addr}");
axum::serve(listener, app).await?;
Ok(())
})
}
fn spawn_embed_engine(dir: PathBuf, embed_batch: usize) -> Result<EmbedState> {
let mut engine = EmbedEngine::auto(&dir, EMBED_MAX_TOKENS)?;
eprintln!("embed backend: {}", engine.device());
let cfg = engine.config();
let dimension = cfg.hidden;
let is_per_token = matches!(
cfg.pooling,
inferencelayer::pooling::Pooling::PerToken { .. }
);
let pos_offset = match cfg.pos_kind {
PosKind::Learned { offset } => offset,
PosKind::Rope { .. } => 0,
};
let max_seq = cfg.max_pos - pos_offset;
let tok = inferencelayer::load_encoder_tokenizer(&dir, max_seq)
.map_err(|e| anyhow::anyhow!("embed tokenizer: {e}"))?;
let model_name = dir
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "embed-model".into());
let (tx, rx) = std::sync::mpsc::channel::<EmbedJob>();
std::thread::spawn(move || {
loop {
let first = match rx.recv() {
Ok(j) => j,
Err(_) => return,
};
let mut jobs = vec![first];
while jobs.len() < embed_batch {
match rx.try_recv() {
Ok(j) => jobs.push(j),
Err(_) => break,
}
}
let mut embeds = Vec::new();
for j in jobs {
match j {
EmbedJob::Embed(req) => embeds.push(req),
EmbedJob::Rerank(req) => run_rerank(&mut engine, &tok, max_seq, req),
}
}
if !embeds.is_empty() {
run_embed_batch(&mut engine, &tok, max_seq, embeds);
}
}
});
Ok(EmbedState {
submit: tx,
model_name,
dimension,
is_per_token,
})
}
fn run_embed_batch(
engine: &mut EmbedEngine,
tok: &tokenizers::Tokenizer,
max_seq: usize,
jobs: Vec<EmbedReq>,
) {
let texts: Vec<String> = jobs.iter().flat_map(|j| j.texts.iter().cloned()).collect();
let encodings = match tok.encode_batch(texts, true) {
Ok(e) => e,
Err(e) => {
for j in jobs {
let _ = j.reply.send(Err(format!("tokenize: {e}")));
}
return;
}
};
let mut id_lists: Vec<Vec<u32>> = Vec::with_capacity(encodings.len());
for enc in &encodings {
let mut ids = enc.get_ids().to_vec();
if ids.len() > max_seq {
eprintln!(
"embed: truncating a {}-token input to the model's {max_seq}-token window",
ids.len()
);
ids.truncate(max_seq);
}
id_lists.push(ids);
}
let mut vectors: Vec<Option<Vec<f32>>> = vec![None; id_lists.len()];
let mut chunk: Vec<usize> = Vec::new();
let mut chunk_tokens = 0usize;
let mut failed = false;
let flush = |chunk: &mut Vec<usize>,
chunk_tokens: &mut usize,
vectors: &mut Vec<Option<Vec<f32>>>,
engine: &mut EmbedEngine,
id_lists: &[Vec<u32>]|
-> bool {
if chunk.is_empty() {
return true;
}
let batch = EncBatch::from_seqs(chunk.iter().map(|&i| id_lists[i].clone()));
let ok = match engine.encode(&batch) {
Ok(EmbedOut::Pooled(vs)) => {
for (&i, v) in chunk.iter().zip(vs) {
vectors[i] = Some(v);
}
true
}
Ok(EmbedOut::PerToken(_)) => false,
Err(e) => {
eprintln!("embed: encode failed: {e:#}");
false
}
};
chunk.clear();
*chunk_tokens = 0;
ok
};
for (i, ids) in id_lists.iter().enumerate() {
if ids.is_empty() {
continue;
}
if chunk_tokens + ids.len() > EMBED_MAX_TOKENS
&& !flush(
&mut chunk,
&mut chunk_tokens,
&mut vectors,
engine,
&id_lists,
)
{
failed = true;
break;
}
chunk.push(i);
chunk_tokens += ids.len();
}
if !failed {
failed = !flush(
&mut chunk,
&mut chunk_tokens,
&mut vectors,
engine,
&id_lists,
);
}
let mut cursor = 0usize;
for j in jobs {
let n = j.texts.len();
let span = cursor..cursor + n;
cursor += n;
if failed {
let _ = j.reply.send(Err("embedding engine failed".to_string()));
continue;
}
let mut out = Vec::with_capacity(n);
let mut prompt_tokens = 0usize;
let mut err: Option<String> = None;
for i in span {
prompt_tokens += id_lists[i].len();
match &vectors[i] {
Some(v) => out.push(match j.dimensions {
Some(d) => pooling::matryoshka_truncate(v, d),
None => v.clone(),
}),
None => {
err = Some("input tokenized to zero tokens".to_string());
break;
}
}
}
let _ = match err {
Some(e) => j.reply.send(Err(e)),
None => j.reply.send(Ok(EmbedDone {
vectors: out,
prompt_tokens,
})),
};
}
}
fn run_rerank(
engine: &mut EmbedEngine,
tok: &tokenizers::Tokenizer,
max_seq: usize,
req: RerankReq,
) {
let mut texts = Vec::with_capacity(1 + req.documents.len());
texts.push(req.query.clone());
texts.extend(req.documents.iter().cloned());
let encodings = match tok.encode_batch(texts, true) {
Ok(e) => e,
Err(e) => {
let _ = req.reply.send(Err(format!("tokenize: {e}")));
return;
}
};
let id_lists: Vec<Vec<u32>> = encodings
.iter()
.map(|e| {
let mut ids = e.get_ids().to_vec();
ids.truncate(max_seq);
ids
})
.collect();
let prompt_tokens: usize = id_lists.iter().map(Vec::len).sum();
let n = id_lists.len();
let mut pooled: Vec<Vec<f32>> = Vec::with_capacity(n);
let mut per_token: Vec<Vec<Vec<f32>>> = Vec::with_capacity(n);
let mut i = 0;
while i < n {
let mut j = i;
let mut toks = 0usize;
while j < n && (j == i || toks + id_lists[j].len() <= EMBED_MAX_TOKENS) {
toks += id_lists[j].len();
j += 1;
}
let batch = EncBatch::from_seqs(id_lists[i..j].iter().cloned());
match engine.encode(&batch) {
Ok(EmbedOut::Pooled(vs)) => pooled.extend(vs),
Ok(EmbedOut::PerToken(ss)) => per_token.extend(ss),
Err(e) => {
let _ = req.reply.send(Err(format!("encode: {e:#}")));
return;
}
}
i = j;
}
let ndocs = req.documents.len();
let mut ranked: Vec<(usize, f32)> = if !per_token.is_empty() {
let q = &per_token[0];
(0..ndocs)
.map(|d| (d, pooling::maxsim(q, &per_token[d + 1])))
.collect()
} else {
let q = &pooled[0];
(0..ndocs)
.map(|d| {
let doc = &pooled[d + 1];
let s = q.iter().zip(doc).map(|(a, b)| a * b).sum::<f32>();
(d, s)
})
.collect()
};
ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
if let Some(k) = req.top_n {
ranked.truncate(k);
}
let _ = req.reply.send(Ok(RerankDone {
ranked,
prompt_tokens,
}));
}
fn spawn_gen_engine(
model: PathBuf,
served_name: Option<String>,
batch: usize,
max_batched: Option<usize>,
vram_gb: Option<f64>,
kv_fraction: f64,
stats: Arc<Mutex<(ServeStats, usize, usize)>>,
) -> Result<GenState> {
let tokenizer = tokenizers::Tokenizer::from_file(model.join("tokenizer.json"))
.map_err(|e| anyhow::anyhow!("tokenizer: {e}"))?;
let eos = read_eos_ids(&model, &tokenizer);
let gguf_model = std::fs::read_dir(&model).ok().and_then(|rd| {
rd.filter_map(|e| e.ok().map(|e| e.path()))
.find(|p| p.extension().is_some_and(|x| x == "gguf"))
});
if gguf_model.is_some() {
unsafe { std::env::set_var("OSFKB_Q1_WEIGHTS", "1") };
if std::env::var("OSFKB_DN_SLOTS").is_err() {
unsafe { std::env::set_var("OSFKB_DN_SLOTS", "18") };
}
}
eprintln!("eos ids: {eos:?}");
let ctx = Arc::new(GpuCtx::new()?);
eprintln!("backend: {}", ctx.backend);
let w = match &gguf_model {
Some(g) => {
eprintln!("loading Q1 GGUF: {}", g.display());
inferencelayer::gguf::load_bonsai(&ctx, g)?
}
None => Weights::load(&ctx, &model)?,
};
let arch = w.cfg.arch;
let gpu = match vram_gb {
Some(gb) => {
let opts = inferencelayer::EngineOpts::from_vram(
&w.cfg,
(gb * 1024.0 * 1024.0 * 1024.0) as u64,
kv_fraction,
);
eprintln!(
"kv pool: {} tokens ({} blocks) from {gb} GB × {kv_fraction} ({} B/token)",
opts.pool_tokens,
opts.pool_tokens / 16,
inferencelayer::EngineOpts::kv_bytes_per_token(&w.cfg),
);
Lfm2Gpu::new_with_opts(&ctx, w, opts)
}
None => Lfm2Gpu::new(&ctx, w),
};
let model_name = served_name.unwrap_or_else(|| {
model
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "model".into())
});
let (submit_tx, submit_rx) = std::sync::mpsc::channel::<EngineRequest>();
let stats_engine = stats;
let tok_engine = tokenizer.clone();
let vision = VisionCtx::load(&ctx, &model).map(Arc::new);
let vision_handlers = vision.clone();
let ctx_handlers = ctx.clone();
std::thread::spawn(move || {
let wide = max_batched.unwrap_or_else(|| inferencelayer::server::default_wide_budget(&ctx));
let mut sched = Scheduler::new_with_wide(&gpu, &ctx, batch, wide, eos);
let mut live: HashMap<u64, Live> = HashMap::new();
loop {
if sched.pending() == 0 && live.is_empty() {
match submit_rx.recv() {
Ok(r) => admit(&mut sched, &mut live, r),
Err(_) => return,
}
}
while let Ok(r) = submit_rx.try_recv() {
admit(&mut sched, &mut live, r);
}
let ems = match sched.step(&gpu, &ctx) {
Ok(e) => e,
Err(e) => {
for (_, l) in live.drain() {
let _ = l.reply.send(EngineEvent::Error(format!("engine: {e}")));
}
continue;
}
};
let _t_emit = std::time::Instant::now();
for (id, cached) in sched.drain_admissions() {
if let Some(l) = live.get_mut(&id) {
l.cached_tokens = cached;
}
}
for e in ems {
let Some(l) = live.get_mut(&e.id) else {
continue;
};
l.ids.push(e.token);
if let Some(lp) = &e.logprobs {
let token = decode_piece(&tok_engine, lp.chosen);
let top = lp
.top
.iter()
.map(|(id, v)| (decode_piece(&tok_engine, *id), *v))
.collect();
l.logprobs.push(TokenLp {
token,
logprob: lp.chosen_logprob,
top,
});
}
let full = tok_engine.decode(&l.ids, true).unwrap_or_default();
let (visible_end, finish) =
if let Some((pos, which)) = stop::first_stop(&full, &l.stop_strings) {
(pos, Some(Finish::from_stop_string(&which)))
} else if let Some(fr) = e.finish {
(full.len(), Some(Finish::from_scheduler(fr)))
} else {
(stop::safe_stream_end(&full, &l.stop_strings), None)
};
let visible = &full[..visible_end];
if visible.len() > l.sent.len() && visible.starts_with(&l.sent) {
let delta = visible[l.sent.len()..].to_string();
if l.reply
.send(EngineEvent::Delta {
choice: l.choice,
text: delta,
})
.is_err()
{
sched.cancel(e.id);
live.remove(&e.id);
continue;
}
l.sent = visible.to_string();
}
if let Some(finish) = finish {
let stopped_early = finish.reason == "stop"
&& matches!(&finish.stop_reason, Some(v) if v.is_string());
let l = live.remove(&e.id).expect("live entry");
let _ = l.reply.send(EngineEvent::Done {
choice: l.choice,
text: visible.to_string(),
completion_tokens: l.ids.len(),
prompt_tokens: l.prompt_tokens,
cached_tokens: l.cached_tokens,
finish,
logprobs: l.logprobs,
});
if stopped_early {
sched.cancel(e.id);
}
}
}
let mut st = stats_engine.lock().expect("stats lock");
*st = (sched.stats.clone(), sched.cache_entries(), sched.kv_free_blocks());
drop(st);
EMIT_US.fetch_add(
_t_emit.elapsed().as_micros() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
fn admit(sched: &mut Scheduler, live: &mut HashMap<u64, Live>, r: EngineRequest) {
let (prompt_ids, vprompt) = (r.prompt_ids.clone(), r.vprompt.clone());
let prompt_tokens = prompt_ids.len();
for i in 0..r.n {
let mut params = r.params.clone();
params.sampling.seed = params.sampling.seed.wrapping_add(i as u64);
let submitted = match &vprompt {
Some(v) => sched.submit_with_vision(
prompt_ids.clone(),
r.max_tokens.max(1),
params,
v.clone(),
),
None => {
sched.submit_with_params(prompt_ids.clone(), r.max_tokens.max(1), params)
}
};
match submitted {
Ok(id) => {
live.insert(
id,
Live {
reply: r.reply.clone(),
choice: i,
ids: Vec::new(),
sent: String::new(),
prompt_tokens,
cached_tokens: 0,
stop_strings: r.stop_strings.clone(),
logprobs: Vec::new(),
},
);
}
Err(e) => {
let _ = r.reply.send(EngineEvent::Rejected(e.to_string()));
}
}
}
}
});
Ok(GenState {
submit: submit_tx,
tokenizer: Arc::new(tokenizer),
arch,
model_name,
gctx: ctx_handlers,
vision: vision_handlers,
vision_gate: Arc::new(tokio::sync::Semaphore::new(
std::env::var("OSFKB_VISION_CONCURRENCY")
.ok()
.and_then(|v| v.parse().ok())
.filter(|&n: &usize| n > 0)
.unwrap_or(2),
)),
})
}
struct Live {
reply: amp::UnboundedSender<EngineEvent>,
choice: usize,
ids: Vec<u32>,
sent: String,
prompt_tokens: usize,
cached_tokens: usize,
stop_strings: Vec<String>,
logprobs: Vec<TokenLp>,
}
fn read_eos_ids(model: &std::path::Path, tok: &tokenizers::Tokenizer) -> Vec<u32> {
let mut eos = Vec::new();
if let Ok(cfg) = std::fs::read_to_string(model.join("config.json"))
&& let Ok(v) = serde_json::from_str::<serde_json::Value>(&cfg)
{
for spot in [&v["eos_token_id"], &v["text_config"]["eos_token_id"]] {
match spot {
serde_json::Value::Number(n) => eos.extend(n.as_u64().map(|x| x as u32)),
serde_json::Value::Array(a) => {
eos.extend(a.iter().filter_map(|x| x.as_u64()).map(|x| x as u32));
}
_ => {}
}
}
eos.dedup();
}
for t in ["<|im_end|>", "<end_of_turn>"] {
if let Some(id) = tok.token_to_id(t)
&& !eos.contains(&id)
{
eos.push(id);
}
}
eos
}
fn require_gen(st: &AppState) -> Result<GenState, ApiError> {
st.generation.clone().ok_or_else(|| {
ApiError::invalid_request("no generation model loaded (start with --model <dir>)")
})
}
fn check_model(model: &Option<String>, generation: &GenState) -> Result<(), ApiError> {
match model {
Some(m) if m != &generation.model_name => Err(ApiError::not_found(format!(
"model `{m}` not found (this server serves `{}`)",
generation.model_name
))
.with_param("model")
.with_code("model_not_found")),
_ => Ok(()),
}
}
fn build_request_params(c: &CommonParams) -> RequestParams {
RequestParams {
sampling: SamplingParams {
temperature: c.temperature,
top_p: c.top_p,
seed: c.seed,
},
stop_token_ids: c.stop_token_ids.clone(),
presence_penalty: c.presence_penalty,
frequency_penalty: c.frequency_penalty,
repetition_penalty: c.repetition_penalty,
logit_bias: c.logit_bias.clone(),
top_k: c.top_k,
min_p: c.min_p,
logprobs: c.logprobs,
}
}
#[derive(Clone)]
struct TokenLp {
token: String,
logprob: f32,
top: Vec<(String, f32)>,
}
fn decode_piece(tok: &tokenizers::Tokenizer, id: u32) -> String {
tok.decode(&[id], false).unwrap_or_default()
}
fn logprobs_json(kind: RespKind, lps: &[TokenLp]) -> serde_json::Value {
if lps.is_empty() {
return serde_json::Value::Null;
}
let top_obj = |lp: &TokenLp| -> Vec<serde_json::Value> {
lp.top
.iter()
.map(|(t, v)| serde_json::json!({"token": t, "logprob": v}))
.collect()
};
match kind {
RespKind::Chat => {
let content: Vec<serde_json::Value> = lps
.iter()
.map(|lp| {
serde_json::json!({
"token": lp.token,
"logprob": lp.logprob,
"top_logprobs": top_obj(lp),
})
})
.collect();
serde_json::json!({ "content": content })
}
RespKind::Completion => serde_json::json!({
"tokens": lps.iter().map(|lp| lp.token.clone()).collect::<Vec<_>>(),
"token_logprobs": lps.iter().map(|lp| lp.logprob).collect::<Vec<_>>(),
"top_logprobs": lps
.iter()
.map(|lp| lp.top.iter().map(|(t, v)| (t.clone(), *v)).collect::<std::collections::BTreeMap<_, _>>())
.collect::<Vec<_>>(),
}),
}
}
async fn completions(
State(st): State<AppState>,
Json(req): Json<CompletionReq>,
) -> axum::response::Response {
let generation = match require_gen(&st) {
Ok(g) => g,
Err(e) => return e.into_response(),
};
if let Err(e) = check_model(&req.model, &generation) {
return e.into_response();
}
let common = match req.common() {
Ok(c) => c,
Err(e) => return e.into_response(),
};
let prompt = match req.prompt_text() {
Ok(p) => p,
Err(e) => return e.into_response(),
};
let ids = match generation.tokenizer.encode(prompt, true) {
Ok(e) => e.get_ids().to_vec(),
Err(e) => return ApiError::invalid_request(format!("tokenize: {e}")).into_response(),
};
let images = match decode_images(&req.images) {
Ok(v) => v,
Err(e) => return e.into_response(),
};
run_with_images(generation, ids, images, common, RespKind::Completion, false).await
}
fn decode_images(b64s: &[String]) -> Result<Vec<Vec<u8>>, ApiError> {
b64s.iter()
.map(|s| {
let payload = match s.split_once(";base64,") {
Some((_, tail)) => tail,
None => s.as_str(),
};
inferencelayer::serve::b64::decode(payload.trim()).map_err(|e| {
ApiError::invalid_request(format!("image base64: {e}")).with_param("images")
})
})
.collect()
}
async fn chat_completions(
State(st): State<AppState>,
Json(req): Json<ChatReq>,
) -> axum::response::Response {
let generation = match require_gen(&st) {
Ok(g) => g,
Err(e) => return e.into_response(),
};
if let Err(e) = check_model(&req.model, &generation) {
return e.into_response();
}
let common = match req.common() {
Ok(c) => c,
Err(e) => return e.into_response(),
};
let tool_defs = match req.tools_to_render() {
Ok(t) => t,
Err(e) => return e.into_response(),
};
if common.stream && !tool_defs.is_empty() {
return ApiError::invalid_request(
"streaming is not supported together with tools — send stream:false for tool calls",
)
.with_param("stream")
.into_response();
}
let tools_block = (!tool_defs.is_empty()).then(|| tools::tools_system_block(&tool_defs));
let turns: Vec<ChatTurn> = req
.messages
.iter()
.map(|m| ChatTurn {
role: &m.role,
content: &m.content,
tool_calls: m
.tool_calls
.iter()
.map(|tc| ToolCall {
name: &tc.function.name,
arguments: &tc.function.arguments,
})
.collect(),
})
.collect();
let prompt = chat::render_chat_prompt(generation.arch, &turns, tools_block.as_deref());
let ids = match generation.tokenizer.encode(prompt.as_str(), true) {
Ok(e) => e.get_ids().to_vec(),
Err(e) => return ApiError::invalid_request(format!("tokenize: {e}")).into_response(),
};
run(
generation,
ids,
common,
RespKind::Chat,
!tool_defs.is_empty(),
)
.await
}
#[derive(Deserialize)]
#[serde(untagged)]
enum EmbedInput {
One(String),
Many(Vec<String>),
}
#[derive(Deserialize)]
struct EmbeddingsReq {
input: EmbedInput,
#[serde(default)]
#[allow(dead_code)]
model: Option<String>,
#[serde(default)]
encoding_format: Option<String>,
#[serde(default)]
dimensions: Option<usize>,
}
#[derive(Serialize)]
struct EmbeddingItem {
object: &'static str,
index: usize,
embedding: serde_json::Value,
}
#[derive(Serialize)]
struct EmbedUsage {
prompt_tokens: usize,
total_tokens: usize,
}
#[derive(Serialize)]
struct EmbeddingsResp {
object: &'static str,
data: Vec<EmbeddingItem>,
model: String,
usage: EmbedUsage,
}
async fn embeddings(
State(st): State<AppState>,
Json(req): Json<EmbeddingsReq>,
) -> axum::response::Response {
let Some(es) = st.embed.clone() else {
return (
axum::http::StatusCode::BAD_REQUEST,
"no embedding model loaded (start with --embed-model <dir>)",
)
.into_response();
};
let base64 = match req.encoding_format.as_deref() {
None | Some("float") => false,
Some("base64") => true,
Some(fmt) => {
return (
axum::http::StatusCode::BAD_REQUEST,
format!("encoding_format `{fmt}` not supported (float | base64)"),
)
.into_response();
}
};
if es.is_per_token {
return (
axum::http::StatusCode::BAD_REQUEST,
"this checkpoint produces per-token (ColBERT) embeddings; use POST /v1/rerank",
)
.into_response();
}
let texts = match req.input {
EmbedInput::One(s) => vec![s],
EmbedInput::Many(v) => v,
};
if texts.is_empty() {
return (
axum::http::StatusCode::BAD_REQUEST,
"input must not be empty",
)
.into_response();
}
if let Some(d) = req.dimensions
&& (d == 0 || d > es.dimension)
{
return (
axum::http::StatusCode::BAD_REQUEST,
format!("dimensions must be in 1..={} for this model", es.dimension),
)
.into_response();
}
let (tx, rx) = tokio::sync::oneshot::channel();
if es
.submit
.send(EmbedJob::Embed(EmbedReq {
texts,
dimensions: req.dimensions,
reply: tx,
}))
.is_err()
{
return (
axum::http::StatusCode::SERVICE_UNAVAILABLE,
"embedding engine thread gone",
)
.into_response();
}
match rx.await {
Ok(Ok(done)) => {
let data: Vec<EmbeddingItem> = done
.vectors
.into_iter()
.enumerate()
.map(|(index, embedding)| EmbeddingItem {
object: "embedding",
index,
embedding: if base64 {
serde_json::Value::String(inferencelayer::serve::b64::encode_f32_le(
&embedding,
))
} else {
serde_json::json!(embedding)
},
})
.collect();
Json(EmbeddingsResp {
object: "list",
data,
model: es.model_name.clone(),
usage: EmbedUsage {
prompt_tokens: done.prompt_tokens,
total_tokens: done.prompt_tokens,
},
})
.into_response()
}
Ok(Err(e)) => (axum::http::StatusCode::BAD_REQUEST, e).into_response(),
Err(_) => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
"embedding engine dropped the request",
)
.into_response(),
}
}
#[derive(Deserialize)]
struct RerankApiReq {
#[serde(default)]
#[allow(dead_code)] model: Option<String>,
query: String,
documents: Vec<String>,
#[serde(default)]
top_n: Option<usize>,
#[serde(default)]
return_documents: bool,
}
async fn rerank(
State(st): State<AppState>,
Json(req): Json<RerankApiReq>,
) -> axum::response::Response {
let Some(es) = st.embed.clone() else {
return (
axum::http::StatusCode::BAD_REQUEST,
"no embedding model loaded (start with --embed-model <dir>)",
)
.into_response();
};
if req.documents.is_empty() {
return (
axum::http::StatusCode::BAD_REQUEST,
"documents must not be empty",
)
.into_response();
}
let (tx, rx) = tokio::sync::oneshot::channel();
if es
.submit
.send(EmbedJob::Rerank(RerankReq {
query: req.query,
documents: req.documents.clone(),
top_n: req.top_n,
reply: tx,
}))
.is_err()
{
return (
axum::http::StatusCode::SERVICE_UNAVAILABLE,
"embedding engine thread gone",
)
.into_response();
}
match rx.await {
Ok(Ok(done)) => {
let results: Vec<serde_json::Value> = done
.ranked
.iter()
.map(|(idx, score)| {
let mut obj = serde_json::json!({"index": idx, "relevance_score": score});
if req.return_documents {
obj["document"] = serde_json::json!({"text": req.documents[*idx]});
}
obj
})
.collect();
Json(serde_json::json!({
"model": es.model_name,
"results": results,
"usage": {"total_tokens": done.prompt_tokens},
}))
.into_response()
}
Ok(Err(e)) => (axum::http::StatusCode::BAD_REQUEST, e).into_response(),
Err(_) => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
"embedding engine dropped the request",
)
.into_response(),
}
}
struct ChoiceOut {
text: String,
finish: Finish,
logprobs: Vec<TokenLp>,
}
fn chat_choice_json(
index: usize,
c: &ChoiceOut,
parse_tools: bool,
logprobs: serde_json::Value,
) -> serde_json::Value {
if parse_tools {
let (content, calls) = tools::parse_tool_calls(&c.text);
if !calls.is_empty() {
let tool_calls: Vec<serde_json::Value> = calls
.iter()
.map(|tc| {
serde_json::json!({
"id": response_id("call"),
"type": "function",
"function": {"name": tc.name, "arguments": tc.arguments},
})
})
.collect();
let content = if content.is_empty() {
serde_json::Value::Null
} else {
serde_json::Value::String(content)
};
return serde_json::json!({
"index": index,
"message": {"role": "assistant", "content": content, "tool_calls": tool_calls},
"finish_reason": "tool_calls",
"stop_reason": c.finish.stop_reason,
"logprobs": logprobs,
});
}
}
serde_json::json!({
"index": index,
"message": {"role": "assistant", "content": c.text},
"finish_reason": c.finish.reason,
"stop_reason": c.finish.stop_reason,
"logprobs": logprobs,
})
}
async fn run(
generation: GenState,
ids: Vec<u32>,
common: CommonParams,
kind: RespKind,
parse_tools: bool,
) -> axum::response::Response {
run_with_images(generation, ids, Vec::new(), common, kind, parse_tools).await
}
async fn run_with_images(
generation: GenState,
ids: Vec<u32>,
images: Vec<Vec<u8>>,
common: CommonParams,
kind: RespKind,
parse_tools: bool,
) -> axum::response::Response {
let (tx, mut rx) = amp::unbounded_channel();
let n = common.n;
let (ids, vprompt) = if images.is_empty() {
(ids, None)
} else {
let Some(vx) = generation.vision.clone() else {
return ApiError::invalid_request(
"this checkpoint has no vision tower; images are not accepted",
)
.into_response();
};
let gctx = generation.gctx.clone();
let _permit = generation
.vision_gate
.clone()
.acquire_owned()
.await
.expect("vision gate never closes");
let _t_vis = std::time::Instant::now();
match tokio::task::spawn_blocking(move || vx.prepare(&gctx, &ids, &images)).await {
Ok(Ok((expanded, vp))) => {
VISION_US.fetch_add(
_t_vis.elapsed().as_micros() as u64,
std::sync::atomic::Ordering::Relaxed,
);
VISION_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
(expanded, Some(vp))
}
Ok(Err(e)) => {
return ApiError::invalid_request(format!("image: {e:#}")).into_response()
}
Err(e) => return ApiError::unavailable(format!("vision task: {e}")).into_response(),
}
};
let request = EngineRequest {
prompt_ids: ids,
vprompt,
max_tokens: common.max_tokens,
n,
params: build_request_params(&common),
stop_strings: common.stop.clone(),
reply: tx,
};
if generation.submit.send(request).is_err() {
return ApiError::unavailable("engine thread gone").into_response();
}
if common.stream {
return Sse::new(SseStream {
rx,
model: generation.model_name.clone(),
kind,
id: response_id(kind.id_prefix()),
created: created_epoch(),
remaining: n,
role_sent: vec![false; n],
include_usage: common.include_usage,
prompt_tokens: 0,
completion_tokens: 0,
cached_tokens: 0,
queued: VecDeque::new(),
ended: false,
finalized: false,
})
.into_response();
}
let mut choices: Vec<Option<ChoiceOut>> = (0..n).map(|_| None).collect();
let mut remaining = n;
let mut prompt_tokens = 0;
let mut completion_tokens = 0;
let mut cached_tokens = 0;
while remaining > 0 {
match rx.recv().await {
Some(EngineEvent::Delta { .. }) => {}
Some(EngineEvent::Done {
choice,
text,
completion_tokens: ct,
prompt_tokens: pt,
cached_tokens: cached,
finish,
logprobs,
}) => {
prompt_tokens = pt;
cached_tokens = cached;
completion_tokens += ct;
if let Some(slot) = choices.get_mut(choice) {
*slot = Some(ChoiceOut {
text,
finish,
logprobs,
});
}
remaining -= 1;
}
Some(EngineEvent::Error(e)) => return ApiError::internal(e).into_response(),
Some(EngineEvent::Rejected(e)) => {
return ApiError::invalid_request(e)
.with_code("context_length_exceeded")
.with_param("max_tokens")
.into_response();
}
None => break, }
}
let usage = Usage::new(prompt_tokens, completion_tokens, cached_tokens);
let choice_json: Vec<serde_json::Value> = choices
.into_iter()
.enumerate()
.map(|(i, c)| {
let c = c.unwrap_or(ChoiceOut {
text: String::new(),
finish: Finish {
reason: "stop",
stop_reason: None,
},
logprobs: Vec::new(),
});
let logprobs = logprobs_json(kind, &c.logprobs);
match kind {
RespKind::Completion => serde_json::json!({
"index": i,
"text": c.text,
"finish_reason": c.finish.reason,
"stop_reason": c.finish.stop_reason,
"logprobs": logprobs,
}),
RespKind::Chat => chat_choice_json(i, &c, parse_tools, logprobs),
}
})
.collect();
Json(serde_json::json!({
"id": response_id(kind.id_prefix()),
"object": kind.object(),
"created": created_epoch(),
"model": generation.model_name,
"choices": choice_json,
"usage": usage,
}))
.into_response()
}
struct SseStream {
rx: amp::UnboundedReceiver<EngineEvent>,
model: String,
kind: RespKind,
id: String,
created: u64,
remaining: usize,
role_sent: Vec<bool>,
include_usage: bool,
prompt_tokens: usize,
completion_tokens: usize,
cached_tokens: usize,
queued: VecDeque<Event>,
ended: bool,
finalized: bool,
}
impl SseStream {
fn delta_chunk(&mut self, choice: usize, text: &str) -> serde_json::Value {
let choices = match self.kind {
RespKind::Completion => serde_json::json!([{"index": choice, "text": text}]),
RespKind::Chat => {
let delta = if !self.role_sent[choice] {
self.role_sent[choice] = true;
serde_json::json!({"role": "assistant", "content": text})
} else {
serde_json::json!({"content": text})
};
serde_json::json!([{"index": choice, "delta": delta}])
}
};
serde_json::json!({
"id": self.id,
"object": self.kind.chunk_object(),
"created": self.created,
"model": self.model,
"choices": choices,
})
}
fn finish_chunk(&mut self, choice: usize, finish: &Finish) -> serde_json::Value {
let choices = match self.kind {
RespKind::Completion => serde_json::json!([{
"index": choice,
"text": "",
"finish_reason": finish.reason,
"stop_reason": finish.stop_reason,
}]),
RespKind::Chat => {
let delta = if !self.role_sent[choice] {
self.role_sent[choice] = true;
serde_json::json!({"role": "assistant", "content": ""})
} else {
serde_json::json!({})
};
serde_json::json!([{
"index": choice,
"delta": delta,
"finish_reason": finish.reason,
"stop_reason": finish.stop_reason,
}])
}
};
serde_json::json!({
"id": self.id,
"object": self.kind.chunk_object(),
"created": self.created,
"model": self.model,
"choices": choices,
})
}
fn finalize(&mut self) {
if self.finalized {
return;
}
self.finalized = true;
if self.include_usage {
let usage = Usage::new(
self.prompt_tokens,
self.completion_tokens,
self.cached_tokens,
);
let chunk = serde_json::json!({
"id": self.id,
"object": self.kind.chunk_object(),
"created": self.created,
"model": self.model,
"choices": [],
"usage": usage,
});
self.queued
.push_back(Event::default().data(chunk.to_string()));
}
self.queued.push_back(Event::default().data("[DONE]"));
self.ended = true;
}
}
impl futures_core::Stream for SseStream {
type Item = Result<Event, std::convert::Infallible>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
use std::task::Poll;
loop {
if let Some(ev) = self.queued.pop_front() {
return Poll::Ready(Some(Ok(ev)));
}
if self.ended {
return Poll::Ready(None);
}
match self.rx.poll_recv(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => {
self.finalize();
}
Poll::Ready(Some(EngineEvent::Delta { choice, text })) => {
let chunk = self.delta_chunk(choice, &text);
self.queued
.push_back(Event::default().data(chunk.to_string()));
}
Poll::Ready(Some(EngineEvent::Done {
choice,
completion_tokens,
prompt_tokens,
cached_tokens,
finish,
..
})) => {
self.prompt_tokens = prompt_tokens;
self.cached_tokens = cached_tokens;
self.completion_tokens += completion_tokens;
let chunk = self.finish_chunk(choice, &finish);
self.queued
.push_back(Event::default().data(chunk.to_string()));
self.remaining = self.remaining.saturating_sub(1);
if self.remaining == 0 {
self.finalize();
}
}
Poll::Ready(Some(EngineEvent::Error(e)))
| Poll::Ready(Some(EngineEvent::Rejected(e))) => {
self.queued
.push_back(Event::default().data(format!("{{\"error\":{e:?}}}")));
self.finalize();
}
}
}
}
}
async fn models(State(st): State<AppState>) -> impl IntoResponse {
let created = created_epoch();
let mut data = Vec::new();
if let Some(g) = &st.generation {
data.push(serde_json::json!({
"id": g.model_name, "object": "model", "created": created, "owned_by": "inferencelayer",
}));
}
if let Some(e) = &st.embed {
data.push(serde_json::json!({
"id": e.model_name, "object": "model", "created": created, "owned_by": "inferencelayer",
}));
}
Json(serde_json::json!({"object": "list", "data": data}))
}
async fn health() -> impl IntoResponse {
Json(serde_json::json!({"status": "ok"}))
}
async fn metrics(State(st): State<AppState>) -> impl IntoResponse {
let (s, cache, free_blocks) = st.stats.lock().expect("stats lock").clone();
Json(serde_json::json!({
"steps": s.steps,
"kv_free_blocks": free_blocks,
"tokens_out": s.tokens_out,
"prefill_tokens": s.prefill_tokens,
"cache_hit_tokens": s.cache_hit_tokens,
"admitted": s.admitted,
"finished": s.finished,
"rejected_full": s.rejected_full,
"cancelled": s.cancelled,
"preempted": s.preempted,
"wide_steps": s.wide_steps,
"spec_rounds": s.spec_rounds,
"spec_drafted": s.spec_drafted,
"spec_accepted": s.spec_accepted,
"pld_hits": s.pld_hits,
"radix_entries": cache,
"wide_us": s.wide_us,
"decode_us": s.decode_us,
"vision_us": VISION_US.load(std::sync::atomic::Ordering::Relaxed),
"vision_calls": VISION_CALLS.load(std::sync::atomic::Ordering::Relaxed),
"vision_pre_us": VISION_PRE_US.load(std::sync::atomic::Ordering::Relaxed),
"emit_us": EMIT_US.load(std::sync::atomic::Ordering::Relaxed),
"step_us": s.step_us,
"build_us": s.build_us,
"post_us": s.post_us,
"chain_us": s.chain_us,
"chained_steps": s.chained_steps,
}))
}