1use std::ops::ControlFlow;
4use std::path::Path;
5
6use rand::rngs::StdRng;
7use rand::{Rng, SeedableRng};
8use serde::Deserialize;
9
10#[cfg(feature = "train")]
11use crate::autograd::{TVar, Tape};
12use crate::device::Device;
13use crate::error::{ForgeError, Result};
14use crate::nn::{Embedding, LayerNorm, Linear};
15use crate::ops::{self, MatmulSpec};
16use crate::tensor::Tensor;
17use crate::tokenizer::Tokenizer;
18
19fn default_eps() -> f32 {
20 1e-5
21}
22
23#[derive(Debug, Clone)]
24pub struct Gpt2Config {
25 pub n_layer: usize,
26 pub n_head: usize,
27 pub n_embd: usize,
28 pub n_ctx: usize,
29 pub vocab_size: usize,
30 pub layer_norm_epsilon: f32,
31 pub eos_token_id: Option<u32>,
32}
33
34#[derive(Deserialize)]
36struct RawConfig {
37 n_layer: usize,
38 n_head: usize,
39 n_embd: usize,
40 #[serde(default)]
41 n_ctx: Option<usize>,
42 #[serde(default)]
43 n_positions: Option<usize>,
44 vocab_size: usize,
45 #[serde(default = "default_eps")]
46 layer_norm_epsilon: f32,
47 #[serde(default)]
48 eos_token_id: Option<u32>,
49}
50
51impl Gpt2Config {
52 pub fn gpt2() -> Self {
54 Gpt2Config {
55 n_layer: 12,
56 n_head: 12,
57 n_embd: 768,
58 n_ctx: 1024,
59 vocab_size: 50257,
60 layer_norm_epsilon: 1e-5,
61 eos_token_id: Some(50256),
62 }
63 }
64
65 pub fn from_json(path: impl AsRef<Path>) -> Result<Self> {
66 let text = std::fs::read_to_string(path)?;
67 Self::from_json_str(&text)
68 }
69
70 pub fn from_json_str(text: &str) -> Result<Self> {
73 let raw: RawConfig = serde_json::from_str(text)?;
74 let n_ctx = raw.n_ctx.or(raw.n_positions).ok_or_else(|| {
75 ForgeError::Json(serde::de::Error::custom("missing n_ctx/n_positions"))
76 })?;
77 Ok(Gpt2Config {
78 n_layer: raw.n_layer,
79 n_head: raw.n_head,
80 n_embd: raw.n_embd,
81 n_ctx,
82 vocab_size: raw.vocab_size,
83 layer_norm_epsilon: raw.layer_norm_epsilon,
84 eos_token_id: raw.eos_token_id,
85 })
86 }
87}
88
89struct Block {
90 ln_1: LayerNorm,
91 attn_qkv: Linear,
92 attn_proj: Linear,
93 ln_2: LayerNorm,
94 mlp_fc: Linear,
95 mlp_proj: Linear,
96}
97
98pub struct Gpt2 {
99 emb: Embedding,
100 blocks: Vec<Block>,
101 ln_f: LayerNorm,
102 pub config: Gpt2Config,
103}
104
105#[derive(Debug, Clone, Copy)]
106pub enum Sampling {
107 Greedy,
108 TopK {
109 k: usize,
110 temperature: f32,
111 seed: u64,
112 },
113}
114
115#[derive(Debug, Clone)]
123pub struct AttnStep {
124 pub layer: usize,
125 pub n_head: usize,
126 pub q_len: usize,
128 pub kv_len: usize,
130 pub probs: Vec<f32>,
131}
132
133#[derive(Debug, Clone)]
141pub struct LayerDetail {
142 pub layer: usize,
143 pub ln1_out: Vec<f32>,
145 pub q: Vec<f32>,
147 pub k: Vec<f32>,
148 pub v: Vec<f32>,
149 pub scores: Vec<f32>,
153 pub attn_head_out: Vec<f32>,
157 pub attn_proj_out: Vec<f32>,
159 pub resid_attn: Vec<f32>,
161 pub ln2_out: Vec<f32>,
163 pub mlp_hidden: Vec<f32>,
165 pub block_out: Vec<f32>,
167}
168
169#[derive(Debug, Clone)]
173pub struct StepTrace {
174 pub q_len: usize,
176 pub kv_len: usize,
178 pub n_head: usize,
179 pub n_embd: usize,
180 pub embedding: Vec<f32>,
183 pub attn: Vec<AttnStep>,
185 pub detail: Vec<LayerDetail>,
187 pub ln_f_out: Vec<f32>,
190 pub top: Vec<(u32, f32)>,
193}
194
195#[derive(Debug, Clone, Copy)]
198enum ProbeKind {
199 Embedding,
200 Ln1Out { layer: usize },
201 Query { layer: usize },
202 Key { layer: usize },
203 Value { layer: usize },
204 Scores { layer: usize },
205 Attention { layer: usize },
206 AttnHeadOut { layer: usize },
207 AttnProjOut { layer: usize },
208 ResidAttn { layer: usize },
209 Ln2Out { layer: usize },
210 MlpHidden { layer: usize },
211 BlockOut { layer: usize },
212 LnFOut,
213}
214
215struct Probe {
222 detail_layers: usize,
223 kinds: Vec<ProbeKind>,
224 tensors: Vec<Tensor>,
225}
226
227impl Probe {
228 fn new(detail_layers: usize) -> Self {
229 Probe {
230 detail_layers,
231 kinds: Vec::new(),
232 tensors: Vec::new(),
233 }
234 }
235
236 fn detail(&self, layer: usize) -> bool {
237 layer < self.detail_layers
238 }
239
240 fn push(&mut self, kind: ProbeKind, t: &Tensor) {
241 self.kinds.push(kind);
242 self.tensors.push(t.clone());
243 }
244}
245
246pub struct KvCache {
249 k: Vec<Tensor>,
250 v: Vec<Tensor>,
251 len: usize,
252}
253
254impl KvCache {
255 pub fn len(&self) -> usize {
256 self.len
257 }
258
259 pub fn is_empty(&self) -> bool {
260 self.len == 0
261 }
262}
263
264#[derive(Clone, Debug)]
268pub struct Surprisal {
269 pub bits: Vec<f32>,
271 pub top: Vec<u32>,
273 pub top_p: Vec<f32>,
275}
276
277impl Gpt2 {
278 pub fn from_safetensors(
282 path: impl AsRef<Path>,
283 config: Gpt2Config,
284 device: &Device,
285 ) -> Result<Self> {
286 let bytes = std::fs::read(path.as_ref())?;
287 Self::from_safetensors_bytes(&bytes, config, device)
288 }
289
290 pub fn from_safetensors_bytes(
295 bytes: &[u8],
296 config: Gpt2Config,
297 device: &Device,
298 ) -> Result<Self> {
299 let st = safetensors::SafeTensors::deserialize(bytes)
300 .map_err(|e| ForgeError::SafeTensors(format!("deserialize: {e}")))?;
301 let view = |name: &str| -> Result<safetensors::tensor::TensorView<'_>> {
302 st.tensor(name)
303 .or_else(|_| st.tensor(&format!("transformer.{name}")))
304 .map_err(|_| ForgeError::SafeTensors(format!("missing tensor {name}")))
305 };
306 let host = |name: &str| -> Result<(Vec<usize>, Vec<f32>)> {
307 let v = view(name)?;
308 if v.dtype() != safetensors::tensor::Dtype::F32 {
309 return Err(ForgeError::SafeTensors(format!(
310 "{name}: expected f32, got {:?}",
311 v.dtype()
312 )));
313 }
314 Ok((v.shape().to_vec(), bytemuck::pod_collect_to_vec(v.data())))
315 };
316 let take = |name: &str| -> Result<Tensor> {
317 let (shape, data) = host(name)?;
318 Tensor::from_f32(&data, shape, device)
319 };
320 let eps = config.layer_norm_epsilon;
321 let (_, wte_host) = host("wte.weight")?;
324 let emb = Embedding::from_host_wte(
325 &wte_host,
326 config.vocab_size,
327 config.n_embd,
328 take("wpe.weight")?,
329 device,
330 )?;
331 drop(wte_host);
332 let mut blocks = Vec::with_capacity(config.n_layer);
333 for i in 0..config.n_layer {
334 blocks.push(Block {
335 ln_1: LayerNorm {
336 gamma: take(&format!("h.{i}.ln_1.weight"))?,
337 beta: take(&format!("h.{i}.ln_1.bias"))?,
338 eps,
339 },
340 attn_qkv: Linear {
341 w: take(&format!("h.{i}.attn.c_attn.weight"))?,
342 b: Some(take(&format!("h.{i}.attn.c_attn.bias"))?),
343 },
344 attn_proj: Linear {
345 w: take(&format!("h.{i}.attn.c_proj.weight"))?,
346 b: Some(take(&format!("h.{i}.attn.c_proj.bias"))?),
347 },
348 ln_2: LayerNorm {
349 gamma: take(&format!("h.{i}.ln_2.weight"))?,
350 beta: take(&format!("h.{i}.ln_2.bias"))?,
351 eps,
352 },
353 mlp_fc: Linear {
354 w: take(&format!("h.{i}.mlp.c_fc.weight"))?,
355 b: Some(take(&format!("h.{i}.mlp.c_fc.bias"))?),
356 },
357 mlp_proj: Linear {
358 w: take(&format!("h.{i}.mlp.c_proj.weight"))?,
359 b: Some(take(&format!("h.{i}.mlp.c_proj.bias"))?),
360 },
361 });
362 }
363 let ln_f = LayerNorm {
364 gamma: take("ln_f.weight")?,
365 beta: take("ln_f.bias")?,
366 eps,
367 };
368 Ok(Gpt2 {
369 emb,
370 blocks,
371 ln_f,
372 config,
373 })
374 }
375
376 fn attention(&self, block: &Block, h: &Tensor) -> Result<Tensor> {
377 let n_head = self.config.n_head;
378 let hd = self.config.n_embd / n_head;
379 let qkv = block.attn_qkv.forward(h)?; let (q, k, v) = ops::split_heads(&qkv, n_head)?; let att = ops::matmul(
382 &q,
383 &k,
384 None,
385 MatmulSpec {
386 trans_b: true,
387 alpha: 1.0 / (hd as f32).sqrt(),
388 ..Default::default()
389 },
390 )?; let att = ops::softmax(&att, true, 0)?;
392 let y = ops::matmul(&att, &v, None, MatmulSpec::default())?; let y = ops::merge_heads(&y)?; block.attn_proj.forward(&y)
395 }
396
397 #[allow(clippy::too_many_arguments)] fn attention_cached(
407 &self,
408 layer: usize,
409 block: &Block,
410 h: &Tensor,
411 k_cache: &mut Tensor,
412 v_cache: &mut Tensor,
413 len: usize,
414 mut probe: Option<&mut Probe>,
415 ) -> Result<Tensor> {
416 let n_head = self.config.n_head;
417 let hd = self.config.n_embd / n_head;
418 let qkv = block.attn_qkv.forward(h)?; let (q, k, v) = ops::split_heads(&qkv, n_head)?; if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
421 p.push(ProbeKind::Query { layer }, &q);
422 p.push(ProbeKind::Key { layer }, &k);
423 p.push(ProbeKind::Value { layer }, &v);
424 }
425 let t = q.shape().dim(1);
426 ops::kv_append(k_cache, &k, len)?;
427 ops::kv_append(v_cache, &v, len)?;
428 let kv_len = len + t;
429 let scores = ops::matmul(
430 &q,
431 k_cache,
432 None,
433 MatmulSpec {
434 trans_b: true,
435 alpha: 1.0 / (hd as f32).sqrt(),
436 b_rows: Some(kv_len),
437 ..Default::default()
438 },
439 )?; if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
444 p.push(ProbeKind::Scores { layer }, &scores);
445 }
446 let att = ops::softmax(&scores, true, len)?; if let Some(p) = probe.as_deref_mut() {
448 p.push(ProbeKind::Attention { layer }, &att);
449 }
450 let y = ops::matmul(
451 &att,
452 v_cache,
453 None,
454 MatmulSpec {
455 b_rows: Some(kv_len),
456 ..Default::default()
457 },
458 )?; let y = ops::merge_heads(&y)?;
460 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
464 p.push(ProbeKind::AttnHeadOut { layer }, &y);
465 }
466 let proj = block.attn_proj.forward(&y)?;
467 if let Some(p) = probe.filter(|p| p.detail(layer)) {
468 p.push(ProbeKind::AttnProjOut { layer }, &proj);
469 }
470 Ok(proj)
471 }
472
473 fn hidden(&self, ids: &[u32]) -> Result<Tensor> {
475 if ids.is_empty() {
476 return Err(ForgeError::Shape("empty token sequence".into()));
477 }
478 if ids.len() > self.config.n_ctx {
479 return Err(ForgeError::Shape(format!(
480 "sequence length {} exceeds n_ctx {}",
481 ids.len(),
482 self.config.n_ctx
483 )));
484 }
485 let device = self.emb.wpe.device();
486 let _scope = device.dispatch_scope();
488 let ids_t = Tensor::from_u32(ids, [ids.len()], &device)?;
489 let mut x = self.emb.forward(&ids_t, 0)?;
490 for block in &self.blocks {
491 let attn_out = self.attention(block, &block.ln_1.forward(&x)?)?;
492 x = ops::add(&x, &attn_out)?;
493 let mlp_in = block.ln_2.forward(&x)?;
494 let mlp_out = block
495 .mlp_proj
496 .forward(&ops::gelu(&block.mlp_fc.forward(&mlp_in)?)?)?;
497 x = ops::add(&x, &mlp_out)?;
498 }
499 self.ln_f.forward(&x)
500 }
501
502 fn hidden_cached(
507 &self,
508 ids: &[u32],
509 cache: &mut KvCache,
510 mut probe: Option<&mut Probe>,
511 ) -> Result<Tensor> {
512 if ids.is_empty() {
513 return Err(ForgeError::Shape("empty token sequence".into()));
514 }
515 let pos = cache.len;
516 if pos + ids.len() > self.config.n_ctx {
517 return Err(ForgeError::Shape(format!(
518 "sequence length {}+{} exceeds n_ctx {}",
519 pos,
520 ids.len(),
521 self.config.n_ctx
522 )));
523 }
524 let device = self.emb.wpe.device();
525 let _scope = device.dispatch_scope();
529 let ids_t = Tensor::from_u32(ids, [ids.len()], &device)?;
530 let mut x = self.emb.forward(&ids_t, pos)?;
531 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail_layers > 0) {
534 p.push(ProbeKind::Embedding, &x);
535 }
536 for (layer, (block, (kc, vc))) in self
537 .blocks
538 .iter()
539 .zip(cache.k.iter_mut().zip(cache.v.iter_mut()))
540 .enumerate()
541 {
542 let ln1 = block.ln_1.forward(&x)?;
545 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
546 p.push(ProbeKind::Ln1Out { layer }, &ln1);
547 }
548 let attn_out =
549 self.attention_cached(layer, block, &ln1, kc, vc, pos, probe.as_deref_mut())?;
550 x = ops::add(&x, &attn_out)?;
551 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
552 p.push(ProbeKind::ResidAttn { layer }, &x);
553 }
554 let mlp_in = block.ln_2.forward(&x)?;
555 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
556 p.push(ProbeKind::Ln2Out { layer }, &mlp_in);
557 }
558 let mlp_hidden = ops::gelu(&block.mlp_fc.forward(&mlp_in)?)?;
559 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
560 p.push(ProbeKind::MlpHidden { layer }, &mlp_hidden);
561 }
562 let mlp_out = block.mlp_proj.forward(&mlp_hidden)?;
563 x = ops::add(&x, &mlp_out)?;
564 if let Some(p) = probe.as_deref_mut().filter(|p| p.detail(layer)) {
565 p.push(ProbeKind::BlockOut { layer }, &x);
566 }
567 }
568 cache.len += ids.len();
569 let out = self.ln_f.forward(&x)?;
570 if let Some(p) = probe.filter(|p| p.detail_layers > 0) {
571 p.push(ProbeKind::LnFOut, &out);
572 }
573 Ok(out)
574 }
575
576 pub fn new_cache(&self) -> Result<KvCache> {
578 let device = self.emb.wpe.device();
579 let hd = self.config.n_embd / self.config.n_head;
580 let shape = [self.config.n_head, self.config.n_ctx, hd];
581 let mut k = Vec::with_capacity(self.config.n_layer);
582 let mut v = Vec::with_capacity(self.config.n_layer);
583 for _ in 0..self.config.n_layer {
584 k.push(Tensor::zeros(shape, &device)?);
585 v.push(Tensor::zeros(shape, &device)?);
586 }
587 Ok(KvCache { k, v, len: 0 })
588 }
589
590 pub fn forward(&self, ids: &[u32]) -> Result<Tensor> {
593 let _scope = self.emb.wpe.device().dispatch_scope();
596 let h = self.hidden(ids)?;
597 ops::matmul_chunked_transb(&h, &self.emb.wte_chunks, 1.0)
598 }
599
600 pub fn logits_last(&self, ids: &[u32]) -> Result<Vec<f32>> {
603 let _scope = self.emb.wpe.device().dispatch_scope();
606 let h = self.hidden(ids)?;
607 let last = h.narrow_rows(ids.len() - 1, 1)?;
608 ops::matmul_chunked_transb(&last, &self.emb.wte_chunks, 1.0)?.to_vec_f32()
609 }
610
611 pub fn logits_step(&self, ids: &[u32], cache: &mut KvCache) -> Result<Vec<f32>> {
614 let _scope = self.emb.wpe.device().dispatch_scope();
617 let h = self.hidden_cached(ids, cache, None)?;
618 let last = h.narrow_rows(ids.len() - 1, 1)?;
619 ops::matmul_chunked_transb(&last, &self.emb.wte_chunks, 1.0)?.to_vec_f32()
620 }
621
622 pub async fn logits_step_async(&self, ids: &[u32], cache: &mut KvCache) -> Result<Vec<f32>> {
625 let _scope = self.emb.wpe.device().dispatch_scope();
628 let h = self.hidden_cached(ids, cache, None)?;
629 let last = h.narrow_rows(ids.len() - 1, 1)?;
630 ops::matmul_chunked_transb(&last, &self.emb.wte_chunks, 1.0)?
631 .to_vec_f32_async()
632 .await
633 }
634
635 pub fn hidden_step(&self, ids: &[u32], cache: &mut KvCache) -> Result<Vec<f32>> {
643 let h = self.hidden_cached(ids, cache, None)?;
644 h.narrow_rows(ids.len() - 1, 1)?.to_vec_f32()
645 }
646
647 pub async fn hidden_step_async(&self, ids: &[u32], cache: &mut KvCache) -> Result<Vec<f32>> {
650 let h = self.hidden_cached(ids, cache, None)?;
651 h.narrow_rows(ids.len() - 1, 1)?.to_vec_f32_async().await
652 }
653
654 pub fn logits_from_hidden(&self, h: &[f32]) -> Result<Vec<f32>> {
659 ops::matmul_chunked_transb(&self.hidden_tensor(h)?, &self.emb.wte_chunks, 1.0)?.to_vec_f32()
660 }
661
662 pub async fn logits_from_hidden_async(&self, h: &[f32]) -> Result<Vec<f32>> {
664 ops::matmul_chunked_transb(&self.hidden_tensor(h)?, &self.emb.wte_chunks, 1.0)?
665 .to_vec_f32_async()
666 .await
667 }
668
669 pub fn wte_host(&self) -> Result<Vec<f32>> {
673 let mut out = Vec::with_capacity(self.config.vocab_size * self.config.n_embd);
674 for chunk in &self.emb.wte_chunks {
675 out.extend(chunk.to_vec_f32()?);
676 }
677 Ok(out)
678 }
679
680 fn hidden_tensor(&self, h: &[f32]) -> Result<Tensor> {
681 if h.len() != self.config.n_embd {
682 return Err(ForgeError::Shape(format!(
683 "hidden state has {} elements, expected n_embd {}",
684 h.len(),
685 self.config.n_embd
686 )));
687 }
688 Tensor::from_f32(h, [1, self.config.n_embd], &self.emb.wpe.device())
689 }
690
691 pub async fn logits_step_attn_async(
698 &self,
699 ids: &[u32],
700 cache: &mut KvCache,
701 ) -> Result<(Vec<f32>, Vec<AttnStep>)> {
702 let (logits, trace) = self.logits_step_trace_async(ids, cache, 0, 0).await?;
703 Ok((logits, trace.attn))
704 }
705
706 pub async fn logits_step_trace_async(
724 &self,
725 ids: &[u32],
726 cache: &mut KvCache,
727 detail_layers: usize,
728 top_n: usize,
729 ) -> Result<(Vec<f32>, StepTrace)> {
730 let q_len = ids.len();
731 let detail_layers = detail_layers.min(self.config.n_layer);
732 let mut probe = Probe::new(detail_layers);
733 let h = self.hidden_cached(ids, cache, Some(&mut probe))?;
734 let last = h.narrow_rows(q_len - 1, 1)?;
735 let Probe {
736 kinds,
737 mut tensors,
738 detail_layers,
739 } = probe;
740 let shapes: Vec<Vec<usize>> = tensors.iter().map(|t| t.shape().dims().to_vec()).collect();
743 tensors.push(ops::matmul_chunked_transb(
744 &last,
745 &self.emb.wte_chunks,
746 1.0,
747 )?);
748
749 let mut read = Tensor::to_vec_f32_batch(&tensors).await?;
750 let logits = read.pop().expect("logits were pushed last");
751
752 let mut trace = StepTrace {
753 q_len,
754 kv_len: cache.len,
755 n_head: self.config.n_head,
756 n_embd: self.config.n_embd,
757 embedding: Vec::new(),
758 attn: Vec::with_capacity(self.config.n_layer),
759 detail: (0..detail_layers)
760 .map(|layer| LayerDetail {
761 layer,
762 ln1_out: Vec::new(),
763 q: Vec::new(),
764 k: Vec::new(),
765 v: Vec::new(),
766 scores: Vec::new(),
767 attn_head_out: Vec::new(),
768 attn_proj_out: Vec::new(),
769 resid_attn: Vec::new(),
770 ln2_out: Vec::new(),
771 mlp_hidden: Vec::new(),
772 block_out: Vec::new(),
773 })
774 .collect(),
775 ln_f_out: Vec::new(),
776 top: top_probs(&logits, top_n),
777 };
778 for ((kind, dims), data) in kinds.iter().zip(&shapes).zip(read) {
779 match *kind {
780 ProbeKind::Embedding => trace.embedding = data,
781 ProbeKind::Ln1Out { layer } => trace.detail[layer].ln1_out = data,
782 ProbeKind::Query { layer } => trace.detail[layer].q = data,
783 ProbeKind::Key { layer } => trace.detail[layer].k = data,
784 ProbeKind::Value { layer } => trace.detail[layer].v = data,
785 ProbeKind::Scores { layer } => trace.detail[layer].scores = data,
786 ProbeKind::AttnHeadOut { layer } => trace.detail[layer].attn_head_out = data,
787 ProbeKind::AttnProjOut { layer } => trace.detail[layer].attn_proj_out = data,
788 ProbeKind::ResidAttn { layer } => trace.detail[layer].resid_attn = data,
789 ProbeKind::Ln2Out { layer } => trace.detail[layer].ln2_out = data,
790 ProbeKind::MlpHidden { layer } => trace.detail[layer].mlp_hidden = data,
791 ProbeKind::BlockOut { layer } => trace.detail[layer].block_out = data,
792 ProbeKind::LnFOut => trace.ln_f_out = data,
793 ProbeKind::Attention { layer } => {
794 let [n_head, q_len, kv_len] = dims[..] else {
795 return Err(ForgeError::Shape(format!(
796 "attention probe expected rank 3, got {dims:?}"
797 )));
798 };
799 trace.attn.push(AttnStep {
800 layer,
801 n_head,
802 q_len,
803 kv_len,
804 probs: data,
805 });
806 }
807 }
808 }
809 Ok((logits, trace))
810 }
811
812 pub async fn surprisal_async(&self, ids: &[u32]) -> Result<Surprisal> {
837 let t = ids.len();
838 if t == 0 {
839 return Err(ForgeError::Shape(
840 "surprisal needs a non-empty sequence".into(),
841 ));
842 }
843 let vocab = self.config.vocab_size;
844 let logits = self.forward(ids)?.to_vec_f32_async().await?;
845
846 let mut out = Surprisal {
847 bits: vec![0.0; t],
848 top: vec![ids[0]; t],
849 top_p: vec![0.0; t],
850 };
851 for i in 1..t {
854 let row = &logits[(i - 1) * vocab..i * vocab];
855 let max = row.iter().copied().fold(f32::NEG_INFINITY, f32::max);
858 let sum: f32 = row.iter().map(|l| (l - max).exp()).sum();
859 let log_z = max + sum.ln();
860
861 let target = ids[i] as usize;
862 if target >= vocab {
863 return Err(ForgeError::Shape(format!(
864 "token id {target} >= vocab_size {vocab}"
865 )));
866 }
867 out.bits[i] = -(row[target] - log_z) / std::f32::consts::LN_2;
869
870 let (best, best_logit) = row
871 .iter()
872 .enumerate()
873 .max_by(|a, b| a.1.total_cmp(b.1))
874 .map(|(i, l)| (i as u32, *l))
875 .unwrap_or((0, 0.0));
876 out.top[i] = best;
877 out.top_p[i] = (best_logit - log_z).exp();
878 }
879 Ok(out)
880 }
881
882 pub fn init_random(config: Gpt2Config, device: &Device, seed: u64) -> Result<Gpt2> {
894 let mut rng = StdRng::seed_from_u64(seed);
895 let c = config.n_embd;
896 let std = 0.02f32;
897 let resid_std = std / (2.0 * config.n_layer as f32).sqrt();
898 let mut normal = |n: usize, std: f32| -> Vec<f32> {
899 (0..n)
900 .map(|_| {
901 let u1: f32 = rng.random::<f32>().max(1e-7);
902 let u2: f32 = rng.random::<f32>();
903 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos() * std
904 })
905 .collect()
906 };
907 let wte_host = normal(config.vocab_size * c, std);
908 let wpe = Tensor::from_f32(&normal(config.n_ctx * c, std), [config.n_ctx, c], device)?;
909 let emb = Embedding::from_host_wte(&wte_host, config.vocab_size, c, wpe, device)?;
910 drop(wte_host);
911 let eps = config.layer_norm_epsilon;
912 let ones = |dev: &Device| Tensor::from_f32(&vec![1.0f32; c], [c], dev);
913 let zeros1 = |dev: &Device, n: usize| Tensor::zeros([n], dev);
914 let mut blocks = Vec::with_capacity(config.n_layer);
915 for _ in 0..config.n_layer {
916 blocks.push(Block {
917 ln_1: LayerNorm {
918 gamma: ones(device)?,
919 beta: zeros1(device, c)?,
920 eps,
921 },
922 attn_qkv: Linear {
923 w: Tensor::from_f32(&normal(c * 3 * c, std), [c, 3 * c], device)?,
924 b: Some(zeros1(device, 3 * c)?),
925 },
926 attn_proj: Linear {
927 w: Tensor::from_f32(&normal(c * c, resid_std), [c, c], device)?,
928 b: Some(zeros1(device, c)?),
929 },
930 ln_2: LayerNorm {
931 gamma: ones(device)?,
932 beta: zeros1(device, c)?,
933 eps,
934 },
935 mlp_fc: Linear {
936 w: Tensor::from_f32(&normal(c * 4 * c, std), [c, 4 * c], device)?,
937 b: Some(zeros1(device, 4 * c)?),
938 },
939 mlp_proj: Linear {
940 w: Tensor::from_f32(&normal(4 * c * c, resid_std), [4 * c, c], device)?,
941 b: Some(zeros1(device, c)?),
942 },
943 });
944 }
945 let ln_f = LayerNorm {
946 gamma: ones(device)?,
947 beta: zeros1(device, c)?,
948 eps,
949 };
950 Ok(Gpt2 {
951 emb,
952 blocks,
953 ln_f,
954 config,
955 })
956 }
957
958 pub fn param_specs(&self) -> Vec<(String, bool)> {
961 let mut out = vec![
962 ("wte.weight".to_string(), true),
963 ("wpe.weight".to_string(), true),
964 ];
965 for i in 0..self.config.n_layer {
966 out.push((format!("h.{i}.ln_1.weight"), false));
967 out.push((format!("h.{i}.ln_1.bias"), false));
968 out.push((format!("h.{i}.attn.c_attn.weight"), true));
969 out.push((format!("h.{i}.attn.c_attn.bias"), false));
970 out.push((format!("h.{i}.attn.c_proj.weight"), true));
971 out.push((format!("h.{i}.attn.c_proj.bias"), false));
972 out.push((format!("h.{i}.ln_2.weight"), false));
973 out.push((format!("h.{i}.ln_2.bias"), false));
974 out.push((format!("h.{i}.mlp.c_fc.weight"), true));
975 out.push((format!("h.{i}.mlp.c_fc.bias"), false));
976 out.push((format!("h.{i}.mlp.c_proj.weight"), true));
977 out.push((format!("h.{i}.mlp.c_proj.bias"), false));
978 }
979 out.push(("ln_f.weight".to_string(), false));
980 out.push(("ln_f.bias".to_string(), false));
981 out
982 }
983
984 fn wte_single(&self) -> Result<&Tensor> {
986 if self.emb.wte_chunks.len() != 1 {
987 return Err(ForgeError::Shape(format!(
988 "training requires a single-chunk wte; got {} chunks (device \
989 binding limit too small for vocab x n_embd)",
990 self.emb.wte_chunks.len()
991 )));
992 }
993 Ok(&self.emb.wte_chunks[0])
994 }
995
996 pub fn params(&self) -> Result<Vec<&Tensor>> {
998 let mut out = vec![self.wte_single()?, &self.emb.wpe];
999 for b in &self.blocks {
1000 out.extend([&b.ln_1.gamma, &b.ln_1.beta, &b.attn_qkv.w]);
1001 out.push(bias(&b.attn_qkv)?);
1002 out.push(&b.attn_proj.w);
1003 out.push(bias(&b.attn_proj)?);
1004 out.extend([&b.ln_2.gamma, &b.ln_2.beta, &b.mlp_fc.w]);
1005 out.push(bias(&b.mlp_fc)?);
1006 out.push(&b.mlp_proj.w);
1007 out.push(bias(&b.mlp_proj)?);
1008 }
1009 out.extend([&self.ln_f.gamma, &self.ln_f.beta]);
1010 Ok(out)
1011 }
1012
1013 pub fn params_mut(&mut self) -> Result<Vec<&mut Tensor>> {
1015 if self.emb.wte_chunks.len() != 1 {
1016 return Err(ForgeError::Shape(
1017 "training requires a single-chunk wte".into(),
1018 ));
1019 }
1020 let mut out: Vec<&mut Tensor> = Vec::new();
1021 out.push(&mut self.emb.wte_chunks[0]);
1022 out.push(&mut self.emb.wpe);
1023 for b in &mut self.blocks {
1024 out.push(&mut b.ln_1.gamma);
1025 out.push(&mut b.ln_1.beta);
1026 out.push(&mut b.attn_qkv.w);
1027 out.push(
1028 b.attn_qkv
1029 .b
1030 .as_mut()
1031 .ok_or_else(|| ForgeError::Shape("training requires biases".into()))?,
1032 );
1033 out.push(&mut b.attn_proj.w);
1034 out.push(
1035 b.attn_proj
1036 .b
1037 .as_mut()
1038 .ok_or_else(|| ForgeError::Shape("training requires biases".into()))?,
1039 );
1040 out.push(&mut b.ln_2.gamma);
1041 out.push(&mut b.ln_2.beta);
1042 out.push(&mut b.mlp_fc.w);
1043 out.push(
1044 b.mlp_fc
1045 .b
1046 .as_mut()
1047 .ok_or_else(|| ForgeError::Shape("training requires biases".into()))?,
1048 );
1049 out.push(&mut b.mlp_proj.w);
1050 out.push(
1051 b.mlp_proj
1052 .b
1053 .as_mut()
1054 .ok_or_else(|| ForgeError::Shape("training requires biases".into()))?,
1055 );
1056 }
1057 out.push(&mut self.ln_f.gamma);
1058 out.push(&mut self.ln_f.beta);
1059 Ok(out)
1060 }
1061
1062 pub fn save_safetensors(&self, path: impl AsRef<Path>) -> Result<()> {
1065 let c = self.config.n_embd;
1066 let mut wte_host = Vec::with_capacity(self.config.vocab_size * c);
1067 for ch in &self.emb.wte_chunks {
1068 wte_host.extend(ch.to_vec_f32()?);
1069 }
1070 let mut entries = vec![(
1071 "wte.weight".to_string(),
1072 vec![self.config.vocab_size, c],
1073 wte_host,
1074 )];
1075 let specs = self.param_specs();
1076 let params = self.params_for_save()?;
1077 for ((name, _), t) in specs.iter().zip(params).skip(1) {
1078 entries.push((name.clone(), t.shape().dims().to_vec(), t.to_vec_f32()?));
1079 }
1080 crate::serialization::save_safetensors(path, &entries)
1081 }
1082
1083 fn params_for_save(&self) -> Result<Vec<&Tensor>> {
1086 let mut out = vec![&self.emb.wte_chunks[0], &self.emb.wpe];
1087 for b in &self.blocks {
1088 out.extend([&b.ln_1.gamma, &b.ln_1.beta, &b.attn_qkv.w]);
1089 out.push(bias(&b.attn_qkv)?);
1090 out.push(&b.attn_proj.w);
1091 out.push(bias(&b.attn_proj)?);
1092 out.extend([&b.ln_2.gamma, &b.ln_2.beta, &b.mlp_fc.w]);
1093 out.push(bias(&b.mlp_fc)?);
1094 out.push(&b.mlp_proj.w);
1095 out.push(bias(&b.mlp_proj)?);
1096 }
1097 out.extend([&self.ln_f.gamma, &self.ln_f.beta]);
1098 Ok(out)
1099 }
1100
1101 #[cfg(feature = "train")]
1102 #[cfg_attr(docsrs, doc(cfg(feature = "train")))]
1103 pub fn loss(&self, input: &[u32], targets: &[u32]) -> Result<f32> {
1108 if input.is_empty() || input.len() != targets.len() {
1109 return Err(ForgeError::Shape(
1110 "loss needs equal, non-empty input/target lengths".into(),
1111 ));
1112 }
1113 let t = input.len();
1114 let device = self.emb.wpe.device();
1115 let tgt_t = Tensor::from_u32(targets, [t], &device)?;
1116 let logits = self.forward(input)?;
1117 let probs = ops::softmax(&logits, false, 0)?;
1118 let nll = ops::gather_nll(&probs, &tgt_t)?.to_vec_f32()?;
1119 Ok(nll.iter().sum::<f32>() / t as f32)
1120 }
1121
1122 #[cfg(feature = "train")]
1123 #[cfg_attr(docsrs, doc(cfg(feature = "train")))]
1124 pub fn loss_grads(
1129 &self,
1130 input: &[u32],
1131 targets: &[u32],
1132 dropout_p: f32,
1133 seed: u32,
1134 ) -> Result<(f32, Vec<Tensor>)> {
1135 if input.is_empty() || input.len() != targets.len() {
1136 return Err(ForgeError::Shape(
1137 "loss_grads needs equal, non-empty input/target lengths".into(),
1138 ));
1139 }
1140 if input.len() > self.config.n_ctx {
1141 return Err(ForgeError::Shape(format!(
1142 "sequence length {} exceeds n_ctx {}",
1143 input.len(),
1144 self.config.n_ctx
1145 )));
1146 }
1147 let device = self.emb.wpe.device();
1148 let t = input.len();
1149 let ids_t = Tensor::from_u32(input, [t], &device)?;
1150 let tgt_t = Tensor::from_u32(targets, [t], &device)?;
1151 let eps = self.config.layer_norm_epsilon;
1152 let n_head = self.config.n_head;
1153 let hd = self.config.n_embd / n_head;
1154
1155 let mut tape = Tape::new();
1156 let pvars: Vec<TVar> = self
1158 .params()?
1159 .into_iter()
1160 .map(|p| tape.leaf(p.clone()))
1161 .collect();
1162 let n_params = pvars.len();
1163
1164 let mut site = 0u32;
1165 let mut dseed = move || {
1166 site = site.wrapping_add(1);
1167 seed.wrapping_mul(0x9E37_79B1)
1168 .wrapping_add(site.wrapping_mul(0x85EB_CA77))
1169 };
1170
1171 let mut x = tape.embedding(&ids_t, &pvars[0], &pvars[1], 0)?;
1172 x = tape.dropout(&x, dropout_p, dseed())?;
1173 for i in 0..self.config.n_layer {
1174 let base = 2 + i * 12;
1175 let [g1, b1, wqkv, bqkv, wproj, bproj, g2, b2, wfc, bfc, wmp, bmp] =
1176 std::array::from_fn(|j| &pvars[base + j]);
1177 let a = tape.layernorm(&x, g1, b1, eps)?;
1178 let qkv = tape.matmul(&a, wqkv, Some(bqkv), MatmulSpec::default())?;
1179 let (q, k, v) = tape.split_heads(&qkv, n_head)?;
1180 let att = tape.matmul(
1181 &q,
1182 &k,
1183 None,
1184 MatmulSpec {
1185 trans_b: true,
1186 alpha: 1.0 / (hd as f32).sqrt(),
1187 ..Default::default()
1188 },
1189 )?;
1190 let probs = tape.softmax(&att, true, 0)?;
1191 let probs = tape.dropout(&probs, dropout_p, dseed())?;
1192 let y = tape.matmul(&probs, &v, None, MatmulSpec::default())?;
1193 let y = tape.merge_heads(&y)?;
1194 let y = tape.matmul(&y, wproj, Some(bproj), MatmulSpec::default())?;
1195 let y = tape.dropout(&y, dropout_p, dseed())?;
1196 x = tape.add(&x, &y)?;
1197 let a2 = tape.layernorm(&x, g2, b2, eps)?;
1198 let f = tape.matmul(&a2, wfc, Some(bfc), MatmulSpec::default())?;
1199 let f = tape.gelu(&f)?;
1200 let f = tape.matmul(&f, wmp, Some(bmp), MatmulSpec::default())?;
1201 let f = tape.dropout(&f, dropout_p, dseed())?;
1202 x = tape.add(&x, &f)?;
1203 }
1204 let xf = tape.layernorm(&x, &pvars[n_params - 2], &pvars[n_params - 1], eps)?;
1205 let logits = tape.matmul(
1208 &xf,
1209 &pvars[0],
1210 None,
1211 MatmulSpec {
1212 trans_b: true,
1213 ..Default::default()
1214 },
1215 )?;
1216
1217 let probs = ops::softmax(&logits.t, false, 0)?;
1218 let nll = ops::gather_nll(&probs, &tgt_t)?.to_vec_f32()?;
1219 let loss = nll.iter().sum::<f32>() / t as f32;
1220 let dlogits = ops::ce_bwd(&probs, &tgt_t, 1.0 / t as f32)?;
1221 let all = tape.backward(&logits, dlogits)?;
1222 let mut grads = Vec::with_capacity(n_params);
1223 for (i, g) in all.into_iter().take(n_params).enumerate() {
1224 grads.push(
1225 g.ok_or_else(|| ForgeError::Shape(format!("parameter {i} received no gradient")))?,
1226 );
1227 }
1228 Ok((loss, grads))
1229 }
1230
1231 pub fn generate(
1234 &self,
1235 tokenizer: &impl Tokenizer,
1236 prompt: &str,
1237 max_new_tokens: usize,
1238 sampling: Sampling,
1239 ) -> Result<String> {
1240 self.generate_streaming(tokenizer, prompt, max_new_tokens, sampling, |_, _| {})
1241 }
1242
1243 pub fn generate_streaming(
1252 &self,
1253 tokenizer: &impl Tokenizer,
1254 prompt: &str,
1255 max_new_tokens: usize,
1256 sampling: Sampling,
1257 mut on_token: impl FnMut(u32, &str),
1258 ) -> Result<String> {
1259 self.generate_streaming_ctl(tokenizer, prompt, max_new_tokens, sampling, |id, text| {
1260 on_token(id, text);
1261 ControlFlow::Continue(())
1262 })
1263 }
1264
1265 pub fn generate_streaming_ctl(
1270 &self,
1271 tokenizer: &impl Tokenizer,
1272 prompt: &str,
1273 max_new_tokens: usize,
1274 sampling: Sampling,
1275 mut on_token: impl FnMut(u32, &str) -> ControlFlow<()>,
1276 ) -> Result<String> {
1277 let mut ids = tokenizer.encode(prompt)?;
1278 if ids.is_empty() {
1279 return Err(ForgeError::Tokenizer("prompt produced no tokens".into()));
1280 }
1281 let mut rng = match sampling {
1282 Sampling::TopK { seed, .. } => Some(StdRng::seed_from_u64(seed)),
1283 Sampling::Greedy => None,
1284 };
1285 let mut cache = self.new_cache()?;
1286 let mut logits = self.logits_step(&ids, &mut cache)?; let mut bytes = tokenizer.decode_bytes(&ids);
1290 let mut sent = emit_valid_prefix(&bytes, 0, &mut |_: &str| {});
1291 for _ in 0..max_new_tokens {
1292 let next = sample(&logits, sampling, rng.as_mut());
1293 if Some(next) == self.config.eos_token_id {
1294 break;
1295 }
1296 ids.push(next);
1297 bytes.extend(tokenizer.decode_bytes(&[next]));
1298 let mut delta = String::new();
1299 sent = emit_valid_prefix(&bytes, sent, &mut |s: &str| delta.push_str(s));
1300 if on_token(next, &delta).is_break() {
1301 break;
1302 }
1303 if ids.len() >= self.config.n_ctx {
1304 break;
1305 }
1306 logits = self.logits_step(&[next], &mut cache)?; }
1308 Ok(tokenizer.decode(&ids))
1309 }
1310
1311 pub async fn generate_async(
1316 &self,
1317 tokenizer: &impl Tokenizer,
1318 prompt: &str,
1319 max_new_tokens: usize,
1320 sampling: Sampling,
1321 mut on_text: impl FnMut(&str),
1322 ) -> Result<String> {
1323 self.generate_async_ctl(tokenizer, prompt, max_new_tokens, sampling, |s| {
1324 on_text(s);
1325 ControlFlow::Continue(())
1326 })
1327 .await
1328 }
1329
1330 pub async fn generate_async_ctl(
1335 &self,
1336 tokenizer: &impl Tokenizer,
1337 prompt: &str,
1338 max_new_tokens: usize,
1339 sampling: Sampling,
1340 on_text: impl FnMut(&str) -> ControlFlow<()>,
1341 ) -> Result<String> {
1342 self.generate_async_probe(
1345 tokenizer,
1346 prompt,
1347 max_new_tokens,
1348 sampling,
1349 on_text,
1350 None::<fn(&[AttnStep])>,
1351 )
1352 .await
1353 }
1354
1355 pub async fn generate_async_probe(
1364 &self,
1365 tokenizer: &impl Tokenizer,
1366 prompt: &str,
1367 max_new_tokens: usize,
1368 sampling: Sampling,
1369 on_text: impl FnMut(&str) -> ControlFlow<()>,
1370 on_attn: Option<impl FnMut(&[AttnStep])>,
1371 ) -> Result<String> {
1372 self.generate_async_trace(
1375 tokenizer,
1376 prompt,
1377 max_new_tokens,
1378 sampling,
1379 on_text,
1380 on_attn.map(|mut f| move |t: &StepTrace| f(&t.attn)),
1381 0,
1382 0,
1383 )
1384 .await
1385 }
1386
1387 #[allow(clippy::too_many_arguments)] pub async fn generate_async_trace(
1398 &self,
1399 tokenizer: &impl Tokenizer,
1400 prompt: &str,
1401 max_new_tokens: usize,
1402 sampling: Sampling,
1403 mut on_text: impl FnMut(&str) -> ControlFlow<()>,
1404 mut on_trace: Option<impl FnMut(&StepTrace)>,
1405 detail_layers: usize,
1406 top_n: usize,
1407 ) -> Result<String> {
1408 let stop = std::cell::Cell::new(false);
1412 let mut on_text = |s: &str| {
1413 if on_text(s).is_break() {
1414 stop.set(true);
1415 }
1416 };
1417 let mut ids = tokenizer.encode(prompt)?;
1418 if ids.is_empty() {
1419 return Err(ForgeError::Tokenizer("prompt produced no tokens".into()));
1420 }
1421 let mut rng = match sampling {
1422 Sampling::TopK { seed, .. } => Some(StdRng::seed_from_u64(seed)),
1423 Sampling::Greedy => None,
1424 };
1425 let mut cache = self.new_cache()?;
1426 let mut logits = self
1427 .step_async(&ids, &mut cache, &mut on_trace, detail_layers, top_n)
1428 .await?; let mut bytes = tokenizer.decode_bytes(&ids);
1433 let mut sent = emit_valid_prefix(&bytes, 0, &mut on_text);
1434 for _ in 0..max_new_tokens {
1435 let next = sample(&logits, sampling, rng.as_mut());
1436 if Some(next) == self.config.eos_token_id {
1437 break;
1438 }
1439 ids.push(next);
1440 bytes.extend(tokenizer.decode_bytes(&[next]));
1441 sent = emit_valid_prefix(&bytes, sent, &mut on_text);
1442 if stop.get() {
1443 break;
1444 }
1445 if ids.len() >= self.config.n_ctx {
1446 break;
1447 }
1448 logits = self
1449 .step_async(&[next], &mut cache, &mut on_trace, detail_layers, top_n)
1450 .await?; }
1452 Ok(tokenizer.decode(&ids))
1453 }
1454
1455 async fn step_async(
1457 &self,
1458 ids: &[u32],
1459 cache: &mut KvCache,
1460 on_trace: &mut Option<impl FnMut(&StepTrace)>,
1461 detail_layers: usize,
1462 top_n: usize,
1463 ) -> Result<Vec<f32>> {
1464 match on_trace {
1465 Some(f) => {
1466 let (logits, trace) = self
1467 .logits_step_trace_async(ids, cache, detail_layers, top_n)
1468 .await?;
1469 f(&trace);
1470 Ok(logits)
1471 }
1472 None => self.logits_step_async(ids, cache).await,
1473 }
1474 }
1475}
1476
1477fn emit_valid_prefix(bytes: &[u8], mut sent: usize, on_text: &mut impl FnMut(&str)) -> usize {
1482 loop {
1483 match std::str::from_utf8(&bytes[sent..]) {
1484 Ok(s) => {
1485 if !s.is_empty() {
1486 on_text(s);
1487 }
1488 return bytes.len();
1489 }
1490 Err(e) => {
1491 let valid = e.valid_up_to();
1492 if valid > 0 {
1493 on_text(std::str::from_utf8(&bytes[sent..sent + valid]).unwrap());
1494 sent += valid;
1495 }
1496 match e.error_len() {
1497 Some(bad) => {
1499 on_text("\u{FFFD}");
1500 sent += bad;
1501 }
1502 None => return sent,
1504 }
1505 }
1506 }
1507 }
1508}
1509
1510pub fn top_probs(logits: &[f32], n: usize) -> Vec<(u32, f32)> {
1519 let n = n.min(logits.len());
1520 if n == 0 {
1521 return Vec::new();
1522 }
1523 let by_logit = |a: &u32, b: &u32| logits[*b as usize].total_cmp(&logits[*a as usize]);
1524 let mut idx: Vec<u32> = (0..logits.len() as u32).collect();
1525 idx.select_nth_unstable_by(n - 1, by_logit);
1528 idx.truncate(n);
1529 idx.sort_unstable_by(by_logit);
1530
1531 let max = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
1532 let total: f32 = logits.iter().map(|l| (l - max).exp()).sum();
1533 idx.into_iter()
1534 .map(|i| (i, (logits[i as usize] - max).exp() / total))
1535 .collect()
1536}
1537
1538fn bias(l: &Linear) -> Result<&Tensor> {
1539 l.b.as_ref()
1540 .ok_or_else(|| ForgeError::Shape("training requires biases".into()))
1541}
1542
1543pub(crate) fn sample(logits: &[f32], sampling: Sampling, rng: Option<&mut StdRng>) -> u32 {
1544 match sampling {
1545 Sampling::Greedy => argmax(logits),
1546 Sampling::TopK { k, temperature, .. } => {
1547 let mut indexed: Vec<(usize, f32)> = logits.iter().copied().enumerate().collect();
1548 indexed.sort_by(|a, b| b.1.total_cmp(&a.1));
1549 indexed.truncate(k.max(1));
1550 let t = temperature.max(1e-4);
1551 let max = indexed[0].1;
1552 let weights: Vec<f32> = indexed.iter().map(|(_, l)| ((l - max) / t).exp()).collect();
1553 let total: f32 = weights.iter().sum();
1554 let mut r = rng.expect("rng required for TopK").random::<f32>() * total;
1555 for ((idx, _), w) in indexed.iter().zip(&weights) {
1556 if r <= *w {
1557 return *idx as u32;
1558 }
1559 r -= w;
1560 }
1561 indexed[0].0 as u32
1562 }
1563 }
1564}
1565
1566pub struct Sampler {
1583 rng: StdRng,
1584}
1585
1586impl Sampler {
1587 pub fn new(seed: u64) -> Self {
1588 Sampler {
1589 rng: StdRng::seed_from_u64(seed),
1590 }
1591 }
1592
1593 pub fn pick(&mut self, logits: &[f32], sampling: Sampling) -> u32 {
1596 sample(logits, sampling, Some(&mut self.rng))
1597 }
1598
1599 pub fn reseed(&mut self, seed: u64) {
1601 self.rng = StdRng::seed_from_u64(seed);
1602 }
1603}
1604
1605fn argmax(v: &[f32]) -> u32 {
1606 let mut best = 0usize;
1607 for (i, &x) in v.iter().enumerate() {
1608 if x > v[best] {
1609 best = i;
1610 }
1611 }
1612 best as u32
1613}