Correct M attention precision and validate every temporal transformer block

This commit is contained in:
Codex committed 2026-09-30 21:30:05 +03:00
1 parent e15f6a73a6
commit b8d801ae4c
14 files changed
+623 -18

No files matched your search

+8 -2
View File
@@ -50,7 +50,7 @@ foreach(family k m)
list(APPEND native_flags -DD4R_K_FP16_BASELINE) list(APPEND native_flags -DD4R_K_FP16_BASELINE)
endif() endif()
if(family STREQUAL "m" AND D4R_M_FP16_BASELINE) if(family STREQUAL "m" AND D4R_M_FP16_BASELINE)
list(APPEND native_flags -DSWIN_EXACT) list(APPEND native_flags -DSWIN_EXACT -DSWIN_EXACT_PV)
endif() endif()
if(flags_line) if(flags_line)
separate_arguments(source_flags NATIVE_COMMAND "${CMAKE_MATCH_1}") separate_arguments(source_flags NATIVE_COMMAND "${CMAKE_MATCH_1}")
@@ -68,7 +68,7 @@ foreach(family k m)
endforeach() endforeach()
endforeach() endforeach()
add_custom_target(d4r_native_gfx1201_compile ALL DEPENDS ${native_objects}) add_custom_target(d4r_native_gfx1201_compile ALL DEPENDS ${native_objects})
foreach(probe hip_gfx1201_probe gfx12_wmma_probe k_module_probe native_replay_probe cuda_driver_probe cuda_image_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe ngx_cuda_init_probe d3d12_dlss_probe) foreach(probe hip_gfx1201_probe gfx12_wmma_probe k_module_probe native_replay_probe cuda_driver_probe cuda_mma_probe cuda_image_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe ngx_cuda_init_probe d3d12_dlss_probe)
add_executable(d4r_${probe} tools/windows/${probe}.cpp) add_executable(d4r_${probe} tools/windows/${probe}.cpp)
target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__) target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
target_include_directories(d4r_${probe} PRIVATE "${D4R_HIP_ROOT}/include") target_include_directories(d4r_${probe} PRIVATE "${D4R_HIP_ROOT}/include")
@@ -147,6 +147,7 @@ install(FILES tools/windows/k_ptx_validate.py DESTINATION .)
install(FILES tools/windows/frame_compare.py DESTINATION .) install(FILES tools/windows/frame_compare.py DESTINATION .)
install(FILES tools/windows/m_replay_validate.py DESTINATION .) install(FILES tools/windows/m_replay_validate.py DESTINATION .)
install(FILES tools/windows/m_ptx_validate.py DESTINATION .) install(FILES tools/windows/m_ptx_validate.py DESTINATION .)
install(FILES tools/windows/m_temporal_validate.py tools/windows/m_capture_compare.py DESTINATION .)
install(FILES scripts/windows/test-m-replay.ps1 DESTINATION .) install(FILES scripts/windows/test-m-replay.ps1 DESTINATION .)
install(FILES scripts/windows/test-m-captures.ps1 DESTINATION .) install(FILES scripts/windows/test-m-captures.ps1 DESTINATION .)
install(FILES kernels/tools/pwin_model.py kernels/tools/pwin_check.py kernels/tools/ptxsim.py install(FILES kernels/tools/pwin_model.py kernels/tools/pwin_check.py kernels/tools/ptxsim.py
@@ -182,6 +183,11 @@ if(EXISTS "${D4R_ZLUDA_ROOT}/nvcuda.dll")
set_tests_properties(cuda_${context} PROPERTIES TIMEOUT 180 LABELS "hardware;zluda") set_tests_properties(cuda_${context} PROPERTIES TIMEOUT 180 LABELS "hardware;zluda")
endforeach() endforeach()
if(EXISTS "${D4R_ZLUDA_ROOT}/build-info.json") if(EXISTS "${D4R_ZLUDA_ROOT}/build-info.json")
add_test(NAME cuda_f16_mma_reference COMMAND d4r_cuda_mma_probe
--hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll")
set_tests_properties(cuda_f16_mma_reference PROPERTIES TIMEOUT 120
LABELS "hardware;zluda;mma;reference"
ENVIRONMENT "D4R_ZLUDA_WMMA=1;D4R_ZLUDA_WMMA_F16_REFERENCE=1")
add_test(NAME cuda_images COMMAND d4r_cuda_image_probe add_test(NAME cuda_images COMMAND d4r_cuda_image_probe
--hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll" --iterations 32) --hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll" --iterations 32)
set_tests_properties(cuda_images PROPERTIES TIMEOUT 90 LABELS "hardware;zluda;descriptors") set_tests_properties(cuda_images PROPERTIES TIMEOUT 90 LABELS "hardware;zluda;descriptors")
+67 -4
View File
@@ -13,8 +13,12 @@ frames on Windows. M now passes all five synthetic and real-weight layers agains
NumPy and independent PTX, and four native CUDA frames. The standalone D3D12 NumPy and independent PTX, and four native CUDA frames. The standalone D3D12
harness now produces four K and four M frames using imported VRAM and shared harness now produces four K and four M frames using imported VRAM and shared
fences, with bit-exact RGB agreement against the corresponding CUDA harnesses. fences, with bit-exact RGB agreement against the corresponding CUDA harnesses.
Game command-list/queue integration remains in progress. The fully translated M Game command-list/queue integration remains in progress. A full temporal M scan
comparison is being rerun with the correct WMMA options and a writable JIT cache. found rare attention P/V accumulation mismatches that the earlier twelve-block
sampling missed. `SWIN_EXACT_PV` fixes the native baseline; ZLUDA patch `0013`
adds the corresponding opt-in f16 reference accumulation. All spatial blocks in
40 actual native M launches now exactly match NumPy. Four complete native M RGB
frames exactly match fully translated M with the corrected reference option.
Do not integrate NGX until the integer PTX workload is stable on the real GPU. Do not integrate NGX until the integer PTX workload is stable on the real GPU.
| Gate | Status | | Gate | Status |
@@ -25,11 +29,70 @@ 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, Create/Evaluate PASS; E, K and M CUDA harnesses execute | | M3: Windows NGX initialization and simple DLSS path | Init, SR capabilities, Create/Evaluate PASS; E, K and M CUDA harnesses execute |
| M4: D3D12 / HIP external memory and fence round trip | PASS with TheRock; stable 7.2 has mapped-view leak | | 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 + upstream layout, max abs/relative error 0 | | M5: independently validated gfx12 WMMA backend | PASS: raw + legacy adapter + upstream layout, max abs/relative error 0 |
| M6: K layers, full transformer and image validation | CUDA harness PASS: 11 real-weight layers, native launches for all layers, four finite frames, 81.85–92.91 dB versus translated K; D3D12 integration pending | | M6: K layers, full transformer and image validation | PASS: 11 real-weight layers, 44 native launches, four D3D12 frames bit-exact against native CUDA, 81.85–92.91 dB versus translated K |
| M7: M FP16-equivalent baseline and full transformer | CUDA harness PASS: 5 real-weight layers exactly match NumPy/PTX, 4 finite frames, 40 native Swin launches; D3D12 and full translated M comparison pending | | M7: M FP16-equivalent baseline and full transformer | PASS: all blocks of 40 native launches exactly match NumPy; targeted independent PTX regression matches; four D3D12 frames bit-exact against corrected translated M |
| M8: standalone Windows D3D12 NGX harness | PASS at explicit submitted queue boundaries: K/M, four frames each, no shim CPU image copies, bit-exact RGB against CUDA harness | | M8: standalone Windows D3D12 NGX harness | PASS at explicit submitted queue boundaries: K/M, four frames each, no shim CPU image copies, bit-exact RGB against CUDA harness |
| M9: OptiScaler integration, profiling, installation | Pending | | M9: OptiScaler integration, profiling, installation | Pending |
## M temporal precision regression (2026-09-30)
The earlier finite-frame and sampled-layer results did not cover every spatial
block. The second enc1 launch has a cancellation-sensitive P/V dot in block
`0,4`: the old native f32 WMMA route differed by up to 21 e4m3 code steps.
NumPy and the independent PTX interpreter agree exactly on that block.
`SWIN_EXACT_PV` stores each wave's sixteen P rows and V rows in LDS, sums sixteen
f16 products in f64, rounds directly to f16, then applies the existing e4m3
quantisation. Other GEMMs retain the gfx12 WMMA layout and FP16 widening.
The flag is enabled by `D4R_M_FP16_BASELINE`; upstream fast/Linux defaults are
preserved when that flag is absent. Intermediate snapshots are compiled only
with `D4R_SWIN_DIAGNOSTICS`; they are absent from packaged kernels.
The translated reference also differed: first frame, fourth enc3_tube launch,
block `0,0`, thirty stored codes, maximum fifteen e4m3 steps. Its inputs match
the corrected native launch exactly. Patch `0013` adds
`D4R_ZLUDA_WMMA_F16_REFERENCE=1` for gfx12 f16-accumulator MMA helpers, retaining
the established fragment gathers, pair fusion and conversions. It uses f64
sums and integer f16 RNE encoding, including subnormal bits. FP8 GEMMs continue
using the existing native WMMA widening. The default upstream helper behavior
remains available with this option unset or zero. Windows diagnostics select
the reference option by default. All d4r codegen options enter the JIT cache key.
This is a match to the independent mathematical NumPy/PTX baseline. NVIDIA's
[PTX floating-point MMA specification](https://docs.nvidia.com/cuda/parallel-thread-execution/#warp-level-matrix-instructions-mma)
leaves accumulation order, rounding and subnormal handling unspecified; these
checks do not establish bit-exact agreement with NVIDIA hardware.
Recorded results on the RX 9070 XT / TheRock runtime:
* Public `cuda_mma_probe`: 512 synthetic matrix cases, 65,536 f16 outputs,
zero differing bits with reference accumulation. The old fast WMMA route
differs in 23,308 outputs on the same cancellation/subnormal stress fixture.
* `m-native-exact-pv-temporal-reference`: all spatial blocks of 40 actual native
launches, every stored e4m3 value identical to NumPy, max abs/relative error 0.
* `m-exact-pv-enc1-regression-ptx`: original problem block `0,4`, independent
PTX versus native and versus NumPy both max absolute/relative error 0.
* `m-exact-pv-complete-f16-reference.log`: four complete RGB frames against
corrected fully translated M, max absolute/relative error 0, PSNR infinite.
* `m-translated-f16-temporal-reference`: all blocks of all forty translated
M launches exactly match NumPy; no sampled-window exemption.
* `m-temporal-final-ctest.log`: 11/11 native Windows hardware tests PASS.
Pinned fresh-source application of all twelve ZLUDA patches PASS.
* `d3d12-m-exact-pv-final.zip`: four finite frames, forty native launches,
no shim CPU image copies, frame age zero. This remains the explicit queue
boundary harness; open game command-list support is the next gate.
All captures, extracted PTX and real weights remain in ignored private result
directories. To repeat the full temporal scan without editing sources:
```powershell
$env:PYTHONPATH="$PWD/.tools/python/vendor"
python tools/windows/m_temporal_validate.py --capture-dir test-results/ngx-m-exact-pv-capture/captures --output-dir test-results/m-temporal-reference --exact
```
The game selected for the next integration test is Silent Hill 2 at
`D:\Games\SILENT HILL 2\SHProto\Binaries\Win64\SHProto-Win64-Shipping.exe`.
No game files have been modified at this milestone.
## Source baseline (checked 2026-09-29) ## Source baseline (checked 2026-09-29)
* d4r: `ed1ab60`, main, 14 commits; https://github.com/countervolts/d4r. * d4r: `ed1ab60`, main, 14 commits; https://github.com/countervolts/d4r.
+54 -2
View File
@@ -5,6 +5,20 @@
// CIN patch-expand input channels (0 = none): decoder input = f16(q8(expand(low-res CIN)) + skip) // CIN patch-expand input channels (0 = none): decoder input = f16(q8(expand(low-res CIN)) + skip)
#pragma once #pragma once
#include "swin_common.h" #include "swin_common.h"
#ifdef D4R_SWIN_DIAGNOSTICS
extern "C" {
__device__ int d4r_swin_debug_block[2] = {0, 4};
__device__ half_t d4r_swin_debug_values[4 * 64 * 128];
}
template <int C, int AST> __device__ __forceinline__ void swin_snapshot(
const half_t* activation, int stage, int bx, int by, int tid, bool pair_order) {
if (bx == d4r_swin_debug_block[0] && by == d4r_swin_debug_block[1] && tid < 64)
for (int c = 0; c < C; ++c)
d4r_swin_debug_values[(stage * 64 + tid) * 128 + c] =
activation[tid * AST + (pair_order ? gperm(c) : apos(c))];
__syncthreads();
}
#endif
// Immediate lane masks need only a compile-time integer, not the host C++ // Immediate lane masks need only a compile-time integer, not the host C++
// standard library (which is absent from device-only Windows HIP builds). // standard library (which is absent from device-only Windows HIP builds).
@@ -79,7 +93,12 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
static_assert(NW % NH == 0 && NW / NH <= 4, "waves per head"); static_assert(NW % NH == 0 && NW / NH <= 4, "waves per head");
constexpr int NPL = L::NPL, NT = L::NT, INNER = L::INNER; constexpr int NPL = L::NPL, NT = L::NT, INNER = L::INNER;
constexpr int AST = (C > INNER ? C : INNER) + 8; constexpr int AST = (C > INNER ? C : INNER) + 8;
constexpr int GST = 40, QST = 40, PST = 24; constexpr int GST = 40, PST = 24;
#ifdef SWIN_EXACT_PV
constexpr int QST = 56;
#else
constexpr int QST = 40;
#endif
#ifndef SWIN_MLP_R #ifndef SWIN_MLP_R
#define SWIN_MLP_R(NW) ((NW) / 2) #define SWIN_MLP_R(NW) ((NW) / 2)
#endif #endif
@@ -294,6 +313,9 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
__syncthreads(); __syncthreads();
// ------------------------------------------------ stage 2: Q/V projection + attention (wave = head) // ------------------------------------------------ stage 2: Q/V projection + attention (wave = head)
#ifdef D4R_SWIN_DIAGNOSTICS
swin_snapshot<C, AST>(A, 0, bx, by, tid, false);
#endif
{ {
constexpr int MW = 4 / (NW / NH); // windows per wave constexpr int MW = 4 / (NW / NH); // windows per wave
const int h = NW == NH ? wv : wv % NH, m0 = NW == NH ? 0 : MW * (wv / NH); const int h = NW == NH ? wv : wv % NH, m0 = NW == NH ? 0 : MW * (wv / NH);
@@ -400,6 +422,13 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
Qs[mrow(2 * k + 1) * PST + l16] = pw[1]; Qs[mrow(2 * k + 1) * PST + l16] = pw[1];
} }
wave_sync(); wave_sync();
#ifdef SWIN_EXACT_PV
half_t* Vs = Qs + 16 * PST;
for (int nt = 0; nt < 2; ++nt)
for (int i = 0; i < 8; ++i)
Vs[mrow(i) * 32 + 16 * nt + l16] = vp[m][nt][i / 2][i & 1];
wave_sync();
#endif
const sop pa = lds16(Qs + l16 * PST); const sop pa = lds16(Qs + l16 * PST);
#pragma unroll #pragma unroll
for (int nt = 0; nt < 2; ++nt) for (int nt = 0; nt < 2; ++nt)
@@ -422,7 +451,19 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
} }
const h16 vb = __builtin_bit_cast(h16, vw); const h16 vb = __builtin_bit_cast(h16, vw);
#endif #endif
const f8v o = wmma(pa, vb, splat(0.0f)); f8v o = wmma(pa, vb, splat(0.0f));
#ifdef SWIN_EXACT_PV
// FP16 P/V products need more than 24 significant bits in a
// cancellation-heavy dot. An f32 WMMA can round to the wrong
// FP16 neighbour at a midpoint, then flip the FP8 output code.
// This baseline sums the 16 FP16 products before one FP16 round.
for (int i = 0; i < 8; ++i) {
double dot = 0.0;
for (int k = 0; k < 16; ++k)
dot += (double)Qs[mrow(i) * PST + k] * (double)Vs[k * 32 + 16 * nt + l16];
o[i] = (float)(half_t)dot;
}
#endif
const int ocol = apos(32 * h + ginv(16 * nt + l16)); const int ocol = apos(32 * h + ginv(16 * nt + l16));
#pragma unroll #pragma unroll
for (int i = 0; i < 8; i += 2) for (int i = 0; i < 8; i += 2)
@@ -438,6 +479,9 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
__syncthreads(); __syncthreads();
// ------------------------------------------------ stage 3: output projection + residual // ------------------------------------------------ stage 3: output projection + residual
#ifdef D4R_SWIN_DIAGNOSTICS
swin_snapshot<INNER, AST>(A, 3, bx, by, tid, false);
#endif
#pragma unroll #pragma unroll
for (int kc = 0; kc < NH; ++kc) for (int kc = 0; kc < NH; ++kc)
{ {
@@ -471,6 +515,11 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
if (tid < 64) if (tid < 64)
{ {
// the row as 128-bit LDS loads: element 8 j + c of n8 tile j // the row as 128-bit LDS loads: element 8 j + c of n8 tile j
#ifdef D4R_SWIN_DIAGNOSTICS
if (bx == d4r_swin_debug_block[0] && by == d4r_swin_debug_block[1])
for (int c = 0; c < C; ++c)
d4r_swin_debug_values[(64 + tid) * 128 + c] = A[tid * AST + gperm(c)];
#endif
h8 row[L::NT8]; h8 row[L::NT8];
#pragma unroll #pragma unroll
for (int j = 0; j < L::NT8; ++j) for (int j = 0; j < L::NT8; ++j)
@@ -529,6 +578,9 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
__syncthreads(); __syncthreads();
// ------------------------------------------------ stage 4: MLP, R chunks per round // ------------------------------------------------ stage 4: MLP, R chunks per round
#ifdef D4R_SWIN_DIAGNOSTICS
swin_snapshot<C, AST>(A, 2, bx, by, tid, false);
#endif
{ {
constexpr int WPC = NW / R, MPW = 4 / WPC; // waves per chunk, fc1 m tiles per wave constexpr int WPC = NW / R, MPW = 4 / WPC; // waves per chunk, fc1 m tiles per wave
const int cw = wv / WPC, mb = MPW * (wv % WPC); const int cw = wv / WPC, mb = MPW * (wv % WPC);
+3 -1
View File
@@ -98,7 +98,7 @@ def layout(C, heads, pre=0):
L['end'] = M0 + 66 * C + 64 + (64 * C + 64) * (NC - 1) L['end'] = M0 + 66 * C + 64 + (64 * C + 64) * (NC - 1)
return L return L
def swin_block(P, x0, C, heads, pre=0): def swin_block(P, x0, C, heads, pre=0, stages=None):
"""x0: 64 x C natural (f16 values). Returns (x2 pair order, out natural fp8 values).""" """x0: 64 x C natural (f16 values). Returns (x2 pair order, out natural fp8 values)."""
fp8 = lambda T: E4M3[P.wbuf[T]] fp8 = lambda T: E4M3[P.wbuf[T]]
L = layout(C, heads, pre) L = layout(C, heads, pre)
@@ -116,6 +116,8 @@ def swin_block(P, x0, C, heads, pre=0):
Wo[32 * s:32 * s + 32] = woff_table(L['head'][s] + 64 * C + 512, 0, 512, 32, C) Wo[32 * s:32 * s + 32] = woff_table(L['head'][s] + 64 * C + 512, 0, 512, 32, C)
x1 = mma_chain(pair_to_nat(q8(O)), fp8(Wo), C0=f16(nat_to_pair(x0) + P.f16vec(L['bo'], C)[None, :])) x1 = mma_chain(pair_to_nat(q8(O)), fp8(Wo), C0=f16(nat_to_pair(x0) + P.f16vec(L['bo'], C)[None, :]))
h2 = pair_to_nat(rms_from_sum(x1, tree_norm2(x1), P.f16vec(L['g2'], C))) h2 = pair_to_nat(rms_from_sum(x1, tree_norm2(x1), P.f16vec(L['g2'], C)))
if stages is not None:
stages.update(h1=h1, x1=pair_to_nat(x1), h2=h2, O=pair_to_nat(q8(O)))
x2 = f16(x1 + P.f16vec(L['b2'], C)[None, :]) x2 = f16(x1 + P.f16vec(L['b2'], C)[None, :])
idx = gp(np.arange(32)) idx = gp(np.arange(32))
for c, B1 in enumerate(L['b1']): for c, B1 in enumerate(L['b1']):
@@ -0,0 +1,151 @@
diff --git a/llvm_zluda/src/compile.rs b/llvm_zluda/src/compile.rs
index 5a54417..90c0034 100644
--- a/llvm_zluda/src/compile.rs
+++ b/llvm_zluda/src/compile.rs
@@ -158,6 +158,10 @@ fn wmma_rewrite_flags(gcn_arch: &str) -> Option<u32> {
let mut flags = if wmma_fp8_rewrite_requested() { 1 } else { 0 };
if family == 12 {
flags |= 2;
+ // Explicit diagnostic/correctness mode; retain upstream's fast default.
+ if std::env::var("D4R_ZLUDA_WMMA_F16_REFERENCE").is_ok_and(|value| value.trim() == "1") {
+ flags |= 8;
+ }
// native FP8 needs gfx12 hardware; the layout shim on gfx11 takes it only for tests (D4R_WMMA12_SHIM)
if flags & 1 != 0 && wmma_fp8_native_requested() {
flags |= 4;
diff --git a/llvm_zluda/src/lib.cpp b/llvm_zluda/src/lib.cpp
index 07238db..d262fc6 100644
--- a/llvm_zluda/src/lib.cpp
+++ b/llvm_zluda/src/lib.cpp
@@ -970,6 +970,7 @@ static unsigned widenMmaK8(Module *M)
// Flags bit 0: also lower m16n8k32 e4m3 MMAs (D4R_ZLUDA_WMMA_FP8=1).
// Flags bit 1: gfx12 (RDNA4) WMMA layout: the d4r_wmma12_* helpers.
// Flags bit 2 (with bit 1): e4m3 MMAs on native FP8 WMMA: the d4r_wmma12f8_* e4m3 helpers.
+// Flags bit 3 (with bit 1): f64 sum / one f16 round for f16 accumulator MMAs.
// The helper sets share one contract (see zluda_ptx_impl.cpp), so only their names change here.
unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef, unsigned Flags)
{
@@ -999,14 +1000,15 @@ unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef, unsigned Flags)
const std::string Arch = Gfx12 ? "__zluda_ptx_impl_d4r_wmma12_" : "__zluda_ptx_impl_d4r_wmma_";
const std::string E4m3 = NativeFp8 ? "__zluda_ptx_impl_d4r_wmma12f8_" : Arch;
const std::string Widen = NativeFp8 ? E4m3 : "__zluda_ptx_impl_d4r_wmma_";
+ const std::string F16 = Arch + (Gfx12 && (Flags & 8) != 0 ? "t_mma_f16_reference" : "t_mma_f16");
Helpers H;
H.AFromB = M->getOrInsertFunction((Arch + "t_a_from_b").c_str(), Attributes, V8, V2);
H.BFromA = M->getOrInsertFunction((Arch + "t_b_from_a").c_str(), Attributes, V8, V4);
- H.Mma = M->getOrInsertFunction((Arch + "t_mma_f16").c_str(), Attributes, V2, V8, V8, V2);
+ H.Mma = M->getOrInsertFunction(F16.c_str(), Attributes, V2, V8, V8, V2);
H.FromNv = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_from_nv", Attributes, V2, V2);
H.ToNv = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_to_nv", Attributes, V2, V2);
H.AFromBPair = M->getOrInsertFunction((Arch + "t_a_from_b_pair").c_str(), Attributes, V8, V2, V2);
- H.MmaPair = M->getOrInsertFunction((Arch + "t_mma_f16_pair").c_str(), Attributes, V4, V8, V8, V2, V2);
+ H.MmaPair = M->getOrInsertFunction((F16 + "_pair").c_str(), Attributes, V4, V8, V8, V2, V2);
{
Type *F = Type::getFloatTy(Context);
Type *V4F = FixedVectorType::get(F, 4);
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
index 4ff25dd..36bd6b1 100644
--- a/ptx/lib/zluda_ptx_impl.cpp
+++ b/ptx/lib/zluda_ptx_impl.cpp
@@ -1801,6 +1801,78 @@ __device__ static wmma_float8 wmma12_f16(wmma_uint8 a, wmma_uint8 b, wmma_float8
#endif
}
+// Correctness baseline for f16-accumulator MMAs. f32 WMMA accumulation may
+// reach the other f16 neighbour around cancellation-heavy rounding midpoints.
+// Keep the existing gfx12 gathers/layout; sum their f16 operands in f64 and
+// round directly to f16 once, matching d4r's independent NumPy/PTX reference.
+// The e4m3 and f32-shadow paths continue using native WMMA.
+__device__ static double wmma12_half_value(uint32_t bits)
+{
+ const uint32_t magnitude = bits & 0x7fffu, exponent = magnitude >> 10;
+ const double value = exponent == 0 ? double(magnitude) * 0x1p-24 :
+ double(std::bit_cast<float>(exponent == 31 ? (0x7f800000u | ((magnitude & 1023u) << 13)) :
+ ((magnitude << 13) + 0x38000000u)));
+ return bits & 0x8000u ? -value : value;
+}
+
+// Preserve f16 subnormal result bits even when the target's default f16
+// arithmetic mode flushes them. No intermediate f32 rounding is involved.
+__device__ static uint32_t wmma12_half_round(double value)
+{
+ const uint64_t bits = std::bit_cast<uint64_t>(value);
+ const uint32_t sign = uint32_t(bits >> 48) & 0x8000u;
+ const uint32_t exponent = uint32_t((bits >> 52) & 2047u);
+ const uint64_t fraction = bits & 0xfffffffffffffu;
+ if (exponent == 2047u) return sign | (fraction ? 0x7e00u : 0x7c00u);
+ const int power = int(exponent) - 1023;
+ if (power < -25) return sign;
+ if (power > 15) return sign | 0x7c00u;
+ const unsigned shift = power < -14 ? unsigned(28 - power) : 42u;
+ const uint64_t significand = fraction | 0x10000000000000ull;
+ uint32_t code = uint32_t(significand >> shift);
+ const uint64_t tail = significand & ((uint64_t(1) << shift) - 1);
+ const uint64_t midpoint = uint64_t(1) << (shift - 1);
+ code += tail > midpoint || (tail == midpoint && (code & 1u));
+ if (power >= -14) code += uint32_t(power + 14) << 10;
+ return sign | code;
+}
+
+__device__ static wmma_uint4 wmma12_reference_c(uint2::Native_vec_ c0, uint2::Native_vec_ c1, bool paired)
+{
+ const bool high = wmma12_half() != 0;
+ const uint2::Native_vec_ own = paired && high ? c1 : c0;
+ const uint2::Native_vec_ send = paired && !high ? c1 : c0;
+ const uint2::Native_vec_ other = {wmma12_other_half(send.x), wmma12_other_half(send.y)};
+ const uint2::Native_vec_ even = paired && high ? other : own;
+ const uint2::Native_vec_ odd = paired && high ? own : other;
+ return {(even.x & 0xffffu) | (odd.x << 16), (even.x >> 16) | (odd.x & 0xffff0000u),
+ (even.y & 0xffffu) | (odd.y << 16), (even.y >> 16) | (odd.y & 0xffff0000u)};
+}
+
+__device__ static wmma_uint4 wmma12_f16_reference(wmma_uint8 a, wmma_uint8 b, wmma_uint4 c)
+{
+ // x/y: packed even rows, z/w: packed odd rows, in this half-wave.
+ wmma_uint4 result = {};
+ const uint32_t half = wmma12_half();
+ for (uint32_t i = 0; i < 8; ++i)
+ {
+ const uint32_t row = i + 8 * half;
+ double sum = wmma12_half_value((c[i / 2] >> (16 * (i & 1))) & 0xffffu);
+ for (uint32_t khalf = 0; khalf < 2; ++khalf)
+ for (uint32_t j = 0; j < 4; ++j)
+ {
+ const uint32_t av = wmma_from_lane(row + 16 * khalf, a[j]);
+ const uint32_t other = wmma12_other_half(b[j]);
+ const uint32_t bv = half == khalf ? b[j] : other;
+ sum += wmma12_half_value(av & 0xffffu) * wmma12_half_value(bv & 0xffffu);
+ sum += wmma12_half_value(av >> 16) * wmma12_half_value(bv >> 16);
+ }
+ // Do not round through f32: that can reintroduce the midpoint error.
+ result[(i & 1) * 2 + i / 4] |= wmma12_half_round(sum) << (16 * ((i / 2) & 1));
+ }
+ return result;
+}
+
// f32 D' = A' B' + C' for operands whose dwords hold two e4m3 bytes in their low 16 bits (the d4r_wmma12f8
// e4m3 "widening" below keeps the bytes): true e4m3 values, no scaling
__device__ static wmma_float8 wmma12_e4m3(wmma_uint8 a, wmma_uint8 b, wmma_float8 c)
@@ -1959,6 +2031,22 @@ extern "C"
return wmma12_t_from_d_pair(wmma12_f16(a_t, b_t, wmma12_c_from_t_pair(c0_t, c1_t)));
}
+ uint2::Native_vec_ FUNC(d4r_wmma12_t_mma_f16_reference)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c_t)
+ {
+ const wmma_uint4 rows = wmma12_f16_reference(a_t, b_t, wmma12_reference_c(c_t, c_t, false));
+ const uint2::Native_vec_ other = {wmma12_other_half(rows.z), wmma12_other_half(rows.w)};
+ return wmma12_half() ? other : uint2::Native_vec_{rows.x, rows.y};
+ }
+
+ uint4::Native_vec_ FUNC(d4r_wmma12_t_mma_f16_reference_pair)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c0_t, uint2::Native_vec_ c1_t)
+ {
+ const wmma_uint4 rows = wmma12_f16_reference(a_t, b_t, wmma12_reference_c(c0_t, c1_t, true));
+ const bool high = wmma12_half() != 0;
+ const uint32_t r0 = wmma12_other_half(high ? rows.x : rows.z);
+ const uint32_t r1 = wmma12_other_half(high ? rows.y : rows.w);
+ return high ? uint4::Native_vec_{r0, r1, rows.z, rows.w} : uint4::Native_vec_{rows.x, rows.y, r0, r1};
+ }
+
wmma_float4 FUNC(d4r_wmma12_s_mma_f16)(wmma_uint8 a_t, wmma_uint8 b_t, wmma_float4 c, float up, float down)
{
return wmma12_s_from_d(wmma12_f16(a_t, b_t, wmma12_c_from_s(c * up))) * down;
+1 -1
View File
@@ -16,7 +16,7 @@ foreach ($layer in @('enc1','enc2','enc3_tube','dec2','dec1')) {
} }
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll & python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll
if ($LASTEXITCODE) { throw "Native manifest generation failed ($LASTEXITCODE)" } if ($LASTEXITCODE) { throw "Native manifest generation failed ($LASTEXITCODE)" }
@{architecture='gfx1201'; family='M'; numerics='SWIN_EXACT; FP16 widening; native FP8 disabled'; @{architecture='gfx1201'; family='M'; numerics='SWIN_EXACT; SWIN_EXACT_PV; FP16 widening; native FP8 disabled';
dlssSha256=(Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash; dlssSha256=(Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash;
validation='synthetic layers passed; actual DLSS capture/reference validation required'; validation='synthetic layers passed; actual DLSS capture/reference validation required';
modules=@(Get-ChildItem -LiteralPath $OutputDirectory -Filter '*.hsaco' | Get-FileHash -Algorithm SHA256)} | modules=@(Get-ChildItem -LiteralPath $OutputDirectory -Filter '*.hsaco' | Get-FileHash -Algorithm SHA256)} |
+11 -3
View File
@@ -53,7 +53,7 @@ $originalPythonPath = $env:PYTHONPATH
$originalVerbose = $env:D4R_ZLUDA_VERBOSE $originalVerbose = $env:D4R_ZLUDA_VERBOSE
$originalCache = $env:ZLUDA_CACHE_DIR $originalCache = $env:ZLUDA_CACHE_DIR
$originalCodegen = @{} $originalCodegen = @{}
foreach ($setting in @('D4R_ZLUDA_WMMA','D4R_ZLUDA_WMMA_FP8','D4R_ZLUDA_WMMA_FP8_NATIVE')) { foreach ($setting in @('D4R_ZLUDA_WMMA','D4R_ZLUDA_WMMA_FP8','D4R_ZLUDA_WMMA_FP8_NATIVE','D4R_ZLUDA_WMMA_F16_REFERENCE')) {
$originalCodegen[$setting] = [Environment]::GetEnvironmentVariable($setting,'Process') $originalCodegen[$setting] = [Environment]::GetEnvironmentVariable($setting,'Process')
} }
$exitStatus = 1 $exitStatus = 1
@@ -103,14 +103,14 @@ function Invoke-Probe([string]$Name, [string]$Exe, [string[]]$Arguments) {
try { try {
if (!$env:ZLUDA_CACHE_DIR) { $env:ZLUDA_CACHE_DIR = Join-Path $repoRoot 'build/zluda-cache-windows' } if (!$env:ZLUDA_CACHE_DIR) { $env:ZLUDA_CACHE_DIR = Join-Path $repoRoot 'build/zluda-cache-windows' }
$summary.zludaCacheDirectory = $env:ZLUDA_CACHE_DIR $summary.zludaCacheDirectory = $env:ZLUDA_CACHE_DIR
foreach ($setting in @('D4R_ZLUDA_WMMA','D4R_ZLUDA_WMMA_FP8','D4R_ZLUDA_WMMA_FP8_NATIVE')) { foreach ($setting in @('D4R_ZLUDA_WMMA','D4R_ZLUDA_WMMA_FP8','D4R_ZLUDA_WMMA_FP8_NATIVE','D4R_ZLUDA_WMMA_F16_REFERENCE')) {
if ($null -eq $originalCodegen[$setting]) { if ($null -eq $originalCodegen[$setting]) {
[Environment]::SetEnvironmentVariable($setting, $(if ($setting -eq 'D4R_ZLUDA_WMMA_FP8_NATIVE') { '0' } else { '1' }), 'Process') [Environment]::SetEnvironmentVariable($setting, $(if ($setting -eq 'D4R_ZLUDA_WMMA_FP8_NATIVE') { '0' } else { '1' }), 'Process')
} }
} }
$summary.codegen = @{} $summary.codegen = @{}
foreach ($setting in $originalCodegen.Keys) { $summary.codegen[$setting] = [Environment]::GetEnvironmentVariable($setting,'Process') } foreach ($setting in $originalCodegen.Keys) { $summary.codegen[$setting] = [Environment]::GetEnvironmentVariable($setting,'Process') }
Write-Host "ZLUDA codegen: WMMA=$env:D4R_ZLUDA_WMMA FP8 widening=$env:D4R_ZLUDA_WMMA_FP8 native FP8=$env:D4R_ZLUDA_WMMA_FP8_NATIVE" Write-Host "ZLUDA codegen: WMMA=$env:D4R_ZLUDA_WMMA FP8 widening=$env:D4R_ZLUDA_WMMA_FP8 native FP8=$env:D4R_ZLUDA_WMMA_FP8_NATIVE f16 reference=$env:D4R_ZLUDA_WMMA_F16_REFERENCE"
if ($NgxOnly -and (!$NgxCore -or !$DlssDll)) { throw '-NgxOnly requires both locally supplied NVIDIA DLL paths.' } if ($NgxOnly -and (!$NgxCore -or !$DlssDll)) { throw '-NgxOnly requires both locally supplied NVIDIA DLL paths.' }
if ($RequireNativeNetwork) { if ($RequireNativeNetwork) {
if ($NgxMode -notin @('evaluate','d3d12') -or $Preset -notin @(11,13) -or !$env:D4R_ZLUDA_NATIVE_DIR) { if ($NgxMode -notin @('evaluate','d3d12') -or $Preset -notin @(11,13) -or !$env:D4R_ZLUDA_NATIVE_DIR) {
@@ -197,6 +197,14 @@ try {
if ($cudaOk -and ($NgxMode -eq 'evaluate' -or (Test-Path (Join-Path $ZludaRoot 'build-info.json')))) { if ($cudaOk -and ($NgxMode -eq 'evaluate' -or (Test-Path (Join-Path $ZludaRoot 'build-info.json')))) {
$cudaOk = Invoke-Probe 'cuda-images' (Join-Path $bin 'd4r_cuda_image_probe.exe') @( $cudaOk = Invoke-Probe 'cuda-images' (Join-Path $bin 'd4r_cuda_image_probe.exe') @(
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'), '--iterations', "$Iterations") '--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'), '--iterations', "$Iterations")
if ($cudaOk) {
$previousF16 = $env:D4R_ZLUDA_WMMA_F16_REFERENCE
try {
$env:D4R_ZLUDA_WMMA_F16_REFERENCE = '1'
$cudaOk = Invoke-Probe 'cuda-f16-mma-reference' (Join-Path $bin 'd4r_cuda_mma_probe.exe') @(
'--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'))
} finally { $env:D4R_ZLUDA_WMMA_F16_REFERENCE = $previousF16 }
}
} }
} }
if ($cudaOk) { if ($cudaOk) {
+165
View File
@@ -0,0 +1,165 @@
#include "hip_api.h"
#include "cuda_api.h"
#include <array>
#include <cmath>
#include <cstring>
// Public synthetic matrix fragments, independent of NVIDIA DLLs and weights.
static constexpr char source[] = R"PTX(
.version 7.0
.target sm_75
.address_size 64
.visible .entry d4r_f16_mma_reference(
.param .u64 A, .param .u64 B, .param .u64 C, .param .u64 D)
{
.reg .b32 %r<24>;
.reg .b64 %rd<12>;
.reg .b16 %h<4>;
ld.param.u64 %rd0, [A];
ld.param.u64 %rd1, [B];
ld.param.u64 %rd2, [C];
ld.param.u64 %rd3, [D];
mov.u32 %r0, %ctaid.x;
mov.u32 %r1, %tid.x;
shr.u32 %r2, %r1, 2;
and.b32 %r3, %r1, 3;
shl.b32 %r3, %r3, 1;
mul.wide.u32 %rd4, %r0, 512;
add.u64 %rd0, %rd0, %rd4;
mul.wide.u32 %rd4, %r0, 256;
add.u64 %rd1, %rd1, %rd4;
add.u64 %rd2, %rd2, %rd4;
add.u64 %rd3, %rd3, %rd4;
mad.lo.u32 %r4, %r2, 16, %r3;
mul.wide.u32 %rd4, %r4, 2;
add.u64 %rd5, %rd0, %rd4;
ld.global.u32 %r8, [%rd5];
ld.global.u32 %r9, [%rd5+256];
ld.global.u32 %r10, [%rd5+16];
ld.global.u32 %r11, [%rd5+272];
mad.lo.u32 %r4, %r3, 8, %r2;
mul.wide.u32 %rd4, %r4, 2;
add.u64 %rd5, %rd1, %rd4;
ld.global.b16 %h0, [%rd5];
ld.global.b16 %h1, [%rd5+16];
mov.b32 %r12, {%h0,%h1};
ld.global.b16 %h2, [%rd5+128];
ld.global.b16 %h3, [%rd5+144];
mov.b32 %r13, {%h2,%h3};
mad.lo.u32 %r4, %r2, 8, %r3;
mul.wide.u32 %rd4, %r4, 2;
add.u64 %rd5, %rd2, %rd4;
ld.global.u32 %r14, [%rd5];
ld.global.u32 %r15, [%rd5+128];
mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16
{%r16,%r17}, {%r8,%r9,%r10,%r11}, {%r12,%r13}, {%r14,%r15};
add.u64 %rd5, %rd3, %rd4;
st.global.u32 [%rd5], %r16;
st.global.u32 [%rd5+128], %r17;
ret;
}
)PTX";
static uint16_t half_bits(double value) {
_Float16 half = static_cast<_Float16>(value);
uint16_t bits;
std::memcpy(&bits, &half, sizeof(bits));
return bits;
}
static double half_value(uint16_t bits) {
_Float16 half;
std::memcpy(&half, &bits, sizeof(bits));
return static_cast<double>(half);
}
int main(int argc, char** argv) {
using namespace d4r::diag;
start();
try {
Args args(argc, argv);
HipApi hip(args.hip_root);
hipDeviceProp_t props{};
hip.select_gfx1201(args.device, props);
CudaApi cuda(args.cuda_dll);
cuda.check(cuda.cuInit(0), "cuInit");
CUdevice device = -1;
int count = 0;
cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount");
for (int i = 0; i < count; ++i) {
CUdevice candidate;
int bus, pci, domain;
cuda.check(cuda.cuDeviceGet(&candidate, i), "cuDeviceGet");
cuda.check(cuda.cuDeviceGetAttribute(&bus, 33, candidate), "PCI bus");
cuda.check(cuda.cuDeviceGetAttribute(&pci, 34, candidate), "PCI device");
cuda.check(cuda.cuDeviceGetAttribute(&domain, 50, candidate), "PCI domain");
if (bus == props.pciBusID && pci == props.pciDeviceID && domain == props.pciDomainID) device = candidate;
}
if (device < 0) throw std::runtime_error("CUDA/HIP PCI identity mismatch");
CUcontext context = nullptr;
cuda.check(cuda.cuDevicePrimaryCtxRetain(&context, device), "primary context");
struct Context { CudaApi& api; CUdevice device; ~Context() { api.cuCtxSetCurrent(nullptr); api.cuDevicePrimaryCtxRelease_v2(device); } } context_owner{cuda, device};
cuda.check(cuda.cuCtxSetCurrent(context), "cuCtxSetCurrent");
CUmodule module = nullptr;
cuda.check(cuda.cuModuleLoadData(&module, source), "MMA PTX JIT");
struct Module { CudaApi& api; CUmodule module; ~Module() { api.cuModuleUnload(module); } } module_owner{cuda, module};
CUfunction kernel = nullptr;
cuda.check(cuda.cuModuleGetFunction(&kernel, module, "d4r_f16_mma_reference"), "MMA kernel");
constexpr size_t cases = 512;
std::vector<uint16_t> a(cases * 256), b(cases * 128), c(cases * 128), d(cases * 128);
uint32_t random = 0x192a321u;
auto next = [&]() { random = random * 1664525u + 1013904223u; return random; };
for (size_t n = 0; n < cases; ++n) {
for (size_t i = 0; i < 256; ++i) a[n * 256 + i] = uint16_t(0x2800u + (next() >> 20));
for (size_t i = 0; i < 128; ++i) {
b[n * 128 + i] = uint16_t((next() & 0x8000u) | (0x3000u + (next() >> 20)));
c[n * 128 + i] = half_bits(n % 3 ? 0. : 0.015625);
}
// Sweep large cancellation terms and a tiny surviving product
// across K positions; this exposes loss hidden by integer fixtures.
if (n < 256) {
for (size_t i = 0; i < 256; ++i) a[n * 256 + i] = half_bits(1.);
std::fill(b.begin() + n * 128, b.begin() + (n + 1) * 128, half_bits(0.));
for (size_t col = 0; col < 8; ++col) {
b[n * 128 + (n % 16) * 8 + col] = half_bits(32768.);
b[n * 128 + ((n % 16 + 1) % 16) * 8 + col] = half_bits(-32768.);
b[n * 128 + ((n % 16 + 2 + n / 16 % 14) % 16) * 8 + col] = half_bits(0.0009765625);
}
}
}
struct Allocation {
CudaApi& api; CUdeviceptr pointer = 0;
Allocation(CudaApi& api, size_t bytes) : api(api) { api.check(api.cuMemAlloc_v2(&pointer, bytes), "MMA allocate"); }
~Allocation() { if (pointer) api.cuMemFree_v2(pointer); }
} da(cuda, a.size() * 2), db(cuda, b.size() * 2), dc(cuda, c.size() * 2), dd(cuda, d.size() * 2);
cuda.check(cuda.cuMemcpyHtoD_v2(da.pointer, a.data(), a.size() * 2), "MMA A upload");
cuda.check(cuda.cuMemcpyHtoD_v2(db.pointer, b.data(), b.size() * 2), "MMA B upload");
cuda.check(cuda.cuMemcpyHtoD_v2(dc.pointer, c.data(), c.size() * 2), "MMA C upload");
std::fill(d.begin(), d.end(), uint16_t(0x7e00));
cuda.check(cuda.cuMemcpyHtoD_v2(dd.pointer, d.data(), d.size() * 2), "MMA NaN sentinel");
void* parameters[] = {&da.pointer, &db.pointer, &dc.pointer, &dd.pointer};
cuda.check(cuda.cuLaunchKernel(kernel, cases, 1, 1, 32, 1, 1, 0, nullptr, parameters, nullptr), "MMA launch");
cuda.check(cuda.cuCtxSynchronize(), "MMA synchronize");
cuda.check(cuda.cuMemcpyDtoH_v2(d.data(), dd.pointer, d.size() * 2), "MMA download");
size_t mismatch = 0;
for (size_t n = 0; n < cases; ++n)
for (size_t row = 0; row < 16; ++row)
for (size_t col = 0; col < 8; ++col) {
double sum = half_value(c[n * 128 + row * 8 + col]);
for (size_t k = 0; k < 16; ++k)
sum += half_value(a[n * 256 + row * 16 + k]) * half_value(b[n * 128 + k * 8 + col]);
const uint16_t expected = half_bits(sum);
const uint16_t actual = d[n * 128 + row * 8 + col];
if (expected != actual) {
if (mismatch++ < 8) std::fprintf(stderr, "MMA_DIFFERENT case=%zu row=%zu column=%zu expected=%04x actual=%04x\n", n, row, col, expected, actual);
}
}
std::printf("MMA_REFERENCE cases=%zu elements=%zu differing=%zu\n", cases, d.size(), mismatch);
if (mismatch) return 5;
std::printf("PASS CUDA_F16_MMA architecture=gfx1201 cases=%zu exact_f16=1\n", cases);
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "FAIL CUDA_F16_MMA %s\n", error.what());
loaded_modules();
return 4;
}
}
+54
View File
@@ -0,0 +1,54 @@
"""Compare logical M inputs/outputs between private temporal capture sequences."""
import argparse
import pathlib
import numpy as np
from m_replay_validate import me, LAYERS
def logical(parameters, pointer, count, output=False, directory=None):
base, allocation = parameters.alloc_of(pointer)
if output:
identifier = next(i for i, (b, _) in parameters.allocs.items() if b == base)
allocation = np.fromfile(directory / 'gpu-output' / f'alloc-{identifier}.bin', np.uint8)
return allocation[pointer - base:pointer - base + count]
def compare(reference, actual):
paths = []
order = ['enc1', 'enc2', 'enc3_tube', 'dec2', 'dec1']
for path in reference.glob('replay-*-rrlite_*_4x4'):
layer = path.name.split('rrlite_', 1)[1][:-4]
ordinal = int(path.name.split('-')[1])
frame = (ordinal - 1) // (6 if layer == 'enc3_tube' else 1)
paths.append((frame, order.index(layer), ordinal, path, layer))
for frame, _, ordinal, path, layer in sorted(paths):
other = actual / path.name
a, b = me.Params(str(path)), me.Params(str(other))
c, _, merged, cin = LAYERS[layer]
if (a.W, a.H, a.sx, a.sy, a.grid) != (b.W, b.H, b.sx, b.sy, b.grid):
raise RuntimeError(f'Launch geometry differs: {path.name}')
fields = [('input', 'inp', (a.W // 2) * (a.H // 2) * cin if cin else a.W * a.H * c, False)]
if cin:
fields.append(('skip', 'p32', a.W * a.H * c, False))
fields.append(('output', 'out', a.W * a.H * c, True))
if merged:
fields.append(('merge', 'p48', (a.W // 2) * (a.H // 2) * merged, True))
for label, field, count, output in fields:
x = logical(a, getattr(a, field), count, output, path)
y = logical(b, getattr(b, field), count, output, other)
if x.size != count or y.size != count:
raise RuntimeError(f'Truncated {path.name} {label}')
differing = np.flatnonzero(x != y)
print(f'M_CAPTURE_COMPARE frame={frame} layer={layer} ordinal={ordinal} field={label} '
f'codes={count} differing={differing.size} '
f'first={int(differing[0]) if differing.size else -1}', flush=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--reference', type=pathlib.Path, required=True)
parser.add_argument('--actual', type=pathlib.Path, required=True)
args = parser.parse_args()
compare(args.reference, args.actual)
+9 -2
View File
@@ -18,7 +18,10 @@ def validate(args):
raise RuntimeError('Capture kernel does not match requested M layer') raise RuntimeError('Capture kernel does not match requested M layer')
native = {i: np.fromfile(args.native_dir / f'alloc-{i}.bin', np.uint8) for i in parameters.allocs} native = {i: np.fromfile(args.native_dir / f'alloc-{i}.bin', np.uint8) for i in parameters.allocs}
candidates = [] candidates = []
for bx, by in blocks(parameters.grid, 12): coordinates = args.block or blocks(parameters.grid, args.candidate_blocks)
for bx, by in coordinates:
if not (0 <= bx < parameters.grid[0] and 0 <= by < parameters.grid[1]):
raise RuntimeError(f'Block {bx},{by} is outside the capture grid')
model = model_block(parameters, args.layer, bx, by) model = model_block(parameters, args.layer, bx, by)
expected, actual = stored(parameters, args.layer, bx, by, native, model) expected, actual = stored(parameters, args.layer, bx, by, native, model)
difference = float(np.abs(expected - actual).max()) difference = float(np.abs(expected - actual).max())
@@ -52,8 +55,12 @@ if __name__ == '__main__':
parser.add_argument('--dlss-dll', type=pathlib.Path, required=True) parser.add_argument('--dlss-dll', type=pathlib.Path, required=True)
parser.add_argument('--output-dir', type=pathlib.Path, required=True) parser.add_argument('--output-dir', type=pathlib.Path, required=True)
parser.add_argument('--blocks', type=int, default=2) parser.add_argument('--blocks', type=int, default=2)
parser.add_argument('--candidate-blocks', type=int, default=12,
help='NumPy scan size used to choose the worst blocks for independent PTX')
parser.add_argument('--block', type=lambda value: tuple(map(int, value.split(','))),
action='append', help='Explicit x,y regression block (repeatable)')
args = parser.parse_args() args = parser.parse_args()
if args.blocks < 1: if args.blocks < 1 or args.candidate_blocks < 1:
parser.error('--blocks must be positive') parser.error('--blocks must be positive')
try: try:
validate(args) validate(args)
+4 -3
View File
@@ -142,7 +142,7 @@ def metrics(expected, actual, label):
return maximum, peak, psnr, steps return maximum, peak, psnr, steps
def validate(layer, directory, output, block_count, real_capture): def validate(layer, directory, output, block_count, real_capture, exact=False):
parameters = me.Params(str(directory)) parameters = me.Params(str(directory))
if parameters.kernel != f'rrlite_{layer}_4x4': if parameters.kernel != f'rrlite_{layer}_4x4':
raise RuntimeError('Replay kernel does not match requested M layer') raise RuntimeError('Replay kernel does not match requested M layer')
@@ -151,7 +151,7 @@ def validate(layer, directory, output, block_count, real_capture):
for bx, by in blocks(parameters.grid, block_count)] for bx, by in blocks(parameters.grid, block_count)]
expected, actual = (np.concatenate(values) for values in zip(*pairs)) expected, actual = (np.concatenate(values) for values in zip(*pairs))
maximum, peak, psnr, steps = metrics(expected, actual, f'M_REPLAY_REFERENCE kernel={layer}') maximum, peak, psnr, steps = metrics(expected, actual, f'M_REPLAY_REFERENCE kernel={layer}')
if (not real_capture and maximum != 0) or (real_capture and (steps.max() > 1 or psnr < 50)): if ((exact or not real_capture) and maximum != 0) or (real_capture and (steps.max() > 1 or psnr < 50)):
raise RuntimeError(f'M mismatch: exact synthetic / <=1 FP8 step and >=50 dB real gate; peak={peak}') raise RuntimeError(f'M mismatch: exact synthetic / <=1 FP8 step and >=50 dB real gate; peak={peak}')
print(f'PASS M_REPLAY_REFERENCE kernel={layer} blocks={len(pairs)} architecture=gfx1201 nan_inf=0') print(f'PASS M_REPLAY_REFERENCE kernel={layer} blocks={len(pairs)} architecture=gfx1201 nan_inf=0')
@@ -165,6 +165,7 @@ if __name__ == '__main__':
parser.add_argument('--size', type=int, choices=[8, 16, 24, 32], default=16) parser.add_argument('--size', type=int, choices=[8, 16, 24, 32], default=16)
parser.add_argument('--blocks', type=int, default=12) parser.add_argument('--blocks', type=int, default=12)
parser.add_argument('--real-capture', action='store_true') parser.add_argument('--real-capture', action='store_true')
parser.add_argument('--exact', action='store_true', help='Require every stored real FP8 value to match')
args = parser.parse_args() args = parser.parse_args()
try: try:
if args.mode == 'generate': if args.mode == 'generate':
@@ -172,7 +173,7 @@ if __name__ == '__main__':
else: else:
if not args.output_dir or args.blocks < 1: if not args.output_dir or args.blocks < 1:
parser.error('validate requires --output-dir and positive --blocks') parser.error('validate requires --output-dir and positive --blocks')
validate(args.layer, args.fixture_dir, args.output_dir, args.blocks, args.real_capture) validate(args.layer, args.fixture_dir, args.output_dir, args.blocks, args.real_capture, args.exact)
except Exception as error: except Exception as error:
print(f'FAIL M_REPLAY {error}', file=sys.stderr) print(f'FAIL M_REPLAY {error}', file=sys.stderr)
raise SystemExit(5) raise SystemExit(5)
+53
View File
@@ -0,0 +1,53 @@
"""Validate every captured M launch and spatial block; all allocations stay private."""
import argparse
import contextlib
import json
import pathlib
import sys
import traceback
from m_replay_validate import LAYERS, validate
def main(args):
args.output_dir.mkdir(parents=True, exist_ok=True)
summary = {'passed': False, 'captured_outputs': True, 'exact': args.exact, 'launches': []}
status = 1
try:
captures = sorted(args.capture_dir.glob('replay-*-rrlite_*_4x4'))
if not captures:
raise RuntimeError('No audited M captures were found')
for capture in captures:
layer = capture.name.split('rrlite_', 1)[1][:-4]
if layer not in LAYERS:
raise RuntimeError(f'Unknown captured M layer: {layer}')
logfile = args.output_dir / (capture.name + '.log')
with logfile.open('w', encoding='utf-8') as output, contextlib.redirect_stdout(output):
validate(layer, capture, capture / 'gpu-output', args.blocks, True, args.exact)
print(capture.name + ' ' + logfile.read_text().splitlines()[0], flush=True)
summary['launches'].append({'capture': capture.name, 'layer': layer, 'passed': True})
present = {item['layer'] for item in summary['launches']}
if present != set(LAYERS):
raise RuntimeError('Capture set does not contain all five M layers')
summary['passed'] = True
print(f'PASS M_TEMPORAL_REFERENCE launches={len(captures)} blocks={args.blocks} exact={int(args.exact)}', flush=True)
status = 0
except Exception as error:
summary['error'] = str(error)
summary['failed_capture'] = capture.name if 'capture' in locals() else None
traceback.print_exc()
finally:
(args.output_dir / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n', encoding='utf-8')
return status
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--capture-dir', type=pathlib.Path, required=True)
parser.add_argument('--output-dir', type=pathlib.Path, required=True)
parser.add_argument('--blocks', type=int, default=10000)
parser.add_argument('--exact', action='store_true')
args = parser.parse_args()
if args.blocks < 1:
parser.error('--blocks must be positive')
sys.exit(main(args))
+17
View File
@@ -82,6 +82,14 @@ int main(int argc, char** argv) {
hipModule_t module = nullptr; hipModule_t module = nullptr;
hip.check(hip.hipModuleLoad(&module, args.module.c_str()), "hipModuleLoad(replay gfx1201)"); hip.check(hip.hipModuleLoad(&module, args.module.c_str()), "hipModuleLoad(replay gfx1201)");
struct Cleanup { HipApi& hip; hipModule_t module; ~Cleanup() { (void)hip.hipModuleUnload(module); } } cleanup{hip, module}; struct Cleanup { HipApi& hip; hipModule_t module; ~Cleanup() { (void)hip.hipModuleUnload(module); } } cleanup{hip, module};
if (const char* debugBlock = std::getenv("D4R_SWIN_DEBUG_BLOCK")) {
int coordinates[2]{};
if (std::sscanf(debugBlock, "%d,%d", &coordinates[0], &coordinates[1]) != 2) throw std::runtime_error("D4R_SWIN_DEBUG_BLOCK must be x,y");
hipDeviceptr_t address = nullptr; size_t size = 0;
hip.check(hip.hipModuleGetGlobal(&address, &size, module, "d4r_swin_debug_block"), "Find Swin diagnostic block");
if (size != sizeof(coordinates)) throw std::runtime_error("Swin diagnostic coordinates ABI");
hip.check(hip.hipMemcpy(address, coordinates, size, hipMemcpyHostToDevice), "Set Swin diagnostic block");
}
auto scalar = [&](const char* name, unsigned fallback) { auto scalar = [&](const char* name, unsigned fallback) {
hipDeviceptr_t address = nullptr; size_t size = 0; hipDeviceptr_t address = nullptr; size_t size = 0;
if (hip.hipModuleGetGlobal(&address, &size, module, name) != hipSuccess) return fallback; if (hip.hipModuleGetGlobal(&address, &size, module, name) != hipSuccess) return fallback;
@@ -108,6 +116,15 @@ int main(int argc, char** argv) {
nullptr, launchArguments, nullptr), "hipModuleLaunchKernel(replay)"); nullptr, launchArguments, nullptr), "hipModuleLaunchKernel(replay)");
hip.check(hip.hipDeviceSynchronize(), "hipDeviceSynchronize(replay)"); hip.check(hip.hipDeviceSynchronize(), "hipDeviceSynchronize(replay)");
std::filesystem::create_directories(output); std::filesystem::create_directories(output);
if (std::getenv("D4R_SWIN_DEBUG_BLOCK")) {
hipDeviceptr_t address = nullptr; size_t size = 0;
hip.check(hip.hipModuleGetGlobal(&address, &size, module, "d4r_swin_debug_values"), "Find Swin diagnostic stages");
std::vector<uint8_t> bytes(size);
hip.check(hip.hipMemcpy(bytes.data(), address, size, hipMemcpyDeviceToHost), "Download Swin diagnostic stages");
std::ofstream file(output / "swin-stages.f16", std::ios::binary);
file.write(reinterpret_cast<const char*>(bytes.data()), std::streamsize(bytes.size()));
if (!file) throw std::runtime_error("Cannot save Swin diagnostic stages");
}
for (const auto& allocation : allocations) { for (const auto& allocation : allocations) {
std::vector<uint8_t> bytes(allocation->size); std::vector<uint8_t> bytes(allocation->size);
hip.check(hip.hipMemcpy(bytes.data(), allocation->device, bytes.size(), hipMemcpyDeviceToHost), "hipMemcpy(replay result)"); hip.check(hip.hipMemcpy(bytes.data(), allocation->device, bytes.size(), hipMemcpyDeviceToHost), "hipMemcpy(replay result)");
+26
View File
@@ -0,0 +1,26 @@
"""Compare compile-time Swin diagnostic snapshots with the NumPy intermediate values."""
import argparse
import numpy as np
from m_replay_validate import me, sm, LAYERS, metrics
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--capture-dir', required=True)
parser.add_argument('--stages', required=True)
parser.add_argument('--block', default='0,4')
parser.add_argument('--layer', choices=list(LAYERS), default='enc1')
args = parser.parse_args()
p = me.Params(args.capture_dir)
c, heads, _, cin = LAYERS[args.layer]
bx, by = map(int, args.block.split(','))
pre = 4 * c * cin + 8 * c if cin else 0
x0 = sm.decoder_input(p, bx, by, c, cin, heads, p.p32) if cin else sm.load_tokens(p, bx, by, c // 32)
stages = {}
sm.swin_block(p, x0, c, heads, pre, stages=stages)
actual = np.fromfile(args.stages, np.float16).reshape(4, 64, 128).astype(np.float64)
for index, name in enumerate(('h1', 'x1', 'h2', 'O')):
expected = stages[name]
values = actual[index, :, :expected.shape[1]]
metrics(expected, values, f'SWIN_STAGE {name}')
different = np.argwhere(expected != values)
for row, channel in different[:8]:
print(f'DIFFERENT stage={name} token={row} channel={channel} reference={expected[row, channel]} native={values[row, channel]}')