use serde_json::Value;
use super::byte_level::byte_level_decode;
use super::streaming::{ByteFallbackRule, DecodePost, RenderRules, WordSeparator};
enum ReplacePattern {
Str(String),
Regex(Box<regexr::Regex>),
}
enum DecodeOp {
ByteLevel,
Replace { pattern: ReplacePattern, to: String },
Strip {
content: char,
start: usize,
stop: usize,
},
Fuse,
ByteFallback,
Metaspace {
replacement: char,
add_prefix_space: bool,
},
WordPiece { prefix: String, cleanup: bool },
Bpe { suffix: String },
}
pub struct Decoder {
ops: Vec<DecodeOp>,
}
impl Decoder {
pub fn decode(&self, tokens: Vec<String>) -> String {
let mut toks = tokens;
for op in &self.ops {
toks = op.apply(toks);
}
toks.concat()
}
pub(crate) fn lower(&self) -> Option<(RenderRules, Vec<DecodePost>)> {
self.lower_or_reason().ok()
}
pub(crate) fn unstreamable_op(&self) -> Option<&'static str> {
self.lower_or_reason().err()
}
fn lower_or_reason(&self) -> Result<(RenderRules, Vec<DecodePost>), &'static str> {
let mut byte_fallback = ByteFallbackRule::None;
let mut use_byte_level = false;
let mut replaces: Vec<(String, String)> = Vec::new();
let mut separator: Option<WordSeparator> = None;
let mut unit_cleanup = false;
let mut post = Vec::new();
let mut fused = false;
let mut bytes_resolved = false;
for op in &self.ops {
match op {
DecodeOp::ByteLevel => {
if fused || bytes_resolved || !replaces.is_empty() || separator.is_some() {
return Err("ByteLevel");
}
use_byte_level = true;
fused = true;
}
DecodeOp::Replace { pattern, to } => {
let from = match pattern {
ReplacePattern::Str(from) => from,
ReplacePattern::Regex(_) => return Err("Replace"),
};
if fused || bytes_resolved {
return Err("Replace");
}
replaces.push((from.clone(), to.clone()));
}
DecodeOp::Strip {
content,
start,
stop,
} => {
if !fused || *stop > 0 {
return Err("Strip");
}
match *start {
0 => {}
1 if *content == ' ' => post.push(DecodePost::StripLeadingSpace),
_ => return Err("Strip"),
}
}
DecodeOp::Fuse => fused = true,
DecodeOp::ByteFallback => {
if fused {
return Err("ByteFallback");
}
byte_fallback = ByteFallbackRule::DeclaredRun;
bytes_resolved = true;
}
DecodeOp::Metaspace {
replacement,
add_prefix_space,
} => {
if fused || bytes_resolved {
return Err("Metaspace");
}
replaces.push((replacement.to_string(), " ".to_string()));
if *add_prefix_space {
post.push(DecodePost::StripLeadingSpace);
}
}
DecodeOp::WordPiece { prefix, cleanup } => {
if fused || bytes_resolved || separator.is_some() {
return Err("WordPiece");
}
separator = Some(if prefix.is_empty() {
WordSeparator::None
} else {
WordSeparator::Continuation(prefix.clone())
});
unit_cleanup = *cleanup;
}
DecodeOp::Bpe { .. } => return Err("BPEDecoder"),
}
}
let mut rules = RenderRules::declared(byte_fallback, use_byte_level);
for (from, to) in replaces {
rules = rules.with_surface_replace(from, to);
}
if let Some(separator) = separator {
rules = rules.with_word_separator(separator);
}
if unit_cleanup {
rules = rules.with_unit_cleanup();
}
Ok((rules, post))
}
}
impl DecodeOp {
fn apply(&self, tokens: Vec<String>) -> Vec<String> {
match self {
DecodeOp::ByteLevel => {
let mut bytes = Vec::new();
for t in &tokens {
match byte_level_decode(t) {
Some(b) => bytes.extend_from_slice(&b),
None => bytes.extend_from_slice(t.as_bytes()),
}
}
vec![String::from_utf8_lossy(&bytes).into_owned()]
}
DecodeOp::Replace { pattern, to } => tokens
.into_iter()
.map(|t| match pattern {
ReplacePattern::Str(from) => t.replace(from.as_str(), to),
ReplacePattern::Regex(re) => re.replace_all(&t, to).into_owned(),
})
.collect(),
DecodeOp::Strip {
content,
start,
stop,
} => tokens
.into_iter()
.map(|t| strip_token(&t, *content, *start, *stop))
.collect(),
DecodeOp::Fuse => vec![tokens.concat()],
DecodeOp::ByteFallback => byte_fallback(tokens),
DecodeOp::Metaspace {
replacement,
add_prefix_space,
} => tokens
.into_iter()
.enumerate()
.map(|(i, t)| {
let replaced: String = t
.chars()
.map(|c| if c == *replacement { ' ' } else { c })
.collect();
if i == 0 && *add_prefix_space {
replaced
.strip_prefix(' ')
.map(str::to_string)
.unwrap_or(replaced)
} else {
replaced
}
})
.collect(),
DecodeOp::WordPiece { prefix, cleanup } => tokens
.into_iter()
.enumerate()
.map(|(i, t)| {
let mut s = if i != 0 {
match t.strip_prefix(prefix.as_str()) {
Some(rest) => rest.to_string(),
None => format!(" {t}"),
}
} else {
t
};
if *cleanup {
s = wordpiece_cleanup(&s);
}
s
})
.collect(),
DecodeOp::Bpe { suffix } => {
let n = tokens.len();
tokens
.into_iter()
.enumerate()
.map(|(i, t)| t.replace(suffix.as_str(), if i + 1 == n { "" } else { " " }))
.collect()
}
}
}
}
fn strip_token(token: &str, content: char, start: usize, stop: usize) -> String {
let chars: Vec<char> = token.chars().collect();
let mut lo = 0;
while lo < start && lo < chars.len() && chars[lo] == content {
lo += 1;
}
let mut hi = chars.len();
let mut removed = 0;
while removed < stop && hi > lo && chars[hi - 1] == content {
hi -= 1;
removed += 1;
}
chars[lo..hi].iter().collect()
}
fn byte_fallback(tokens: Vec<String>) -> Vec<String> {
let mut out = Vec::with_capacity(tokens.len());
let mut run: Vec<u8> = Vec::new();
let flush = |run: &mut Vec<u8>, out: &mut Vec<String>| {
if run.is_empty() {
return;
}
match std::str::from_utf8(run) {
Ok(s) => out.push(s.to_string()),
Err(_) => out.extend(run.iter().map(|_| "\u{fffd}".to_string())),
}
run.clear();
};
for t in tokens {
match parse_byte_token(&t) {
Some(b) => run.push(b),
None => {
flush(&mut run, &mut out);
out.push(t);
}
}
}
flush(&mut run, &mut out);
out
}
pub(crate) fn parse_byte_token(token: &str) -> Option<u8> {
let hex = token.strip_prefix("<0x")?.strip_suffix('>')?;
if hex.len() == 2 {
u8::from_str_radix(hex, 16).ok()
} else {
None
}
}
pub(crate) fn wordpiece_cleanup(s: &str) -> String {
s.replace(" .", ".")
.replace(" ?", "?")
.replace(" !", "!")
.replace(" ,", ",")
.replace(" ' ", "'")
.replace(" n't", "n't")
.replace(" 'm", "'m")
.replace(" 's", "'s")
.replace(" 've", "'ve")
.replace(" 're", "'re")
}
pub fn parse(decoder: Option<&Value>) -> Option<Decoder> {
let decoder = decoder?;
let mut ops = Vec::new();
walk(decoder, &mut ops);
if ops.is_empty() {
None
} else {
Some(Decoder { ops })
}
}
fn walk(v: &Value, ops: &mut Vec<DecodeOp>) {
match v.get("type").and_then(Value::as_str) {
Some("Sequence") => {
if let Some(list) = v.get("decoders").and_then(Value::as_array) {
for item in list {
walk(item, ops);
}
}
}
Some("ByteLevel") => ops.push(DecodeOp::ByteLevel),
Some("Replace") => {
let to = v
.get("content")
.and_then(Value::as_str)
.unwrap_or("")
.to_string();
if let Some(p) = v.get("pattern") {
if let Some(s) = p.get("String").and_then(Value::as_str) {
ops.push(DecodeOp::Replace {
pattern: ReplacePattern::Str(s.to_string()),
to,
});
} else if let Some(re) = p.get("Regex").and_then(Value::as_str) {
if let Ok(compiled) = regexr::RegexBuilder::new(re).build() {
ops.push(DecodeOp::Replace {
pattern: ReplacePattern::Regex(Box::new(compiled)),
to,
});
}
}
}
}
Some("Strip") => {
let content = v
.get("content")
.and_then(Value::as_str)
.and_then(|s| s.chars().next())
.unwrap_or(' ');
ops.push(DecodeOp::Strip {
content,
start: v.get("start").and_then(Value::as_u64).unwrap_or(0) as usize,
stop: v.get("stop").and_then(Value::as_u64).unwrap_or(0) as usize,
});
}
Some("Fuse") => ops.push(DecodeOp::Fuse),
Some("ByteFallback") => ops.push(DecodeOp::ByteFallback),
Some("Metaspace") => {
let replacement = v
.get("replacement")
.and_then(Value::as_str)
.and_then(|s| s.chars().next())
.unwrap_or('▁');
let add_prefix_space = match v.get("prepend_scheme").and_then(Value::as_str) {
Some(scheme) => scheme != "never",
None => v
.get("add_prefix_space")
.and_then(Value::as_bool)
.unwrap_or(true),
};
ops.push(DecodeOp::Metaspace {
replacement,
add_prefix_space,
});
}
Some("WordPiece") => ops.push(DecodeOp::WordPiece {
prefix: v
.get("prefix")
.and_then(Value::as_str)
.unwrap_or("##")
.to_string(),
cleanup: v.get("cleanup").and_then(Value::as_bool).unwrap_or(true),
}),
Some("BPEDecoder") => ops.push(DecodeOp::Bpe {
suffix: v
.get("suffix")
.and_then(Value::as_str)
.unwrap_or("</w>")
.to_string(),
}),
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::streaming::{DecodeState, Surfaces};
use rustc_hash::{FxHashMap, FxHashSet};
use std::convert::Infallible;
use std::sync::Arc;
fn dec(json: serde_json::Value, tokens: &[&str]) -> String {
let d = parse(Some(&json)).unwrap();
d.decode(tokens.iter().map(|s| s.to_string()).collect())
}
fn lowered(json: &serde_json::Value, tokens: &[&str], chunk: usize) -> String {
let decoder = parse(Some(json)).unwrap();
let (rules, post) = decoder.lower().expect("the pipeline lowers");
let surfaces: Vec<String> = tokens.iter().map(|s| s.to_string()).collect();
let state = DecodeState::new(
rules.with_vocabulary(
Surfaces::ByIndex(Arc::new(surfaces)),
Arc::new(FxHashMap::default()),
Arc::new(FxHashSet::default()),
),
post,
);
let ids: Vec<u32> = (0..tokens.len() as u32).collect();
let mut cursor = state.cursor_with_capacity(tokens.len() * 4);
let mut out = String::new();
for group in ids.chunks(chunk.max(1)) {
let emitted = match cursor.feed(group, |_| Ok::<(), Infallible>(())) {
Ok(text) => text,
Err(never) => match never {},
};
out.push_str(&emitted.unwrap_or_default());
}
out.push_str(&cursor.flush());
out
}
fn same(json: serde_json::Value, tokens: &[&str]) -> String {
let expected = dec(json.clone(), tokens);
for chunk in 1..=tokens.len().max(1) {
assert_eq!(
lowered(&json, tokens, chunk),
expected,
"lowered drive in chunks of {chunk} over {tokens:?}"
);
}
expected
}
#[test]
fn byte_level_reassembles_cross_token_utf8_and_keeps_literals() {
let j = serde_json::json!({"type": "ByteLevel"});
assert_eq!(dec(j.clone(), &["c", "a", "f", "Ã", "©"]), "café");
assert_eq!(dec(j, &["a", " ", "b"]), "a b");
}
#[test]
fn metaspace_strips_leading_only_when_prefixing() {
let always = serde_json::json!({"type": "Metaspace", "prepend_scheme": "always"});
assert_eq!(dec(always, &["▁Hello", "▁world"]), "Hello world");
let never = serde_json::json!({"type": "Metaspace", "prepend_scheme": "never"});
assert_eq!(dec(never, &["▁Hello", "▁world"]), " Hello world");
}
#[test]
fn wordpiece_prefix_and_cleanup() {
let j = serde_json::json!({"type": "WordPiece", "prefix": "##", "cleanup": true});
assert_eq!(dec(j.clone(), &["hello", "##world"]), "helloworld");
assert_eq!(dec(j.clone(), &["hello", ",", "world"]), "hello, world");
assert_eq!(dec(j, &["don", "'", "t"]), "don ' t");
}
#[test]
fn metaspace_byte_fallback_fuse_sequence() {
let j = serde_json::json!({"type": "Sequence", "decoders": [
{"type": "Replace", "pattern": {"String": "▁"}, "content": " "},
{"type": "ByteFallback"},
{"type": "Fuse"},
{"type": "Strip", "content": " ", "start": 1, "stop": 0},
]});
assert_eq!(dec(j, &["▁Hi", "<0xE2>", "<0x82>", "<0xAC>"]), "Hi€");
}
#[test]
fn bpe_word_suffix_decoder() {
let j = serde_json::json!({"type": "BPEDecoder", "suffix": "</w>"});
assert_eq!(dec(j, &["hello</w>", "world</w>"]), "hello world");
}
#[test]
fn declared_byte_fallback_is_one_replacement_char_per_byte() {
let j = mistral_chain();
assert_eq!(same(j.clone(), &["<0x80>"]), "\u{fffd}");
assert_eq!(same(j.clone(), &["<0x80>", "<0x80>"]), "\u{fffd}\u{fffd}");
assert_eq!(same(j.clone(), &["<0xE2>", "<0x41>"]), "\u{fffd}\u{fffd}");
assert_eq!(same(j, &["<0xF0>", "<0x9F>"]), "\u{fffd}\u{fffd}");
}
#[test]
fn byte_token_parse_is_strict_two_hex_digits() {
assert_eq!(parse_byte_token("<0x00>"), Some(0x00));
assert_eq!(parse_byte_token("<0x41>"), Some(0x41));
assert_eq!(parse_byte_token("<0xFF>"), Some(0xff));
assert_eq!(parse_byte_token("<0x4a>"), Some(0x4a));
assert_eq!(parse_byte_token("<0x4A>"), Some(0x4a));
assert_eq!(parse_byte_token("<0x1>"), None);
assert_eq!(parse_byte_token("<0x>"), None);
assert_eq!(parse_byte_token("<0x041>"), None);
assert_eq!(parse_byte_token("<0xG1>"), None);
assert_eq!(parse_byte_token("<0x 1>"), None);
assert_eq!(parse_byte_token("0x41"), None);
assert_eq!(parse_byte_token("<0x41"), None);
assert_eq!(parse_byte_token("hello"), None);
}
#[test]
fn single_hex_digit_surface_is_literal_text() {
assert_eq!(same(mistral_chain(), &["<0x1>"]), "<0x1>");
assert_eq!(same(mistral_chain(), &["▁a", "<0x1>"]), "a<0x1>");
}
fn mistral_chain() -> serde_json::Value {
serde_json::json!({"type": "Sequence", "decoders": [
{"type": "Replace", "pattern": {"String": "▁"}, "content": " "},
{"type": "ByteFallback"},
{"type": "Fuse"},
{"type": "Strip", "content": " ", "start": 1, "stop": 0},
]})
}
#[test]
fn every_shipping_pipeline_lowers_to_the_same_text() {
let byte_level = serde_json::json!({"type": "ByteLevel"});
assert_eq!(same(byte_level.clone(), &["c", "a", "f", "Ã", "©"]), "café");
assert_eq!(same(byte_level, &["a", " ", "b"]), "a b");
let always = serde_json::json!({"type": "Metaspace", "prepend_scheme": "always"});
assert_eq!(same(always, &["▁Hello", "▁world"]), "Hello world");
let never = serde_json::json!({"type": "Metaspace", "prepend_scheme": "never"});
assert_eq!(same(never, &["▁Hello", "▁world"]), " Hello world");
let wordpiece = serde_json::json!({"type": "WordPiece", "prefix": "##", "cleanup": true});
assert_eq!(same(wordpiece.clone(), &["hello", "##world"]), "helloworld");
assert_eq!(
same(wordpiece.clone(), &["hello", ",", "world"]),
"hello, world"
);
assert_eq!(same(wordpiece.clone(), &["don", "'", "t"]), "don ' t");
assert_eq!(same(wordpiece, &["it", "'s", "fine"]), "it's fine");
let plain = serde_json::json!({"type": "WordPiece", "prefix": "##", "cleanup": false});
assert_eq!(same(plain, &["hello", ",", "world"]), "hello , world");
let unmarked = serde_json::json!({"type": "WordPiece", "prefix": "", "cleanup": false});
assert_eq!(same(unmarked, &["hello", "world"]), "helloworld");
let mistral = mistral_chain();
assert_eq!(
same(mistral.clone(), &["▁Hi", "<0xE2>", "<0x82>", "<0xAC>"]),
"Hi€"
);
assert_eq!(
same(mistral.clone(), &["▁a", "<0x80>", "▁b"]),
"a\u{fffd} b"
);
assert_eq!(same(mistral, &["▁", "▁a"]), " a");
}
#[test]
fn pipelines_that_cannot_stream_do_not_lower() {
let lowers = |json: serde_json::Value| parse(Some(&json)).unwrap().lower().is_some();
assert!(!lowers(
serde_json::json!({"type": "BPEDecoder", "suffix": "</w>"})
));
assert!(!lowers(
serde_json::json!({"type": "Sequence", "decoders": [
{"type": "ByteFallback"},
{"type": "Fuse"},
{"type": "Strip", "content": " ", "start": 0, "stop": 1},
]})
));
assert!(!lowers(
serde_json::json!({"type": "Sequence", "decoders": [
{"type": "ByteFallback"},
{"type": "Fuse"},
{"type": "Replace", "pattern": {"Regex": " +"}, "content": " "},
]})
));
assert!(lowers(mistral_chain()));
}
}