pub struct Graph { /* private fields */ }Expand description
A pipeline builder. Each operation declares placement and actual buffer use.
Implementations§
Source§impl Graph
impl Graph
Sourcepub unsafe fn gpu_scoped(
&mut self,
bindings: &[GpuAccess],
callback: impl FnMut(&[View<'_>]) -> Result<()> + Send + 'static,
) -> Result<Node>
pub unsafe fn gpu_scoped( &mut self, bindings: &[GpuAccess], callback: impl FnMut(&[View<'_>]) -> Result<()> + Send + 'static, ) -> Result<Node>
Integrate owned native GPU execution into this graph’s hazards and
producer dependencies. The callback runs on a worker, never in poll.
§Safety
The callback must retain all private native resources it uses, access these views only as declared, and synchronize every stream touching them before returning. It must not let raw pointers/views escape this scope. Private storage must not be concurrently accessible outside the callback. On an uncertain failure return an error; captured resources and bindings are then quarantined together, preserving native lifetimes.
Sourcepub fn fill(&mut self, destination: BufferView, value: u8) -> Result<Node>
pub fn fill(&mut self, destination: BufferView, value: u8) -> Result<Node>
Fill a GPU-visible range with a byte pattern.
Examples found in repository?
4fn main() -> hrx::Result<()> {
5 let runtime = Runtime::new()?;
6 let buffer = runtime.allocate(4096, MemoryPlacement::GpuLocal)?;
7 let stream = Stream::open()?;
8 let raw = stream.allocate(4096)?;
9 for count in [128, 512, 1024, 2048] {
10 let begin = Instant::now();
11 let mut graph = runtime.graph();
12 for _ in 0..count {
13 graph.fill(buffer.view(), 0)?;
14 }
15 let graph = graph.prepare()?;
16 let inferred = begin.elapsed();
17 let begin = Instant::now();
18 let mut native = stream.graph()?;
19 let mut last = None;
20 for _ in 0..count {
21 last = Some(native.fill(last.as_slice(), raw.binding(), 0)?);
22 }
23 let _native = native.finish()?;
24 println!(
25 "nodes={count} inferred_us={} explicit_us={}",
26 inferred.as_micros(),
27 begin.elapsed().as_micros()
28 );
29 graph.submit()?.wait()?;
30 }
31 Ok(())
32}More examples
8fn main() -> Result<()> {
9 let args: Vec<_> = std::env::args().collect();
10 if args.len() != 4 {
11 return Err(hrx::Error::Message(
12 "usage: shared_roundtrip <passthrough.xclbin> <instructions.bin> <bytes>".into(),
13 ));
14 }
15 let bytes = args[3]
16 .parse::<usize>()
17 .map_err(|e| hrx::Error::Message(e.to_string()))?;
18 let runtime = Runtime::new()?;
19 // This example accepts trusted local artifacts with the documented ABI.
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "u32 passthrough".into(),
26 };
27 let contract = KernelContract {
28 bindings: vec![
29 binding(bytes, Access::Read),
30 binding(4096, Access::Read),
31 binding(bytes, Access::Write),
32 ],
33 constants: vec![],
34 };
35 let kernel = unsafe { program.kernel(&std::fs::read(&args[2])?, contract) }?;
36 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
37 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
38 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
39 let result = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
40 let mut graph = runtime.graph();
41 graph.fill(a.view(), 0x5a)?;
42 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
43 graph.copy(result.view(), c.view())?;
44 let graph = graph.prepare()?;
45 for _ in 0..10 {
46 graph.submit()?.wait()?;
47 let mapped = result.map_read()?;
48 if mapped.iter().any(|&b| b != 0x5a) {
49 return Err(hrx::Error::Message("GPU/NPU roundtrip mismatch".into()));
50 }
51 }
52 println!(
53 "GPU -> NPU -> GPU: all {bytes} bytes correct, 10 executions, {} prepared regions",
54 graph.regions()
55 );
56 Ok(())
57}9fn main() -> Result<()> {
10 let args: Vec<_> = std::env::args().collect();
11 if args.len() != 4 {
12 return Err(hrx::Error::Message(
13 "usage: shared_bench <passthrough.xclbin> <instructions.bin> <bytes>".into(),
14 ));
15 }
16 let bytes = args[3]
17 .parse::<usize>()
18 .map_err(|e| hrx::Error::Message(e.to_string()))?;
19 let runtime = Runtime::new()?;
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "passthrough".into(),
26 };
27 let kernel = unsafe {
28 program.kernel(
29 &std::fs::read(&args[2])?,
30 KernelContract {
31 bindings: vec![
32 binding(bytes, Access::Read),
33 binding(4096, Access::Read),
34 binding(bytes, Access::Write),
35 ],
36 constants: vec![],
37 },
38 )
39 }?;
40 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
41 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
42 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
43 let mut graph = runtime.graph();
44 graph.fill(a.view(), 0x35)?;
45 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
46 let graph = graph.prepare()?;
47 for _ in 0..5 {
48 graph.submit()?.wait()?;
49 }
50 let mut samples = Vec::new();
51 for _ in 0..100 {
52 let start = Instant::now();
53 graph.submit()?.wait()?;
54 samples.push(start.elapsed().as_secs_f64());
55 }
56 if c.map_read()?.iter().any(|&b| b != 0x35) {
57 return Err(hrx::Error::Message("incorrect benchmark output".into()));
58 }
59 samples.sort_by(f64::total_cmp);
60 println!(
61 "bytes={bytes} p50_us={:.2} p95_us={:.2}",
62 percentile(&samples, 50) * 1e6,
63 percentile(&samples, 95) * 1e6
64 );
65 Ok(())
66}58fn main() -> Result<()> {
59 let args: Vec<_> = std::env::args().collect();
60 if args.len() != 6 {
61 return Err(hrx::Error::Message(
62 "usage: gemm_pipeline <row-major-bf16.xclbin> <instructions.bin> M K N".into(),
63 ));
64 }
65 let parse = |i: usize| {
66 args[i]
67 .parse::<usize>()
68 .map_err(|e| hrx::Error::Message(e.to_string()))
69 };
70 let (m, k, n) = (parse(3)?, parse(4)?, parse(5)?);
71 let extent = |a: usize, b: usize, width: usize| {
72 a.checked_mul(b)
73 .filter(|&count| count > 0 && count <= (1 << 30))
74 .and_then(|count| count.checked_mul(width))
75 .ok_or_else(|| hrx::Error::Message("invalid matrix extent".into()))
76 };
77 let (a_bytes, b_bytes, c_bytes) = (extent(m, k, 2)?, extent(k, n, 2)?, extent(m, n, 4)?);
78 let runtime = Runtime::new()?;
79 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
80 let npu = unsafe {
81 program.kernel(
82 &std::fs::read(&args[2])?,
83 KernelContract {
84 bindings: vec![
85 binding(a_bytes, Access::Read),
86 binding(b_bytes, Access::Read),
87 binding(c_bytes, Access::Write),
88 ],
89 constants: vec![],
90 },
91 )
92 }?;
93 let prep = gpu_kernel(
94 &runtime,
95 include_str!("../tests/kernels/euler.loom"),
96 "krea2_euler",
97 &[
98 ("krea2.euler.grid_x", (m * k).div_ceil(256).to_string()),
99 ("krea2.euler.grid_y", "1".into()),
100 ],
101 m * k,
102 vec![
103 binding(a_bytes, Access::ReadWrite),
104 binding(a_bytes, Access::Read),
105 ],
106 )?;
107 let epilogue = gpu_kernel(
108 &runtime,
109 include_str!("../tests/kernels/add_f32.loom"),
110 "add_f32",
111 &[("add.grid", (m * n).div_ceil(256).to_string())],
112 m * n,
113 vec![binding(c_bytes, Access::ReadWrite)],
114 )?;
115 let allocate = |bytes| runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()));
116 let weights = allocate(b_bytes)?;
117 let ones = allocate(a_bytes)?;
118 for buffer in [&weights, &ones] {
119 for word in buffer.map_write()?.as_chunks_mut::<2>().0 {
120 word.copy_from_slice(&0x3f80u16.to_le_bytes());
121 }
122 }
123 let prepare = || -> Result<(ExecutableGraph, Buffer)> {
124 let a = allocate(a_bytes)?;
125 let c = allocate(c_bytes)?;
126 let mut graph = runtime.graph();
127 graph.fill(a.view(), 0)?;
128 graph.gpu(&prep, &[a.view(), ones.view()])?;
129 graph.npu(&npu, &[a.view(), weights.view(), c.view()])?;
130 graph.gpu(&epilogue, &[c.view()])?;
131 Ok((graph.prepare()?, c))
132 };
133 let (first, c0) = prepare()?;
134 let (second, c1) = prepare()?;
135 for _ in 0..5 {
136 first.submit()?.wait()?;
137 second.submit()?.wait()?;
138 }
139 let before = runtime.statistics();
140 let mut latency = Vec::with_capacity(50);
141 for _ in 0..50 {
142 let start = Instant::now();
143 first.submit()?.wait()?;
144 latency.push(start.elapsed().as_secs_f64());
145 }
146 let start = Instant::now();
147 for _ in 0..50 {
148 let a = first.submit()?;
149 let b = second.submit()?;
150 a.wait()?;
151 b.wait()?;
152 }
153 let requests_per_second = 100.0 / start.elapsed().as_secs_f64();
154 for c in [&c0, &c1] {
155 if c.map_read()?
156 .as_chunks::<4>()
157 .0
158 .iter()
159 .any(|v| f32::from_le_bytes(*v) != k as f32 + 1.0)
160 {
161 return Err(hrx::Error::Message("GEMM pipeline output mismatch".into()));
162 }
163 }
164 let after = runtime.statistics();
165 assert_eq!(before.allocations, after.allocations);
166 assert_eq!(before.imports, after.imports);
167 assert_eq!(after.copied_bytes, 0);
168 latency.sort_by(f64::total_cmp);
169 println!(
170 "M={m} K={k} N={n} p50_ms={:.3} p95_ms={:.3} pipelined_requests_s={requests_per_second:.2}",
171 percentile(&latency, 50) * 1e3,
172 percentile(&latency, 95) * 1e3
173 );
174 println!("{}", serde_json::to_string(&after)?);
175 Ok(())
176}Sourcepub fn copy(
&mut self,
destination: BufferView,
source: BufferView,
) -> Result<Node>
pub fn copy( &mut self, destination: BufferView, source: BufferView, ) -> Result<Node>
Explicit GPU copy. Lengths must match and ranges must not overlap.
Examples found in repository?
8fn main() -> Result<()> {
9 let args: Vec<_> = std::env::args().collect();
10 if args.len() != 4 {
11 return Err(hrx::Error::Message(
12 "usage: shared_roundtrip <passthrough.xclbin> <instructions.bin> <bytes>".into(),
13 ));
14 }
15 let bytes = args[3]
16 .parse::<usize>()
17 .map_err(|e| hrx::Error::Message(e.to_string()))?;
18 let runtime = Runtime::new()?;
19 // This example accepts trusted local artifacts with the documented ABI.
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "u32 passthrough".into(),
26 };
27 let contract = KernelContract {
28 bindings: vec![
29 binding(bytes, Access::Read),
30 binding(4096, Access::Read),
31 binding(bytes, Access::Write),
32 ],
33 constants: vec![],
34 };
35 let kernel = unsafe { program.kernel(&std::fs::read(&args[2])?, contract) }?;
36 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
37 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
38 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
39 let result = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
40 let mut graph = runtime.graph();
41 graph.fill(a.view(), 0x5a)?;
42 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
43 graph.copy(result.view(), c.view())?;
44 let graph = graph.prepare()?;
45 for _ in 0..10 {
46 graph.submit()?.wait()?;
47 let mapped = result.map_read()?;
48 if mapped.iter().any(|&b| b != 0x5a) {
49 return Err(hrx::Error::Message("GPU/NPU roundtrip mismatch".into()));
50 }
51 }
52 println!(
53 "GPU -> NPU -> GPU: all {bytes} bytes correct, 10 executions, {} prepared regions",
54 graph.regions()
55 );
56 Ok(())
57}Sourcepub fn copy_on(
&mut self,
destination: BufferView,
source: BufferView,
lane: GpuLane,
) -> Result<Node>
pub fn copy_on( &mut self, destination: BufferView, source: BufferView, lane: GpuLane, ) -> Result<Node>
Copy on an explicit lane, retaining the same cross-lane hazard checks.
Sourcepub fn gpu(
&mut self,
kernel: &GpuKernel,
bindings: &[BufferView],
) -> Result<Node>
pub fn gpu( &mut self, kernel: &GpuKernel, bindings: &[BufferView], ) -> Result<Node>
Invoke a trusted GPU kernel; binding extents and alignment are checked. The selected runtime must expose allocation-address queries (interop ABI 1), including for GPU-local bindings. No base-pointer alignment is assumed.
Examples found in repository?
58fn main() -> Result<()> {
59 let args: Vec<_> = std::env::args().collect();
60 if args.len() != 6 {
61 return Err(hrx::Error::Message(
62 "usage: gemm_pipeline <row-major-bf16.xclbin> <instructions.bin> M K N".into(),
63 ));
64 }
65 let parse = |i: usize| {
66 args[i]
67 .parse::<usize>()
68 .map_err(|e| hrx::Error::Message(e.to_string()))
69 };
70 let (m, k, n) = (parse(3)?, parse(4)?, parse(5)?);
71 let extent = |a: usize, b: usize, width: usize| {
72 a.checked_mul(b)
73 .filter(|&count| count > 0 && count <= (1 << 30))
74 .and_then(|count| count.checked_mul(width))
75 .ok_or_else(|| hrx::Error::Message("invalid matrix extent".into()))
76 };
77 let (a_bytes, b_bytes, c_bytes) = (extent(m, k, 2)?, extent(k, n, 2)?, extent(m, n, 4)?);
78 let runtime = Runtime::new()?;
79 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
80 let npu = unsafe {
81 program.kernel(
82 &std::fs::read(&args[2])?,
83 KernelContract {
84 bindings: vec![
85 binding(a_bytes, Access::Read),
86 binding(b_bytes, Access::Read),
87 binding(c_bytes, Access::Write),
88 ],
89 constants: vec![],
90 },
91 )
92 }?;
93 let prep = gpu_kernel(
94 &runtime,
95 include_str!("../tests/kernels/euler.loom"),
96 "krea2_euler",
97 &[
98 ("krea2.euler.grid_x", (m * k).div_ceil(256).to_string()),
99 ("krea2.euler.grid_y", "1".into()),
100 ],
101 m * k,
102 vec![
103 binding(a_bytes, Access::ReadWrite),
104 binding(a_bytes, Access::Read),
105 ],
106 )?;
107 let epilogue = gpu_kernel(
108 &runtime,
109 include_str!("../tests/kernels/add_f32.loom"),
110 "add_f32",
111 &[("add.grid", (m * n).div_ceil(256).to_string())],
112 m * n,
113 vec![binding(c_bytes, Access::ReadWrite)],
114 )?;
115 let allocate = |bytes| runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()));
116 let weights = allocate(b_bytes)?;
117 let ones = allocate(a_bytes)?;
118 for buffer in [&weights, &ones] {
119 for word in buffer.map_write()?.as_chunks_mut::<2>().0 {
120 word.copy_from_slice(&0x3f80u16.to_le_bytes());
121 }
122 }
123 let prepare = || -> Result<(ExecutableGraph, Buffer)> {
124 let a = allocate(a_bytes)?;
125 let c = allocate(c_bytes)?;
126 let mut graph = runtime.graph();
127 graph.fill(a.view(), 0)?;
128 graph.gpu(&prep, &[a.view(), ones.view()])?;
129 graph.npu(&npu, &[a.view(), weights.view(), c.view()])?;
130 graph.gpu(&epilogue, &[c.view()])?;
131 Ok((graph.prepare()?, c))
132 };
133 let (first, c0) = prepare()?;
134 let (second, c1) = prepare()?;
135 for _ in 0..5 {
136 first.submit()?.wait()?;
137 second.submit()?.wait()?;
138 }
139 let before = runtime.statistics();
140 let mut latency = Vec::with_capacity(50);
141 for _ in 0..50 {
142 let start = Instant::now();
143 first.submit()?.wait()?;
144 latency.push(start.elapsed().as_secs_f64());
145 }
146 let start = Instant::now();
147 for _ in 0..50 {
148 let a = first.submit()?;
149 let b = second.submit()?;
150 a.wait()?;
151 b.wait()?;
152 }
153 let requests_per_second = 100.0 / start.elapsed().as_secs_f64();
154 for c in [&c0, &c1] {
155 if c.map_read()?
156 .as_chunks::<4>()
157 .0
158 .iter()
159 .any(|v| f32::from_le_bytes(*v) != k as f32 + 1.0)
160 {
161 return Err(hrx::Error::Message("GEMM pipeline output mismatch".into()));
162 }
163 }
164 let after = runtime.statistics();
165 assert_eq!(before.allocations, after.allocations);
166 assert_eq!(before.imports, after.imports);
167 assert_eq!(after.copied_bytes, 0);
168 latency.sort_by(f64::total_cmp);
169 println!(
170 "M={m} K={k} N={n} p50_ms={:.3} p95_ms={:.3} pipelined_requests_s={requests_per_second:.2}",
171 percentile(&latency, 50) * 1e3,
172 percentile(&latency, 95) * 1e3
173 );
174 println!("{}", serde_json::to_string(&after)?);
175 Ok(())
176}Sourcepub unsafe fn gpu_aliasing(
&mut self,
kernel: &GpuKernel,
bindings: &[BufferView],
) -> Result<Node>
pub unsafe fn gpu_aliasing( &mut self, kernel: &GpuKernel, bindings: &[BufferView], ) -> Result<Node>
Invoke a trusted GPU kernel permitting writable argument aliases. Extents, alignment, runtime identity and cross-operation hazards remain checked.
§Safety
The kernel must be valid for the overlapping argument regions supplied; no inter-thread data races or unsupported in-place accesses may occur.
Sourcepub fn npu(
&mut self,
kernel: &NpuKernel,
bindings: &[BufferView],
) -> Result<Node>
pub fn npu( &mut self, kernel: &NpuKernel, bindings: &[BufferView], ) -> Result<Node>
Invoke a trusted NPU specialization with checked device and memory-bank compatibility. Host-only and imported BOs may originate in a different program context on the same device and memory bank. Their owning contexts remain retained.
Examples found in repository?
8fn main() -> Result<()> {
9 let args: Vec<_> = std::env::args().collect();
10 if args.len() != 4 {
11 return Err(hrx::Error::Message(
12 "usage: shared_roundtrip <passthrough.xclbin> <instructions.bin> <bytes>".into(),
13 ));
14 }
15 let bytes = args[3]
16 .parse::<usize>()
17 .map_err(|e| hrx::Error::Message(e.to_string()))?;
18 let runtime = Runtime::new()?;
19 // This example accepts trusted local artifacts with the documented ABI.
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "u32 passthrough".into(),
26 };
27 let contract = KernelContract {
28 bindings: vec![
29 binding(bytes, Access::Read),
30 binding(4096, Access::Read),
31 binding(bytes, Access::Write),
32 ],
33 constants: vec![],
34 };
35 let kernel = unsafe { program.kernel(&std::fs::read(&args[2])?, contract) }?;
36 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
37 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
38 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
39 let result = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
40 let mut graph = runtime.graph();
41 graph.fill(a.view(), 0x5a)?;
42 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
43 graph.copy(result.view(), c.view())?;
44 let graph = graph.prepare()?;
45 for _ in 0..10 {
46 graph.submit()?.wait()?;
47 let mapped = result.map_read()?;
48 if mapped.iter().any(|&b| b != 0x5a) {
49 return Err(hrx::Error::Message("GPU/NPU roundtrip mismatch".into()));
50 }
51 }
52 println!(
53 "GPU -> NPU -> GPU: all {bytes} bytes correct, 10 executions, {} prepared regions",
54 graph.regions()
55 );
56 Ok(())
57}More examples
9fn main() -> Result<()> {
10 let args: Vec<_> = std::env::args().collect();
11 if args.len() != 4 {
12 return Err(hrx::Error::Message(
13 "usage: shared_bench <passthrough.xclbin> <instructions.bin> <bytes>".into(),
14 ));
15 }
16 let bytes = args[3]
17 .parse::<usize>()
18 .map_err(|e| hrx::Error::Message(e.to_string()))?;
19 let runtime = Runtime::new()?;
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "passthrough".into(),
26 };
27 let kernel = unsafe {
28 program.kernel(
29 &std::fs::read(&args[2])?,
30 KernelContract {
31 bindings: vec![
32 binding(bytes, Access::Read),
33 binding(4096, Access::Read),
34 binding(bytes, Access::Write),
35 ],
36 constants: vec![],
37 },
38 )
39 }?;
40 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
41 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
42 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
43 let mut graph = runtime.graph();
44 graph.fill(a.view(), 0x35)?;
45 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
46 let graph = graph.prepare()?;
47 for _ in 0..5 {
48 graph.submit()?.wait()?;
49 }
50 let mut samples = Vec::new();
51 for _ in 0..100 {
52 let start = Instant::now();
53 graph.submit()?.wait()?;
54 samples.push(start.elapsed().as_secs_f64());
55 }
56 if c.map_read()?.iter().any(|&b| b != 0x35) {
57 return Err(hrx::Error::Message("incorrect benchmark output".into()));
58 }
59 samples.sort_by(f64::total_cmp);
60 println!(
61 "bytes={bytes} p50_us={:.2} p95_us={:.2}",
62 percentile(&samples, 50) * 1e6,
63 percentile(&samples, 95) * 1e6
64 );
65 Ok(())
66}58fn main() -> Result<()> {
59 let args: Vec<_> = std::env::args().collect();
60 if args.len() != 6 {
61 return Err(hrx::Error::Message(
62 "usage: gemm_pipeline <row-major-bf16.xclbin> <instructions.bin> M K N".into(),
63 ));
64 }
65 let parse = |i: usize| {
66 args[i]
67 .parse::<usize>()
68 .map_err(|e| hrx::Error::Message(e.to_string()))
69 };
70 let (m, k, n) = (parse(3)?, parse(4)?, parse(5)?);
71 let extent = |a: usize, b: usize, width: usize| {
72 a.checked_mul(b)
73 .filter(|&count| count > 0 && count <= (1 << 30))
74 .and_then(|count| count.checked_mul(width))
75 .ok_or_else(|| hrx::Error::Message("invalid matrix extent".into()))
76 };
77 let (a_bytes, b_bytes, c_bytes) = (extent(m, k, 2)?, extent(k, n, 2)?, extent(m, n, 4)?);
78 let runtime = Runtime::new()?;
79 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
80 let npu = unsafe {
81 program.kernel(
82 &std::fs::read(&args[2])?,
83 KernelContract {
84 bindings: vec![
85 binding(a_bytes, Access::Read),
86 binding(b_bytes, Access::Read),
87 binding(c_bytes, Access::Write),
88 ],
89 constants: vec![],
90 },
91 )
92 }?;
93 let prep = gpu_kernel(
94 &runtime,
95 include_str!("../tests/kernels/euler.loom"),
96 "krea2_euler",
97 &[
98 ("krea2.euler.grid_x", (m * k).div_ceil(256).to_string()),
99 ("krea2.euler.grid_y", "1".into()),
100 ],
101 m * k,
102 vec![
103 binding(a_bytes, Access::ReadWrite),
104 binding(a_bytes, Access::Read),
105 ],
106 )?;
107 let epilogue = gpu_kernel(
108 &runtime,
109 include_str!("../tests/kernels/add_f32.loom"),
110 "add_f32",
111 &[("add.grid", (m * n).div_ceil(256).to_string())],
112 m * n,
113 vec![binding(c_bytes, Access::ReadWrite)],
114 )?;
115 let allocate = |bytes| runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()));
116 let weights = allocate(b_bytes)?;
117 let ones = allocate(a_bytes)?;
118 for buffer in [&weights, &ones] {
119 for word in buffer.map_write()?.as_chunks_mut::<2>().0 {
120 word.copy_from_slice(&0x3f80u16.to_le_bytes());
121 }
122 }
123 let prepare = || -> Result<(ExecutableGraph, Buffer)> {
124 let a = allocate(a_bytes)?;
125 let c = allocate(c_bytes)?;
126 let mut graph = runtime.graph();
127 graph.fill(a.view(), 0)?;
128 graph.gpu(&prep, &[a.view(), ones.view()])?;
129 graph.npu(&npu, &[a.view(), weights.view(), c.view()])?;
130 graph.gpu(&epilogue, &[c.view()])?;
131 Ok((graph.prepare()?, c))
132 };
133 let (first, c0) = prepare()?;
134 let (second, c1) = prepare()?;
135 for _ in 0..5 {
136 first.submit()?.wait()?;
137 second.submit()?.wait()?;
138 }
139 let before = runtime.statistics();
140 let mut latency = Vec::with_capacity(50);
141 for _ in 0..50 {
142 let start = Instant::now();
143 first.submit()?.wait()?;
144 latency.push(start.elapsed().as_secs_f64());
145 }
146 let start = Instant::now();
147 for _ in 0..50 {
148 let a = first.submit()?;
149 let b = second.submit()?;
150 a.wait()?;
151 b.wait()?;
152 }
153 let requests_per_second = 100.0 / start.elapsed().as_secs_f64();
154 for c in [&c0, &c1] {
155 if c.map_read()?
156 .as_chunks::<4>()
157 .0
158 .iter()
159 .any(|v| f32::from_le_bytes(*v) != k as f32 + 1.0)
160 {
161 return Err(hrx::Error::Message("GEMM pipeline output mismatch".into()));
162 }
163 }
164 let after = runtime.statistics();
165 assert_eq!(before.allocations, after.allocations);
166 assert_eq!(before.imports, after.imports);
167 assert_eq!(after.copied_bytes, 0);
168 latency.sort_by(f64::total_cmp);
169 println!(
170 "M={m} K={k} N={n} p50_ms={:.3} p95_ms={:.3} pipelined_requests_s={requests_per_second:.2}",
171 percentile(&latency, 50) * 1e3,
172 percentile(&latency, 95) * 1e3
173 );
174 println!("{}", serde_json::to_string(&after)?);
175 Ok(())
176}Sourcepub fn prepare(self) -> Result<ExecutableGraph>
pub fn prepare(self) -> Result<ExecutableGraph>
Lower GPU regions and import NPU views once; reserve reusable run slots.
Examples found in repository?
4fn main() -> hrx::Result<()> {
5 let runtime = Runtime::new()?;
6 let buffer = runtime.allocate(4096, MemoryPlacement::GpuLocal)?;
7 let stream = Stream::open()?;
8 let raw = stream.allocate(4096)?;
9 for count in [128, 512, 1024, 2048] {
10 let begin = Instant::now();
11 let mut graph = runtime.graph();
12 for _ in 0..count {
13 graph.fill(buffer.view(), 0)?;
14 }
15 let graph = graph.prepare()?;
16 let inferred = begin.elapsed();
17 let begin = Instant::now();
18 let mut native = stream.graph()?;
19 let mut last = None;
20 for _ in 0..count {
21 last = Some(native.fill(last.as_slice(), raw.binding(), 0)?);
22 }
23 let _native = native.finish()?;
24 println!(
25 "nodes={count} inferred_us={} explicit_us={}",
26 inferred.as_micros(),
27 begin.elapsed().as_micros()
28 );
29 graph.submit()?.wait()?;
30 }
31 Ok(())
32}More examples
8fn main() -> Result<()> {
9 let args: Vec<_> = std::env::args().collect();
10 if args.len() != 4 {
11 return Err(hrx::Error::Message(
12 "usage: shared_roundtrip <passthrough.xclbin> <instructions.bin> <bytes>".into(),
13 ));
14 }
15 let bytes = args[3]
16 .parse::<usize>()
17 .map_err(|e| hrx::Error::Message(e.to_string()))?;
18 let runtime = Runtime::new()?;
19 // This example accepts trusted local artifacts with the documented ABI.
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "u32 passthrough".into(),
26 };
27 let contract = KernelContract {
28 bindings: vec![
29 binding(bytes, Access::Read),
30 binding(4096, Access::Read),
31 binding(bytes, Access::Write),
32 ],
33 constants: vec![],
34 };
35 let kernel = unsafe { program.kernel(&std::fs::read(&args[2])?, contract) }?;
36 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
37 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
38 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
39 let result = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
40 let mut graph = runtime.graph();
41 graph.fill(a.view(), 0x5a)?;
42 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
43 graph.copy(result.view(), c.view())?;
44 let graph = graph.prepare()?;
45 for _ in 0..10 {
46 graph.submit()?.wait()?;
47 let mapped = result.map_read()?;
48 if mapped.iter().any(|&b| b != 0x5a) {
49 return Err(hrx::Error::Message("GPU/NPU roundtrip mismatch".into()));
50 }
51 }
52 println!(
53 "GPU -> NPU -> GPU: all {bytes} bytes correct, 10 executions, {} prepared regions",
54 graph.regions()
55 );
56 Ok(())
57}9fn main() -> Result<()> {
10 let args: Vec<_> = std::env::args().collect();
11 if args.len() != 4 {
12 return Err(hrx::Error::Message(
13 "usage: shared_bench <passthrough.xclbin> <instructions.bin> <bytes>".into(),
14 ));
15 }
16 let bytes = args[3]
17 .parse::<usize>()
18 .map_err(|e| hrx::Error::Message(e.to_string()))?;
19 let runtime = Runtime::new()?;
20 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
21 let binding = |bytes, access| BindingContract {
22 bytes,
23 access,
24 alignment: 4,
25 layout: "passthrough".into(),
26 };
27 let kernel = unsafe {
28 program.kernel(
29 &std::fs::read(&args[2])?,
30 KernelContract {
31 bindings: vec![
32 binding(bytes, Access::Read),
33 binding(4096, Access::Read),
34 binding(bytes, Access::Write),
35 ],
36 constants: vec![],
37 },
38 )
39 }?;
40 let a = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
41 let b = runtime.allocate(4096, MemoryPlacement::Shared(program.clone()))?;
42 let c = runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()))?;
43 let mut graph = runtime.graph();
44 graph.fill(a.view(), 0x35)?;
45 graph.npu(&kernel, &[a.view(), b.view(), c.view()])?;
46 let graph = graph.prepare()?;
47 for _ in 0..5 {
48 graph.submit()?.wait()?;
49 }
50 let mut samples = Vec::new();
51 for _ in 0..100 {
52 let start = Instant::now();
53 graph.submit()?.wait()?;
54 samples.push(start.elapsed().as_secs_f64());
55 }
56 if c.map_read()?.iter().any(|&b| b != 0x35) {
57 return Err(hrx::Error::Message("incorrect benchmark output".into()));
58 }
59 samples.sort_by(f64::total_cmp);
60 println!(
61 "bytes={bytes} p50_us={:.2} p95_us={:.2}",
62 percentile(&samples, 50) * 1e6,
63 percentile(&samples, 95) * 1e6
64 );
65 Ok(())
66}58fn main() -> Result<()> {
59 let args: Vec<_> = std::env::args().collect();
60 if args.len() != 6 {
61 return Err(hrx::Error::Message(
62 "usage: gemm_pipeline <row-major-bf16.xclbin> <instructions.bin> M K N".into(),
63 ));
64 }
65 let parse = |i: usize| {
66 args[i]
67 .parse::<usize>()
68 .map_err(|e| hrx::Error::Message(e.to_string()))
69 };
70 let (m, k, n) = (parse(3)?, parse(4)?, parse(5)?);
71 let extent = |a: usize, b: usize, width: usize| {
72 a.checked_mul(b)
73 .filter(|&count| count > 0 && count <= (1 << 30))
74 .and_then(|count| count.checked_mul(width))
75 .ok_or_else(|| hrx::Error::Message("invalid matrix extent".into()))
76 };
77 let (a_bytes, b_bytes, c_bytes) = (extent(m, k, 2)?, extent(k, n, 2)?, extent(m, n, 4)?);
78 let runtime = Runtime::new()?;
79 let program = unsafe { runtime.npu(0)?.load_program(&args[1]) }?;
80 let npu = unsafe {
81 program.kernel(
82 &std::fs::read(&args[2])?,
83 KernelContract {
84 bindings: vec![
85 binding(a_bytes, Access::Read),
86 binding(b_bytes, Access::Read),
87 binding(c_bytes, Access::Write),
88 ],
89 constants: vec![],
90 },
91 )
92 }?;
93 let prep = gpu_kernel(
94 &runtime,
95 include_str!("../tests/kernels/euler.loom"),
96 "krea2_euler",
97 &[
98 ("krea2.euler.grid_x", (m * k).div_ceil(256).to_string()),
99 ("krea2.euler.grid_y", "1".into()),
100 ],
101 m * k,
102 vec![
103 binding(a_bytes, Access::ReadWrite),
104 binding(a_bytes, Access::Read),
105 ],
106 )?;
107 let epilogue = gpu_kernel(
108 &runtime,
109 include_str!("../tests/kernels/add_f32.loom"),
110 "add_f32",
111 &[("add.grid", (m * n).div_ceil(256).to_string())],
112 m * n,
113 vec![binding(c_bytes, Access::ReadWrite)],
114 )?;
115 let allocate = |bytes| runtime.allocate(bytes, MemoryPlacement::Shared(program.clone()));
116 let weights = allocate(b_bytes)?;
117 let ones = allocate(a_bytes)?;
118 for buffer in [&weights, &ones] {
119 for word in buffer.map_write()?.as_chunks_mut::<2>().0 {
120 word.copy_from_slice(&0x3f80u16.to_le_bytes());
121 }
122 }
123 let prepare = || -> Result<(ExecutableGraph, Buffer)> {
124 let a = allocate(a_bytes)?;
125 let c = allocate(c_bytes)?;
126 let mut graph = runtime.graph();
127 graph.fill(a.view(), 0)?;
128 graph.gpu(&prep, &[a.view(), ones.view()])?;
129 graph.npu(&npu, &[a.view(), weights.view(), c.view()])?;
130 graph.gpu(&epilogue, &[c.view()])?;
131 Ok((graph.prepare()?, c))
132 };
133 let (first, c0) = prepare()?;
134 let (second, c1) = prepare()?;
135 for _ in 0..5 {
136 first.submit()?.wait()?;
137 second.submit()?.wait()?;
138 }
139 let before = runtime.statistics();
140 let mut latency = Vec::with_capacity(50);
141 for _ in 0..50 {
142 let start = Instant::now();
143 first.submit()?.wait()?;
144 latency.push(start.elapsed().as_secs_f64());
145 }
146 let start = Instant::now();
147 for _ in 0..50 {
148 let a = first.submit()?;
149 let b = second.submit()?;
150 a.wait()?;
151 b.wait()?;
152 }
153 let requests_per_second = 100.0 / start.elapsed().as_secs_f64();
154 for c in [&c0, &c1] {
155 if c.map_read()?
156 .as_chunks::<4>()
157 .0
158 .iter()
159 .any(|v| f32::from_le_bytes(*v) != k as f32 + 1.0)
160 {
161 return Err(hrx::Error::Message("GEMM pipeline output mismatch".into()));
162 }
163 }
164 let after = runtime.statistics();
165 assert_eq!(before.allocations, after.allocations);
166 assert_eq!(before.imports, after.imports);
167 assert_eq!(after.copied_bytes, 0);
168 latency.sort_by(f64::total_cmp);
169 println!(
170 "M={m} K={k} N={n} p50_ms={:.3} p95_ms={:.3} pipelined_requests_s={requests_per_second:.2}",
171 percentile(&latency, 50) * 1e3,
172 percentile(&latency, 95) * 1e3
173 );
174 println!("{}", serde_json::to_string(&after)?);
175 Ok(())
176}Auto Trait Implementations§
impl !RefUnwindSafe for Graph
impl !UnwindSafe for Graph
impl Freeze for Graph
impl Send for Graph
impl Sync for Graph
impl Unpin for Graph
impl UnsafeUnpin for Graph
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> DropFlavorWrapper<T> for T
impl<T> DropFlavorWrapper<T> for T
Source§impl<T, W> HasTypeWitness<W> for Twhere
W: MakeTypeWitness<Arg = T>,
T: ?Sized,
impl<T, W> HasTypeWitness<W> for Twhere
W: MakeTypeWitness<Arg = T>,
T: ?Sized,
Source§impl<T> Identity for Twhere
T: ?Sized,
impl<T> Identity for Twhere
T: ?Sized,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more