pub struct Device { /* private fields */ }Expand description
An owned GPU address and execution domain.
Implementations§
Source§impl Device
impl Device
Sourcepub fn open(index: i32) -> Result<Self>
pub fn open(index: i32) -> Result<Self>
Activate an exact GPU ordinal in the native provider.
Examples found in repository?
99fn main() -> Result<()> {
100 let args: Vec<_> = std::env::args().skip(1).collect();
101 let parse = |value: &str| value.parse::<usize>().ok().filter(|&n| n > 0);
102 let queues = args.first().and_then(|arg| parse(arg));
103 let samples = args.get(1).map_or(Some(15), |arg| parse(arg));
104 let (queues, samples) = match (queues, samples, args.len()) {
105 (Some(queues), Some(samples), 1..=2) => (queues, samples),
106 _ => {
107 return Err(hrx::Error::Message(
108 "usage: queue_pool_bench QUEUES [SAMPLES] (positive integers)".into(),
109 ));
110 }
111 };
112 let device = Device::open(0)?;
113 device.set_stream_queue_count(queues)?;
114 let start = Instant::now();
115 let streams = (0..STREAMS)
116 .map(|_| device.stream())
117 .collect::<Result<Vec<_>>>()?;
118 let creation_ms = milliseconds(start);
119 // Keep these streams alive so every case uses the same already-created queues.
120 let mut small = (0..STREAMS)
121 .map(|_| Lane::new(&device, 4096))
122 .collect::<Result<Vec<_>>>()?;
123 let small_batch = 1024;
124 let small_times = throughput(&mut small, samples, small_batch)?;
125 let threaded_times = concurrent(&mut small, samples, small_batch)?;
126 let mut idle_latency = Vec::new();
127 for round in 0..samples + WARMUP {
128 for lane in &mut small {
129 let start = Instant::now();
130 lane.enqueue()?;
131 lane.stream.synchronize()?;
132 if round >= WARMUP {
133 idle_latency.push(milliseconds(start));
134 }
135 }
136 }
137 // A 1 GiB aggregate working set exceeds the on-device caches on gfx1151.
138 let bulk_bytes = 32 * 1024 * 1024;
139 let bulk_batch = 16;
140 let mut bulk = (0..STREAMS)
141 .map(|_| Lane::new(&device, bulk_bytes))
142 .collect::<Result<Vec<_>>>()?;
143 let bulk_times = throughput(&mut bulk, samples, bulk_batch)?;
144 let mut loaded_latency = Vec::new();
145 for round in 0..samples + WARMUP {
146 // Probe every queue position. Enqueue a bulk backlog on all streams,
147 // then measure one small copy's completion before draining the backlog.
148 for probe in &mut small {
149 for _ in 0..4 {
150 for lane in &bulk {
151 lane.enqueue()?;
152 }
153 }
154 let start = Instant::now();
155 probe.enqueue()?;
156 probe.stream.synchronize()?;
157 if round >= WARMUP {
158 loaded_latency.push(milliseconds(start));
159 }
160 for lane in &mut bulk {
161 lane.stream.synchronize()?;
162 }
163 }
164 }
165 for lane in small.iter_mut().chain(bulk.iter_mut()) {
166 lane.check()?;
167 }
168 let idle = Distribution::from_samples(idle_latency)?;
169 let loaded = Distribution::from_samples(loaded_latency)?;
170 println!(
171 "{}",
172 json!({
173 "target": device.target().as_str(), "queues": queues, "streams": STREAMS,
174 "samples": samples, "warmup": WARMUP, "stream_creation_ms": creation_ms,
175 "small_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / small_times.median_ms,
176 "threaded_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / threaded_times.median_ms,
177 "bulk_copy_gb_s": 2.0 * bulk_bytes as f64 * STREAMS as f64 * bulk_batch as f64 / (bulk_times.median_ms * 1e6),
178 "small_batch_median_ms": small_times.median_ms, "small_batch_p95_ms": small_times.p95_ms,
179 "threaded_batch_median_ms": threaded_times.median_ms, "threaded_batch_p95_ms": threaded_times.p95_ms,
180 "bulk_batch_median_ms": bulk_times.median_ms, "bulk_batch_p95_ms": bulk_times.p95_ms,
181 "idle_latency_median_us": idle.median_ms * 1_000.0, "idle_latency_p95_us": idle.p95_ms * 1_000.0,
182 "loaded_latency_median_us": loaded.median_ms * 1_000.0, "loaded_latency_p95_us": loaded.p95_ms * 1_000.0,
183 })
184 );
185 drop(streams);
186 Ok(())
187}Sourcepub fn open_with_lifetime(index: i32, lifetime: NativeLifetime) -> Result<Self>
pub fn open_with_lifetime(index: i32, lifetime: NativeLifetime) -> Result<Self>
Activate a GPU in the selected native ownership domain.
Sourcepub fn open_for(index: i32, target: &str) -> Result<Self>
pub fn open_for(index: i32, target: &str) -> Result<Self>
Require the workload’s exact target before creating execution resources.
Sourcepub fn target(&self) -> &Target
pub fn target(&self) -> &Target
Exact compiler deployment key.
Examples found in repository?
99fn main() -> Result<()> {
100 let args: Vec<_> = std::env::args().skip(1).collect();
101 let parse = |value: &str| value.parse::<usize>().ok().filter(|&n| n > 0);
102 let queues = args.first().and_then(|arg| parse(arg));
103 let samples = args.get(1).map_or(Some(15), |arg| parse(arg));
104 let (queues, samples) = match (queues, samples, args.len()) {
105 (Some(queues), Some(samples), 1..=2) => (queues, samples),
106 _ => {
107 return Err(hrx::Error::Message(
108 "usage: queue_pool_bench QUEUES [SAMPLES] (positive integers)".into(),
109 ));
110 }
111 };
112 let device = Device::open(0)?;
113 device.set_stream_queue_count(queues)?;
114 let start = Instant::now();
115 let streams = (0..STREAMS)
116 .map(|_| device.stream())
117 .collect::<Result<Vec<_>>>()?;
118 let creation_ms = milliseconds(start);
119 // Keep these streams alive so every case uses the same already-created queues.
120 let mut small = (0..STREAMS)
121 .map(|_| Lane::new(&device, 4096))
122 .collect::<Result<Vec<_>>>()?;
123 let small_batch = 1024;
124 let small_times = throughput(&mut small, samples, small_batch)?;
125 let threaded_times = concurrent(&mut small, samples, small_batch)?;
126 let mut idle_latency = Vec::new();
127 for round in 0..samples + WARMUP {
128 for lane in &mut small {
129 let start = Instant::now();
130 lane.enqueue()?;
131 lane.stream.synchronize()?;
132 if round >= WARMUP {
133 idle_latency.push(milliseconds(start));
134 }
135 }
136 }
137 // A 1 GiB aggregate working set exceeds the on-device caches on gfx1151.
138 let bulk_bytes = 32 * 1024 * 1024;
139 let bulk_batch = 16;
140 let mut bulk = (0..STREAMS)
141 .map(|_| Lane::new(&device, bulk_bytes))
142 .collect::<Result<Vec<_>>>()?;
143 let bulk_times = throughput(&mut bulk, samples, bulk_batch)?;
144 let mut loaded_latency = Vec::new();
145 for round in 0..samples + WARMUP {
146 // Probe every queue position. Enqueue a bulk backlog on all streams,
147 // then measure one small copy's completion before draining the backlog.
148 for probe in &mut small {
149 for _ in 0..4 {
150 for lane in &bulk {
151 lane.enqueue()?;
152 }
153 }
154 let start = Instant::now();
155 probe.enqueue()?;
156 probe.stream.synchronize()?;
157 if round >= WARMUP {
158 loaded_latency.push(milliseconds(start));
159 }
160 for lane in &mut bulk {
161 lane.stream.synchronize()?;
162 }
163 }
164 }
165 for lane in small.iter_mut().chain(bulk.iter_mut()) {
166 lane.check()?;
167 }
168 let idle = Distribution::from_samples(idle_latency)?;
169 let loaded = Distribution::from_samples(loaded_latency)?;
170 println!(
171 "{}",
172 json!({
173 "target": device.target().as_str(), "queues": queues, "streams": STREAMS,
174 "samples": samples, "warmup": WARMUP, "stream_creation_ms": creation_ms,
175 "small_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / small_times.median_ms,
176 "threaded_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / threaded_times.median_ms,
177 "bulk_copy_gb_s": 2.0 * bulk_bytes as f64 * STREAMS as f64 * bulk_batch as f64 / (bulk_times.median_ms * 1e6),
178 "small_batch_median_ms": small_times.median_ms, "small_batch_p95_ms": small_times.p95_ms,
179 "threaded_batch_median_ms": threaded_times.median_ms, "threaded_batch_p95_ms": threaded_times.p95_ms,
180 "bulk_batch_median_ms": bulk_times.median_ms, "bulk_batch_p95_ms": bulk_times.p95_ms,
181 "idle_latency_median_us": idle.median_ms * 1_000.0, "idle_latency_p95_us": idle.p95_ms * 1_000.0,
182 "loaded_latency_median_us": loaded.median_ms * 1_000.0, "loaded_latency_p95_us": loaded.p95_ms * 1_000.0,
183 })
184 );
185 drop(streams);
186 Ok(())
187}Sourcepub fn set_stream_queue_count(&self, count: usize) -> Result<()>
pub fn set_stream_queue_count(&self, count: usize) -> Result<()>
Set the maximum number of native queues shared by this device’s streams.
The count must be positive. Call before creating the first stream on this
device; changing the count afterward returns Error::Busy. Repeating
the current count is allowed. Clones and reopened handles to this live
device share the setting, including streams created by Stream::open.
Queues are created lazily, and allocation can fail at the device’s limit.
Examples found in repository?
99fn main() -> Result<()> {
100 let args: Vec<_> = std::env::args().skip(1).collect();
101 let parse = |value: &str| value.parse::<usize>().ok().filter(|&n| n > 0);
102 let queues = args.first().and_then(|arg| parse(arg));
103 let samples = args.get(1).map_or(Some(15), |arg| parse(arg));
104 let (queues, samples) = match (queues, samples, args.len()) {
105 (Some(queues), Some(samples), 1..=2) => (queues, samples),
106 _ => {
107 return Err(hrx::Error::Message(
108 "usage: queue_pool_bench QUEUES [SAMPLES] (positive integers)".into(),
109 ));
110 }
111 };
112 let device = Device::open(0)?;
113 device.set_stream_queue_count(queues)?;
114 let start = Instant::now();
115 let streams = (0..STREAMS)
116 .map(|_| device.stream())
117 .collect::<Result<Vec<_>>>()?;
118 let creation_ms = milliseconds(start);
119 // Keep these streams alive so every case uses the same already-created queues.
120 let mut small = (0..STREAMS)
121 .map(|_| Lane::new(&device, 4096))
122 .collect::<Result<Vec<_>>>()?;
123 let small_batch = 1024;
124 let small_times = throughput(&mut small, samples, small_batch)?;
125 let threaded_times = concurrent(&mut small, samples, small_batch)?;
126 let mut idle_latency = Vec::new();
127 for round in 0..samples + WARMUP {
128 for lane in &mut small {
129 let start = Instant::now();
130 lane.enqueue()?;
131 lane.stream.synchronize()?;
132 if round >= WARMUP {
133 idle_latency.push(milliseconds(start));
134 }
135 }
136 }
137 // A 1 GiB aggregate working set exceeds the on-device caches on gfx1151.
138 let bulk_bytes = 32 * 1024 * 1024;
139 let bulk_batch = 16;
140 let mut bulk = (0..STREAMS)
141 .map(|_| Lane::new(&device, bulk_bytes))
142 .collect::<Result<Vec<_>>>()?;
143 let bulk_times = throughput(&mut bulk, samples, bulk_batch)?;
144 let mut loaded_latency = Vec::new();
145 for round in 0..samples + WARMUP {
146 // Probe every queue position. Enqueue a bulk backlog on all streams,
147 // then measure one small copy's completion before draining the backlog.
148 for probe in &mut small {
149 for _ in 0..4 {
150 for lane in &bulk {
151 lane.enqueue()?;
152 }
153 }
154 let start = Instant::now();
155 probe.enqueue()?;
156 probe.stream.synchronize()?;
157 if round >= WARMUP {
158 loaded_latency.push(milliseconds(start));
159 }
160 for lane in &mut bulk {
161 lane.stream.synchronize()?;
162 }
163 }
164 }
165 for lane in small.iter_mut().chain(bulk.iter_mut()) {
166 lane.check()?;
167 }
168 let idle = Distribution::from_samples(idle_latency)?;
169 let loaded = Distribution::from_samples(loaded_latency)?;
170 println!(
171 "{}",
172 json!({
173 "target": device.target().as_str(), "queues": queues, "streams": STREAMS,
174 "samples": samples, "warmup": WARMUP, "stream_creation_ms": creation_ms,
175 "small_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / small_times.median_ms,
176 "threaded_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / threaded_times.median_ms,
177 "bulk_copy_gb_s": 2.0 * bulk_bytes as f64 * STREAMS as f64 * bulk_batch as f64 / (bulk_times.median_ms * 1e6),
178 "small_batch_median_ms": small_times.median_ms, "small_batch_p95_ms": small_times.p95_ms,
179 "threaded_batch_median_ms": threaded_times.median_ms, "threaded_batch_p95_ms": threaded_times.p95_ms,
180 "bulk_batch_median_ms": bulk_times.median_ms, "bulk_batch_p95_ms": bulk_times.p95_ms,
181 "idle_latency_median_us": idle.median_ms * 1_000.0, "idle_latency_p95_us": idle.p95_ms * 1_000.0,
182 "loaded_latency_median_us": loaded.median_ms * 1_000.0, "loaded_latency_p95_us": loaded.p95_ms * 1_000.0,
183 })
184 );
185 drop(streams);
186 Ok(())
187}Sourcepub fn stream_queue_count(&self) -> Result<usize>
pub fn stream_queue_count(&self) -> Result<usize>
Maximum native queue count for this device’s streams (eight by default).
Sourcepub fn stream(&self) -> Result<Stream>
pub fn stream(&self) -> Result<Stream>
Create an ordered stream assigned to a bounded pool of native queues. Assignment is round-robin, without priority or load awareness. A long command or event wait can delay other streams assigned to the same queue.
Examples found in repository?
16 fn new(device: &Device, bytes: usize) -> Result<Self> {
17 let mut stream = device.stream()?;
18 let source = stream.allocate(bytes)?;
19 let destination = stream.allocate(bytes)?;
20 stream.fill(source.binding(), 0x3c)?;
21 stream.copy(destination.binding(), source.binding())?;
22 stream.synchronize()?;
23 Ok(Self {
24 stream,
25 source,
26 destination,
27 })
28 }
29 fn enqueue(&self) -> Result<()> {
30 self.stream
31 .copy(self.destination.binding(), self.source.binding())
32 }
33 fn check(&mut self) -> Result<()> {
34 let mut bytes = [0; 64];
35 self.stream
36 .read_blocking(self.destination.slice(0, 64), &mut bytes)?;
37 assert_eq!(bytes, [0x3c; 64]);
38 Ok(())
39 }
40}
41
42fn milliseconds(start: Instant) -> f64 {
43 start.elapsed().as_secs_f64() * 1_000.0
44}
45
46fn throughput(lanes: &mut [Lane], samples: usize, batch: usize) -> Result<Distribution> {
47 let mut timings = Vec::new();
48 for round in 0..samples + WARMUP {
49 let start = Instant::now();
50 for _ in 0..batch {
51 for lane in &*lanes {
52 lane.enqueue()?;
53 }
54 }
55 for lane in &mut *lanes {
56 lane.stream.synchronize()?;
57 }
58 if round >= WARMUP {
59 timings.push(milliseconds(start));
60 }
61 }
62 Distribution::from_samples(timings)
63}
64
65fn concurrent(lanes: &mut [Lane], samples: usize, batch: usize) -> Result<Distribution> {
66 let mut timings = Vec::new();
67 let barrier = Barrier::new(lanes.len());
68 for round in 0..samples + WARMUP {
69 // Creation and joining are excluded from each worker's elapsed time.
70 // The slowest worker determines the batch's throughput.
71 let elapsed = std::thread::scope(|scope| {
72 let workers: Vec<_> = lanes
73 .iter_mut()
74 .map(|lane| {
75 let barrier = &barrier;
76 scope.spawn(move || -> Result<f64> {
77 barrier.wait();
78 let start = Instant::now();
79 for _ in 0..batch {
80 lane.enqueue()?;
81 }
82 lane.stream.synchronize()?;
83 Ok(milliseconds(start))
84 })
85 })
86 .collect();
87 workers
88 .into_iter()
89 .map(|worker| worker.join().expect("benchmark worker panicked"))
90 .collect::<Result<Vec<_>>>()
91 })?;
92 if round >= WARMUP {
93 timings.push(elapsed.into_iter().fold(0.0, f64::max));
94 }
95 }
96 Distribution::from_samples(timings)
97}
98
99fn main() -> Result<()> {
100 let args: Vec<_> = std::env::args().skip(1).collect();
101 let parse = |value: &str| value.parse::<usize>().ok().filter(|&n| n > 0);
102 let queues = args.first().and_then(|arg| parse(arg));
103 let samples = args.get(1).map_or(Some(15), |arg| parse(arg));
104 let (queues, samples) = match (queues, samples, args.len()) {
105 (Some(queues), Some(samples), 1..=2) => (queues, samples),
106 _ => {
107 return Err(hrx::Error::Message(
108 "usage: queue_pool_bench QUEUES [SAMPLES] (positive integers)".into(),
109 ));
110 }
111 };
112 let device = Device::open(0)?;
113 device.set_stream_queue_count(queues)?;
114 let start = Instant::now();
115 let streams = (0..STREAMS)
116 .map(|_| device.stream())
117 .collect::<Result<Vec<_>>>()?;
118 let creation_ms = milliseconds(start);
119 // Keep these streams alive so every case uses the same already-created queues.
120 let mut small = (0..STREAMS)
121 .map(|_| Lane::new(&device, 4096))
122 .collect::<Result<Vec<_>>>()?;
123 let small_batch = 1024;
124 let small_times = throughput(&mut small, samples, small_batch)?;
125 let threaded_times = concurrent(&mut small, samples, small_batch)?;
126 let mut idle_latency = Vec::new();
127 for round in 0..samples + WARMUP {
128 for lane in &mut small {
129 let start = Instant::now();
130 lane.enqueue()?;
131 lane.stream.synchronize()?;
132 if round >= WARMUP {
133 idle_latency.push(milliseconds(start));
134 }
135 }
136 }
137 // A 1 GiB aggregate working set exceeds the on-device caches on gfx1151.
138 let bulk_bytes = 32 * 1024 * 1024;
139 let bulk_batch = 16;
140 let mut bulk = (0..STREAMS)
141 .map(|_| Lane::new(&device, bulk_bytes))
142 .collect::<Result<Vec<_>>>()?;
143 let bulk_times = throughput(&mut bulk, samples, bulk_batch)?;
144 let mut loaded_latency = Vec::new();
145 for round in 0..samples + WARMUP {
146 // Probe every queue position. Enqueue a bulk backlog on all streams,
147 // then measure one small copy's completion before draining the backlog.
148 for probe in &mut small {
149 for _ in 0..4 {
150 for lane in &bulk {
151 lane.enqueue()?;
152 }
153 }
154 let start = Instant::now();
155 probe.enqueue()?;
156 probe.stream.synchronize()?;
157 if round >= WARMUP {
158 loaded_latency.push(milliseconds(start));
159 }
160 for lane in &mut bulk {
161 lane.stream.synchronize()?;
162 }
163 }
164 }
165 for lane in small.iter_mut().chain(bulk.iter_mut()) {
166 lane.check()?;
167 }
168 let idle = Distribution::from_samples(idle_latency)?;
169 let loaded = Distribution::from_samples(loaded_latency)?;
170 println!(
171 "{}",
172 json!({
173 "target": device.target().as_str(), "queues": queues, "streams": STREAMS,
174 "samples": samples, "warmup": WARMUP, "stream_creation_ms": creation_ms,
175 "small_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / small_times.median_ms,
176 "threaded_commands_per_second": STREAMS as f64 * small_batch as f64 * 1_000.0 / threaded_times.median_ms,
177 "bulk_copy_gb_s": 2.0 * bulk_bytes as f64 * STREAMS as f64 * bulk_batch as f64 / (bulk_times.median_ms * 1e6),
178 "small_batch_median_ms": small_times.median_ms, "small_batch_p95_ms": small_times.p95_ms,
179 "threaded_batch_median_ms": threaded_times.median_ms, "threaded_batch_p95_ms": threaded_times.p95_ms,
180 "bulk_batch_median_ms": bulk_times.median_ms, "bulk_batch_p95_ms": bulk_times.p95_ms,
181 "idle_latency_median_us": idle.median_ms * 1_000.0, "idle_latency_p95_us": idle.p95_ms * 1_000.0,
182 "loaded_latency_median_us": loaded.median_ms * 1_000.0, "loaded_latency_p95_us": loaded.p95_ms * 1_000.0,
183 })
184 );
185 drop(streams);
186 Ok(())
187}Sourcepub fn stream_with_options(&self, options: StreamOptions) -> Result<Stream>
pub fn stream_with_options(&self, options: StreamOptions) -> Result<Stream>
Create a stream whose eager dispatches and recorded graphs use the selected engines. Engine changes fence on the host. SDMA selects coherent allocation backing.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Device
impl RefUnwindSafe for Device
impl Send for Device
impl Sync for Device
impl Unpin for Device
impl UnsafeUnpin for Device
impl UnwindSafe for Device
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