Skip to main content

search/
search.rs

1//! Search 100,000 text records for a pattern on the GPU, from an ordinary
2//! Rust function, and check every result against that function.
3//!
4//! cargo run --release -p handwired-rs-gpu --example search
5
6use std::time::Instant;
7
8use handwired_rs::TensorExecutor;
9use handwired_rs::handwired;
10use handwired_rs_gpu::{Gpu, GpuBatch};
11
12const TEXT: usize = 32;
13
14/// Where `pattern` first occurs in `text`, as (found, position).
15#[handwired]
16fn find_pattern(text: [u8; 32], pattern: [u8; 3]) -> (bool, u8) {
17    for start in 0..30 {
18        if text[start] == pattern[0] && text[start + 1] == pattern[1] && text[start + 2] == pattern[2] {
19            return (true, start as u8);
20        }
21    }
22    (false, 0)
23}
24
25fn main() {
26    let records = 100_000;
27    let words: [&[u8]; 8] = [b"cat ", b"dog ", b"the ", b"cart ", b"scatter ", b"a ", b"category ", b"doge "];
28    let mut seed = 0x2545_F491_4F6C_DD1Du64;
29    let mut next = move || {
30        seed ^= seed << 13;
31        seed ^= seed >> 7;
32        seed ^= seed << 17;
33        seed
34    };
35    let inputs: Vec<([u8; TEXT], [u8; 3])> = (0..records)
36        .map(|_| {
37            let mut text = Vec::new();
38            while text.len() < TEXT {
39                text.extend_from_slice(words[(next() % 8) as usize]);
40            }
41            let text: [u8; TEXT] = text[..TEXT].try_into().unwrap();
42            let pattern = if next() % 2 == 0 { *b"cat" } else { *b"dog" };
43            (text, pattern)
44        })
45        .collect();
46
47    let start = Instant::now();
48    let compiled = find_pattern_circuit();
49    println!(
50        "circuit: {} live operations in {} layers, built in {:.1?}",
51        compiled.program.live_operation_count(),
52        compiled.program.layers.len(),
53        start.elapsed()
54    );
55
56    let start = Instant::now();
57    let native: Vec<(bool, u8)> = inputs.iter().map(|&(text, pattern)| find_pattern(text, pattern)).collect();
58    let native_time = start.elapsed();
59
60    let start = Instant::now();
61    let mut executor = TensorExecutor::new(&compiled.program);
62    let mut source = Vec::with_capacity(TEXT + 3);
63    let cpu: Vec<(bool, u8)> = inputs
64        .iter()
65        .map(|record| {
66            source.clear();
67            handwired_rs::dsl::Params::encode(&(record.0, record.1), &mut source);
68            let outputs = executor.run(&source);
69            (outputs[0] == 1.0, outputs[1] as u8)
70        })
71        .collect();
72    let cpu_time = start.elapsed();
73
74    let gpu = Gpu::new().expect("a GPU adapter");
75    let start = Instant::now();
76    let batch = GpuBatch::new(&gpu, &compiled).unwrap();
77    let shader_time = start.elapsed();
78    let _ = batch.run(&gpu, &inputs[..1024]).unwrap();
79    let start = Instant::now();
80    let on_gpu = batch.run(&gpu, &inputs).unwrap();
81    let gpu_time = start.elapsed();
82
83    assert_eq!(cpu, native, "the CPU tensor executor disagrees with the function");
84    assert_eq!(on_gpu, native, "the GPU disagrees with the function");
85    let found = native.iter().filter(|result| result.0).count();
86    println!("{records} records, {found} contain their pattern; every result matches the function");
87    println!("plain Rust:           {native_time:>10.1?}");
88    println!("CPU tensor executor:  {cpu_time:>10.1?}  (one thread)");
89    println!(
90        "GPU ({}, {:?}): {gpu_time:>10.1?}  (upload, run, read back; shader built once in {shader_time:.1?})",
91        gpu.info.name, gpu.info.device_type
92    );
93}