Skip to content

Commit 18a11fb

Browse files
test: pin real predictor fixture (#198)
1 parent 21fc503 commit 18a11fb

3 files changed

Lines changed: 105 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# pwm-testkit fixtures
2+
3+
## `lewm_predictor_real_compact.json`
4+
5+
Compact real-checkpoint predictor regression fixture for issue #178.
6+
7+
- Checkpoint: `quentinll/lewm-pusht` `weights.pt`, revision
8+
`22b330c28c27ead4bfd1888615af1340e3fe9052`, SHA-256
9+
`48938400ae3464c9680731287f583a9cb516f55a8ec64ea13a91be47fb15b607`,
10+
MIT license.
11+
- Input episode: `lerobot/pusht`, revision
12+
`7628202a2180972f291ba1bc6723834921e72c19`, episode `0`, frames
13+
`[0, 5, 10]`. The parquet SHA-256 is
14+
`9abc0431f12d10c33b5b37b1bf1e6e557c8ae1c8c29fb86658969e48fcbbbf01`;
15+
the mp4 SHA-256 is
16+
`f58d11857651dad5983c021b56a50a68a6ce19068834c1fb5cac099219fb3a78`.
17+
- Input encoding: the three frames are encoded with the checkpoint ViT encoder
18+
and projector. The matching real 2D action and agent-state rows are normalized
19+
with the repository PushT adapter rule and encoded with the checkpoint action
20+
encoder. The fixture keeps the first 8 latent and action coordinates.
21+
- Predictor slice: compact dimensions are `d=8`, `s=3`, `h=2`, `dh=4`,
22+
`mlp=16`, `depth=2`. Every compact weight is a row and column slice of the
23+
real checkpoint tensor, preserving Q/K/V head bands and AdaLN chunk structure.
24+
- Quantization: generated with `pwm_export.predictor_quant.bundle_quant`, using
25+
calibrated SiLU, GELU, inverse-sqrt, and softmax-exp tables. The
26+
`weights_root` in the JSON is the Rust predictor root for tensor ids `1000+`
27+
in registration order `adaln,qkv,out,fc1,fc2` with scale ids `10+`.
28+
29+
The fixture is intentionally small enough for default `cargo test --workspace`.
30+
It does not contain the full checkpoint, raw video, or raw parquet data.

crates/pwm-testkit/fixtures/lewm_predictor_real_compact.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

crates/pwm-testkit/src/lewm_predictor.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,8 +875,14 @@ mod tests {
875875
use super::*;
876876
use pwm_core::audit::output_tensor;
877877
use pwm_core::commit::weights_root;
878+
use pwm_core::serialize::canonical_bytes;
879+
use pwm_core::transcript::blake2s256;
878880
use pwm_verifier::CommitmentKind;
879881

882+
const REAL_COMPACT_FIXTURE: &str = include_str!("../fixtures/lewm_predictor_real_compact.json");
883+
const REAL_COMPACT_OUTPUT_DIGEST: &str =
884+
"6b26564e0dd461ac784993cab5aaf4ab72ebb0ce6fe81dd9b16f6768e1f4a653";
885+
880886
fn small() -> Dims {
881887
Dims {
882888
d: 8,
@@ -888,6 +894,40 @@ mod tests {
888894
}
889895
}
890896

897+
fn hex(bytes: &[u8; 32]) -> String {
898+
bytes.iter().map(|b| format!("{b:02x}")).collect()
899+
}
900+
901+
fn real_compact_circuit() -> PredictorCircuit {
902+
let loaded =
903+
load_real_predictor(REAL_COMPACT_FIXTURE).expect("load compact real predictor fixture");
904+
assert_eq!(loaded.dims.d, 8);
905+
assert_eq!(loaded.dims.s, 3);
906+
assert_eq!(loaded.dims.h, 2);
907+
assert_eq!(loaded.dims.dh, 4);
908+
assert_eq!(loaded.dims.mlp, 16);
909+
assert_eq!(loaded.dims.depth, 2);
910+
assert!(loaded.input_source.contains("lerobot/pusht"));
911+
assert!(loaded.weights_root.is_some());
912+
913+
let weights_root = loaded.weights_root;
914+
let z_float = loaded.quant.z_out_float.clone();
915+
let tolerance = loaded.quant.tolerance;
916+
let mut circuit = build_predictor_real(
917+
loaded.dims,
918+
loaded.blocks,
919+
loaded.x,
920+
loaded.c,
921+
loaded.quant.scheme,
922+
loaded.quant.tables,
923+
)
924+
.expect("build compact real predictor");
925+
circuit.export_weights_root = weights_root;
926+
circuit.z_float = Some(z_float);
927+
circuit.tolerance = Some(tolerance);
928+
circuit
929+
}
930+
891931
fn run(d: Dims) -> Vec<i64> {
892932
let c = build_predictor(d);
893933
let artifact = prove(&c).expect("prove");
@@ -970,6 +1010,40 @@ mod tests {
9701010
));
9711011
}
9721012

1013+
#[test]
1014+
fn real_checkpoint_compact_fixture_proves_verifies_and_matches_digest() {
1015+
let c = real_compact_circuit();
1016+
let f_out = c.scheme.f_ln;
1017+
let z_float = c.z_float.clone().expect("fixture float reference");
1018+
let tolerance = c.tolerance.expect("fixture tolerance");
1019+
let artifact = prove(&c).expect("prove compact real predictor");
1020+
let out = verify(&artifact).expect("verify compact real predictor");
1021+
1022+
let digest = hex(&blake2s256(&canonical_bytes(&artifact.claimed_output)));
1023+
assert_eq!(
1024+
digest, REAL_COMPACT_OUTPUT_DIGEST,
1025+
"compact real predictor output digest changed"
1026+
);
1027+
let err = max_float_error(&out, f_out, &z_float);
1028+
assert!(
1029+
err <= tolerance,
1030+
"compact real predictor float error {err} exceeds {tolerance}"
1031+
);
1032+
}
1033+
1034+
#[test]
1035+
fn real_checkpoint_compact_fixture_tamper_is_rejected() {
1036+
let c = real_compact_circuit();
1037+
let mut artifact = prove(&c).expect("prove compact real predictor");
1038+
let forged_op = tamper(&mut artifact).expect("a linear op to tamper");
1039+
match verify(&artifact) {
1040+
Err(VerifyError::FreivaldsCheckFailed { op_id })
1041+
| Err(VerifyError::AccumulatorRange { op_id })
1042+
if op_id == forged_op => {}
1043+
other => panic!("real predictor tamper was not rejected as expected: {other:?}"),
1044+
}
1045+
}
1046+
9731047
#[test]
9741048
fn swapped_weight_is_a_model_commitment_mismatch() {
9751049
let c = build_predictor(small());

0 commit comments

Comments
 (0)