///| A deterministic model adapter for examples, regression tests, and
///| benchmark fixtures. Each step associates a required token context with
///| logits returned by a draft or target model. Production users can replace
///| this adapter with their own inference bridge while preserving the decoder
///|
/// protocol.
pub enum ReplayError {
EmptyReplay
EmptyVocabulary(Int)
VocabularyMismatch(Int)
ContextMismatch(Int)
Exhausted(Int)
} derive(Eq, Debug)
///|
/// One response emitted by a replayable logits provider.
pub struct ReplayStep {
expected_context : Array[Int]
logits : Array[Double]
}
///|
pub fn ReplayStep::new(
expected_context : Array[Int],
logits : Array[Double],
) -> ReplayStep {
{ expected_context, logits }
}
///|
pub fn ReplayStep::context(self : ReplayStep) -> Array[Int] {
self.expected_context
}
///|
pub fn ReplayStep::logits(self : ReplayStep) -> Array[Double] {
self.logits
}
///|
/// Stateful cursor over deterministic model responses.
pub struct ReplayModel {
steps : Array[ReplayStep]
vocabulary_size : Int
mut cursor : Int
}
///|
fn same_tokens(left : Array[Int], right : Array[Int]) -> Bool {
if left.length() != right.length() {
return false
}
for index in 0.. Result[ReplayModel, ReplayError] {
if steps.length() == 0 {
return Err(EmptyReplay)
}
let vocabulary_size = steps[0].logits.length()
if vocabulary_size == 0 {
return Err(EmptyVocabulary(0))
}
for index in 1.. Int {
self.vocabulary_size
}
///|
pub fn ReplayModel::position(self : ReplayModel) -> Int {
self.cursor
}
///|
pub fn ReplayModel::remaining(self : ReplayModel) -> Int {
self.steps.length() - self.cursor
}
///| Reset only the fixture cursor. It does not alter model steps, making one
///|
/// fixture safely reusable across multiple strategy comparisons.
pub fn ReplayModel::reset(self : ReplayModel) -> Unit {
self.cursor = 0
}
///| Read the next logits response, ensuring callers ask the model with the
///|
/// same token prefix recorded by the fixture.
pub fn ReplayModel::next_logits(
self : ReplayModel,
context : Array[Int],
) -> Result[Array[Double], ReplayError] {
if self.cursor >= self.steps.length() {
return Err(Exhausted(self.cursor))
}
let step = self.steps[self.cursor]
if !same_tokens(step.expected_context, context) {
return Err(ContextMismatch(self.cursor))
}
self.cursor = self.cursor + 1
Ok(step.logits)
}
///| Return a human-readable compact record for logs and failed fixtures. The
///|
/// full logits are intentionally omitted because they can be very large.
pub fn ReplayModel::describe_next(self : ReplayModel) -> String {
if self.cursor >= self.steps.length() {
"replay exhausted at step=" + self.cursor.to_string()
} else {
let step = self.steps[self.cursor]
"replay step=" +
self.cursor.to_string() +
" context_length=" +
step.expected_context.length().to_string() +
" vocabulary=" +
step.logits.length().to_string()
}
}