engine/rr: lighter binary16 conversion guards in the hybrid stages
ptx2rr.py guards every cvt.rn.f16.f32 with two opaque zeros: on the f32 bits (against the fused multiply-convert, v_fma_mix*_f16) and on the binary16 bits (against RADV folding float(float16_t(x))). The hybrid stages' modules (enc0, dec0_1) also go through spv_exact_convert.py, whose NoContraction on every conversion already prevents the fold, so with --exact-convert only the f32 OR is emitted. Checked in RADV's ISA: the same v_cvt_f16_f32 / v_cvt_f32_f16 counts as before and no v_fma_mix*_f16 (dropping the f32 OR as well brings v_fma_mixlo_f16 back, so that one stays). Every image byte-identical on the replay inputs, the split gather build (D4R_RR2_SPLIT=1) as well. 224 / 153 fewer instructions; RX 9070 XT, 720p -> 1440p: enc0 747 -> 729 us, dec0_1 598 -> 592 us. Signed-off-by: itchyfeetleech <[email protected]>
This commit is contained in:
1 parent
49439afb81
commit
2fa4561bc1
3 files changed
+25
-2
No files matched your search
@@ -168,6 +168,9 @@ def main():
|
|||||||
cmd += [f'--buffer={off}' for off in BUFFERS.get(stage, [])]
|
cmd += [f'--buffer={off}' for off in BUFFERS.get(stage, [])]
|
||||||
cmd += [f'--snorm16={off}' for off in SNORM16.get(stage, [])]
|
cmd += [f'--snorm16={off}' for off in SNORM16.get(stage, [])]
|
||||||
cmd += [f'--surface={off}={fmt}' for off, fmt in SURFACES.get(stage, {}).items()]
|
cmd += [f'--surface={off}={fmt}' for off, fmt in SURFACES.get(stage, {}).items()]
|
||||||
|
# the hybrid stages' modules go through spv_exact_convert.py, so their conversions need only the lighter guard
|
||||||
|
exact = bool(a.native) and stage in HYBRID
|
||||||
|
cmd += ['--exact-convert'] if exact else []
|
||||||
subprocess.run(cmd, check=True, stderr=subprocess.DEVNULL)
|
subprocess.run(cmd, check=True, stderr=subprocess.DEVNULL)
|
||||||
info = json.loads(generated.with_suffix('.json').read_text())
|
info = json.loads(generated.with_suffix('.json').read_text())
|
||||||
weights = '-'
|
weights = '-'
|
||||||
|
|||||||
+9
-1
@@ -547,7 +547,15 @@ registers to the waves its shared memory leaves room for (enc1 needed 97 vector
|
|||||||
- **dec0_1 hands r and the MLP sum to its tail in XT and KVS' place** (KIND 5, one window per workgroup): written as
|
- **dec0_1 hands r and the MLP sum to its tail in XT and KVS' place** (KIND 5, one window per workgroup): written as
|
||||||
binary16 pairs of natural channels after a barrier, read in those pairs by the tail; 14 -> 8 KiB, 192 -> 120 vector
|
binary16 pairs of natural channels after a barrier, read in those pairs by the tail; 14 -> 8 KiB, 192 -> 120 vector
|
||||||
registers, 8 -> 12 waves per SIMD: 660 -> 623 us.
|
registers, 8 -> 12 waves per SIMD: 660 -> 623 us.
|
||||||
Frame 4.09 -> 4.00 ms (network 3.39 -> 3.30). Tried without gain: the decoders' first kernels' expansion in KVS (16 /
|
- **Lighter conversion guards in the hybrid stages** (enc0, dec0_1; `ptx2rr.py --exact-convert`): their modules go
|
||||||
|
through `spv_exact_convert.py`, whose NoContraction on every conversion already stops the fold of
|
||||||
|
`float(float16_t(x))`, so a `cvt.rn.f16.f32` keeps only the opaque zero on its f32 bits (against the fused
|
||||||
|
multiply-convert, which `precise` alone does not stop: without it ACO emits `v_fma_mixlo_f16` there). In the ISA the
|
||||||
|
same number of `v_cvt_f16_f32` / `v_cvt_f32_f16` as before and no `v_fma_mix*_f16`; 224 / 153 fewer instructions:
|
||||||
|
enc0 747 -> 729 us, dec0_1 598 -> 592. The output passes keep both ORs (they are not passed through
|
||||||
|
`spv_exact_convert.py`, and their ISA already uses `v_fma_mix*_f16` for plain conversions, so it cannot be checked
|
||||||
|
the same way).
|
||||||
|
Frame 4.09 -> 3.97 ms (network 3.39 -> 3.28). Tried without gain: the decoders' first kernels' expansion in KVS (16 /
|
||||||
12 / 8 KiB, but the deep levels have too few windows for the waves to matter and the extra barrier cost 4 - 5 us
|
12 / 8 KiB, but the deep levels have too few windows for the waves to matter and the extra barrier cost 4 - 5 us
|
||||||
each), all twelve tiles of a 1440p frame in one group (sixteen tile slots: the same), the output passes on 32-wide
|
each), all twelve tiles of a 1440p frame in one group (sixteen tile slots: the same), the output passes on 32-wide
|
||||||
waves (0.67 -> 0.82 ms) and unrolled head / branch loops in the 160-channel stages (+10 - 20 us each). Per stage
|
waves (0.67 -> 0.82 ms) and unrolled head / branch loops in the 160-channel stages (+10 - 20 us each). Per stage
|
||||||
|
|||||||
+13
-1
@@ -59,6 +59,9 @@ def mem_shape(o):
|
|||||||
return size, count
|
return size, count
|
||||||
|
|
||||||
|
|
||||||
|
# --exact-convert (the module is passed through engine/rr/spv_exact_convert.py, which keeps every conversion): a
|
||||||
|
# cvt.rn.f16.f32 only needs the opaque zero on its f32 bits, against the fused multiply-convert (see cvt)
|
||||||
|
CVT_GUARD = '--exact-convert' not in sys.argv
|
||||||
RR_RUNTIME = r'''
|
RR_RUNTIME = r'''
|
||||||
float16_t H(uint u) { return uint16BitsToFloat16(uint16_t(u)); }
|
float16_t H(uint u) { return uint16BitsToFloat16(uint16_t(u)); }
|
||||||
uint HU(float16_t h) { return uint(float16BitsToUint16(h)); }
|
uint HU(float16_t h) { return uint(float16BitsToUint16(h)); }
|
||||||
@@ -716,7 +719,7 @@ class RRT(base.T):
|
|||||||
for i, d in enumerate(dst):
|
for i, d in enumerate(dst):
|
||||||
# a binary16 result is the texel rounded to binary16: through H(HU(...) | SUST_Z) so that the driver cannot
|
# a binary16 result is the texel rounded to binary16: through H(HU(...) | SUST_Z) so that the driver cannot
|
||||||
# fold the rounding away when the value is widened again (RR_RUNTIME's note on sust_b)
|
# fold the rounding away when the value is widened again (RR_RUNTIME's note on sust_b)
|
||||||
self.A(d, f'H(HU(float16_t(t_[{i}])) | SUST_Z)' if f16 else f't_[{i}]', 'f16' if f16 else 'float')
|
self.A(d, (f'H(HU(float16_t(t_[{i}])) | SUST_Z)' if CVT_GUARD else f'float16_t(t_[{i}])') if f16 else f't_[{i}]', 'f16' if f16 else 'float')
|
||||||
self.E('}')
|
self.E('}')
|
||||||
|
|
||||||
def sust(self, op, a):
|
def sust(self, op, a):
|
||||||
@@ -777,6 +780,11 @@ class RRT(base.T):
|
|||||||
# conversion into v_fma_mix, one rounding of the exact value, where the kernel rounds to f32 first: an f32
|
# conversion into v_fma_mix, one rounding of the exact value, where the kernel rounds to f32 first: an f32
|
||||||
# result that is exactly a binary16 midpoint then rounds the other way (the output stages' rare one-step
|
# result that is exactly a binary16 midpoint then rounds the other way (the output stages' rare one-step
|
||||||
# differences, e.g. log2(x) * c1 * c2; found 2026-10-13): the OR on the f32 bits keeps the f32 value.
|
# differences, e.g. log2(x) * c1 * c2; found 2026-10-13): the OR on the f32 bits keeps the f32 value.
|
||||||
|
# With --exact-convert the conversion itself is decorated NoContraction (spv_exact_convert.py), which keeps the
|
||||||
|
# first: only the f32 OR is needed (checked in the ISA: the same v_cvt_f16_f32 / v_cvt_f32_f16 counts, no
|
||||||
|
# v_fma_mix*_f16; without the f32 OR, ACO fuses some products into v_fma_mixlo_f16 despite `precise`)
|
||||||
|
if not CVT_GUARD:
|
||||||
|
return self.A(a[0], f'float16_t(uintBitsToFloat(floatBitsToUint({self.val(a[1], "f32")}) | SUST_Z))', 'f16')
|
||||||
return self.A(a[0], f'H(HU(float16_t(uintBitsToFloat(floatBitsToUint({self.val(a[1], "f32")}) | SUST_Z))) | SUST_Z)', 'f16')
|
return self.A(a[0], f'H(HU(float16_t(uintBitsToFloat(floatBitsToUint({self.val(a[1], "f32")}) | SUST_Z))) | SUST_Z)', 'f16')
|
||||||
return super().cvt(op, o, a)
|
return super().cvt(op, o, a)
|
||||||
|
|
||||||
@@ -859,6 +867,8 @@ class RRT(base.T):
|
|||||||
return self.A(a[0], f'tanh_ocml({self.val(a[1], "f32")})', 'float')
|
return self.A(a[0], f'tanh_ocml({self.val(a[1], "f32")})', 'float')
|
||||||
if op == 'ex2.approx.f16':
|
if op == 'ex2.approx.f16':
|
||||||
# v_exp_f32 of the widened value, rounded to binary16; the ORs keep that rounding (see cvt)
|
# v_exp_f32 of the widened value, rounded to binary16; the ORs keep that rounding (see cvt)
|
||||||
|
if not CVT_GUARD:
|
||||||
|
return self.A(a[0], f'float16_t(uintBitsToFloat(floatBitsToUint(exp2(float({self.val(a[1], "f16")}))) | SUST_Z))', 'f16')
|
||||||
return self.A(a[0], f'H(HU(float16_t(uintBitsToFloat(floatBitsToUint(exp2(float({self.val(a[1], "f16")}))) | SUST_Z))) | SUST_Z)', 'f16')
|
return self.A(a[0], f'H(HU(float16_t(uintBitsToFloat(floatBitsToUint(exp2(float({self.val(a[1], "f16")}))) | SUST_Z))) | SUST_Z)', 'f16')
|
||||||
if op == 'neg.f16':
|
if op == 'neg.f16':
|
||||||
return self.A(a[0], f'-{self.val(a[1], "f16")}', 'f16')
|
return self.A(a[0], f'-{self.val(a[1], "f16")}', 'f16')
|
||||||
@@ -945,6 +955,8 @@ def main():
|
|||||||
'execution) for workgroup --probe-block, into a debug buffer bound after the surfaces')
|
'execution) for workgroup --probe-block, into a debug buffer bound after the surfaces')
|
||||||
ap.add_argument('--probe-block', default='0,0')
|
ap.add_argument('--probe-block', default='0,0')
|
||||||
ap.add_argument('--oob-report', action='store_true', help='debugging: count out-of-range buffer accesses')
|
ap.add_argument('--oob-report', action='store_true', help='debugging: count out-of-range buffer accesses')
|
||||||
|
ap.add_argument('--exact-convert', action='store_true',
|
||||||
|
help='the module goes through spv_exact_convert.py: lighter guards on binary16 conversions (CVT_GUARD)')
|
||||||
ns = ap.parse_args()
|
ns = ap.parse_args()
|
||||||
block = tuple(int(x) for x in ns.block.split(',')) if ns.block else default_block(ns.kernel)
|
block = tuple(int(x) for x in ns.block.split(',')) if ns.block else default_block(ns.kernel)
|
||||||
formats = {int(k): v for k, v in (x.split('=') for x in ns.surface)}
|
formats = {int(k): v for k, v in (x.split('=') for x in ns.surface)}
|
||||||
|
|||||||
Reference in new issue
Block a user