Skip to main content

Target

Struct Target 

Source
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

Source

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.

Source

pub fn xdna() -> Self

Exact Strix Halo NPU5 deployment profile.

Examples found in repository?
examples/compile_npu.rs (line 9)
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}
Source

pub fn is_xdna(&self) -> bool

Whether this profile targets native XDNA execution.

Source

pub fn as_str(&self) -> &str

Exact architecture key.

Examples found in repository?
examples/gpu_npu_parallel.rs (line 139)
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_artifact = Compiler::for_target(None, gpu.target())?
143        .import_cxx(CxxSource::new(
144            "affine.cpp",
145            format!(
146                r#"
147#include <hip/hip_runtime.h>
148__global__ [[loom::workgroup_size(256, 1, 1), loom::workgroup_count({}, 1, 1)]]
149void affine(const int* input, int* output) {{
150  unsigned i = blockIdx.x * 256u + threadIdx.x;
151  output[i] = input[i] * 3 + 7;
152}}
153"#,
154                elements / 256
155            ),
156        ))?
157        .compile(&Specialization::new("affine"))?;
158    // SAFETY: the fixed launch covers exactly elements i32 values in each
159    // nonaliasing binding; bounded input values cannot overflow the arithmetic.
160    let gpu_kernel = unsafe {
161        runtime.load_gpu_kernel(
162            gpu_artifact.path(),
163            "affine",
164            [(elements / 256) as u32, 1, 1],
165            [256, 1, 1],
166            contract(&[(elements * 4, Access::Read), (elements * 4, Access::Write)]),
167        )
168    }?;
169    let npu_artifact = Compiler::for_target(None, npu.target())?
170        .module(include_str!("kernels/batched_gemm.xdna.loom"))
171        .compile(&Specialization::new("gemm").with_config("gemm.batch", matrices.to_string()))?;
172    // SAFETY: the pipeline consumes matrices pairs of 8x8 BF16 inputs and
173    // produces the same number of 8x8 FP32 outputs on one NPU column.
174    let npu_kernel = unsafe {
175        npu.load_artifact(
176            &npu_artifact,
177            1,
178            contract(&[
179                (matrices * 128, Access::Read),
180                (matrices * 128, Access::Read),
181                (matrices * 256, Access::Write),
182            ]),
183        )
184    }?;
185    let input = runtime.allocate(elements * 4, MemoryPlacement::GpuLocal)?;
186    let output = runtime.allocate(elements * 4, MemoryPlacement::GpuLocal)?;
187    let host_input = runtime.allocate(elements * 4, MemoryPlacement::HostVisible)?;
188    let host_output = runtime.allocate(elements * 4, MemoryPlacement::HostVisible)?;
189    let mut upload = runtime.graph();
190    upload.copy(input.view(), host_input.view())?;
191    let upload = upload.prepare()?;
192    let mut download = runtime.graph();
193    download.copy(host_output.view(), output.view())?;
194    let download = download.prepare()?;
195    let a = runtime.allocate(matrices * 128, MemoryPlacement::NpuLocal(npu.clone()))?;
196    let b = runtime.allocate(matrices * 128, MemoryPlacement::NpuLocal(npu.clone()))?;
197    let c = runtime.allocate(matrices * 256, MemoryPlacement::NpuLocal(npu))?;
198
199    let mut gpu_only = runtime.graph();
200    gpu_only.gpu(&gpu_kernel, &[input.view(), output.view()])?;
201    let gpu_only = gpu_only.prepare()?;
202    let mut npu_only = runtime.graph();
203    npu_only.npu(&npu_kernel, &[a.view(), b.view(), c.view()])?;
204    let npu_only = npu_only.prepare()?;
205
206    // Disjoint buffers imply no dependency edge. One submit admits both
207    // branches to separate GPU/NPU workers; one wait joins their completions.
208    let mut parallel = runtime.graph();
209    parallel.gpu(&gpu_kernel, &[input.view(), output.view()])?;
210    parallel.npu(&npu_kernel, &[a.view(), b.view(), c.view()])?;
211    let parallel = parallel.prepare()?;
212    for pass in 0..3 {
213        initialize(&host_input, &a, &b, pass * 2)?;
214        upload.submit()?.wait()?;
215        gpu_only.submit()?.wait()?;
216        npu_only.submit()?.wait()?;
217        download.submit()?.wait()?;
218        validate(&host_output, &c, pass * 2)?;
219        // Change both inputs so validation cannot accept the sequential run's
220        // old output if either parallel branch fails to write its result.
221        initialize(&host_input, &a, &b, pass * 2 + 1)?;
222        upload.submit()?.wait()?;
223        parallel.submit()?.wait()?;
224        download.submit()?.wait()?;
225        validate(&host_output, &c, pass * 2 + 1)?;
226    }
227
228    let before = runtime.statistics();
229    let mut sequential_ms = Vec::with_capacity(samples);
230    let mut parallel_ms = Vec::with_capacity(samples);
231    for sample in 0..samples {
232        // Alternate order to reduce systematic clock/thermal ordering bias.
233        for concurrent in if sample % 2 == 0 {
234            [false, true]
235        } else {
236            [true, false]
237        } {
238            if concurrent {
239                parallel_ms.push(completed_ms(&parallel)?);
240            } else {
241                let start = Instant::now();
242                gpu_only.submit()?.wait()?;
243                npu_only.submit()?.wait()?;
244                sequential_ms.push(start.elapsed().as_secs_f64() * 1000.0);
245            }
246        }
247    }
248    let after = runtime.statistics();
249    if before.allocations != after.allocations || before.imports != after.imports {
250        return Err(Error::Message(
251            "replay unexpectedly allocated or imported storage".into(),
252        ));
253    }
254    download.submit()?.wait()?;
255    validate(&host_output, &c, 5)?;
256    let sequential = median(&mut sequential_ms);
257    let concurrent = median(&mut parallel_ms);
258    println!("{elements} GPU elements + {matrices} NPU matrices; {samples} paired samples");
259    println!(
260        "sequential median: {sequential:.3} ms; parallel median: {concurrent:.3} ms; speedup: {:.2}x",
261        sequential / concurrent
262    );
263    println!(
264        "Both CPU references match exactly, including changed-input replays. No native allocations/imports during timed replay."
265    );
266
267    // Capture separately so tracing does not affect the benchmark samples.
268    runtime.start_trace(32)?;
269    for _ in 0..4 {
270        parallel.submit()?.wait()?;
271    }
272    let trace = runtime
273        .finish_trace()
274        .ok_or_else(|| Error::Message("missing execution trace".into()))?;
275    let overlap: f64 = trace
276        .events
277        .iter()
278        .filter(|e| e.lane == 1)
279        .flat_map(|gpu| {
280            trace.events.iter().filter(|e| e.lane == 3).map(move |npu| {
281                ((gpu.start_us + gpu.duration_us).min(npu.start_us + npu.duration_us)
282                    - gpu.start_us.max(npu.start_us))
283                .max(0.0)
284            })
285        })
286        .sum();
287    println!(
288        "GPU/NPU host-observed overlap across four replays: {overlap:.1} us (not device timestamps)"
289    );
290    if let Some(path) = trace_path {
291        std::fs::write(&path, trace.to_json()?)?;
292        println!("Trace: {}", path.display());
293    }
294    Ok(())
295}
Source

pub fn manifest_key(&self) -> String

Bundle platform and architecture identifier.

Trait Implementations§

Source§

impl Clone for Target

Source§

fn clone(&self) -> Self

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 Debug for Target

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Default for Target

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for Target

Source§

impl PartialEq for Target

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Target

Auto Trait Implementations§

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<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
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<E> ResultError for E
where E: Send + Debug + Sync,

Source§

impl<T> ResultType for T
where T: Send + Clone + Sync + Debug,

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