Skip to content

Commit 7089740

Browse files
committed
coop: add GPU test, API doc
1 parent 70d03f4 commit 7089740

15 files changed

Lines changed: 586 additions & 123 deletions

File tree

docs/api-specs/cooperative_matrix.md

Lines changed: 450 additions & 0 deletions
Large diffs are not rendered by default.

docs/api-specs/ray_tracing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ fn render(/*whatever args you need to render*/) {
7878
/* do other preparations on the TlasInstance.*/
7979
encoder.build_acceleration_structures(iter::empty(), iter::once(&tlas_package));
8080
/* more render code */
81-
queue.submit(Some(encoder.finish()));
81+
queue.submit([encoder.finish()]);
8282
}
8383
```
8484

examples/features/src/cooperative_matrix/README.md

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,26 @@
22

33
This example demonstrates how to use cooperative matrix operations (also known as tensor cores on NVIDIA GPUs) to perform efficient matrix multiplication on the GPU.
44

5-
## Overview
5+
For the full description of the cooperative matrix feature (supported configurations, WGSL types and operations, validation rules, and backend support), see the central API spec:
66

7-
Cooperative matrices allow a workgroup to collectively load, store, and perform matrix multiply-accumulate operations on small tiles of data. This enables hardware-accelerated matrix math that can be significantly faster than traditional element-wise approaches.
7+
- `docs/api-specs/cooperative_matrix.md`
88

9-
The example computes `C = A * B + C` where:
9+
## Example specifics
10+
11+
This example computes `C = A * B + C` where:
1012
- A is a 64×64 matrix
1113
- B is a 64×64 matrix
1214
- C is a 64×64 matrix (accumulator/result)
1315

14-
## Querying Supported Configurations
15-
16-
Before using cooperative matrices, you should query what configurations your hardware supports:
17-
18-
```rust
19-
let coop_props = adapter.cooperative_matrix_properties();
20-
for prop in &coop_props {
21-
println!(
22-
"{:?}x{:?}x{:?} - AB: {:?}, CR: {:?}",
23-
prop.m_size, prop.n_size, prop.k_size,
24-
prop.ab_type, prop.cr_type
25-
);
26-
}
27-
```
28-
29-
Each `CooperativeMatrixProperties` describes a supported configuration with:
30-
- `m_size`, `n_size`, `k_size`: Matrix dimensions as `naga::CooperativeSize` (M×K × K×N → M×N)
31-
- `ab_type`: Element type for input matrices A and B (as `naga::Scalar`)
32-
- `cr_type`: Element type for accumulator matrix C and the result
33-
- `saturating_accumulation`: Whether overflow clamping is supported
34-
35-
## Key Concepts
36-
37-
### Cooperative Matrix Types
38-
39-
In WGSL, cooperative matrices are declared with a specific size, element type, and role:
40-
41-
```wgsl
42-
coop_mat8x8<f32, A> // Matrix A (left operand)
43-
coop_mat8x8<f32, B> // Matrix B (right operand)
44-
coop_mat8x8<f32, C> // Matrix C (accumulator)
45-
```
46-
47-
The role (A, B, or C) determines how the matrix is used in multiply-accumulate operations.
48-
49-
### Operations
50-
51-
- `coopLoad<T>(pointer, stride)` - Cooperatively load a tile from memory
52-
- `coopStore(matrix, pointer, stride)` - Cooperatively store a tile to memory
53-
- `coopMultiplyAdd(a, b, c)` - Compute `a * b + c`
54-
55-
### Workgroup Cooperation
56-
57-
All threads in a workgroup must participate in cooperative matrix operations together. The workgroup size should match the cooperative matrix dimensions (8×8 in this example).
16+
The example:
17+
- Tiles the 64×64 matrices into cooperative matrix tiles (e.g. 8×8) and performs a tiled matmul
18+
- Uses a compute shader and compares GPU results against a CPU reference implementation
5819

5920
## Requirements
6021

61-
- GPU with cooperative matrix support:
62-
- Metal: Apple7+ (A14 chip) or Mac2+ (M1 chip) with MSL 2.3+
63-
- Supports 8x8 f32, 8x8 f16, and mixed precision (f16 inputs, f32 accumulator)
64-
- Vulkan: Requires VK_KHR_cooperative_matrix extension
65-
- Most NVIDIA/AMD GPUs support f16 at 16x16 sizes
66-
- 8x8 f32 support varies by hardware
67-
- `Features::EXPERIMENTAL_COOPERATIVE_MATRIX` must be enabled
68-
- Use `adapter.cooperative_matrix_properties()` to check available configurations
22+
- A GPU and backend that expose `Features::EXPERIMENTAL_COOPERATIVE_MATRIX`
23+
- A configuration returned from `adapter.cooperative_matrix_properties()` that matches the tile size and element types used by this example
24+
- See `docs/api-specs/cooperative_matrix.md` for details on hardware / backend support
6925

7026
## Running
7127

@@ -76,8 +32,5 @@ cargo run --bin wgpu-examples -- cooperative_matrix
7632
## Notes
7733

7834
- This is an experimental feature and may not work on all hardware
79-
- Always query `adapter.cooperative_matrix_properties()` to check what's supported
80-
- The 8x8 f32 matrix format is well supported on Metal (simdgroup matrix operations)
81-
- Vulkan support depends on hardware - most GPUs (NVIDIA, AMD) support f16 inputs at 16x16 sizes
8235
- The shader uses the standard `create_shader_module` with full validation
8336
- Results are verified against a CPU reference implementation

examples/features/src/cooperative_matrix/mod.rs

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,49 @@ async fn run() {
163163
.expect("Failed to create device")
164164
};
165165

166+
let results = execute(&device, &queue, config).await;
167+
168+
log::info!(
169+
"Matrix multiplication {M}x{K}x{N} completed using {} precision!",
170+
if use_f16 { "f16" } else { "f32" }
171+
);
172+
log::info!("Max error vs CPU reference: {:.6}", results.max_error);
173+
174+
if results.max_error < results.tolerance {
175+
log::info!(
176+
"✓ Results match CPU reference within tolerance ({})",
177+
results.tolerance
178+
);
179+
} else {
180+
log::warn!(
181+
"✗ Results differ from CPU reference (tolerance: {})",
182+
results.tolerance
183+
);
184+
}
185+
186+
// Print a small sample of the result
187+
log::info!("Sample of result matrix C (top-left 4x4):");
188+
for i in 0..4 {
189+
let row: Vec<String> = (0..4)
190+
.map(|j| format!("{:6.2}", results.matrix[i * N as usize + j]))
191+
.collect();
192+
log::info!(" [{}]", row.join(", "));
193+
}
194+
}
195+
196+
struct ExecuteResults {
197+
max_error: f32,
198+
tolerance: f32,
199+
matrix: Vec<f32>,
200+
}
201+
202+
async fn execute(
203+
device: &wgpu::Device,
204+
queue: &wgpu::Queue,
205+
config: &wgpu::CooperativeMatrixProperties,
206+
) -> ExecuteResults {
207+
let use_f16 = config.ab_type == wgpu::CooperativeScalarType::F16;
208+
166209
// Select the appropriate shader based on configuration
167210
let shader_source = if use_f16 {
168211
include_str!("shader_f16_16x16.wgsl")
@@ -347,7 +390,7 @@ async fn run() {
347390
compute_pass.set_pipeline(&pipeline);
348391
compute_pass.set_bind_group(0, &bind_group, &[]);
349392
// Dispatch one workgroup per tile of the output
350-
compute_pass.dispatch_workgroups(M / tile_size, N / tile_size, 1);
393+
compute_pass.dispatch_workgroups(M / config.m_size, N / config.m_size, 1);
351394
}
352395

353396
// Copy result to staging buffer
@@ -398,29 +441,11 @@ async fn run() {
398441
max_error = max_error.max(error);
399442
}
400443

401-
log::info!(
402-
"Matrix multiplication {M}x{K}x{N} completed using {} precision!",
403-
if use_f16 { "f16" } else { "f32" }
404-
);
405-
log::info!("Max error vs CPU reference: {max_error:.6}");
406-
407-
if max_error < tolerance {
408-
log::info!("✓ Results match CPU reference within tolerance ({tolerance})");
409-
} else {
410-
log::warn!("✗ Results differ from CPU reference (tolerance: {tolerance})");
411-
}
412-
413-
// Print a small sample of the result
414-
log::info!("Sample of result matrix C (top-left 4x4):");
415-
for i in 0..4 {
416-
let row: Vec<String> = (0..4)
417-
.map(|j| format!("{:6.2}", result[i * N as usize + j]))
418-
.collect();
419-
log::info!(" [{}]", row.join(", "));
444+
ExecuteResults {
445+
max_error,
446+
tolerance,
447+
matrix: result,
420448
}
421-
422-
drop(data);
423-
staging_buffer.unmap();
424449
}
425450

426451
pub fn main() {
@@ -440,3 +465,6 @@ pub fn main() {
440465
wasm_bindgen_futures::spawn_local(run());
441466
}
442467
}
468+
469+
#[cfg(test)]
470+
pub mod tests;

examples/features/src/cooperative_matrix/shader.wgsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// Each workgroup cooperatively loads tiles of A and B, multiplies them,
88
// and accumulates the result into C.
99

10-
enable experimental_cooperative_matrix;
10+
enable wgpu_cooperative_matrix;
1111

1212
// Matrix dimensions (8x8 tiles)
1313
const TILE_SIZE: u32 = 8u;

examples/features/src/cooperative_matrix/shader_f16_16x16.wgsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// and accumulates the result into C.
1212

1313
enable f16;
14-
enable experimental_cooperative_matrix;
14+
enable wgpu_cooperative_matrix;
1515

1616
// Matrix dimensions (16x16 tiles)
1717
const TILE_SIZE: u32 = 16u;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use super::*;
2+
use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters};
3+
4+
#[gpu_test]
5+
pub static COOPERATIVE_MATRIX: GpuTestConfiguration = GpuTestConfiguration::new()
6+
.parameters(
7+
TestParameters::default()
8+
.features(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX)
9+
.limits(wgpu::Limits::default()),
10+
)
11+
.run_async(|ctx| async move {
12+
let coop_props = ctx.adapter.cooperative_matrix_properties();
13+
let config = coop_props
14+
.iter()
15+
.find(|prop| {
16+
prop.m_size == 16
17+
&& prop.n_size == 16
18+
&& prop.k_size == 16
19+
&& prop.ab_type == wgpu::CooperativeScalarType::F16
20+
&& prop.cr_type == wgpu::CooperativeScalarType::F16
21+
})
22+
.or_else(|| {
23+
coop_props.iter().find(|prop| {
24+
prop.m_size == 8
25+
&& prop.n_size == 8
26+
&& prop.k_size == 8
27+
&& prop.ab_type == wgpu::CooperativeScalarType::F32
28+
&& prop.cr_type == wgpu::CooperativeScalarType::F32
29+
})
30+
})
31+
.unwrap();
32+
let ExecuteResults {
33+
max_error,
34+
tolerance,
35+
matrix: _,
36+
} = execute(&ctx.device, &ctx.queue, config).await;
37+
assert!(max_error < tolerance);
38+
});

examples/features/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ fn all_tests() -> Vec<wgpu_test::GpuTestInitializer> {
8181
#[cfg(not(target_arch = "wasm32"))]
8282
{
8383
test_list.push(big_compute_buffers::tests::TWO_BUFFERS);
84+
test_list.push(cooperative_matrix::tests::COOPERATIVE_MATRIX);
8485
}
8586

8687
test_list

naga/src/front/wgsl/lower/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,6 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
531531
span: Span,
532532
) -> Result<'source, Handle<ir::Expression>> {
533533
let mut eval = self.as_const_evaluator();
534-
log::debug!("appending {expr:?}");
535534
eval.try_eval_and_append(expr, span)
536535
.map_err(|e| Box::new(Error::ConstantEvaluatorError(e.into(), span)))
537536
}

naga/src/front/wgsl/parse/directive/enable_extension.rs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub struct EnableExtensions {
1717
/// Whether `enable f16;` was written earlier in the shader module.
1818
f16: bool,
1919
clip_distances: bool,
20-
experimental_cooperative_matrix: bool,
20+
wgpu_cooperative_matrix: bool,
2121
}
2222

2323
impl EnableExtensions {
@@ -29,7 +29,7 @@ impl EnableExtensions {
2929
f16: false,
3030
dual_source_blending: false,
3131
clip_distances: false,
32-
experimental_cooperative_matrix: false,
32+
wgpu_cooperative_matrix: false,
3333
}
3434
}
3535

@@ -44,9 +44,7 @@ impl EnableExtensions {
4444
ImplementedEnableExtension::DualSourceBlending => &mut self.dual_source_blending,
4545
ImplementedEnableExtension::F16 => &mut self.f16,
4646
ImplementedEnableExtension::ClipDistances => &mut self.clip_distances,
47-
ImplementedEnableExtension::ExperimentalCooperativeMatrix => {
48-
&mut self.experimental_cooperative_matrix
49-
}
47+
ImplementedEnableExtension::WgpuCooperativeMatrix => &mut self.wgpu_cooperative_matrix,
5048
};
5149
*field = true;
5250
}
@@ -62,9 +60,7 @@ impl EnableExtensions {
6260
ImplementedEnableExtension::DualSourceBlending => self.dual_source_blending,
6361
ImplementedEnableExtension::F16 => self.f16,
6462
ImplementedEnableExtension::ClipDistances => self.clip_distances,
65-
ImplementedEnableExtension::ExperimentalCooperativeMatrix => {
66-
self.experimental_cooperative_matrix
67-
}
63+
ImplementedEnableExtension::WgpuCooperativeMatrix => self.wgpu_cooperative_matrix,
6864
}
6965
}
7066
}
@@ -97,7 +93,7 @@ impl EnableExtension {
9793
const MESH_SHADER: &'static str = "wgpu_mesh_shader";
9894
const RAY_QUERY: &'static str = "wgpu_ray_query";
9995
const RAY_QUERY_VERTEX_RETURN: &'static str = "wgpu_ray_query_vertex_return";
100-
const EXPERIMENTAL_COOPERATIVE_MATRIX: &'static str = "experimental_cooperative_matrix";
96+
const COOPERATIVE_MATRIX: &'static str = "wgpu_cooperative_matrix";
10197
const SUBGROUPS: &'static str = "subgroups";
10298
const PRIMITIVE_INDEX: &'static str = "primitive_index";
10399

@@ -114,8 +110,8 @@ impl EnableExtension {
114110
Self::RAY_QUERY_VERTEX_RETURN => {
115111
Self::Implemented(ImplementedEnableExtension::WgpuRayQueryVertexReturn)
116112
}
117-
Self::EXPERIMENTAL_COOPERATIVE_MATRIX => {
118-
Self::Implemented(ImplementedEnableExtension::ExperimentalCooperativeMatrix)
113+
Self::COOPERATIVE_MATRIX => {
114+
Self::Implemented(ImplementedEnableExtension::WgpuCooperativeMatrix)
119115
}
120116
Self::SUBGROUPS => Self::Unimplemented(UnimplementedEnableExtension::Subgroups),
121117
Self::PRIMITIVE_INDEX => {
@@ -134,9 +130,7 @@ impl EnableExtension {
134130
ImplementedEnableExtension::WgpuRayQueryVertexReturn => {
135131
Self::RAY_QUERY_VERTEX_RETURN
136132
}
137-
ImplementedEnableExtension::ExperimentalCooperativeMatrix => {
138-
Self::EXPERIMENTAL_COOPERATIVE_MATRIX
139-
}
133+
ImplementedEnableExtension::WgpuCooperativeMatrix => Self::COOPERATIVE_MATRIX,
140134
ImplementedEnableExtension::DualSourceBlending => Self::DUAL_SOURCE_BLENDING,
141135
ImplementedEnableExtension::F16 => Self::F16,
142136
ImplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES,
@@ -176,8 +170,8 @@ pub enum ImplementedEnableExtension {
176170
WgpuRayQuery,
177171
/// Enables the `wgpu_ray_query_vertex_return` extension, native only.
178172
WgpuRayQueryVertexReturn,
179-
/// Enables the `experimental_cooperative_matrix` extension, native only.
180-
ExperimentalCooperativeMatrix,
173+
/// Enables the `wgpu_cooperative_matrix` extension, native only.
174+
WgpuCooperativeMatrix,
181175
}
182176

183177
/// A variant of [`EnableExtension::Unimplemented`].

0 commit comments

Comments
 (0)