diff --git a/CMakeLists.txt b/CMakeLists.txt index 926cdae..2a6f471 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,19 +29,24 @@ add_custom_command(OUTPUT "${wmma_object}" -o "${wmma_object}" DEPENDS tools/windows/wmma_gfx1201.hip kernels/common/wmma_backend.h VERBATIM) add_custom_target(d4r_wmma_code_object ALL DEPENDS "${wmma_object}") -# Compile-only K port smoke test. This object is intentionally not installed as -# a native override until its transformer outputs pass the K reference/replay. -set(k_enc1_object "${CMAKE_CURRENT_BINARY_DIR}/dltss_pwin_enc1_layer_gfx1201.hsaco") -add_custom_command(OUTPUT "${k_enc1_object}" - COMMAND "${D4R_HIP_CLANG}" -x hip -std=c++17 --offload-arch=gfx1201 - --offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64 - -nogpuinc -nogpulib -O2 -DD4R_DEVICE_ONLY_MINIMAL - "${CMAKE_CURRENT_SOURCE_DIR}/kernels/k/dltss_pwin_enc1_layer.hip" - -o "${k_enc1_object}" - DEPENDS kernels/k/dltss_pwin_enc1_layer.hip kernels/k/pwin_layer.h - kernels/k/pwin_common.h kernels/common/wmma_backend.h - kernels/common/hip_device_minimal.h VERBATIM) -add_custom_target(d4r_k_gfx1201_compile ALL DEPENDS "${k_enc1_object}") +# These K objects stay in experimental/k, outside the ZLUDA native override +# directory, until each layer and then the complete network pass replay. +set(k_objects) +foreach(layer enc1 enc2) + set(k_object "${CMAKE_CURRENT_BINARY_DIR}/dltss_pwin_${layer}_layer_gfx1201.hsaco") + add_custom_command(OUTPUT "${k_object}" + COMMAND "${D4R_HIP_CLANG}" -x hip -std=c++17 --offload-arch=gfx1201 + --offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64 + -nogpuinc -nogpulib -O2 -DD4R_DEVICE_ONLY_MINIMAL + "${CMAKE_CURRENT_SOURCE_DIR}/kernels/k/dltss_pwin_${layer}_layer.hip" + -o "${k_object}" + DEPENDS kernels/k/dltss_pwin_${layer}_layer.hip kernels/k/pwin_layer.h + kernels/k/pwin_common.h kernels/common/wmma_backend.h + kernels/common/hip_device_minimal.h VERBATIM) + list(APPEND k_objects "${k_object}") + install(FILES "${k_object}" DESTINATION experimental/k) +endforeach() +add_custom_target(d4r_k_gfx1201_compile ALL DEPENDS ${k_objects}) foreach(probe hip_gfx1201_probe gfx12_wmma_probe k_module_probe cuda_driver_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe ngx_cuda_init_probe) add_executable(d4r_${probe} tools/windows/${probe}.cpp) target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__) @@ -97,9 +102,8 @@ add_dependencies(d4r_gfx12_wmma_probe d4r_wmma_code_object) add_dependencies(d4r_k_module_probe d4r_k_gfx1201_compile) install(FILES "${probe_object}" DESTINATION bin) install(FILES "${wmma_object}" DESTINATION bin) -install(FILES "${k_enc1_object}" DESTINATION experimental/k) install(FILES scripts/windows/test-windows-rdna4.ps1 DESTINATION .) -install(FILES tools/windows/k_enc1_validate.py DESTINATION .) +install(FILES tools/windows/k_layer_validate.py DESTINATION .) install(FILES kernels/tools/pwin_model.py DESTINATION reference) install(FILES docs/windows-rdna4-port.md DESTINATION docs) include(CTest) @@ -109,9 +113,13 @@ set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201" add_test(NAME gfx12_wmma COMMAND d4r_gfx12_wmma_probe --hip-root "${D4R_HIP_ROOT}" --module "${wmma_object}" --iterations 16) set_tests_properties(gfx12_wmma PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201;wmma") -add_test(NAME k_enc1_execution COMMAND d4r_k_module_probe - --hip-root "${D4R_HIP_ROOT}" --module "${k_enc1_object}") -set_tests_properties(k_enc1_execution PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201;k_execution") +foreach(layer enc1 enc2) + add_test(NAME k_${layer}_execution COMMAND d4r_k_module_probe + --hip-root "${D4R_HIP_ROOT}" + --module "${CMAKE_CURRENT_BINARY_DIR}/dltss_pwin_${layer}_layer_gfx1201.hsaco" + --kernel-name ${layer}) + set_tests_properties(k_${layer}_execution PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201;k_execution") +endforeach() add_test(NAME d3d12_hip_interop COMMAND d4r_d3d12_hip_interop_probe --hip-root "${D4R_HIP_ROOT}" --module "${probe_object}" --iterations 32) set_tests_properties(d3d12_hip_interop PROPERTIES TIMEOUT 120 LABELS "hardware;interop") diff --git a/README.md b/README.md index 8b9e3c8..c663a81 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ It is an experiment. It has been tested on one GPU in a handful of games, it dep **Windows / RDNA4 development:** the `windows-rdna4` branch adds native Windows gfx1201 HIP/ZLUDA diagnostics, validated D3D12/HIP interop, an NGX init probe, -and a CPU-checked gfx12 WMMA backend. The first K `enc1` kernel executes on -gfx1201 and matches the existing numpy model on a synthetic nonzero fixture. +and a CPU-checked gfx12 WMMA backend. K `enc1` and `enc2` execute on +gfx1201 and match the existing numpy model on synthetic nonzero fixtures. The remaining K/M kernels and Windows game runtime are still in progress. Build commands, hardware results and remaining porting gates are in [docs/windows-rdna4-port.md](docs/windows-rdna4-port.md). diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index f518d2a..881ff34 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -7,8 +7,8 @@ This is a work log for an implementation in progress, not a claim of DLSS suppor ## Milestone and gates Current milestone: M1/M2/M4/M5 passed; NGX init + DLSS CUDA capability discovery -passed with the user's DLLs. M6 is in progress: the first K `enc1` prep and -transformer run on gfx1201 and pass a synthetic nonzero numpy reference check. +passed with the user's DLLs. M6 is in progress: K `enc1` and `enc2` prep and +transformers run on gfx1201 and pass synthetic nonzero numpy reference checks. Do not integrate NGX until the integer PTX workload is stable on the real GPU. | Gate | Status | @@ -19,7 +19,7 @@ Do not integrate NGX until the integer PTX workload is stable on the real GPU. | M3: Windows NGX initialization and simple DLSS path | Init + SR capabilities PASS; Evaluate/transformer pending | | M4: D3D12 / HIP external memory and fence round trip | PASS with TheRock; stable 7.2 has mapped-view leak | | M5: independently validated gfx12 WMMA backend | PASS: raw + legacy adapter, 16 cases, max abs/relative error 0 | -| M6: K layers, full transformer and image validation | Partial: `enc1` executes, nonzero fixture matches reference; other layers/full network pending | +| M6: K layers, full transformer and image validation | Partial: `enc1`/`enc2` execute and match nonzero references; other layers/full network pending | | M7: M FP16-equivalent baseline and full transformer | Pending | | M8: standalone Windows D3D12 NGX harness | Pending | | M9: OptiScaler integration, profiling, installation | Pending | @@ -137,13 +137,13 @@ different inputs; max absolute and relative errors were both zero. `kernels/k/pwin_common.h` now calls the tested adapter. The Windows device-only compiler uses `kernels/common/hip_device_minimal.h`, exposing only public Clang AMDGPU work-item/grid/barrier builtins and HIP-compatible qualifiers; -the ordinary Linux HIP include path remains. CMake builds `enc1` for gfx1201 +the ordinary Linux HIP include path remains. CMake builds `enc1`/`enc2` for gfx1201 into `experimental/k/` and verifies that HIP loads both prep and transformer entry points. The `k_module_probe` now launches prep and transformer. The identity fixture verifies all 4096 full-resolution and 1024 merged FP16 values bitwise. A second fixture enables nonzero V projections, position-only attention, Wo, MLP/GELU and patch merge; it checks finite values and a patch -identity, then saves all inputs/outputs for `k_enc1_validate.py`. That script +identity, then saves all inputs/outputs for `k_layer_validate.py`. That script runs the existing `pwin_model.py` and measures max absolute/relative error and PSNR. The code object remains in `experimental/k/`, outside ZLUDA's override path, until the other K layers and real captured weights/activations pass. @@ -259,7 +259,7 @@ One command adds the local NGX initialization gate after eight diagnostics: powershell -NoProfile -File scripts/windows/test-windows-rdna4.ps1 -RuntimeProfile therock -NgxCore "C:/Users/Administrator/d4r/_nvngx.dll" -DlssDll "C:/Users/Administrator/d4r/nvngx_dlss.dll" ``` -Success requires all nine tests, including `PASS K_REFERENCE` and +Success with the two local NGX DLLs requires all eleven tests, including both `PASS K_REFERENCE` checks and `PASS NGX_INIT ... sr_available=1`. The NGX probe still reports `transformer_executed=0`: the standalone K launch has not been integrated into DLSS. On NGX failure, CUDA trace and NVAPI query @@ -279,7 +279,7 @@ cmake --install build/windows-rdna4 --prefix dist/windows-rdna4-diagnostics The one-command diagnostic runner is also installed at `dist/windows-rdna4-diagnostics/test-windows-rdna4.ps1`. It requires the selected HIP SDK and ZLUDA directory; the developer setup auto-discovers local defaults. -Success: exit 0, all five tests passed in `summary.json`, `PASS HIP`, `PASS CUDA`, +Success: exit 0, every selected test passed in `summary.json`, `PASS HIP`, `PASS CUDA`, `PASS INTEROP_LIFETIME` and `PASS INTEROP` in stdout. Failure: send the **single printed ZIP path**; it contains stdout/stderr, timeout/exception exit code, DLL paths/versions/hashes, driver/OS inventory and a minidump for an unhandled exception. Upstream trace is attempted @@ -351,6 +351,16 @@ Reference dependency: pinned local NumPy `2.4.6` on Python 3.11, installed by `setup-windows-tools.ps1` into `.tools/python/vendor` (not the game runtime). This is a synthetic one-layer check, not proof of complete K or DLSS output. +2026-09-29 M6 second kernel: `enc2` exercises nonzero Q/K/V, learned attention +and softmax, Wo, MLP/GELU and patch merge on gfx1201. Full output max absolute +error `3.81469727e-06`, max relative error `0.00166893` (denominator floor +`1e-3`), PSNR `130.39 dB`; merged output max absolute error `3.81469727e-06`, +PSNR `132.49 dB`. All 4096 full and 1536 merged elements written, no NaN/Inf. +Both layers pass 32 identity iterations and the nonzero numpy reference. +Bundle `test-results/m6-k-enc2-final.zip`: 11/11 gates PASS. CTest: 8/8 PASS. +The generic probe/reference runner selects `--kernel-name enc1|enc2`; both +objects remain under `experimental/k` and are not used as DLSS overrides yet. + Stable HIP SDK 7.2 compiler caveat: its `-O2` code object for `enc1` produces NaN already on the identity fixture (first element `0xfe00` instead of `0xbc00`). Its `-O1` object also miscomputes the identity fixture; `-O0` diff --git a/scripts/windows/test-windows-rdna4.ps1 b/scripts/windows/test-windows-rdna4.ps1 index 21645e5..49108f3 100644 --- a/scripts/windows/test-windows-rdna4.ps1 +++ b/scripts/windows/test-windows-rdna4.ps1 @@ -101,7 +101,8 @@ try { $files = @((Join-Path $HipRoot 'bin/amdhip64_7.dll'), $comgr, (Join-Path $ZludaRoot 'nvcuda.dll'), (Join-Path $PackageRoot 'bin/probe_gfx1201.hsaco'), (Join-Path $PackageRoot 'bin/wmma_gfx1201.hsaco'), - (Join-Path $PackageRoot 'experimental/k/dltss_pwin_enc1_layer_gfx1201.hsaco')) + (Join-Path $PackageRoot 'experimental/k/dltss_pwin_enc1_layer_gfx1201.hsaco'), + (Join-Path $PackageRoot 'experimental/k/dltss_pwin_enc2_layer_gfx1201.hsaco')) if ($NgxCore -or $DlssDll) { if (!$NgxCore -or !$DlssDll) { throw 'Supply both -NgxCore and -DlssDll absolute paths.' } if (![IO.Path]::IsPathRooted($NgxCore) -or ![IO.Path]::IsPathRooted($DlssDll)) { @@ -127,16 +128,19 @@ try { $wmmaOk = Invoke-Probe 'gfx12-wmma' (Join-Path $bin 'd4r_gfx12_wmma_probe.exe') @( '--hip-root', $HipRoot, '--module', (Join-Path $bin 'wmma_gfx1201.hsaco'), '--iterations', "$Iterations") if (!$wmmaOk) { throw 'gfx12 WMMA layout validation failed; refusing to mark K/M readiness.' } - $kFixture = Join-Path $OutputDirectory 'k-enc1-fixture' - $kModuleOk = Invoke-Probe 'k-enc1-gpu' (Join-Path $bin 'd4r_k_module_probe.exe') @( - '--hip-root', $HipRoot, '--module', (Join-Path $PackageRoot 'experimental/k/dltss_pwin_enc1_layer_gfx1201.hsaco'), - '--fixture-dir', $kFixture, '--iterations', "$Iterations") - if (!$kModuleOk) { throw 'K enc1 GPU execution failed; see the k-enc1-gpu logs.' } $pythonCommand = Get-Command python -ErrorAction SilentlyContinue - if (!$pythonCommand) { throw 'Python 3.11+ with NumPy 2.4.6 is needed for the K reference check; run setup-windows-tools.ps1.' } - $kReferenceOk = Invoke-Probe 'k-enc1-reference' $pythonCommand.Source @( - (Join-Path $PackageRoot 'k_enc1_validate.py'), '--fixture-dir', $kFixture) - if (!$kReferenceOk) { throw 'K enc1 output differs from pwin_model.py; see the k-enc1-reference logs.' } + if (!$pythonCommand) { throw 'Python 3.11+ with NumPy 2.4.6 is needed for K reference checks; run setup-windows-tools.ps1.' } + foreach ($layer in @('enc1', 'enc2')) { + $kFixture = Join-Path $OutputDirectory "k-$layer-fixture" + $kModuleOk = Invoke-Probe "k-$layer-gpu" (Join-Path $bin 'd4r_k_module_probe.exe') @( + '--hip-root', $HipRoot, + '--module', (Join-Path $PackageRoot "experimental/k/dltss_pwin_${layer}_layer_gfx1201.hsaco"), + '--kernel-name', $layer, '--fixture-dir', $kFixture, '--iterations', "$Iterations") + if (!$kModuleOk) { throw "K $layer GPU execution failed; see the k-$layer-gpu logs." } + $kReferenceOk = Invoke-Probe "k-$layer-reference" $pythonCommand.Source @( + (Join-Path $PackageRoot 'k_layer_validate.py'), '--kernel-name', $layer, '--fixture-dir', $kFixture) + if (!$kReferenceOk) { throw "K $layer output differs from pwin_model.py; see the k-$layer-reference logs." } + } $cudaOk = $true foreach ($context in @('primary', 'created')) { $ok = Invoke-Probe "cuda-$context" (Join-Path $bin 'd4r_cuda_driver_probe.exe') @( diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index 79e4096..9d2c8fb 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -90,6 +90,7 @@ struct Args { std::string nvapi_dll; std::string ngx_abi = "driver"; std::string fixture_dir; + std::string kernel_name = "enc1"; unsigned iterations = 32; int device = -1; Args(int argc, char** argv) { @@ -108,6 +109,7 @@ struct Args { else if (key == "--nvapi-dll") nvapi_dll = value; else if (key == "--ngx-abi") ngx_abi = value; else if (key == "--fixture-dir") fixture_dir = value; + else if (key == "--kernel-name") kernel_name = value; else if (key == "--iterations") { size_t end = 0; iterations = static_cast(std::stoul(value, &end)); diff --git a/tools/windows/k_enc1_validate.py b/tools/windows/k_layer_validate.py similarity index 83% rename from tools/windows/k_enc1_validate.py rename to tools/windows/k_layer_validate.py index 81ce18d..2d51fbc 100644 --- a/tools/windows/k_enc1_validate.py +++ b/tools/windows/k_layer_validate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Compare a real gfx1201 K enc1 launch with the existing numpy K reference.""" +"""Compare real gfx1201 K enc1/enc2 launches with the existing numpy reference.""" import argparse import math import pathlib @@ -57,21 +57,24 @@ def compare(name, actual, expected): def main(): parser = argparse.ArgumentParser() parser.add_argument("--fixture-dir", required=True, type=pathlib.Path) + parser.add_argument("--kernel-name", choices=("enc1", "enc2"), default="enc1") args = parser.parse_args() folder = args.fixture_dir params = SyntheticParams(folder) + output_channels = 96 if args.kernel_name == "enc2" else 64 reference_full, reference_merged = pwin_encoder( - params, 0, 0, H=2, C=64, COUT=64, posattn=True) + params, 0, 0, H=2, C=64, COUT=output_channels, + posattn=args.kernel_name == "enc1") actual_full = np.fromfile(folder / "full.bin", np.float16).reshape(64, 64) - actual_merged = np.fromfile(folder / "merged.bin", np.float16).reshape(16, 64) + actual_merged = np.fromfile(folder / "merged.bin", np.float16).reshape(16, output_channels) full_abs, full_psnr = compare("full", actual_full, reference_full) merged_abs, merged_psnr = compare("merged", actual_merged, reference_merged) # Initial acceptance for the synthetic nonzero FP16 fixture. Tighten this # after each K stage has been compared on captured DLSS weights/activations. if full_abs > 0.001 or full_psnr < 60.0 or merged_abs > 0.001 or merged_psnr < 60.0: - raise RuntimeError("K enc1 numerical mismatch against pwin_model.py") - print("PASS K_REFERENCE architecture=gfx1201 kernel=enc1 nonzero_weights=1 " - "transformer_executed=1 nan_inf=0") + raise RuntimeError(f"K {args.kernel_name} numerical mismatch against pwin_model.py") + print(f"PASS K_REFERENCE architecture=gfx1201 kernel={args.kernel_name} " + "nonzero_weights=1 transformer_executed=1 nan_inf=0") if __name__ == "__main__": diff --git a/tools/windows/k_module_probe.cpp b/tools/windows/k_module_probe.cpp index 46fb79f..b6d3012 100644 --- a/tools/windows/k_module_probe.cpp +++ b/tools/windows/k_module_probe.cpp @@ -43,24 +43,29 @@ int main(int argc, char** argv) HipApi hip(args.hip_root); hipDeviceProp_t properties{}; hip.select_gfx1201(args.device, properties); + if (args.kernel_name != "enc1" && args.kernel_name != "enc2") + throw std::runtime_error("--kernel-name must be enc1 or enc2"); + const bool enc2 = args.kernel_name == "enc2"; + const std::string entry = "dltss_pwin_" + args.kernel_name + "_layer"; hipModule_t module = nullptr; - hip.check(hip.hipModuleLoad(&module, args.module.c_str()), "hipModuleLoad(K enc1)"); + hip.check(hip.hipModuleLoad(&module, args.module.c_str()), "hipModuleLoad(K)"); struct Cleanup { HipApi& hip; hipModule_t module; ~Cleanup() { if (module) (void)hip.hipModuleUnload(module); } } cleanup{hip, module}; hipFunction_t prep = nullptr, transformer = nullptr; - hip.check(hip.hipModuleGetFunction(&prep, module, "dltss_pwin_enc1_layer_prep"), "K enc1 prep"); - hip.check(hip.hipModuleGetFunction(&transformer, module, "dltss_pwin_enc1_layer"), "K enc1 transformer"); + hip.check(hip.hipModuleGetFunction(&prep, module, (entry + "_prep").c_str()), "K prep"); + hip.check(hip.hipModuleGetFunction(&transformer, module, entry.c_str()), "K transformer"); if (!prep || !transformer) throw std::runtime_error("Null K entry point"); // Identity fixture: all projection, MLP and patch-merge weights are zero. // G1/G2 are one. The full transformer must return its input through its // residual path and the merged output must be zero. Every stage launches. constexpr size_t full_elements = 8u * 8u * 64u; - constexpr size_t merged_elements = 4u * 4u * 64u; - constexpr size_t weight_bytes = 149504u; - constexpr size_t prep_items = 256u * 16u + 1024u; + const size_t output_channels = enc2 ? 96u : 64u; + const size_t merged_elements = 4u * 4u * output_channels; + const size_t weight_bytes = enc2 ? 166080u : 149504u; + const size_t prep_items = (enc2 ? 288u : 256u) * 16u + 1024u; std::vector weights(weight_bytes, 0); auto set_half = [&](size_t byte, uint16_t bits) { weights.at(byte) = static_cast(bits); @@ -119,8 +124,8 @@ int main(int argc, char** argv) return 5; } } - std::printf("PASS K_IDENTITY architecture=gfx1201 iterations=%u full_elements=%zu merged_elements=%zu transformer_executed=1 nonzero_weights=0\n", - args.iterations, full_elements, merged_elements); + std::printf("PASS K_IDENTITY architecture=gfx1201 kernel=%s iterations=%u full_elements=%zu merged_elements=%zu transformer_executed=1 nonzero_weights=0\n", + args.kernel_name.c_str(), args.iterations, full_elements, merged_elements); // Exercise nonzero V projection, position-only attention and Wo GEMMs. // The generated data is saved for comparison with pwin_model.py. @@ -135,6 +140,12 @@ int main(int argc, char** argv) constexpr size_t qkv = 128u, head = 12288u, bias = 24704u, wo = 41216u; constexpr size_t w1 = 49664u, w2 = 82944u, pm = 115712u; for (unsigned k = 0; k < 32u; ++k) { + if (enc2) { + set_matrix(qkv, k, k, 1024u); // head 0 Q + set_matrix(qkv + 2048u, k, k, 1024u); // head 0 K + set_matrix(qkv + head + 2048u * 3u, k, k, 1024u); // head 1 Q + set_matrix(qkv + head + 2048u * 4u, k, k, 1024u); // head 1 K + } set_matrix(qkv + 2048u * 2u, k, k, 1024u); // head 0 V <- input 0:32 set_matrix(qkv + head + 2048u * 5u, k, k, 1024u); // head 1 V <- input 32:64 set_matrix(wo, k, k, 1024u); // head 0 Wo -> output 0:32 @@ -181,9 +192,10 @@ int main(int argc, char** argv) for (unsigned mt = 0; mt < 16u; ++mt) { const unsigned my = mt >> 2, mx = mt & 3u; const size_t full_base = (8u * (2u * my) + 2u * mx) * 64u; - for (unsigned channel = 0; channel < 64u; ++channel) { - const size_t i = mt * 64u + channel; - if (merged[i] != full[full_base + channel]) + for (unsigned channel = 0; channel < output_channels; ++channel) { + const size_t i = mt * output_channels + channel; + const uint16_t expected = channel < 64u ? full[full_base + channel] : 0u; + if (merged[i] != expected) throw std::runtime_error("K patch merge identity mismatch at " + std::to_string(i)); } } @@ -201,8 +213,8 @@ int main(int argc, char** argv) save("full.bin", full.data(), full.size() * sizeof(uint16_t)); save("merged.bin", merged.data(), merged.size() * sizeof(uint16_t)); } - std::printf("PASS K_NONZERO architecture=gfx1201 changed_elements=%zu full_elements=%zu merged_elements=%zu fixture_saved=%d numerical_reference_pending=1\n", - changed, full.size(), merged.size(), !args.fixture_dir.empty()); + std::printf("PASS K_NONZERO architecture=gfx1201 kernel=%s changed_elements=%zu full_elements=%zu merged_elements=%zu fixture_saved=%d numerical_reference_pending=1\n", + args.kernel_name.c_str(), changed, full.size(), merged.size(), !args.fixture_dir.empty()); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "FAIL K_MODULE %s\n", error.what());