Skip to main content

superfluid_daemon/
scheduler.rs

1//! The multi-lane scheduler.
2
3use std::collections::{HashMap, HashSet, VecDeque};
4use std::path::PathBuf;
5use std::sync::atomic::Ordering;
6use std::sync::mpsc::{Receiver, Sender, TryRecvError};
7use std::sync::{Arc, Mutex};
8
9use superfluid_abi::{encoding, finish, op_state};
10use superfluid_proto::linkw::{self, ALL_SPACES};
11
12use crate::bus::SessionBus;
13use crate::codec::{Channelizer, TextCodec, Utf8Stream};
14use crate::park;
15use crate::runtime::EngineHost;
16use crate::store::{CommittedEvent, SessionStore};
17use crate::wal::GenParams;
18use crate::DaemonError;
19
20const KV_SPACE: u32 = 1;
21const MAX_WORKER_RESPAWNS: u32 = 3;
22// An admission stages at most two token-in slots: its prompt and a resumed prefix.
23const STAGES_PER_ADMISSION: u32 = 2;
24
25fn lane_cap(configured: usize, max_seqs: Option<u64>, emit_slots: Option<u32>) -> usize {
26    let lanes = match max_seqs {
27        Some(n) if (n as usize) < configured => {
28            eprintln!("[superfluid] {configured} lanes asked for; the runtime holds {n} sequences, so {n} run");
29            (n as usize).max(1)
30        }
31        _ => configured,
32    };
33    // Every decoding lane writes one token-out slot a tick, read back after
34    // it: more lanes than slots would overwrite the first emits.
35    match emit_slots {
36        Some(n) if (n as usize) < lanes => {
37            eprintln!("[superfluid] {lanes} lanes asked for; the worker's token-out ring holds {n} emits a tick, so {n} run");
38            (n as usize).max(1)
39        }
40        _ => lanes,
41    }
42}
43
44fn ring_slots(host: &mut EngineHost, ring_id: u32) -> Option<u32> {
45    if !host.is_alive() {
46        return None;
47    }
48    host.client()
49        .hello
50        .limits
51        .ring_specs
52        .iter()
53        .find(|r| r.ring_id == ring_id)
54        .map(|r| r.slots)
55}
56
57fn worker_is_gone(e: &DaemonError) -> bool {
58    matches!(
59        e,
60        DaemonError::Agent(
61            superfluid_agent::AgentError::Io(_)
62                | superfluid_agent::AgentError::Closed
63                | superfluid_agent::AgentError::Envelope(_)
64        )
65    )
66}
67
68pub const PREFILL_BUDGET: u32 = 4096;
69pub const SOLO_PREFILL_BUDGET: u32 = 16384;
70const TICK_DECODE: u16 = 32;
71const TICK_DECODE_MAX: u16 = 256;
72const DECODE_SHARE_BESIDE_PREFILL: f64 = 0.25;
73/// The same while a chat or an inline completion decodes: its reply is
74/// short and watched, so the prefill beside it waits more, not the reply.
75const DECODE_SHARE_BESIDE_PREFILL_INTERACTIVE: f64 = 2.0;
76const TICK_DECODE_BESIDE_PREFILL: u16 = 8;
77const TARGET_TICK_TOTAL_SECS: f64 = 2.0;
78const OPEN_TICK_SECS: f64 = 0.25;
79const MAX_PREFILL_TICK_SECS: f64 = 8.0;
80const MAX_LONE_PREFILL_TICK_SECS: f64 = 16.0;
81/// A tick's prefill while a request could still join: a newcomer waits for
82/// the tick in flight, so this bounds its time to first token.
83const OPEN_PREFILL_TICK_SECS: f64 = 0.5;
84/// And in tokens, whatever the learned rate says: an engine that returns
85/// before its GPU work ends makes an early tick look all but free.
86const OPEN_PREFILL_TICK_MAX: u32 = 1024;
87
88/// The open tick's target on an engine that ends a tick at its next step when a
89/// preempting request arrives.
90const OPEN_PREFILL_TICK_SECS_YIELDING: f64 = 2.0;
91
92/// How long after the last chat or inline completion the engine keeps its
93/// prefill steps short.
94const LATENCY_HOLD: std::time::Duration = std::time::Duration::from_secs(300);
95
96/// A queued request waits for a lane prefilling the same prefix (at least this long) when
97/// that lane retires soon, and starts from its cached prefix.
98const SHARED_PREFIX_MIN_TOKENS: u64 = 1024;
99/// "Soon": at most this many tokens left to generate.
100const SHARED_PREFIX_TAIL_TOKENS: u32 = 64;
101/// Never hold a request longer than this.
102const SHARED_PREFIX_MAX_HOLD_TICKS: u64 = 64;
103/// On a runtime whose state cannot be cut back, a chat's input of at least
104/// this many tokens is checkpointed where it ends (the executor's floor for
105/// the checkpoint where a prompt ends): less costs little to run again.
106const TURN_CHECKPOINT_MIN_TOKENS: u64 = 128;
107/// The turn checkpoints remembered so that a later turn's supersedes them.
108/// One forgotten stays in the cache until evicted.
109const TURN_CHECKPOINTS_TRACKED: usize = 64;
110
111const PREFILL_RATE_MIN_TOKENS: u32 = 256;
112
113
114/// The daemon's cache classes (a bitset the engine maps its entries onto):
115/// a resident prefix, and one that has started two lanes or more.
116const SHARED_PREFIX_CLASS: u64 = 2;
117/// The prefix cache's entries held in host memory (llama.cpp's exports): they
118/// free nothing in the pool, and are named only when the machine is short of
119/// memory.
120const HOST_EXPORT_CLASS: u64 = 4;
121
122#[derive(Clone, Copy, Debug, PartialEq)]
123struct TickShape {
124    open_secs: f64,
125    full_secs: f64,
126    decode_floor: u16,
127    beside_prefill: u16,
128}
129
130const NATIVE_TICKS: TickShape =
131    TickShape { open_secs: OPEN_TICK_SECS, full_secs: f64::INFINITY, decode_floor: TICK_DECODE, beside_prefill: TICK_DECODE_BESIDE_PREFILL };
132const ROUND_TICKS: TickShape = TickShape { open_secs: 0.05, full_secs: 0.25, decode_floor: 4, beside_prefill: 1 };
133
134fn tick_target(shape: &TickShape, configured: f64, lanes: usize, max_lanes: usize, finishing: bool) -> f64 {
135    if lanes < max_lanes || finishing {
136        configured.min(shape.open_secs)
137    } else {
138        configured.min(shape.full_secs)
139    }
140}
141
142pub struct GenerateJob {
143    pub session: u64,
144    pub stream: Vec<u32>,
145    /// Where the conversation's input ends in `stream`, before the
146    /// generation prompt the daemon appended to it (0: none appended).
147    pub input_end: u64,
148    pub params: GenParams,
149    pub max_tokens: u32,
150    pub events: Sender<LaneEvent>,
151    pub class: u8,
152    pub batch_invariant: bool,
153    pub carry: Option<LaneCarry>,
154    pub queued_at: u64,
155    pub completion: bool,
156    pub deadline: Option<std::time::Instant>,
157    pub media: Vec<(u64, std::path::PathBuf, u32)>,
158    pub grammar_handle: u32,
159    pub extras: GenExtras,
160    pub trace: tracing::Span,
161}
162
163#[derive(Debug, Clone, Default)]
164pub struct GenExtras {
165    pub presence_penalty: f32,
166    pub frequency_penalty: f32,
167    pub repeat_penalty: f32,
168    pub grammar_handle: u32,
169    pub logit_bias_handle: u32,
170    pub want_logprobs: bool,
171    pub top_logprobs: u8,
172    pub wants_deltas: bool,
173    pub ephemeral: bool,
174    pub open_channel: u32,
175    pub thinking: Option<bool>,
176    pub ignore_eos: bool,
177    pub seed_drawn: bool,
178    pub tool_schemas: Option<std::sync::Arc<crate::codec::ToolSchemas>>,
179}
180
181pub struct LaneCarry {
182    seg: Box<dyn Channelizer>,
183    utf8: Utf8Stream,
184    disp_seg: Box<dyn Channelizer>,
185    disp_utf8: Utf8Stream,
186    tool_buf: String,
187    produced_before: u32,
188    spec_before: (u64, u64),
189    decode_before: (u64, f64),
190    warm_prefix_first: Option<u64>,
191}
192
193#[derive(Debug, Clone)]
194pub struct TokenLogprob {
195    pub token: u32,
196    pub logprob: f32,
197    pub top: Vec<(u32, f32)>,
198}
199
200pub enum LaneEvent {
201    Committed(CommittedEvent),
202    Provisional { channel: u32, text: String, produced: u32 },
203    TokenLogprobs(Vec<TokenLogprob>),
204    CompletionTokens(Vec<u32>),
205    Expired,
206    Done {
207        tokens_generated: u32,
208        finish: u32,
209        warm_prefix: u64,
210        spec: (u64, u64),
211        /// Tokens decoded after the request's first tick and the seconds they
212        /// took, tick end to tick end (see `Lane::decode`).
213        decode: (u64, f64),
214    },
215    Failed(String),
216}
217
218fn provisional_ingest(
219    codec: &Arc<dyn TextCodec + Send + Sync>,
220    bus: &SessionBus,
221    lane: &mut Lane,
222    tokens: &[u32],
223) {
224    if tokens.is_empty() {
225        return;
226    }
227    lane.disp_tokens += tokens.len() as u32;
228    let produced = lane.disp_tokens;
229    for run in lane.disp_seg.split(tokens) {
230        let mut text = String::new();
231        for &t in &run.text {
232            text.push_str(&lane.disp_utf8.push(&codec.token_bytes(t)));
233        }
234        if text.is_empty() {
235            continue;
236        }
237        let channel = run.channel;
238        if channel == crate::wal::channel::TEXT || channel == crate::wal::channel::REASONING {
239            bus.publish_provisional(lane.session, channel, &text);
240            let _ = lane.job.events.send(LaneEvent::Provisional { channel, text, produced });
241        }
242    }
243}
244
245fn provisional_flush(bus: &SessionBus, lane: &mut Lane) {
246    let text = lane.disp_utf8.flush();
247    if text.is_empty() {
248        return;
249    }
250    let channel = lane.disp_seg.current_channel();
251    if channel == crate::wal::channel::TEXT || channel == crate::wal::channel::REASONING {
252        bus.publish_provisional(lane.session, channel, &text);
253        let produced = lane.disp_tokens;
254        let _ = lane.job.events.send(LaneEvent::Provisional { channel, text, produced });
255    }
256}
257
258pub enum SchedCmd {
259    Submit(Box<GenerateJob>),
260    Sync(linkw::StateSyncReqMsg, Sender<Result<linkw::StateSyncOkMsg, String>>),
261    SyncAs(u8, Box<SchedCmd>),
262    SyncStream(
263        linkw::StateSyncReqMsg,
264        Sender<linkw::TranscribeSegmentMsg>,
265        Sender<Result<linkw::StateSyncOkMsg, String>>,
266    ),
267    Pin(crate::pins::PinReq),
268    PinHeld(Vec<crate::pins::PinKey>, Sender<u64>),
269    PinUnanchor(u64),
270    Shutdown,
271}
272
273pub struct SchedulerHandle {
274    pub tx: Sender<SchedCmd>,
275    join: std::sync::Mutex<Option<std::thread::JoinHandle<()>>>,
276}
277
278impl SchedulerHandle {
279    pub fn signal_shutdown(&self) {
280        let _ = self.tx.send(SchedCmd::Shutdown);
281    }
282
283    pub fn shutdown(&self) {
284        let mut guard = self.join.lock().expect("join");
285        if let Some(j) = guard.take() {
286            self.signal_shutdown();
287            let _ = j.join();
288        }
289    }
290}
291
292impl Drop for SchedulerHandle {
293    fn drop(&mut self) {
294        self.shutdown();
295    }
296}
297
298enum LanePhase {
299    Prefilling,
300    Decoding,
301    Finishing,
302}
303
304struct Lane {
305    tag: u64,
306    session: u64,
307    job: GenerateJob,
308    phase: LanePhase,
309    prefilled: u64,
310    produced: u32,
311    fin: u32,
312    warm_prefix: u64,
313    utf8: Utf8Stream,
314    seg: Box<dyn Channelizer>,
315    disp_seg: Box<dyn Channelizer>,
316    disp_utf8: Utf8Stream,
317    frame_fed: usize,
318    emit_dead: bool,
319    tool_buf: String,
320    admitted: bool,
321    seed_handle: u64,
322    span: linkw::TokenRefMsg,
323    all_tokens: Vec<u32>,
324    parked: bool,
325    parked_lossy: bool,
326    preempted: bool,
327    starved_ticks: u32,
328    prefill_starved_ticks: u32,
329    /// On a recurrent runtime: where this lane's prefill stops to keep a state for queued
330    /// requests sharing that prefix (0: nowhere), whether this chunk ends there, and the
331    /// longest state kept.
332    checkpoint_at: u64,
333    checkpoint_due: bool,
334    checkpointed: u64,
335    /// Whether the state where the job's input ends has been kept (or tried).
336    turn_kept: bool,
337    produced_before: u32,
338    spec: (u64, u64),
339    /// The request's decode as delivered tick by tick: tokens after its first
340    /// tick and the seconds from that tick's end to the last one's, summed over
341    /// its spans (a preempted lane starts a new span). A tick hands over all
342    /// of its tokens at once, under one or more events, so only tick ends time
343    /// it; the first tick also ran the prefill, so it only opens the span.
344    decode: (u64, f64),
345    /// The open span: (end of its first tick, tokens by then, end of its
346    /// latest tick, tokens by then).
347    decode_ticks: Option<(std::time::Instant, u32, std::time::Instant, u32)>,
348    disp_tokens: u32,
349    warm_prefix_first: Option<u64>,
350    admitted_at: std::time::Instant,
351    ttft_observed: bool,
352    ttft_frame_ms: Option<u64>,
353    fault_code: Option<u32>,
354}
355
356impl Lane {
357    /// `decode` with the open span closed.
358    fn decode_measured(&self) -> (u64, f64) {
359        let mut d = self.decode;
360        if let Some((at, by, last_at, last_by)) = self.decode_ticks {
361            if last_by > by {
362                d.0 += u64::from(last_by - by);
363                d.1 += last_at.saturating_duration_since(at).as_secs_f64();
364            }
365        }
366        d
367    }
368}
369
370#[derive(Default)]
371struct ClassQueue {
372    jobs: VecDeque<GenerateJob>,
373}
374
375pub fn kv_bits_label(bits: u64) -> Option<&'static str> {
376    match bits {
377        16 => Some("f16"),
378        8 => Some("q8_0"),
379        4 => Some("q4_0"),
380        84 => Some("K q8_0 / V q4_0"),
381        _ => None,
382    }
383}
384
385#[derive(Debug, Default)]
386pub struct SchedStats {
387    pub pressure_evictions: std::sync::atomic::AtomicU64,
388    pub pressure_bytes_evicted: std::sync::atomic::AtomicU64,
389    pub unservable_cold_admissions: std::sync::atomic::AtomicU64,
390    pub unservable_park_refusals: std::sync::atomic::AtomicU64,
391    pub preemptions: std::sync::atomic::AtomicU64,
392    pub admits_deferred: std::sync::atomic::AtomicU64,
393    pub starvation_grants: std::sync::atomic::AtomicU64,
394    pub os_pressure_events: std::sync::atomic::AtomicU64,
395    pub ticks: std::sync::atomic::AtomicU64,
396    pub lanes_active: std::sync::atomic::AtomicU64,
397    pub queue_depth: [std::sync::atomic::AtomicU64; 4],
398    pub prefill_tokens: std::sync::atomic::AtomicU64,
399    pub decode_tokens: std::sync::atomic::AtomicU64,
400    pub warm_prefix_tokens: std::sync::atomic::AtomicU64,
401    pub cold_admissions: std::sync::atomic::AtomicU64,
402    pub parks_lossless: std::sync::atomic::AtomicU64,
403    pub parks_lossy: std::sync::atomic::AtomicU64,
404    pub resumes: std::sync::atomic::AtomicU64,
405    pub worker_respawns: std::sync::atomic::AtomicU64,
406    pub spec_proposed: std::sync::atomic::AtomicU64,
407    pub spec_accepted: std::sync::atomic::AtomicU64,
408    pub ttft_buckets: [std::sync::atomic::AtomicU64; TTFT_BUCKETS_MS.len() + 1],
409    pub ttft_sum_ms: std::sync::atomic::AtomicU64,
410    pub ttft_count: std::sync::atomic::AtomicU64,
411    pub ttft_last_ms: std::sync::atomic::AtomicU64,
412    pub pool_blocks_used: std::sync::atomic::AtomicU64,
413    pub prefill_budget_live: std::sync::atomic::AtomicU64,
414    pub decode_grant_live: std::sync::atomic::AtomicU64,
415    pub tick_target_live_ms: std::sync::atomic::AtomicU64,
416    pub pool_blocks_total: std::sync::atomic::AtomicU64,
417    pub kv_bits: std::sync::atomic::AtomicU64,
418    pub pins_held: std::sync::atomic::AtomicU64,
419    pub pinned_bytes: std::sync::atomic::AtomicU64,
420    pub pins_yielded: std::sync::atomic::AtomicU64,
421    pub pins_expired: std::sync::atomic::AtomicU64,
422    pub tick_wall_ms_live: std::sync::atomic::AtomicU64,
423    pub prefill_rate_live: std::sync::atomic::AtomicU64,
424    pub decode_rate_live: std::sync::atomic::AtomicU64,
425    pub work_ticks: std::sync::atomic::AtomicU64,
426    tick_seq: std::sync::atomic::AtomicU64,
427    tick_since_ms: std::sync::atomic::AtomicU64,
428    tick_prefill_tokens: std::sync::atomic::AtomicU64,
429    tick_prefill_lanes: std::sync::atomic::AtomicU64,
430    tick_decode_lanes: std::sync::atomic::AtomicU64,
431    tick_admits: std::sync::atomic::AtomicU64,
432    tick_retires: std::sync::atomic::AtomicU64,
433    tick_target_ms: std::sync::atomic::AtomicU64,
434}
435
436#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
437pub struct TickInFlight {
438    pub since_ms: u64,
439    pub prefill_tokens: u64,
440    pub prefill_lanes: u64,
441    pub decode_lanes: u64,
442    pub admits: u64,
443    pub retires: u64,
444    pub target_ms: u64,
445}
446
447impl SchedStats {
448    pub fn set_tick_in_flight(&self, t: TickInFlight) {
449        use std::sync::atomic::Ordering::SeqCst;
450        self.tick_seq.fetch_add(1, SeqCst);
451        self.tick_since_ms.store(t.since_ms, SeqCst);
452        self.tick_prefill_tokens.store(t.prefill_tokens, SeqCst);
453        self.tick_prefill_lanes.store(t.prefill_lanes, SeqCst);
454        self.tick_decode_lanes.store(t.decode_lanes, SeqCst);
455        self.tick_admits.store(t.admits, SeqCst);
456        self.tick_retires.store(t.retires, SeqCst);
457        self.tick_target_ms.store(t.target_ms, SeqCst);
458        self.tick_seq.fetch_add(1, SeqCst);
459    }
460
461    pub fn clear_tick_in_flight(&self) {
462        self.set_tick_in_flight(TickInFlight::default());
463    }
464
465    pub fn tick_in_flight(&self) -> Option<TickInFlight> {
466        use std::sync::atomic::Ordering::SeqCst;
467        loop {
468            let s1 = self.tick_seq.load(SeqCst);
469            if s1 & 1 == 1 {
470                std::hint::spin_loop();
471                continue;
472            }
473            let t = TickInFlight {
474                since_ms: self.tick_since_ms.load(SeqCst),
475                prefill_tokens: self.tick_prefill_tokens.load(SeqCst),
476                prefill_lanes: self.tick_prefill_lanes.load(SeqCst),
477                decode_lanes: self.tick_decode_lanes.load(SeqCst),
478                admits: self.tick_admits.load(SeqCst),
479                retires: self.tick_retires.load(SeqCst),
480                target_ms: self.tick_target_ms.load(SeqCst),
481            };
482            if self.tick_seq.load(SeqCst) == s1 {
483                return (t.since_ms != 0).then_some(t);
484            }
485        }
486    }
487}
488
489pub fn uptime_ms() -> u64 {
490    static START: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
491    START.get_or_init(std::time::Instant::now).elapsed().as_millis() as u64
492}
493
494fn per_sec(tokens: u64, secs: f64) -> u64 {
495    if tokens == 0 || secs <= 0.0 {
496        0
497    } else {
498        (tokens as f64 / secs) as u64
499    }
500}
501
502pub const TTFT_BUCKETS_MS: [u64; 13] =
503    [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10_000, 20_000, 40_000];
504
505impl SchedStats {
506    pub fn observe_ttft(&self, ms: u64) {
507        let idx = TTFT_BUCKETS_MS
508            .iter()
509            .position(|&b| ms <= b)
510            .unwrap_or(TTFT_BUCKETS_MS.len());
511        self.ttft_buckets[idx].fetch_add(1, Ordering::Relaxed);
512        self.ttft_sum_ms.fetch_add(ms, Ordering::Relaxed);
513        self.ttft_count.fetch_add(1, Ordering::Relaxed);
514        self.ttft_last_ms.store(ms, Ordering::Relaxed);
515    }
516}
517
518#[derive(Debug, Clone, PartialEq, Eq)]
519pub struct SpeculationGrant {
520    pub strategy_id: String,
521    pub slot: u32,
522    pub class: u8,
523    pub cert_id: u32,
524    pub identity: u64,
525    pub modes: u8,
526}
527
528fn proposal_config(id: &str, opts: &crate::SpeculationOptions) -> String {
529    fn env_depth(key: &str) -> Option<u32> {
530        std::env::var(key)
531            .ok()
532            .and_then(|v| v.trim().parse::<u32>().ok())
533            .filter(|n| (1..=15).contains(n))
534    }
535    fn env_bool(key: &str, default: bool) -> bool {
536        std::env::var(key)
537            .ok()
538            .map(|v| v.trim().parse::<i32>().unwrap_or(i32::from(default)) != 0)
539            .unwrap_or(default)
540    }
541    let strategy_depth = || match id {
542        "mtp-head" => env_depth("SUPERFLUID_MTP_DRAFTS").unwrap_or(0),
543        "eagle3" => env_depth("SUPERFLUID_EAGLE_DRAFTS").unwrap_or(2),
544        "dflash" | "dspark" => env_depth("SUPERFLUID_DFLASH_DRAFTS").unwrap_or(0),
545        _ => 4,
546    };
547    let max_draft = opts.max_draft.unwrap_or_else(strategy_depth);
548    let adaptive = opts
549        .adaptive
550        .unwrap_or_else(|| env_bool("SUPERFLUID_SPEC_ADAPTIVE", true));
551    let min_yield = opts.min_yield.unwrap_or_else(|| {
552        std::env::var("SUPERFLUID_SPEC_MIN_YIELD")
553            .ok()
554            .and_then(|v| v.trim().parse::<f64>().ok())
555            .filter(|v| v.is_finite() && *v >= 0.0)
556            .unwrap_or(0.75)
557    });
558    let yield_rounds = opts.yield_rounds.unwrap_or_else(|| {
559        std::env::var("SUPERFLUID_SPEC_LANE_YIELD_ROUNDS")
560            .ok()
561            .and_then(|v| v.trim().parse::<u32>().ok())
562            .filter(|n| *n > 0)
563            .unwrap_or(24)
564    });
565    let throughput_gate = opts
566        .throughput_gate
567        .unwrap_or_else(|| env_bool("SUPERFLUID_SPEC_GATE", true));
568    let gate_probe_tokens = opts.gate_probe_tokens.unwrap_or_else(|| {
569        std::env::var("SUPERFLUID_SPEC_GATE_PROBE")
570            .ok()
571            .and_then(|v| v.trim().parse::<u32>().ok())
572            .unwrap_or(16)
573    });
574    let min_speedup = opts.min_speedup.unwrap_or_else(|| {
575        std::env::var("SUPERFLUID_SPEC_MIN_SPEEDUP")
576            .ok()
577            .and_then(|v| v.trim().parse::<f64>().ok())
578            .filter(|v| v.is_finite() && *v >= 1.0)
579            .unwrap_or(1.08)
580    });
581    let env_u32 = |key: &str, default: u32| {
582        std::env::var(key)
583            .ok()
584            .and_then(|v| v.trim().parse::<u32>().ok())
585            .unwrap_or(default)
586    };
587    let gate_reprobe = opts
588        .gate_reprobe
589        .unwrap_or_else(|| env_u32("SUPERFLUID_SPEC_GATE_REPROBE", 32));
590    let gate_reprobe_max = opts
591        .gate_reprobe_max
592        .unwrap_or_else(|| env_u32("SUPERFLUID_SPEC_GATE_REPROBE_MAX", 1024));
593    let bitexact = env_bool("BASERT_SPEC_BITEXACT", false);
594    let mut cfg = format!(
595        "max_draft={max_draft},adaptive={},min_yield={min_yield:.6},yield_rounds={yield_rounds},throughput_gate={},gate_probe_tokens={gate_probe_tokens},min_speedup={min_speedup:.6},gate_reprobe={gate_reprobe},gate_reprobe_max={gate_reprobe_max},bitexact={}",
596        u8::from(adaptive),
597        u8::from(throughput_gate),
598        u8::from(bitexact),
599    );
600    if id == "dspark" {
601        let confidence = opts.dspark_confidence.or_else(|| {
602            std::env::var("SUPERFLUID_DSPARK_CONFIDENCE")
603                .ok()
604                .and_then(|v| v.trim().parse::<f32>().ok())
605                .filter(|t| t.is_finite() && *t > 0.0)
606        });
607        if let Some(t) = confidence {
608            cfg.push_str(&format!(",confidence={t:.6}"));
609        }
610    }
611    cfg
612}
613
614fn prefill_tick_cap(secs_per_token: f64, lone: bool) -> u32 {
615    let secs = if lone { MAX_LONE_PREFILL_TICK_SECS } else { MAX_PREFILL_TICK_SECS };
616    (secs / secs_per_token).clamp(512.0, PREFILL_BUDGET as f64) as u32
617}
618
619/// Whole 512-token chunks; whole 128-token steps on a model where one chunk passes the
620/// half second.
621/// How long an open tick's prefill may run: longer on an engine that yields at its next
622/// step, unless a latency-sensitive request is around.
623fn open_prefill_tick_target(yields: bool, latency: bool) -> f64 {
624    if yields && !latency {
625        OPEN_PREFILL_TICK_SECS_YIELDING
626    } else {
627        OPEN_PREFILL_TICK_SECS
628    }
629}
630
631fn open_prefill_tick_cap(secs_per_token: f64, target_secs: f64) -> u32 {
632    let tokens = (target_secs / secs_per_token).clamp(128.0, OPEN_PREFILL_TICK_MAX as f64) as u32;
633    let step = if tokens < 512 { 128 } else { 512 };
634    tokens / step * step
635}
636
637/// Why a shared-prefix checkpoint was not kept: the engine forks no lane's
638/// sequence (none will be), or this one failed.
639enum CheckpointError {
640    Refused(String),
641    Failed(String),
642}
643
644impl CheckpointError {
645    /// Only an engine that says it cannot fork refuses for good; anything
646    /// else (a lane that faulted, a full sequence table) fails this one.
647    fn of(e: superfluid_agent::AgentError) -> CheckpointError {
648        if matches!(e, superfluid_agent::AgentError::Rejected(s) if s == superfluid_abi::Status::Unsupported as i32) {
649            CheckpointError::Refused(e.to_string())
650        } else {
651            CheckpointError::Failed(e.to_string())
652        }
653    }
654}
655
656struct PrefillBudget {
657    left: u64,
658    tail_capped: bool,
659    held_below: Option<u8>,
660}
661
662impl PrefillBudget {
663    const TAIL_FLOOR: u64 = 256;
664
665    fn new(budget: u64) -> Self {
666        PrefillBudget { left: budget, tail_capped: false, held_below: None }
667    }
668
669    fn offer(&self, want: u64) -> u64 {
670        want.min(self.left)
671    }
672
673    fn offer_to(&self, want: u64, class: u8) -> u64 {
674        if self.held_below.is_some_and(|c| class > c) {
675            return 0;
676        }
677        self.offer(want)
678    }
679
680    /// A chat or an inline completion finished its prompt this tick: nothing
681    /// of a lower class prefills beside it, so its first token is not held
682    /// behind someone else's prompt.
683    fn hold_below(&mut self, class: u8) {
684        self.held_below = Some(self.held_below.map_or(class, |c| c.min(class)));
685    }
686
687    fn take(&mut self, chunk: u64, completes: bool) {
688        self.left -= chunk;
689        if completes && !self.tail_capped {
690            self.tail_capped = true;
691            self.left = self.left.min(chunk.max(Self::TAIL_FLOOR));
692        }
693    }
694}
695
696/// `plan` with only its first `keep` admissions, and none of the work of the
697/// lanes it drops.
698fn with_first_admits(plan: &linkw::TickPlanMsg, keep: usize) -> linkw::TickPlanMsg {
699    let mut p = plan.clone();
700    let dropped: std::collections::HashSet<u64> = p.admits.split_off(keep.min(p.admits.len())).iter().map(|a| a.lane_tag).collect();
701    p.prefills.retain(|x| !dropped.contains(&x.lane_tag));
702    p.decodes.retain(|x| !dropped.contains(&x.lane_tag));
703    p.commits.retain(|x| !dropped.contains(&x.lane_tag));
704    p
705}
706
707/// A lane's starved-tick count after a tick: `waiting` (prefill left, none
708/// this tick) counts only when the youngest lane served was admitted after
709/// it (lane tags rise with admission).
710fn starved_after(prev: u32, waiting: bool, youngest_served: Option<u64>, tag: u64) -> u32 {
711    if waiting && youngest_served.is_some_and(|y| y > tag) {
712        prev.saturating_add(1)
713    } else {
714        prev
715    }
716}
717
718fn prefill_rank(class: u8, remaining: u64, tag: u64, starved_ticks: u32, bound: u32) -> (bool, u8, u64, u64) {
719    let starving = remaining > 0 && starved_ticks >= bound;
720    (!starving, class, remaining, tag)
721}
722
723#[cfg(test)]
724mod prefill_order_tests {
725    use super::{prefill_rank, prefill_tick_cap, starved_after, PrefillBudget, PREFILL_BUDGET};
726
727    #[test]
728    fn prompts_that_arrived_together_prefill_one_after_another() {
729        // Eight equal prompts, one tick's budget each time: the shortest
730        // left goes first, and nobody is passed over by a younger lane, so
731        // they finish in order instead of in turns.
732        const BOUND: u32 = 8;
733        let mut lanes: Vec<(u64, u64, u32)> = (0..8).map(|t| (t, 7680, 0)).collect();
734        let mut finished_at = Vec::new();
735        for tick in 0..400u32 {
736            let mut order: Vec<usize> = (0..lanes.len()).filter(|&i| lanes[i].1 > 0).collect();
737            if order.is_empty() {
738                break;
739            }
740            order.sort_by_key(|&i| prefill_rank(3, lanes[i].1, lanes[i].0, lanes[i].2, BOUND));
741            let served = order[0];
742            lanes[served].1 -= 512;
743            lanes[served].2 = 0;
744            if lanes[served].1 == 0 {
745                finished_at.push(tick + 1);
746            }
747            let youngest = Some(lanes[served].0);
748            for (i, l) in lanes.iter_mut().enumerate() {
749                l.2 = starved_after(l.2, i != served && l.1 > 0, youngest, l.0);
750            }
751        }
752        assert_eq!(finished_at, (1..=8).map(|k| 15 * k).collect::<Vec<_>>(), "fifteen ticks each, in turn");
753    }
754
755    #[test]
756    fn only_an_engine_that_cannot_fork_refuses_checkpoints_for_good() {
757        use super::CheckpointError;
758        use superfluid_abi::Status;
759        use superfluid_agent::AgentError;
760        let refused = |e| matches!(CheckpointError::of(e), CheckpointError::Refused(_));
761        assert!(refused(AgentError::Rejected(Status::Unsupported as i32)));
762        assert!(!refused(AgentError::Rejected(Status::UnknownHandle as i32)), "a lane that faulted in its tick");
763        assert!(!refused(AgentError::Rejected(Status::NeedsReplan as i32)), "a full sequence table");
764        assert!(!refused(AgentError::Closed), "a worker that went away");
765    }
766
767    #[test]
768    fn an_open_tick_prefills_whole_chunks_for_about_half_a_second() {
769        use super::open_prefill_tick_cap;
770        assert_eq!(open_prefill_tick_cap(1.0 / 1200.0, super::OPEN_PREFILL_TICK_SECS), 512, "600 tokens round down to one chunk");
771        assert_eq!(open_prefill_tick_cap(1.0 / 5000.0, super::OPEN_PREFILL_TICK_SECS), 1024, "2500 tokens, at most two chunks");
772        assert_eq!(open_prefill_tick_cap(1.0, super::OPEN_PREFILL_TICK_SECS), 128, "never below one step");
773        assert_eq!(open_prefill_tick_cap(1.0 / 330.0, super::OPEN_PREFILL_TICK_SECS), 128, "a 27B: 165 tokens round down to one step");
774        assert_eq!(open_prefill_tick_cap(1.0 / 700.0, super::OPEN_PREFILL_TICK_SECS), 256, "350 tokens, two steps");
775        assert_eq!(open_prefill_tick_cap(1e-6, super::OPEN_PREFILL_TICK_SECS), 1024, "never above two chunks, whatever an early tick suggests");
776        assert_eq!(
777            open_prefill_tick_cap(1.0 / 330.0, super::OPEN_PREFILL_TICK_SECS_YIELDING),
778            512,
779            "an engine that yields mid-tick: a 27B's open tick is a whole chunk"
780        );
781        use super::open_prefill_tick_target as target;
782        assert_eq!(target(true, false), super::OPEN_PREFILL_TICK_SECS_YIELDING, "agents alone, on an engine that yields");
783        assert_eq!(target(true, true), super::OPEN_PREFILL_TICK_SECS, "a chat around: its next tokens wait out each tick");
784        assert_eq!(target(false, false), super::OPEN_PREFILL_TICK_SECS, "an engine that runs a tick whole");
785    }
786
787    #[test]
788    fn lone_lane_plans_longer_prefill_ticks() {
789        let spt = 0.0078125;
790        assert_eq!(prefill_tick_cap(spt, false), 1024);
791        assert_eq!(prefill_tick_cap(spt, true), 2048);
792        assert_eq!(prefill_tick_cap(1.0, true), 512);
793        assert_eq!(prefill_tick_cap(1e-6, false), PREFILL_BUDGET);
794        assert_eq!(prefill_tick_cap(1e-6, true), PREFILL_BUDGET);
795    }
796
797    fn grants(budget: u64, remaining: &[u64]) -> Vec<u64> {
798        let mut b = PrefillBudget::new(budget);
799        remaining
800            .iter()
801            .map(|&r| {
802                let c = b.offer(r);
803                if c > 0 {
804                    b.take(c, c == r);
805                }
806                c
807            })
808            .collect()
809    }
810
811    #[test]
812    fn the_tail_after_the_first_completion_is_capped_in_aggregate() {
813        let g = grants(4096, &[500, 4000, 4000, 4000, 4000, 4000, 4000, 4000]);
814        assert_eq!(g, vec![500, 500, 0, 0, 0, 0, 0, 0]);
815        let g = grants(4096, &[40, 4000, 4000]);
816        assert_eq!(g, vec![40, 256, 0]);
817        let g = grants(4096, &[1000, 300, 200, 4000]);
818        assert_eq!(g, vec![1000, 300, 200, 500]);
819        assert_eq!(grants(4096, &[3000, 3000]), vec![3000, 1096]);
820    }
821
822    #[test]
823    fn nothing_of_a_lower_class_prefills_beside_a_chat_that_finishes_its_prompt() {
824        let mut b = PrefillBudget::new(4096);
825        assert_eq!(b.offer_to(22, 0), 22);
826        b.take(22, true);
827        b.hold_below(0);
828        assert_eq!(b.offer_to(4000, 3), 0, "an agent waits a tick");
829        assert_eq!(b.offer_to(100, 0), 100, "another chat does not");
830        let mut b = PrefillBudget::new(4096);
831        b.take(500, false);
832        assert_eq!(b.offer_to(4000, 3), 3596, "a chat still prefilling holds nothing back");
833    }
834
835    #[test]
836    fn a_long_prefill_progresses_under_a_stream_of_short_prompts() {
837        const BUDGET: u64 = 512;
838        const BOUND: u32 = 3;
839        let mut lanes: Vec<(u64, u64, u32)> = vec![(0, 8 * BUDGET, 0)];
840        let mut next_tag = 1;
841        let mut long_ticks = Vec::new();
842        for tick in 0..64u32 {
843            lanes.retain(|l| l.0 == 0 || l.1 > 0);
844            while lanes.len() < 3 {
845                lanes.push((next_tag, BUDGET, 0));
846                next_tag += 1;
847            }
848            let mut order: Vec<usize> = (0..lanes.len()).collect();
849            order.sort_by_key(|&i| prefill_rank(0, lanes[i].1, lanes[i].0, lanes[i].2, BOUND));
850            let mut budget = PrefillBudget::new(BUDGET);
851            let mut served = Vec::new();
852            for i in order {
853                let l = &mut lanes[i];
854                if l.1 == 0 {
855                    continue;
856                }
857                let chunk = budget.offer(l.1);
858                l.1 -= chunk;
859                if chunk > 0 {
860                    l.2 = 0;
861                    served.push(l.0);
862                    budget.take(chunk, l.1 == 0);
863                }
864                if l.0 == 0 && chunk > 0 {
865                    long_ticks.push(tick);
866                }
867            }
868            let youngest = served.iter().copied().max();
869            for l in lanes.iter_mut() {
870                l.2 = starved_after(l.2, l.1 > 0 && !served.contains(&l.0), youngest, l.0);
871            }
872            assert!(lanes[0].2 <= BOUND, "tick {tick}: the long lane waited past the bound");
873            if lanes[0].1 == 0 {
874                break;
875            }
876        }
877        assert_eq!(lanes[0], (0, 0, 0), "the long prompt finished prefilling: {long_ticks:?}");
878        assert!(long_ticks.windows(2).all(|w| w[1] - w[0] <= BOUND + 1), "bounded gaps: {long_ticks:?}");
879        assert!(prefill_rank(0, 8 * BUDGET, 0, 0, BOUND) > prefill_rank(0, BUDGET, 9, 0, BOUND));
880        assert!(prefill_rank(5, 8 * BUDGET, 0, BOUND, BOUND) < prefill_rank(0, 1, 9, 0, BOUND));
881        assert!(prefill_rank(5, 0, 0, BOUND, BOUND) > prefill_rank(0, 1, 9, 0, BOUND));
882    }
883}
884
885#[cfg(test)]
886mod tick_target_tests {
887    use super::{tick_target, NATIVE_TICKS, OPEN_TICK_SECS, ROUND_TICKS};
888
889    #[test]
890    fn a_tick_is_short_while_a_joiner_could_be_admitted_next() {
891        assert_eq!(tick_target(&NATIVE_TICKS, 2.0, 1, 2, false), OPEN_TICK_SECS, "a slot is free");
892        assert_eq!(tick_target(&NATIVE_TICKS, 2.0, 2, 2, false), 2.0, "full and staying full: nothing could admit a joiner");
893        assert_eq!(tick_target(&ROUND_TICKS, 2.0, 1, 2, false), 0.05, "a round-granular runtime's open tick is a few rounds");
894        assert_eq!(tick_target(&ROUND_TICKS, 2.0, 2, 2, false), 0.25, "and its full one is never long");
895        assert_eq!(
896            tick_target(&NATIVE_TICKS, 2.0, 2, 2, true),
897            OPEN_TICK_SECS,
898            "a finishing lane frees a slot, and its client waits on the retire"
899        );
900        assert_eq!(tick_target(&NATIVE_TICKS, 0.1, 2, 2, true), 0.1, "a configured target under the open one stands");
901    }
902}
903
904#[cfg(test)]
905mod speculation_config_tests {
906    use super::proposal_config;
907    use crate::SpeculationOptions;
908
909    #[test]
910    fn cli_policy_is_serialized_for_the_engine_and_fingerprint() {
911        let cfg = proposal_config(
912            "dspark",
913            &SpeculationOptions {
914                max_draft: Some(2),
915                adaptive: Some(false),
916                min_yield: Some(0.5),
917                yield_rounds: Some(12),
918                throughput_gate: Some(false),
919                gate_probe_tokens: Some(8),
920                min_speedup: Some(1.12),
921                gate_reprobe: Some(0),
922                gate_reprobe_max: Some(64),
923                dspark_confidence: Some(0.42),
924            },
925        );
926        assert!(cfg.starts_with(
927            "max_draft=2,adaptive=0,min_yield=0.500000,yield_rounds=12,throughput_gate=0,gate_probe_tokens=8,min_speedup=1.120000,gate_reprobe=0,gate_reprobe_max=64,"
928        ));
929        assert!(cfg.ends_with(",confidence=0.420000"));
930    }
931
932    #[test]
933    fn one_draft_override_applies_to_mtp() {
934        let cfg = proposal_config(
935            "mtp-head",
936            &SpeculationOptions {
937                max_draft: Some(1),
938                ..SpeculationOptions::default()
939            },
940        );
941        assert!(cfg.starts_with("max_draft=1,"));
942        assert!(!cfg.contains("confidence="));
943    }
944}
945
946#[derive(Default)]
947pub struct Speculation {
948    reg: Mutex<Option<linkw::StrategyRegisterMsg>>,
949    grant: Mutex<Option<SpeculationGrant>>,
950}
951
952impl Speculation {
953    pub fn register(
954        this: &Arc<Speculation>,
955        host: &mut EngineHost,
956        strategy: &str,
957        opts: &crate::SpeculationOptions,
958    ) -> Result<(), DaemonError> {
959        let (id, draft_path) = match strategy.split_once(':') {
960            Some(("draft-model", path)) => ("draft-model", Some(path.to_string())),
961            Some(("dflash", path)) => ("dflash", Some(path.to_string())),
962            Some(("dspark", path)) => ("dspark", Some(path.to_string())),
963            Some(("eagle3", path)) => ("eagle3", Some(path.to_string())),
964            _ if strategy == "prompt-lookup" => ("prompt-lookup", None),
965            _ if strategy == "mtp-head" => ("mtp-head", None),
966            _ => {
967                return Err(DaemonError::Config(
968                    "unknown speculation strategy (prompt-lookup | mtp-head | dflash / dspark / eagle3 / draft-model followed by ':' and a model id or .base path)",
969                ))
970            }
971        };
972        let draft_identity = match draft_path.as_ref() {
973            Some(p) => Some(draft_weights_identity(p)?),
974            None => None,
975        };
976        let impl_hash = {
977            let mut h = [0u8; 32];
978            let tag = if id == "dflash" {
979                "dflash/1"
980            } else if id == "dspark" {
981                "dspark/1"
982            } else if id == "eagle3" {
983                "eagle3/1"
984            } else if draft_path.is_some() {
985                "draft-model/1"
986            } else if id == "mtp-head" {
987                "mtp-head/1"
988            } else {
989                "prompt-lookup/1"
990            };
991            h[..8].copy_from_slice(&superfluid_fingerprint::fnv1a64(tag.as_bytes(), 0xCBF2_9CE4_8422_2325).to_le_bytes());
992            h
993        };
994        let proposal_cfg = proposal_config(id, opts);
995        let config_hash = {
996            let mut h = [0u8; 32];
997            let cfg = match (&draft_path, &draft_identity) {
998                (Some(p), Some((fnv, size))) => {
999                    format!("draft={p},weights={fnv:016x},bytes={size},{proposal_cfg}")
1000                }
1001                _ => format!("n_max=4,{proposal_cfg}"),
1002            };
1003            h[..8].copy_from_slice(&superfluid_fingerprint::fnv1a64(cfg.as_bytes(), 0xCBF2_9CE4_8422_2325).to_le_bytes());
1004            h
1005        };
1006        let artifacts = match (&draft_path, &draft_identity) {
1007            (Some(path), Some((fnv, size))) => {
1008                let mut content_hash = [0u8; 32];
1009                content_hash[..8].copy_from_slice(&fnv.to_le_bytes());
1010                vec![linkw::ArtifactMsg {
1011                    role: superfluid_abi::artifact_role::DRAFT_WEIGHTS,
1012                    content_hash,
1013                    byte_size: *size,
1014                    load_path: path.clone(),
1015                }]
1016            }
1017            _ => vec![],
1018        };
1019        let reg = linkw::StrategyRegisterMsg {
1020            strategy_id: id.to_string(),
1021            impl_version: "1.0.0".into(),
1022            impl_hash,
1023            config_hash,
1024            artifacts,
1025            taps: vec![],
1026            capabilities: vec![linkw::CapabilityReqMsg {
1027                kind_id: superfluid_abi::strategy_cap::PROPOSAL_LINEAR,
1028                params: proposal_cfg.into_bytes(),
1029            }],
1030            target_archs: vec![],
1031            kernel_caps_required: 0,
1032            est_state_bytes: 0,
1033            claimed_exactness: superfluid_abi::exactness::SEED_PATH_INVARIANT,
1034            rng_contract_version: 1,
1035        };
1036        *this.reg.lock().expect("spec reg") = Some(reg);
1037        this.reregister(host)
1038    }
1039
1040    pub fn reregister(&self, host: &mut EngineHost) -> Result<(), DaemonError> {
1041        let Some(reg) = self.reg.lock().expect("spec reg").clone() else {
1042            return Ok(());
1043        };
1044        let ok = host.client().register_strategy(reg.clone()).map_err(|e| {
1045            DaemonError::Generation(format!("speculation strategy {} refused: {e}", reg.strategy_id))
1046        })?;
1047        let best = ok
1048            .certificates
1049            .iter()
1050            .filter(|c| c.sampling_modes & (superfluid_abi::cert_mode::GREEDY | superfluid_abi::cert_mode::GUMBEL) != 0)
1051            .max_by_key(|c| c.exactness);
1052        let identity = {
1053            let mut h = superfluid_fingerprint::RecordHasher::new();
1054            h.field(1, reg.strategy_id.as_bytes())
1055                .field(2, &reg.impl_hash)
1056                .field(3, &reg.config_hash);
1057            h.finish()
1058        };
1059        let modes = ok.certificates.iter().fold(0u8, |m, c| m | c.sampling_modes);
1060        *self.grant.lock().expect("spec grant") = Some(SpeculationGrant {
1061            strategy_id: reg.strategy_id.clone(),
1062            slot: ok.strategy_slot,
1063            class: best.map(|c| c.exactness).unwrap_or(superfluid_abi::exactness::APPROXIMATE),
1064            cert_id: best.map(|c| c.cert_id).unwrap_or(0),
1065            identity,
1066            modes,
1067        });
1068        Ok(())
1069    }
1070
1071    pub fn grant(&self) -> Option<SpeculationGrant> {
1072        self.grant.lock().expect("spec grant").clone()
1073    }
1074
1075    pub fn slot_for(&self, sampled: bool) -> u32 {
1076        let mode = if sampled {
1077            superfluid_abi::cert_mode::GUMBEL
1078        } else {
1079            superfluid_abi::cert_mode::GREEDY
1080        };
1081        self.grant
1082            .lock()
1083            .expect("spec grant")
1084            .as_ref()
1085            .filter(|g| g.modes & mode != 0)
1086            .map(|g| g.slot)
1087            .unwrap_or(0)
1088    }
1089
1090    pub fn identity_for_fingerprint(&self) -> Option<u64> {
1091        self.grant
1092            .lock()
1093            .expect("spec grant")
1094            .as_ref()
1095            .filter(|g| g.class < superfluid_abi::exactness::SEED_PATH_INVARIANT)
1096            .map(|g| g.identity)
1097    }
1098}
1099
1100#[derive(Debug, Clone, PartialEq, Eq)]
1101pub enum ParkRefused {
1102    SpaceUnservable,
1103    Failed(String),
1104}
1105
1106impl std::fmt::Display for ParkRefused {
1107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1108        match self {
1109            ParkRefused::SpaceUnservable => {
1110                write!(f, "a state space has no ops (hybrid GDN before P2.5); park refused")
1111            }
1112            ParkRefused::Failed(m) => f.write_str(m),
1113        }
1114    }
1115}
1116
1117impl From<String> for ParkRefused {
1118    fn from(m: String) -> Self {
1119        ParkRefused::Failed(m)
1120    }
1121}
1122
1123impl From<&str> for ParkRefused {
1124    fn from(m: &str) -> Self {
1125        ParkRefused::Failed(m.to_string())
1126    }
1127}
1128
1129enum ParkTask {
1130    Write(Box<ParkJob>),
1131    Remove { dir: PathBuf, session: u64 },
1132}
1133
1134struct ParkJob {
1135    dir: PathBuf,
1136    session: u64,
1137    covered: u64,
1138    digest: u64,
1139    encoding: u8,
1140    sealed: Vec<u8>,
1141    spaces: Option<Vec<park::ParkedSpace>>,
1142}
1143
1144pub struct Scheduler {
1145    host: EngineHost,
1146    store: Arc<Mutex<SessionStore>>,
1147    codec: Arc<dyn TextCodec + Send + Sync>,
1148    cancels: Arc<crate::CancelSet>,
1149    active: Arc<Mutex<HashSet<u64>>>,
1150    stats: Arc<SchedStats>,
1151    rx: Receiver<SchedCmd>,
1152    max_lanes: usize,
1153    configured_lanes: usize,
1154    retired: Arc<Mutex<Option<String>>>,
1155    park_dir: Option<PathBuf>,
1156    pressure_high_pct: u8,
1157    pressure_low_pct: u8,
1158    park_lossy: bool,
1159    trace_scope: crate::otlp::TraceScope,
1160    park_tx: Option<std::sync::mpsc::SyncSender<ParkTask>>,
1161    ephemeral_parked: std::collections::HashSet<u64>,
1162    park_removals_pending: Vec<u64>,
1163    park_writer: Option<std::thread::JoinHandle<()>>,
1164    bus: Arc<SessionBus>,
1165
1166    plan_seq: u64,
1167    next_lane: u64,
1168    lanes: Vec<Lane>,
1169    queues: [ClassQueue; 4],
1170    class_lanes: [usize; 4],
1171    tick_decode_budget: u32,
1172    tick_target_secs: f64,
1173    tick_shape: TickShape,
1174    prefill_budget: u32,
1175    prefill_budget_fixed: bool,
1176    decode_round_secs: Option<f64>,
1177    prefill_secs_per_token: Option<f64>,
1178    just_retired: bool,
1179    prefill_secs_per_token_bulk: Option<f64>,
1180    prefill_secs_per_token_last: Option<f64>,
1181    prefill_rate_samples: u32,
1182    /// The engine refused to fork a lane's sequence: no checkpoint where a
1183    /// shared prefix ends is kept, and no request is held for one.
1184    prefix_checkpoints_off: bool,
1185    /// The turn checkpoints kept, oldest first, by the length and digest of the tokens each
1186    /// covers; a turn's checkpoint is dropped once its next turn keeps one.
1187    turn_checkpoints: std::collections::VecDeque<(u64, [u8; 32])>,
1188    tick_decode_budget_fixed: bool,
1189    starvation_ticks: u32,
1190    background_tick_divisor: Arc<std::sync::atomic::AtomicU32>,
1191    pressure_source: Option<Box<dyn crate::pressure::PressureSource + Send>>,
1192    tick_count: u64,
1193    speculation: Arc<Speculation>,
1194    tool_lease_ms: u64,
1195    respawn_streak: u32,
1196    staged_unread: u32,
1197    deferred_sync: VecDeque<(u8, u64, SchedCmd)>,
1198    admit_retry: bool,
1199    pins: crate::pins::Pins,
1200}
1201
1202impl Scheduler {
1203    fn respawn_worker(&mut self) -> Result<(), DaemonError> {
1204        self.host.respawn()?;
1205        self.stats.kv_bits.store(self.host.kv_bits() as u64, Ordering::Relaxed);
1206        Ok(())
1207    }
1208
1209    #[allow(clippy::too_many_arguments)]
1210    pub fn spawn(
1211        host: EngineHost,
1212        store: Arc<Mutex<SessionStore>>,
1213        codec: Arc<dyn TextCodec + Send + Sync>,
1214        cancels: Arc<crate::CancelSet>,
1215        active: Arc<Mutex<HashSet<u64>>>,
1216        stats: Arc<SchedStats>,
1217        background_tick_divisor: Arc<std::sync::atomic::AtomicU32>,
1218        speculation: Arc<Speculation>,
1219        retired: Arc<Mutex<Option<String>>>,
1220        mut opts: crate::DaemonOptions,
1221    ) -> SchedulerHandle {
1222        let (tx, rx) = std::sync::mpsc::channel();
1223        let join = std::thread::Builder::new()
1224            .name("superfluid-scheduler".into())
1225            .spawn(move || {
1226                let bus = store.lock().expect("store").bus();
1227                let host_max_stream = host.max_stream_tokens();
1228                let mut host = host;
1229                let max_lanes = lane_cap(
1230                    opts.max_lanes,
1231                    host.max_seqs(),
1232                    ring_slots(&mut host, superfluid_shm::TOKEN_RING_OUT),
1233                );
1234                let park_refused = host.park_refused();
1235                let lossy_allowed = host.lossy_park_allowed();
1236                let tick_shape = if host.round_granular() { ROUND_TICKS } else { NATIVE_TICKS };
1237                let (park_tx, park_writer) = if opts.park_dir.is_some() && park_refused.is_none() {
1238                    let (tx, rx) = std::sync::mpsc::sync_channel::<ParkTask>(4);
1239                    let park_budget = opts.park_budget_bytes;
1240                    let writer = std::thread::Builder::new()
1241                        .name("superfluid-park-writer".into())
1242                        .spawn(move || {
1243                            while let Ok(task) = rx.recv() {
1244                                let job = match task {
1245                                    ParkTask::Write(job) => job,
1246                                    ParkTask::Remove { dir, session } => {
1247                                        park::remove(&dir, session);
1248                                        continue;
1249                                    }
1250                                };
1251                                let res = match &job.spaces {
1252                                    Some(spaces) => park::write_v3(
1253                                        &job.dir,
1254                                        job.session,
1255                                        job.covered,
1256                                        job.digest,
1257                                        spaces,
1258                                    ),
1259                                    None => park::write(
1260                                        &job.dir,
1261                                        job.session,
1262                                        job.covered,
1263                                        job.digest,
1264                                        job.encoding,
1265                                        &job.sealed,
1266                                    ),
1267                                };
1268                                if let Err(e) = res {
1269                                    eprintln!(
1270                                        "[superfluid] park write failed (session {}): {e}",
1271                                        job.session
1272                                    );
1273                                }
1274                                if park_budget > 0 {
1275                                    let freed = park::sweep(&job.dir, park_budget);
1276                                    if freed > 0 {
1277                                        tracing::debug!(
1278                                            freed_bytes = freed,
1279                                            budget_bytes = park_budget,
1280                                            "park retention sweep"
1281                                        );
1282                                    }
1283                                }
1284                            }
1285                        })
1286                        .expect("spawn park writer");
1287                    (Some(tx), Some(writer))
1288                } else {
1289                    (None, None)
1290                };
1291                Scheduler {
1292                    host,
1293                    store,
1294                    codec,
1295                    cancels,
1296                    active,
1297                    stats,
1298                    rx,
1299                    max_lanes,
1300                    configured_lanes: opts.max_lanes,
1301                    retired,
1302                    park_dir: match park_refused {
1303                        Some(why) if opts.park_dir.is_some() => {
1304                            eprintln!("[superfluid] parking is off for this model: {why}");
1305                            None
1306                        }
1307                        _ => opts.park_dir,
1308                    },
1309                    pressure_high_pct: opts.pressure_high_pct,
1310                    pressure_low_pct: opts.pressure_low_pct,
1311                    park_lossy: opts.park_lossy && lossy_allowed,
1312                    trace_scope: opts.trace_scope.clone(),
1313                    park_tx,
1314                    park_writer,
1315                    ephemeral_parked: std::collections::HashSet::new(),
1316                    park_removals_pending: Vec::new(),
1317                    bus,
1318                    plan_seq: 0,
1319                    next_lane: 1,
1320                    lanes: Vec::new(),
1321                    queues: Default::default(),
1322                    class_lanes: opts.class_lanes,
1323                    tick_target_secs: if opts.tick_target_ms == 0 {
1324                        TARGET_TICK_TOTAL_SECS
1325                    } else {
1326                        opts.tick_target_ms as f64 / 1000.0
1327                    },
1328                    tick_shape,
1329                    tick_decode_budget: if opts.tick_decode_budget == 0 {
1330                        (max_lanes as u32).saturating_mul(TICK_DECODE as u32)
1331                    } else {
1332                        opts.tick_decode_budget
1333                    },
1334                    prefill_budget: if opts.prefill_budget == 0 {
1335                        1024
1336                    } else {
1337                        if opts.prefill_budget > PREFILL_BUDGET {
1338                            eprintln!(
1339                                "superfluid: --prefill-budget {} is above the {PREFILL_BUDGET}-token per-tick \
1340                                 ceiling (the media run-size limit); using {PREFILL_BUDGET}",
1341                                opts.prefill_budget
1342                            );
1343                        }
1344                        opts.prefill_budget.clamp(64, PREFILL_BUDGET)
1345                    },
1346                    prefill_budget_fixed: opts.prefill_budget != 0,
1347                    decode_round_secs: None,
1348                    prefill_secs_per_token: None,
1349                    just_retired: false,
1350                    prefill_secs_per_token_bulk: None,
1351                    prefill_secs_per_token_last: None,
1352                    prefill_rate_samples: 0,
1353                    prefix_checkpoints_off: false,
1354                    turn_checkpoints: std::collections::VecDeque::new(),
1355                    tick_decode_budget_fixed: opts.tick_decode_budget != 0,
1356                    starvation_ticks: opts.agent_starvation_ticks.max(1),
1357                    background_tick_divisor,
1358                    pressure_source: opts.pressure_source.take_source(),
1359                    tick_count: 0,
1360                    speculation,
1361                    tool_lease_ms: opts.tool_lease_ms,
1362                    respawn_streak: 0,
1363                    staged_unread: 0,
1364                    deferred_sync: VecDeque::new(),
1365                    admit_retry: false,
1366                    pins: crate::pins::Pins::new(
1367                        opts.pin_budget_pct,
1368                        host_max_stream * max_lanes as u64,
1369                    ),
1370                }
1371                .run();
1372            })
1373            .expect("spawn scheduler");
1374        SchedulerHandle {
1375            tx,
1376            join: std::sync::Mutex::new(Some(join)),
1377        }
1378    }
1379
1380    fn run(mut self) {
1381        self.run_loop();
1382        drop(self.park_tx.take());
1383        if let Some(w) = self.park_writer.take() {
1384            let _ = w.join();
1385        }
1386        if let Some(dir) = self.park_dir.clone() {
1387            let pending = std::mem::take(&mut self.park_removals_pending);
1388            let tracked = std::mem::take(&mut self.ephemeral_parked);
1389            for session in pending.into_iter().chain(tracked) {
1390                park::remove(&dir, session);
1391            }
1392        }
1393    }
1394
1395    fn run_loop(&mut self) {
1396        loop {
1397            if self.lanes.is_empty() && self.queued() == 0 && self.deferred_sync.is_empty() {
1398                self.retry_park_removals();
1399                let retry = (!self.park_removals_pending.is_empty())
1400                    .then(|| std::time::Duration::from_millis(50));
1401                let wait = match (self.pins.until_next_deadline(), retry) {
1402                    (Some(a), Some(b)) => Some(a.min(b)),
1403                    (a, b) => a.or(b),
1404                };
1405                let cmd = match wait {
1406                    None => self.rx.recv().map_err(|_| ()),
1407                    Some(wait) => match self.rx.recv_timeout(wait) {
1408                        Ok(cmd) => Ok(cmd),
1409                        Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
1410                            self.pins.sweep(&mut self.host, &self.stats);
1411                            continue;
1412                        }
1413                        Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(()),
1414                    },
1415                };
1416                match cmd {
1417                    Ok(SchedCmd::Shutdown) | Err(()) => return,
1418                    Ok(cmd) => self.dispatch(cmd),
1419                }
1420            }
1421            // Lowered before what is waiting is taken in: anything that
1422            // arrives after raises it for the tick about to run.
1423            self.host.clear_yield();
1424            // A request that arrives between the look and the store raised
1425            // the word itself: looked at again, it is raised again.
1426            let slot = self.host.yield_slot();
1427            let latency = self.latency_sensitive();
1428            crate::runtime::set_latency(&slot, latency);
1429            if !latency && self.cancels.latency_recent(LATENCY_HOLD) {
1430                crate::runtime::set_latency(&slot, true);
1431            }
1432            loop {
1433                match self.rx.try_recv() {
1434                    Ok(SchedCmd::Shutdown) => return,
1435                    Ok(cmd) => self.dispatch(cmd),
1436                    Err(TryRecvError::Empty) => break,
1437                    Err(TryRecvError::Disconnected) => return,
1438                }
1439            }
1440            if !self.host.is_alive() {
1441                if self.respawn_streak < MAX_WORKER_RESPAWNS {
1442                    self.respawn_streak += 1;
1443                    if self.respawn_worker().is_ok() {
1444                        let _ = self.speculation.reregister(&mut self.host);
1445                        self.after_respawn();
1446                    }
1447                }
1448                if !self.host.is_alive() {
1449                    if self.respawn_streak >= MAX_WORKER_RESPAWNS {
1450                        self.give_up_on_worker();
1451                    }
1452                    let why = self
1453                        .retired
1454                        .lock()
1455                        .expect("retired")
1456                        .clone()
1457                        .unwrap_or_else(|| "engine worker unavailable (respawn failed)".into());
1458                    let orphaned: Vec<GenerateJob> =
1459                        self.queues.iter_mut().flat_map(|q| q.jobs.drain(..)).collect();
1460                    for job in orphaned {
1461                        self.finish_failed(&job, &why);
1462                    }
1463                    for (_, _, cmd) in std::mem::take(&mut self.deferred_sync) {
1464                        self.dispatch(cmd);
1465                    }
1466                    continue;
1467                }
1468            }
1469            self.pins.sweep(&mut self.host, &self.stats);
1470            let tick_span = if self.lanes.is_empty() && self.queued() == 0 {
1471                tracing::Span::none()
1472            } else {
1473                tracing::debug_span!(
1474                    parent: None,
1475                    "tick",
1476                    tick_seq = tracing::field::Empty,
1477                    no_tick = tracing::field::Empty,
1478                    lanes = tracing::field::Empty,
1479                    queued = self.queued(),
1480                    model = self.trace_scope.model.as_str(),
1481                    store_ns = self.trace_scope.ns
1482                )
1483            };
1484            let _tick = tick_span.enter();
1485            self.poll_os_pressure();
1486            self.preempt_for_queued();
1487            self.admit_from_queue();
1488            self.run_deferred_syncs();
1489            let ticks_before = self.tick_count;
1490            let tick_result = self.tick_once(&tick_span);
1491            if !tick_span.is_disabled() {
1492                if self.tick_count != ticks_before {
1493                    tick_span.record("tick_seq", self.tick_count);
1494                } else {
1495                    tick_span.record("no_tick", true);
1496                }
1497            }
1498            if tick_result.is_ok() {
1499                self.respawn_streak = 0;
1500            }
1501            if let Err(e) = tick_result {
1502                let admitted: Vec<u64> = self
1503                    .lanes
1504                    .iter()
1505                    .filter(|l| l.admitted)
1506                    .map(|l| l.tag)
1507                    .collect();
1508                if !admitted.is_empty() {
1509                    let mut p = linkw::TickPlanMsg {
1510                        plan_seq: {
1511                            self.plan_seq += 1;
1512                            self.plan_seq
1513                        },
1514                        flags: 0,
1515                        prefill_token_budget: PREFILL_BUDGET,
1516                        max_decode_lanes: self.max_lanes as u32,
1517                        admits: vec![],
1518                        commits: vec![],
1519                        prefills: vec![],
1520                        decodes: vec![],
1521                        retires: vec![],
1522                        shed_policy: linkw::ShedPolicyMsg {
1523                            victim_lanes: vec![],
1524                            evictable_cache_classes: u64::MAX,
1525                            protected_quota_bytes: 0,
1526                            max_evict_bytes: u64::MAX,
1527                        },
1528                    };
1529                    for tag in admitted {
1530                        p.retires.push(linkw::LaneRetireMsg {
1531                            lane_tag: tag,
1532                            publish_to_cache: true,
1533                        });
1534                    }
1535                    self.stats.set_tick_in_flight(TickInFlight {
1536                        since_ms: uptime_ms().max(1),
1537                        retires: p.retires.len() as u64,
1538                        target_ms: self.stats.tick_target_live_ms.load(Ordering::Relaxed),
1539                        ..Default::default()
1540                    });
1541                    let _ = self.host.client().tick(p);
1542                    self.stats.clear_tick_in_flight();
1543                }
1544                let msg = format!("scheduler tick failed: {e}");
1545                let drained: Vec<u64> = self.lanes.iter().map(|l| l.session).collect();
1546                for lane in self.lanes.drain(..) {
1547                    self.active.lock().expect("active set").remove(&lane.session);
1548                    let _ = lane.job.events.send(LaneEvent::Failed(msg.clone()));
1549                }
1550                for session in drained {
1551                    self.drop_ephemeral_artifact(session);
1552                }
1553                self.publish_occupancy();
1554                if worker_is_gone(&e) && self.host.is_process() {
1555                    let mut fresh = false;
1556                    if self.respawn_streak < MAX_WORKER_RESPAWNS {
1557                        self.respawn_streak += 1;
1558                        self.stats.worker_respawns.fetch_add(1, Ordering::Relaxed);
1559                        match self.respawn_worker().and_then(|()| self.speculation.reregister(&mut self.host)) {
1560                            Ok(()) => {
1561                                eprintln!("[superfluid] engine worker died ({e}); respawned a fresh one");
1562                                self.after_respawn();
1563                                fresh = true;
1564                            }
1565                            Err(err) => eprintln!(
1566                                "[superfluid] engine worker died ({e}); respawn FAILED: {err}"
1567                            ),
1568                        }
1569                    }
1570                    if !fresh && self.respawn_streak >= MAX_WORKER_RESPAWNS {
1571                        self.give_up_on_worker();
1572                    }
1573                }
1574            }
1575            self.pins.sweep(&mut self.host, &self.stats);
1576        }
1577    }
1578
1579    fn dispatch(&mut self, cmd: SchedCmd) {
1580        match cmd {
1581            SchedCmd::Submit(job) => self.enqueue(*job),
1582            SchedCmd::Sync(req, reply) => self.run_sync(req, reply),
1583            SchedCmd::SyncStream(req, segs, reply) => self.run_sync_stream(req, segs, reply),
1584            SchedCmd::Pin(req) => self.pins.handle(&mut self.host, &self.stats, self.tick_count, req),
1585            SchedCmd::PinHeld(keys, reply) => {
1586                let _ = reply.send(self.pins.held_max(&self.host, &keys));
1587            }
1588            SchedCmd::PinUnanchor(session) => self.pins.unanchor(session),
1589            SchedCmd::SyncAs(class, inner) => {
1590                self.deferred_sync.push_back((class, self.tick_count, *inner));
1591            }
1592            SchedCmd::Shutdown => {}
1593        }
1594    }
1595
1596    fn sync_must_wait(&self, class: u8) -> bool {
1597        self.lanes.iter().any(|l| l.job.class < class)
1598            || self.queues.iter().take(class as usize).any(|q| !q.jobs.is_empty())
1599    }
1600
1601    fn run_deferred_syncs(&mut self) {
1602        let period = self.starvation_ticks as u64;
1603        let stuck = self.lanes.is_empty() && !self.admit_retry;
1604        let starved = |queued: u64| self.tick_count.saturating_sub(queued) >= period;
1605        let pick = self
1606            .deferred_sync
1607            .iter()
1608            .enumerate()
1609            .filter(|(_, (class, queued, _))| stuck || !self.sync_must_wait(*class) || starved(*queued))
1610            .min_by_key(|&(i, (class, queued, _))| {
1611                let aged = starved(*queued);
1612                (!aged, if aged { 0 } else { self.sync_rank(*class) }, i)
1613            })
1614            .map(|(i, _)| i);
1615        if let Some((_, _, cmd)) = pick.and_then(|i| self.deferred_sync.remove(i)) {
1616            self.dispatch(cmd);
1617        }
1618    }
1619
1620    fn sync_rank(&self, class: u8) -> usize {
1621        class as usize
1622    }
1623
1624    fn run_sync(&mut self, req: linkw::StateSyncReqMsg, reply: Sender<Result<linkw::StateSyncOkMsg, String>>) {
1625        let res = if self.host.is_alive() {
1626            self.host.client().state_sync(req).map_err(|e| e.to_string())
1627        } else {
1628            Err("engine worker unavailable".into())
1629        };
1630        let _ = reply.send(res);
1631    }
1632
1633    fn run_sync_stream(
1634        &mut self,
1635        req: linkw::StateSyncReqMsg,
1636        segs: Sender<linkw::TranscribeSegmentMsg>,
1637        reply: Sender<Result<linkw::StateSyncOkMsg, String>>,
1638    ) {
1639        let res = if self.host.is_alive() {
1640            self.host
1641                .client()
1642                .state_sync_streaming(req, &mut |seg| {
1643                    segs.send(seg).is_ok()
1644                })
1645                .map_err(|e| e.to_string())
1646        } else {
1647            Err("engine worker unavailable".into())
1648        };
1649        let _ = reply.send(res);
1650    }
1651
1652    fn queued(&self) -> usize {
1653        self.queues.iter().map(|q| q.jobs.len()).sum()
1654    }
1655
1656    fn enqueue(&mut self, mut job: GenerateJob) {
1657        let c = (job.class as usize).min(3);
1658        job.queued_at = self.tick_count;
1659        self.queues[c].jobs.push_back(job);
1660    }
1661
1662    fn engine_exclusive(&self, job: &GenerateJob) -> bool {
1663        job.batch_invariant || (!job.media.is_empty() && self.host.has_recurrent_space())
1664    }
1665
1666    fn strategy_slot_for(speculation: &Speculation, job: &GenerateJob) -> u32 {
1667        if job.grammar_handle != 0 || job.extras.logit_bias_handle != 0 || job.extras.want_logprobs {
1668            0
1669        } else {
1670            speculation.slot_for(job.params.temperature != 0.0)
1671        }
1672    }
1673
1674    fn decode_replay_for(strategy_slot: u32, produced_before: u32) -> u32 {
1675        if strategy_slot == 0 { produced_before } else { 0 }
1676    }
1677
1678    fn prefill_end_of(speculation: &Speculation, lane: &Lane) -> u64 {
1679        let replay = Self::decode_replay_for(Self::strategy_slot_for(speculation, &lane.job), lane.produced_before);
1680        (lane.job.stream.len() as u64)
1681            .saturating_sub(replay as u64)
1682            .max(lane.prefilled)
1683    }
1684
1685    fn pop_admissible(&mut self) -> Option<GenerateJob> {
1686        if self.lanes.iter().any(|l| self.engine_exclusive(&l.job)) {
1687            return None;
1688        }
1689        let mut per_class = [0usize; 4];
1690        for l in &self.lanes {
1691            per_class[(l.job.class as usize).min(3)] += 1;
1692        }
1693        let now = self.tick_count;
1694        let period = self.starvation_ticks as u64;
1695        let mut best: Option<(u8, usize)> = None;
1696        let mut excl_waiting: Option<u8> = None;
1697        for (c, q) in self.queues.iter().enumerate() {
1698            let Some(head) = q.jobs.front() else {
1699                continue;
1700            };
1701            let cap = if self.class_lanes[c] == 0 {
1702                self.max_lanes
1703            } else {
1704                self.class_lanes[c]
1705            };
1706            if per_class[c] >= cap {
1707                continue;
1708            }
1709            let waited = now.saturating_sub(head.queued_at);
1710            let aged = (waited / period).min(c as u64) as u8;
1711            let eff = c as u8 - aged;
1712            if self.engine_exclusive(head) && !self.lanes.is_empty() {
1713                if excl_waiting.is_none_or(|b| eff < b) {
1714                    excl_waiting = Some(eff);
1715                }
1716                continue;
1717            }
1718            if best.is_none_or(|(b, _)| eff < b) {
1719                best = Some((eff, c));
1720            }
1721        }
1722        if let (Some(e), Some((b, _))) = (excl_waiting, best) {
1723            if e <= b {
1724                return None;
1725            }
1726        }
1727        let (_, c) = best?;
1728        self.queues[c].jobs.pop_front()
1729    }
1730
1731    /// Whether `job` should wait for a lane that is prefilling a long prefix
1732    /// it shares and will publish that prefix to the cache shortly.
1733    fn awaits_a_shared_prefix(&self, job: &GenerateJob) -> bool {
1734        if job.carry.is_some()
1735            || job.class <= crate::qos::INLINE_COMPLETION
1736            || !job.media.is_empty()
1737            || self.tick_count.saturating_sub(job.queued_at) >= SHARED_PREFIX_MAX_HOLD_TICKS
1738            || self.host.has_unservable_space()
1739            || !self.host.cache_serves_all_spaces()
1740        {
1741            return false;
1742        }
1743        let Some(page) = self.host.page_size().filter(|&p| p > 0) else {
1744            return false;
1745        };
1746        if self.host.needs_prefix_checkpoints() {
1747            if self.prefix_checkpoints_off {
1748                return false;
1749            }
1750            // A retired lane's entry covers its reply and seeds nothing that
1751            // only shares its prompt: wait instead for the state kept where
1752            // the shared part ends, while a lane can still stop there.
1753            return self.lanes.iter().any(|l| {
1754                if l.preempted || l.job.completion || !l.job.media.is_empty() {
1755                    return false;
1756                }
1757                let shared = job.stream.iter().zip(&l.all_tokens).take_while(|(a, b)| a == b).count() as u64;
1758                let total = Self::prefill_end_of(&self.speculation, l);
1759                shared >= SHARED_PREFIX_MIN_TOKENS && shared + 1 < total && !Self::kept_for(l, shared) && l.prefilled < shared
1760            });
1761        }
1762        self.lanes.iter().any(|l| {
1763            if l.preempted
1764                || l.parked_lossy
1765                || l.job.completion
1766                || !l.job.media.is_empty()
1767                || l.job.max_tokens.saturating_sub(l.produced) > SHARED_PREFIX_TAIL_TOKENS
1768            {
1769                return false;
1770            }
1771            let shared = job.stream.iter().zip(&l.all_tokens).take_while(|(a, b)| a == b).count() as u64;
1772            let shared = shared / page * page;
1773            shared >= SHARED_PREFIX_MIN_TOKENS && shared > l.warm_prefix
1774        })
1775    }
1776
1777    /// On a runtime whose state cannot be cut back, a prefilling lane stops
1778    /// where the requests queued behind it stop sharing its prompt (the
1779    /// shortest such share of at least SHARED_PREFIX_MIN_TOKENS), so its
1780    /// state there can be kept and seed them all; and where a chat's input
1781    /// ends, before the generation prompt. The next turn renders that input
1782    /// again but not the generation prompt as it was (a template drops the
1783    /// reasoning opener from an assistant turn in history), so the state the
1784    /// executor keeps where the prompt ends is no prefix of it. Of the points
1785    /// past what is prefilled, the nearest comes first; the next is planned
1786    /// once the prefill has passed it.
1787    fn plan_prefix_checkpoints(&mut self) {
1788        if !self.host.needs_prefix_checkpoints() || self.prefix_checkpoints_off {
1789            for l in self.lanes.iter_mut() {
1790                l.checkpoint_at = 0;
1791            }
1792            return;
1793        }
1794        // A prefill that stopped where the input ends without planning to
1795        // (a shed chunk planned again from there, or a budget that ran out
1796        // there) keeps the state there before it goes on.
1797        let mut landed = false;
1798        for l in self.lanes.iter_mut() {
1799            if Self::turn_point(&self.speculation, l) == Some(l.prefilled) {
1800                (l.checkpoint_at, l.checkpoint_due, landed) = (l.prefilled, true, true);
1801            }
1802        }
1803        if landed {
1804            self.publish_prefix_checkpoints();
1805        }
1806        for i in 0..self.lanes.len() {
1807            let l = &self.lanes[i];
1808            let total = Self::prefill_end_of(&self.speculation, l);
1809            let mut at = 0u64;
1810            if Self::checkpoint_eligible(l, total) {
1811                for job in self.queues.iter().flat_map(|q| q.jobs.iter()) {
1812                    let shared = job.stream.iter().zip(&l.all_tokens).take_while(|(a, b)| a == b).count() as u64;
1813                    let useful = shared >= SHARED_PREFIX_MIN_TOKENS
1814                        && shared + 1 < total
1815                        && shared > l.prefilled
1816                        && !Self::kept_for(l, shared);
1817                    if useful && (at == 0 || shared < at) {
1818                        at = shared;
1819                    }
1820                }
1821                if let Some(end) = Self::turn_point(&self.speculation, l).filter(|&end| end > l.prefilled) {
1822                    if at == 0 || end < at {
1823                        at = end;
1824                    }
1825                }
1826            }
1827            self.lanes[i].checkpoint_at = at;
1828        }
1829    }
1830
1831    fn checkpoint_eligible(l: &Lane, total: u64) -> bool {
1832        !l.preempted
1833            && !l.job.completion
1834            && l.job.media.is_empty()
1835            && !matches!(l.phase, LanePhase::Finishing)
1836            && l.prefilled < total
1837    }
1838
1839    /// Where this lane's state is kept for the chat's next turn: the end of the input,
1840    /// unless the lane started there or already prefilled past it.
1841    fn turn_point(speculation: &Speculation, l: &Lane) -> Option<u64> {
1842        let end = l.job.input_end;
1843        let total = Self::prefill_end_of(speculation, l);
1844        (Self::checkpoint_eligible(l, total)
1845            && !l.turn_kept
1846            && end >= TURN_CHECKPOINT_MIN_TOKENS
1847            && end + 1 < total
1848            && end >= l.prefilled
1849            && end != l.warm_prefix)
1850            .then_some(end)
1851    }
1852
1853    /// Whether a state that seeds a request sharing `shared` tokens of this
1854    /// lane's prompt is in the cache already: the lane kept one, or started
1855    /// from one.
1856    fn kept_for(l: &Lane, shared: u64) -> bool {
1857        let serves = |at: u64| at >= SHARED_PREFIX_MIN_TOKENS && at <= shared;
1858        serves(l.checkpointed) || serves(l.warm_prefix)
1859    }
1860
1861    /// Keeps a copy of each lane's state where this tick's prefill stopped
1862    /// for a checkpoint, in the prefix cache under the tokens it covers.
1863    fn publish_prefix_checkpoints(&mut self) {
1864        for i in 0..self.lanes.len() {
1865            if !std::mem::take(&mut self.lanes[i].checkpoint_due) {
1866                continue;
1867            }
1868            let (tag, at) = (self.lanes[i].tag, self.lanes[i].checkpoint_at);
1869            let tokens = self.lanes[i].all_tokens[..at as usize].to_vec();
1870            let turn = at == self.lanes[i].job.input_end;
1871            self.lanes[i].turn_kept |= turn;
1872            // A turn's checkpoint serves the chat's next turn alone, and goes
1873            // before a prefix requests share when the cache is short of room.
1874            match self.fork_into_cache(tag, &tokens, turn) {
1875                Ok(()) => {
1876                    self.lanes[i].checkpointed = at;
1877                    if turn {
1878                        self.supersede_turn_checkpoints(&tokens);
1879                    }
1880                }
1881                Err(CheckpointError::Refused(e)) => {
1882                    tracing::info!(lane = tag, "the engine forks no lane's sequence ({e}): no shared-prefix checkpoints");
1883                    self.prefix_checkpoints_off = true;
1884                }
1885                Err(CheckpointError::Failed(e)) => tracing::debug!(lane = tag, at, "prefix checkpoint not kept: {e}"),
1886            }
1887        }
1888    }
1889
1890    /// Records the turn checkpoint just kept under `tokens`, and drops those
1891    /// it extends: earlier turns of the same chat.
1892    fn supersede_turn_checkpoints(&mut self, tokens: &[u32]) {
1893        let digest = superfluid_fingerprint::content_digest(tokens);
1894        let mut kept = std::collections::VecDeque::with_capacity(self.turn_checkpoints.len() + 1);
1895        for (len, d) in std::mem::take(&mut self.turn_checkpoints) {
1896            let n = len as usize;
1897            if n == tokens.len() && d == digest {
1898                continue;
1899            }
1900            if n < tokens.len() && superfluid_fingerprint::content_digest(&tokens[..n]) == d {
1901                self.unpublish(&tokens[..n]);
1902                continue;
1903            }
1904            kept.push_back((len, d));
1905        }
1906        kept.push_back((tokens.len() as u64, digest));
1907        while kept.len() > TURN_CHECKPOINTS_TRACKED {
1908            kept.pop_front();
1909        }
1910        self.turn_checkpoints = kept;
1911    }
1912
1913    fn unpublish(&mut self, tokens: &[u32]) {
1914        let res = match self.stage(tokens) {
1915            Ok(span) => self.host.client().state_sync(linkw::StateSyncReqMsg::Unpublish { span }).map_err(|e| e.to_string()),
1916            Err(e) => Err(e.to_string()),
1917        };
1918        if let Err(e) = res {
1919            tracing::debug!(len = tokens.len(), "superseded turn checkpoint not dropped: {e}");
1920        }
1921    }
1922
1923    fn fork_into_cache(&mut self, tag: u64, tokens: &[u32], private: bool) -> Result<(), CheckpointError> {
1924        use CheckpointError::Failed;
1925        let seq = match self.host.client().state_sync(linkw::StateSyncReqMsg::LaneSequence { lane_tag: tag }) {
1926            Ok(linkw::StateSyncOkMsg::LaneSequence { sequence }) => sequence,
1927            Ok(_) => return Err(Failed("mismatched LaneSequence reply".into())),
1928            Err(e) => return Err(CheckpointError::of(e)),
1929        };
1930        let child = match self.host.client().state_sync(linkw::StateSyncReqMsg::Fork {
1931            parent_sequence: seq,
1932            flags: if private { superfluid_abi::fork_flags::PRIVATE } else { 0 },
1933        }) {
1934            Ok(linkw::StateSyncOkMsg::Fork { child_sequence }) => child_sequence,
1935            Ok(_) => return Err(Failed("mismatched Fork reply".into())),
1936            Err(e) => return Err(CheckpointError::of(e)),
1937        };
1938        let published = match self.stage(tokens) {
1939            Ok(span) => match self.host.client().state_sync(linkw::StateSyncReqMsg::PublishSequence { sequence: child, span }) {
1940                Ok(linkw::StateSyncOkMsg::PublishSequence) => Ok(()),
1941                Ok(_) => Err("mismatched PublishSequence reply".into()),
1942                Err(e) => Err(e.to_string()),
1943            },
1944            Err(e) => Err(e.to_string()),
1945        };
1946        if published.is_err() {
1947            let _ = self.host.client().state_sync(linkw::StateSyncReqMsg::FreeSequence { sequence: child });
1948        }
1949        published.map_err(Failed)
1950    }
1951
1952    /// Whether a chat or inline completion is queued, being served, or arrived within
1953    /// LATENCY_HOLD: such a tick stays short.
1954    fn latency_sensitive(&self) -> bool {
1955        self.cancels.latency_recent(LATENCY_HOLD)
1956            || self.queues[..=crate::qos::INLINE_COMPLETION as usize].iter().any(|q| !q.jobs.is_empty())
1957            || self.lanes.iter().any(|l| l.job.class <= crate::qos::INLINE_COMPLETION && !l.preempted)
1958    }
1959
1960    fn joiner_waits_on_this_tick(&self) -> bool {
1961        if self.max_lanes <= 1 {
1962            return false;
1963        }
1964        self.live_lanes() < self.max_lanes
1965            || self.queues[..=crate::qos::INLINE_COMPLETION as usize]
1966                .iter()
1967                .any(|q| !q.jobs.is_empty())
1968            || self.lanes.iter().any(|l| {
1969                l.job.class > crate::qos::INLINE_COMPLETION
1970                    && !l.preempted
1971                    && l.job.media.is_empty()
1972                    && !self.engine_exclusive(&l.job)
1973            })
1974    }
1975
1976    /// Lanes holding a slot past this tick: a preempted lane retires in it,
1977    /// and the engine retires before it admits, so its slot is already free.
1978    fn live_lanes(&self) -> usize {
1979        self.lanes
1980            .iter()
1981            .filter(|l| !(l.preempted && matches!(l.phase, LanePhase::Finishing)))
1982            .count()
1983    }
1984
1985    fn preempt_for_queued(&mut self) {
1986        if self.live_lanes() < self.max_lanes {
1987            return;
1988        }
1989        let Some(want) = self
1990            .queues
1991            .iter()
1992            .enumerate()
1993            .find(|(_, q)| !q.jobs.is_empty())
1994            .map(|(c, _)| c as u8)
1995        else {
1996            return;
1997        };
1998        if want > crate::qos::INLINE_COMPLETION {
1999            return;
2000        }
2001        // A decoding lane first; else one still prefilling, whose prefilled
2002        // pages go to the prefix cache on retire and seed it when it resumes.
2003        let victim_in = |phase_decoding: bool| {
2004            self.lanes
2005                .iter()
2006                .enumerate()
2007                .filter(|(_, l)| {
2008                    l.admitted
2009                        && !l.preempted
2010                        && !self.engine_exclusive(&l.job)
2011                        && l.job.class > want
2012                        && l.job.media.is_empty()
2013                        && if phase_decoding {
2014                            matches!(l.phase, LanePhase::Decoding)
2015                        } else {
2016                            matches!(l.phase, LanePhase::Prefilling)
2017                        }
2018                })
2019                .max_by_key(|(_, l)| (l.job.class, l.tag))
2020                .map(|(i, _)| i)
2021        };
2022        let victim = victim_in(true).or_else(|| victim_in(false));
2023        if let Some(i) = victim {
2024            self.preempt_lane(i);
2025        }
2026    }
2027
2028    fn preempt_lane(&mut self, i: usize) {
2029        let lane = &mut self.lanes[i];
2030        lane.preempted = true;
2031        lane.phase = LanePhase::Finishing;
2032        lane.fin = finish::NONE;
2033        tracing::info!(
2034            session = lane.session,
2035            lane = lane.tag,
2036            class = lane.job.class,
2037            produced = lane.produced,
2038            "lane preempted"
2039        );
2040        self.stats.preemptions.fetch_add(1, Ordering::Relaxed);
2041    }
2042
2043    fn poll_os_pressure(&mut self) {
2044        let Some(src) = self.pressure_source.as_mut() else {
2045            return;
2046        };
2047        match src.level() {
2048            crate::pressure::PressureLevel::Normal => {}
2049            crate::pressure::PressureLevel::Warning => {
2050                self.stats.os_pressure_events.fetch_add(1, Ordering::Relaxed);
2051                self.relief_round_now();
2052            }
2053            crate::pressure::PressureLevel::Critical => {
2054                self.stats.os_pressure_events.fetch_add(1, Ordering::Relaxed);
2055                self.relief_round_now();
2056                let victims: Vec<usize> = self
2057                    .lanes
2058                    .iter()
2059                    .enumerate()
2060                    .filter(|(_, l)| {
2061                        l.admitted
2062                            && !l.preempted
2063                            && l.job.class == crate::qos::BACKGROUND_AGENT
2064                            && matches!(l.phase, LanePhase::Decoding)
2065                    })
2066                    .map(|(i, _)| i)
2067                    .collect();
2068                for i in victims {
2069                    self.preempt_lane(i);
2070                }
2071            }
2072        }
2073    }
2074
2075    fn relief_round_now(&mut self) {
2076        // Memory is short on the machine: what the cache holds in host memory
2077        // goes first, whatever the pool holds.
2078        self.evict_cache(0, HOST_EXPORT_CLASS);
2079        if let Ok(pong) = self.host.client().ping() {
2080            let mem = pong.mem;
2081            let low = self.pressure_low_pct as u64;
2082            let (used, total) = (mem.pool_blocks_used, mem.pool_blocks_total);
2083            if total == 0 || used * 100 <= total * low {
2084                return;
2085            }
2086            let saved = self.pressure_high_pct;
2087            self.pressure_high_pct = 1;
2088            self.relieve_pressure(&mem, true);
2089            self.pressure_high_pct = saved;
2090        }
2091    }
2092
2093    fn after_respawn(&mut self) {
2094        self.max_lanes = lane_cap(
2095            self.configured_lanes,
2096            self.host.max_seqs(),
2097            ring_slots(&mut self.host, superfluid_shm::TOKEN_RING_OUT),
2098        );
2099        self.pins.set_fallback_pool_tokens(self.host.max_stream_tokens() * self.max_lanes as u64);
2100        if let Some(why) = self.host.take_record_change() {
2101            self.retire(&why);
2102        }
2103    }
2104
2105    // Respawning stops after a streak; without a retire nothing would load
2106    // the model again and every request would fail until a restart.
2107    fn give_up_on_worker(&mut self) {
2108        if self.retired.lock().expect("retired").is_none() {
2109            self.retire(&format!(
2110                "the engine worker died and {MAX_WORKER_RESPAWNS} respawns in a row did not keep one up"
2111            ));
2112        }
2113    }
2114
2115    fn retire(&mut self, why: &str) {
2116        let msg = format!("{why}; the model loads again on its next request, so retry");
2117        eprintln!("[superfluid] {msg}");
2118        *self.retired.lock().expect("retired") = Some(msg.clone());
2119        let drained: Vec<u64> = self.lanes.iter().map(|l| l.session).collect();
2120        for lane in self.lanes.drain(..) {
2121            self.active.lock().expect("active set").remove(&lane.session);
2122            let _ = lane.job.events.send(LaneEvent::Failed(msg.clone()));
2123        }
2124        for session in drained {
2125            self.drop_ephemeral_artifact(session);
2126        }
2127        let queued: Vec<GenerateJob> = self.queues.iter_mut().flat_map(|q| q.jobs.drain(..)).collect();
2128        for job in queued {
2129            self.finish_failed(&job, &msg);
2130        }
2131    }
2132
2133    fn stage(&mut self, tokens: &[u32]) -> Result<linkw::TokenRefMsg, superfluid_agent::AgentError> {
2134        self.staged_unread += 1;
2135        self.host.client().stage_prompt(tokens)
2136    }
2137
2138    fn admit_from_queue(&mut self) {
2139        self.admit_retry = false;
2140        let retired = self.retired.lock().expect("retired").clone();
2141        if let Some(msg) = retired {
2142            let queued: Vec<GenerateJob> = self.queues.iter_mut().flat_map(|q| q.jobs.drain(..)).collect();
2143            for job in queued {
2144                self.finish_failed(&job, &msg);
2145            }
2146            return;
2147        }
2148        let mut held: Vec<GenerateJob> = Vec::new();
2149        if self.lanes.iter().all(|l| l.admitted) {
2150            self.staged_unread = 0;
2151        }
2152        let stage_slots = ring_slots(&mut self.host, superfluid_shm::TOKEN_RING_IN).unwrap_or(u32::MAX);
2153        while self.live_lanes() < self.max_lanes {
2154            // A staged prompt is read when its tick admits it; staging past
2155            // the ring's size would overwrite one still waiting, and fail the
2156            // tick. The rest wait a tick in their queue.
2157            if self.staged_unread > 0 && self.staged_unread.saturating_add(STAGES_PER_ADMISSION) > stage_slots {
2158                break;
2159            }
2160            let Some(job) = self.pop_admissible() else {
2161                break;
2162            };
2163            if job.stream.is_empty() {
2164                self.finish_failed(&job, "session has no content to generate from");
2165                continue;
2166            }
2167            if self.awaits_a_shared_prefix(&job) {
2168                held.push(job);
2169                continue;
2170            }
2171            if let Some(deadline) = job.deadline {
2172                if std::time::Instant::now() > deadline {
2173                    let _ = job.events.send(LaneEvent::Expired);
2174                    self.active.lock().expect("active set").remove(&job.session);
2175                    self.drop_ephemeral_artifact(job.session);
2176                    continue;
2177                }
2178            }
2179            let max_stream = self.host.max_stream_tokens();
2180            if job.stream.len() as u64 > max_stream {
2181                let msg = DaemonError::StreamTooLong {
2182                    len: job.stream.len() as u64,
2183                    max: max_stream,
2184                }
2185                .to_string();
2186                self.finish_failed(&job, &msg);
2187                continue;
2188            }
2189            let span = match self.stage(&job.stream) {
2190                Ok(s) => s,
2191                Err(e) => {
2192                    let transport = matches!(
2193                        e,
2194                        superfluid_agent::AgentError::Io(_)
2195                            | superfluid_agent::AgentError::Closed
2196                            | superfluid_agent::AgentError::Shm(_)
2197                    );
2198                    if transport && self.host.is_process() && self.respawn_streak < MAX_WORKER_RESPAWNS {
2199                        self.respawn_streak += 1;
2200                        eprintln!(
2201                            "[superfluid] engine worker gone at admission ({e}); respawning"
2202                        );
2203                        self.stats.worker_respawns.fetch_add(1, Ordering::Relaxed);
2204                        if self
2205                            .respawn_worker()
2206                            .and_then(|()| self.speculation.reregister(&mut self.host))
2207                            .is_ok()
2208                        {
2209                            let c = (job.class as usize).min(3);
2210                            self.queues[c].jobs.push_front(job);
2211                            self.after_respawn();
2212                            self.admit_retry = true;
2213                            break;
2214                        }
2215                    }
2216                    self.finish_failed(&job, &format!("stage failed: {e}"));
2217                    continue;
2218                }
2219            };
2220            let mut seed_handle = 0u64;
2221            let mut warm_prefix = 0u64;
2222            if self.host.has_unservable_space() {
2223                self.stats
2224                    .unservable_cold_admissions
2225                    .fetch_add(1, Ordering::Relaxed);
2226            } else if let Some(page) = self.host.page_size() {
2227                if self.host.cache_serves_all_spaces() && job.media.is_empty() {
2228                if let Ok(m) = self.host.client().match_prefix(ALL_SPACES, span) {
2229                    let align_down = |l: u64| l.checked_div(page).unwrap_or(0) * page;
2230                    let replay = Self::decode_replay_for(
2231                        Self::strategy_slot_for(&self.speculation, &job),
2232                        job.carry.as_ref().map(|c| c.produced_before).unwrap_or(0),
2233                    );
2234                    let stream_len = job.stream.len() as u64;
2235                    let aligned_cap =
2236                        align_down(stream_len.saturating_sub(1).min(stream_len.saturating_sub(replay as u64)));
2237                    let best = crate::pins::common_seed_lengths(&self.host, &m, aligned_cap)
2238                        .first()
2239                        .copied()
2240                        .unwrap_or(0);
2241                    if best >= page {
2242                        if let Ok(h) = self.host.client().seed_acquire(span, best, 0) {
2243                            seed_handle = h;
2244                            warm_prefix = best;
2245                        }
2246                    }
2247                }
2248                }
2249                (seed_handle, warm_prefix) =
2250                    self.resume_from_park(&job, span, page, seed_handle, warm_prefix);
2251            }
2252            if warm_prefix > 0 {
2253                self.stats
2254                    .warm_prefix_tokens
2255                    .fetch_add(warm_prefix, Ordering::Relaxed);
2256            } else {
2257                self.stats.cold_admissions.fetch_add(1, Ordering::Relaxed);
2258            }
2259            let tag = self.next_lane;
2260            self.next_lane += 1;
2261            let mut job = job;
2262            let mut media_ok = true;
2263            let mut bound: Vec<u64> = Vec::new();
2264            for (offset, path, _n) in job.media.iter().filter(|(o, _, _)| *o >= warm_prefix) {
2265                let path_s = path.to_string_lossy().into_owned();
2266                let enc = self.host.client().state_sync(linkw::StateSyncReqMsg::MediaEncode {
2267                    image_path: path_s.clone(),
2268                });
2269                let handle = match enc {
2270                    Ok(linkw::StateSyncOkMsg::MediaEncode { media_handle, .. }) => media_handle,
2271                    other => {
2272                        eprintln!("[superfluid] media encode failed for {path_s}: {other:?}");
2273                        media_ok = false;
2274                        break;
2275                    }
2276                };
2277                let bind = self.host.client().state_sync(linkw::StateSyncReqMsg::MediaBind {
2278                    lane_tag: tag,
2279                    media_handle: handle,
2280                    token_offset: *offset as u32,
2281                });
2282                if matches!(bind, Ok(linkw::StateSyncOkMsg::MediaBind)) {
2283                    bound.push(handle);
2284                } else {
2285                    let _ = self
2286                        .host
2287                        .client()
2288                        .state_sync(linkw::StateSyncReqMsg::MediaRelease { media_handle: handle });
2289                    media_ok = false;
2290                    break;
2291                }
2292            }
2293            if !media_ok {
2294                for handle in &bound {
2295                    let _ = self.host.client().state_sync(
2296                        linkw::StateSyncReqMsg::MediaRelease { media_handle: *handle },
2297                    );
2298                }
2299                if seed_handle != 0 {
2300                    let _ = self.host.client().seed_release(seed_handle);
2301                }
2302                self.finish_failed(&job, "media encode/bind failed (see daemon log)");
2303                continue;
2304            }
2305            let spec_before = job.carry.as_ref().map(|c| c.spec_before).unwrap_or_default();
2306            let decode_before = job.carry.as_ref().map(|c| c.decode_before).unwrap_or_default();
2307            let (seg, utf8, disp_seg, disp_utf8, tool_buf, produced_before, warm_prefix_first) =
2308                match job.carry.take() {
2309                    Some(c) => (
2310                        c.seg,
2311                        c.utf8,
2312                        c.disp_seg,
2313                        c.disp_utf8,
2314                        c.tool_buf,
2315                        c.produced_before,
2316                        c.warm_prefix_first,
2317                    ),
2318                    None => {
2319                        let mk = || {
2320                            let mut seg = self.codec.channelizer();
2321                            if job.extras.open_channel != 0 {
2322                                seg.prime(job.extras.open_channel);
2323                            }
2324                            seg
2325                        };
2326                        (
2327                            mk(),
2328                            Utf8Stream::default(),
2329                            mk(),
2330                            Utf8Stream::default(),
2331                            String::new(),
2332                            0,
2333                            None,
2334                        )
2335                    }
2336                };
2337            let all_tokens = job.stream.clone();
2338            self.lanes.push(Lane {
2339                tag,
2340                session: job.session,
2341                job,
2342                phase: LanePhase::Prefilling,
2343                prefilled: warm_prefix,
2344                produced: 0,
2345                fin: finish::NONE,
2346                warm_prefix,
2347                utf8,
2348                seg,
2349                disp_seg,
2350                disp_utf8,
2351                frame_fed: 0,
2352                emit_dead: false,
2353                tool_buf,
2354                admitted: false,
2355                seed_handle,
2356                span,
2357                all_tokens,
2358                parked: false,
2359                parked_lossy: false,
2360                preempted: false,
2361                starved_ticks: 0,
2362                prefill_starved_ticks: 0,
2363                checkpoint_at: 0,
2364                checkpoint_due: false,
2365                checkpointed: 0,
2366                turn_kept: false,
2367                produced_before,
2368                disp_tokens: produced_before,
2369                spec: spec_before,
2370                decode: decode_before,
2371                decode_ticks: None,
2372                warm_prefix_first,
2373                admitted_at: std::time::Instant::now(),
2374                ttft_observed: false,
2375                ttft_frame_ms: None,
2376                fault_code: None,
2377            });
2378        }
2379        for job in held.into_iter().rev() {
2380            let c = (job.class as usize).min(3);
2381            self.queues[c].jobs.push_front(job);
2382        }
2383    }
2384
2385    fn resume_from_park(
2386        &mut self,
2387        job: &GenerateJob,
2388        span: linkw::TokenRefMsg,
2389        page: u64,
2390        seed_handle: u64,
2391        warm_prefix: u64,
2392    ) -> (u64, u64) {
2393        let Some(dir) = self.park_dir.clone() else {
2394            return (seed_handle, warm_prefix);
2395        };
2396        let Some(p) = park::read(&dir, job.session) else {
2397            return (seed_handle, warm_prefix);
2398        };
2399        if p.multi_space {
2400            if !self.host.multi_space() && self.host.parkable_spaces().len() != p.spaces.len() {
2401                return (seed_handle, warm_prefix);
2402            }
2403            return match self.resume_adopt(job, &p) {
2404                Ok((h, covered)) => {
2405                    if seed_handle != 0 {
2406                        let _ = self.host.client().seed_release(seed_handle);
2407                    }
2408                    self.stats.resumes.fetch_add(1, Ordering::Relaxed);
2409                    (h, covered)
2410                }
2411                Err(msg) => {
2412                    eprintln!("[superfluid] park resume fell back cold (session {}): {msg}", job.session);
2413                    (seed_handle, warm_prefix)
2414                }
2415            };
2416        }
2417        if self.host.multi_space() || !job.media.is_empty() {
2418            return (seed_handle, warm_prefix);
2419        }
2420        let align_down = |l: u64| l.checked_div(page).unwrap_or(0) * page;
2421        let aligned_cap = align_down(job.stream.len() as u64 - 1);
2422        let usable = p.covered.min(aligned_cap);
2423        if p.covered % page != 0
2424            || usable <= warm_prefix
2425            || p.covered as usize > job.stream.len()
2426            || park::stream_digest(&job.stream[..p.covered as usize]) != p.stream_digest
2427        {
2428            return (seed_handle, warm_prefix);
2429        }
2430        let covered_span = match self.stage(&job.stream[..p.covered as usize]) {
2431            Ok(s) => s,
2432            Err(e) => {
2433                eprintln!(
2434                    "[superfluid] park resume fell back cold (session {}): stage: {e}",
2435                    job.session
2436                );
2437                return (seed_handle, warm_prefix);
2438            }
2439        };
2440        if let Err(msg) = self.restore_to_cache(job.session, &p, covered_span) {
2441            eprintln!(
2442                "[superfluid] park resume fell back cold (session {}): {msg}",
2443                job.session
2444            );
2445            return (seed_handle, warm_prefix);
2446        }
2447        match self.host.client().seed_acquire(span, usable, 0) {
2448            Ok(h) => {
2449                if seed_handle != 0 {
2450                    let _ = self.host.client().seed_release(seed_handle);
2451                }
2452                self.stats.resumes.fetch_add(1, Ordering::Relaxed);
2453                (h, usable)
2454            }
2455            Err(e) => {
2456                eprintln!(
2457                    "[superfluid] park resume restored but could not seed (session {}): {e}",
2458                    job.session
2459                );
2460                (seed_handle, warm_prefix)
2461            }
2462        }
2463    }
2464
2465    fn restore_to_cache(
2466        &mut self,
2467        session: u64,
2468        p: &park::Parked,
2469        span: linkw::TokenRefMsg,
2470    ) -> Result<(), String> {
2471        let _op_span = tracing::info_span!(
2472            "restore",
2473            session,
2474            model = self.trace_scope.model.as_str(),
2475            store_ns = self.trace_scope.ns,
2476            covered = p.covered,
2477            bytes = p.sealed.len(),
2478            encoding = p.encoding
2479        )
2480        .entered();
2481        let client = self.host.client();
2482        let seq = match client.state_sync(linkw::StateSyncReqMsg::CreateSequence) {
2483            Ok(linkw::StateSyncOkMsg::CreateSequence { sequence }) => sequence,
2484            Ok(_) => return Err("mismatched CreateSequence reply".into()),
2485            Err(e) => return Err(e.to_string()),
2486        };
2487        let free_seq = |client: &mut superfluid_agent::WorkerClient| {
2488            let _ = client.state_sync(linkw::StateSyncReqMsg::FreeSequence { sequence: seq });
2489        };
2490        let buf = match client.buf_create(p.sealed.len() as u64) {
2491            Ok(b) => b,
2492            Err(e) => {
2493                free_seq(client);
2494                return Err(e.to_string());
2495            }
2496        };
2497        let result = (|| {
2498            client
2499                .buf_write(buf, &p.sealed)
2500                .map_err(|e| e.to_string())?;
2501            let op = client
2502                .state_op(linkw::StateOpKind::Restore {
2503                    sequence: seq,
2504                    space_id: KV_SPACE,
2505                    buf_id: buf,
2506                })
2507                .map_err(|e| e.to_string())?;
2508            let done = client.wait_op_done(op).map_err(|e| e.to_string())?;
2509            if done.state != op_state::DONE || done.error != 0 {
2510                return Err(format!(
2511                    "restore terminal state {} error {}",
2512                    done.state, done.error
2513                ));
2514            }
2515            Ok(())
2516        })();
2517        let _ = client.buf_release(buf);
2518        if let Err(e) = result {
2519            free_seq(client);
2520            return Err(e);
2521        }
2522        match client.state_sync(linkw::StateSyncReqMsg::PublishSequence {
2523            sequence: seq,
2524            span,
2525        }) {
2526            Ok(linkw::StateSyncOkMsg::PublishSequence) => Ok(()),
2527            Ok(_) => {
2528                free_seq(client);
2529                Err("mismatched PublishSequence reply".into())
2530            }
2531            Err(e) => {
2532                free_seq(client);
2533                Err(e.to_string())
2534            }
2535        }
2536    }
2537
2538    fn relieve_pressure(&mut self, mem: &linkw::MemCountersMsg, host_short: bool) {
2539        let (high, low) = (self.pressure_high_pct as u64, self.pressure_low_pct as u64);
2540        if std::env::var_os("SUPERFLUID_DEBUG_PRESSURE").is_some() {
2541            eprintln!(
2542                "[pressure] used={} total={} evictable={}",
2543                mem.pool_blocks_used, mem.pool_blocks_total, mem.pool_bytes_evictable
2544            );
2545        }
2546        if mem.pool_blocks_total == 0 || high == 0 {
2547            return;
2548        }
2549        let Some(block_bytes) = self.host.kv_block_bytes() else {
2550            return;
2551        };
2552        let (used, total) = (mem.pool_blocks_used, mem.pool_blocks_total);
2553        if used * 100 < total * high {
2554            return;
2555        }
2556        let low_blocks = total * low / 100;
2557        let excess = used.saturating_sub(low_blocks);
2558        if excess == 0 {
2559            return;
2560        }
2561        let slack = total * block_bytes / 4;
2562        let mut evictable = mem.pool_bytes_evictable;
2563        let need = excess * block_bytes;
2564        if need > evictable && self.pins.any_held() {
2565            evictable += self
2566                .pins
2567                .yield_for(&mut self.host, &self.stats, need - evictable);
2568        }
2569        let bytes_target = (excess * block_bytes + slack).min(evictable.max(1));
2570        // The pool's pressure leaves what the cache holds in host memory
2571        // alone, and the entries that leave the pool may be kept there.
2572        let all = if host_short { u64::MAX } else { !HOST_EXPORT_CLASS };
2573        // The slack is taken from entries no two requests have shared; a
2574        // shared prefix goes only for what the pool needs.
2575        let mut freed = self.evict_cache(bytes_target, all & !SHARED_PREFIX_CLASS);
2576        if freed < need {
2577            freed += self.evict_cache(need - freed, all);
2578        }
2579        if freed < need && self.pins.any_held() {
2580            let yielded = self
2581                .pins
2582                .yield_for(&mut self.host, &self.stats, need - freed);
2583            if yielded > 0 {
2584                freed += self.evict_cache((need - freed + slack).min(yielded), all);
2585            }
2586        }
2587        if freed > 0 {
2588            self.stats.pressure_evictions.fetch_add(1, Ordering::Relaxed);
2589            self.stats
2590                .pressure_bytes_evicted
2591                .fetch_add(freed, Ordering::Relaxed);
2592        }
2593    }
2594
2595    fn evict_cache(&mut self, bytes_target: u64, classes: u64) -> u64 {
2596        let res = self
2597            .host
2598            .client()
2599            .state_sync(linkw::StateSyncReqMsg::CacheEvict {
2600                bytes_target,
2601                policy: linkw::ShedPolicyMsg {
2602                    victim_lanes: vec![],
2603                    evictable_cache_classes: classes,
2604                    protected_quota_bytes: 0,
2605                    max_evict_bytes: bytes_target,
2606                },
2607            });
2608        match res {
2609            Ok(linkw::StateSyncOkMsg::CacheEvict { bytes_freed }) => bytes_freed,
2610            _ => 0,
2611        }
2612    }
2613
2614    fn retry_park_removals(&mut self) {
2615        if self.park_removals_pending.is_empty() {
2616            return;
2617        }
2618        let (Some(dir), Some(tx)) = (self.park_dir.clone(), self.park_tx.as_ref()) else {
2619            self.park_removals_pending.clear();
2620            return;
2621        };
2622        self.park_removals_pending.retain(|&session| {
2623            matches!(
2624                tx.try_send(ParkTask::Remove {
2625                    dir: dir.clone(),
2626                    session,
2627                }),
2628                Err(std::sync::mpsc::TrySendError::Full(_))
2629            )
2630        });
2631    }
2632
2633    /// Lanes whose admission the engine could not take this tick go back to
2634    /// the head of their class queue, their seed leases released, to be
2635    /// admitted on a later tick.
2636    fn defer_admits(&mut self, tags: &[u64]) {
2637        let mut back: Vec<GenerateJob> = Vec::new();
2638        let mut seeds: Vec<u64> = Vec::new();
2639        let mut i = 0;
2640        while i < self.lanes.len() {
2641            if tags.contains(&self.lanes[i].tag) {
2642                let lane = self.lanes.remove(i);
2643                if lane.seed_handle != 0 {
2644                    seeds.push(lane.seed_handle);
2645                }
2646                back.push(lane.job);
2647            } else {
2648                i += 1;
2649            }
2650        }
2651        for seed in seeds {
2652            let _ = self.host.client().seed_release(seed);
2653        }
2654        for job in back.into_iter().rev() {
2655            let c = (job.class as usize).min(3);
2656            self.queues[c].jobs.push_front(job);
2657        }
2658    }
2659
2660    fn drop_refused(&mut self, refused: &HashMap<u64, u32>) {
2661        let mut gone: Vec<(u64, u64)> = Vec::new();
2662        let mut active = self.active.lock().expect("active set");
2663        self.lanes.retain(|lane| {
2664            let Some(&code) = refused.get(&lane.tag) else { return true };
2665            active.remove(&lane.session);
2666            let status = superfluid_abi::Status::from_raw(code as i32)
2667                .map(|s| format!("{s:?}"))
2668                .unwrap_or_else(|| format!("status {}", code as i32));
2669            tracing::warn!(session = lane.session, lane = lane.tag, status = %status, "engine refused an admit");
2670            let _ = lane.job.events.send(LaneEvent::Failed(format!("the engine refused to admit this request ({status})")));
2671            gone.push((lane.session, lane.seed_handle));
2672            false
2673        });
2674        drop(active);
2675        for (session, seed) in gone {
2676            if seed != 0 {
2677                let _ = self.host.client().seed_release(seed);
2678            }
2679            self.drop_ephemeral_artifact(session);
2680        }
2681    }
2682
2683    fn drop_ephemeral_artifact(&mut self, session: u64) {
2684        if !self.ephemeral_parked.remove(&session) {
2685            return;
2686        }
2687        let Some(dir) = self.park_dir.clone() else {
2688            return;
2689        };
2690        let Some(tx) = self.park_tx.as_ref() else {
2691            return;
2692        };
2693        if tx.try_send(ParkTask::Remove { dir, session }).is_err() {
2694            self.park_removals_pending.push(session);
2695        }
2696    }
2697
2698    fn park_lane(&mut self, i: usize) -> Result<(), ParkRefused> {
2699        let Some(dir) = self.park_dir.clone() else {
2700            return Ok(());
2701        };
2702        if self.lanes[i].job.completion {
2703            return Ok(());
2704        }
2705        if (self.host.multi_space() || !self.lanes[i].job.media.is_empty()) && !self.host.has_unservable_space() {
2706            return self.park_lane_multi(i, dir);
2707        }
2708        if self.host.has_unservable_space() {
2709            self.stats
2710                .unservable_park_refusals
2711                .fetch_add(1, Ordering::Relaxed);
2712            return Err(ParkRefused::SpaceUnservable);
2713        }
2714        let Some(page) = self.host.page_size() else {
2715            return Ok(());
2716        };
2717        let (tag, session) = (self.lanes[i].tag, self.lanes[i].session);
2718        let n = self.lanes[i].all_tokens.len() as u64;
2719        if n <= 1 {
2720            return Ok(());
2721        }
2722        let mut aligned = ((n - 1) / page) * page;
2723        if aligned < page {
2724            return Ok(());
2725        }
2726        let park_encoding = if self.park_lossy {
2727            encoding::Q8
2728        } else {
2729            encoding::LOSSLESS
2730        };
2731        let client = self.host.client();
2732        let seq = match client.state_sync(linkw::StateSyncReqMsg::LaneSequence { lane_tag: tag }) {
2733            Ok(linkw::StateSyncOkMsg::LaneSequence { sequence }) => sequence,
2734            Ok(_) => return Err("mismatched LaneSequence reply".into()),
2735            Err(e) => return Err(e.to_string().into()),
2736        };
2737        if let Ok(linkw::StateSyncOkMsg::SnapshotBoundary { boundary }) =
2738            client.state_sync(linkw::StateSyncReqMsg::SnapshotBoundary { sequence: seq, space_id: KV_SPACE, cap: n - 1 })
2739        {
2740            aligned = aligned.min(boundary);
2741            if aligned < page {
2742                return Ok(());
2743            }
2744        }
2745        let digest = park::stream_digest(&self.lanes[i].all_tokens[..aligned as usize]);
2746        let _span = tracing::info_span!(
2747            "park",
2748            session,
2749            model = self.trace_scope.model.as_str(),
2750            store_ns = self.trace_scope.ns,
2751            covered = aligned,
2752            lossy = self.park_lossy
2753        )
2754        .entered();
2755        let (bytes, sizing_gen) = match client.state_sync(linkw::StateSyncReqMsg::ExportSize {
2756            sequence: seq,
2757            space_id: KV_SPACE,
2758            range: linkw::TokenRangeMsg {
2759                start: 0,
2760                end: aligned,
2761            },
2762            encoding: park_encoding,
2763        }) {
2764            Ok(linkw::StateSyncOkMsg::ExportSize {
2765                required_bytes,
2766                sizing_gen,
2767            }) => (required_bytes, sizing_gen),
2768            Ok(_) => return Err("mismatched ExportSize reply".into()),
2769            Err(e) => return Err(e.to_string().into()),
2770        };
2771        let buf = client.buf_create(bytes).map_err(|e| ParkRefused::from(e.to_string()))?;
2772        let result = (|| {
2773            let op_kind = if self.park_lossy {
2774                linkw::StateOpKind::Demote {
2775                    sequence: seq,
2776                    space_id: KV_SPACE,
2777                    range: linkw::TokenRangeMsg {
2778                        start: 0,
2779                        end: aligned,
2780                    },
2781                    encoding: encoding::Q8,
2782                    buf_id: buf,
2783                    sizing_gen,
2784                }
2785            } else {
2786                linkw::StateOpKind::Snapshot {
2787                    sequence: seq,
2788                    space_id: KV_SPACE,
2789                    boundary_pos: aligned,
2790                    buf_id: buf,
2791                    sizing_gen,
2792                }
2793            };
2794            let op = client.state_op(op_kind).map_err(|e| e.to_string())?;
2795            let done = client.wait_op_done(op).map_err(|e| e.to_string())?;
2796            if done.state != op_state::DONE || done.error != 0 {
2797                return Err(format!(
2798                    "snapshot terminal state {} error {}",
2799                    done.state, done.error
2800                ));
2801            }
2802            let take = if done.bytes_moved > 0 {
2803                done.bytes_moved.min(bytes)
2804            } else {
2805                bytes
2806            };
2807            client
2808                .buf_read(buf, take as usize)
2809                .map_err(|e| e.to_string())
2810        })();
2811        let _ = client.buf_release(buf);
2812        let sealed = result.map_err(ParkRefused::from)?;
2813        let job = ParkJob {
2814            dir,
2815            session,
2816            covered: aligned,
2817            digest,
2818            encoding: park_encoding,
2819            sealed,
2820            spaces: None,
2821        };
2822        match self
2823            .park_tx
2824            .as_ref()
2825            .expect("writer exists with park_dir")
2826            .try_send(ParkTask::Write(Box::new(job)))
2827        {
2828            Ok(()) => {
2829                if self.lanes[i].job.extras.ephemeral {
2830                    self.ephemeral_parked.insert(session);
2831                }
2832                if self.park_lossy {
2833                    self.stats.parks_lossy.fetch_add(1, Ordering::Relaxed);
2834                } else {
2835                    self.stats.parks_lossless.fetch_add(1, Ordering::Relaxed);
2836                }
2837            }
2838            Err(std::sync::mpsc::TrySendError::Full(_)) => {
2839                return Err("park writer backlogged; artifact dropped".into());
2840            }
2841            Err(std::sync::mpsc::TrySendError::Disconnected(_)) => {
2842                return Err("park writer gone".into());
2843            }
2844        }
2845        self.lanes[i].parked_lossy = self.park_lossy;
2846        Ok(())
2847    }
2848
2849    fn park_lane_multi(&mut self, i: usize, dir: PathBuf) -> Result<(), ParkRefused> {
2850        let (tag, session) = (self.lanes[i].tag, self.lanes[i].session);
2851        let n = self.lanes[i].all_tokens.len() as u64;
2852        if n <= 1 {
2853            return Ok(());
2854        }
2855        let spaces = self.host.parkable_spaces();
2856        let lossy = self.park_lossy;
2857        let fallback_boundary = self.host.park_boundary(n - 1);
2858        let client = self.host.client();
2859        let seq = match client.state_sync(linkw::StateSyncReqMsg::LaneSequence { lane_tag: tag }) {
2860            Ok(linkw::StateSyncOkMsg::LaneSequence { sequence }) => sequence,
2861            Ok(_) => return Err("mismatched LaneSequence reply".into()),
2862            Err(e) => return Err(e.to_string().into()),
2863        };
2864        let cap = n - 1;
2865        let mut covered = cap;
2866        let mut queried = false;
2867        for &(space_id, _, _, _) in &spaces {
2868            match client.state_sync(linkw::StateSyncReqMsg::SnapshotBoundary { sequence: seq, space_id, cap }) {
2869                Ok(linkw::StateSyncOkMsg::SnapshotBoundary { boundary }) => {
2870                    queried = true;
2871                    covered = covered.min(boundary);
2872                }
2873                Ok(_) => return Err("mismatched SnapshotBoundary reply".into()),
2874                Err(_) => break,
2875            }
2876        }
2877        if !queried {
2878            covered = fallback_boundary;
2879        }
2880        if covered == 0 {
2881            return Ok(());
2882        }
2883        let digest = park::stream_digest(&self.lanes[i].all_tokens[..covered as usize]);
2884        let _span = tracing::info_span!(
2885            "park",
2886            session,
2887            model = self.trace_scope.model.as_str(),
2888            store_ns = self.trace_scope.ns,
2889            covered,
2890            lossy = self.park_lossy,
2891            multi = true
2892        )
2893        .entered();
2894        let mut parked: Vec<park::ParkedSpace> = Vec::with_capacity(spaces.len());
2895        for &(space_id, kind, _, _) in &spaces {
2896            let paged = kind == superfluid_abi::space_kind::PAGED_TOKEN_KV;
2897            let encoding = if paged && lossy { encoding::Q8 } else { encoding::LOSSLESS };
2898            let range = if paged {
2899                linkw::TokenRangeMsg { start: 0, end: covered }
2900            } else {
2901                linkw::TokenRangeMsg { start: covered, end: covered }
2902            };
2903            let (bytes, sizing_gen) = match client.state_sync(linkw::StateSyncReqMsg::ExportSize {
2904                sequence: seq,
2905                space_id,
2906                range,
2907                encoding,
2908            }) {
2909                Ok(linkw::StateSyncOkMsg::ExportSize { required_bytes, sizing_gen }) => {
2910                    (required_bytes, sizing_gen)
2911                }
2912                Ok(_) => return Err("mismatched ExportSize reply".into()),
2913                Err(e) => return Err(format!("export size (space {space_id}): {e}").into()),
2914            };
2915            let buf = client.buf_create(bytes).map_err(|e| ParkRefused::from(e.to_string()))?;
2916            let result = (|| {
2917                let op_kind = if paged && lossy {
2918                    linkw::StateOpKind::Demote {
2919                        sequence: seq,
2920                        space_id,
2921                        range,
2922                        encoding: encoding::Q8,
2923                        buf_id: buf,
2924                        sizing_gen,
2925                    }
2926                } else {
2927                    linkw::StateOpKind::Snapshot {
2928                        sequence: seq,
2929                        space_id,
2930                        boundary_pos: covered,
2931                        buf_id: buf,
2932                        sizing_gen,
2933                    }
2934                };
2935                let op = client.state_op(op_kind).map_err(|e| e.to_string())?;
2936                let done = client.wait_op_done(op).map_err(|e| e.to_string())?;
2937                if done.state != op_state::DONE || done.error != 0 {
2938                    return Err(format!(
2939                        "space {space_id} export terminal state {} error {}",
2940                        done.state, done.error
2941                    ));
2942                }
2943                let take = if done.bytes_moved > 0 { done.bytes_moved.min(bytes) } else { bytes };
2944                client.buf_read(buf, take as usize).map_err(|e| e.to_string())
2945            })();
2946            let _ = client.buf_release(buf);
2947            let sealed = result.map_err(ParkRefused::from)?;
2948            parked.push(park::ParkedSpace {
2949                space_id,
2950                kind,
2951                encoding,
2952                sealed,
2953            });
2954        }
2955        let job = ParkJob {
2956            dir,
2957            session,
2958            covered,
2959            digest,
2960            encoding: if lossy { encoding::Q8 } else { encoding::LOSSLESS },
2961            sealed: Vec::new(),
2962            spaces: Some(parked),
2963        };
2964        match self
2965            .park_tx
2966            .as_ref()
2967            .expect("writer exists with park_dir")
2968            .try_send(ParkTask::Write(Box::new(job)))
2969        {
2970            Ok(()) => {
2971                if self.lanes[i].job.extras.ephemeral {
2972                    self.ephemeral_parked.insert(session);
2973                }
2974                if lossy {
2975                    self.stats.parks_lossy.fetch_add(1, Ordering::Relaxed);
2976                } else {
2977                    self.stats.parks_lossless.fetch_add(1, Ordering::Relaxed);
2978                }
2979            }
2980            Err(std::sync::mpsc::TrySendError::Full(_)) => {
2981                return Err("park writer backlogged; artifact dropped".into());
2982            }
2983            Err(std::sync::mpsc::TrySendError::Disconnected(_)) => {
2984                return Err("park writer gone".into());
2985            }
2986        }
2987        self.lanes[i].parked_lossy = lossy;
2988        Ok(())
2989    }
2990
2991    fn resume_adopt(&mut self, job: &GenerateJob, p: &park::Parked) -> Result<(u64, u64), String> {
2992        if p.covered as usize >= job.stream.len() {
2993            return Err("artifact covers the whole stream (nothing to prefill)".into());
2994        }
2995        if park::stream_digest(&job.stream[..p.covered as usize]) != p.stream_digest {
2996            return Err("stream digest mismatch (rewritten history)".into());
2997        }
2998        let _op_span = tracing::info_span!(
2999            "restore",
3000            session = job.session,
3001            model = self.trace_scope.model.as_str(),
3002            store_ns = self.trace_scope.ns,
3003            covered = p.covered,
3004            spaces = p.spaces.len(),
3005            multi = true
3006        )
3007        .entered();
3008        let want = self.host.parkable_spaces();
3009        for &(id, _, _, _) in &want {
3010            if !p.spaces.iter().any(|s| s.space_id == id) {
3011                return Err(format!("artifact lacks space {id}"));
3012            }
3013        }
3014        let covered_span = self
3015            .stage(&job.stream[..p.covered as usize])
3016            .map_err(|e| e.to_string())?;
3017        let client = self.host.client();
3018        let seq = match client.state_sync(linkw::StateSyncReqMsg::CreateSequence) {
3019            Ok(linkw::StateSyncOkMsg::CreateSequence { sequence }) => sequence,
3020            Ok(_) => return Err("mismatched CreateSequence reply".into()),
3021            Err(e) => return Err(e.to_string()),
3022        };
3023        let free_seq = |client: &mut superfluid_agent::WorkerClient| {
3024            let _ = client.state_sync(linkw::StateSyncReqMsg::FreeSequence { sequence: seq });
3025        };
3026        let mut order: Vec<&park::ParkedSpace> = p.spaces.iter().collect();
3027        order.sort_by_key(|s| (s.kind != superfluid_abi::space_kind::PAGED_TOKEN_KV, s.space_id));
3028        for sp in order {
3029            let buf = match client.buf_create(sp.sealed.len() as u64) {
3030                Ok(b) => b,
3031                Err(e) => {
3032                    free_seq(client);
3033                    return Err(e.to_string());
3034                }
3035            };
3036            let result = (|| {
3037                client.buf_write(buf, &sp.sealed).map_err(|e| e.to_string())?;
3038                let op = client
3039                    .state_op(linkw::StateOpKind::Restore {
3040                        sequence: seq,
3041                        space_id: sp.space_id,
3042                        buf_id: buf,
3043                    })
3044                    .map_err(|e| e.to_string())?;
3045                let done = client.wait_op_done(op).map_err(|e| e.to_string())?;
3046                if done.state != op_state::DONE || done.error != 0 {
3047                    return Err(format!(
3048                        "restore space {} terminal state {} error {}",
3049                        sp.space_id, done.state, done.error
3050                    ));
3051                }
3052                Ok(())
3053            })();
3054            let _ = client.buf_release(buf);
3055            if let Err(e) = result {
3056                free_seq(client);
3057                return Err(e);
3058            }
3059        }
3060        match client.state_sync(linkw::StateSyncReqMsg::AdoptSequence {
3061            sequence: seq,
3062            span: covered_span,
3063        }) {
3064            Ok(linkw::StateSyncOkMsg::AdoptSequence { seed_handle }) => Ok((seed_handle, p.covered)),
3065            Ok(_) => {
3066                free_seq(client);
3067                Err("mismatched AdoptSequence reply".into())
3068            }
3069            Err(e) => {
3070                free_seq(client);
3071                Err(e.to_string())
3072            }
3073        }
3074    }
3075
3076    fn commit_tool_block(
3077        codec: &Arc<dyn TextCodec + Send + Sync>,
3078        store: &Arc<Mutex<SessionStore>>,
3079        tool_lease_ms: u64,
3080        session: u64,
3081        raw: String,
3082        schemas: Option<&crate::codec::ToolSchemas>,
3083    ) -> Result<Option<crate::CommittedEvent>, DaemonError> {
3084        match codec.parse_tool_call_with(&raw, schemas) {
3085            Some((name, args)) => {
3086                let mut store = store.lock().expect("store");
3087                let e = store.commit_tool_use(session, name, args)?;
3088                if tool_lease_ms > 0 {
3089                    let deadline = crate::wal::now_unix_ms() + tool_lease_ms;
3090                    let _ = store.commit_tool_lease(session, e.event_id, deadline);
3091                }
3092                Ok(Some(e))
3093            }
3094            None => Ok(Some(
3095                store.lock().expect("store").commit_tool_parse_failure(session, raw)?,
3096            )),
3097        }
3098    }
3099
3100    fn finish_failed(&mut self, job: &GenerateJob, msg: &str) {
3101        self.drop_ephemeral_artifact(job.session);
3102        self.active.lock().expect("active set").remove(&job.session);
3103        let _ = job.events.send(LaneEvent::Failed(msg.to_string()));
3104    }
3105
3106    fn publish_occupancy(&self) {
3107        self.stats.clear_tick_in_flight();
3108        self.stats
3109            .lanes_active
3110            .store(self.lanes.len() as u64, Ordering::Relaxed);
3111        for (c, q) in self.queues.iter().enumerate() {
3112            self.stats.queue_depth[c].store(q.jobs.len() as u64, Ordering::Relaxed);
3113        }
3114    }
3115
3116    fn tick_once(&mut self, tick_span: &tracing::Span) -> Result<(), DaemonError> {
3117        self.publish_occupancy();
3118        if self.lanes.is_empty() {
3119            return Ok(());
3120        }
3121
3122        let mut locally_done: Vec<u64> = Vec::new();
3123        for i in 0..self.lanes.len() {
3124            let session = self.lanes[i].session;
3125            let is_active = !matches!(self.lanes[i].phase, LanePhase::Finishing);
3126            if is_active && self.cancels.lock().expect("cancels").remove(&session) {
3127                let lane = &mut self.lanes[i];
3128                lane.fin = finish::CANCELLED;
3129                provisional_flush(&self.bus, lane);
3130                let text = lane.utf8.flush();
3131                let e = self.store.lock().expect("store").commit_generated(
3132                    session,
3133                    Vec::new(),
3134                    text,
3135                    lane.seg.current_channel(),
3136                    finish::CANCELLED,
3137                )?;
3138                let _ = lane.job.events.send(LaneEvent::Committed(e));
3139                if lane.admitted {
3140                    lane.phase = LanePhase::Finishing;
3141                } else {
3142                    if lane.seed_handle != 0 {
3143                        let _ = self.host.client().seed_release(lane.seed_handle);
3144                    }
3145                    locally_done.push(lane.tag);
3146                }
3147            }
3148        }
3149        let now_dl = std::time::Instant::now();
3150        for i in 0..self.lanes.len() {
3151            if matches!(self.lanes[i].phase, LanePhase::Finishing) {
3152                continue;
3153            }
3154            let Some(deadline) = self.lanes[i].job.deadline else {
3155                continue;
3156            };
3157            if now_dl <= deadline {
3158                continue;
3159            }
3160            let completion = self.lanes[i].job.completion;
3161            let session = self.lanes[i].session;
3162            if !completion {
3163                provisional_flush(&self.bus, &mut self.lanes[i]);
3164                let text = self.lanes[i].utf8.flush();
3165                let channel = self.lanes[i].seg.current_channel();
3166                let e = self.store.lock().expect("store").commit_generated(
3167                    session,
3168                    Vec::new(),
3169                    text,
3170                    channel,
3171                    finish::LENGTH,
3172                )?;
3173                let _ = self.lanes[i].job.events.send(LaneEvent::Committed(e));
3174                self.lanes[i].fin = finish::LENGTH;
3175            } else {
3176                let _ = self.lanes[i].job.events.send(LaneEvent::Expired);
3177            }
3178            let lane = &mut self.lanes[i];
3179            if lane.admitted {
3180                lane.phase = LanePhase::Finishing;
3181            } else {
3182                if lane.seed_handle != 0 {
3183                    let _ = self.host.client().seed_release(lane.seed_handle);
3184                }
3185                locally_done.push(lane.tag);
3186            }
3187        }
3188        if !locally_done.is_empty() {
3189            let mut locally_retired: Vec<u64> = Vec::new();
3190            let mut active = self.active.lock().expect("active set");
3191            self.lanes.retain(|lane| {
3192                if locally_done.contains(&lane.tag) {
3193                    active.remove(&lane.session);
3194                    let _ = lane.job.events.send(LaneEvent::Done {
3195                        tokens_generated: lane.produced,
3196                        finish: lane.fin,
3197                        warm_prefix: lane.warm_prefix,
3198                        spec: lane.spec,
3199                        decode: lane.decode_measured(),
3200                    });
3201                    locally_retired.push(lane.session);
3202                    false
3203                } else {
3204                    true
3205                }
3206            });
3207            drop(active);
3208            for session in locally_retired {
3209                self.drop_ephemeral_artifact(session);
3210            }
3211        }
3212        self.publish_occupancy();
3213        if self.lanes.is_empty() {
3214            return Ok(());
3215        }
3216
3217        self.tick_count += 1;
3218        self.stats.ticks.fetch_add(1, Ordering::Relaxed);
3219
3220        if self.park_lossy {
3221            for lane in &mut self.lanes {
3222                if lane.preempted
3223                    && !lane.parked
3224                    && lane.job.params.seed != 0
3225                    && !lane.job.extras.seed_drawn
3226                {
3227                    lane.parked = true;
3228                }
3229            }
3230        }
3231        if self.park_lossy && !self.pins.is_empty() && self.pins.enforceable(&self.host) {
3232            for lane in &mut self.lanes {
3233                if matches!(lane.phase, LanePhase::Finishing)
3234                    && !lane.parked
3235                    && lane.job.media.is_empty()
3236                    && !lane.job.completion
3237                    && self.pins.awaits_lease(&self.host, lane.session)
3238                {
3239                    lane.parked = true;
3240                }
3241            }
3242        }
3243
3244        if !self.park_removals_pending.is_empty() {
3245            if let (Some(dir), Some(tx)) = (self.park_dir.clone(), self.park_tx.as_ref()) {
3246                self.park_removals_pending.retain(|&session| {
3247                    matches!(
3248                        tx.try_send(ParkTask::Remove {
3249                            dir: dir.clone(),
3250                            session,
3251                        }),
3252                        Err(std::sync::mpsc::TrySendError::Full(_))
3253                    )
3254                });
3255            } else {
3256                self.park_removals_pending.clear();
3257            }
3258        }
3259
3260        if self.park_dir.is_some() {
3261            for i in 0..self.lanes.len() {
3262                if matches!(self.lanes[i].phase, LanePhase::Finishing)
3263                    && self.lanes[i].admitted
3264                    && !self.lanes[i].parked
3265                    && (!self.lanes[i].job.extras.ephemeral || self.lanes[i].preempted)
3266                {
3267                    self.lanes[i].parked = true;
3268                    if let Err(msg) = self.park_lane(i) {
3269                        eprintln!(
3270                            "[superfluid] park skipped (session {}): {msg}",
3271                            self.lanes[i].session
3272                        );
3273                    }
3274                }
3275            }
3276        }
3277
3278        let rate_cap = |ceiling: u32| match self.prefill_secs_per_token_bulk {
3279            Some(spt) if spt > 0.0 => Some((MAX_PREFILL_TICK_SECS / spt).clamp(512.0, ceiling as f64) as u32),
3280            _ => None,
3281        };
3282        let solo = !self.prefill_budget_fixed
3283            && self.lanes.len() == 1
3284            && self.queued() == 0
3285            && self
3286                .lanes
3287                .iter()
3288                .all(|l| matches!(l.phase, LanePhase::Prefilling));
3289        let solo_budget = if solo { rate_cap(SOLO_PREFILL_BUDGET) } else { None };
3290        let open = self.joiner_waits_on_this_tick();
3291        let tick_prefill_budget = if self
3292            .lanes
3293            .iter()
3294            .any(|l| !l.job.media.is_empty() && matches!(l.phase, LanePhase::Prefilling))
3295        {
3296            PREFILL_BUDGET
3297        } else if open {
3298            // One chunk until two ticks have taught the rate; then the slower of the
3299            // average and the last tick.
3300            let spt = match (self.prefill_secs_per_token_bulk, self.prefill_secs_per_token_last) {
3301                (Some(a), Some(b)) => Some(a.max(b)),
3302                (a, b) => a.or(b),
3303            };
3304            let cap = match spt.filter(|&t| t > 0.0 && self.prefill_rate_samples >= 2) {
3305                Some(spt) => {
3306                    let yields = matches!(
3307                        self.host.capabilities().read().expect("capability record").get("serving", "tick_yield"),
3308                        crate::capabilities::Cap::Yes
3309                    );
3310                    open_prefill_tick_cap(spt, open_prefill_tick_target(yields, self.latency_sensitive()))
3311                }
3312                None => 512,
3313            };
3314            self.prefill_budget.min(cap)
3315        } else if let Some(cap) = solo_budget.filter(|&cap| cap > self.prefill_budget) {
3316            cap
3317        } else {
3318            match self.prefill_secs_per_token_bulk {
3319                Some(spt) if spt > 0.0 => {
3320                    let lone = self.lanes.len() == 1
3321                        && self.queues.iter().all(|q| q.jobs.is_empty())
3322                        && self.lanes.iter().all(|l| {
3323                            Self::prefill_end_of(&self.speculation, l).saturating_sub(l.prefilled)
3324                                > prefill_tick_cap(spt, true) as u64
3325                        });
3326                    self.prefill_budget.min(prefill_tick_cap(spt, lone))
3327                }
3328                _ => self.prefill_budget,
3329            }
3330        };
3331        let mut plan = linkw::TickPlanMsg {
3332            plan_seq: {
3333                self.plan_seq += 1;
3334                self.plan_seq
3335            },
3336            flags: 0,
3337            prefill_token_budget: tick_prefill_budget,
3338            max_decode_lanes: self.max_lanes as u32,
3339            admits: vec![],
3340            commits: vec![],
3341            prefills: vec![],
3342            decodes: vec![],
3343            retires: vec![],
3344            shed_policy: linkw::ShedPolicyMsg {
3345                victim_lanes: vec![],
3346                evictable_cache_classes: u64::MAX,
3347                protected_quota_bytes: 0,
3348                max_evict_bytes: u64::MAX,
3349            },
3350        };
3351
3352        let prefill_demand_lanes = self
3353            .lanes
3354            .iter()
3355            .filter(|l| {
3356                !matches!(l.phase, LanePhase::Finishing)
3357                    && l.prefilled < Self::prefill_end_of(&self.speculation, l)
3358            })
3359            .count()
3360            + self.queues.iter().map(|q| q.jobs.len()).sum::<usize>();
3361        self.plan_prefix_checkpoints();
3362        let mut budget = PrefillBudget::new(tick_prefill_budget as u64);
3363        let mut retiring: Vec<u64> = Vec::new();
3364        let starvation = self.starvation_ticks;
3365        let mut order: Vec<usize> = (0..self.lanes.len()).collect();
3366        order.sort_by_key(|&i| {
3367            let l = &self.lanes[i];
3368            let remaining = Self::prefill_end_of(&self.speculation, l).saturating_sub(l.prefilled);
3369            prefill_rank(l.job.class, remaining, l.tag, l.prefill_starved_ticks, starvation)
3370        });
3371        let mut decode_candidates: Vec<usize> = Vec::new();
3372        let speculation = Arc::clone(&self.speculation);
3373        for &li in &order {
3374            let lane = &mut self.lanes[li];
3375            if matches!(lane.phase, LanePhase::Finishing) {
3376                plan.retires.push(linkw::LaneRetireMsg {
3377                    lane_tag: lane.tag,
3378                    publish_to_cache: !lane.parked_lossy
3379                        && lane.job.media.is_empty()
3380                        && !lane.job.completion,
3381                });
3382                retiring.push(lane.tag);
3383                continue;
3384            }
3385            if !lane.admitted {
3386                let strategy_slot = Self::strategy_slot_for(&speculation, &lane.job);
3387                plan.admits.push(linkw::LaneAdmitMsg {
3388                    lane_tag: lane.tag,
3389                    prompt: lane.span,
3390                    seed_handle: lane.seed_handle,
3391                    sampling: if lane.job.params.temperature == 0.0 { 0 } else { 1 },
3392                    params: linkw::SamplingParamsMsg {
3393                        temperature: lane.job.params.temperature,
3394                        top_p: lane.job.params.top_p,
3395                        min_p: lane.job.params.min_p,
3396                        top_k: lane.job.params.top_k,
3397                        freq_penalty: lane.job.extras.frequency_penalty,
3398                        presence_penalty: lane.job.extras.presence_penalty,
3399                        repeat_penalty: lane.job.extras.repeat_penalty,
3400                        flags: if lane.job.extras.ignore_eos {
3401                            superfluid_abi::sampling_flag::IGNORE_EOS
3402                        } else {
3403                            0
3404                        },
3405                    },
3406                    rng_counter_base: lane
3407                        .job
3408                        .params
3409                        .seed
3410                        .wrapping_add(lane.job.stream.len() as u64),
3411                    grammar_handle: lane.job.grammar_handle,
3412                    grammar_replay: if lane.job.grammar_handle != 0 { lane.produced_before } else { 0 },
3413                    decode_replay: Self::decode_replay_for(strategy_slot, lane.produced_before),
3414                    logit_bias_handle: lane.job.extras.logit_bias_handle,
3415                    want_logprobs: lane.job.extras.want_logprobs,
3416                    top_logprobs: lane.job.extras.top_logprobs,
3417                    strategy_slot,
3418                    determinism_class: 0,
3419                    minimum_exactness: 0,
3420                    allow_approximate: false,
3421                    required_cert_id: 0,
3422                    host_sampler_identity: [0; 32],
3423                });
3424            }
3425            let total = Self::prefill_end_of(&speculation, lane);
3426            if lane.prefilled < total {
3427                let mut chunk = budget.offer_to(total - lane.prefilled, lane.job.class) as u32;
3428                if lane.checkpoint_at > lane.prefilled {
3429                    chunk = chunk.min((lane.checkpoint_at - lane.prefilled) as u32);
3430                }
3431                for &(off, _, n) in &lane.job.media {
3432                    let (rs, re) = (off, off + n as u64);
3433                    let (cs, ce) = (lane.prefilled, lane.prefilled + chunk as u64);
3434                    if rs < ce && re > ce {
3435                        chunk = if rs > cs { (rs - cs) as u32 } else { 0 };
3436                    }
3437                }
3438                if chunk > 0 {
3439                    lane.prefill_starved_ticks = 0;
3440                }
3441                if chunk > 0 {
3442                    plan.prefills.push(linkw::LanePrefillMsg {
3443                        lane_tag: lane.tag,
3444                        token_offset: lane.prefilled as u32,
3445                        token_count: chunk,
3446                    });
3447                    lane.prefilled += chunk as u64;
3448                    lane.checkpoint_due = lane.checkpoint_at > 0 && lane.prefilled == lane.checkpoint_at;
3449                    budget.take(chunk as u64, lane.prefilled >= total);
3450                    if lane.prefilled >= total && lane.job.class <= crate::qos::INLINE_COMPLETION {
3451                        budget.hold_below(lane.job.class);
3452                    }
3453                }
3454            }
3455            if lane.prefilled >= total {
3456                lane.phase = LanePhase::Decoding;
3457                if lane.job.max_tokens > lane.produced {
3458                    decode_candidates.push(li);
3459                }
3460            }
3461        }
3462        // A lane counts a starved tick only when the prefill went to a younger lane.
3463        let youngest_served = plan.prefills.iter().map(|p| p.lane_tag).max();
3464        for lane in self.lanes.iter_mut() {
3465            let waiting = lane.prefilled < Self::prefill_end_of(&self.speculation, lane)
3466                && !matches!(lane.phase, LanePhase::Finishing)
3467                && !plan.prefills.iter().any(|p| p.lane_tag == lane.tag);
3468            lane.prefill_starved_ticks = starved_after(lane.prefill_starved_ticks, waiting, youngest_served, lane.tag);
3469        }
3470        let divisor = self.background_tick_divisor.load(Ordering::Relaxed).max(1);
3471        let tick_no = self.tick_count;
3472        decode_candidates.sort_by_key(|&i| {
3473            let l = &self.lanes[i];
3474            let starving = l.starved_ticks >= starvation;
3475            (!starving, l.job.class, l.tag)
3476        });
3477        let planned_prefill_tokens: f64 = plan.prefills.iter().map(|p| p.token_count as f64).sum();
3478        let finishing = self.lanes.iter().any(|l| matches!(l.phase, LanePhase::Finishing));
3479        let shape = self.tick_shape;
3480        let target_secs = tick_target(&shape, self.tick_target_secs, self.lanes.len(), self.max_lanes, finishing);
3481        self.stats.tick_target_live_ms.store((target_secs * 1000.0).round() as u64, Ordering::Relaxed);
3482        let grant_cap: u16 = match (plan.prefills.len(), self.decode_round_secs) {
3483            (p, Some(c)) if c > 0.0 && !decode_candidates.is_empty() => {
3484                if p > 0 {
3485                    let prefill_secs = self.prefill_secs_per_token.map_or(0.0, |s| s * planned_prefill_tokens);
3486                    let interactive =
3487                        decode_candidates.iter().any(|&i| self.lanes[i].job.class <= crate::qos::INLINE_COMPLETION);
3488                    let share =
3489                        if interactive { DECODE_SHARE_BESIDE_PREFILL_INTERACTIVE } else { DECODE_SHARE_BESIDE_PREFILL };
3490                    ((prefill_secs * share / c) as u32)
3491                        .clamp(shape.beside_prefill as u32, TICK_DECODE_MAX as u32) as u16
3492                } else {
3493                    let rounds = ((target_secs / c) as u32).clamp(shape.decode_floor as u32, TICK_DECODE_MAX as u32);
3494                    let first_out = decode_candidates
3495                        .iter()
3496                        .map(|&i| self.lanes[i].job.max_tokens.saturating_sub(self.lanes[i].produced))
3497                        .min()
3498                        .unwrap_or(rounds);
3499                    let rounds = rounds.min(first_out.max(shape.beside_prefill as u32));
3500                    if self.just_retired {
3501                        rounds.min(shape.beside_prefill as u32) as u16
3502                    } else {
3503                        rounds as u16
3504                    }
3505                }
3506            }
3507            _ => shape.decode_floor,
3508        };
3509        self.stats.decode_grant_live.store(grant_cap as u64, Ordering::Relaxed);
3510        let mut decode_left = if self.tick_decode_budget_fixed {
3511            self.tick_decode_budget
3512        } else {
3513            (self.max_lanes as u32).saturating_mul(grant_cap as u32)
3514        };
3515        let retire_only = plan.prefills.is_empty() && !plan.retires.is_empty();
3516        if retire_only {
3517            decode_candidates.clear();
3518        }
3519        self.just_retired = retire_only;
3520        for &li in &decode_candidates {
3521            let lane = &mut self.lanes[li];
3522            let background = lane.job.class >= crate::qos::FOREGROUND_AGENT;
3523            let throttled = background && divisor > 1 && !tick_no.is_multiple_of(divisor as u64);
3524            let want = if throttled {
3525                0
3526            } else {
3527                (lane.job.max_tokens - lane.produced)
3528                    .min(grant_cap as u32)
3529                    .min(decode_left) as u16
3530            };
3531            if want > 0 {
3532                if lane.starved_ticks >= starvation {
3533                    self.stats.starvation_grants.fetch_add(1, Ordering::Relaxed);
3534                }
3535                lane.starved_ticks = 0;
3536                decode_left -= want as u32;
3537                let overshoot = (lane.job.max_tokens - lane.produced - want as u32).min(16) as u16;
3538                plan.decodes.push(linkw::LaneDecodeMsg {
3539                    lane_tag: lane.tag,
3540                    max_new_tokens: want,
3541                    overshoot,
3542                });
3543            } else {
3544                lane.starved_ticks = lane.starved_ticks.saturating_add(1);
3545            }
3546        }
3547
3548        let wants_partial = self.lanes.iter().any(|l| {
3549            !matches!(l.phase, LanePhase::Finishing)
3550                && (l.job.extras.wants_deltas
3551                    || l.job.completion
3552                    || self.bus.has_provisional_subscriber(l.session))
3553        });
3554        if wants_partial {
3555            plan.flags |= superfluid_abi::tick_flags::PARTIAL_EMITS;
3556        }
3557        let mut plan_prefill_tokens: u32 = plan.prefills.iter().map(|p| p.token_count).sum();
3558        let prefill_by_tag: HashMap<u64, u32> =
3559            plan.prefills.iter().map(|p| (p.lane_tag, p.token_count)).collect();
3560        let plan_decode_rounds: u32 =
3561            plan.decodes.iter().map(|d| d.max_new_tokens as u32).max().unwrap_or(0);
3562        let plan_prefill_lanes_planned = plan.prefills.len();
3563        let plan_prefill_lanes = plan.prefills.len().max(prefill_demand_lanes);
3564        let plan_decode_lanes = plan.decodes.len();
3565        if !tick_span.is_disabled() {
3566            let planned: HashSet<u64> = plan
3567                .admits
3568                .iter()
3569                .map(|a| a.lane_tag)
3570                .chain(plan.prefills.iter().map(|p| p.lane_tag))
3571                .chain(plan.decodes.iter().map(|d| d.lane_tag))
3572                .chain(plan.retires.iter().map(|r| r.lane_tag))
3573                .collect();
3574            tick_span.record("lanes", planned.len());
3575            for lane in self.lanes.iter().filter(|l| planned.contains(&l.tag)) {
3576                tick_span.follows_from(&lane.job.trace);
3577            }
3578        }
3579        let tick_started = std::time::Instant::now();
3580        self.stats.set_tick_in_flight(TickInFlight {
3581            since_ms: uptime_ms().max(1),
3582            prefill_tokens: plan_prefill_tokens as u64,
3583            prefill_lanes: plan_prefill_lanes_planned as u64,
3584            decode_lanes: plan_decode_lanes as u64,
3585            admits: plan.admits.len() as u64,
3586            retires: plan.retires.len() as u64,
3587            target_ms: (target_secs * 1000.0).round() as u64,
3588        });
3589        let mut deferred: Vec<u64> = Vec::new();
3590        let ev = {
3591            let Scheduler {
3592                host,
3593                lanes,
3594                bus,
3595                codec,
3596                pins,
3597                stats,
3598                ..
3599            } = self;
3600            let by_tag: std::collections::HashMap<u64, usize> =
3601                lanes.iter().enumerate().map(|(i, l)| (l.tag, i)).collect();
3602            // The engine answers NeedsReplan when this tick's admissions do
3603            // not fit (sequence slots or cells it cannot free); it has run
3604            // nothing. Admit fewer rather than fail every lane.
3605            let fewer = (!plan.admits.is_empty()
3606                && plan.admits.iter().all(|a| {
3607                    by_tag.get(&a.lane_tag).is_some_and(|&i| lanes[i].job.media.is_empty())
3608                }))
3609            .then(|| plan.clone());
3610            let mut on_emit = |em: linkw::TickEmitMsg| {
3611                let Some(&i) = by_tag.get(&em.lane_tag) else { return };
3612                let lane = &mut lanes[i];
3613                lane.frame_fed += em.tokens.len();
3614                stats
3615                    .decode_tokens
3616                    .fetch_add(em.tokens.len() as u64, Ordering::Relaxed);
3617                if !lane.ttft_observed && lane.ttft_frame_ms.is_none() && !em.tokens.is_empty() {
3618                    lane.ttft_frame_ms = Some(lane.admitted_at.elapsed().as_millis() as u64);
3619                }
3620                if lane.job.completion {
3621                    if !lane.emit_dead
3622                        && lane.job.events.send(LaneEvent::CompletionTokens(em.tokens)).is_err()
3623                    {
3624                        lane.emit_dead = true;
3625                    }
3626                } else {
3627                    provisional_ingest(codec, bus, lane, &em.tokens);
3628                }
3629            };
3630            let retry = pins.any_held().then(|| plan.clone());
3631            let needs_replan = |r: &Result<linkw::TickEventsMsg, superfluid_agent::AgentError>| {
3632                matches!(r, Err(superfluid_agent::AgentError::Rejected(s)) if *s == superfluid_abi::Status::NeedsReplan as i32)
3633            };
3634            let mut res = match (host.client().tick_streaming(plan, &mut on_emit), retry) {
3635                (Err(superfluid_agent::AgentError::Rejected(status)), Some(plan))
3636                    if status == superfluid_abi::Status::NeedsReplan as i32
3637                        && pins.yield_all(host, stats) =>
3638                {
3639                    host.client().tick_streaming(plan, &mut on_emit)
3640                }
3641                (res, _) => res,
3642            };
3643            if let Some(full) = fewer.filter(|_| needs_replan(&res)) {
3644                let mut keep = full.admits.len();
3645                while keep > 0 && needs_replan(&res) {
3646                    keep /= 2;
3647                    res = host.client().tick_streaming(with_first_admits(&full, keep), &mut on_emit);
3648                }
3649                if res.is_ok() {
3650                    deferred = full.admits[keep..].iter().map(|a| a.lane_tag).collect();
3651                }
3652            }
3653            stats.clear_tick_in_flight();
3654            res?
3655        };
3656        if !deferred.is_empty() {
3657            plan_prefill_tokens = plan_prefill_tokens
3658                .saturating_sub(deferred.iter().filter_map(|t| prefill_by_tag.get(t)).sum());
3659            self.stats.admits_deferred.fetch_add(deferred.len() as u64, Ordering::Relaxed);
3660            self.defer_admits(&deferred);
3661        }
3662        let tick_secs = tick_started.elapsed().as_secs_f64().max(1e-3);
3663        let tick_done = std::time::Instant::now();
3664        // Prefill the engine shed (a tick that yielded between steps) is
3665        // planned again from where it stopped.
3666        for e in ev.shed.entries.iter().filter(|e| e.kind == superfluid_abi::shed_kind::PREFILL_CHUNK) {
3667            if let Some(lane) = self.lanes.iter_mut().find(|l| l.tag == e.lane_tag) {
3668                lane.prefilled = lane.prefilled.saturating_sub(e.tokens);
3669                if matches!(lane.phase, LanePhase::Decoding) {
3670                    lane.phase = LanePhase::Prefilling;
3671                }
3672                if lane.checkpoint_due && lane.prefilled != lane.checkpoint_at {
3673                    lane.checkpoint_due = false;
3674                }
3675            }
3676        }
3677        let prefill_tokens_ran: u32 = plan_prefill_tokens
3678            .saturating_sub(
3679                ev.admit_results
3680                    .iter()
3681                    .filter(|a| a.status != superfluid_abi::admit_status::ADMITTED)
3682                    .filter_map(|a| prefill_by_tag.get(&a.lane_tag))
3683                    .sum(),
3684            )
3685            .saturating_sub(
3686                ev.faults
3687                    .iter()
3688                    .filter(|f| f.code == superfluid_abi::fault::MEDIA)
3689                    .filter_map(|f| prefill_by_tag.get(&f.lane_tag))
3690                    .sum(),
3691            )
3692            .saturating_sub(
3693                ev.shed
3694                    .entries
3695                    .iter()
3696                    .filter(|e| e.kind == superfluid_abi::shed_kind::PREFILL_CHUNK)
3697                    .map(|e| e.tokens.min(u32::MAX as u64) as u32)
3698                    .sum(),
3699            );
3700        let tick_rounds_exec: u32 = ev
3701            .emits
3702            .iter()
3703            .filter(|e| e.n_tokens > 0)
3704            .map(|e| e.n_tokens.saturating_sub(e.spec.accepted).max(1))
3705            .max()
3706            .unwrap_or(0);
3707        // A lane whose prefill ended in this tick takes its first token from
3708        // the prefill's logits, not from a decode round: a tick that only
3709        // finished prompts ran no decode round to learn a decode cost from.
3710        let first_from_prefill: HashSet<u64> = self
3711            .lanes
3712            .iter()
3713            .filter(|l| l.produced == 0 && prefill_by_tag.contains_key(&l.tag))
3714            .map(|l| l.tag)
3715            .collect();
3716        let tick_decode_rounds: Option<u32> = ev
3717            .emits
3718            .iter()
3719            .filter(|e| e.n_tokens > 0)
3720            .map(|e| {
3721                let rounds = e.n_tokens.saturating_sub(e.spec.accepted).max(1);
3722                rounds - u32::from(first_from_prefill.contains(&e.lane_tag))
3723            })
3724            .max();
3725        self.stats
3726            .prefill_tokens
3727            .fetch_add(prefill_tokens_ran as u64, Ordering::Relaxed);
3728        let phase_split = plan_prefill_tokens == 0 || ev.timings.prefill_ns > 0;
3729        let (prefill_secs_meas, decode_secs_meas) = if phase_split {
3730            (ev.timings.prefill_ns as f64 / 1e9, ev.timings.decode_ns as f64 / 1e9)
3731        } else {
3732            let decode_est = self.decode_round_secs.unwrap_or(0.0) * tick_decode_rounds.unwrap_or(0) as f64;
3733            ((tick_secs - decode_est).max(1e-3), decode_est)
3734        };
3735        // A tick the daemon ended early ran fewer steps than planned: the
3736        // rates are of what ran.
3737        let decode_rounds_ran = match tick_decode_rounds {
3738            Some(rounds) => rounds.min(plan_decode_rounds),
3739            None => plan_decode_rounds,
3740        };
3741        if decode_rounds_ran > 0 && phase_split && decode_secs_meas > 0.0 {
3742            let sample = decode_secs_meas / decode_rounds_ran as f64;
3743            self.decode_round_secs = Some(match self.decode_round_secs {
3744                Some(c) => 0.7 * c + 0.3 * sample,
3745                None => sample,
3746            });
3747        } else if plan_prefill_tokens == 0 && decode_rounds_ran > 0 {
3748            let sample = tick_secs / decode_rounds_ran as f64;
3749            self.decode_round_secs = Some(match self.decode_round_secs {
3750                Some(c) => 0.7 * c + 0.3 * sample,
3751                None => sample,
3752            });
3753        }
3754        if prefill_tokens_ran >= PREFILL_RATE_MIN_TOKENS && phase_split {
3755            let sample = prefill_secs_meas.max(1e-3) / prefill_tokens_ran as f64;
3756            self.prefill_secs_per_token = Some(match self.prefill_secs_per_token {
3757                Some(c) => 0.7 * c + 0.3 * sample,
3758                None => sample,
3759            });
3760            if prefill_tokens_ran >= 512 {
3761                self.prefill_secs_per_token_last = Some(sample);
3762                self.prefill_rate_samples = self.prefill_rate_samples.saturating_add(1);
3763                self.prefill_secs_per_token_bulk = Some(match self.prefill_secs_per_token_bulk {
3764                    Some(c) => 0.7 * c + 0.3 * sample,
3765                    None => sample,
3766                });
3767            }
3768        }
3769        if !self.prefill_budget_fixed && prefill_tokens_ran > 0 {
3770            // What the whole planned prefill would have taken at the rate of
3771            // what ran.
3772            let prefill_secs = prefill_secs_meas.max(1e-3) * plan_prefill_tokens as f64 / prefill_tokens_ran as f64;
3773            let share = plan_prefill_lanes as f64
3774                / (plan_prefill_lanes as f64 + plan_decode_lanes as f64);
3775            let factor = (self.tick_target_secs * share / prefill_secs).clamp(0.5, 2.0);
3776            self.prefill_budget =
3777                ((self.prefill_budget as f64 * factor) as u32).clamp(256, PREFILL_BUDGET);
3778        }
3779        if std::env::var_os("SUPERFLUID_DEBUG_TICK").is_some() {
3780            eprintln!(
3781                "[tick] secs={:.3} pf_secs={:.3} dec_secs={:.3} prefill_tok={} rounds={} P_eff={} P_plan={} D={} round_secs={:?} pf_secs_per_tok={:?} budget={} grant={}",
3782                tick_secs, prefill_secs_meas, decode_secs_meas, plan_prefill_tokens, plan_decode_rounds, plan_prefill_lanes, plan_prefill_lanes_planned,
3783                plan_decode_lanes, self.decode_round_secs, self.prefill_secs_per_token, self.prefill_budget, grant_cap
3784            );
3785        }
3786        self.stats
3787            .prefill_budget_live
3788            .store(self.prefill_budget as u64, Ordering::Relaxed);
3789        if !self.pins.is_empty() {
3790            let retired: Vec<(u64, Vec<u32>)> = self
3791                .lanes
3792                .iter()
3793                .filter(|l| retiring.contains(&l.tag) && !l.job.completion)
3794                .map(|l| (l.session, l.all_tokens.clone()))
3795                .collect();
3796            self.pins
3797                .after_tick(&mut self.host, &self.stats, self.tick_count, &retired);
3798        }
3799        self.publish_prefix_checkpoints();
3800        self.relieve_pressure(&ev.mem, false);
3801        self.stats
3802            .pool_blocks_used
3803            .store(ev.mem.pool_blocks_used, Ordering::Relaxed);
3804        self.stats
3805            .pool_blocks_total
3806            .store(ev.mem.pool_blocks_total, Ordering::Relaxed);
3807
3808        let refused: HashMap<u64, u32> = ev
3809            .admit_results
3810            .iter()
3811            .filter(|r| r.status == superfluid_abi::admit_status::REJECTED)
3812            .map(|r| (r.lane_tag, r.reject_code))
3813            .collect();
3814        if !refused.is_empty() {
3815            self.drop_refused(&refused);
3816        }
3817
3818        for f in &ev.faults {
3819            if let Some(lane) = self.lanes.iter_mut().find(|l| l.tag == f.lane_tag) {
3820                if lane.fault_code.is_some() {
3821                    continue;
3822                }
3823                lane.fault_code = Some(f.code);
3824                lane.fin = finish::ERROR;
3825                provisional_flush(&self.bus, lane);
3826                let text = lane.utf8.flush();
3827                let e = self.store.lock().expect("store").commit_generated(
3828                    lane.session,
3829                    Vec::new(),
3830                    text,
3831                    lane.seg.current_channel(),
3832                    finish::ERROR,
3833                )?;
3834                let _ = lane.job.events.send(LaneEvent::Committed(e));
3835                lane.phase = LanePhase::Finishing;
3836                tracing::warn!(session = lane.session, lane = lane.tag, code = f.code, "engine lane fault");
3837            }
3838        }
3839
3840        #[allow(clippy::type_complexity)]
3841        let mut logprobs_by_lane: HashMap<u64, Vec<(f32, Vec<(u32, f32)>)>> = HashMap::new();
3842        for lp in &ev.logprobs {
3843            let top: Vec<(u32, f32)> = lp
3844                .top_ids
3845                .iter()
3846                .zip(&lp.top_logprob_bits)
3847                .map(|(id, b)| (*id, f32::from_bits(*b)))
3848                .collect();
3849            logprobs_by_lane
3850                .entry(lp.lane_tag)
3851                .or_default()
3852                .push((f32::from_bits(lp.logprob_bits), top));
3853        }
3854        let mut emitted: HashMap<u64, (Vec<u32>, u32)> = HashMap::new();
3855        for emit in &ev.emits {
3856            if emit.spec.proposed > 0 {
3857                self.stats
3858                    .spec_proposed
3859                    .fetch_add(emit.spec.proposed as u64, Ordering::Relaxed);
3860                self.stats
3861                    .spec_accepted
3862                    .fetch_add(emit.spec.accepted as u64, Ordering::Relaxed);
3863                if let Some(lane) = self.lanes.iter_mut().find(|l| l.tag == emit.lane_tag) {
3864                    lane.spec.0 += emit.spec.proposed as u64;
3865                    lane.spec.1 += emit.spec.accepted as u64;
3866                }
3867            }
3868            if emit.n_tokens > 0 {
3869                let tokens = self.host.client().read_tokens(&emit.token_ref)?;
3870                emitted.insert(emit.lane_tag, (tokens, emit.finish));
3871            } else if emit.finish != finish::NONE {
3872                emitted.insert(emit.lane_tag, (Vec::new(), emit.finish));
3873            }
3874        }
3875        let tick_decode_tokens: u64 = emitted.values().map(|(t, _)| t.len() as u64).sum();
3876        let ttft_tail_secs = if tick_rounds_exec > 1 {
3877            decode_secs_meas * (tick_rounds_exec as f64 - 1.0) / tick_rounds_exec as f64
3878        } else {
3879            0.0
3880        };
3881        if prefill_tokens_ran > 0 || tick_decode_tokens > 0 {
3882            self.stats
3883                .tick_wall_ms_live
3884                .store((tick_secs * 1e3) as u64, Ordering::Relaxed);
3885            self.stats.prefill_rate_live.store(
3886                per_sec(prefill_tokens_ran as u64, prefill_secs_meas),
3887                Ordering::Relaxed,
3888            );
3889            let decode_secs_for_rate =
3890                if decode_secs_meas > 0.0 { decode_secs_meas } else { tick_secs };
3891            self.stats.decode_rate_live.store(
3892                per_sec(tick_decode_tokens, decode_secs_for_rate),
3893                Ordering::Relaxed,
3894            );
3895            self.stats.work_ticks.fetch_add(1, Ordering::Relaxed);
3896        }
3897        let terminators = self.codec.turn_terminators();
3898        for lane in &mut self.lanes {
3899            lane.admitted = true;
3900            let Some((tokens, fin)) = emitted.remove(&lane.tag) else {
3901                continue;
3902            };
3903            if tokens.is_empty() {
3904                lane.fin = fin;
3905                if !lane.job.completion {
3906                    provisional_flush(&self.bus, lane);
3907                    let text = lane.utf8.flush();
3908                    let e = self.store.lock().expect("store").commit_generated(
3909                        lane.session,
3910                        Vec::new(),
3911                        text,
3912                        lane.seg.current_channel(),
3913                        fin,
3914                    )?;
3915                    let _ = lane.job.events.send(LaneEvent::Committed(e));
3916                }
3917                lane.phase = LanePhase::Finishing;
3918                continue;
3919            }
3920            lane.all_tokens.extend_from_slice(&tokens);
3921            lane.produced += tokens.len() as u32;
3922            lane.fin = fin;
3923            lane.decode_ticks = Some(match lane.decode_ticks {
3924                None => (tick_done, lane.produced, tick_done, lane.produced),
3925                Some((at, by, _, _)) => (at, by, tick_done, lane.produced),
3926            });
3927            let frame_fed = std::mem::take(&mut lane.frame_fed).min(tokens.len());
3928            if !lane.job.completion {
3929                provisional_ingest(&self.codec, &self.bus, lane, &tokens[frame_fed..]);
3930            }
3931            if lane.fin == finish::NONE
3932                && !lane.job.extras.ignore_eos
3933                && !terminators.is_empty()
3934                && tokens.iter().any(|t| terminators.contains(t))
3935            {
3936                lane.fin = finish::EOS;
3937            }
3938            if lane.job.extras.want_logprobs {
3939                if let Some(recs) = logprobs_by_lane.remove(&lane.tag) {
3940                    if !recs.is_empty() {
3941                        let items: Vec<TokenLogprob> = tokens
3942                            .iter()
3943                            .zip(recs)
3944                            .map(|(t, (lp, top))| TokenLogprob { token: *t, logprob: lp, top })
3945                            .collect();
3946                        let _ = lane.job.events.send(LaneEvent::TokenLogprobs(items));
3947                    }
3948                }
3949            }
3950            self.stats
3951                .decode_tokens
3952                .fetch_add((tokens.len() - frame_fed) as u64, Ordering::Relaxed);
3953            if !lane.ttft_observed && !tokens.is_empty() {
3954                lane.ttft_observed = true;
3955                let ms = lane.ttft_frame_ms.take().unwrap_or_else(|| {
3956                    let elapsed = lane.admitted_at.elapsed().as_secs_f64();
3957                    ((elapsed - ttft_tail_secs).max(0.0) * 1e3) as u64
3958                });
3959                self.stats.observe_ttft(ms);
3960            }
3961            let terminal_now = lane.fin != finish::NONE || lane.produced >= lane.job.max_tokens;
3962            if lane.job.completion {
3963                let leftover = tokens[frame_fed..].to_vec();
3964                if lane.emit_dead
3965                    || (!leftover.is_empty()
3966                        && lane.job.events.send(LaneEvent::CompletionTokens(leftover)).is_err())
3967                {
3968                    lane.phase = LanePhase::Finishing;
3969                    continue;
3970                }
3971                if terminal_now {
3972                    lane.phase = LanePhase::Finishing;
3973                }
3974                continue;
3975            }
3976            if terminal_now {
3977                provisional_flush(&self.bus, lane);
3978            }
3979            let runs = lane.seg.split(&tokens);
3980            let n_runs = runs.len().max(1);
3981            let mut dead = false;
3982            for (i, run) in runs.into_iter().enumerate() {
3983                let last = i + 1 == n_runs;
3984                let mut text = String::new();
3985                for &t in &run.text {
3986                    text.push_str(&lane.utf8.push(&self.codec.token_bytes(t)));
3987                }
3988                if last && terminal_now {
3989                    text.push_str(&lane.utf8.flush());
3990                }
3991                let channel = run.channel;
3992                let tool_closed = run.closes && channel == crate::wal::channel::TOOL_CALL;
3993                if channel == crate::wal::channel::TOOL_CALL {
3994                    lane.tool_buf.push_str(&text);
3995                }
3996                let e = self.store.lock().expect("store").commit_generated(
3997                    lane.session,
3998                    run.span,
3999                    text,
4000                    channel,
4001                    if last { fin } else { finish::NONE },
4002                )?;
4003                let tool_event = if tool_closed {
4004                    let raw = std::mem::take(&mut lane.tool_buf);
4005                    Self::commit_tool_block(
4006                        &self.codec,
4007                        &self.store,
4008                        self.tool_lease_ms,
4009                        lane.session,
4010                        raw,
4011                        lane.job.extras.tool_schemas.as_deref(),
4012                    )?
4013                } else {
4014                    None
4015                };
4016                if lane.job.events.send(LaneEvent::Committed(e)).is_err() {
4017                    dead = true;
4018                    break;
4019                }
4020                if let Some(te) = tool_event {
4021                    if lane.job.events.send(LaneEvent::Committed(te)).is_err() {
4022                        dead = true;
4023                        break;
4024                    }
4025                }
4026            }
4027            if dead {
4028                lane.fin = finish::CANCELLED;
4029                provisional_flush(&self.bus, lane);
4030                let text = lane.utf8.flush();
4031                let _ = self.store.lock().expect("store").commit_generated(
4032                    lane.session,
4033                    Vec::new(),
4034                    text,
4035                    lane.seg.current_channel(),
4036                    finish::CANCELLED,
4037                )?;
4038                lane.phase = LanePhase::Finishing;
4039                continue;
4040            }
4041            if terminal_now {
4042                if !lane.tool_buf.is_empty() {
4043                    let raw = std::mem::take(&mut lane.tool_buf);
4044                    let te = Self::commit_tool_block(
4045                        &self.codec,
4046                        &self.store,
4047                        self.tool_lease_ms,
4048                        lane.session,
4049                        raw,
4050                        lane.job.extras.tool_schemas.as_deref(),
4051                    )?;
4052                    if let Some(te) = te {
4053                        let _ = lane.job.events.send(LaneEvent::Committed(te));
4054                    }
4055                }
4056                lane.phase = LanePhase::Finishing;
4057            }
4058        }
4059
4060        if !retiring.is_empty() {
4061            let mut i = 0;
4062            let mut requeue: Vec<GenerateJob> = Vec::new();
4063            while i < self.lanes.len() {
4064                if !retiring.contains(&self.lanes[i].tag) {
4065                    i += 1;
4066                    continue;
4067                }
4068                let lane = self.lanes.remove(i);
4069                if lane.preempted && lane.fin == finish::NONE {
4070                    let decode_before = lane.decode_measured();
4071                    let mut job = lane.job;
4072                    job.stream = lane.all_tokens;
4073                    job.max_tokens -= lane.produced;
4074                    job.carry = Some(LaneCarry {
4075                        seg: lane.seg,
4076                        utf8: lane.utf8,
4077                        disp_seg: lane.disp_seg,
4078                        disp_utf8: lane.disp_utf8,
4079                        tool_buf: lane.tool_buf,
4080                        produced_before: lane.produced_before + lane.produced,
4081                        spec_before: lane.spec,
4082                        decode_before,
4083                        warm_prefix_first: Some(lane.warm_prefix_first.unwrap_or(lane.warm_prefix)),
4084                    });
4085                    requeue.push(job);
4086                } else {
4087                    self.drop_ephemeral_artifact(lane.session);
4088                    self.active.lock().expect("active set").remove(&lane.session);
4089                    if let Some(code) = lane.fault_code {
4090                        let _ = lane
4091                            .job
4092                            .events
4093                            .send(LaneEvent::Failed(format!("engine lane fault (code {code})")));
4094                    } else {
4095                        let _ = lane.job.events.send(LaneEvent::Done {
4096                            tokens_generated: lane.produced_before + lane.produced,
4097                            finish: lane.fin,
4098                            warm_prefix: lane.warm_prefix_first.unwrap_or(lane.warm_prefix),
4099                            spec: lane.spec,
4100                            decode: lane.decode_measured(),
4101                        });
4102                    }
4103                }
4104            }
4105            for job in requeue {
4106                let c = (job.class as usize).min(3);
4107                self.queues[c].jobs.push_front(job);
4108            }
4109        }
4110        self.publish_occupancy();
4111        Ok(())
4112    }
4113}
4114
4115fn draft_weights_identity(path: &str) -> Result<(u64, u64), DaemonError> {
4116    use std::io::Read;
4117    const SEED: u64 = 0xCBF2_9CE4_8422_2325;
4118    let mut f = std::fs::File::open(path).map_err(|_| {
4119        DaemonError::Config(
4120            "draft-model weights are not readable by the daemon; an exact draft identity \
4121             requires the daemon to read the weights file",
4122        )
4123    })?;
4124    let size = f.metadata().map(|m| m.len()).unwrap_or(0);
4125    let mut h = SEED;
4126    let mut buf = vec![0u8; 1 << 20];
4127    loop {
4128        match f.read(&mut buf) {
4129            Ok(0) => break,
4130            Ok(n) => h = superfluid_fingerprint::fnv1a64(&buf[..n], h),
4131            Err(_) => {
4132                return Err(DaemonError::Config(
4133                    "failed reading draft-model weights for identity",
4134                ))
4135            }
4136        }
4137    }
4138    Ok((h, size))
4139}
4140
4141pub fn run_completion(
4142    tx: &Sender<SchedCmd>,
4143    stream: Vec<u32>,
4144    params: GenParams,
4145    max_tokens: u32,
4146    deadline_ms: u64,
4147    grammar_handle: u32,
4148) -> Result<(Vec<u32>, u32, bool), DaemonError> {
4149    run_completion_streaming(
4150        tx,
4151        stream,
4152        params,
4153        max_tokens,
4154        deadline_ms,
4155        GenExtras { grammar_handle, ..GenExtras::default() },
4156        |_| std::ops::ControlFlow::Continue(()),
4157    )
4158}
4159
4160pub fn run_completion_streaming(
4161    tx: &Sender<SchedCmd>,
4162    stream: Vec<u32>,
4163    params: GenParams,
4164    max_tokens: u32,
4165    deadline_ms: u64,
4166    extras: GenExtras,
4167    mut on_tokens: impl FnMut(&[u32]) -> std::ops::ControlFlow<()>,
4168) -> Result<(Vec<u32>, u32, bool), DaemonError> {
4169    let (etx, erx) = std::sync::mpsc::channel();
4170    tx.send(SchedCmd::Submit(Box::new(GenerateJob {
4171        session: 0,
4172        stream,
4173        input_end: 0,
4174        params,
4175        max_tokens,
4176        events: etx,
4177        class: crate::qos::INLINE_COMPLETION,
4178        batch_invariant: false,
4179        carry: None,
4180        queued_at: 0,
4181        media: Vec::new(),
4182        completion: true,
4183        deadline: (deadline_ms > 0).then(|| std::time::Instant::now() + std::time::Duration::from_millis(deadline_ms)),
4184        grammar_handle: extras.grammar_handle,
4185        extras,
4186        trace: tracing::Span::current(),
4187    })))
4188    .map_err(|_| DaemonError::Protocol("scheduler is gone"))?;
4189    let mut tokens = Vec::new();
4190    loop {
4191        match erx.recv() {
4192            Ok(LaneEvent::Provisional { .. }) => {}
4193            Ok(LaneEvent::CompletionTokens(t)) => {
4194                if on_tokens(&t).is_break() {
4195                    tokens.extend(t);
4196                    return Ok((tokens, finish::CANCELLED, false));
4197                }
4198                tokens.extend(t);
4199            }
4200            Ok(LaneEvent::Done { finish, .. }) => return Ok((tokens, finish, false)),
4201            Ok(LaneEvent::Expired) => return Ok((Vec::new(), finish::NONE, true)),
4202            Ok(LaneEvent::Committed(_)) => {}
4203            Ok(LaneEvent::TokenLogprobs(_)) => {}
4204            Ok(LaneEvent::Failed(msg)) => return Err(DaemonError::Generation(msg)),
4205            Err(_) => return Err(DaemonError::Protocol("scheduler dropped the lane")),
4206        }
4207    }
4208}
4209
4210pub type RunJobResult = (Vec<CommittedEvent>, u32, u32, u64, Vec<TokenLogprob>, (u64, u64), (u64, f64));
4211
4212#[allow(clippy::too_many_arguments)]
4213pub fn run_job(
4214    tx: &Sender<SchedCmd>,
4215    job_session: u64,
4216    stream: Vec<u32>,
4217    input_end: u64,
4218    params: GenParams,
4219    max_tokens: u32,
4220    class: u8,
4221    batch_invariant: bool,
4222    media: Vec<(u64, std::path::PathBuf, u32)>,
4223    extras: GenExtras,
4224    cancels: &Arc<crate::CancelSet>,
4225    mut on_event: impl FnMut(&CommittedEvent) -> Result<(), DaemonError>,
4226    mut on_logprobs: impl FnMut(&[TokenLogprob]),
4227    mut on_delta: impl FnMut(u32, &str, u32),
4228) -> Result<RunJobResult, DaemonError> {
4229    let (etx, erx) = std::sync::mpsc::channel();
4230    let grammar_handle = extras.grammar_handle;
4231    tx.send(SchedCmd::Submit(Box::new(GenerateJob {
4232        session: job_session,
4233        stream,
4234        input_end,
4235        params,
4236        max_tokens,
4237        events: etx,
4238        class,
4239        batch_invariant,
4240        carry: None,
4241        queued_at: 0,
4242        media,
4243        completion: false,
4244        deadline: None,
4245        grammar_handle,
4246        extras,
4247        trace: tracing::Span::current(),
4248    })))
4249    .map_err(|_| DaemonError::Protocol("scheduler is gone"))?;
4250    if class <= crate::qos::INLINE_COMPLETION {
4251        // A chat or a completion that may preempt: the running tick ends at
4252        // its next step instead of making it wait, and steps stay short while
4253        // such requests keep coming.
4254        cancels.latency_request();
4255    }
4256    let mut events = Vec::new();
4257    let mut logprobs: Vec<TokenLogprob> = Vec::new();
4258    let mut delivery_err: Option<DaemonError> = None;
4259    loop {
4260        match erx.recv() {
4261            Ok(LaneEvent::Provisional { channel, text, produced }) => {
4262                if delivery_err.is_none() {
4263                    on_delta(channel, &text, produced);
4264                }
4265            }
4266            Ok(LaneEvent::TokenLogprobs(items)) => {
4267                if delivery_err.is_none() {
4268                    on_logprobs(&items);
4269                }
4270                logprobs.extend(items);
4271            }
4272            Ok(LaneEvent::Committed(e)) => {
4273                if delivery_err.is_none() {
4274                    if let Err(err) = on_event(&e) {
4275                        cancels.cancel(job_session);
4276                        delivery_err = Some(err);
4277                    } else {
4278                        events.push(e);
4279                    }
4280                }
4281            }
4282            Ok(LaneEvent::Done {
4283                tokens_generated,
4284                finish,
4285                warm_prefix,
4286                spec,
4287                decode,
4288            }) => {
4289                return match delivery_err {
4290                    Some(err) => Err(err),
4291                    None => Ok((events, tokens_generated, finish, warm_prefix, logprobs, spec, decode)),
4292                };
4293            }
4294            Ok(LaneEvent::Failed(msg)) => {
4295                return Err(delivery_err.unwrap_or(DaemonError::Generation(msg)));
4296            }
4297            Ok(LaneEvent::CompletionTokens(_)) | Ok(LaneEvent::Expired) => {
4298                return Err(DaemonError::Protocol("completion event on a generate lane"));
4299            }
4300            Err(_) => {
4301                return Err(delivery_err.unwrap_or(DaemonError::Protocol("scheduler dropped the lane")));
4302            }
4303        }
4304    }
4305}