Add MicroCUDA backend, adaptive output wait, wider native K coverage (improve perf)
Shim - Adaptive output-event wait: sleep to ~120 us before the predicted completion (smoothed per wait site), spin near it, back off when late. D4R_SHIM_OUTPUT_POLL_US restores a fixed interval. - Defer the input-marker wait into the worker thread with VRAM interop (D4R_SHIM_DEFER_MARKER, default on), taking the host setup off the critical path. MicroCUDA - Experimental ZLUDA-free backend in wine_nvcuda_bridge, plus tools/microcuda and scripts/build_microcuda.sh. Serves accuracy, fast and native kernels; unregistered modules fall back to the reference path. Unclassified writes invalidate prepared weights. M kernels - SWIN_PREP_IDENTITY_AT: key weight prep by allocation identity (MicroCUDA); enc3 tube uses it with 8 slots. ZLUDA ignores it. - dec2 (gfx1101): SWIN_PACKED_RESIDUAL, byte-identical, ~1.6% faster. - Opt-in gfx12 hardware fp8 conversion (SWIN_FP8_HWCVT), unverified on hardware. Native K translators - ctile: handle variants that re-materialize the parameter base (mvlo). - rewrite_output: check live-in registers, not just outputs. - build.sh: native input covers all 8 flag variants, output the 4 HDR "max" variants; depth-tile skip stays mvhi-only. Tools - dump_runner and bench_dlss_kernel harness additions.
This commit is contained in:
1 parent
7c60d53cd0
commit
a627439025
20 files changed
+2186
-44
No files matched your search
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
"""GPU integration checks for the MicroCUDA runtime; no games or NGX evaluation.
|
||||
usage: check_runtime.py LIBRARY PACK_DIR KNOWN_PTX NATIVE_DIR
|
||||
|
||||
KNOWN_PTX is the K enc0 module text (a module in the pack with a native dltss_pwin_enc0_layer in NATIVE_DIR).
|
||||
Pack-state failures (switch mismatch, tampered object) run in child processes: the pack is read once.
|
||||
"""
|
||||
import ctypes as c
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ptr, u64, size = c.c_void_p, c.c_uint64, c.c_size_t
|
||||
|
||||
|
||||
def api(lib, name, args):
|
||||
f = getattr(lib, name)
|
||||
f.argtypes = args
|
||||
f.restype = c.c_int
|
||||
return f
|
||||
|
||||
|
||||
def child(library, ptx):
|
||||
"""Prints the cuModuleLoadData result for ptx under the environment it was started with."""
|
||||
lib = c.CDLL(library)
|
||||
assert api(lib, 'cuInit', [c.c_uint])(0) == 0
|
||||
context = ptr()
|
||||
assert api(lib, 'cuCtxCreate_v2', [c.POINTER(ptr), c.c_uint, c.c_int])(c.byref(context), 0, 0) == 0
|
||||
module = ptr()
|
||||
print(api(lib, 'cuModuleLoadData', [c.POINTER(ptr), ptr])(c.byref(module), c.create_string_buffer(Path(ptx).read_bytes())))
|
||||
|
||||
|
||||
def run_child(library, ptx, **env):
|
||||
out = subprocess.run([sys.executable, __file__, '--child', library, ptx], env={**os.environ, **env},
|
||||
capture_output=True, text=True, check=True)
|
||||
return int(out.stdout.split()[-1])
|
||||
|
||||
|
||||
def main():
|
||||
if sys.argv[1] == '--child':
|
||||
return child(*sys.argv[2:])
|
||||
library, pack, ptx, native = sys.argv[1:]
|
||||
os.environ['D4R_MICROCUDA_PACK'] = pack
|
||||
os.environ['D4R_MICROCUDA_NATIVE_DIR'] = native
|
||||
switches = next(l[9:].strip() for l in open(Path(pack) / 'microcuda-pack.txt') if l.startswith('switches'))
|
||||
for key in [k for k in os.environ if k.startswith('D4R_ZLUDA_')]:
|
||||
del os.environ[key]
|
||||
for item in filter(None, switches.split(',')):
|
||||
k, v = item.split('=', 1)
|
||||
os.environ[k] = v
|
||||
checks = []
|
||||
|
||||
def expect(label, result, code=0):
|
||||
assert result == code, (label, result, code)
|
||||
checks.append(label)
|
||||
|
||||
expect('switch-set mismatch refuses the pack', run_child(library, ptx, D4R_ZLUDA_WMMA='0'), 200)
|
||||
with tempfile.TemporaryDirectory(prefix='d4r-microcuda-check-') as directory:
|
||||
shutil.copytree(pack, directory, dirs_exist_ok=True)
|
||||
for co in (Path(directory) / 'modules').glob('*.co'):
|
||||
b = bytearray(co.read_bytes())
|
||||
b[-1] ^= 1
|
||||
co.write_bytes(b)
|
||||
expect('tampered pack object rejected', run_child(library, ptx, D4R_MICROCUDA_PACK=directory), 200)
|
||||
expect('missing pack', run_child(library, ptx, D4R_MICROCUDA_PACK='/nonexistent-microcuda'), 500)
|
||||
|
||||
lib = c.CDLL(library)
|
||||
A = lambda name, args: api(lib, name, args)
|
||||
init = A('cuInit', [c.c_uint])
|
||||
load = A('cuModuleLoadData', [c.POINTER(ptr), ptr])
|
||||
unload = A('cuModuleUnload', [ptr])
|
||||
get = A('cuModuleGetFunction', [c.POINTER(ptr), ptr, c.c_char_p])
|
||||
launch = A('cuLaunchKernel', [ptr] + [c.c_uint] * 7 + [ptr, c.POINTER(ptr), c.POINTER(ptr)])
|
||||
text = c.create_string_buffer(Path(ptx).read_bytes())
|
||||
module, function = ptr(), ptr()
|
||||
expect('module before init', load(c.byref(module), text), 3)
|
||||
expect('invalid init flags', init(1), 1)
|
||||
expect('init', init(0))
|
||||
proc = A('cuGetProcAddress_v2', [c.c_char_p, c.POINTER(ptr), c.c_int, u64, c.POINTER(c.c_int)])
|
||||
address, status = ptr(), c.c_int()
|
||||
expect('getProc modern alias', proc(b'cuMemAlloc', c.byref(address), 12080, 0, c.byref(status)))
|
||||
assert address.value == c.cast(lib.cuMemAlloc_v2, ptr).value and status.value == 0
|
||||
expect('getProc array API', proc(b'cuArrayCreate', c.byref(address), 12080, 0, c.byref(status)))
|
||||
expect('getProc unknown', proc(b'cuGraphLaunch', c.byref(address), 12080, 0, c.byref(status)), 500)
|
||||
assert address.value is None and status.value == 1
|
||||
expect('getProc perthread flag rejected', proc(b'cuLaunchKernel', c.byref(address), 12080, 2, c.byref(status)), 801)
|
||||
expect('getProc future version rejected', proc(b'cuLaunchKernel', c.byref(address), 13000, 0, c.byref(status)), 801)
|
||||
expect('getProc null output', proc(b'cuInit', None, 12080, 0, None), 1)
|
||||
device = c.c_int()
|
||||
expect('device', A('cuDeviceGet', [c.POINTER(c.c_int), c.c_int])(c.byref(device), 0))
|
||||
context = ptr()
|
||||
expect('context', A('cuCtxCreate_v2', [c.POINTER(ptr), c.c_uint, c.c_int])(c.byref(context), 0, device.value))
|
||||
current = ptr()
|
||||
expect('get current', A('cuCtxGetCurrent', [c.POINTER(ptr)])(c.byref(current)))
|
||||
assert current.value == context.value
|
||||
|
||||
expect('unknown PTX', load(c.byref(module), c.create_string_buffer(b'.version 8.7\n.target sm_89\n.address_size 64\n')), 200)
|
||||
bad = bytearray(Path(ptx).read_bytes())
|
||||
bad[0] ^= 1
|
||||
expect('changed PTX', load(c.byref(module), c.create_string_buffer(bytes(bad))), 200)
|
||||
expect('known PTX', load(c.byref(module), text))
|
||||
expect('unknown function', get(c.byref(function), module, b'unknown'), 500)
|
||||
assert function.value is None
|
||||
expect('native function', get(c.byref(function), module, b'dltss_pwin_enc0_layer'))
|
||||
another = ptr()
|
||||
expect('cached lookup', get(c.byref(another), module, b'dltss_pwin_enc0_layer'))
|
||||
assert another.value == function.value
|
||||
os.environ['D4R_MICROCUDA_NATIVE_DIR'] = '/nonexistent-microcuda'
|
||||
plain_module, plain = ptr(), ptr()
|
||||
expect('second module instance', load(c.byref(plain_module), text))
|
||||
expect('pack function without native override', get(c.byref(plain), plain_module, b'dltss_pwin_enc0_layer'))
|
||||
assert plain.value != function.value
|
||||
expect('second module unload', unload(plain_module))
|
||||
os.environ['D4R_MICROCUDA_NATIVE_DIR'] = native
|
||||
args = c.create_string_buffer(176)
|
||||
n = size(176)
|
||||
extra = (ptr * 5)(3, c.addressof(args), 2, c.addressof(n), None)
|
||||
expect('HIP token rejected as CUDA token', launch(function, 1, 1, 1, 32, 1, 1, 0, None, None, extra), 801)
|
||||
unterminated = (ptr * 8)(1, c.addressof(args), 2, c.addressof(n), 1, c.addressof(args), 2, c.addressof(n))
|
||||
expect('unterminated packed args', launch(function, 1, 1, 1, 32, 1, 1, 0, None, None, unterminated), 1)
|
||||
expect('null function', launch(None, 1, 1, 1, 32, 1, 1, 0, None, None, extra), 400)
|
||||
|
||||
allocate = A('cuMemAlloc_v2', [c.POINTER(u64), size])
|
||||
d2h = A('cuMemcpyDtoH_v2', [ptr, u64, size])
|
||||
source = c.create_string_buffer(bytes(range(256)))
|
||||
result = c.create_string_buffer(256)
|
||||
device_mem = u64()
|
||||
expect('allocation', allocate(c.byref(device_mem), 256))
|
||||
expect('zero-filled allocation readback', d2h(result, device_mem, 256))
|
||||
assert result.raw == bytes(256)
|
||||
stream = ptr()
|
||||
expect('stream create', A('cuStreamCreate', [c.POINTER(ptr), c.c_uint])(c.byref(stream), 1))
|
||||
expect('async H2D', A('cuMemcpyHtoDAsync_v2', [u64, ptr, size, ptr])(device_mem, source, 256, stream))
|
||||
event = ptr()
|
||||
expect('event create', A('cuEventCreate', [c.POINTER(ptr), c.c_uint])(c.byref(event), 0))
|
||||
expect('event record', A('cuEventRecord', [ptr, ptr])(event, stream))
|
||||
expect('event wait', A('cuEventSynchronize', [ptr])(event))
|
||||
expect('event query ready', A('cuEventQuery', [ptr])(event))
|
||||
expect('D2H', d2h(result, device_mem, 256))
|
||||
assert result.raw == source.raw[:256]
|
||||
expect('event destroy', A('cuEventDestroy_v2', [ptr])(event))
|
||||
expect('stream destroy', A('cuStreamDestroy_v2', [ptr])(stream))
|
||||
expect('free', A('cuMemFree_v2', [u64])(device_mem))
|
||||
|
||||
# arrays: CUDA_ARRAY_DESCRIPTOR {size_t Width, Height; int Format; unsigned NumChannels}
|
||||
array_create = A('cuArrayCreate_v2', [c.POINTER(ptr), ptr])
|
||||
array_desc = A('cuArrayGetDescriptor_v2', [ptr, ptr])
|
||||
array_destroy = A('cuArrayDestroy', [ptr])
|
||||
memcpy2d = A('cuMemcpy2D_v2', [ptr])
|
||||
unorm = ptr()
|
||||
expect('UNORM8x4 array', array_create(c.byref(unorm), c.create_string_buffer(struct.pack('<QQiI', 4, 4, 194, 4))))
|
||||
expect('array descriptor unsupported, as ZLUDA', array_desc(c.create_string_buffer(24), unorm), 801)
|
||||
# CUDA_RESOURCE_DESC (144 bytes): resType 0 = ARRAY, hArray at 8
|
||||
resource = c.create_string_buffer(struct.pack('<iiQ', 0, 0, unorm.value) + bytes(128))
|
||||
surface = u64()
|
||||
expect('surface object', A('cuSurfObjectCreate', [c.POINTER(u64), ptr])(c.byref(surface), resource))
|
||||
handle = c.create_string_buffer(96)
|
||||
expect('surface handle readback', d2h(handle, surface, 96))
|
||||
assert struct.unpack_from('<I', handle.raw, 80)[0] == 194 and handle.raw[48:80] == bytes(32)
|
||||
back = c.create_string_buffer(144)
|
||||
expect('surface resource desc', A('cuSurfObjectGetResourceDesc', [ptr, u64])(back, surface))
|
||||
assert back.raw == resource.raw[:144]
|
||||
# CUDA_TEXTURE_DESC (104 bytes): address modes, filter, flags (bit 1 = normalized coordinates)
|
||||
texture = c.create_string_buffer(struct.pack('<iiiiI', 1, 1, 1, 0, 2) + bytes(84))
|
||||
tex = u64()
|
||||
expect('texture object', A('cuTexObjectCreate', [c.POINTER(u64), ptr, ptr, ptr])(c.byref(tex), resource, texture, None))
|
||||
expect('texture destroy', A('cuTexObjectDestroy', [u64])(tex))
|
||||
expect('surface destroy', A('cuSurfObjectDestroy', [u64])(surface))
|
||||
expect('unknown surface destroy', A('cuSurfObjectDestroy', [u64])(surface), 1)
|
||||
expect('array destroy', array_destroy(unorm))
|
||||
|
||||
# packed UNORM 10:10:10:2 through its RGBA32F backing: host -> array -> host
|
||||
packed = ptr()
|
||||
expect('packed 1010102 array', array_create(c.byref(packed), c.create_string_buffer(struct.pack('<QQiI', 4, 2, 80, 4))))
|
||||
words = [(i * 37 % 1024) | (i * 101 % 1024) << 10 | (i * 7 % 1024) << 20 | (i % 4) << 30 for i in range(8)]
|
||||
host_in = c.create_string_buffer(struct.pack('<8I', *words))
|
||||
host_out = c.create_string_buffer(32)
|
||||
|
||||
def copy2d(src_type, src_host, src_array, dst_type, dst_host, dst_array):
|
||||
# CUDA_MEMCPY2D: srcX,srcY,srcType,srcHost,srcDevice,srcArray,srcPitch, dst..., WidthInBytes, Height
|
||||
return memcpy2d(c.create_string_buffer(struct.pack(
|
||||
'<QQi4xQQQQ' 'QQi4xQQQQ' 'QQ', 0, 0, src_type, src_host or 0, 0, src_array or 0, 16,
|
||||
0, 0, dst_type, dst_host or 0, 0, dst_array or 0, 16, 16, 2)))
|
||||
expect('packed upload', copy2d(1, c.addressof(host_in), None, 3, None, packed.value))
|
||||
expect('packed readback', copy2d(3, None, packed.value, 1, c.addressof(host_out), None))
|
||||
assert host_out.raw == host_in.raw[:32], (host_out.raw.hex(), host_in.raw.hex())
|
||||
expect('packed array destroy', array_destroy(packed))
|
||||
|
||||
expect('module unload', unload(module))
|
||||
expect('double unload', unload(module), 400)
|
||||
expect('lookup after unload', get(c.byref(function), module, b'dltss_pwin_enc0_layer'), 400)
|
||||
expect('context destroy', A('cuCtxDestroy_v2', [ptr])(context))
|
||||
print(json.dumps({'passed': len(checks), 'checks': checks}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user