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