Skip to main content

View

Struct View 

Source
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>

Source

pub fn access(self, access: Access) -> AccessView<'a>

Declare how a graph operation accesses this view.

Source

pub fn read(self) -> AccessView<'a>

Declare read-only graph access.

Source

pub fn write(self) -> AccessView<'a>

Declare write-only graph access.

Source

pub fn read_write(self) -> AccessView<'a>

Declare graph access that reads and writes existing bytes.

Source§

impl<'a> View<'a>

Source

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
Hide additional 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}
Source

pub fn offset(self) -> usize

Byte offset from the start of the owning allocation.

Source

pub fn owner(self) -> &'a Buffer

The buffer handle from which this view was borrowed.

Source

pub fn len(self) -> usize

The bytes this view covers.

Source

pub fn is_empty(self) -> bool

Whether this view covers no bytes.

Trait Implementations§

Source§

impl<'a> Clone for View<'a>

Source§

fn clone(&self) -> View<'a>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'a> Copy for View<'a>

Source§

impl<'a> Debug for View<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DropFlavorWrapper<T> for T

Source§

type Flavor = MayDrop

The DropFlavor that wraps T into Self
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, W> HasTypeWitness<W> for T
where W: MakeTypeWitness<Arg = T>, T: ?Sized,

Source§

const WITNESS: W = W::MAKE

A constant of the type witness
Source§

impl<T> Identity for T
where T: ?Sized,

Source§

const TYPE_EQ: TypeEq<T, <T as Identity>::Type> = TypeEq::NEW

Proof that Self is the same type as Self::Type, provides methods for casting between Self and Self::Type.
Source§

type Type = T

The same type as Self, used to emulate type equality bounds (T == U) with associated type equality constraints (T: Identity<Type = U>).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

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
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more