use super::json_schema::JsonSchemaTables;
use super::token_bytes::TokenByteTable;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SchemaMeta {
pub root: u32,
pub nodes_base: u32,
pub props_base: u32,
pub choice_lits_base: u32,
pub spans_base: u32,
pub n_nodes: u32,
pub n_props: u32,
pub n_choice_lits: u32,
pub n_spans: u32,
pub n_bytes: u32,
}
impl SchemaMeta {
pub fn as_words(&self) -> [u32; 11] {
[
self.root,
self.nodes_base,
self.props_base,
self.choice_lits_base,
self.spans_base,
self.n_nodes,
self.n_props,
self.n_choice_lits,
self.n_spans,
self.n_bytes,
0,
]
}
}
#[derive(Clone, Debug)]
pub struct GpuSchema {
pub words: Vec<u32>,
pub bytes: Vec<u32>,
pub meta: SchemaMeta,
}
impl GpuSchema {
pub fn from_tables(t: &JsonSchemaTables) -> Self {
let nodes = t.raw_nodes();
let props = t.raw_props();
let choice_lits = t.raw_choice_lits();
let spans = t.raw_literal_spans();
let lit_bytes = t.raw_literal_bytes();
let mut words =
Vec::with_capacity(nodes.len() + props.len() + choice_lits.len() + spans.len());
let nodes_base = words.len() as u32;
words.extend_from_slice(nodes);
let props_base = words.len() as u32;
words.extend_from_slice(props);
let choice_lits_base = words.len() as u32;
words.extend_from_slice(choice_lits);
let spans_base = words.len() as u32;
words.extend_from_slice(spans);
let bytes: Vec<u32> = lit_bytes.iter().map(|&b| b as u32).collect();
let meta = SchemaMeta {
root: t.root(),
nodes_base,
props_base,
choice_lits_base,
spans_base,
n_nodes: (nodes.len() / JsonSchemaTables::NODE_WORDS) as u32,
n_props: (props.len() / 2) as u32,
n_choice_lits: choice_lits.len() as u32,
n_spans: (spans.len() / 2) as u32,
n_bytes: lit_bytes.len() as u32,
};
Self { words, bytes, meta }
}
pub fn nodes(&self) -> &[u32] {
let a = self.meta.nodes_base as usize;
let b = a + self.meta.n_nodes as usize * JsonSchemaTables::NODE_WORDS;
&self.words[a..b]
}
pub fn props(&self) -> &[u32] {
let a = self.meta.props_base as usize;
&self.words[a..a + self.meta.n_props as usize * 2]
}
pub fn choice_lits(&self) -> &[u32] {
let a = self.meta.choice_lits_base as usize;
&self.words[a..a + self.meta.n_choice_lits as usize]
}
pub fn spans(&self) -> &[u32] {
let a = self.meta.spans_base as usize;
&self.words[a..a + self.meta.n_spans as usize * 2]
}
pub fn literal(&self, span: u32) -> Vec<u8> {
let base = self.meta.spans_base as usize + span as usize * 2;
let off = self.words[base] as usize;
let len = self.words[base + 1] as usize;
self.bytes[off..off + len]
.iter()
.map(|&w| w as u8)
.collect()
}
}
#[derive(Clone, Debug)]
pub struct GpuVocab {
pub bytes: Vec<u32>,
pub off: Vec<u32>,
}
impl GpuVocab {
pub fn from_table(table: &TokenByteTable) -> Self {
let mut bytes = Vec::new();
let mut off = vec![0u32];
for id in 0..table.len() as u32 {
let piece = table.bytes(id).unwrap_or(&[]);
bytes.extend(piece.iter().map(|&b| b as u32));
off.push(bytes.len() as u32);
}
Self { bytes, off }
}
pub fn len(&self) -> usize {
self.off.len() - 1
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn piece(&self, id: u32) -> Vec<u8> {
let a = self.off[id as usize] as usize;
let b = self.off[id as usize + 1] as usize;
self.bytes[a..b].iter().map(|&w| w as u8).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grammar::json_schema::NodeKind;
use serde_json::json;
fn schemas() -> Vec<serde_json::Value> {
vec![
json!({ "type": "number" }),
json!({ "type": "boolean" }),
json!({ "enum": ["red", "green", "blue"] }),
json!({ "type": "array", "items": { "type": "integer" }, "minItems": 1, "maxItems": 3 }),
json!({
"type": "object",
"properties": { "name": { "type": "string" }, "age": { "type": "integer" } },
"required": ["name", "age"],
"additionalProperties": false
}),
]
}
#[test]
fn should_round_trip_flat_schema_encoding() {
for schema in schemas() {
let t = JsonSchemaTables::compile(&schema).expect("compile");
let g = GpuSchema::from_tables(&t);
assert_eq!(g.nodes(), t.raw_nodes(), "nodes sub-table");
assert_eq!(g.props(), t.raw_props(), "props sub-table");
assert_eq!(
g.choice_lits(),
t.raw_choice_lits(),
"choice_lits sub-table"
);
assert_eq!(g.spans(), t.raw_literal_spans(), "spans sub-table");
assert_eq!(g.bytes.len(), t.raw_literal_bytes().len(), "bytes length");
for n in 0..g.meta.n_nodes {
let flat_kind =
g.words[g.meta.nodes_base as usize + n as usize * JsonSchemaTables::NODE_WORDS];
assert_eq!(flat_kind, t.kind(n) as u32, "flat kind of node {n}");
}
for s in 0..g.meta.n_spans {
assert_eq!(g.literal(s), t.literal_for_test(s), "literal span {s}");
}
}
}
#[test]
fn should_encode_object_keys_in_required_order() {
let t = JsonSchemaTables::compile(&json!({
"type": "object",
"properties": { "name": { "type": "string" }, "age": { "type": "integer" } },
"required": ["name", "age"],
"additionalProperties": false
}))
.unwrap();
let g = GpuSchema::from_tables(&t);
let root = g.meta.root;
let kind =
g.words[g.meta.nodes_base as usize + root as usize * JsonSchemaTables::NODE_WORDS];
assert_eq!(kind, NodeKind::Object as u32);
let prop_start =
g.words[g.meta.nodes_base as usize + root as usize * JsonSchemaTables::NODE_WORDS + 1];
let key0_span = g.words[g.meta.props_base as usize + prop_start as usize * 2];
let key1_span = g.words[g.meta.props_base as usize + (prop_start as usize + 1) * 2];
assert_eq!(g.literal(key0_span), b"\"name\"");
assert_eq!(g.literal(key1_span), b"\"age\"");
}
#[test]
fn should_round_trip_flat_vocab_encoding() {
let tok = r#"{ "model": { "vocab": { "ĠParis": 0, "Hello": 1, "Ġworld": 2 } } }"#;
let table = TokenByteTable::from_tokenizer_json(tok.as_bytes()).expect("byte table");
let g = GpuVocab::from_table(&table);
assert_eq!(g.len(), table.len());
for id in 0..table.len() as u32 {
assert_eq!(g.piece(id), table.bytes(id).unwrap(), "vocab piece {id}");
}
}
}