pub struct Runtime { /* private fields */ }Expand description
Shared scheduler and allocation domain. Clones use the same dependency state.
Upload, compute, download, and NPU regions have independent scheduling lanes. Each lane runs one region at a time; conflicting memory accesses are ordered across all lanes and submissions. Actual device overlap depends on hardware.
Implementations§
Source§impl Runtime
impl Runtime
Sourcepub unsafe fn with_gpu_access<T>(
&self,
stream: &mut Stream,
accesses: &[GpuAccess],
callback: impl FnOnce(&mut Stream, &[View<'_>]) -> Result<T>,
) -> Result<T>
pub unsafe fn with_gpu_access<T>( &self, stream: &mut Stream, accesses: &[GpuAccess], callback: impl FnOnce(&mut Stream, &[View<'_>]) -> Result<T>, ) -> Result<T>
Run existing Stream operations against coordinated allocations at a stage
boundary. Reservations are allocation-wide; conflicting submitted work
completes first. A live host mapping or another handoff returns Busy.
The supplied stream is synchronized before releasing the reservations,
including when the callback fails or panics.
§Safety
Declare every access to these allocations accurately. The callback must not retain pointers, buffers, or recorded uses, and must submit all such work on the supplied stream. No work may escape onto another stream. Kernel dispatch still carries the low-level GPU API’s safety contract.
Source§impl Runtime
impl Runtime
Sourcepub fn start_trace(&self, event_capacity: usize) -> Result<()>
pub fn start_trace(&self, event_capacity: usize) -> Result<()>
Begin an opt-in bounded trace of host-observed native-region latencies.
Sourcepub fn finish_trace(&self) -> Option<ExecutionTrace>
pub fn finish_trace(&self) -> Option<ExecutionTrace>
Stop capture without waiting for pending work. Wait for its completions first when the capture should include the entire request.
Sourcepub fn owns(&self, view: &BufferView) -> bool
pub fn owns(&self, view: &BufferView) -> bool
Whether a view belongs to this runtime’s allocation and scheduling domain.
Sourcepub fn same_domain(&self, other: &Self) -> bool
pub fn same_domain(&self, other: &Self) -> bool
Whether two handles share the same allocation and scheduling domain.
Sourcepub fn memory_budget(&self) -> Option<&MemoryBudget>
pub fn memory_budget(&self) -> Option<&MemoryBudget>
Shared allocation ceiling, if selected at construction. Native clients can attach it to their streams before loading weights or workspace.
Sourcepub fn new() -> Result<Self>
pub fn new() -> Result<Self>
Create a runtime. Native devices are opened lazily when requested.
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 with_options(options: RuntimeOptions) -> Result<Self>
pub fn with_options(options: RuntimeOptions) -> Result<Self>
Create a runtime with bounded pending submission and graph capacities.
Sourcepub fn gpu(&self) -> Result<GpuDevice>
pub fn gpu(&self) -> Result<GpuDevice>
Open the selected GPU and return its actual target.
Examples found in repository?
21fn gpu_kernel(
22 runtime: &Runtime,
23 source: &str,
24 symbol: &str,
25 config: &[(&str, String)],
26 elements: usize,
27 bindings: Vec<BindingContract>,
28) -> Result<GpuKernel> {
29 let compiler = hrx::loom::Compiler::for_target(None, runtime.gpu()?.target())?;
30 let module = compiler.module(source);
31 let mut spec = hrx::loom::Specialization::new(symbol);
32 for (key, value) in config {
33 spec.set_config(*key, value.clone());
34 }
35 let artifact = module.compile(&spec)?;
36 let stream = hrx::gpu::Stream::open()?;
37 let raw = unsafe { stream.load_artifact(&artifact) }?;
38 let mut constants = hrx::gpu::Constants::new();
39 match raw.info().constant_byte_length {
40 8 => constants.push(elements as u32)?,
41 12 => constants.push(elements as u64)?,
42 _ => return Err(hrx::Error::Message("unexpected scalar ABI".into())),
43 }
44 constants.push(1f32)?;
45 unsafe {
46 runtime.load_gpu_kernel(
47 artifact.path(),
48 symbol,
49 [elements.div_ceil(256) as u32, 1, 1],
50 [256, 1, 1],
51 KernelContract {
52 bindings,
53 constants: constants.as_bytes().to_vec(),
54 },
55 )
56 }
57}Sourcepub fn npu(&self, index: i32) -> Result<NpuDevice>
pub fn npu(&self, index: i32) -> Result<NpuDevice>
Select an NPU. Opening its program validates actual device access.
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 allocate(
&self,
bytes: usize,
placement: MemoryPlacement,
) -> Result<Buffer>
pub fn allocate( &self, bytes: usize, placement: MemoryPlacement, ) -> Result<Buffer>
Allocate initialized storage. Shared memory is exported and imported once.
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 unsafe fn adopt_gpu_buffer(
&self,
buffer: Buffer,
placement: MemoryPlacement,
) -> Result<Buffer>
pub unsafe fn adopt_gpu_buffer( &self, buffer: Buffer, placement: MemoryPlacement, ) -> Result<Buffer>
Transfer an initialized allocation into this runtime, optionally importing it into an NPU program once. Only GpuLocal and Shared placement are accepted.
§Safety
All bytes must be initialized and prior uses complete. No old pointer, recorded graph or other external alias may access the allocation after adoption, except through this runtime’s scoped GPU handoff.
Sourcepub fn statistics(&self) -> Statistics
pub fn statistics(&self) -> Statistics
Observe allocation, transfer, and execution counters.
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 fn graph(&self) -> Graph
pub fn graph(&self) -> Graph
Start describing a reusable pipeline in this allocation domain.
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 unsafe fn load_gpu_kernel(
&self,
path: impl AsRef<Path>,
symbol: &str,
grid: [u32; 3],
block: [u32; 3],
contract: KernelContract,
) -> Result<GpuKernel>
pub unsafe fn load_gpu_kernel( &self, path: impl AsRef<Path>, symbol: &str, grid: [u32; 3], block: [u32; 3], contract: KernelContract, ) -> Result<GpuKernel>
Load a fixed GPU specialization with its trusted memory contract.
§Safety
The code must obey the contract for this grid/block and every accepted binding. Kernel memory access is not sandboxed by native binding lengths.
Examples found in repository?
21fn gpu_kernel(
22 runtime: &Runtime,
23 source: &str,
24 symbol: &str,
25 config: &[(&str, String)],
26 elements: usize,
27 bindings: Vec<BindingContract>,
28) -> Result<GpuKernel> {
29 let compiler = hrx::loom::Compiler::for_target(None, runtime.gpu()?.target())?;
30 let module = compiler.module(source);
31 let mut spec = hrx::loom::Specialization::new(symbol);
32 for (key, value) in config {
33 spec.set_config(*key, value.clone());
34 }
35 let artifact = module.compile(&spec)?;
36 let stream = hrx::gpu::Stream::open()?;
37 let raw = unsafe { stream.load_artifact(&artifact) }?;
38 let mut constants = hrx::gpu::Constants::new();
39 match raw.info().constant_byte_length {
40 8 => constants.push(elements as u32)?,
41 12 => constants.push(elements as u64)?,
42 _ => return Err(hrx::Error::Message("unexpected scalar ABI".into())),
43 }
44 constants.push(1f32)?;
45 unsafe {
46 runtime.load_gpu_kernel(
47 artifact.path(),
48 symbol,
49 [elements.div_ceil(256) as u32, 1, 1],
50 [256, 1, 1],
51 KernelContract {
52 bindings,
53 constants: constants.as_bytes().to_vec(),
54 },
55 )
56 }
57}Sourcepub unsafe fn adopt_gpu_kernel(
&self,
raw: Kernel,
grid: [u32; 3],
block: [u32; 3],
contract: KernelContract,
) -> Result<GpuKernel>
pub unsafe fn adopt_gpu_kernel( &self, raw: Kernel, grid: [u32; 3], block: [u32; 3], contract: KernelContract, ) -> Result<GpuKernel>
Retain a loaded executable without loading or copying its code again.
§Safety
The kernel must obey the declared memory contract for this launch and
every accepted binding, as for Self::load_gpu_kernel.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Runtime
impl RefUnwindSafe for Runtime
impl Send for Runtime
impl Sync for Runtime
impl Unpin for Runtime
impl UnsafeUnpin for Runtime
impl UnwindSafe for Runtime
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> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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