use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use tokio::sync::mpsc;
use super::{
BoxError, EngineError, Event, FinishReason, GenerateRequest, LlmEngine, Logits, Prompt,
StepModel, TextPipeline,
};
use crate::llm::pipeline::Tokenizer;
pub const EOS_TOKEN: &str = "<|eos|>";
pub const EOS_ID: u32 = 256;
pub fn byte_tokenizer() -> Tokenizer {
let vocab: serde_json::Map<String, serde_json::Value> = (0u8..=255)
.map(|b| (byte_char(b).to_string(), serde_json::Value::from(b)))
.collect();
let json = serde_json::json!({
"version": "1.0",
"added_tokens": [{
"id": EOS_ID, "content": EOS_TOKEN, "single_word": false, "lstrip": false,
"rstrip": false, "normalized": false, "special": true
}],
"normalizer": null,
"pre_tokenizer": {
"type": "ByteLevel", "add_prefix_space": false, "trim_offsets": false,
"use_regex": false
},
"post_processor": null,
"decoder": {
"type": "ByteLevel", "add_prefix_space": false, "trim_offsets": false,
"use_regex": false
},
"model": {"type": "BPE", "vocab": vocab, "merges": []}
});
json.to_string()
.parse()
.expect("the byte-level test tokenizer is valid")
}
fn byte_char(b: u8) -> char {
let printable = |b: u8| matches!(b, b'!'..=b'~' | 0xA1..=0xAC | 0xAE..=0xFF);
if printable(b) {
char::from(b)
} else {
let shift = (0..b).filter(|&x| !printable(x)).count() as u32;
char::from_u32(256 + shift).expect("a valid character")
}
}
pub fn byte_pipeline() -> TextPipeline {
TextPipeline::new(byte_tokenizer())
.with_eos_token(EOS_TOKEN)
.expect("the EOS token is in the vocabulary")
.with_chat_template(
"{% for m in messages %}<|{{ m.role }}|>{{ m.content }}\n{% endfor %}\
{% if add_generation_prompt %}<|assistant|>{% endif %}",
)
.expect("the chat template is valid")
}
#[derive(Debug, Clone, Default)]
pub struct SharedCount(Arc<AtomicUsize>);
impl SharedCount {
pub fn get(&self) -> usize {
self.0.load(Ordering::Acquire)
}
fn guard(&self) -> CountGuard {
self.0.fetch_add(1, Ordering::AcqRel);
CountGuard(self.0.clone())
}
}
#[derive(Debug)]
struct CountGuard(Arc<AtomicUsize>);
impl Drop for CountGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::AcqRel);
}
}
#[derive(Debug)]
pub struct MockStepModel {
name: String,
reply: Vec<u32>,
vocab: usize,
eos: u32,
max_context: usize,
max_sequences: usize,
prefill_delay: Duration,
token_delay: Duration,
live: SharedCount,
decodes: SharedCount,
}
#[derive(Debug)]
pub struct MockSequence {
position: usize,
_live: CountGuard,
}
impl MockStepModel {
pub fn new(name: &str, reply: &str) -> Self {
Self {
name: name.to_string(),
reply: reply.bytes().map(u32::from).collect(),
vocab: EOS_ID as usize + 1,
eos: EOS_ID,
max_context: 4096,
max_sequences: 16,
prefill_delay: Duration::ZERO,
token_delay: Duration::ZERO,
live: SharedCount::default(),
decodes: SharedCount::default(),
}
}
pub fn prefill_delay(mut self, delay: Duration) -> Self {
self.prefill_delay = delay;
self
}
pub fn token_delay(mut self, delay: Duration) -> Self {
self.token_delay = delay;
self
}
pub fn max_context(mut self, tokens: usize) -> Self {
self.max_context = tokens;
self
}
pub fn max_sequences(mut self, sequences: usize) -> Self {
self.max_sequences = sequences;
self
}
pub fn live(&self) -> SharedCount {
self.live.clone()
}
pub fn decodes(&self) -> SharedCount {
self.decodes.clone()
}
fn logits_for(&self, position: usize) -> Logits {
let token = self.reply.get(position).copied().unwrap_or(self.eos);
let mut logits = vec![0.0; self.vocab];
logits[token as usize] = 10.0;
Logits(logits)
}
}
impl StepModel for MockStepModel {
type Sequence = MockSequence;
fn name(&self) -> &str {
&self.name
}
fn max_context(&self) -> usize {
self.max_context
}
fn max_sequences(&self) -> usize {
self.max_sequences
}
fn prefill(&self, _prompt: &[u32]) -> Result<(MockSequence, Logits), BoxError> {
std::thread::sleep(self.prefill_delay);
let sequence = MockSequence {
position: 0,
_live: self.live.guard(),
};
Ok((sequence, self.logits_for(0)))
}
fn decode(&self, sequence: &mut MockSequence, _token: u32) -> Result<Logits, BoxError> {
std::thread::sleep(self.token_delay);
sequence.position += 1;
self.decodes.0.fetch_add(1, Ordering::AcqRel);
Ok(self.logits_for(sequence.position))
}
}
#[derive(Debug)]
pub struct MockEngine {
name: String,
pieces: Vec<String>,
first_delay: Duration,
piece_delay: Duration,
max_context: usize,
live: SharedCount,
}
impl MockEngine {
pub fn new(name: &str, pieces: &[&str]) -> Self {
Self {
name: name.to_string(),
pieces: pieces.iter().map(|p| p.to_string()).collect(),
first_delay: Duration::ZERO,
piece_delay: Duration::ZERO,
max_context: 4096,
live: SharedCount::default(),
}
}
pub fn first_delay(mut self, delay: Duration) -> Self {
self.first_delay = delay;
self
}
pub fn piece_delay(mut self, delay: Duration) -> Self {
self.piece_delay = delay;
self
}
pub fn live(&self) -> SharedCount {
self.live.clone()
}
}
impl LlmEngine for MockEngine {
fn name(&self) -> &str {
&self.name
}
fn max_context(&self) -> usize {
self.max_context
}
fn generate(&self, request: GenerateRequest) -> Result<mpsc::Receiver<Event>, EngineError> {
let prompt_tokens = match &request.input {
Prompt::Chat(messages) => messages.iter().map(|m| m.content.len()).sum(),
Prompt::Text(text) => text.len(),
};
if prompt_tokens >= self.max_context {
return Err(EngineError::TooLong {
prompt_tokens,
max_context: self.max_context,
});
}
let max_tokens = request.params.max_tokens.unwrap_or(usize::MAX);
let pieces = self.pieces.clone();
let (first_delay, piece_delay) = (self.first_delay, self.piece_delay);
let live = self.live.guard();
let (events, receiver) = mpsc::channel(16);
tokio::spawn(async move {
let _live = live;
let send = |event| {
let events = events.clone();
async move {
tokio::select! {
sent = events.send(event) => sent.is_ok(),
() = events.closed() => false,
}
}
};
if !send(Event::Started { prompt_tokens }).await {
return;
}
for (i, piece) in pieces.iter().enumerate() {
if i == max_tokens {
send(Event::Finished {
reason: FinishReason::Length,
completion_tokens: i,
})
.await;
return;
}
let delay = if i == 0 { first_delay } else { piece_delay };
tokio::select! {
() = tokio::time::sleep(delay) => {}
() = events.closed() => return,
}
if !send(Event::Text(piece.clone())).await {
return;
}
}
send(Event::Finished {
reason: FinishReason::Stop,
completion_tokens: pieces.len(),
})
.await;
});
Ok(receiver)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_byte_tokenizer_has_one_token_per_byte() {
let tokenizer = byte_tokenizer();
let text = "Hi é ✓";
let ids = tokenizer.encode(text, false).unwrap().get_ids().to_vec();
assert_eq!(ids, text.bytes().map(u32::from).collect::<Vec<_>>());
assert_eq!(tokenizer.decode(&ids, true).unwrap(), text);
assert_eq!(tokenizer.token_to_id(EOS_TOKEN), Some(EOS_ID));
}
#[test]
fn incremental_detokenization_waits_for_whole_characters() {
let tokenizer = byte_tokenizer();
let mut stream = tokenizer.decode_stream(true);
let mut out = Vec::new();
for byte in "a✓".bytes() {
out.push(stream.step(u32::from(byte)).unwrap());
}
assert_eq!(
out,
[Some("a".to_string()), None, None, Some("✓".to_string())]
);
}
}