1use 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;
22const 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 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;
73const 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;
81const OPEN_PREFILL_TICK_SECS: f64 = 0.5;
84const OPEN_PREFILL_TICK_MAX: u32 = 1024;
87
88const OPEN_PREFILL_TICK_SECS_YIELDING: f64 = 2.0;
91
92const LATENCY_HOLD: std::time::Duration = std::time::Duration::from_secs(300);
95
96const SHARED_PREFIX_MIN_TOKENS: u64 = 1024;
99const SHARED_PREFIX_TAIL_TOKENS: u32 = 64;
101const SHARED_PREFIX_MAX_HOLD_TICKS: u64 = 64;
103const TURN_CHECKPOINT_MIN_TOKENS: u64 = 128;
107const TURN_CHECKPOINTS_TRACKED: usize = 64;
110
111const PREFILL_RATE_MIN_TOKENS: u32 = 256;
112
113
114const SHARED_PREFIX_CLASS: u64 = 2;
117const 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 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 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 checkpoint_at: u64,
333 checkpoint_due: bool,
334 checkpointed: u64,
335 turn_kept: bool,
337 produced_before: u32,
338 spec: (u64, u64),
339 decode: (u64, f64),
345 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 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
619fn 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
637enum CheckpointError {
640 Refused(String),
641 Failed(String),
642}
643
644impl CheckpointError {
645 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 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
696fn 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
707fn 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 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, ®.impl_hash)
1056 .field(3, ®.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 prefix_checkpoints_off: bool,
1185 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 self.host.clear_yield();
1424 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let all = if host_short { u64::MAX } else { !HOST_EXPORT_CLASS };
2573 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 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 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 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 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 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 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 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 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 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}