Skip to content

Commit 07c7b4b

Browse files
authored
hpc: HDR rolling floor — integer σ-lattice thresholds on demand (#328)
hpc: HDR rolling floor — integer σ-lattice thresholds on demand
2 parents 031f5c8 + aeda1f4 commit 07c7b4b

5 files changed

Lines changed: 1567 additions & 0 deletions

File tree

‎Cargo.toml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ required-features = ["std"]
207207
name = "rolling_floor_probe"
208208
required-features = ["std"]
209209

210+
[[example]]
211+
name = "hdr_rolling_floor_bench"
212+
required-features = ["std"]
213+
210214
[[example]]
211215
name = "codec_overlap_probe"
212216
required-features = ["std"]
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//! Cost split of the HDR rolling floor: the per-observation hot path versus
2+
//! the periodic shape path.
3+
//!
4+
//! ```sh
5+
//! cargo run --release --example hdr_rolling_floor_bench
6+
//! ```
7+
//!
8+
//! Hot path, per observation:
9+
//! 1. popcount / Hamming distance of two 2048-byte vectors
10+
//! 2. exact moments update (`MomentsU32::observe`, and `moments_u32` batch)
11+
//! 3. reservoir update
12+
//! 4. full rolling-floor update (moments + reservoir + checkpoint test)
13+
//!
14+
//! Periodic path, once per 1000 observations:
15+
//! 5. shape evaluation (sort 1000 samples, median, kurtosis)
16+
//! 6. empirical shape: locate 8 σ-lattice levels
17+
//!
18+
//! Query path, on demand (nothing is stored):
19+
//! 7. Gaussian thresholds of 8 levels
20+
//! 8. shade of a response over 8 levels
21+
22+
use ndarray::hpc::bitwise::hamming_distance_raw;
23+
use ndarray::hpc::rolling_floor::{quantile_of_sorted, EmpiricalShape, ReservoirU32, RollingFloor, SigmaLevel};
24+
use ndarray::hpc::statistics::{moments_u32, MomentsU32};
25+
use std::hint::black_box;
26+
use std::time::Instant;
27+
28+
fn xorshift(n: usize, mut s: u64) -> Vec<u64> {
29+
(0..n)
30+
.map(|_| {
31+
s ^= s << 13;
32+
s ^= s >> 7;
33+
s ^= s << 17;
34+
s
35+
})
36+
.collect()
37+
}
38+
39+
fn ns_per(label: &str, n: usize, f: impl FnOnce()) {
40+
let t = Instant::now();
41+
f();
42+
let dt = t.elapsed();
43+
println!("{label:<44} {:>9.2} ns/op ({n} ops)", dt.as_nanos() as f64 / n as f64);
44+
}
45+
46+
fn main() {
47+
println!("avx512f={} avx2={}", cfg!(target_feature = "avx512f"), cfg!(target_feature = "avx2"));
48+
49+
const VBYTES: usize = 2048; // 16384-bit vectors
50+
const N: usize = 2_000_000;
51+
let words = xorshift(VBYTES / 8 * 65, 1);
52+
let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
53+
let query = &bytes[..VBYTES];
54+
let db = &bytes[VBYTES..];
55+
let dists: Vec<u32> = (0..N)
56+
.map(|i| {
57+
let j = i % 64;
58+
hamming_distance_raw(query, &db[j * VBYTES..(j + 1) * VBYTES]) as u32
59+
})
60+
.collect();
61+
62+
println!("-- hot path, per observation --");
63+
ns_per("1. popcount/Hamming (2048 B)", N, || {
64+
let mut acc = 0u64;
65+
for i in 0..N {
66+
let j = i % 64;
67+
acc += hamming_distance_raw(black_box(query), &db[j * VBYTES..(j + 1) * VBYTES]);
68+
}
69+
black_box(acc);
70+
});
71+
ns_per("2a. MomentsU32::observe (scalar)", N, || {
72+
let mut m = MomentsU32::default();
73+
for &d in &dists {
74+
m.observe(black_box(d));
75+
}
76+
black_box(m);
77+
});
78+
ns_per("2b. moments_u32 (batch)", N, || {
79+
black_box(moments_u32(black_box(&dists)));
80+
});
81+
ns_per("3. ReservoirU32::observe (cap 1000)", N, || {
82+
let mut r = ReservoirU32::new(1000);
83+
for &d in &dists {
84+
r.observe(black_box(d));
85+
}
86+
black_box(r.len());
87+
});
88+
ns_per("4a. RollingFloor::observe (incl. checkpoints)", N, || {
89+
let mut f = RollingFloor::for_width(16384);
90+
for &d in &dists {
91+
if let Some(s) = f.observe(black_box(d)) {
92+
f.recalibrate(&s);
93+
}
94+
}
95+
black_box(f.mu());
96+
});
97+
ns_per("4b. RollingFloor::observe_batch (incl. checkpoints)", N, || {
98+
let mut f = RollingFloor::for_width(16384);
99+
let mut rest: &[u32] = &dists;
100+
while !rest.is_empty() {
101+
let (used, s) = f.observe_batch(rest);
102+
rest = &rest[used..];
103+
if let Some(s) = s {
104+
f.recalibrate(&s);
105+
}
106+
}
107+
black_box(f.mu());
108+
});
109+
110+
println!("-- periodic path, per checkpoint (every 1000 observations) --");
111+
let mut r = ReservoirU32::new(1000);
112+
dists[..5000].iter().for_each(|&d| r.observe(d));
113+
const K: usize = 20_000;
114+
ns_per("5. shape: sort + median + kurtosis", K, || {
115+
for _ in 0..K {
116+
let sorted = black_box(&r).sorted();
117+
black_box(quantile_of_sorted(&sorted, 5000));
118+
black_box(r.kurtosis(8192, 64));
119+
}
120+
});
121+
let lattice = [4u8, 6, 7, 8, 9, 10, 11, 12].map(SigmaLevel);
122+
let shape = EmpiricalShape::from_sample(r.samples()).unwrap();
123+
ns_per("6. empirical: locate 8 lattice levels", K, || {
124+
for _ in 0..K {
125+
black_box(lattice.map(|l| black_box(&shape).locate(l, 8200, 70)));
126+
}
127+
});
128+
129+
println!("-- query path, on demand --");
130+
let mut g = RollingFloor::for_width(16384);
131+
dists[..3000].iter().for_each(|&d| {
132+
if let Some(s) = g.observe(d) {
133+
g.recalibrate(&s);
134+
}
135+
});
136+
ns_per("7. Gaussian: thresholds of 8 levels", N, || {
137+
for _ in 0..N {
138+
black_box(black_box(&g).thresholds(&lattice));
139+
}
140+
});
141+
ns_per("8. Gaussian: shade over 8 levels", N, || {
142+
let mut acc = 0usize;
143+
for &d in &dists {
144+
acc += black_box(&g).shade(d, &lattice);
145+
}
146+
black_box(acc);
147+
});
148+
}

‎src/hpc/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod blas_level2;
2525
pub mod blas_level3;
2626
pub mod reductions;
2727
pub mod statistics;
28+
pub mod rolling_floor;
2829
/// Reliability & validity statistics: Pearson r, Spearman ρ, Cronbach α, ICC.
2930
pub mod reliability;
3031
/// Entropy ladder: Staunen↔Wisdom coordinate over NARS truth + Pearl-2³ SPO.

0 commit comments

Comments
 (0)