pub struct Target(/* private fields */);Expand description
An exact AMDGPU or XDNA deployment key.
This is the profile target: what a device reports and what the compiler
emits for. It is deliberately narrower than the target a Loom source file
names in amdgpu.target<...>, which accepts generic families such as
gfx11-generic. The two are chosen independently — generic source compiles
under a bare profile — so this type rejects generic names rather than
silently accepting one a device can never report.
Implementations§
Source§impl Target
impl Target
Sourcepub fn new(key: &str) -> Result<Self>
pub fn new(key: &str) -> Result<Self>
Validate an architecture key, for example gfx1100, gfx1151 or gfx942. Availability is determined by the selected native runtime and compiler. Keys must be bare architecture names, without feature suffixes.
Sourcepub fn xdna() -> Self
pub fn xdna() -> Self
Exact Strix Halo NPU5 deployment profile.
Examples found in repository?
7fn main() -> Result<()> {
8 let packets = std::env::args().nth(1).unwrap_or_else(|| "256".into());
9 let artifact = Compiler::for_target(None, &Target::xdna())?
10 .module(include_str!("../tests/kernels/copy.xdna.loom"))
11 .compile(
12 &Specialization::new("copy")
13 .with_config("copy.packets", packets)
14 .with_report(ReportMode::Summary),
15 )?;
16 println!("{}", artifact.path().display());
17 Ok(())
18}Sourcepub fn as_str(&self) -> &str
pub fn as_str(&self) -> &str
Exact architecture 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}More examples
104fn main() -> Result<()> {
105 let args: Vec<_> = std::env::args().skip(1).collect();
106 if args.len() > 4 || args.iter().any(|arg| arg == "--help") {
107 println!(
108 "usage: gpu_npu_parallel [gpu-elements=16777216] [npu-matrices=1024] [samples=21] [trace.json]"
109 );
110 return if args.len() <= 4 {
111 Ok(())
112 } else {
113 Err(Error::Message("too many arguments".into()))
114 };
115 }
116 let number = |index: usize, default: usize| -> Result<usize> {
117 args.get(index).map_or(Ok(default), |value| {
118 value
119 .parse()
120 .map_err(|_| Error::Message(format!("invalid integer: {value}")))
121 })
122 };
123 let elements = number(0, 1 << 24)?;
124 let matrices = number(1, 1024)?;
125 let samples = number(2, 21)?;
126 if !(256..=1 << 26).contains(&elements)
127 || !elements.is_multiple_of(256)
128 || !(1..=16384).contains(&matrices)
129 || !(3..=1000).contains(&samples)
130 {
131 return Err(Error::Message("gpu-elements must be a multiple of 256 in 256..=67108864; npu-matrices in 1..=16384; samples in 3..=1000".into()));
132 }
133 let trace_path = args.get(3).map(PathBuf::from);
134 let runtime = Runtime::new()?;
135 let gpu = runtime.gpu()?;
136 let npu = runtime.npu(0)?;
137 println!(
138 "GPU: {}; NPU: {}",
139 gpu.target().as_str(),
140 npu.target().as_str()
141 );
142 let gpu_compiler = Compiler::for_target(None, gpu.target())?;
143 let gpu_artifact = gpu_compiler
144 .import_cxx(CxxSource::new(
145 "affine.cpp",
146 format!(
147 r#"
148#include <hip/hip_runtime.h>
149__global__ [[loom::workgroup_size(256, 1, 1), loom::workgroup_count({}, 1, 1)]]
150void affine(const int* input, int* output) {{
151 unsigned i = blockIdx.x * 256u + threadIdx.x;
152 output[i] = input[i] * 3 + 7;
153}}
154"#,
155 elements / 256
156 ),
157 ))?
158 .compile(&Specialization::new("affine"))?;
159 // SAFETY: the fixed launch covers exactly elements i32 values in each
160 // nonaliasing binding; bounded input values cannot overflow the arithmetic.
161 let gpu_kernel = unsafe {
162 runtime.load_gpu_artifact(
163 &gpu_artifact,
164 &[],
165 contract(&[(elements * 4, Access::Read), (elements * 4, Access::Write)]),
166 )
167 }?;
168 let npu_artifact = Compiler::for_target(None, npu.target())?
169 .module(include_str!("kernels/batched_gemm.xdna.loom"))
170 .compile(&Specialization::new("gemm").with_config("gemm.batch", matrices.to_string()))?;
171 // SAFETY: the pipeline consumes matrices pairs of 8x8 BF16 inputs and
172 // produces the same number of 8x8 FP32 outputs on one NPU column.
173 let npu_kernel = unsafe {
174 npu.load_artifact(
175 &npu_artifact,
176 1,
177 contract(&[
178 (matrices * 128, Access::Read),
179 (matrices * 128, Access::Read),
180 (matrices * 256, Access::Write),
181 ]),
182 )
183 }?;
184 let input = runtime.allocate(elements * 4, MemoryPlacement::GpuLocal)?;
185 let output = runtime.allocate(elements * 4, MemoryPlacement::GpuLocal)?;
186 let host_input = runtime.allocate(elements * 4, MemoryPlacement::HostVisible)?;
187 let host_output = runtime.allocate(elements * 4, MemoryPlacement::HostVisible)?;
188 let mut upload = runtime.graph();
189 upload.copy(input.view(), host_input.view())?;
190 let upload = upload.prepare()?;
191 let mut download = runtime.graph();
192 download.copy(host_output.view(), output.view())?;
193 let download = download.prepare()?;
194 let a = runtime.allocate(matrices * 128, MemoryPlacement::NpuLocal(npu.clone()))?;
195 let b = runtime.allocate(matrices * 128, MemoryPlacement::NpuLocal(npu.clone()))?;
196 let c = runtime.allocate(matrices * 256, MemoryPlacement::NpuLocal(npu))?;
197
198 let mut gpu_only = runtime.graph();
199 gpu_only.gpu(&gpu_kernel, &[input.view(), output.view()])?;
200 let gpu_only = gpu_only.prepare()?;
201 let mut npu_only = runtime.graph();
202 npu_only.npu(&npu_kernel, &[a.view(), b.view(), c.view()])?;
203 let npu_only = npu_only.prepare()?;
204
205 // Disjoint buffers imply no dependency edge. One submit admits both
206 // branches to separate GPU/NPU workers; one wait joins their completions.
207 let mut parallel = runtime.graph();
208 parallel.gpu(&gpu_kernel, &[input.view(), output.view()])?;
209 parallel.npu(&npu_kernel, &[a.view(), b.view(), c.view()])?;
210 let parallel = parallel.prepare()?;
211 for pass in 0..3 {
212 initialize(&host_input, &a, &b, pass * 2)?;
213 upload.submit()?.wait()?;
214 gpu_only.submit()?.wait()?;
215 npu_only.submit()?.wait()?;
216 download.submit()?.wait()?;
217 validate(&host_output, &c, pass * 2)?;
218 // Change both inputs so validation cannot accept the sequential run's
219 // old output if either parallel branch fails to write its result.
220 initialize(&host_input, &a, &b, pass * 2 + 1)?;
221 upload.submit()?.wait()?;
222 parallel.submit()?.wait()?;
223 download.submit()?.wait()?;
224 validate(&host_output, &c, pass * 2 + 1)?;
225 }
226
227 let before = runtime.statistics();
228 let mut sequential_ms = Vec::with_capacity(samples);
229 let mut parallel_ms = Vec::with_capacity(samples);
230 for sample in 0..samples {
231 // Alternate order to reduce systematic clock/thermal ordering bias.
232 for concurrent in if sample % 2 == 0 {
233 [false, true]
234 } else {
235 [true, false]
236 } {
237 if concurrent {
238 parallel_ms.push(completed_ms(¶llel)?);
239 } else {
240 let start = Instant::now();
241 gpu_only.submit()?.wait()?;
242 npu_only.submit()?.wait()?;
243 sequential_ms.push(start.elapsed().as_secs_f64() * 1000.0);
244 }
245 }
246 }
247 let after = runtime.statistics();
248 if before.allocations != after.allocations || before.imports != after.imports {
249 return Err(Error::Message(
250 "replay unexpectedly allocated or imported storage".into(),
251 ));
252 }
253 download.submit()?.wait()?;
254 validate(&host_output, &c, 5)?;
255 let sequential = median(&mut sequential_ms);
256 let concurrent = median(&mut parallel_ms);
257 println!("{elements} GPU elements + {matrices} NPU matrices; {samples} paired samples");
258 println!(
259 "sequential median: {sequential:.3} ms; parallel median: {concurrent:.3} ms; speedup: {:.2}x",
260 sequential / concurrent
261 );
262 println!(
263 "Both CPU references match exactly, including changed-input replays. No native allocations/imports during timed replay."
264 );
265
266 // Capture separately so tracing does not affect the benchmark samples.
267 runtime.start_trace(32)?;
268 for _ in 0..4 {
269 parallel.submit()?.wait()?;
270 }
271 let trace = runtime
272 .finish_trace()
273 .ok_or_else(|| Error::Message("missing execution trace".into()))?;
274 let overlap: f64 = trace
275 .events
276 .iter()
277 .filter(|e| e.lane == 1)
278 .flat_map(|gpu| {
279 trace.events.iter().filter(|e| e.lane == 3).map(move |npu| {
280 ((gpu.start_us + gpu.duration_us).min(npu.start_us + npu.duration_us)
281 - gpu.start_us.max(npu.start_us))
282 .max(0.0)
283 })
284 })
285 .sum();
286 println!(
287 "GPU/NPU host-observed overlap across four replays: {overlap:.1} us (not device timestamps)"
288 );
289 if let Some(path) = trace_path {
290 std::fs::write(&path, trace.to_json()?)?;
291 println!("Trace: {}", path.display());
292 }
293 Ok(())
294}Sourcepub fn manifest_key(&self) -> String
pub fn manifest_key(&self) -> String
Bundle platform and architecture identifier.
Trait Implementations§
impl Eq for Target
impl StructuralPartialEq for Target
Auto Trait Implementations§
impl Freeze for Target
impl RefUnwindSafe for Target
impl Send for Target
impl Sync for Target
impl Unpin for Target
impl UnsafeUnpin for Target
impl UnwindSafe for Target
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<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.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