1use std::fmt;
20use std::path::Path;
21use std::time::Instant;
22
23use taconite_bundle::{Manifest, Store};
24use npu::Buffer;
25pub use taconite_sam3::Timing;
26use taconite_sam3::cpu::{dot, ln_row, par_rows};
27use taconite_sam3::tokenizer::Tokenizer;
28
29pub mod npu;
30pub mod preprocess;
31pub mod tower;
32
33use npu::Npu;
34use preprocess::Preprocess;
35use tower::Tower;
36
37pub const VERSION: u32 = 1;
38
39#[derive(Debug)]
40pub enum Error {
41 Bundle(String),
42 Npu(String),
43 Input(String),
44}
45
46impl fmt::Display for Error {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Error::Bundle(m) => write!(f, "bundle: {m}"),
50 Error::Npu(m) => write!(f, "NPU: {m}"),
51 Error::Input(m) => write!(f, "input: {m}"),
52 }
53 }
54}
55
56impl std::error::Error for Error {}
57
58impl From<taconite_bundle::Error> for Error {
59 fn from(e: taconite_bundle::Error) -> Self {
60 Error::Bundle(e.to_string())
61 }
62}
63
64impl From<taconite::Error> for Error {
65 fn from(e: taconite::Error) -> Self {
66 Error::Npu(e.to_string())
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct Config {
73 pub v_layers: usize,
74 pub v_dim: usize,
75 pub v_tokens: usize,
76 pub v_stride: usize,
78 pub v_batch: usize,
79 pub v_rows: usize,
80 pub v_mha_rows: usize,
81 pub v_eps: f32,
82 pub patch: usize,
83 pub image_size: usize,
84 pub t_layers: usize,
85 pub t_dim: usize,
86 pub t_batch: usize,
87 pub t_seq_pad: usize,
88 pub t_rows: usize,
89 pub t_eps: f32,
90 pub context: usize,
91 pub embed_dim: usize,
92 pub logit_scale: f32,
93 pub eos_argmax: bool,
94 pub prompt_template: String,
95}
96
97impl Config {
98 fn load(m: &Manifest) -> Result<Self, Error> {
99 let u = |k: &str| -> Result<usize, Error> { Ok(m.param_as(k)?) };
100 let f = |k: &str| -> Result<f32, Error> { Ok(m.param_as(k)?) };
101 Ok(Config {
102 v_layers: u("v_layers")?,
103 v_dim: u("v_dim")?,
104 v_tokens: u("v_tokens")?,
105 v_stride: u("v_stride")?,
106 v_batch: u("v_batch")?,
107 v_rows: u("v_rows")?,
108 v_mha_rows: u("v_mha_rows")?,
109 v_eps: f("v_eps")?,
110 patch: u("patch")?,
111 image_size: u("image_size")?,
112 t_layers: u("t_layers")?,
113 t_dim: u("t_dim")?,
114 t_batch: u("t_batch")?,
115 t_seq_pad: u("t_seq_pad")?,
116 t_rows: u("t_rows")?,
117 t_eps: f("t_eps")?,
118 context: u("context")?,
119 embed_dim: u("embed_dim")?,
120 logit_scale: f("logit_scale")?,
121 eos_argmax: u("eos_argmax")? != 0,
122 prompt_template: m.param("prompt_template")?.to_string(),
123 })
124 }
125
126 pub fn grid(&self) -> usize {
128 self.image_size / self.patch
129 }
130}
131
132pub(crate) fn push(src: &[u16], b: &mut Buffer) -> Result<(), Error> {
134 const COPY: usize = 1 << 16;
135 let dst = &mut b.as_mut_slice::<u16>()[..src.len()];
136 par_rows(dst, COPY, |r0, piece| piece.copy_from_slice(&src[r0 * COPY..][..piece.len()]));
137 Ok(b.sync_to_device()?)
138}
139
140pub(crate) fn pull(b: &Buffer, n: usize) -> Result<Vec<u16>, Error> {
142 const COPY: usize = 1 << 16;
143 b.sync_from_device()?;
144 let src = &b.as_slice::<u16>()[..n];
145 let mut dst = vec![0u16; n];
146 par_rows(&mut dst, COPY, |r0, piece| piece.copy_from_slice(&src[r0 * COPY..][..piece.len()]));
147 Ok(dst)
148}
149
150pub struct Clip {
151 pub cfg: Config,
152 pub manifest: Manifest,
153 pub store: Store,
154 pub tokenizer: Tokenizer,
155 pub preprocess: Preprocess,
156 pub timing: Timing,
157 npu: Npu,
158 vision: Tower,
159 text: Tower,
160}
161
162impl Clip {
163 pub fn load(dir: &Path) -> Result<Self, Error> {
166 let manifest = Manifest::load(dir, VERSION)?;
167 let cfg = Config::load(&manifest)?;
168 let store = Store::load(dir)?;
169 let u = |k: &str| -> Result<u32, Error> { Ok(manifest.param_as(k)?) };
170 let tokenizer = Tokenizer::load(dir, u("bos")?, u("eos")?, u("pad")?, cfg.context)
171 .map_err(|e| Error::Bundle(e.to_string()))?;
172 let three = |k: &str| -> Result<[f32; 3], Error> {
173 let v: Vec<f32> = manifest.list(k)?;
174 v.try_into().map_err(|_| Error::Bundle(format!("param {k}: expected 3 values")))
175 };
176 let preprocess = Preprocess {
177 shortest: manifest.param_as("resize_shortest")?,
178 crop: manifest.param_as("crop")?,
179 mean: three("mean")?,
180 std: three("std")?,
181 };
182 if preprocess.crop != cfg.image_size {
183 return Err(Error::Bundle("crop size != image size".into()));
184 }
185
186 let npu = Npu::open(&manifest)?;
187 let v_mha: Vec<(usize, usize)> = (0..cfg.v_batch).map(|i| (i * cfg.v_stride, cfg.v_mha_rows)).collect();
191 let vision = Tower::new(&npu, &store, "v", cfg.v_layers, cfg.v_dim, cfg.v_rows, cfg.v_eps, &v_mha)?;
192 let text = Tower::new(
193 &npu,
194 &store,
195 "t",
196 cfg.t_layers,
197 cfg.t_dim,
198 cfg.t_rows,
199 cfg.t_eps,
200 &[(0, cfg.t_batch * cfg.t_seq_pad)],
201 )?;
202 Ok(Clip { cfg, manifest, store, tokenizer, preprocess, timing: Timing::default(), npu, vision, text })
203 }
204
205 pub fn contexts(&self) -> usize {
207 self.npu.contexts
208 }
209
210 pub fn preprocess(&self, rgb: &[u8], w: usize, h: usize) -> Vec<f32> {
212 self.preprocess.run(rgb, w, h)
213 }
214
215 pub fn encode_images(&mut self, pixels: &[f32]) -> Result<Vec<f32>, Error> {
218 let c = self.cfg.clone();
219 let (s, ps, g, d, t, b) = (c.image_size, c.patch, c.grid(), c.v_dim, c.v_tokens, c.v_batch);
220 let st = c.v_stride;
221 let img = 3 * s * s;
222 if pixels.is_empty() || pixels.len() % img != 0 {
223 return Err(Error::Input(format!("pixels must be [n, 3, {s}, {s}]")));
224 }
225 let n = pixels.len() / img;
226 let (cls, pos) = (self.store.f32("v.cls")?, self.store.f32("v.pos")?);
227 let (pre_w, pre_b) = (self.store.f32("v.ln_pre.w")?, self.store.f32("v.ln_pre.b")?);
228 let mut out = Vec::with_capacity(n * c.embed_dim);
229 for i0 in (0..n).step_by(b) {
230 let ng = (n - i0).min(b);
231 let t0 = Instant::now();
232 let kp = 3 * ps * ps;
234 let mut patches = vec![0f32; ng * g * g * kp];
235 par_rows(&mut patches, kp, |r0, piece| {
236 for (ri, row) in piece.chunks_mut(kp).enumerate() {
237 let r = r0 + ri;
238 let (im, p) = (i0 + r / (g * g), r % (g * g));
239 let (py, px) = (p / g, p % g);
240 let src = &pixels[im * img..][..img];
241 for ch in 0..3 {
242 for ky in 0..ps {
243 let base = ch * s * s + (py * ps + ky) * s + px * ps;
244 row[(ch * ps + ky) * ps..][..ps].copy_from_slice(&src[base..][..ps]);
245 }
246 }
247 }
248 });
249 let wp = self.store.f32("v.patch_w")?;
253 let mut emb = vec![0f32; ng * g * g * d];
254 par_rows(&mut emb, d, |r0, piece| {
255 for (ri, row) in piece.chunks_mut(d).enumerate() {
256 let x = &patches[(r0 + ri) * kp..][..kp];
257 for (j, o) in row.iter_mut().enumerate() {
258 *o = dot(&wp[j * kp..][..kp], x);
259 }
260 }
261 });
262 self.timing.add("v_patches", t0.elapsed());
263 let t1 = Instant::now();
264 let mut x = vec![0f32; self.vision.rows * d];
267 par_rows(&mut x[..ng * st * d], d, |r0, piece| {
268 let mut tmp = vec![0f32; d];
269 for (ri, row) in piece.chunks_mut(d).enumerate() {
270 let r = r0 + ri;
271 let (im, tok) = (r / st, r % st);
272 if tok >= t {
273 continue;
274 }
275 for j in 0..d {
276 let e = if tok == 0 { cls[j] } else { emb[(im * g * g + tok - 1) * d + j] };
277 tmp[j] = e + pos[tok * d + j];
278 }
279 ln_row(&tmp, row, pre_w, pre_b, c.v_eps);
280 }
281 });
282 self.timing.add("v_embed_host", t1.elapsed());
283 let y = self.vision.run(&self.npu, &x, &mut self.timing)?;
284 let t2 = Instant::now();
285 let (post_w, post_b) = (self.store.f32("v.ln_post.w")?, self.store.f32("v.ln_post.b")?);
286 for im in 0..ng {
287 let mut pooled = vec![0f32; d];
288 ln_row(&y[im * st * d..][..d], &mut pooled, post_w, post_b, c.v_eps);
289 out.extend(project(&pooled, self.store.f32("v.proj")?, c.embed_dim));
290 }
291 self.timing.add("v_head", t2.elapsed());
292 }
293 Ok(out)
294 }
295
296 pub fn tokenize(&self, text: &str) -> Vec<u32> {
298 self.tokenizer.encode(text).0
299 }
300
301 pub fn encode_texts(&mut self, texts: &[&str]) -> Result<Vec<f32>, Error> {
303 let t0 = Instant::now();
304 let ids: Vec<u32> = texts.iter().flat_map(|t| self.tokenize(t)).collect();
305 self.timing.add("t_tokenize", t0.elapsed());
306 self.encode_token_ids(&ids)
307 }
308
309 pub fn encode_token_ids(&mut self, ids: &[u32]) -> Result<Vec<f32>, Error> {
312 let c = self.cfg.clone();
313 let (l, d, sp, b) = (c.context, c.t_dim, c.t_seq_pad, c.t_batch);
314 if ids.is_empty() || ids.len() % l != 0 {
315 return Err(Error::Input(format!("token ids must be [n, {l}]")));
316 }
317 let n = ids.len() / l;
318 let vocab = self.store.shape("t.tok_emb")?[0];
319 if let Some(&bad) = ids.iter().find(|&&i| i as usize >= vocab) {
320 return Err(Error::Input(format!("token id {bad} >= vocabulary size {vocab}")));
321 }
322 let eos = self.tokenizer.eos;
323 let mut out = Vec::with_capacity(n * c.embed_dim);
324 for p0 in (0..n).step_by(b) {
325 let ng = (n - p0).min(b);
326 let t0 = Instant::now();
327 let (tok, pos) = (self.store.f32("t.tok_emb")?, self.store.f32("t.pos")?);
328 let mut x = vec![0f32; self.text.rows * d];
330 par_rows(&mut x[..ng * sp * d], d, |r0, piece| {
331 for (ri, row) in piece.chunks_mut(d).enumerate() {
332 let (p, s) = ((r0 + ri) / sp, (r0 + ri) % sp);
333 if s < l {
334 let id = ids[(p0 + p) * l + s] as usize;
335 for j in 0..d {
336 row[j] = tok[id * d + j] + pos[s * d + j];
337 }
338 }
339 }
340 });
341 self.timing.add("t_embed_host", t0.elapsed());
342 let y = self.text.run(&self.npu, &x, &mut self.timing)?;
343 let t1 = Instant::now();
344 let (fw, fb) = (self.store.f32("t.ln_final.w")?, self.store.f32("t.ln_final.b")?);
345 for p in 0..ng {
346 let row = &ids[(p0 + p) * l..][..l];
347 let e = if c.eos_argmax {
350 let m = *row.iter().max().unwrap();
351 row.iter().position(|&i| i == m).unwrap()
352 } else {
353 row.iter().position(|&i| i == eos).unwrap_or(0)
354 };
355 let mut pooled = vec![0f32; d];
356 ln_row(&y[(p * sp + e) * d..][..d], &mut pooled, fw, fb, c.t_eps);
357 out.extend(project(&pooled, self.store.f32("t.proj")?, c.embed_dim));
358 }
359 self.timing.add("t_head", t1.elapsed());
360 }
361 Ok(out)
362 }
363
364 pub fn prompt(&self, label: &str) -> String {
366 self.cfg.prompt_template.replace("{}", label)
367 }
368}
369
370fn project(x: &[f32], w: &[f32], out: usize) -> Vec<f32> {
372 let n = x.len();
373 (0..out).map(|o| dot(&w[o * n..][..n], x)).collect()
374}
375
376pub fn normalize(x: &[f32], dim: usize) -> Vec<f32> {
378 x.chunks(dim)
379 .flat_map(|r| {
380 let s = 1.0 / dot(r, r).sqrt().max(1e-12);
381 r.iter().map(move |v| v * s)
382 })
383 .collect()
384}
385
386pub fn logits(images: &[f32], texts: &[f32], dim: usize, scale: f32) -> Vec<f32> {
388 let (a, b) = (normalize(images, dim), normalize(texts, dim));
389 a.chunks(dim).flat_map(|i| b.chunks(dim).map(move |t| scale * dot(i, t))).collect()
390}
391
392pub fn softmax(x: &[f32], n: usize) -> Vec<f32> {
394 x.chunks(n)
395 .flat_map(|r| {
396 let m = r.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
397 let e: Vec<f32> = r.iter().map(|v| (v - m).exp()).collect();
398 let s: f32 = e.iter().sum();
399 e.into_iter().map(move |v| v / s)
400 })
401 .collect()
402}