pub struct Module { /* private fields */ }Expand description
Immutable source plus compiler ownership. Clones share the frozen native index.
Implementations§
Source§impl Module
impl Module
Sourcepub fn key(&self, spec: &Specialization) -> Result<String>
pub fn key(&self, spec: &Specialization) -> Result<String>
Cache identity including compiler, pipeline contract, source and specialization.
Sourcepub fn compile(&self, spec: &Specialization) -> Result<Artifact>
pub fn compile(&self, spec: &Specialization) -> Result<Artifact>
Compile in process or return verified cached bytes. Publication is atomic and serialized per key across threads and processes; failures are retryable.
Examples found in repository?
examples/compile_npu.rs (lines 11-15)
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}More examples
examples/gemm_pipeline.rs (line 37)
23fn gpu_kernel(
24 runtime: &Runtime,
25 source: &str,
26 symbol: &str,
27 config: &[(&str, String)],
28 elements: usize,
29 bindings: Vec<BindingContract>,
30) -> Result<GpuKernel> {
31 let compiler = hrx::loom::Compiler::for_target(None, runtime.gpu()?.target())?;
32 let module = compiler.module(source);
33 let mut spec = hrx::loom::Specialization::new(symbol);
34 for (key, value) in config {
35 spec.set_config(*key, value.clone());
36 }
37 let artifact = module.compile(&spec)?;
38 let stream = hrx::gpu::Stream::open()?;
39 let raw = unsafe { stream.load_artifact(&artifact) }?;
40 let mut constants = hrx::gpu::Constants::new();
41 match raw.info().constant_byte_length {
42 8 => constants.push(elements as u32)?,
43 12 => constants.push(elements as u64)?,
44 _ => return Err(hrx::Error::Message("unexpected scalar ABI".into())),
45 }
46 constants.push(1f32)?;
47 unsafe {
48 runtime.load_gpu_kernel(
49 artifact.path(),
50 symbol,
51 [elements.div_ceil(256) as u32, 1, 1],
52 [256, 1, 1],
53 KernelContract {
54 bindings,
55 constants: constants.as_bytes().to_vec(),
56 },
57 )
58 }
59}
60fn main() -> Result<()> {
61 let args: Vec<_> = std::env::args().collect();
62 let args = if args.len() == 1 {
63 vec![
64 args[0].clone(),
65 String::new(),
66 "gemm".into(),
67 "8".into(),
68 "8".into(),
69 "8".into(),
70 ]
71 } else {
72 args
73 };
74 if args.len() != 6 {
75 return Err(hrx::Error::Message(
76 "usage: gemm_pipeline <gemm.loom> <symbol> M K N".into(),
77 ));
78 }
79 let parse = |i: usize| {
80 args[i]
81 .parse::<usize>()
82 .map_err(|e| hrx::Error::Message(e.to_string()))
83 };
84 let (m, k, n) = (parse(3)?, parse(4)?, parse(5)?);
85 let extent = |a: usize, b: usize, width: usize| {
86 a.checked_mul(b)
87 .filter(|&count| count > 0 && count <= (1 << 30))
88 .and_then(|count| count.checked_mul(width))
89 .ok_or_else(|| hrx::Error::Message("invalid matrix extent".into()))
90 };
91 let (a_bytes, b_bytes, c_bytes) = (extent(m, k, 2)?, extent(k, n, 2)?, extent(m, n, 4)?);
92 let runtime = Runtime::new()?;
93 let device = runtime.npu(0)?;
94 let (source, spec) = if args[1].is_empty() {
95 (
96 include_str!("../tests/kernels/gemm_bf16.xdna.loom").to_string(),
97 hrx::loom::Specialization::new("gemm"),
98 )
99 } else {
100 (
101 std::fs::read_to_string(&args[1])?,
102 hrx::loom::Specialization::new(&args[2])
103 .with_config("gemm.m", m.to_string())
104 .with_config("gemm.k", k.to_string())
105 .with_config("gemm.n", n.to_string()),
106 )
107 };
108 let artifact = hrx::loom::Compiler::for_target(None, device.target())?
109 .module(&source)
110 .compile(&spec)?;
111 let npu = unsafe {
112 device.load_artifact(
113 &artifact,
114 1,
115 KernelContract {
116 bindings: vec![
117 binding(a_bytes, Access::Read),
118 binding(b_bytes, Access::Read),
119 binding(c_bytes, Access::Write),
120 ],
121 constants: vec![],
122 },
123 )
124 }?;
125 let prep = gpu_kernel(
126 &runtime,
127 include_str!("../tests/kernels/euler.loom"),
128 "krea2_euler",
129 &[
130 ("krea2.euler.grid_x", (m * k).div_ceil(256).to_string()),
131 ("krea2.euler.grid_y", "1".into()),
132 ],
133 m * k,
134 vec![
135 binding(a_bytes, Access::ReadWrite),
136 binding(a_bytes, Access::Read),
137 ],
138 )?;
139 let epilogue = gpu_kernel(
140 &runtime,
141 include_str!("../tests/kernels/add_f32.loom"),
142 "add_f32",
143 &[("add.grid", (m * n).div_ceil(256).to_string())],
144 m * n,
145 vec![binding(c_bytes, Access::ReadWrite)],
146 )?;
147 let allocate = |bytes| runtime.allocate(bytes, MemoryPlacement::Shared(device.clone()));
148 let weights = allocate(b_bytes)?;
149 let ones = allocate(a_bytes)?;
150 for buffer in [&weights, &ones] {
151 for word in buffer.map_write()?.as_chunks_mut::<2>().0 {
152 word.copy_from_slice(&0x3f80u16.to_le_bytes());
153 }
154 }
155 let prepare = || -> Result<(ExecutableGraph, Buffer)> {
156 let a = allocate(a_bytes)?;
157 let c = allocate(c_bytes)?;
158 let mut graph = runtime.graph();
159 graph.fill(a.view(), 0)?;
160 graph.gpu(&prep, &[a.view(), ones.view()])?;
161 graph.npu(&npu, &[a.view(), weights.view(), c.view()])?;
162 graph.gpu(&epilogue, &[c.view()])?;
163 Ok((graph.prepare()?, c))
164 };
165 let (first, c0) = prepare()?;
166 let (second, c1) = prepare()?;
167 for _ in 0..5 {
168 first.submit()?.wait()?;
169 second.submit()?.wait()?;
170 }
171 let before = runtime.statistics();
172 let mut latency = Vec::with_capacity(50);
173 for _ in 0..50 {
174 let start = Instant::now();
175 first.submit()?.wait()?;
176 latency.push(start.elapsed().as_secs_f64());
177 }
178 let start = Instant::now();
179 for _ in 0..50 {
180 let a = first.submit()?;
181 let b = second.submit()?;
182 a.wait()?;
183 b.wait()?;
184 }
185 let requests_per_second = 100.0 / start.elapsed().as_secs_f64();
186 for c in [&c0, &c1] {
187 if c.map_read()?
188 .as_chunks::<4>()
189 .0
190 .iter()
191 .any(|v| f32::from_le_bytes(*v) != k as f32 + 1.0)
192 {
193 return Err(hrx::Error::Message("GEMM pipeline output mismatch".into()));
194 }
195 }
196 let after = runtime.statistics();
197 assert_eq!(before.allocations, after.allocations);
198 assert_eq!(before.imports, after.imports);
199 assert_eq!(after.copied_bytes, 0);
200 latency.sort_by(f64::total_cmp);
201 println!(
202 "M={m} K={k} N={n} p50_ms={:.3} p95_ms={:.3} pipelined_requests_s={requests_per_second:.2}",
203 percentile(&latency, 50) * 1e3,
204 percentile(&latency, 95) * 1e3
205 );
206 println!("{}", serde_json::to_string(&after)?);
207 Ok(())
208}examples/patch_bench.rs (line 285)
279fn prepare(
280 stream: &mut Stream,
281 compiler: &Compiler,
282 case: &Case,
283 shared: Option<std::sync::Arc<Vec<Buffer>>>,
284) -> Result<Arm> {
285 let artifact = compiler.module(case.source).compile(&case.spec)?;
286 let kernel = unsafe { stream.load_artifact(&artifact) }?;
287 let constants = Constants::indices(&kernel, &case.indices)?;
288 let buffers = if let Some(buffers) = shared {
289 buffers
290 } else {
291 std::sync::Arc::new(
292 case.data
293 .iter()
294 .map(|data| {
295 let buffer = stream.allocate(data.len())?;
296 stream.upload_blocking(buffer.binding(), data)?;
297 Ok(buffer)
298 })
299 .collect::<Result<Vec<_>>>()?,
300 )
301 };
302 let bindings: Vec<_> = buffers.iter().map(Buffer::binding).collect();
303 let mut graph = stream.graph()?;
304 let mut prior = None;
305 for _ in 0..32 {
306 prior = Some(unsafe {
307 graph.dispatch(
308 prior.as_slice(),
309 &kernel,
310 case.grid,
311 case.block,
312 &constants,
313 &bindings,
314 )
315 }?);
316 }
317 let identity = serde_json::json!({"compiler":artifact.compiler_identity(), "artifact":hrx::bundle::digest(artifact.bytes()),
318 "report":artifact.report().map(|report| report.json())});
319 Ok(Arm {
320 graph: graph.finish()?,
321 buffers,
322 identity,
323 })
324}examples/startup_bench.rs (line 54)
21fn main() -> Result<()> {
22 let compiler_only = match std::env::args().nth(1).as_deref() {
23 None => false,
24 Some("--compiler-only") => true,
25 Some(_) => {
26 return Err(hrx::Error::Message(
27 "usage: startup_bench [--compiler-only]".into(),
28 ));
29 }
30 };
31 let initial = memory()?;
32 let start = Instant::now();
33 let stream = if compiler_only {
34 None
35 } else {
36 Some(Stream::open()?)
37 };
38 let open_ms = stream
39 .as_ref()
40 .map(|_| start.elapsed().as_secs_f64() * 1000.0);
41 let after_open = if compiler_only { None } else { Some(memory()?) };
42 let start = Instant::now();
43 let compiler = match &stream {
44 Some(stream) => hrx::loom::Compiler::for_stream(None, stream)?,
45 None => hrx::loom::Compiler::resolve(None)?,
46 };
47 let compiler_ms = start.elapsed().as_secs_f64() * 1000.0;
48 let after_compiler = memory()?;
49 let module = compiler.module(include_str!("../tests/kernels/euler.loom"));
50 let mut spec = Specialization::new("krea2_euler");
51 spec.set_config("krea2.euler.grid_x", "1");
52 spec.set_config("krea2.euler.grid_y", "1");
53 let start = Instant::now();
54 let artifact = module.compile(&spec)?;
55 let first_compile_ms = start.elapsed().as_secs_f64() * 1000.0;
56 let after_compile = memory()?;
57 let start = Instant::now();
58 let cached = module.compile(&spec)?;
59 let cache_hit_ms = start.elapsed().as_secs_f64() * 1000.0;
60 assert_eq!(artifact.bytes(), cached.bytes());
61 println!(
62 "{}",
63 json!({"compiler_only": compiler_only, "open_ms": open_ms, "compiler_ms": compiler_ms,
64 "first_compile_ms": first_compile_ms, "cache_hit_ms": cache_hit_ms,
65 "initial": initial, "after_open": after_open,
66 "after_compiler": after_compiler, "after_compile": after_compile,
67 "compiler_identity": artifact.compiler_identity()})
68 );
69 Ok(())
70}examples/shared_roundtrip.rs (lines 30-33)
8fn main() -> Result<()> {
9 let args: Vec<_> = std::env::args().collect();
10 if args.len() > 2 {
11 return Err(hrx::Error::Message(
12 "usage: shared_roundtrip [bytes]".into(),
13 ));
14 }
15 let bytes = args
16 .get(1)
17 .map(String::as_str)
18 .unwrap_or("1048576")
19 .parse::<usize>()
20 .map_err(|e| hrx::Error::Message(e.to_string()))?;
21 if bytes == 0 || bytes % 4096 != 0 || bytes > 64 * 1024 * 1024 {
22 return Err(hrx::Error::Message(
23 "bytes must be a multiple of 4096 in 4096..=67108864".into(),
24 ));
25 }
26 let runtime = Runtime::new()?;
27 let device = runtime.npu(0)?;
28 let artifact = hrx::loom::Compiler::for_target(None, device.target())?
29 .module(include_str!("../tests/kernels/copy.xdna.loom"))
30 .compile(
31 &hrx::loom::Specialization::new("copy")
32 .with_config("copy.packets", (bytes / 4096).to_string()),
33 )?;
34 let binding = |bytes, access| BindingContract {
35 bytes,
36 access,
37 alignment: 4,
38 layout: "u32 passthrough".into(),
39 };
40 let contract = KernelContract {
41 bindings: vec![binding(bytes, Access::Read), binding(bytes, Access::Write)],
42 constants: vec![],
43 };
44 let kernel = unsafe { device.load_artifact(&artifact, 1, contract) }?;
45 let a = runtime.allocate(bytes, MemoryPlacement::Shared(device.clone()))?;
46 let c = runtime.allocate(bytes, MemoryPlacement::Shared(device.clone()))?;
47 let result = runtime.allocate(bytes, MemoryPlacement::Shared(device.clone()))?;
48 let mut graph = runtime.graph();
49 graph.fill(a.view(), 0x5a)?;
50 graph.npu(&kernel, &[a.view(), c.view()])?;
51 graph.copy(result.view(), c.view())?;
52 let graph = graph.prepare()?;
53 for _ in 0..10 {
54 graph.submit()?.wait()?;
55 let mapped = result.map_read()?;
56 if mapped.iter().any(|&b| b != 0x5a) {
57 return Err(hrx::Error::Message("GPU/NPU roundtrip mismatch".into()));
58 }
59 }
60 println!(
61 "GPU -> NPU -> GPU: all {bytes} bytes correct, 10 executions, {} prepared regions",
62 graph.regions()
63 );
64 Ok(())
65}examples/shared_bench.rs (lines 29-32)
9fn main() -> Result<()> {
10 let args: Vec<_> = std::env::args().collect();
11 if args.len() > 2 {
12 return Err(hrx::Error::Message("usage: shared_bench [bytes]".into()));
13 }
14 let bytes = args
15 .get(1)
16 .map(String::as_str)
17 .unwrap_or("1048576")
18 .parse::<usize>()
19 .map_err(|e| hrx::Error::Message(e.to_string()))?;
20 if bytes == 0 || bytes % 4096 != 0 || bytes > 64 * 1024 * 1024 {
21 return Err(hrx::Error::Message(
22 "bytes must be a multiple of 4096 in 4096..=67108864".into(),
23 ));
24 }
25 let runtime = Runtime::new()?;
26 let device = runtime.npu(0)?;
27 let artifact = hrx::loom::Compiler::for_target(None, device.target())?
28 .module(include_str!("../tests/kernels/copy.xdna.loom"))
29 .compile(
30 &hrx::loom::Specialization::new("copy")
31 .with_config("copy.packets", (bytes / 4096).to_string()),
32 )?;
33 let binding = |bytes, access| BindingContract {
34 bytes,
35 access,
36 alignment: 4,
37 layout: "passthrough".into(),
38 };
39 let kernel = unsafe {
40 device.load_artifact(
41 &artifact,
42 1,
43 KernelContract {
44 bindings: vec![binding(bytes, Access::Read), binding(bytes, Access::Write)],
45 constants: vec![],
46 },
47 )
48 }?;
49 let a = runtime.allocate(bytes, MemoryPlacement::Shared(device.clone()))?;
50 let c = runtime.allocate(bytes, MemoryPlacement::Shared(device.clone()))?;
51 let mut graph = runtime.graph();
52 graph.fill(a.view(), 0x35)?;
53 graph.npu(&kernel, &[a.view(), c.view()])?;
54 let graph = graph.prepare()?;
55 for _ in 0..5 {
56 graph.submit()?.wait()?;
57 }
58 let mut samples = Vec::new();
59 for _ in 0..100 {
60 let start = Instant::now();
61 graph.submit()?.wait()?;
62 samples.push(start.elapsed().as_secs_f64());
63 }
64 if c.map_read()?.iter().any(|&b| b != 0x35) {
65 return Err(hrx::Error::Message("incorrect benchmark output".into()));
66 }
67 samples.sort_by(f64::total_cmp);
68 println!(
69 "bytes={bytes} p50_us={:.2} p95_us={:.2}",
70 percentile(&samples, 50) * 1e6,
71 percentile(&samples, 95) * 1e6
72 );
73 Ok(())
74}Additional examples can be found in:
Sourcepub fn compile_traced(
&self,
spec: &Specialization,
options: &TraceOptions,
writer: &mut dyn Write,
) -> Result<Artifact>
pub fn compile_traced( &self, spec: &Specialization, options: &TraceOptions, writer: &mut dyn Write, ) -> Result<Artifact>
Recompile and stream compiler-owned pass traces, even on a cache hit. A writer error, panic, or exhausted byte limit aborts compilation and leaves any existing cached artifact intact. The writer is not retained.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Module
impl RefUnwindSafe for Module
impl Send for Module
impl Sync for Module
impl Unpin for Module
impl UnsafeUnpin for Module
impl UnwindSafe for Module
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