1use hrx::{Constants, Result, Stream};
3use std::{collections::BTreeMap, hint::black_box, time::Instant};
4
5fn measure(
6 stream: &mut Stream,
7 samples: usize,
8 operations: usize,
9 mut run: impl FnMut(&mut Stream) -> Result<()>,
10) -> Result<(f64, f64)> {
11 let mut enqueue = Vec::new();
12 let mut complete = Vec::new();
13 for sample in 0..samples + 3 {
14 stream.synchronize()?;
15 let start = Instant::now();
16 run(stream)?;
17 let recorded = start.elapsed();
18 stream.synchronize()?;
19 let finished = start.elapsed();
20 if sample >= 3 {
21 enqueue.push(recorded.as_secs_f64() * 1e9 / operations as f64);
22 complete.push(finished.as_secs_f64() * 1e9 / operations as f64);
23 }
24 }
25 enqueue.sort_by(f64::total_cmp);
26 complete.sort_by(f64::total_cmp);
27 Ok((enqueue[samples / 2], complete[samples / 2]))
28}
29
30fn main() -> Result<()> {
31 let mut stream = Stream::open()?;
32 let mut metrics = BTreeMap::new();
33 let samples = std::env::var("HRX_BENCH_SAMPLES")
34 .ok()
35 .and_then(|value| value.parse::<usize>().ok())
36 .filter(|&value| value > 0)
37 .unwrap_or(9);
38 let (ns, _) = measure(&mut stream, samples, 50_000, |stream| {
39 for _ in 0..50_000 {
40 let buffer = stream.scratch(1024 * 1024)?;
41 black_box(&buffer);
42 stream.recycle(buffer)?;
43 }
44 Ok(())
45 })?;
46 metrics.insert("scratch_reuse_ns", ns);
47 let (ns, _) = measure(&mut stream, samples, 2_000, |stream| {
48 for _ in 0..2_000 {
49 black_box(stream.allocate(1024 * 1024)?);
50 }
51 Ok(())
52 })?;
53 metrics.insert("allocate_drop_ns", ns);
54
55 let bytes = 256 * 1024 * 1024;
56 let chunk = vec![0x3cu8; 4 * 1024 * 1024];
57 let weights = stream.allocate(bytes)?;
58 let (_, ns) = measure(&mut stream, samples, 1, |stream| {
59 for offset in (0..bytes).step_by(chunk.len()) {
60 stream.upload(weights.binding().slice(offset, chunk.len())?, &chunk)?;
61 }
62 Ok(())
63 })?;
64 metrics.insert(
65 "queued_upload_gib_s",
66 bytes as f64 / (1024f64.powi(3) * ns * 1e-9),
67 );
68 let mut check = [0u8; 16];
69 stream.read_blocking(
70 weights.try_slice(bytes - check.len(), check.len())?,
71 &mut check,
72 )?;
73 assert_eq!(check, [0x3c; 16]);
74 drop(weights);
75
76 let compiler = hrx::loom::Compiler::for_stream(None, &stream)?;
77 let module = compiler.module(include_str!("../tests/kernels/euler.loom"));
78 let mut spec = hrx::loom::Specialization::new("krea2_euler");
79 spec.set_config("krea2.euler.grid_x", "1");
80 spec.set_config("krea2.euler.grid_y", "1");
81 let artifact = module.compile(&spec)?;
82 let kernel = unsafe { stream.load_artifact(&artifact)? };
84 let sample = stream.allocate(512)?;
85 let velocity = stream.allocate(512)?;
86 let ones: Vec<u8> = (0..256).flat_map(|_| 0x3f80u16.to_le_bytes()).collect();
87 stream.upload_blocking(sample.binding(), &ones)?;
88 stream.upload_blocking(velocity.binding(), &ones)?;
89 let mut constants = Constants::new();
90 match kernel.info().constant_byte_length {
91 8 => constants.push(256u32)?,
92 12 => constants.push(256u64)?,
93 n => return Err(hrx::Error::Message(format!("unexpected constant size {n}"))),
94 }
95 constants.push(0f32)?;
96 let bindings = [sample.binding(), velocity.binding()];
97 let (enqueue, complete) = measure(&mut stream, samples, 2048, |stream| {
98 for _ in 0..2048 {
99 unsafe {
101 stream.dispatch(&kernel, [1; 3], [256, 1, 1], &constants, &bindings)?;
102 }
103 }
104 Ok(())
105 })?;
106 metrics.insert("dispatch_enqueue_ns", enqueue);
107 metrics.insert("dispatch_complete_ns", complete);
108
109 let mut graph_buffers = Vec::new();
112 for _ in 0..32 {
113 let sample = stream.allocate(512)?;
114 let velocity = stream.allocate(512)?;
115 stream.upload(sample.binding(), &ones)?;
116 stream.upload(velocity.binding(), &ones)?;
117 graph_buffers.push((sample, velocity));
118 }
119 stream.synchronize()?;
120 let mut builder = stream.graph()?;
121 let mut previous = None;
122 for (sample, velocity) in &graph_buffers {
123 let after = previous.as_slice();
124 previous = Some(unsafe {
125 builder.dispatch(
126 after,
127 &kernel,
128 [1; 3],
129 [256, 1, 1],
130 &constants,
131 &[sample.binding(), velocity.binding()],
132 )?
133 });
134 }
135 let chained = builder.finish()?;
136
137 let mut builder = stream.graph()?;
139 for (sample, velocity) in &graph_buffers {
140 unsafe {
141 builder.dispatch(
142 &[],
143 &kernel,
144 [1; 3],
145 [256, 1, 1],
146 &constants,
147 &[sample.binding(), velocity.binding()],
148 )?;
149 }
150 }
151 let independent = builder.finish()?;
152 let mut graphs = [chained, independent];
153 let mut enqueue = [Vec::new(), Vec::new()];
154 let mut complete = [Vec::new(), Vec::new()];
155 for round in 0..samples + 3 {
156 for arm in if round % 2 == 0 { [0, 1] } else { [1, 0] } {
157 stream.synchronize()?;
158 let start = Instant::now();
159 for _ in 0..256 {
160 stream.launch(&mut graphs[arm])?;
161 }
162 let submitted = start.elapsed();
163 stream.synchronize()?;
164 let elapsed = start.elapsed();
165 if round >= 3 {
166 enqueue[arm].push(submitted.as_secs_f64() * 1e9 / 256.0);
167 complete[arm].push(elapsed.as_secs_f64() * 1e9 / (256.0 * 32.0));
168 }
169 }
170 }
171 for values in enqueue.iter_mut().chain(complete.iter_mut()) {
172 values.sort_by(f64::total_cmp);
173 }
174 let ns = complete[0][samples / 2];
175 let ns_free = complete[1][samples / 2];
176 metrics.insert("graph_enqueue_ns_per_replay", enqueue[0][samples / 2]);
177 metrics.insert("graph_complete_ns_per_kernel", ns);
178 metrics.insert("graph_independent_ns_per_kernel", ns_free);
179 metrics.insert("graph_scheduling_difference_ns_per_kernel", ns - ns_free);
180 for (sample, _) in &graph_buffers {
181 let mut output = vec![0; 512];
182 stream.read_blocking(sample.binding(), &mut output)?;
183 assert_eq!(output, ones);
184 }
185 let mut output = vec![0; 512];
186 stream.read_blocking(sample.binding(), &mut output)?;
187 assert_eq!(output, ones);
188 println!("{}", serde_json::to_string(&metrics)?);
189 Ok(())
190}