zluda: coalesce pair shadow stores, signed native FP8 conversion

Patch 0011 merges a complete accumulator-register pair overwrite into one
shadow-layout conversion (reads, rewritten shadow accesses and block
boundaries prevent it; partial writes keep read/modify/write), and requests
eight waves per SIMD for gfx12 native-FP8 functions. Patch 0012 keeps signs
in the native conversion: finite overflow clamped, NaNs mapped to signed
infinity, post-conversion sign masks gone; plain and ReLU remain
exhaustive-table identical to the software codec.

Eighty-frame harness at 720p->4K, excluding the first twenty: control
24.764 ms, coalescing 23.340 ms, plus eight waves 20.135 ms, plus signed
conversion 16.234 ms (15.144 ms with the harness idle gap removed); all
eighty saved frames byte-identical to the matching control. Twelve- and
ten-wave budgets regress (25.56/24.61 ms); fast math changes the image
(18.92 ms) and is not selected.
This commit is contained in:
woutermans committed 2026-10-12 12:47:53 +02:00
1 parent 406c0286c0
commit 798e25611c
7 files changed
+231 -18

No files matched your search

@@ -0,0 +1,34 @@
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
--- a/ptx/lib/zluda_ptx_impl.cpp
+++ b/ptx/lib/zluda_ptx_impl.cpp
@@ -877,21 +877,18 @@
typedef _Float16 half_vector __attribute__((ext_vector_type(2)));
typedef uint16_t magnitude_vector __attribute__((ext_vector_type(2)));
const uint32_t bits = std::bit_cast<uint32_t>(input);
- // Positive half encodings are ordered by magnitude. Clamp both lanes
- // with one packed integer min, then retain NaNs as positive quiet NaNs.
- // Restore original signs after conversion, including negative zero.
const magnitude_vector magnitude = std::bit_cast<magnitude_vector>(bits & 0x7fff7fffu);
- const magnitude_vector saturated =
+ // Clamp finite overflow and infinities explicitly. Map NaNs to signed
+ // infinity: native infinity encodes as 0x7f/0xff, whereas native NaN is
+ // always 0xff. This preserves the sign without post-conversion masks.
+ const magnitude_vector bounded =
__builtin_elementwise_min(magnitude, magnitude_vector{0x5f00u, 0x5f00u});
const magnitude_vector kept{
- magnitude[0] > 0x7c00u ? uint16_t(0x7e00u) : saturated[0],
- magnitude[1] > 0x7c00u ? uint16_t(0x7e00u) : saturated[1]};
- const half_vector halves = std::bit_cast<half_vector>(kept);
- const float x = float(halves[0]), y = float(halves[1]);
- const uint32_t encoded = d4r_cvt_pk_fp8_f32(x, y, 0, false);
- // AMD canonicalizes NaNs to 0xff. PTX retains each input's sign.
- const uint16_t packed = uint16_t((encoded & 0x7f7fu) |
- ((bits >> 8) & 0x80u) | ((bits >> 16) & 0x8000u));
+ magnitude[0] > 0x7c00u ? uint16_t(0x7c00u) : bounded[0],
+ magnitude[1] > 0x7c00u ? uint16_t(0x7c00u) : bounded[1]};
+ const uint32_t signed_bits = std::bit_cast<uint32_t>(kept) | (bits & 0x80008000u);
+ const half_vector halves = std::bit_cast<half_vector>(signed_bits);
+ const uint16_t packed = uint16_t(d4r_cvt_pk_fp8_f32(float(halves[0]), float(halves[1]), 0, false));
return std::bit_cast<Fp8x2>(packed);
}