1#![forbid(unsafe_code)]
12
13use std::fmt;
14use std::future::Future;
15use std::pin::Pin;
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::sync::{Arc, Mutex, PoisonError};
18use std::task::{Context, Poll, Waker};
19use std::time::{Duration, Instant};
20
21use kime_core::answer::{LAYA_MODEL, Response, Temperatures, laya_answer};
22use kime_core::render::{compat_question, compat_state};
23use kime_core::request::{Limits, Problem, Question, Request, parse};
24use kime_model::Model;
25use kime_tensor::{BatchBuf, Buckets, Executor, Outputs};
26use kime_tok::Tokenizer;
27use kime_tok::layout::{CompatBudget, CompatSequence, Cut};
28use serde_json::Value;
29
30mod cache;
31pub mod hub;
32mod split;
33
34use cache::{AnswerCache, Entry};
35pub use cache::{CacheMode, CacheStats};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum Device {
40 #[default]
42 Auto,
43 Cpu {
45 threads: usize,
47 },
48 Cuda(usize),
50 Metal,
52 Ane,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub enum Precision {
59 #[default]
61 F16,
62 F32,
64 Int8,
68}
69
70#[derive(Debug)]
72pub enum Error {
73 Invalid(Vec<Problem>),
75 NotFound(String),
77 Model(kime_model::Error),
79 Tokenizer(String),
81 Backend(kime_tensor::Error),
83 TooLong {
86 question: String,
88 options: usize,
90 fit: usize,
92 },
93 Unsupported(String),
95}
96
97impl fmt::Display for Error {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Error::Invalid(p) => {
101 write!(f, "invalid request:")?;
102 for p in p {
103 write!(f, " {};", p.to_json())?;
104 }
105 Ok(())
106 }
107 Error::NotFound(m) | Error::Tokenizer(m) | Error::Unsupported(m) => f.write_str(m),
108 Error::Model(e) => write!(f, "{e}"),
109 Error::Backend(e) => write!(f, "{e}"),
110 Error::TooLong { question, options, fit } => write!(
111 f,
112 "question {question:?} has {options} options but only {fit} fit the head budget"
113 ),
114 }
115 }
116}
117
118impl std::error::Error for Error {}
119
120impl From<kime_tensor::Error> for Error {
121 fn from(e: kime_tensor::Error) -> Self {
122 Error::Backend(e)
123 }
124}
125
126#[derive(Debug, Clone, Default)]
128pub struct Builder {
129 model: Option<String>,
130 device: Device,
131 precision: Precision,
132 preload: bool,
133 answer_cache: usize,
134}
135
136impl Builder {
137 #[must_use]
140 pub fn model(mut self, name: impl Into<String>) -> Self {
141 self.model = Some(name.into());
142 self
143 }
144
145 #[must_use]
147 pub fn device(mut self, d: Device) -> Self {
148 self.device = d;
149 self
150 }
151
152 #[must_use]
154 pub fn precision(mut self, p: Precision) -> Self {
155 self.precision = p;
156 self
157 }
158
159 #[must_use]
162 pub fn preload(mut self, yes: bool) -> Self {
163 self.preload = yes;
164 self
165 }
166
167 #[must_use]
170 pub fn answer_cache(mut self, entries: usize) -> Self {
171 self.answer_cache = entries;
172 self
173 }
174
175 pub fn build(self) -> Result<Kime, Error> {
181 let name = self.model.unwrap_or_else(|| "laya".into());
182 let path = hub::resolve(&name).map_err(Error::NotFound)?;
183 let model = Model::open(&path).map_err(Error::Model)?;
184 let tok_json = model
185 .file("tokenizer/tokenizer.json")
186 .ok_or_else(|| Error::Tokenizer(format!("{}: no tokenizer.json", path.display())))?;
187 let tok = Tokenizer::from_bytes(tok_json, model.file("tokenizer/tokenizer_config.json"))
188 .map_err(|e| Error::Tokenizer(e.to_string()))?;
189 let agent = &model.spec.agent;
190 let temps = Temperatures::new(agent.temperature, &agent.temperature_by_options);
191 let budget = CompatBudget { max_len: agent.max_len, head_max_len: agent.head_max_len };
192 let mut runner = Runner::open(&model, self.device, self.precision)?;
193 let embed = runner.add_graph(model.graph.embed_plan(&model.spec));
194 if self.preload {
195 for b in Buckets::default().stage("compat").iter().take(4) {
196 runner.prepare(*b)?;
197 }
198 }
199 let buckets = Buckets::default().stage("compat").to_vec();
200 let embed_buckets = Buckets::default().stage(EMBED_STAGE).to_vec();
201 let (weights, plans) = runner.memory();
202 Ok(Kime {
203 inner: Arc::new(Inner {
204 id: model.spec.id.clone(),
205 mask: tok.mask_text().to_string(),
206 tok,
207 budget,
208 temps,
209 buckets,
210 embed_buckets,
211 d: model.spec.encoder.d,
212 memory: [AtomicUsize::new(weights), AtomicUsize::new(plans)],
213 cache: (self.answer_cache > 0).then(|| AnswerCache::new(self.answer_cache)),
214 runner: Mutex::new(Session {
215 runner,
216 embed,
217 buf: BatchBuf::default(),
218 out: Outputs::default(),
219 }),
220 }),
221 })
222 }
223}
224
225enum Runner {
226 Cpu(Box<Executor<kime_cpu::CpuBackend>>),
227 #[cfg(feature = "cuda")]
228 Cuda(Box<Executor<kime_cuda::CudaBackend>>),
229}
230
231impl Runner {
232 fn open(model: &Model, device: Device, precision: Precision) -> Result<Self, Error> {
233 let cpu = |threads: usize| {
234 let t = if threads == 0 { kime_cpu::par::available() } else { threads };
235 let backend = kime_cpu::CpuBackend::new(t).with_int8(precision == Precision::Int8);
236 Ok(Runner::Cpu(Box::new(kime_cpu::executor_with(model, backend)?)))
237 };
238 match device {
239 Device::Cpu { threads } => cpu(threads),
240 #[cfg(feature = "cuda")]
241 Device::Cuda(n) => {
242 Ok(Runner::Cuda(Box::new(kime_cuda::executor(model, n, cuda(precision)?)?)))
243 }
244 #[cfg(feature = "cuda")]
245 Device::Auto => {
246 match cuda(precision).and_then(|p| Ok(kime_cuda::executor(model, 0, p)?)) {
247 Ok(e) => Ok(Runner::Cuda(Box::new(e))),
248 Err(_) => cpu(0),
249 }
250 }
251 #[cfg(not(feature = "cuda"))]
252 Device::Auto => cpu(0),
253 #[cfg(not(feature = "cuda"))]
254 Device::Cuda(_) => Err(Error::Unsupported("this build has no CUDA backend".into())),
255 Device::Metal | Device::Ane => {
256 Err(Error::Unsupported("the Apple backends arrive with M4".into()))
257 }
258 }
259 }
260
261 fn add_graph(&mut self, g: kime_tensor::Graph) -> usize {
262 let b = Buckets::default();
263 match self {
264 Runner::Cpu(e) => e.add_graph(g, &b, EMBED_STAGE),
265 #[cfg(feature = "cuda")]
266 Runner::Cuda(e) => e.add_graph(g, &b, EMBED_STAGE),
267 }
268 }
269
270 fn prepare(&mut self, b: kime_tensor::Bucket) -> Result<(), Error> {
271 match self {
272 Runner::Cpu(e) => e.prepare(b)?,
273 #[cfg(feature = "cuda")]
274 Runner::Cuda(e) => e.prepare(b)?,
275 };
276 Ok(())
277 }
278
279 fn run(&mut self, buf: &BatchBuf, out: &mut Outputs) -> Result<(), Error> {
280 self.run_lane(0, buf, out)
281 }
282
283 fn run_lane(&mut self, lane: usize, buf: &BatchBuf, out: &mut Outputs) -> Result<(), Error> {
284 match self {
285 Runner::Cpu(e) => e.run_lane(lane, &buf.batch(), out)?,
286 #[cfg(feature = "cuda")]
287 Runner::Cuda(e) => e.run_lane(lane, &buf.batch(), out)?,
288 };
289 Ok(())
290 }
291
292 fn memory(&self) -> (usize, usize) {
293 match self {
294 Runner::Cpu(e) => e.memory(),
295 #[cfg(feature = "cuda")]
296 Runner::Cuda(e) => e.memory(),
297 }
298 }
299
300 fn int8(&self) -> bool {
301 match self {
302 Runner::Cpu(e) => e.backend().int8(),
303 #[cfg(feature = "cuda")]
304 Runner::Cuda(_) => false,
305 }
306 }
307
308 fn describe(&self) -> String {
309 match self {
310 Runner::Cpu(e) => {
311 let int8 = if e.backend().int8() { ", int8" } else { "" };
312 format!("cpu, {} threads{int8}", kime_tensor::Backend::caps(e.backend()).threads)
313 }
314 #[cfg(feature = "cuda")]
315 Runner::Cuda(e) => format!("cuda, {}", e.backend().name()),
316 }
317 }
318}
319
320#[cfg(feature = "cuda")]
321fn cuda(p: Precision) -> Result<kime_cuda::Precision, Error> {
322 match p {
323 Precision::F16 => Ok(kime_cuda::Precision::F16),
324 Precision::F32 => Ok(kime_cuda::Precision::F32),
325 Precision::Int8 => Err(Error::Unsupported("INT8 runs on the CPU only for now".into())),
326 }
327}
328
329const EMBED_STAGE: &str = "state";
331
332struct Session {
333 runner: Runner,
334 embed: usize,
336 buf: BatchBuf,
337 out: Outputs,
338}
339
340struct Inner {
341 id: String,
342 tok: Tokenizer,
343 mask: String,
344 budget: CompatBudget,
345 temps: Temperatures,
346 buckets: Vec<kime_tensor::Bucket>,
348 embed_buckets: Vec<kime_tensor::Bucket>,
350 d: usize,
352 runner: Mutex<Session>,
353 memory: [AtomicUsize; 2],
355 cache: Option<AnswerCache>,
356}
357
358#[derive(Clone)]
360pub struct Kime {
361 inner: Arc<Inner>,
362}
363
364impl fmt::Debug for Kime {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 f.debug_struct("Kime").field("model", &self.inner.id).finish_non_exhaustive()
367 }
368}
369
370#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
372pub struct Timing {
373 pub tokenize: Duration,
375 pub device: Duration,
377 pub batches: usize,
379 pub truncated: usize,
381 pub cut_tokens: usize,
383 pub cached: usize,
385}
386
387#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
389pub struct Memory {
390 pub weights: usize,
392 pub plans: usize,
394}
395
396struct Item<'a> {
398 req: usize,
399 q: &'a Question,
400 seq: CompatSequence,
401 logits: Vec<f32>,
402 act: [f32; 2],
403}
404
405impl Item<'_> {
406 fn key(&self) -> cache::Key {
407 AnswerCache::key(&self.seq, self.q.qtype.index() as u8)
408 }
409
410 fn fill(&mut self, e: Entry) {
411 self.logits = e.logits.into_vec();
412 self.act = e.act;
413 }
414}
415
416fn mode(req: &Request) -> CacheMode {
418 req.kime
419 .as_ref()
420 .and_then(|k| k.get("cache"))
421 .and_then(Value::as_str)
422 .and_then(CacheMode::parse)
423 .unwrap_or_default()
424}
425
426impl Kime {
427 #[must_use]
429 pub fn builder() -> Builder {
430 Builder::default()
431 }
432
433 #[must_use]
435 pub fn model_id(&self) -> &str {
436 &self.inner.id
437 }
438
439 #[must_use]
441 pub fn device(&self) -> String {
442 self.lock().runner.describe()
443 }
444
445 #[must_use]
448 pub fn max_row_tokens(&self) -> usize {
449 self.inner.budget.max_len
450 }
451
452 #[must_use]
454 pub fn memory(&self) -> Memory {
455 let m = &self.inner.memory;
456 Memory { weights: m[0].load(Ordering::Relaxed), plans: m[1].load(Ordering::Relaxed) }
457 }
458
459 #[must_use]
461 pub fn cache_stats(&self) -> CacheStats {
462 self.inner.cache.as_ref().map(AnswerCache::stats).unwrap_or_default()
463 }
464
465 #[must_use]
469 pub fn cached(&self, req: &Request) -> Option<Response> {
470 let cache = self.inner.cache.as_ref()?;
471 if mode(req) != CacheMode::Use || req.questions.is_empty() {
472 return None;
473 }
474 let parsed = [parse(&req.to_json(), &Limits::LAYA).ok()?];
475 let mut items = self.lay_out(&parsed).ok()?;
476 let keys: Vec<_> = items.iter().map(Item::key).collect();
477 for (it, e) in items.iter_mut().zip(cache.all(&keys)?) {
478 it.fill(e);
479 }
480 self.respond(&parsed, &items).pop()
481 }
482
483 fn lock(&self) -> std::sync::MutexGuard<'_, Session> {
484 self.inner.runner.lock().unwrap_or_else(PoisonError::into_inner)
485 }
486
487 #[must_use]
491 pub fn count_tokens(&self, req: &Request) -> usize {
492 let inner = &*self.inner;
493 let mut n = inner.tok.encode(&compat_state(&req.state, &inner.mask)).len();
494 for q in &req.questions {
495 let text = compat_question(q, &inner.mask);
496 n += inner.tok.encode(&text.head).len();
497 n += text.options.iter().map(|o| inner.tok.encode(o).len()).sum::<usize>();
498 }
499 n
500 }
501
502 pub fn decide(&self, req: &Request) -> Result<Response, Error> {
509 Ok(self.decide_batch(std::slice::from_ref(req))?.remove(0))
510 }
511
512 pub fn decide_batch(&self, reqs: &[Request]) -> Result<Vec<Response>, Error> {
519 Ok(self.decide_batch_timed(reqs)?.0)
520 }
521
522 pub fn decide_batch_timed(&self, reqs: &[Request]) -> Result<(Vec<Response>, Timing), Error> {
528 let t0 = Instant::now();
529 let mut parsed = Vec::with_capacity(reqs.len());
530 for r in reqs {
531 parsed.push(parse(&r.to_json(), &Limits::LAYA).map_err(Error::Invalid)?);
532 }
533 let mut items = self.lay_out(&parsed)?;
534 let tokenize = t0.elapsed();
535 let t1 = Instant::now();
536 let (run, keys) = self.look_up(reqs, &mut items);
537 let batches = if run.is_empty() { 0 } else { self.run(&mut items, &run)? };
538 if let Some(c) = &self.inner.cache {
539 let keep = run.iter().filter(|&&i| mode(&reqs[items[i].req]) != CacheMode::Bypass);
540 c.insert(keep.map(|&i| {
541 (keys[i], Entry { logits: items[i].logits.clone().into(), act: items[i].act })
542 }));
543 }
544 let cached = items.len() - run.len();
545 let cut = items.iter().map(|it| it.seq.state_tokens - it.seq.state_tokens_used);
546 let timing = Timing {
547 tokenize,
548 device: t1.elapsed(),
549 batches,
550 truncated: cut.clone().filter(|&n| n > 0).count(),
551 cut_tokens: cut.sum(),
552 cached,
553 };
554 Ok((self.respond(&parsed, &items), timing))
555 }
556
557 fn lay_out<'a>(&self, parsed: &'a [Request]) -> Result<Vec<Item<'a>>, Error> {
559 let inner = &*self.inner;
560 let mut items = Vec::new();
561 for (i, r) in parsed.iter().enumerate() {
562 if r.questions.is_empty() {
563 continue;
564 }
565 let cut = if matches!(r.state, Value::Array(_)) { Cut::Head } else { Cut::Tail };
567 let state = inner.tok.encode_state(&compat_state(&r.state, &inner.mask));
568 for q in &r.questions {
569 let text = compat_question(q, &inner.mask);
570 let seq =
571 inner.tok.compat_sequence(&text.head, &text.options, &state, inner.budget, cut);
572 if seq.markers.len() != q.criteria.len() {
573 return Err(Error::TooLong {
574 question: q.id.clone(),
575 options: q.criteria.len(),
576 fit: seq.markers.len(),
577 });
578 }
579 items.push(Item { req: i, q, seq, logits: Vec::new(), act: [0.0; 2] });
580 }
581 }
582 Ok(items)
583 }
584
585 fn look_up(&self, reqs: &[Request], items: &mut [Item<'_>]) -> (Vec<usize>, Vec<cache::Key>) {
588 let Some(c) = &self.inner.cache else { return ((0..items.len()).collect(), Vec::new()) };
589 let keys: Vec<_> = items.iter().map(Item::key).collect();
590 let look: Vec<usize> =
591 (0..items.len()).filter(|&i| mode(&reqs[items[i].req]) == CacheMode::Use).collect();
592 let found = c.get(&look.iter().map(|&i| keys[i]).collect::<Vec<_>>());
593 let mut hit = vec![false; items.len()];
594 for (&i, e) in look.iter().zip(found) {
595 if let Some(e) = e {
596 items[i].fill(e);
597 hit[i] = true;
598 }
599 }
600 ((0..items.len()).filter(|&i| !hit[i]).collect(), keys)
601 }
602
603 fn respond(&self, parsed: &[Request], items: &[Item<'_>]) -> Vec<Response> {
605 let inner = &*self.inner;
606 let mut out: Vec<Response> = parsed
607 .iter()
608 .map(|_| Response { model: LAYA_MODEL.into(), answers: Vec::new(), input_tokens: 0 })
609 .collect();
610 for it in items {
611 let res = &mut out[it.req];
612 res.input_tokens += it.seq.ids.len();
613 res.answers
614 .push((it.q.id.clone(), laya_answer(it.q, &it.logits, it.act, &inner.temps)));
615 }
616 out
617 }
618
619 fn run(&self, items: &mut [Item<'_>], which: &[usize]) -> Result<usize, Error> {
622 let sizes: Vec<(usize, usize)> =
623 which.iter().map(|&i| (items[i].seq.ids.len(), items[i].seq.markers.len())).collect();
624 let batches: Vec<Vec<usize>> = split::split(&self.inner.buckets, &sizes)
625 .into_iter()
626 .map(|b| b.into_iter().map(|j| which[j]).collect())
627 .collect();
628 let mut s = self.lock();
629 let Session { runner, buf, out, .. } = &mut *s;
630 for batch in &batches {
631 buf.clear();
632 for &i in batch {
633 let it = &items[i];
634 buf.push(&it.seq.ids, &it.seq.markers, it.q.qtype.index() as u8);
635 }
636 runner.run(buf, out)?;
637 let mut at = 0;
638 for (&i, a) in batch.iter().zip(&out.act) {
639 let it = &mut items[i];
640 let k = it.seq.markers.len();
641 it.logits = out.logits[at..at + k].to_vec();
642 it.act = *a;
643 at += k;
644 }
645 }
646 let (weights, plans) = runner.memory();
647 self.inner.memory[0].store(weights, Ordering::Relaxed);
648 self.inner.memory[1].store(plans, Ordering::Relaxed);
649 Ok(batches.len())
650 }
651
652 pub fn embed(&self, texts: &[&str], max_length: usize) -> Result<Vec<Vec<f32>>, Error> {
661 let inner = &*self.inner;
662 if self.lock().runner.int8() {
663 return Err(Error::Unsupported("embeddings need FP32 or FP16, not INT8".into()));
665 }
666 let sp = inner.tok.specials();
667 let keep = max_length.saturating_sub(2);
668 let seqs: Vec<Vec<u32>> = texts
669 .iter()
670 .map(|t| {
671 let mut ids = Vec::with_capacity(keep.min(t.len()) + 2);
672 ids.push(sp.cls);
673 inner.tok.encode_into(t, &mut ids);
674 ids.truncate(keep + 1);
675 ids.push(sp.sep);
676 ids
677 })
678 .collect();
679 let sizes: Vec<(usize, usize)> = seqs.iter().map(|s| (s.len(), 0)).collect();
680 let batches = split::split(&inner.embed_buckets, &sizes);
681 let mut rows = vec![Vec::new(); texts.len()];
682 let mut s = self.lock();
683 let Session { runner, embed, buf, out } = &mut *s;
684 for batch in &batches {
685 buf.clear();
686 for &i in batch {
687 buf.push(&seqs[i], &[], 0);
688 }
689 runner.run_lane(*embed, buf, out)?;
690 for (&i, row) in batch.iter().zip(out.pooled.chunks_exact(inner.d)) {
691 rows[i] = row.to_vec();
692 }
693 }
694 let (weights, plans) = runner.memory();
695 inner.memory[0].store(weights, Ordering::Relaxed);
696 inner.memory[1].store(plans, Ordering::Relaxed);
697 Ok(rows)
698 }
699
700 #[must_use]
703 pub fn decide_async(&self, req: &Request) -> Decision {
704 let shared = Arc::new(Mutex::new((None, None::<Waker>)));
705 let (kime, req, done) = (self.clone(), req.clone(), shared.clone());
706 std::thread::spawn(move || {
707 let r = kime.decide(&req);
708 let mut g = done.lock().unwrap_or_else(PoisonError::into_inner);
709 g.0 = Some(r);
710 if let Some(w) = g.1.take() {
711 w.wake();
712 }
713 });
714 Decision { shared }
715 }
716}
717
718#[derive(Debug)]
720pub struct Decision {
721 #[allow(clippy::type_complexity)]
722 shared: Arc<Mutex<(Option<Result<Response, Error>>, Option<Waker>)>>,
723}
724
725impl Future for Decision {
726 type Output = Result<Response, Error>;
727
728 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
729 let mut g = self.shared.lock().unwrap_or_else(PoisonError::into_inner);
730 match g.0.take() {
731 Some(r) => Poll::Ready(r),
732 None => {
733 g.1 = Some(cx.waker().clone());
734 Poll::Pending
735 }
736 }
737 }
738}