Merge remote-tracking branch 'origin/linux' into linux
# Conflicts: # engine/rr/ptx2rr.py # install.sh # packaging/build_engine_models.sh
This commit is contained in:
commit
c0fed5e5cb
50 files changed
+1627
-238
No files matched your search
@@ -0,0 +1,149 @@
|
||||
"""GPU identity and model capability decisions without a GPU or Wine process."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class GpuCompatibilityTests(unittest.TestCase):
|
||||
def compile_run(self, source, cxx=False):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
executable = Path(t) / 'check'
|
||||
result = subprocess.run(['c++' if cxx else 'cc', '-x', 'c++' if cxx else 'c',
|
||||
'-std=c++17' if cxx else '-std=gnu11', '-Wall', '-Wextra', '-Werror',
|
||||
'-pthread',
|
||||
'-I', str(ROOT / 'tools'), '-o', str(executable), '-'], input=source,
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
subprocess.run([str(executable)], check=True, timeout=5)
|
||||
|
||||
def test_pci_identity_is_not_a_vendor_id_or_gpu_size_guess(self):
|
||||
self.compile_run(r'''
|
||||
#include "d4r_gpu_identity.h"
|
||||
#include <assert.h>
|
||||
int main(void) {
|
||||
assert(d4r_same_pci("0000:03:00.0", "00000000:03:00.0"));
|
||||
assert(!d4r_same_pci("0000:03:00.0", "0000:04:00.0"));
|
||||
assert(!d4r_same_pci("0000:03:00.0", "0001:03:00.0"));
|
||||
assert(!d4r_same_pci("0000:03:00.0garbage", "0000:03:00.0"));
|
||||
assert(d4r_pci_matches_kfd("0000:03:00.0", 0, 0x300));
|
||||
assert(!d4r_pci_matches_kfd("0000:03:00.0", 0, 0x400));
|
||||
assert(!d4r_pci_matches_kfd("0000:03:00.0", 1, 0x300));
|
||||
}
|
||||
''')
|
||||
|
||||
def test_bridge_maps_nonzero_compute_ordinal_and_only_its_luid(self):
|
||||
source = (ROOT / 'tools/wine_nvcuda_bridge.c').read_text()
|
||||
selection = source[source.index('static CUresult selected_cuda_device('):
|
||||
source.index('static void create_default_zluda_context(')]
|
||||
luid = source[source.index('CUresult WINAPI cuDeviceGetLuid('):
|
||||
source.index('CUresult WINAPI cuDeviceGetAttribute(')]
|
||||
self.compile_run(r'''
|
||||
#include "d4r_gpu_identity.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define WINAPI
|
||||
typedef int CUresult;
|
||||
typedef int CUdevice;
|
||||
typedef int (*CUDEVICEGETCOUNT_FN)(int*);
|
||||
typedef int (*CUDEVICEGET_FN)(int*, int);
|
||||
typedef int (*CUDEVICEGETLUID_FN)(char*, unsigned*, int);
|
||||
enum { CUDA_SUCCESS=0, CUDA_ERROR_NOT_INITIALIZED=3, CUDA_ERROR_NOT_SUPPORTED=801 };
|
||||
static const char* render = "0000:03:00.0";
|
||||
static int devices = 2;
|
||||
static unsigned GetEnvironmentVariableA(const char* key, char* out, unsigned size) {
|
||||
(void)key; snprintf(out, size, "%s", render); return (unsigned)strlen(render);
|
||||
}
|
||||
static void tracef(const char* fmt, ...) { (void)fmt; }
|
||||
static int count(int* n) { *n=devices; return 0; }
|
||||
static int get(int* d,int n) { *d=100+n; return 0; }
|
||||
static int pci(char* out,int size,int device) {
|
||||
snprintf(out, (size_t)size, "%s", device==101 ? "00000000:03:00.0" : "0000:04:00.0"); return 0;
|
||||
}
|
||||
static int original_luid(char* out,unsigned* mask,int device) { (void)device; memset(out,0,8);*mask=0;return 0; }
|
||||
static void* find_zluda_symbol(const char* name) {
|
||||
if (!strcmp(name,"cuDeviceGetCount")) return (void*)count;
|
||||
if (!strcmp(name,"cuDeviceGet")) return (void*)get;
|
||||
if (!strcmp(name,"cuDeviceGetPCIBusId")) return (void*)pci;
|
||||
return (void*)original_luid;
|
||||
}
|
||||
static int missing(const char* name) { (void)name; return 999; }
|
||||
static unsigned get_process_u32(const char* name,unsigned fallback) {
|
||||
(void)fallback;
|
||||
return strstr(name,"LOW") ? 0x3f2 : strstr(name,"HIGH") ? 0 : 1;
|
||||
}
|
||||
''' + selection + luid + r'''
|
||||
int main(void) {
|
||||
assert(d4r_pci_matches_kfd("0000:03:00.0",0,0x300));
|
||||
int selected=-1; assert(selected_cuda_device(&selected)==0 && selected==101);
|
||||
char id[8]; unsigned mask=9;
|
||||
assert(cuDeviceGetLuid(id,&mask,100)==0 && mask==0);
|
||||
assert(cuDeviceGetLuid(id,&mask,101)==0 && mask==1);
|
||||
unsigned low=0;memcpy(&low,id,4);assert(low==0x3f2);
|
||||
render="0000:05:00.0";assert(selected_cuda_device(&selected)==801);
|
||||
render="";assert(selected_cuda_device(&selected)==801);
|
||||
devices=1;assert(selected_cuda_device(&selected)==0 && selected==100);
|
||||
}
|
||||
''')
|
||||
|
||||
def test_rr_model_cannot_reach_unsupported_fp8_pipelines(self):
|
||||
self.compile_run(r'''
|
||||
#include "d4r_engine_compat.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
const char* fp8 = "D4RR0001\npreset 4\nstage enc0 x.spv\n";
|
||||
const char* wide = "D4RR0001\npreset 4\narithmetic gfx11\nstage enc0 x.spv\n";
|
||||
assert(!d4r_rr_model_compatible(fp8, true, false));
|
||||
assert(!d4r_rr_model_compatible(wide, false, false));
|
||||
assert(d4r_rr_model_compatible(wide, true, false));
|
||||
assert(d4r_rr_model_compatible(fp8, true, true));
|
||||
assert(!d4r_rr_model_compatible("D4RR0002\npreset 6\n", true, false));
|
||||
assert(!d4r_rr_model_compatible("D4RR0001\narithmetic unknown\n", true, true));
|
||||
VkCooperativeMatrixPropertiesKHR p{};
|
||||
p.MSize=p.NSize=p.KSize=16; p.scope=VK_SCOPE_SUBGROUP_KHR;
|
||||
p.AType=p.BType=VK_COMPONENT_TYPE_FLOAT16_KHR;
|
||||
p.CType=p.ResultType=VK_COMPONENT_TYPE_FLOAT32_KHR;
|
||||
assert(d4r_matrix_tuple(p, VK_COMPONENT_TYPE_FLOAT16_KHR));
|
||||
p.KSize=32; assert(!d4r_matrix_tuple(p, VK_COMPONENT_TYPE_FLOAT16_KHR));
|
||||
}
|
||||
''', cxx=True)
|
||||
|
||||
def test_existing_context_is_preserved_and_bootstrap_binds_each_thread(self):
|
||||
text = (ROOT / 'tools/wine_nvcuda_bridge.c').read_text()
|
||||
ensure = text[text.index('static void ensure_context(void)'):text.index('/* D4R_NGX_STREAM_PRIORITY')]
|
||||
self.compile_run(r'''
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
typedef int CUresult;
|
||||
typedef void* CUcontext;
|
||||
typedef CUresult (*CUCTX_CURRENT_FN)(CUcontext);
|
||||
enum { CUDA_SUCCESS=0 };
|
||||
static pthread_once_t context_once=PTHREAD_ONCE_INIT;
|
||||
static int context_bootstrap_result=3, bootstrap_calls=0;
|
||||
static _Thread_local int context_setup_result=3;
|
||||
static _Thread_local void* active;
|
||||
static void* cuda_context;
|
||||
static int current(void** out) { *out=active;return 0; }
|
||||
static int set(void* context) { active=context;return 0; }
|
||||
static void* find_zluda_symbol(const char* key) {
|
||||
return !strcmp(key,"cuCtxGetCurrent") ? (void*)current : (void*)set;
|
||||
}
|
||||
static void create_default_zluda_context(void) {
|
||||
++bootstrap_calls;cuda_context=(void*)(uintptr_t)0x456;context_bootstrap_result=0;
|
||||
}
|
||||
''' + ensure + r'''
|
||||
static void* worker(void* ignored) {
|
||||
(void)ignored;ensure_context();assert(active==cuda_context && context_setup_result==0);return NULL;
|
||||
}
|
||||
int main(void) {
|
||||
active=(void*)(uintptr_t)0x123;ensure_context();assert(active==(void*)(uintptr_t)0x123 && bootstrap_calls==0);
|
||||
pthread_t a;assert(!pthread_create(&a,NULL,worker,NULL));pthread_join(a,NULL);
|
||||
assert(bootstrap_calls==1);ensure_context();assert(active==(void*)(uintptr_t)0x123);
|
||||
assert(!pthread_create(&a,NULL,worker,NULL));pthread_join(a,NULL);assert(bootstrap_calls==1);
|
||||
}
|
||||
''')
|
||||
@@ -0,0 +1,97 @@
|
||||
"""Model builds must time out, retain failures, and reject incomplete/corrupt artifacts."""
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
spec = importlib.util.spec_from_file_location('model_tools', ROOT / 'packaging/model_tools.py')
|
||||
model_tools = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(model_tools)
|
||||
|
||||
|
||||
class ModelBuildTests(unittest.TestCase):
|
||||
def test_installer_key_tracks_model_arithmetic_options(self):
|
||||
text = (ROOT / 'install.sh').read_text()
|
||||
key = text[text.index('key() {'):text.index('have() {')]
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
root = Path(t)
|
||||
(root / 'tools').mkdir()
|
||||
(root / 'tools/compiler.py').write_text('compiler fixture')
|
||||
for name in ('nvngx_dlss.dll', 'nvngx_dlssd.dll'): (root / name).write_bytes(b'DLL fixture')
|
||||
env = {k: v for k, v in os.environ.items() if not k.startswith(('D4R_RR_', 'K_', 'M_'))}
|
||||
command = ['bash', '-c', 'RUNTIME=$1\n' + key + '\nkey 0', '_', t]
|
||||
baseline = subprocess.check_output(command, env=env)
|
||||
for name in ('K_DEFS_ENC0', 'M_EXACT', 'D4R_RR_MIX'):
|
||||
self.assertNotEqual(baseline, subprocess.check_output(command, env=dict(env, **{name: '1'})))
|
||||
|
||||
def test_child_exit_status_and_timeout_are_preserved(self):
|
||||
with tempfile.TemporaryDirectory() as t, contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
||||
log = Path(t) / 'capture.log'
|
||||
self.assertEqual(model_tools.run(log, 5, [sys.executable, '-c', 'print("failure detail");raise SystemExit(37)']), 37)
|
||||
self.assertIn('failure detail', log.read_text())
|
||||
self.assertEqual(model_tools.run(log, .15, [sys.executable, '-c', 'import time;time.sleep(60)']), 124)
|
||||
self.assertTrue(log.exists())
|
||||
|
||||
def test_manifest_rejects_missing_modified_and_extra_files(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
directory = Path(t)
|
||||
(directory / 'model.json').write_text('{}')
|
||||
weights = directory / 'weights.bin'
|
||||
weights.write_bytes(b'weights')
|
||||
model_tools.manifest(directory, write=True)
|
||||
model_tools.manifest(directory)
|
||||
weights.write_bytes(b'corrupt')
|
||||
with self.assertRaises(ValueError): model_tools.manifest(directory)
|
||||
weights.unlink()
|
||||
with self.assertRaises(ValueError): model_tools.manifest(directory)
|
||||
weights.write_bytes(b'weights')
|
||||
(directory / 'unexpected.spv').write_bytes(b'extra')
|
||||
with self.assertRaises(ValueError): model_tools.manifest(directory)
|
||||
|
||||
def test_capture_requires_completed_frame_and_all_layers(self):
|
||||
import numpy as np
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
directory = Path(t)
|
||||
pixels = np.ones((720, 1280, 4), dtype=np.float16)
|
||||
pixels.tofile(directory / 'out.raw')
|
||||
replay = directory / 'replay'
|
||||
replay.mkdir()
|
||||
for layer in ('enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'dec5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0'):
|
||||
kernel = 'dltss_pwin_' + layer + '_layer'
|
||||
d = replay / ('replay-000001-' + kernel)
|
||||
d.mkdir()
|
||||
(d / 'manifest.txt').write_text(f'kernel {kernel}\nargs 8\nalloc 0 0x1000 4\npointer 0 0 0\n')
|
||||
(d / 'args.bin').write_bytes(bytes(8))
|
||||
(d / 'alloc-0.bin').write_bytes(bytes(4))
|
||||
model_tools.capture(directory, 'k')
|
||||
(d / 'alloc-0.bin').write_bytes(b'')
|
||||
with self.assertRaises(ValueError): model_tools.capture(directory, 'k')
|
||||
(d / 'alloc-0.bin').write_bytes(bytes(4))
|
||||
pixels[0, 0, 0] = np.nan
|
||||
pixels.tofile(directory / 'out.raw')
|
||||
with self.assertRaises(ValueError): model_tools.capture(directory, 'k')
|
||||
|
||||
def test_dump_does_not_accept_a_failed_child_with_a_replay_marker(self):
|
||||
text = (ROOT / 'packaging/build_engine_models.sh').read_text()
|
||||
start = text.index(' dump() {')
|
||||
function = text[start:text.index('\n }\n', start) + len('\n }\n')]
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
root = Path(t)
|
||||
(root / 'proton').mkdir(); (root / 'app').mkdir()
|
||||
proton = root / 'proton/proton'
|
||||
proton.write_text('#!/bin/sh\nmkdir -p "$D4R_CUDA_REPLAY_DUMP_DIR/replay-000001-dltss_pwin_dec0_layer"\nexit 37\n')
|
||||
proton.chmod(0o755)
|
||||
prefix = 'set -euo pipefail\nWORK=$1; APP=$WORK/app; PROTON=$WORK/proton; STEAM_ROOT=$WORK; RUNTIME=$WORK\n'
|
||||
prefix += 'MODEL_TOOLS=$2; winpath() { printf "%s" "$1"; }; fail() { echo "$*" >&2; exit 1; }\n'
|
||||
result = subprocess.run(['bash', '-c', prefix + function + '\ndump k 11 ignored\n', '_', t,
|
||||
str(ROOT / 'packaging/model_tools.py')], capture_output=True, text=True, timeout=10)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('capture process failed', result.stderr)
|
||||
self.assertTrue((root / 'k/run.log').exists())
|
||||
@@ -0,0 +1,193 @@
|
||||
"""RDNA2 (gfx103x) native hiluma kernels: build gating, packaging allowlist and the f16 rounding contract.
|
||||
|
||||
Run: python3 -m unittest tests.test_rdna2_native_output -v
|
||||
The compile checks need ROCm's clang (D4R_ROCM_DIR, default /opt/rocm) and are skipped without it. The full
|
||||
texture build additionally needs the extracted DLSS 310.7 PTX (kernels/extracted/ptx) and
|
||||
D4R_RUN_KERNEL_BUILD_TESTS=1, since it compiles all twelve kernels.
|
||||
Arithmetic equality with ZLUDA's compile is checked in the harness (docs/native-kernels.md), not here.
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ROCM = Path(os.environ.get("D4R_ROCM_DIR", "/opt/rocm"))
|
||||
CLANG = ROCM / "lib/llvm/bin/clang++"
|
||||
PTX_DIR = ROOT / "kernels/extracted/ptx"
|
||||
TOOLS = [shutil.which(t) for t in ("llvm-readelf", "llvm-objdump")]
|
||||
OUTPUTS = {f"hiluma_engine_output_{d}_{mv}_hdr_max_v2_rel" for d in ("depthinv", "depthreg") for mv in ("mvhi", "mvlo")}
|
||||
INPUTS = {f"hiluma_engine_input_{d}_{mv}_{r}_v2_rel"
|
||||
for d in ("depthinv", "depthreg") for mv in ("mvhi", "mvlo") for r in ("hdr", "ldr")}
|
||||
# cvt.rn.f16.f32 of an fma (and the packed form of a product) of widened f16 values: LLVM would fold the
|
||||
# conversion into v_fma_mixlo/mixhi_f16, which rounds once instead of twice
|
||||
TINY_PTX = """.version 8.0
|
||||
.target sm_80
|
||||
.address_size 64
|
||||
.visible .entry tiny(
|
||||
.param .align 8 .b8 tiny_param_0[16]
|
||||
)
|
||||
{
|
||||
.reg .b16 %rs<6>;
|
||||
.reg .f32 %f<8>;
|
||||
.reg .b32 %r<3>;
|
||||
.reg .b64 %rd<3>;
|
||||
ld.param.u64 %rd1, [tiny_param_0];
|
||||
cvta.to.global.u64 %rd2, %rd1;
|
||||
ld.global.u16 %rs2, [%rd2+8];
|
||||
ld.global.u16 %rs3, [%rd2+10];
|
||||
ld.global.u16 %rs4, [%rd2+12];
|
||||
cvt.f32.f16 %f3, %rs2;
|
||||
cvt.f32.f16 %f4, %rs3;
|
||||
cvt.f32.f16 %f5, %rs4;
|
||||
fma.rn.f32 %f2, %f3, %f4, %f5;
|
||||
cvt.rn.f16.f32 %rs1, %f2;
|
||||
mul.rn.f32 %f6, %f3, %f4;
|
||||
cvt.rn.f16x2.f32 %r1, %f6, %f2;
|
||||
st.global.u16 [%rd2], %rs1;
|
||||
st.global.u32 [%rd2+4], %r1;
|
||||
ret;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def run(*args, **kwargs):
|
||||
return subprocess.run([str(a) for a in args], text=True, capture_output=True, **kwargs)
|
||||
|
||||
|
||||
class Rdna2BuildGating(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
# a compiler that fails if anything reaches it: the refusals must come first
|
||||
compiler = self.root / "rocm/lib/llvm/bin/clang++"
|
||||
compiler.parent.mkdir(parents=True)
|
||||
compiler.write_text("#!/bin/sh\nexit 99\n")
|
||||
compiler.chmod(0o755)
|
||||
|
||||
def test_wmma_families_are_refused_on_every_rdna2_target(self):
|
||||
for arch in ("gfx1030", "gfx1032", "gfx1036"):
|
||||
for family in ("m", "l", "rr", "rrswin"):
|
||||
env = dict(os.environ, D4R_ROCM_DIR=str(self.root / "rocm"), D4R_GPU_ARCH=arch)
|
||||
result = run("bash", ROOT / "kernels/build.sh", family, self.root / "out", env=env)
|
||||
self.assertEqual(result.returncode, 2, (arch, family, result.stderr))
|
||||
self.assertIn("need WMMA", result.stderr)
|
||||
|
||||
def test_rdna2_texture_build_does_not_need_zluda(self):
|
||||
env = dict(os.environ, D4R_ROCM_DIR=str(self.root / "rocm"), D4R_GPU_ARCH="gfx1032",
|
||||
D4R_DLSS_DLL=str(self.root / "missing.dll"), D4R_DLSS_PTX_DIR=str(self.root / "no-ptx"))
|
||||
env.pop("D4R_ZLUDA_EMIT", None)
|
||||
result = run("bash", ROOT / "kernels/build.sh", "tex", self.root / "out", env=env)
|
||||
self.assertNotIn("D4R_ZLUDA_EMIT", result.stderr)
|
||||
self.assertIn("missing extracted PTX directory", result.stderr)
|
||||
|
||||
def test_rdna3_texture_build_still_needs_zluda(self):
|
||||
env = dict(os.environ, D4R_ROCM_DIR=str(self.root / "rocm"), D4R_GPU_ARCH="gfx1101",
|
||||
D4R_DLSS_DLL=str(self.root / "missing.dll"))
|
||||
env.pop("D4R_ZLUDA_EMIT", None)
|
||||
result = run("bash", ROOT / "kernels/build.sh", "tex", self.root / "out", env=env)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("D4R_ZLUDA_EMIT", result.stderr)
|
||||
|
||||
|
||||
class Rdna2Packaging(unittest.TestCase):
|
||||
def rdna2_branch(self, path):
|
||||
text = (ROOT / path).read_text()
|
||||
start = text.index('if [[ "$arch" == gfx103* ]]; then')
|
||||
return text[start:text.index("\n fi\n", start)]
|
||||
|
||||
def test_package_copies_only_the_validated_hiluma_kernels(self):
|
||||
branch = self.rdna2_branch("scripts/package_release.sh")
|
||||
copies = re.findall(r'"\$tex_dir"/([^ ;]+\.hsaco)', branch)
|
||||
self.assertEqual(sorted(copies), ["hiluma_engine_input_*_v2_rel.hsaco",
|
||||
"hiluma_engine_output_*_hdr_max_v2_rel.hsaco"])
|
||||
self.assertIn('"$VARIANT" != clean', branch) # the no-NVIDIA zip ships no PTX-derived code
|
||||
self.assertNotIn("build.sh\" m", branch)
|
||||
# the accuracy set gets K's layers only (ZLUDA keeps compiling hiluma there)
|
||||
self.assertNotRegex(branch, r'PREFER_ACCURACY=1[^\n]*\n?[^\n]*build.sh" tex')
|
||||
self.assertIn("kernel_manifest.py", branch)
|
||||
|
||||
def test_release_builder_builds_rdna2_fast_textures_only(self):
|
||||
text = (ROOT / "scripts/build_release_glibc241.sh").read_text()
|
||||
start = text.index('if [[ "$arch" == gfx103* ]]; then')
|
||||
branch = text[start:text.index("fi\n", start)]
|
||||
self.assertIn("D4R_PREFER_ACCURACY=0", branch)
|
||||
self.assertNotIn("D4R_PREFER_ACCURACY=1", branch)
|
||||
self.assertIn('kernels/build.sh" tex', branch)
|
||||
self.assertIn("return 0", branch)
|
||||
|
||||
|
||||
class F16RoundingContract(unittest.TestCase):
|
||||
def translate(self, directory):
|
||||
ptx = directory / "tiny.ptx"
|
||||
ptx.write_text(TINY_PTX)
|
||||
result = run("python3", ROOT / "kernels/native/ptx2hip.py", ptx, "tiny", check=True)
|
||||
return result.stdout
|
||||
|
||||
def test_translator_routes_f16_conversions_through_f2h_rn(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
source = self.translate(Path(temp))
|
||||
self.assertEqual(source.count("f2h_rn("), 3)
|
||||
self.assertNotRegex(source, r"\(h1\)f\d")
|
||||
|
||||
@unittest.skipUnless(CLANG.is_file(), "ROCm clang not found (set D4R_ROCM_DIR)")
|
||||
def test_no_single_rounding_mix_instructions(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
temp = Path(temp)
|
||||
(temp / "tiny.hip").write_text(self.translate(temp))
|
||||
for arch, wave in (("gfx1032", "32"), ("gfx1101", "64")):
|
||||
env = dict(os.environ, D4R_ROCM_DIR=str(ROCM), D4R_GPU_ARCH=arch, WAVE=wave)
|
||||
run("bash", ROOT / "kernels/native/build_native.sh", temp / "tiny.hip", temp / f"{arch}.hsaco",
|
||||
env=env, check=True)
|
||||
asm = (temp / f"{arch}.s").read_text()
|
||||
self.assertNotRegex(asm, r"v_fma_mix(lo|hi)_f16", arch)
|
||||
# canary: a plain cast in the same kernel is fused, so the check above can fail
|
||||
(temp / "cast.hip").write_text((temp / "tiny.hip").read_text().replace("f2h_rn(", "(h1)("))
|
||||
env = dict(os.environ, D4R_ROCM_DIR=str(ROCM), D4R_GPU_ARCH="gfx1032", WAVE="32")
|
||||
run("bash", ROOT / "kernels/native/build_native.sh", temp / "cast.hip", temp / "cast.hsaco",
|
||||
env=env, check=True)
|
||||
self.assertRegex((temp / "cast.s").read_text(), r"v_fma_mix(lo|hi)_f16")
|
||||
|
||||
|
||||
@unittest.skipUnless(os.environ.get("D4R_RUN_KERNEL_BUILD_TESTS") == "1", "set D4R_RUN_KERNEL_BUILD_TESTS=1")
|
||||
@unittest.skipUnless(CLANG.is_file() and PTX_DIR.is_dir() and all(TOOLS), "needs ROCm clang, LLVM tools and PTX")
|
||||
class Rdna2TextureBuild(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.temp = tempfile.TemporaryDirectory()
|
||||
cls.out = Path(cls.temp.name) / "gfx1032"
|
||||
env = dict(os.environ, D4R_ROCM_DIR=str(ROCM), D4R_GPU_ARCH="gfx1032", D4R_DLSS_DLL="unused",
|
||||
D4R_DLSS_PTX_DIR=str(PTX_DIR))
|
||||
run("bash", ROOT / "kernels/build.sh", "tex", cls.out, env=env, check=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.temp.cleanup()
|
||||
|
||||
def notes(self, name):
|
||||
return run(TOOLS[0], "--notes", "-h", self.out / f"{name}.hsaco", check=True).stdout
|
||||
|
||||
def test_exactly_the_validated_kernels(self):
|
||||
self.assertEqual({p.stem for p in self.out.glob("*.hsaco")}, OUTPUTS | INPUTS)
|
||||
|
||||
def test_code_objects_are_wave32_gfx1032_without_scratch_or_wmma(self):
|
||||
for name in OUTPUTS | INPUTS:
|
||||
notes = self.notes(name)
|
||||
self.assertIn("gfx1032", notes, name)
|
||||
self.assertRegex(notes, r"\.wavefront_size:\s+32", name)
|
||||
self.assertRegex(notes, r"\.private_segment_fixed_size:\s+0\b", name)
|
||||
code = run(TOOLS[1], "-d", "--mcpu=gfx1032", self.out / f"{name}.hsaco", check=True).stdout
|
||||
self.assertNotRegex(code, r"v_wmma|v_fma_mix(lo|hi)_f16|v_dot\d", name)
|
||||
|
||||
def test_output_kernels_use_wgp_mode(self):
|
||||
for name in OUTPUTS:
|
||||
asm = (self.out / f"{name}.s").read_text()
|
||||
self.assertIn(".amdhsa_workgroup_processor_mode 1", asm, name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -60,6 +60,20 @@ class ShimConfigTests(unittest.TestCase):
|
||||
result = subprocess.run(cmd, env=env, text=True, capture_output=True)
|
||||
self.assertEqual(result.returncode, 2)
|
||||
|
||||
def test_auto_engine_detects_models_without_overriding_environment(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
root = Path(t)
|
||||
ini = root / 'd4r.ini'
|
||||
ini.write_text('[Engine]\nEnabled = auto\n')
|
||||
(root / 'engine/rr-d-gfx11').mkdir(parents=True)
|
||||
env = {k: v for k, v in os.environ.items() if not k.startswith('D4R_')}
|
||||
cmd = ['python3', str(ROOT / 'scripts/d4r_config.py'), '--config', str(ini)]
|
||||
output = subprocess.check_output(cmd, env=env, text=True)
|
||||
self.assertIn('export D4R_ENGINE=1\n', output)
|
||||
self.assertIn('export D4R_ENGINE_K=0\n', output)
|
||||
output = subprocess.check_output(cmd, env=dict(env, D4R_ENGINE='0'), text=True)
|
||||
self.assertNotIn('export D4R_ENGINE=', output)
|
||||
|
||||
def test_launcher_marker_poll_defaults_overrides_and_validation(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
ini = Path(temporary) / 'd4r.ini'
|
||||
|
||||
Reference in new issue
Block a user