#![cfg(all(feature = "setfit", feature = "conformance-fixtures"))]
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use aprender::autograd::{get_grad, Tensor};
use aprender::nn::Module;
use aprender::setfit::{FreezeGroup, SentenceBatch, SetFitError, SetFitMiniLm};
use serde::Deserialize;
use sha2::{Digest, Sha256};
#[path = "setfit_conformance/tolerances_generated.rs"]
pub mod tolerances_generated;
#[path = "setfit_conformance/detach_negative.rs"]
mod detach_negative;
#[path = "setfit_conformance/forward_parity.rs"]
mod forward_parity;
#[path = "setfit_conformance/frozen_gate.rs"]
mod frozen_gate;
#[path = "setfit_conformance/full_weight_parity.rs"]
mod full_weight_parity;
#[path = "setfit_conformance/gradient_gate.rs"]
mod gradient_gate;
use tolerances_generated as tol;
pub fn fixtures_dir() -> PathBuf {
if let Ok(p) = std::env::var("APRENDER_SETFIT_FIXTURES") {
let p = PathBuf::from(p);
if p.is_dir() {
return p;
}
}
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/setfit")
}
pub fn contract_path() -> Option<PathBuf> {
let mut dir: Option<&Path> = Some(Path::new(env!("CARGO_MANIFEST_DIR")));
while let Some(d) = dir {
let candidate = d.join("contracts/setfit-encoder-conformance-v1.yaml");
if candidate.is_file() {
return Some(candidate);
}
dir = d.parent();
}
None
}
pub const SEED: u64 = 0x0108_5E7F_1701;
pub fn slice_model() -> SetFitMiniLm {
let mut m = SetFitMiniLm::from_slice_fixture(&fixtures_dir(), SEED)
.expect("the frozen slice fixture must load through the bound type");
m.set_training(false);
m
}
pub fn batch_from_case(
model: &SetFitMiniLm,
texts: &[String],
) -> Result<SentenceBatch, SetFitError> {
let refs: Vec<&str> = texts.iter().map(String::as_str).collect();
model.tokenize(&refs)
}
pub fn read_fixture<T: for<'de> Deserialize<'de>>(name: &str) -> T {
let path = fixtures_dir().join(name);
let bytes = std::fs::read(&path)
.unwrap_or_else(|e| panic!("fixture `{}` is unreadable: {e}", path.display()));
serde_json::from_slice(&bytes).unwrap_or_else(|e| {
panic!(
"fixture `{}` does not match its schema: {e}",
path.display()
)
})
}
#[derive(Debug, Deserialize)]
pub struct Shape3 {
pub batch: usize,
pub seq: usize,
pub hidden: usize,
}
#[derive(Debug, Deserialize)]
pub struct Shape2 {
pub batch: usize,
pub hidden: usize,
}
#[derive(Debug, Deserialize)]
pub struct ForwardCase {
pub case_id: String,
pub texts: Vec<String>,
pub input_ids_canonical: Vec<Vec<u32>>,
pub attention_mask: Vec<Vec<u8>>,
pub shape: Shape3,
pub embeddings_out: Vec<f32>,
pub layer_outputs: Vec<Vec<f32>>,
pub final_tokens: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct ForwardFixture {
pub cases: Vec<ForwardCase>,
}
#[derive(Debug, Deserialize)]
pub struct PoolingCase {
pub case_id: String,
pub texts: Vec<String>,
pub input_ids_canonical: Vec<Vec<u32>>,
pub attention_mask: Vec<Vec<u8>>,
pub shape: Shape2,
pub pooled: Vec<f32>,
pub normalized: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct PoolingFixture {
pub cases: Vec<PoolingCase>,
}
#[derive(Debug, Deserialize)]
pub struct ActivationFixture {
pub op: String,
pub approximate: String,
pub tanh_vs_exact_max_delta: f32,
pub x: Vec<f32>,
pub y: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct LossPair {
pub a_case_id: String,
pub a_texts: Vec<String>,
pub a_ids_canonical: Vec<Vec<u32>>,
pub b_case_id: String,
pub b_texts: Vec<String>,
pub b_ids_canonical: Vec<Vec<u32>>,
pub labels: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct LossFixture {
pub pair: LossPair,
pub cosine: Vec<f32>,
pub mse: f32,
}
#[derive(Debug, Deserialize)]
pub struct SourcePointer {
pub fixture: String,
pub a_case_id: String,
pub b_case_id: String,
}
#[derive(Debug, Deserialize)]
pub struct NamedGrad {
pub shape: Vec<usize>,
pub grad: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct AnalyticallyZero {
pub name: String,
pub max_abs_grad: f32,
pub justification: String,
}
#[derive(Debug, Deserialize)]
pub struct GradientsFixture {
pub source: SourcePointer,
pub parameter_order: Vec<String>,
pub zero_grad_floor: f32,
pub analytically_zero: Vec<AnalyticallyZero>,
pub grads: BTreeMap<String, NamedGrad>,
}
impl GradientsFixture {
pub fn exempt_names(&self) -> Vec<String> {
self.analytically_zero
.iter()
.map(|e| e.name.clone())
.collect()
}
}
#[derive(Debug, Deserialize)]
pub struct AdamWSpec {
pub lr: f32,
pub betas: Vec<f32>,
pub eps: f32,
pub weight_decay: f32,
}
#[derive(Debug, Deserialize)]
pub struct OptimizerStepFixture {
pub source: SourcePointer,
pub adamw: AdamWSpec,
pub all_trainable: bool,
pub loss_before: f32,
pub loss_after: f32,
pub post_step: BTreeMap<String, Vec<f32>>,
}
#[derive(Debug, Deserialize)]
pub struct OptimizerMultistepFixture {
pub source: SourcePointer,
pub adamw: AdamWSpec,
pub all_trainable: bool,
pub steps: usize,
pub losses: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct InvarianceSingle {
pub case_id: String,
pub texts: Vec<String>,
pub input_ids_canonical: Vec<Vec<u32>>,
pub embedding: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct InvariancePadded {
pub case_id: String,
pub texts: Vec<String>,
pub input_ids_canonical: Vec<Vec<u32>>,
pub embeddings: Vec<f32>,
pub target_row: usize,
}
#[derive(Debug, Deserialize)]
pub struct InvarianceFixture {
pub single: InvarianceSingle,
pub padded_batch: InvariancePadded,
}
#[derive(Debug, Deserialize)]
pub struct FullModelFixture {
pub case_id: String,
pub texts: Vec<String>,
pub shape: Shape2,
pub embeddings: Vec<f32>,
}
#[derive(Debug, Deserialize)]
pub struct TokenizerCase {
pub id: String,
pub texts: Vec<String>,
pub input_ids: Vec<Vec<u32>>,
pub attention_mask: Vec<Vec<u8>>,
}
#[derive(Debug, Deserialize)]
pub struct TokenizerCases {
pub cases: Vec<TokenizerCase>,
}
impl TokenizerCases {
pub fn get(&self, id: &str) -> &TokenizerCase {
self.cases
.iter()
.find(|c| c.id == id)
.unwrap_or_else(|| panic!("case_id `{id}` does not resolve in tokenizer_cases.json"))
}
}
pub fn assert_close(actual: &[f32], expected: &[f32], tol: f32, what: &str) {
assert_eq!(
actual.len(),
expected.len(),
"{what}: length {} != fixture length {}",
actual.len(),
expected.len()
);
let mut worst = 0.0f32;
let mut worst_at = usize::MAX;
for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
assert!(a.is_finite(), "{what}: element {i} is {a}, not finite");
let d = (a - e).abs();
if d > worst {
worst = d;
worst_at = i;
}
}
assert!(
worst <= tol,
"{what}: max |rust - fixture| = {worst:e} at index {worst_at} \
(rust {}, fixture {}), tolerance {tol:e}",
actual[worst_at],
expected[worst_at]
);
}
pub fn all_within(actual: &[f32], expected: &[f32], tol: f32) -> bool {
actual.len() == expected.len()
&& actual
.iter()
.zip(expected.iter())
.all(|(a, e)| (a - e).abs() <= tol)
}
pub fn l2(v: &[f32]) -> f32 {
v.iter()
.map(|x| f64::from(*x) * f64::from(*x))
.sum::<f64>()
.sqrt() as f32
}
pub fn max_abs(v: &[f32]) -> f32 {
v.iter().fold(0.0f32, |m, x| m.max(x.abs()))
}
pub type NamedGrads = Vec<(String, Option<Vec<f32>>)>;
pub struct GateInput<'a> {
pub grads: &'a NamedGrads,
pub deltas: Option<&'a [(String, f32)]>,
pub step_lr: Option<f32>,
pub exemptions: &'a [String],
pub floor: f32,
pub layers: usize,
}
pub fn component_of(name: &str, layers: usize) -> Option<String> {
if FreezeGroup::Embeddings.matches(name) {
return Some("embeddings".to_string());
}
for n in 0..layers {
if FreezeGroup::LayerAttention(n).matches(name) {
return Some(format!("layer{n}.attention"));
}
if FreezeGroup::LayerFfn(n).matches(name) {
return Some(format!("layer{n}.ffn"));
}
if FreezeGroup::LayerNorm(n).matches(name) {
return Some(format!("layer{n}.norm"));
}
}
None
}
pub fn assert_encoder_updates(input: &GateInput) -> Result<(), String> {
let mut problems: Vec<String> = Vec::new();
let mut missing: Vec<&str> = Vec::new();
for (name, g) in input.grads {
match g {
None => missing.push(name),
Some(values) => {
if let Some(i) = values.iter().position(|v| !v.is_finite()) {
problems.push(format!(
"(a) `{name}` gradient element {i} is {} — not finite",
values[i]
));
}
}
}
}
if !missing.is_empty() {
problems.push(format!(
"(a) {} parameter(s) received NO gradient: {}",
missing.len(),
missing.join(", ")
));
}
let mut aggregates: BTreeMap<String, f64> = BTreeMap::new();
let mut uncovered: Vec<&str> = Vec::new();
for (name, g) in input.grads {
match component_of(name, input.layers) {
Some(c) => {
let sq: f64 = g.as_ref().map_or(0.0, |v| {
v.iter().map(|x| f64::from(*x) * f64::from(*x)).sum()
});
*aggregates.entry(c).or_insert(0.0) += sq;
}
None => uncovered.push(name),
}
}
if !uncovered.is_empty() {
problems.push(format!(
"the ENC-04 component partition does not cover: {} — the component mapping has \
drifted from the parameter names",
uncovered.join(", ")
));
}
for (component, sq) in &aggregates {
if sq.sqrt() <= 0.0 {
problems.push(format!(
"(b) component `{component}` aggregate gradient L2 is {} — a severed graph \
anywhere in the body drives some component aggregate to exactly zero",
sq.sqrt()
));
}
}
for (name, g) in input.grads {
let exempt = input.exemptions.iter().any(|e| e == name);
let Some(values) = g else {
continue; };
if exempt {
let m = max_abs(values);
if m > input.floor {
problems.push(format!(
"(e) `{name}` is on the analytically-zero list but max|g| = {m:e} > \
zero_grad_floor {:e}",
input.floor
));
}
} else if l2(values) <= 0.0 {
problems.push(format!(
"(c) `{name}` has a zero gradient and is NOT on the analytically-zero list"
));
}
}
if let Some(deltas) = input.deltas {
let mut moved: BTreeMap<String, f64> = BTreeMap::new();
for (name, d) in deltas {
if let Some(c) = component_of(name, input.layers) {
*moved.entry(c).or_insert(0.0) += f64::from(*d);
}
if input.exemptions.iter().any(|e| e == name) {
continue;
}
if *d <= 0.0 {
problems.push(format!(
"(f) `{name}` did not move across the optimizer step and is not exempt"
));
continue;
}
if let Some(lr) = input.step_lr {
let ratio = *d / lr;
if !(0.9..=1.1).contains(&ratio) {
problems.push(format!(
"(f) `{name}` moved {d:e}, which is {ratio:.4}x lr ({lr:e}); a step-1 \
AdamW update saturates to lr*sign(g) for any tensor carrying a real \
gradient, so this is outside [0.9, 1.1]x"
));
}
}
}
for (component, total) in &moved {
if *total <= 0.0 {
problems.push(format!(
"(f) component `{component}` aggregate post-step delta is {total}"
));
}
}
}
if problems.is_empty() {
Ok(())
} else {
Err(format!(
"ENC-04 gate FAILED with {} finding(s):\n {}",
problems.len(),
problems.join("\n ")
))
}
}
pub fn trainable_grads(model: &mut SetFitMiniLm) -> NamedGrads {
model
.trainable_parameters_mut()
.into_iter()
.map(|(name, t)| (name, get_grad(t.id()).map(|g| g.data().to_vec())))
.collect()
}
pub fn snapshot(model: &SetFitMiniLm) -> Vec<(String, Vec<u32>)> {
model
.encoder()
.named_parameters()
.into_iter()
.map(|(n, t)| (n, t.data().iter().map(|v| v.to_bits()).collect()))
.collect()
}
pub struct PairBatch {
pub a: SentenceBatch,
pub b: SentenceBatch,
pub labels: Vec<f32>,
}
pub fn pair_batch(model: &SetFitMiniLm, source: &SourcePointer) -> PairBatch {
let loss: LossFixture = read_fixture("loss_pair.json");
assert_eq!(
source.fixture, "loss_pair.json",
"the derived fixture points at `{}`, not loss_pair.json — a regeneration moved the \
pair batch and this gate would be comparing against the wrong reference",
source.fixture
);
assert_eq!(source.a_case_id, loss.pair.a_case_id);
assert_eq!(source.b_case_id, loss.pair.b_case_id);
PairBatch {
a: batch_from_case(model, &loss.pair.a_texts).expect("tokenize a"),
b: batch_from_case(model, &loss.pair.b_texts).expect("tokenize b"),
labels: loss.pair.labels,
}
}
fn harness_sources() -> Vec<(PathBuf, String)> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests");
let mut files = vec![root.join("setfit_conformance.rs")];
let dir = root.join("setfit_conformance");
let entries = std::fs::read_dir(&dir).expect("tests/setfit_conformance/ must exist");
let mut sub: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "rs"))
.collect();
sub.sort();
files.extend(sub);
files
.into_iter()
.map(|p| {
let text = std::fs::read_to_string(&p)
.unwrap_or_else(|e| panic!("{} unreadable: {e}", p.display()));
(p, text)
})
.collect()
}
fn code_of(line: &str) -> &str {
line.split("//").next().unwrap_or("")
}
fn needle(head: &str, tail: &str) -> String {
format!("{head}{tail}")
}
#[test]
fn conformance_the_tokenizer_boundary_is_the_only_batch_source() {
let tokenize_call = needle(".token", "ize(");
let batch_literal = needle("SentenceBatch ", "{");
let mut tokenize_sites: Vec<String> = Vec::new();
let mut literal_sites: Vec<String> = Vec::new();
for (path, text) in harness_sources() {
let mut current_fn = String::new();
for (n, line) in text.lines().enumerate() {
let code = code_of(line);
if let Some(rest) = code.trim_start().strip_prefix("pub fn ") {
current_fn = rest.split('(').next().unwrap_or("").to_string();
} else if let Some(rest) = code.trim_start().strip_prefix("fn ") {
current_fn = rest.split('(').next().unwrap_or("").to_string();
}
if code.contains(&tokenize_call) {
tokenize_sites.push(format!(
"{}:{} (in fn `{current_fn}`)",
path.display(),
n + 1
));
}
if code.contains(&batch_literal) {
literal_sites.push(format!("{}:{}", path.display(), n + 1));
}
}
}
assert!(
literal_sites.is_empty(),
"a batch literal was constructed outside the tokenizer boundary at: {literal_sites:?}"
);
assert_eq!(
tokenize_sites.len(),
1,
"the tokenizer must be called exactly once in this harness, inside `batch_from_case`; \
found {tokenize_sites:?}"
);
assert!(
tokenize_sites[0].contains("batch_from_case"),
"the single tokenize call is not inside `batch_from_case`: {}",
tokenize_sites[0]
);
}
#[test]
fn conformance_no_sealed_constructor_is_reached_from_this_harness() {
let forbidden = [
needle("MiniLmImport", "::"),
needle("MiniLmTokenizer::", "from_bytes"),
needle("BertSentenceEncoder::", "from_import"),
];
let mut hits: Vec<String> = Vec::new();
for (path, text) in harness_sources() {
for (n, line) in text.lines().enumerate() {
let code = code_of(line);
for f in &forbidden {
if code.contains(f.as_str()) {
hits.push(format!("{}:{} -> {f}", path.display(), n + 1));
}
}
}
}
assert!(
hits.is_empty(),
"sealed construction path reached: {hits:?}"
);
}
#[test]
fn conformance_the_harness_never_reads_a_pre_remapped_id_array() {
let forbidden = [
needle("input_ids_", "slice"),
needle("a_ids_", "slice"),
needle("b_ids_", "slice"),
];
let mut hits: Vec<String> = Vec::new();
for (path, text) in harness_sources() {
for (n, line) in text.lines().enumerate() {
let code = code_of(line);
for f in &forbidden {
if code.contains(f.as_str()) {
hits.push(format!("{}:{} -> {f}", path.display(), n + 1));
}
}
}
}
assert!(
hits.is_empty(),
"a pre-remapped slice-id array is referenced in harness CODE: {hits:?}"
);
}
#[test]
fn conformance_no_hand_written_tolerance_literal_outside_the_generated_file() {
let e = needle("e", "-");
let table: [(String, bool); 8] = [
(format!(" let tol = 1.5{e}5;"), true),
(format!(" assert!(d <= 7.62939453{e}06);"), true),
(format!(" let eps = 1{e}12;"), true),
(" let labels = [1.0, 0.0];".to_string(), false),
(" assert_eq!(shape.hidden, 64);".to_string(), false),
(format!(" // measured 4.73{e}04 in 01-04"), false),
(" let n = counts[0] - 1;".to_string(), false),
(" let scaled = x * 2.0;".to_string(), false),
];
for (row, want) in &table {
assert_eq!(
has_negative_exponent_literal(code_of(row)),
*want,
"case table row misclassified: {row:?}"
);
}
let mut hits: Vec<String> = Vec::new();
for (path, text) in harness_sources() {
if path.ends_with("tolerances_generated.rs") {
continue;
}
for (n, line) in text.lines().enumerate() {
if has_negative_exponent_literal(code_of(line)) {
hits.push(format!("{}:{} -> {}", path.display(), n + 1, line.trim()));
}
}
}
assert!(
hits.is_empty(),
"hand-written tolerance literal(s) outside tolerances_generated.rs: {hits:?}"
);
}
fn has_negative_exponent_literal(code: &str) -> bool {
let b: Vec<char> = code.chars().collect();
for i in 0..b.len() {
if b[i] != 'e' && b[i] != 'E' {
continue;
}
let before = i > 0 && b[i - 1].is_ascii_digit();
let after = i + 2 < b.len() && b[i + 1] == '-' && b[i + 2].is_ascii_digit();
if before && after {
return true;
}
}
false
}
fn tolerance_bindings() -> Vec<(&'static str, f32, &'static str)> {
vec![
(
"FORWARD_PER_LAYER",
tol::FORWARD_PER_LAYER,
"OBLIG-ENC-03-PER-LAYER-FORWARD-PARITY",
),
(
"POOLING_NORMALIZE",
tol::POOLING_NORMALIZE,
"OBLIG-ENC-03-POOLED-EMBEDDING-PARITY",
),
(
"ACTIVATION",
tol::ACTIVATION,
"OBLIG-ENC-03-ACTIVATION-PARITY",
),
(
"BATCH_INVARIANCE",
tol::BATCH_INVARIANCE,
"OBLIG-ENC-03-PADDING-INVARIANCE",
),
(
"GRADIENTS",
tol::GRADIENTS,
"OBLIG-ENC-04-NAMED-GRADIENT-PARITY",
),
(
"ZERO_GRAD_FLOOR",
tol::ZERO_GRAD_FLOOR,
"OBLIG-ENC-04-GRADIENT-AND-STEP-GATE",
),
(
"LOSS_PAIR",
tol::LOSS_PAIR,
"OBLIG-ENC-06-LOSS-FORWARD-PARITY",
),
(
"OPTIMIZER_STEP",
tol::OPTIMIZER_STEP,
"OBLIG-ENC-04-POST-STEP-PARAMETER-PARITY",
),
(
"OPTIMIZER_MULTISTEP",
tol::OPTIMIZER_MULTISTEP,
"OBLIG-ENC-04-MULTISTEP-TRAJECTORY-PARITY",
),
(
"FULL_MODEL_REFERENCE",
tol::FULL_MODEL_REFERENCE,
"OBLIG-ENC-01-FULL-MODEL-REFERENCE-PARITY",
),
]
}
fn contract_tolerances(path: &Path) -> BTreeMap<String, f64> {
let contract = provable_contracts::schema::parse_contract(path)
.unwrap_or_else(|e| panic!("{} failed to parse: {e}", path.display()));
let mut out = BTreeMap::new();
for o in &contract.proof_obligations {
let Some(t) = o.tolerance else { continue };
if let Some(id) = o.property.split_whitespace().next() {
out.insert(id.trim_end_matches(':').to_string(), t);
}
}
out
}
fn contract_version(path: &Path) -> String {
provable_contracts::schema::parse_contract(path)
.unwrap_or_else(|e| panic!("{} failed to parse: {e}", path.display()))
.metadata
.version
}
pub fn sha256_file(path: &Path) -> String {
let bytes =
std::fs::read(path).unwrap_or_else(|e| panic!("{} unreadable: {e}", path.display()));
let mut h = Sha256::new();
h.update(&bytes);
format!("{:x}", h.finalize())
}
#[test]
fn conformance_tolerances_agree_with_the_contract() {
let Some(path) = contract_path() else {
assert_eq!(
tol::CONTRACT_SHA256.len(),
64,
"CONTRACT_SHA256 is not a 64-character digest"
);
assert!(
tol::CONTRACT_SHA256
.chars()
.all(|c: char| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"CONTRACT_SHA256 is not lowercase hex"
);
return;
};
let digest = sha256_file(&path);
assert_eq!(
digest,
tol::CONTRACT_SHA256,
"the contract has changed since tolerances_generated.rs was generated. \
Regenerate it: {}",
tol::REGENERATE_COMMAND
);
let from_contract = contract_tolerances(&path);
for (name, generated, obligation) in tolerance_bindings() {
let want = from_contract.get(obligation).unwrap_or_else(|| {
panic!(
"obligation `{obligation}` carries no tolerance in {}",
path.display()
)
});
#[allow(clippy::cast_possible_truncation)]
let want32 = *want as f32;
assert!(
generated.to_bits() == want32.to_bits(),
"tolerance drift: {name} is {generated:e} in tolerances_generated.rs but \
{want32:e} in {obligation}. The generated file is DERIVED — regenerate it \
({}), never hand-edit it.",
tol::REGENERATE_COMMAND
);
}
assert_eq!(
tol::CONTRACT_VERSION,
contract_version(&path),
"generated file records a stale contract version"
);
}
#[test]
#[ignore = "generator: writes tolerances_generated.rs from the contract"]
fn conformance_tolerances_regenerate() {
if std::env::var("APRENDER_REGEN_TOLERANCES").is_err() {
conformance_tolerances_agree_with_the_contract();
return;
}
let path = contract_path().expect("regeneration requires a workspace checkout");
let from_contract = contract_tolerances(&path);
let digest = sha256_file(&path);
let version = contract_version(&path);
let mut out = String::new();
out.push_str("//! GENERATED FILE — DO NOT HAND-EDIT.\n//!\n");
out.push_str("//! Tolerance constants for the SetFit conformance harness (plan 01-08).\n//!\n");
out.push_str("//! Source contract: contracts/setfit-encoder-conformance-v1.yaml\n");
out.push_str(&format!("//! Contract metadata.version: {version}\n"));
out.push_str(&format!("//! Contract sha256: {digest}\n//!\n"));
out.push_str("//! Regenerate with:\n//!\n");
out.push_str("//! ```text\n");
out.push_str("//! APRENDER_REGEN_TOLERANCES=1 cargo test -p aprender-core \\\n");
out.push_str("//! --features setfit,conformance-fixtures --test setfit_conformance \\\n");
out.push_str("//! conformance_tolerances_regenerate -- --ignored\n");
out.push_str("//! ```\n//!\n");
out.push_str("//! The emitter is rustfmt-stable; if a future edit breaks that, run\n");
out.push_str("//! `cargo fmt -p aprender-core` after regenerating, so a drift check on\n");
out.push_str("//! this file reports semantics rather than whitespace (D7).\n//!\n");
out.push_str("//! D-14: these numbers exist in ONE place, the versioned contract. Widening\n");
out.push_str("//! one requires a contract edit `pv diff` flags with a semver bump. The\n");
out.push_str(
"//! agreement test in tests/setfit_conformance.rs fails the build if this file\n",
);
out.push_str("//! and the contract ever disagree in a workspace checkout.\n\n");
for (name, _, obligation) in tolerance_bindings() {
let v = from_contract
.get(obligation)
.unwrap_or_else(|| panic!("obligation `{obligation}` carries no tolerance"));
out.push_str(&format!("/// From `{obligation}`.\n"));
out.push_str(&format!("pub const {name}: f32 = {v:.8e};\n"));
}
out.push_str("\n/// sha256 of the source contract at generation time.\n");
out.push_str(&format!(
"pub const CONTRACT_SHA256: &str =\n \"{digest}\";\n"
));
out.push_str("\n/// `metadata.version` of the source contract at generation time.\n");
out.push_str(&format!(
"pub const CONTRACT_VERSION: &str = \"{version}\";\n"
));
out.push_str("\n/// The command that regenerates this file.\n");
out.push_str(
"pub const REGENERATE_COMMAND: &str = \"APRENDER_REGEN_TOLERANCES=1 cargo test \
-p aprender-core --features setfit,conformance-fixtures --test setfit_conformance \
conformance_tolerances_regenerate -- --ignored\";\n",
);
let dest = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/setfit_conformance/tolerances_generated.rs");
std::fs::write(&dest, out).expect("write generated tolerances");
}
#[test]
fn conformance_manifest_self_check() {
let dir = fixtures_dir();
let manifest = std::fs::read_to_string(dir.join("manifest.sha256"))
.expect("manifest.sha256 must be present");
let mut checked = 0usize;
for line in manifest.lines() {
let mut parts = line.split_whitespace();
let (Some(want), Some(name)) = (parts.next(), parts.next()) else {
continue;
};
let got = sha256_file(&dir.join(name));
assert_eq!(
got, want,
"fixture `{name}` has been modified since 01-04 froze it"
);
checked += 1;
}
assert!(checked >= 15, "manifest covered only {checked} files");
}
fn assert_case_join(
model: &SetFitMiniLm,
cases: &TokenizerCases,
case_id: &str,
texts: &[String],
canonical: &[Vec<u32>],
) {
let recorded = cases.get(case_id);
assert_eq!(
recorded.texts, texts,
"`{case_id}`: the fixture's texts differ from tokenizer_cases.json"
);
assert_eq!(
recorded.input_ids, canonical,
"`{case_id}`: tokenizer_cases.json ids differ from the fixture's canonical ids"
);
let batch = batch_from_case(model, texts).expect("tokenize");
let flat: Vec<u32> = canonical.iter().flatten().copied().collect();
assert_eq!(
batch.input_ids(),
flat.as_slice(),
"`{case_id}`: the Rust tokenizer disagrees with the frozen ids"
);
}
#[test]
fn conformance_every_fixture_case_id_joins_the_corpus_of_record() {
let model = slice_model();
let cases: TokenizerCases = read_fixture("tokenizer_cases.json");
let forward: ForwardFixture = read_fixture("forward_per_layer.json");
for c in &forward.cases {
assert_case_join(&model, &cases, &c.case_id, &c.texts, &c.input_ids_canonical);
}
let pooling: PoolingFixture = read_fixture("pooling_normalize.json");
for c in &pooling.cases {
assert_case_join(&model, &cases, &c.case_id, &c.texts, &c.input_ids_canonical);
}
let loss: LossFixture = read_fixture("loss_pair.json");
assert_case_join(
&model,
&cases,
&loss.pair.a_case_id,
&loss.pair.a_texts,
&loss.pair.a_ids_canonical,
);
assert_case_join(
&model,
&cases,
&loss.pair.b_case_id,
&loss.pair.b_texts,
&loss.pair.b_ids_canonical,
);
let inv: InvarianceFixture = read_fixture("batch_invariance.json");
assert_case_join(
&model,
&cases,
&inv.single.case_id,
&inv.single.texts,
&inv.single.input_ids_canonical,
);
assert_case_join(
&model,
&cases,
&inv.padded_batch.case_id,
&inv.padded_batch.texts,
&inv.padded_batch.input_ids_canonical,
);
}
#[test]
fn conformance_the_slice_batch_carries_canonical_ids_the_encoder_must_remap() {
let model = slice_model();
let loss: LossFixture = read_fixture("loss_pair.json");
let batch = batch_from_case(&model, &loss.pair.a_texts).expect("tokenize");
let above = batch.input_ids().iter().filter(|id| **id >= 97).count();
assert!(
above > 0,
"no canonical id in the pair batch exceeds the slice vocabulary, so the remap \
inside the encoder is never exercised by these gates"
);
}
pub fn encode(model: &SetFitMiniLm, batch: &SentenceBatch) -> Tensor {
model.encoder().encode(batch).expect("encode")
}