mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 14:07:34 -06:00
GPU-TESTS: every WGSL kernel parses and validates in the test run, without a GPU
The kernels were only compiled when a device was created, so a module that doesn't build (a function defined twice after an automatic merge, a type error) passed CI on machines without a GPU and failed on users' machines. naga (re-exported by wgpu) now parses and validates every module as it is handed to the device: the finish, blur, resize, map, mask and geometry modules with their generated constants and bindings, the denoise convolution for every block size the planner picks, the pooling kernel and the face detector's two. A test checks that a duplicate definition is caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
03c88c166c
commit
7c9b6d88e5
@@ -763,6 +763,17 @@ pub fn groups1(n: usize) -> [u32; 3] {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// Every kernel module, as it is handed to the device (the generated constants, the bindings, `common.wgsl`, the
|
||||
/// module), parses and validates without a GPU.
|
||||
#[test]
|
||||
fn every_kernel_module_validates_without_a_gpu() {
|
||||
let consts = super::constants();
|
||||
for m in super::MODULES {
|
||||
let name = m.entries.first().copied().unwrap_or("?");
|
||||
assert_eq!(crate::validate_wgsl(name, &super::module_source(m, &consts)), Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limits_override_parses() {
|
||||
assert_eq!(super::parse_limits("webgpu"), Some((128 << 20, 256 << 20)));
|
||||
|
||||
@@ -817,6 +817,13 @@ static PER_CONV: AtomicBool = AtomicBool::new(false);
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The face detector's kernels validate without a GPU.
|
||||
#[test]
|
||||
fn the_face_kernels_validate_without_a_gpu() {
|
||||
assert_eq!(crate::validate_wgsl("faces_conv", include_str!("wgsl/faces_conv.wgsl")), Ok(()));
|
||||
assert_eq!(crate::validate_wgsl("faces_ops", include_str!("wgsl/faces_ops.wgsl")), Ok(()));
|
||||
}
|
||||
|
||||
fn gpu() -> Option<Arc<FaceGpu>> {
|
||||
match accelerator() {
|
||||
Ok(a) => Some(a),
|
||||
|
||||
@@ -550,10 +550,33 @@ pub fn try_render(
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests: parse and validate a WGSL module with naga, as wgpu does before it hands a shader to the driver. Needs no
|
||||
/// GPU, so kernels that don't build (a duplicate definition after a merge, a type error) fail every test run, not only
|
||||
/// the GPU ones.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn validate_wgsl(name: &str, src: &str) -> Result<(), String> {
|
||||
use wgpu::naga;
|
||||
let module = naga::front::wgsl::parse_str(src).map_err(|e| format!("{name}: {}", e.emit_to_string(src)))?;
|
||||
naga::valid::Validator::new(naga::valid::ValidationFlags::all(), naga::valid::Capabilities::all())
|
||||
.validate(&module)
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("{name}: {}", e.emit_to_string(src)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lightcraft_pipeline::{OutputDepth, Overlay};
|
||||
|
||||
/// The helper refuses what a driver would: a function defined twice (what an automatic merge of two branches
|
||||
/// touching the same kernel can leave).
|
||||
#[test]
|
||||
fn wgsl_validation_catches_a_duplicate_definition() {
|
||||
let ok = "fn f() -> f32 { return 1.0; }\n@compute @workgroup_size(1) fn main() { _ = f(); }\n";
|
||||
assert_eq!(super::validate_wgsl("ok", ok), Ok(()));
|
||||
let twice = format!("fn f() -> f32 {{ return 2.0; }}\n{ok}");
|
||||
assert!(super::validate_wgsl("twice", &twice).is_err());
|
||||
}
|
||||
|
||||
// Given a request the 8-bit kernel can't draw (high bit depth, a soft proof, Visualize HDR),
|
||||
// the GPU doesn't take it; the loupe and diagnostic overlays it can draw stay on the GPU
|
||||
#[test]
|
||||
|
||||
@@ -881,6 +881,25 @@ impl TileRunner for NetRunner {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The denoise kernels validate without a GPU: the pooling kernel, and the convolution for every block size the
|
||||
/// planner picks for these layer widths and channel counts.
|
||||
#[test]
|
||||
fn the_network_kernels_validate_without_a_gpu() {
|
||||
assert_eq!(crate::validate_wgsl("nn_pool", include_str!("wgsl/nn_pool.wgsl")), Ok(()));
|
||||
let mut sizes: Vec<(u32, u32, u32)> = Vec::new();
|
||||
for n in [1usize, 3, 8, 16, 24, 32, 48, 64, 96, 128, 256, 512] {
|
||||
for cin in [1usize, 3, 4, 8, 16, 32, 48, 64, 128, 256, 512] {
|
||||
let b = blocks(n, cin);
|
||||
if !sizes.contains(&b) {
|
||||
sizes.push(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (bm, bn, bk) in sizes {
|
||||
assert_eq!(crate::validate_wgsl(&format!("nn_conv {bm}x{bn}x{bk}"), &conv_source(bm, bn, bk)), Ok(()));
|
||||
}
|
||||
}
|
||||
use lightcraft_denoise::{onnx, reference, synthetic};
|
||||
|
||||
fn net(tile: u64, ch: u64, depth: usize, seed: u64) -> Net {
|
||||
|
||||
Reference in New Issue
Block a user