1#![forbid(unsafe_code)]
12
13use std::fmt;
14use std::future::Future;
15use std::pin::Pin;
16use std::sync::{Arc, Mutex, PoisonError};
17use std::task::{Context, Poll, Waker};
18
19use kime_core::answer::{LAYA_MODEL, Response, Temperatures, laya_answer};
20use kime_core::render::{compat_question, compat_state};
21use kime_core::request::{Limits, Problem, Question, Request, parse};
22use kime_model::Model;
23use kime_tensor::{BatchBuf, Buckets, Executor, Outputs};
24use kime_tok::Tokenizer;
25use kime_tok::layout::{CompatBudget, CompatSequence, Cut};
26use serde_json::Value;
27
28pub mod hub;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum Device {
33 #[default]
35 Auto,
36 Cpu {
38 threads: usize,
40 },
41 Cuda(usize),
43 Metal,
45 Ane,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum Precision {
52 #[default]
54 F16,
55 F32,
57 Int8,
61}
62
63#[derive(Debug)]
65pub enum Error {
66 Invalid(Vec<Problem>),
68 NotFound(String),
70 Model(kime_model::Error),
72 Tokenizer(String),
74 Backend(kime_tensor::Error),
76 TooLong {
79 question: String,
81 options: usize,
83 fit: usize,
85 },
86 Unsupported(String),
88}
89
90impl fmt::Display for Error {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Error::Invalid(p) => {
94 write!(f, "invalid request:")?;
95 for p in p {
96 write!(f, " {};", p.to_json())?;
97 }
98 Ok(())
99 }
100 Error::NotFound(m) | Error::Tokenizer(m) | Error::Unsupported(m) => f.write_str(m),
101 Error::Model(e) => write!(f, "{e}"),
102 Error::Backend(e) => write!(f, "{e}"),
103 Error::TooLong { question, options, fit } => write!(
104 f,
105 "question {question:?} has {options} options but only {fit} fit the head budget"
106 ),
107 }
108 }
109}
110
111impl std::error::Error for Error {}
112
113impl From<kime_tensor::Error> for Error {
114 fn from(e: kime_tensor::Error) -> Self {
115 Error::Backend(e)
116 }
117}
118
119#[derive(Debug, Clone, Default)]
121pub struct Builder {
122 model: Option<String>,
123 device: Device,
124 precision: Precision,
125 preload: bool,
126}
127
128impl Builder {
129 #[must_use]
132 pub fn model(mut self, name: impl Into<String>) -> Self {
133 self.model = Some(name.into());
134 self
135 }
136
137 #[must_use]
139 pub fn device(mut self, d: Device) -> Self {
140 self.device = d;
141 self
142 }
143
144 #[must_use]
146 pub fn precision(mut self, p: Precision) -> Self {
147 self.precision = p;
148 self
149 }
150
151 #[must_use]
154 pub fn preload(mut self, yes: bool) -> Self {
155 self.preload = yes;
156 self
157 }
158
159 pub fn build(self) -> Result<Kime, Error> {
165 let name = self.model.unwrap_or_else(|| "laya".into());
166 let path = hub::resolve(&name).map_err(Error::NotFound)?;
167 let model = Model::open(&path).map_err(Error::Model)?;
168 let tok_json = model
169 .file("tokenizer/tokenizer.json")
170 .ok_or_else(|| Error::Tokenizer(format!("{}: no tokenizer.json", path.display())))?;
171 let tok = Tokenizer::from_bytes(tok_json, model.file("tokenizer/tokenizer_config.json"))
172 .map_err(|e| Error::Tokenizer(e.to_string()))?;
173 let agent = &model.spec.agent;
174 let temps = Temperatures::new(agent.temperature, &agent.temperature_by_options);
175 let budget = CompatBudget { max_len: agent.max_len, head_max_len: agent.head_max_len };
176 let mut runner = Runner::open(&model, self.device, self.precision)?;
177 if self.preload {
178 for b in Buckets::default().stage("compat").iter().take(4) {
179 runner.prepare(*b)?;
180 }
181 }
182 let buckets = Buckets::default();
183 let largest = buckets.stage("compat").last().copied().unwrap_or(kime_tensor::Bucket {
184 tokens: 16384,
185 seqs: 1024,
186 markers: 8192,
187 });
188 Ok(Kime {
189 inner: Arc::new(Inner {
190 id: model.spec.id.clone(),
191 mask: tok.mask_text().to_string(),
192 tok,
193 budget,
194 temps,
195 limits: (largest.tokens, largest.seqs, largest.markers),
196 runner: Mutex::new(Session {
197 runner,
198 buf: BatchBuf::default(),
199 out: Outputs::default(),
200 }),
201 }),
202 })
203 }
204}
205
206enum Runner {
207 Cpu(Box<Executor<kime_cpu::CpuBackend>>),
208 #[cfg(feature = "cuda")]
209 Cuda(Box<Executor<kime_cuda::CudaBackend>>),
210}
211
212impl Runner {
213 fn open(model: &Model, device: Device, precision: Precision) -> Result<Self, Error> {
214 let cpu = |threads: usize| {
215 let t = if threads == 0 { kime_cpu::par::available() } else { threads };
216 let backend = kime_cpu::CpuBackend::new(t).with_int8(precision == Precision::Int8);
217 Ok(Runner::Cpu(Box::new(kime_cpu::executor_with(model, backend)?)))
218 };
219 match device {
220 Device::Cpu { threads } => cpu(threads),
221 #[cfg(feature = "cuda")]
222 Device::Cuda(n) => {
223 Ok(Runner::Cuda(Box::new(kime_cuda::executor(model, n, cuda(precision)?)?)))
224 }
225 #[cfg(feature = "cuda")]
226 Device::Auto => {
227 match cuda(precision).and_then(|p| Ok(kime_cuda::executor(model, 0, p)?)) {
228 Ok(e) => Ok(Runner::Cuda(Box::new(e))),
229 Err(_) => cpu(0),
230 }
231 }
232 #[cfg(not(feature = "cuda"))]
233 Device::Auto => cpu(0),
234 #[cfg(not(feature = "cuda"))]
235 Device::Cuda(_) => Err(Error::Unsupported("this build has no CUDA backend".into())),
236 Device::Metal | Device::Ane => {
237 Err(Error::Unsupported("the Apple backends arrive with M4".into()))
238 }
239 }
240 }
241
242 fn prepare(&mut self, b: kime_tensor::Bucket) -> Result<(), Error> {
243 match self {
244 Runner::Cpu(e) => e.prepare(b)?,
245 #[cfg(feature = "cuda")]
246 Runner::Cuda(e) => e.prepare(b)?,
247 };
248 Ok(())
249 }
250
251 fn run(&mut self, buf: &BatchBuf, out: &mut Outputs) -> Result<(), Error> {
252 match self {
253 Runner::Cpu(e) => e.run(&buf.batch(), out)?,
254 #[cfg(feature = "cuda")]
255 Runner::Cuda(e) => e.run(&buf.batch(), out)?,
256 };
257 Ok(())
258 }
259
260 fn describe(&self) -> String {
261 match self {
262 Runner::Cpu(e) => {
263 let int8 = if e.backend().int8() { ", int8" } else { "" };
264 format!("cpu, {} threads{int8}", kime_tensor::Backend::caps(e.backend()).threads)
265 }
266 #[cfg(feature = "cuda")]
267 Runner::Cuda(e) => format!("cuda, {}", e.backend().name()),
268 }
269 }
270}
271
272#[cfg(feature = "cuda")]
273fn cuda(p: Precision) -> Result<kime_cuda::Precision, Error> {
274 match p {
275 Precision::F16 => Ok(kime_cuda::Precision::F16),
276 Precision::F32 => Ok(kime_cuda::Precision::F32),
277 Precision::Int8 => Err(Error::Unsupported("INT8 runs on the CPU only for now".into())),
278 }
279}
280
281struct Session {
282 runner: Runner,
283 buf: BatchBuf,
284 out: Outputs,
285}
286
287struct Inner {
288 id: String,
289 tok: Tokenizer,
290 mask: String,
291 budget: CompatBudget,
292 temps: Temperatures,
293 limits: (usize, usize, usize),
295 runner: Mutex<Session>,
296}
297
298#[derive(Clone)]
300pub struct Kime {
301 inner: Arc<Inner>,
302}
303
304impl fmt::Debug for Kime {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 f.debug_struct("Kime").field("model", &self.inner.id).finish_non_exhaustive()
307 }
308}
309
310struct Item<'a> {
312 req: usize,
313 q: &'a Question,
314 seq: CompatSequence,
315 logits: Vec<f32>,
316 act: [f32; 2],
317}
318
319impl Kime {
320 #[must_use]
322 pub fn builder() -> Builder {
323 Builder::default()
324 }
325
326 #[must_use]
328 pub fn model_id(&self) -> &str {
329 &self.inner.id
330 }
331
332 #[must_use]
334 pub fn device(&self) -> String {
335 self.lock().runner.describe()
336 }
337
338 fn lock(&self) -> std::sync::MutexGuard<'_, Session> {
339 self.inner.runner.lock().unwrap_or_else(PoisonError::into_inner)
340 }
341
342 pub fn decide(&self, req: &Request) -> Result<Response, Error> {
349 Ok(self.decide_batch(std::slice::from_ref(req))?.remove(0))
350 }
351
352 pub fn decide_batch(&self, reqs: &[Request]) -> Result<Vec<Response>, Error> {
359 let inner = &*self.inner;
360 let mut parsed = Vec::with_capacity(reqs.len());
361 for r in reqs {
362 parsed.push(parse(&r.to_json(), &Limits::LAYA).map_err(Error::Invalid)?);
363 }
364 let mut items = Vec::new();
365 for (i, r) in parsed.iter().enumerate() {
366 if r.questions.is_empty() {
367 continue;
368 }
369 let cut = if matches!(r.state, Value::Array(_)) { Cut::Head } else { Cut::Tail };
371 let state = inner.tok.encode_state(&compat_state(&r.state, &inner.mask));
372 for q in &r.questions {
373 let text = compat_question(q, &inner.mask);
374 let seq =
375 inner.tok.compat_sequence(&text.head, &text.options, &state, inner.budget, cut);
376 if seq.markers.len() != q.criteria.len() {
377 return Err(Error::TooLong {
378 question: q.id.clone(),
379 options: q.criteria.len(),
380 fit: seq.markers.len(),
381 });
382 }
383 items.push(Item { req: i, q, seq, logits: Vec::new(), act: [0.0; 2] });
384 }
385 }
386 self.run(&mut items)?;
387 let mut out: Vec<Response> = parsed
388 .iter()
389 .map(|_| Response { model: LAYA_MODEL.into(), answers: Vec::new(), input_tokens: 0 })
390 .collect();
391 for it in &items {
392 let res = &mut out[it.req];
393 res.input_tokens += it.seq.ids.len();
394 res.answers
395 .push((it.q.id.clone(), laya_answer(it.q, &it.logits, it.act, &inner.temps)));
396 }
397 Ok(out)
398 }
399
400 fn run(&self, items: &mut [Item<'_>]) -> Result<(), Error> {
402 let (max_t, max_s, max_m) = self.inner.limits;
403 let mut s = self.lock();
404 let Session { runner, buf, out } = &mut *s;
405 let mut start = 0;
406 while start < items.len() {
407 let (mut t, mut m, mut end) = (0, 0, start);
408 while end < items.len() && end - start < max_s {
409 let it = &items[end];
410 if end > start && (t + it.seq.ids.len() > max_t || m + it.seq.markers.len() > max_m)
411 {
412 break;
413 }
414 t += it.seq.ids.len();
415 m += it.seq.markers.len();
416 end += 1;
417 }
418 buf.clear();
419 for it in &items[start..end] {
420 buf.push(&it.seq.ids, &it.seq.markers, it.q.qtype.index() as u8);
421 }
422 runner.run(buf, out)?;
423 let mut at = 0;
424 for (it, a) in items[start..end].iter_mut().zip(&out.act) {
425 let k = it.seq.markers.len();
426 it.logits = out.logits[at..at + k].to_vec();
427 it.act = *a;
428 at += k;
429 }
430 start = end;
431 }
432 Ok(())
433 }
434
435 #[must_use]
438 pub fn decide_async(&self, req: &Request) -> Decision {
439 let shared = Arc::new(Mutex::new((None, None::<Waker>)));
440 let (kime, req, done) = (self.clone(), req.clone(), shared.clone());
441 std::thread::spawn(move || {
442 let r = kime.decide(&req);
443 let mut g = done.lock().unwrap_or_else(PoisonError::into_inner);
444 g.0 = Some(r);
445 if let Some(w) = g.1.take() {
446 w.wake();
447 }
448 });
449 Decision { shared }
450 }
451}
452
453#[derive(Debug)]
455pub struct Decision {
456 #[allow(clippy::type_complexity)]
457 shared: Arc<Mutex<(Option<Result<Response, Error>>, Option<Waker>)>>,
458}
459
460impl Future for Decision {
461 type Output = Result<Response, Error>;
462
463 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
464 let mut g = self.shared.lock().unwrap_or_else(PoisonError::into_inner);
465 match g.0.take() {
466 Some(r) => Poll::Ready(r),
467 None => {
468 g.1 = Some(cx.waker().clone());
469 Poll::Pending
470 }
471 }
472 }
473}