mixer x4: tests/mixer.rs (200-program v3 fuzz with the Metal pack writer, stats beside v2, dataset edges at every multiplier, determinism against the pinned pack); scratch.rs edge literal gains era_bytes; mixer-x4.md: the v3 vector tables, the v2 diff, the Metal and Apple OpenCL bit-exactness rows, the soundness table so far
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a99fa6375f
commit
36ef07fda2
3 changed files with 339 additions and 3 deletions
|
|
@ -125,8 +125,57 @@ node agent).
|
|||
|
||||
## 4. Vectors (class v3, `proto-cuda/packs-ca2-mixer/`)
|
||||
|
||||
Filled in section 6 from the exported packs: `mx4-genesis` (seed `igneum-genesis`, day `2026-10-03`, 2^28 words,
|
||||
2^26-word cache) and `mx4-devnet-epoch0` (the devnet genesis hash as the epoch seed, day bytes of 2026-10-04).
|
||||
Produced by `igneum-pow export --program-class v3` (the Rust CPU interpreter, 5 October 2026, commit 66eeba3) and
|
||||
checked on the GPUs in section 6. The program of each pack is the version 2 program of the same seed instruction
|
||||
for instruction (`tests/packs.rs`, `v3_packs_are_the_v2_seeds_under_mixer_x4`); the cache is the version 2 cache
|
||||
(day 0 of the growth rule); the dataset words and the hashes are new. The 64 sampled indices are those of every
|
||||
pack (`emit::sample_indices`).
|
||||
|
||||
Pack `mx4-genesis` (seed igneum-genesis, day 2026-10-03, generator 3, class mx4, program id e323b9dcaf283a6f, 2^28 words, 2^26-word cache, cache FNV-1a 64 `48c4f5bf24166b2e` as under v2):
|
||||
|
||||
```
|
||||
dataset words 0..15 (item 0):
|
||||
61ff2180 0d4c7e6c 2177d443 60df9025 cf8b2e10 63675bfb 25289e58 9c45dc42
|
||||
2d271c54 9652369b 2dd77508 5921392c 3afa60ee c640ad68 f2bb56ff cfa46438
|
||||
dataset[0x0fffffff] = 5020180e
|
||||
sampled words (the first 8 of the 64 in vectors.json):
|
||||
dataset[59471966] = de85726d dataset[217795994] = 7cfc31c7 dataset[208353206] = d3cd5289 dataset[42483309] = 6ebbeef1
|
||||
dataset[172547758] = 9858413b dataset[148076330] = e786f141 dataset[183853158] = 64f13833 dataset[214389424] = ca229d04
|
||||
unit at base nonce 0, lanes 0..31:
|
||||
63acd2d273f475ba e929c78b34b80d4b 0b1011cb19982558 1457a0df5497aa11
|
||||
957d0f3bb71d98fb ac16901e6e6f6057 8ea1c6279f4b177a f28146e60bd08ba9
|
||||
fe5b8cfe87f8e65b 49f87240566ace62 6ef6d6b7bdea8e41 46d9c0dc29a97b9c
|
||||
111fe30128db9398 66dc39084f0946d4 8ee11bdfd35fecf2 2861fcfc75db6677
|
||||
31c7667d4bde8556 c5989c48858b4ce0 276395e734a9d30d 84217b41e91368ff
|
||||
3604861e34d9f697 9f51d8ee16bf3639 c89e47bafa84401c 7ae78c1f10b70e19
|
||||
0b8c947157a29a48 d67192e8cfb43842 05a4c6d182c8c675 188e2661f3263f2e
|
||||
a2df24238f7fea2e ed69ea7e13ad3a48 2d44ae509bab91b8 adad61931ea4fb70
|
||||
unit at base nonce 4096: lane 0 edd508ac57e5699a, lane 1 4eefd56d526cdaeb, lane 31 8892f8604733b1e0
|
||||
unit at base nonce 1000000: lane 0 8b3183778a49f59c, lane 1 1831b72a8797e895, lane 31 75eae55eba53a506
|
||||
```
|
||||
|
||||
Pack `mx4-devnet-epoch0` (seed the devnet genesis hash, day bytes of 2026-10-04, generator 3, class mx4, program id 73bcbfe8ccf988f1, 2^28 words, 2^26-word cache, cache FNV-1a 64 `448274a57f508cbc` as under v2):
|
||||
|
||||
```
|
||||
dataset words 0..15 (item 0):
|
||||
afe80d67 b9fbd029 6c79f193 95139ad9 96310aff 4609f8b1 75279e63 28235be1
|
||||
47b17dcb 718e0ef2 a52588c8 a8bf49d5 19cf243e 5ec8905e a4851f66 af9cd9f3
|
||||
dataset[0x0fffffff] = e6a99c7a
|
||||
sampled words (the first 8 of the 64 in vectors.json):
|
||||
dataset[59471966] = 57642b58 dataset[217795994] = c279badd dataset[208353206] = cbccbaad dataset[42483309] = 32cce392
|
||||
dataset[172547758] = 71fdb4c6 dataset[148076330] = f5a268ce dataset[183853158] = 3ca1d676 dataset[214389424] = 7977b03d
|
||||
unit at base nonce 0, lanes 0..31:
|
||||
212c6442b51e87ae c374795c00839331 b6036a220a98f4b3 eb8b8013e637367b
|
||||
db5866e9b73930fd f3f3d01f46e90333 9d913991ab8ed428 7ccb1d8fa100a800
|
||||
3cf45ba44f09a0fe 91acf48ef1a63082 6ea46c69fb082f99 581f0218977a9d72
|
||||
9a4623a5c62ddf2d ab6eb5e768f0feb4 07b70bdccf8aca12 d666311ae5e4311e
|
||||
53114757d669f0a4 bd5d6ace87ce2ce4 fb712015e8189192 a32cec81103e134b
|
||||
83f3d18c3289c124 fe29f1984b132b3d c9ffcf4e3774497a ac99c9243dc63809
|
||||
d78a7e8217a32f3c ab81ad63d242fc31 0e4c30b7e00024af ce014289fff6778d
|
||||
64a1292e2a8b4a91 d5b8c90e681d7e3a 06078117673030fd 51bf77b280173930
|
||||
unit at base nonce 4096: lane 0 3c797978566b5950, lane 1 7c759e60185b6411, lane 31 96a903eb9a0ca390
|
||||
unit at base nonce 1000000: lane 0 d5a8da0568df8ee7, lane 1 68cb69c04208285a, lane 31 f8ca84a1a5d78cf5
|
||||
```
|
||||
|
||||
## 5. The v2 path is byte-identical
|
||||
|
||||
|
|
@ -136,7 +185,56 @@ section 6 also records a fresh `igneum-pow export` of both packs diffed against
|
|||
|
||||
## 6. Measurements
|
||||
|
||||
Filled as they land. Every row names the machine, the date, the command and the lock mode.
|
||||
Machine: Apple M5 Max, 64 GiB, Darwin 25.6.0, 5 October 2026 (night), other agents' builds running beside every
|
||||
run; a timing row says which lock it ran under (`measure` is exclusive; `run` and `build` are not timings).
|
||||
|
||||
### 6.1 The v2 path, byte for byte (no lock needed)
|
||||
|
||||
`igneum-pow export --seed igneum-genesis --day 2026-10-03` and `igneum-pow export --epoch-hex edc4fa84...fb07
|
||||
--day-hex 69676e65756d2d6461792ffa50000000000000` on commit 66eeba3, `diff -r` against
|
||||
`proto-cuda/packs/igneum-genesis-mh` and `igneum-devnet-v4-epoch0`: IDENTICAL, both (twelve files each). The
|
||||
crate tests regenerate the same files and compare them on every run (`tests/packs.rs`, 12 of 12 pass).
|
||||
|
||||
### 6.2 Bit-exactness of the class v3 construction on the GPUs (`with-lock.sh run`, 22:05 UTC)
|
||||
|
||||
`packbench --pack <dir> --batches 1 --batch-log2 24 --group 256` (Metal, built from this branch) and
|
||||
`igneum-bench-cl-igneum-genesis-mh --bench-pack --pack <dir> --batches 1 --batch-log2 24` (Apple OpenCL, built from
|
||||
this branch's host.c). Vectors are the Rust interpreter's; the fingerprint is FNV-1a 64 over the 2^24 outputs at
|
||||
base nonce 0.
|
||||
|
||||
| Pack | Harness | Cache FNV-1a 64 | Dataset head, word MASK, 64 samples | Vectors standalone / in batch | Fingerprint 2^24 | MH/s (GPU time; not a measurement, the run lock) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| mx4-genesis | Metal | 48c4f5bf24166b2e PASS | PASS | 3/3, 3/3 | 6f48d5a2aa0dbe5f | 27.6 |
|
||||
| mx4-genesis | Apple OpenCL | PASS | PASS | 96 of 96 lanes | 6f48d5a2aa0dbe5f | 27.7 (wall) |
|
||||
| mx4-devnet-epoch0 | Metal | 448274a57f508cbc PASS | PASS | 3/3, 3/3 | 73caaebb28e808fe | 27.5 |
|
||||
| mx4-devnet-epoch0 | Apple OpenCL | PASS | PASS | 96 of 96 lanes | 73caaebb28e808fe | 27.6 (wall) |
|
||||
|
||||
Reading: the Rust interpreter, Metal and Apple OpenCL agree on the v3 dataset (head, MASK word, 64 samples), on
|
||||
every vector lane and on the 2^24-output fingerprint of each pack; the hash rate is the v2 rate (27.7 MH/s on this
|
||||
card tonight, readwidth table), as it must be: the hash kernel only loads, the mixer is paid in the build.
|
||||
|
||||
Dataset build under the run lock (indicative only; the measured rows are in 6.4): Metal 30.2 ms GPU (mx4-genesis)
|
||||
and 21.7 ms GPU (mx4-devnet-epoch0) for 1 GiB; Apple OpenCL 55 and 56 ms wall. The readwidth entry's v2 figure on
|
||||
this card is 0.6 to 2.1 ms cache fill and a 1 GiB build the bench-log's memory-hard entry puts at 13 to 30 ms; the
|
||||
x4 build on the GPU is the row the measure lock will settle.
|
||||
|
||||
### 6.3 Soundness suite on the v3 construction (commit 66eeba3 and after; `with-lock.sh build` for cargo, `run` for the GPU)
|
||||
|
||||
| Suite | Command | Result |
|
||||
|---|---|---|
|
||||
| Crate lib tests (memhard schedule table, the by-hand multiplied mixer, the seam, the generator) | `cargo test -j4 --release` | 44 of 44 pass |
|
||||
| Pinned packs, v2 and v3 (`tests/packs.rs`: programs, ids, dataset words, 96 vectors per pack, every emitted file byte for byte, 16 masked loads per kernel, the v3 packs as the v2 seeds under mixer x4) | same | 12 of 12 pass |
|
||||
| Scratch soundness tests of branch ca2-soundness (cherry-pick 0d8f745, one conflict in packbench.swift's RESULT line resolved by hand, `era_bytes: None` added to the edge program literal) | `cargo test -j4 --release --test scratch` | 7 of 7 pass: bijections, re-hit rates, 56 of 56 edge units, 42 of 42 emitted scr kernels, 200 scratch programs and 800 units on the CPU |
|
||||
| v3 fuzz on the CPU (`tests/mixer.rs`): 200 programs through the seam, the contract on every instruction (each is the v2 program of its seed), 4 units each across the 32-bit range with one unit in the top 256 nonces, interpreted twice | `IGNEUM_MIXER_PACKS_OUT=<dir> cargo test -j4 --release --test mixer` | 200 of 200, 800 of 800 units; 200 packs written for the GPU runs (82 s with the three cache fills) |
|
||||
| v3 stats beside v2 (`tests/mixer.rs`): 8,192 outputs per seed, bit balance, single-bit avalanche within the unit and across units, duplicates | same | igneum-genesis v3: avalanche 49.99 percent, worst bit z 1.92, 0 duplicates (v2: 49.87, z 2.25); igneum-genesis/stats1 v3: 49.97, z 3.09 (v2: 49.98, z 2.30) |
|
||||
| v3 edge (`tests/mixer.rs`): items 0, 1, 2^28 - 1 and 2^32 - 1 by hand at m = 1, 2, 4, 8 on a 2^14-word cache; words 0, 15, 16, 17, MASK - 1, MASK through the interpreter's fetch path; the index wrap at MASK + 1 | same | pass |
|
||||
| v3 determinism (`tests/mixer.rs`): two independent epochs, every vector and every emitted file equal, and equal to the pinned pack | same | pass |
|
||||
| Metal and Apple OpenCL on the two pinned v3 packs | section 6.2 | 3/3 standalone, 3/3 in batch, 96 of 96 lanes, dataset head, MASK word and 64 samples, one fingerprint per pack across both harnesses |
|
||||
| Metal fuzz: the 200 packs, 4 units each standalone and the top-256 unit inside a 512-nonce batch at base 4,294,967,040; every tenth pack on Apple OpenCL as well | `packbench --pack <dir> --batches 1 --batch-log2 9 --batch-base 4294967040` | see below |
|
||||
|
||||
### 6.4 Timings (`with-lock.sh measure`)
|
||||
|
||||
Owed until the measure lock frees.
|
||||
|
||||
## 7. The chip model
|
||||
|
||||
|
|
|
|||
237
igneum-pow/tests/mixer.rs
Normal file
237
igneum-pow/tests/mixer.rs
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
//! The class v3 dataset construction (mixer x4, cache growth option C; `docs/plans/mixer-x4.md`): the soundness
|
||||
//! runs the brief asks for, on the CPU, with the packs for the GPU runs written on request.
|
||||
//!
|
||||
//! 1. Fuzz: `IGNEUM_MIXER_FUZZ` (default 200) programs through the seam (`ProgramClass::V3`), the contract on every
|
||||
//! instruction (the v2 program of the seed, instruction for instruction), 4 units each across the 32-bit range
|
||||
//! including the wrap, interpreted twice on the CPU; with `IGNEUM_MIXER_PACKS_OUT=<dir>` every program is written
|
||||
//! as a pack with its 4 bases in vectors.json for `packbench` and the OpenCL host (the Metal fuzz).
|
||||
//! 2. Stats: bit balance and single-bit-flip avalanche of the v3 hash against v2 on the same programs and nonces.
|
||||
//! 3. Edge: the dataset at word 0, word MASK and the item boundary, derived through the interpreter's fetch path and
|
||||
//! by hand at every multiplier 1, 2, 4, 8, on a small cache.
|
||||
//! 4. Determinism: two independent epochs of the same seed and day agree on every vector and every emitted file.
|
||||
|
||||
use igneum_pow::emit::{export_pack, vectors_json};
|
||||
use igneum_pow::generator::{generate_from_seed_bytes, generate_from_seed_bytes_program_class, Op, Program, ProgramClass, GENERATOR_VERSION_V3, INSTR_COUNT, V3_CLASS};
|
||||
use igneum_pow::memhard::{derive_item, mixer, round_key, Cache, MixParams, Shape};
|
||||
use igneum_pow::seed::{day_key, SplitMix64};
|
||||
use igneum_pow::verify::{DatasetMode, DatasetSource, Epoch};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DAY: &str = "2026-10-03";
|
||||
|
||||
fn contract(p: &Program, seed: &str) {
|
||||
assert_eq!(p.generator, GENERATOR_VERSION_V3);
|
||||
assert_eq!(p.class, V3_CLASS);
|
||||
assert_eq!(p.instrs.len(), INSTR_COUNT);
|
||||
assert_eq!(p.instrs.iter().filter(|i| i.op == Op::Load).count(), 16);
|
||||
for (k, i) in p.instrs.iter().enumerate() {
|
||||
assert!(i.src != i.dst, "#{k}: src == dst");
|
||||
assert!((1..=31).contains(&i.rot), "#{k}: rot {}", i.rot);
|
||||
assert!([1u8, 2, 4, 8, 16].contains(&i.mask), "#{k}: mask {}", i.mask);
|
||||
assert!(i.dst < 8 && i.src < 8 && i.src2 < 8);
|
||||
assert_eq!(i.width, 1, "#{k}: a v3 load reads one word");
|
||||
}
|
||||
assert!(igneum_pow::accept::check(p).is_ok(), "an accepted program");
|
||||
let v2 = generate_from_seed_bytes(seed, seed.as_bytes());
|
||||
assert_eq!(p.instrs, v2.instrs, "the v2 program of the seed under the v3 construction");
|
||||
assert_eq!(p.attempt, v2.attempt);
|
||||
}
|
||||
|
||||
/// Write a pack whose vectors.json carries `bases` instead of the three standard bases (packbench and the OpenCL
|
||||
/// host check every unit standalone and the ones inside the batch window).
|
||||
fn write_pack_with_bases(dir: &PathBuf, e: &Epoch, day: &str, bases: &[u32], source: &str) {
|
||||
let mut pack = export_pack(e, day, source);
|
||||
let outs: Vec<[u64; 32]> = bases.iter().map(|&b| e.hash_warp(b)).collect();
|
||||
let vj = vectors_json(&e.program, day, e.dataset.log2_words, bases, &outs, &pack.vectors, e.dataset.mask, source, true);
|
||||
for f in pack.files.iter_mut() {
|
||||
if f.0 == "vectors.json" {
|
||||
f.1 = vj.clone();
|
||||
}
|
||||
}
|
||||
pack.write_to(dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_v3_programs_cpu() {
|
||||
let n: usize = std::env::var("IGNEUM_MIXER_FUZZ").ok().and_then(|s| s.parse().ok()).unwrap_or(200);
|
||||
let out = std::env::var("IGNEUM_MIXER_PACKS_OUT").ok().map(PathBuf::from);
|
||||
let mut rng = SplitMix64::new(0x6967_6e65_756d_2d6d); // "igneum-m"
|
||||
let shape = Shape::for_class(&V3_CLASS);
|
||||
assert_eq!(shape, Shape { mixer_mult: 4, cache_log2_words: 26 });
|
||||
// one memory-hard source per dataset size (the 256 MiB cache fill is 0.2 s each)
|
||||
let mut mh: HashMap<u32, DatasetSource> = HashMap::new();
|
||||
let mut manifest = String::from("pack\tlog2\tprogram_id\tbases\n");
|
||||
let mut units = 0usize;
|
||||
let mut wraps = 0usize;
|
||||
for i in 0..n {
|
||||
let seed = format!("igneum-mixer-fuzz/{i}");
|
||||
let p = generate_from_seed_bytes_program_class(&seed, seed.as_bytes(), ProgramClass::V3, None);
|
||||
contract(&p, &seed);
|
||||
let b0 = (rng.below(8) as u32) * 32;
|
||||
let b1 = 0x8000_0000u32.wrapping_sub(256).wrapping_add((rng.below(16) as u32) * 32);
|
||||
let b2 = 0xffff_ff00u32.wrapping_add((rng.below(8) as u32) * 32);
|
||||
let b3 = (rng.next() as u32) & !31;
|
||||
let bases = [b0, b1, b2, b3];
|
||||
wraps += bases.iter().filter(|&&b| b >= 0xffff_ff00).count();
|
||||
let log2 = [24u32, 26, 28][rng.below(3) as usize];
|
||||
let ds = mh.remove(&log2).unwrap_or_else(|| DatasetSource::new_shape(DAY, DatasetMode::MemoryHard, log2, shape));
|
||||
let e = Epoch { program: p, dataset: ds };
|
||||
for &b in &bases {
|
||||
let r1 = e.interpret_warp(b);
|
||||
let r2 = e.interpret_warp(b);
|
||||
assert_eq!(r1.hashes, r2.hashes);
|
||||
assert!(r1.items_derived >= 120 * 32 / 32 && r1.items_derived <= 4_096, "{seed}: {} items", r1.items_derived);
|
||||
units += 1;
|
||||
}
|
||||
if let Some(dir) = &out {
|
||||
let pack_name = format!("fuzz-{i:03}-mx4-l{log2}");
|
||||
write_pack_with_bases(&dir.join(&pack_name), &e, DAY, &bases, "igneum-pow tests/mixer.rs fuzz");
|
||||
manifest.push_str(&format!(
|
||||
"{pack_name}\t{log2}\t{:016x}\t{}\n",
|
||||
e.program.program_id(),
|
||||
bases.iter().map(|b| format!("{b}")).collect::<Vec<_>>().join(",")
|
||||
));
|
||||
}
|
||||
mh.insert(log2, e.dataset);
|
||||
}
|
||||
println!("fuzz: {n} v3 programs, {units} units on the CPU, {wraps} units in the top 256 nonces");
|
||||
assert_eq!(units, 4 * n);
|
||||
assert_eq!(wraps, n);
|
||||
if let Some(dir) = &out {
|
||||
std::fs::create_dir_all(dir).unwrap();
|
||||
std::fs::write(dir.join("manifest.tsv"), manifest).unwrap();
|
||||
println!("packs written to {}", dir.display());
|
||||
}
|
||||
}
|
||||
|
||||
/// Bit balance and avalanche of the v3 hash beside v2 on the same program (the TESTS.md section 3 shape, on the
|
||||
/// CPU, 2^13 nonces per seed): every output bit within 5 sigma of half ones; a single nonce-bit flip moves 50 percent
|
||||
/// of the output bits within 2 points; no duplicate among the outputs.
|
||||
#[test]
|
||||
fn stats_v3_against_v2() {
|
||||
let n_warps = 256usize; // 8,192 nonces
|
||||
for seed in ["igneum-genesis", "igneum-genesis/stats1"] {
|
||||
let v3 = Epoch {
|
||||
program: generate_from_seed_bytes_program_class(seed, seed.as_bytes(), ProgramClass::V3, None),
|
||||
dataset: DatasetSource::new_shape(DAY, DatasetMode::MemoryHard, 24, Shape::for_class(&V3_CLASS)),
|
||||
};
|
||||
let v2 = Epoch::new(seed, DAY, DatasetMode::MemoryHard, 24);
|
||||
for (name, e) in [("v3", &v3), ("v2", &v2)] {
|
||||
let mut ones = [0u64; 64];
|
||||
let mut outs = Vec::with_capacity(n_warps * 32);
|
||||
for w in 0..n_warps {
|
||||
let h = e.hash_warp(w as u32 * 32);
|
||||
for &x in &h {
|
||||
outs.push(x);
|
||||
for b in 0..64 {
|
||||
ones[b] += (x >> b) & 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let total = (n_warps * 32) as f64;
|
||||
let sigma = (total / 4.0).sqrt();
|
||||
for (b, &c) in ones.iter().enumerate() {
|
||||
let z = (c as f64 - total / 2.0).abs() / sigma;
|
||||
assert!(z < 5.0, "{seed} {name}: bit {b} ones {c} of {total}, z {z:.2}");
|
||||
}
|
||||
// avalanche: flip one bit of the nonce within the unit (lanes 0..31 differ in the low 5 bits) and across
|
||||
// units (bit 5 and up): compare lane l of unit u with lane l ^ (1 << k) and with unit u ^ (1 << k)
|
||||
let mut flips = 0u64;
|
||||
let mut moved = 0u64;
|
||||
for w in 0..64usize {
|
||||
let h = e.hash_warp(w as u32 * 32);
|
||||
for k in 0..5 {
|
||||
for l in 0..32usize {
|
||||
moved += (h[l] ^ h[l ^ (1 << k)]).count_ones() as u64;
|
||||
flips += 1;
|
||||
}
|
||||
}
|
||||
let h2 = e.hash_warp((w ^ 1) as u32 * 32);
|
||||
for l in 0..32usize {
|
||||
moved += (h[l] ^ h2[l]).count_ones() as u64;
|
||||
flips += 1;
|
||||
}
|
||||
}
|
||||
let avg = moved as f64 / flips as f64 / 64.0 * 100.0;
|
||||
assert!((avg - 50.0).abs() < 2.0, "{seed} {name}: avalanche {avg:.2} percent");
|
||||
outs.sort_unstable();
|
||||
let dups = outs.windows(2).filter(|p| p[0] == p[1]).count();
|
||||
assert_eq!(dups, 0, "{seed} {name}: duplicate outputs");
|
||||
println!("{seed} {name}: {} outputs, avalanche {avg:.2} percent, worst bit z {:.2}", outs.len(), ones.iter().map(|&c| (c as f64 - total / 2.0).abs() / sigma).fold(0.0, f64::max));
|
||||
}
|
||||
assert_ne!(v3.hash_warp(0), v2.hash_warp(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// The dataset edges under every multiplier on a small cache: word 0, word MASK, the last word of item 0 and the
|
||||
/// first of item 1, through `DatasetSource::word` and by hand.
|
||||
#[test]
|
||||
fn edge_items_every_multiplier() {
|
||||
let key = day_key(DAY);
|
||||
let cache = Cache::fill_log2(key, 14);
|
||||
for m in [1u32, 2, 4, 8] {
|
||||
let mp = MixParams::with_shape(key, Shape { mixer_mult: m, cache_log2_words: 14 });
|
||||
let by_hand = |t: u32| -> [u32; 16] {
|
||||
let mut s = [0u32; 16];
|
||||
s[..8].copy_from_slice(&key);
|
||||
for i in 0..8 {
|
||||
s[8 + i] = t.wrapping_mul(mp.mul[i]).wrapping_add(mp.rc[i]);
|
||||
}
|
||||
for r in 0..8usize {
|
||||
for j in 0..m as usize {
|
||||
mixer(&mut s, round_key(r * m as usize + j), &mp);
|
||||
}
|
||||
let line = cache.line(s[0]);
|
||||
for i in 0..16 {
|
||||
s[i] ^= line[i];
|
||||
}
|
||||
}
|
||||
for j in 0..m as usize {
|
||||
mixer(&mut s, round_key(8 * m as usize + j), &mp);
|
||||
}
|
||||
s
|
||||
};
|
||||
for t in [0u32, 1, 0x0fff_ffff, 0xffff_ffff] {
|
||||
assert_eq!(derive_item(t, &mp, &cache), by_hand(t), "m {m} item {t}");
|
||||
}
|
||||
}
|
||||
// the interpreter's fetch path at the genesis cache: words 0, 15, 16 and MASK of a 2^20-word dataset agree with
|
||||
// the item derivation, under v3
|
||||
let ds = DatasetSource::new_shape(DAY, DatasetMode::MemoryHard, 20, Shape::for_class(&V3_CLASS));
|
||||
let m = ds.memhard().unwrap();
|
||||
for w in [0u32, 15, 16, 17, ds.mask - 1, ds.mask] {
|
||||
assert_eq!(ds.word(w), derive_item(w >> 4, &m.params, &m.cache)[(w & 15) as usize]);
|
||||
assert_eq!(ds.word(w), m.word(w));
|
||||
}
|
||||
// a load at an out-of-range register masks to the dataset: the word at mask + 1 is the word at 0
|
||||
assert_eq!(ds.word(ds.mask.wrapping_add(1)), ds.word(0));
|
||||
}
|
||||
|
||||
/// Two independent epochs of the same seed and day: every vector and every emitted file identical; the pinned v3
|
||||
/// pack is what a third export writes.
|
||||
#[test]
|
||||
fn determinism_v3() {
|
||||
let build = || Epoch {
|
||||
program: generate_from_seed_bytes_program_class("igneum-genesis", b"igneum-genesis", ProgramClass::V3, None),
|
||||
dataset: DatasetSource::new_shape(DAY, DatasetMode::MemoryHard, 28, Shape::for_class(&V3_CLASS)),
|
||||
};
|
||||
let a = build();
|
||||
let b = build();
|
||||
let pa = export_pack(&a, DAY, "a");
|
||||
let pb = export_pack(&b, DAY, "a");
|
||||
assert_eq!(pa.outs, pb.outs);
|
||||
assert_eq!(pa.vectors, pb.vectors);
|
||||
assert_eq!(pa.files, pb.files);
|
||||
for (w, warp) in [(0u32, 0usize), (4096, 1), (1_000_000, 2)] {
|
||||
assert_eq!(a.hash_warp(w), pa.outs[warp]);
|
||||
}
|
||||
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../proto-cuda/packs-ca2-mixer/mx4-genesis");
|
||||
for (name, text) in &pa.files {
|
||||
if name == "vectors.json" || name == "vectors.h" {
|
||||
continue; // the source string differs ("a" here)
|
||||
}
|
||||
let on_disk = std::fs::read_to_string(dir.join(name)).unwrap();
|
||||
assert_eq!(&on_disk, text, "{name}");
|
||||
}
|
||||
}
|
||||
|
|
@ -261,6 +261,7 @@ fn edge(name: &str, c: LoadClass, instrs: Vec<Instr>) -> Program {
|
|||
generator: GENERATOR_VERSION,
|
||||
attempt: 0,
|
||||
class: c,
|
||||
era_bytes: None,
|
||||
instrs,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue