pub struct View<'a> { /* private fields */ }Expand description
A binding into a device allocation, borrowed from it.
The borrow keeps the buffer alive and identifies the stream allowed to use this view. Any stream on the same device may use it, ordered by the caller.
Implementations§
Source§impl<'a> View<'a>
impl<'a> View<'a>
Sourcepub fn access(self, access: Access) -> AccessView<'a>
pub fn access(self, access: Access) -> AccessView<'a>
Declare how a graph operation accesses this view.
Sourcepub fn read(self) -> AccessView<'a>
pub fn read(self) -> AccessView<'a>
Declare read-only graph access.
Sourcepub fn write(self) -> AccessView<'a>
pub fn write(self) -> AccessView<'a>
Declare write-only graph access.
Sourcepub fn read_write(self) -> AccessView<'a>
pub fn read_write(self) -> AccessView<'a>
Declare graph access that reads and writes existing bytes.
Source§impl<'a> View<'a>
impl<'a> View<'a>
Sourcepub fn slice(self, offset: usize, length: usize) -> Result<Self>
pub fn slice(self, offset: usize, length: usize) -> Result<Self>
Borrow a region relative to this view, rejecting overflow and overruns. An empty region at the end of the view is valid.
Examples found in repository?
examples/import_probe.rs (line 63)
16fn main() -> Result<()> {
17 let mib: usize = match std::env::args().collect::<Vec<_>>().as_slice() {
18 [_] => 16,
19 [_, flag, value] if flag == "--mib" => value.parse().expect("--mib expects an integer"),
20 _ => {
21 eprintln!("usage: import_probe [--mib N]");
22 std::process::exit(2);
23 }
24 };
25 let bytes = mib * 1024 * 1024;
26 let layout = Layout::from_size_align(bytes, PAGE).expect("page-aligned layout");
27
28 // Owned for the whole scope below; the imported buffer never frees it.
29 let host = unsafe { alloc_zeroed(layout) };
30 assert!(!host.is_null(), "host allocation failed");
31 let result = (|| -> Result<()> {
32 let mut stream = Stream::open()?;
33 let buffer = match unsafe { stream.import_host(host.cast(), bytes) } {
34 Ok(buffer) => buffer,
35 Err(error) => {
36 println!("import: UNSUPPORTED ({error})");
37 return Ok(());
38 }
39 };
40 println!("import: ok ({mib} MiB at {host:p})");
41
42 // Prove the host pages start clear, so a match afterwards can only be the GPU.
43 let before = unsafe { std::slice::from_raw_parts(host, bytes) };
44 assert!(before.iter().all(|&b| b == 0), "host memory was not zeroed");
45
46 stream.fill(buffer.binding(), PATTERN)?;
47 stream.synchronize()?;
48
49 // Read the ORIGINAL host pointer -- no download, no staging buffer.
50 let after = unsafe { std::slice::from_raw_parts(host, bytes) };
51 let wrong = after.iter().filter(|&&b| b != PATTERN).count();
52 if wrong == 0 {
53 println!("gpu fill visible on host pointer: YES (zero-copy confirmed)");
54 } else {
55 println!("gpu fill visible on host pointer: NO ({wrong}/{bytes} bytes unwritten)");
56 }
57
58 // A device-side read of the same buffer must also see host writes.
59 let mirror = stream.allocate(bytes)?;
60 unsafe { std::slice::from_raw_parts_mut(host, bytes) }.fill(0x3c);
61 stream.copy(mirror.binding(), buffer.binding())?;
62 let mut check = [0u8; 64];
63 stream.read_blocking(mirror.binding().slice(0, check.len())?, &mut check)?;
64 let coherent = check.iter().all(|&b| b == 0x3c);
65 println!(
66 "host write visible to device: {}",
67 if coherent { "YES" } else { "NO" }
68 );
69 Ok(())
70 })();
71 unsafe { dealloc(host, layout) };
72 result
73}More examples
examples/unified_probe.rs (line 202)
81fn main() -> Result<()> {
82 let args: Vec<String> = std::env::args().skip(1).collect();
83 let (xclbin, mib) = match args.as_slice() {
84 [xclbin] => (xclbin.clone(), 4usize),
85 [xclbin, flag, value] if flag == "--mib" => (
86 xclbin.clone(),
87 value.parse().expect("--mib expects an integer"),
88 ),
89 _ => {
90 eprintln!("usage: unified_probe <xclbin> [--mib N]");
91 std::process::exit(2);
92 }
93 };
94 let bytes = mib * 1024 * 1024;
95 let layout = Layout::from_size_align(bytes, PAGE).expect("page-aligned layout");
96 let host = unsafe { alloc_zeroed(layout) };
97 assert!(!host.is_null(), "host allocation failed");
98
99 let result = (|| -> Result<()> {
100 println!("host allocation: {mib} MiB at {host:p}");
101
102 let context = match unsafe { dvxrt::Context::new(0, &xclbin) } {
103 Ok(context) => context,
104 Err(error) => {
105 println!("npu context: UNAVAILABLE ({error})");
106 return Ok(());
107 }
108 };
109 let group = context.group_id(ARG_A).expect("group id for A");
110
111 // 1. Can amdxdna pin pages we allocated? Reported either way -- if it can, either
112 // runtime may own the allocation; if it cannot, only the NPU's allocator may.
113 match unsafe { context.import_bo(host.cast(), bytes, dvxrt::BoKind::HostOnly, group) } {
114 Ok(_) => println!("npu userptr import (host-owned pages): YES"),
115 Err(error) => println!("npu userptr import (host-owned pages): NO ({error})"),
116 }
117
118 // 2. The direction that matters: let the NPU's allocator own the pages -- it is the
119 // more constrained of the two -- and import its host mapping into the GPU runtime.
120 let bo = context
121 .alloc_bo(bytes, dvxrt::BoKind::HostOnly, group)
122 .expect("allocate an NPU BO");
123 let shared = bo.map().expect("map the NPU BO");
124 println!("npu allocation mapped at {shared:p}");
125 assert_eq!(shared as usize % PAGE, 0, "NPU mapping is not page-aligned");
126
127 let mut stream = Stream::open()?;
128 let imported = unsafe { stream.import_host(shared.cast(), bytes) };
129 match &imported {
130 Ok(_) => println!("gpu import of the NPU mapping: YES"),
131 Err(error) => println!("gpu import of the NPU mapping: NO ({error})"),
132 }
133
134 // 3. If the shared mapping took, check that a GPU write lands in the NPU's BO.
135 if let Ok(buffer) = &imported {
136 stream.fill(buffer.binding(), GPU_PATTERN)?;
137 stream.synchronize()?;
138 bo.sync(false, bytes).expect("invalidate for device reads");
139 let mut seen = vec![0u8; 4096];
140 bo.read(&mut seen).expect("read through the NPU BO");
141 let wrong = seen.iter().filter(|&&b| b != GPU_PATTERN).count();
142 println!(
143 "gpu write -> npu read: {}",
144 if wrong == 0 {
145 "YES".into()
146 } else {
147 format!("NO ({wrong} bytes differ)")
148 }
149 );
150 }
151
152 // 3b. The dma-buf route: the GPU allocates, HSA exports the allocation as a
153 // dma-buf, and XRT imports that descriptor. Neither runtime copies, and unlike
154 // the host-pointer routes above neither driver has to pin the other's pages.
155 // The GPU-side import above HSA-locked these pages, so they are exportable.
156 let locked = unsafe { stream.import_host(host.cast(), bytes) };
157 let export = match &locked {
158 Ok(buffer) => {
159 stream.fill(buffer.binding(), GPU_PATTERN)?;
160 stream.synchronize()?;
161 export_dmabuf(host.cast(), bytes)
162 }
163 Err(error) => Err(format!("host pages are not GPU-resident: {error}")),
164 };
165 match export {
166 Ok((fd, size)) => {
167 println!("hsa dma-buf export of a GPU buffer: YES (fd {fd})");
168 match unsafe { context.import_dmabuf(fd, size) } {
169 Ok(imported) => {
170 println!("npu import of the GPU dma-buf: YES");
171 let mut seen = vec![0u8; 4096];
172 match imported.read(&mut seen) {
173 Ok(()) => {
174 let wrong = seen.iter().filter(|&&b| b != GPU_PATTERN).count();
175 println!(
176 " gpu write -> npu read over dma-buf: {}",
177 if wrong == 0 {
178 "YES".into()
179 } else {
180 format!("NO ({wrong} bytes differ)")
181 }
182 );
183 }
184 Err(error) => {
185 println!(" read through the imported BO failed: {error}")
186 }
187 }
188 }
189 Err(error) => println!("npu import of the GPU dma-buf: NO ({error})"),
190 }
191 }
192 Err(error) => println!("hsa dma-buf export of a GPU buffer: NO ({error})"),
193 }
194
195 // 4. And the reverse: the NPU's BO writes, the GPU reads.
196 if let Ok(buffer) = &imported {
197 bo.write(&vec![NPU_PATTERN; bytes])
198 .expect("write through the NPU BO");
199 let mirror = stream.allocate(bytes)?;
200 stream.copy(mirror.binding(), buffer.binding())?;
201 let mut check = [0u8; 64];
202 stream.read_blocking(mirror.binding().slice(0, check.len())?, &mut check)?;
203 let back = check.iter().all(|&b| b == NPU_PATTERN);
204 println!("npu write -> gpu read: {}", if back { "YES" } else { "NO" });
205 }
206 Ok(())
207 })();
208 unsafe { dealloc(host, layout) };
209 result
210}examples/stream_bench.rs (line 60)
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 // This repository owns the source; use its exact dimensions and scalar layout.
83 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 // The source accesses exactly the 256 bf16 elements in each buffer.
100 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 // Use disjoint allocations in both graphs. Even an idempotent store is a
110 // write: concurrent Euler dispatches cannot share their in/out buffer.
111 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 // The same disjoint workloads, this time allowing concurrent scheduling.
138 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}Trait Implementations§
Auto Trait Implementations§
impl<'a> !Send for View<'a>
impl<'a> !Sync for View<'a>
impl<'a> Freeze for View<'a>
impl<'a> RefUnwindSafe for View<'a>
impl<'a> Unpin for View<'a>
impl<'a> UnsafeUnpin for View<'a>
impl<'a> UnwindSafe for View<'a>
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
Mutably borrows from an owned value. Read more
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> ⓘ
Converts
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> ⓘ
Converts
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