Clear windows branch; preserve previous contents in win-old
This commit is contained in:
1 parent
f0c881985b
commit
73994d4634
311 files changed
-57018
No files matched your search
@@ -1,328 +0,0 @@
|
||||
From 33bbac250ac7e2ca4eff67e8817ada9dad578208 Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 16:10:08 +0300
|
||||
Subject: [PATCH] Support explicitly selected Windows d4r DLSS backend on
|
||||
physical AMD adapters
|
||||
|
||||
---
|
||||
OptiScaler/Config.cpp | 3 ++
|
||||
OptiScaler/Config.h | 1 +
|
||||
OptiScaler/OptiScaler.vcxproj | 4 +--
|
||||
OptiScaler/State.h | 1 +
|
||||
OptiScaler/Util.cpp | 12 +++++--
|
||||
OptiScaler/dllmain.cpp | 33 +++++++++++++++++--
|
||||
OptiScaler/hooks/LibraryLoad_Hooks.cpp | 17 ++++++++++
|
||||
OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp | 17 ++++++++++
|
||||
OptiScaler/misc/IdentifyGpu.cpp | 20 +++++++++++
|
||||
OptiScaler/proxies/NVNGX_Proxy.h | 5 ++-
|
||||
OptiScaler/upscalers/FeatureProvider_Dx12.cpp | 10 ++++++
|
||||
11 files changed, 115 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp
|
||||
index d8c62d3..f444387 100644
|
||||
--- a/OptiScaler/Config.cpp
|
||||
+++ b/OptiScaler/Config.cpp
|
||||
@@ -323,6 +323,7 @@ bool Config::Reload(std::filesystem::path iniPath)
|
||||
{
|
||||
// Don't enable again if set false because of no nvngx found
|
||||
DLSSEnabled.set_from_config(readBool("DLSS", "Enabled"));
|
||||
+ DLSSAllowExternalBackend.set_from_config(readBool("DLSS", "AllowExternalBackend"));
|
||||
UseGenericAppIdWithDlss.set_from_config(readBool("DLSS", "UseGenericAppIdWithDlss"));
|
||||
|
||||
RenderPresetOverride.set_from_config(readBool("DLSS", "RenderPresetOverride"));
|
||||
@@ -1168,6 +1169,8 @@ bool Config::SaveIni()
|
||||
// DLSS
|
||||
{
|
||||
ini.SetValue("DLSS", "Enabled", GetBoolValue(Instance()->DLSSEnabled.value_for_config()).c_str());
|
||||
+ ini.SetValue("DLSS", "AllowExternalBackend",
|
||||
+ GetBoolValue(Instance()->DLSSAllowExternalBackend.value_for_config()).c_str());
|
||||
ini.SetValue("DLSS", "RenderPresetOverride",
|
||||
GetBoolValue(Instance()->RenderPresetOverride.value_for_config()).c_str());
|
||||
ini.SetValue("DLSS", "RenderPresetForAll",
|
||||
diff --git a/OptiScaler/Config.h b/OptiScaler/Config.h
|
||||
index 4ab62a4..0caf8c8 100644
|
||||
--- a/OptiScaler/Config.h
|
||||
+++ b/OptiScaler/Config.h
|
||||
@@ -275,6 +275,7 @@ class Config
|
||||
|
||||
// DLSS
|
||||
CustomOptional<bool> DLSSEnabled { true };
|
||||
+ CustomOptional<bool> DLSSAllowExternalBackend { false };
|
||||
CustomOptional<bool> RenderPresetOverride { false };
|
||||
CustomOptional<uint32_t> RenderPresetForAll { 0 };
|
||||
CustomOptional<uint32_t> RenderPresetDLAA { 0 };
|
||||
diff --git a/OptiScaler/OptiScaler.vcxproj b/OptiScaler/OptiScaler.vcxproj
|
||||
index 24ca80b..dfbfc97 100644
|
||||
--- a/OptiScaler/OptiScaler.vcxproj
|
||||
+++ b/OptiScaler/OptiScaler.vcxproj
|
||||
@@ -236,7 +236,7 @@
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
- <GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
+ <GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalDependencies>winhttp.lib;WindowsApp.lib;Dbghelp.lib;dxgi.lib;d3d11.lib;d3d12.lib;vulkan-1.lib;dxguid.lib;freetype.lib;ffx_fsr2_api_x64.lib;ffx_fsr2_api_dx11_x64.lib;ffx_fsr2_api_dx12_x64.lib;ffx_fsr2_api_vk_x64.lib;ffx_fsr2_212_api_dx12_x64.lib;ffx_fsr2_212_api_vk_x64.lib;ffx_fsr2_212_api_x64.lib;d3dcompiler.lib;ffx_backend_dx11_x64.lib;ffx_fsr3_x64.lib;ffx_frameinterpolation_x64.lib;ffx_fsr3upscaler_x64.lib;ffx_opticalflow_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<ModuleDefinitionFile>Source.def</ModuleDefinitionFile>
|
||||
@@ -873,4 +873,4 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
-</Project>
|
||||
\ No newline at end of file
|
||||
+</Project>
|
||||
diff --git a/OptiScaler/State.h b/OptiScaler/State.h
|
||||
index 42164de..84f8263 100644
|
||||
--- a/OptiScaler/State.h
|
||||
+++ b/OptiScaler/State.h
|
||||
@@ -265,6 +265,7 @@ class State
|
||||
int dlssgDetectedInterpolationCount = 0;
|
||||
|
||||
// DLSS
|
||||
+ bool dlssExternalBackend = false;
|
||||
bool dlssPresetsOverriddenExternally = false;
|
||||
bool dlssPresetsOverridenByOpti = false;
|
||||
uint32_t dlssRenderPresetExternal = 0;
|
||||
diff --git a/OptiScaler/Util.cpp b/OptiScaler/Util.cpp
|
||||
index f7ac6fa..7367a91 100644
|
||||
--- a/OptiScaler/Util.cpp
|
||||
+++ b/OptiScaler/Util.cpp
|
||||
@@ -486,8 +486,16 @@ bool Util::GetFileVersion(std::wstring dllPath, version_t* fileVersionOut, versi
|
||||
|
||||
bool Util::IsSubpath(const std::filesystem::path& path, const std::filesystem::path& base)
|
||||
{
|
||||
- auto rel = std::filesystem::relative(path, base);
|
||||
- auto first = *rel.begin();
|
||||
+ // Called by LdrLoadDll hooks: relative()/weakly_canonical() perform file
|
||||
+ // queries, can re-enter the hooks, and throw when an optional directory or
|
||||
+ // a bare DLL search name cannot be resolved. This is a lexical routing
|
||||
+ // check, not a filesystem access control decision.
|
||||
+ auto normalizedPath = std::filesystem::path(ToLower(path.lexically_normal().wstring()));
|
||||
+ auto normalizedBase = std::filesystem::path(ToLower(base.lexically_normal().wstring()));
|
||||
+ auto rel = normalizedPath.lexically_relative(normalizedBase);
|
||||
+ if (rel.empty())
|
||||
+ return false;
|
||||
+ const auto first = *rel.begin();
|
||||
return first != "." && first != "..";
|
||||
}
|
||||
|
||||
diff --git a/OptiScaler/dllmain.cpp b/OptiScaler/dllmain.cpp
|
||||
index cba14b1..b4b786e 100644
|
||||
--- a/OptiScaler/dllmain.cpp
|
||||
+++ b/OptiScaler/dllmain.cpp
|
||||
@@ -948,7 +948,7 @@ static void CheckWorkingMode()
|
||||
|
||||
// NVAPI
|
||||
// Doesn't seem to like GetModuleHandle for some reason, so call our load to make sure
|
||||
- if (GetDllNameWModule(&nvapiNamesW) != nullptr)
|
||||
+ if (!State::Instance().dlssExternalBackend && GetDllNameWModule(&nvapiNamesW) != nullptr)
|
||||
{
|
||||
// This hooks nvapi as well when possible
|
||||
auto nvapi64 = LibraryLoadHooks::LoadNvApi();
|
||||
@@ -1958,7 +1958,33 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||
return nvidiaDetected;
|
||||
};
|
||||
|
||||
- bool possibleNvidia = isNvidiaViaNvapi();
|
||||
+ // Opt in only to a configured, versioned Windows d4r backend. The
|
||||
+ // marker performs no HIP/NGX initialization under DLL_PROCESS_ATTACH.
|
||||
+ // GPU support is still checked by its public NGX requirements API.
|
||||
+ if (Config::Instance()->DLSSEnabled.value_or_default() &&
|
||||
+ Config::Instance()->DLSSAllowExternalBackend.value_or_default() &&
|
||||
+ Config::Instance()->NvngxPath.has_value())
|
||||
+ {
|
||||
+ auto backendPath = std::filesystem::path(Config::Instance()->NvngxPath.value()) / L"_nvngx.dll";
|
||||
+ if (backendPath.is_absolute())
|
||||
+ {
|
||||
+ auto module = NtdllProxy::LoadLibraryExW_Ldr(backendPath.c_str(), nullptr,
|
||||
+ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
+ if (module)
|
||||
+ {
|
||||
+ using BackendVersion = unsigned (*)();
|
||||
+ auto version = reinterpret_cast<BackendVersion>(
|
||||
+ KernelBaseProxy::GetProcAddress_()(module, "d4r_WindowsBackendVersion"));
|
||||
+ State::Instance().dlssExternalBackend = version && version() == 1;
|
||||
+ NtdllProxy::FreeLibrary_Ldr(module);
|
||||
+ }
|
||||
+ }
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ spdlog::info("Validated external Windows d4r DLSS backend; retaining physical GPU identity");
|
||||
+ else
|
||||
+ spdlog::error("AllowExternalBackend requires an absolute NvngxPath containing Windows d4r ABI 1");
|
||||
+ }
|
||||
+ bool possibleNvidia = !State::Instance().dlssExternalBackend && isNvidiaViaNvapi();
|
||||
|
||||
spdlog::info("");
|
||||
spdlog::info("Check for DLSS files");
|
||||
@@ -1992,7 +2018,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||
State::Instance().NVNGX_DLSSG_Path = Util::FindFilePath(exePath, "nvngx_dlssg.dll");
|
||||
|
||||
// Not 100% accurate for Nvidia cards without DLSS
|
||||
- if (Config::Instance()->DLSSEnabled.value_or_default() && possibleNvidia)
|
||||
+ if (Config::Instance()->DLSSEnabled.value_or_default() &&
|
||||
+ (possibleNvidia || State::Instance().dlssExternalBackend))
|
||||
{
|
||||
if (State::Instance().NVNGX_DLSS_Path.has_value())
|
||||
{
|
||||
diff --git a/OptiScaler/hooks/LibraryLoad_Hooks.cpp b/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||
index 90bdb89..108ea5b 100644
|
||||
--- a/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||
+++ b/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||
@@ -46,6 +46,18 @@ HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, LPCWSTR lpLibF
|
||||
{
|
||||
auto libNameA = wstring_to_string(libName);
|
||||
|
||||
+ // The external D3D12 shim itself uses a locally supplied CUDA NGX core.
|
||||
+ // Keep that exact absolute path distinct from game-facing NGX interception.
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ {
|
||||
+ wchar_t corePath[32768] {};
|
||||
+ const auto length = GetEnvironmentVariableW(L"D4R_NGX_CORE", corePath, 32768);
|
||||
+ if (length && length < 32768 &&
|
||||
+ Util::ToLower(std::filesystem::path(libName).lexically_normal().wstring()) ==
|
||||
+ Util::ToLower(std::filesystem::path(corePath).lexically_normal().wstring()))
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+
|
||||
#ifdef LOG_LIB_OPERATIONS
|
||||
LOG_TRACE("{}", libNameA);
|
||||
#endif
|
||||
@@ -132,6 +144,11 @@ HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, LPCWSTR lpLibF
|
||||
// NvApi64.dll
|
||||
if (CheckDllNameW(&libName, &nvapiNamesW))
|
||||
{
|
||||
+ // An explicit external CUDA backend owns its NVAPI topology provider.
|
||||
+ // Returning OptiScaler's fake-NVAPI would replace that provider and can
|
||||
+ // recurse into DXGI while the NGX loader holds the loader lock.
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ return nullptr; // Continue through the original loader.
|
||||
LOG_INFO("{} call!", libNameA);
|
||||
|
||||
return LibraryLoadHooks::LoadNvApi();
|
||||
diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
index 68e1b42..89c9ba8 100644
|
||||
--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
+++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
@@ -687,6 +687,7 @@ static NVSDK_NGX_Result TryCreateOptiFeature(ID3D12GraphicsCommandList* InCmdLis
|
||||
}
|
||||
|
||||
// Assign handle
|
||||
+ const bool allocatedHandle = *OutHandle == nullptr;
|
||||
if (*OutHandle == nullptr)
|
||||
*OutHandle = new NVSDK_NGX_Handle { handleId };
|
||||
else
|
||||
@@ -705,6 +706,16 @@ static NVSDK_NGX_Result TryCreateOptiFeature(ID3D12GraphicsCommandList* InCmdLis
|
||||
}
|
||||
else
|
||||
{
|
||||
+ if (state.dlssExternalBackend && upscalerBackend == Upscaler::DLSS)
|
||||
+ {
|
||||
+ LOG_ERROR("External DLSS initialization failed; refusing FSR fallback");
|
||||
+ if (shouldRestoreSigs)
|
||||
+ D3D12Hooks::RestoreRoot(InCmdList);
|
||||
+ D3D12Hooks::SetRootSignatureTracking(true);
|
||||
+ Dx12Contexts.erase(handleId);
|
||||
+ if (allocatedHandle) { delete *OutHandle; *OutHandle = nullptr; }
|
||||
+ return NVSDK_NGX_Result_Fail;
|
||||
+ }
|
||||
LOG_ERROR("Feature '{}' initialization failed falling back to FSR 2.1.2", UpscalerDisplayName(upscalerBackend));
|
||||
state.newBackend = Upscaler::FSR21;
|
||||
state.changeBackend[handleId] = true;
|
||||
@@ -1027,6 +1038,12 @@ static NVSDK_NGX_Result TryEvaluateOptiFeature(ID3D12GraphicsCommandList* InCmdL
|
||||
// Fallback to FSR 2.1.2 if feature failed to initialize and user didn't explicitly request it
|
||||
if (!feature->IsInited() && cfg.Dx12Upscaler.value_or_default() != Upscaler::FSR21)
|
||||
{
|
||||
+ if (state.dlssExternalBackend && cfg.Dx12Upscaler.value_or_default() == Upscaler::DLSS)
|
||||
+ {
|
||||
+ LOG_ERROR("External DLSS feature is not initialized; refusing FSR fallback");
|
||||
+ D3D12Hooks::SetRootSignatureTracking(true);
|
||||
+ return NVSDK_NGX_Result_Fail;
|
||||
+ }
|
||||
LOG_WARN("Feature '{}' failed to initialize. Falling back to FSR 2.1.2", feature->Name());
|
||||
ImGui::InsertNotification({ ImGuiToastType::Warning, 10000, "Falling back to FSR 2.1.2" });
|
||||
|
||||
diff --git a/OptiScaler/misc/IdentifyGpu.cpp b/OptiScaler/misc/IdentifyGpu.cpp
|
||||
index 3877f9f..6c05904 100644
|
||||
--- a/OptiScaler/misc/IdentifyGpu.cpp
|
||||
+++ b/OptiScaler/misc/IdentifyGpu.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <proxies/D3d12_Proxy.h>
|
||||
#include "nvapi/NvApiTypes.h"
|
||||
#include <hooks/Amdxc64_Hooks.h>
|
||||
+#include <proxies/NVNGX_Proxy.h>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
@@ -187,6 +188,25 @@ std::vector<GpuInformation> IdentifyGpu::checkGpuInfo()
|
||||
|
||||
for (auto& gpuInfo : localCachedInfo)
|
||||
{
|
||||
+ if (State::Instance().dlssExternalBackend && !gpuInfo.softwareAdapter)
|
||||
+ {
|
||||
+ // Query the configured D3D12 backend outside DLL_PROCESS_ATTACH.
|
||||
+ // Preserve vendor/device/architecture data obtained from DXGI.
|
||||
+ auto requirements = NVNGXProxy::D3D12_GetFeatureRequirements();
|
||||
+ ComPtr<IDXGIAdapter1> physicalAdapter;
|
||||
+ if (requirements && SUCCEEDED(factory->EnumAdapterByLuid(gpuInfo.luid, IID_PPV_ARGS(&physicalAdapter))))
|
||||
+ {
|
||||
+ NVSDK_NGX_FeatureDiscoveryInfo discovery {};
|
||||
+ discovery.SDKVersion = NVSDK_NGX_Version_API;
|
||||
+ discovery.FeatureID = NVSDK_NGX_Feature_SuperSampling;
|
||||
+ NVSDK_NGX_FeatureRequirement supported {};
|
||||
+ const auto result = requirements(physicalAdapter.Get(), &discovery, &supported);
|
||||
+ gpuInfo.dlssCapable = result == NVSDK_NGX_Result_Success &&
|
||||
+ supported.FeatureSupported == NVSDK_NGX_FeatureSupportResult_Supported;
|
||||
+ LOG_INFO("External D3D12 DLSS backend: {} support={} result={:X}",
|
||||
+ gpuInfo.name, gpuInfo.dlssCapable, (unsigned) result);
|
||||
+ }
|
||||
+ }
|
||||
if (gpuInfo.vendorId == VendorId::Nvidia)
|
||||
{
|
||||
queryNvapi(gpuInfo);
|
||||
diff --git a/OptiScaler/proxies/NVNGX_Proxy.h b/OptiScaler/proxies/NVNGX_Proxy.h
|
||||
index f6c08e5..7892009 100644
|
||||
--- a/OptiScaler/proxies/NVNGX_Proxy.h
|
||||
+++ b/OptiScaler/proxies/NVNGX_Proxy.h
|
||||
@@ -500,7 +500,10 @@ class NVNGXProxy
|
||||
{
|
||||
LOG_INFO("Getting nvngx method addresses");
|
||||
|
||||
- HookNgxApi(_module.dll);
|
||||
+ // The external bridge reports physical adapter support itself.
|
||||
+ // Do not overwrite its unsupported result with NVIDIA spoofing.
|
||||
+ if (!State::Instance().dlssExternalBackend)
|
||||
+ HookNgxApi(_module.dll);
|
||||
|
||||
_module.D3D11_Init =
|
||||
(PFN_D3D11_Init) KernelBaseProxy::GetProcAddress_()(_module.dll, "NVSDK_NGX_D3D11_Init");
|
||||
diff --git a/OptiScaler/upscalers/FeatureProvider_Dx12.cpp b/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||
index d738498..1191b29 100644
|
||||
--- a/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||
+++ b/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||
@@ -42,6 +42,11 @@ bool FeatureProvider_Dx12::GetFeature(Upscaler upscaler, UINT handleId, NVSDK_NG
|
||||
break;
|
||||
|
||||
case Upscaler::DLSS:
|
||||
+ if (state.dlssExternalBackend && (!primaryGpu.dlssCapable || !state.NVNGX_DLSS_Path.has_value()))
|
||||
+ {
|
||||
+ LOG_ERROR("Configured external D3D12 DLSS backend is unavailable; refusing FSR fallback");
|
||||
+ return false;
|
||||
+ }
|
||||
if (primaryGpu.dlssCapable && state.NVNGX_DLSS_Path.has_value())
|
||||
{
|
||||
*feature = std::make_unique<DLSSFeatureDx12>(handleId, parameters);
|
||||
@@ -77,6 +82,11 @@ bool FeatureProvider_Dx12::GetFeature(Upscaler upscaler, UINT handleId, NVSDK_NG
|
||||
|
||||
if (!loaded)
|
||||
{
|
||||
+ if (state.dlssExternalBackend && upscaler == Upscaler::DLSS)
|
||||
+ {
|
||||
+ LOG_ERROR("External D3D12 DLSS module failed to load; refusing FSR fallback");
|
||||
+ return false;
|
||||
+ }
|
||||
// Fail after the constructor
|
||||
ImGui::InsertNotification({ ImGuiToastType::Warning, 10000, "Falling back to FSR 2.1.2" });
|
||||
*feature = std::make_unique<FSR2FeatureDx12_212>(handleId, parameters);
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
From 7ed1f1d66b430ba35a197fbd9880cd6a76af8f06 Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 16:45:01 +0300
|
||||
Subject: [PATCH] Install the external d4r command backend before NGX
|
||||
initialization
|
||||
|
||||
---
|
||||
OptiScaler/hooks/D3D12_Hooks.cpp | 14 ++++++++++++++
|
||||
1 file changed, 14 insertions(+)
|
||||
|
||||
diff --git a/OptiScaler/hooks/D3D12_Hooks.cpp b/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
index 29c0bec..3b757be 100644
|
||||
--- a/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
+++ b/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <proxies/IGDExt_Proxy.h>
|
||||
#include <proxies/Streamline_Proxy.h>
|
||||
#include <proxies/KernelBase_Proxy.h>
|
||||
+#include <proxies/NVNGX_Proxy.h>
|
||||
|
||||
#include <detours/detours.h>
|
||||
|
||||
@@ -2230,6 +2231,19 @@ static void HookToDevice(ID3D12Device* InDevice)
|
||||
if (Util::CheckForRealObject(__FUNCTION__, InDevice, (IUnknown**) &realDevice))
|
||||
pVTable = *(PVOID**) realDevice;
|
||||
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ {
|
||||
+ // Capture command signatures/resources created before the game's NGX
|
||||
+ // Init. Install before adding OptiScaler's own method trampolines.
|
||||
+ using InstallCommandBackend = unsigned (*)(ID3D12Device*);
|
||||
+ auto install = reinterpret_cast<InstallCommandBackend>(KernelBaseProxy::GetProcAddress_()(
|
||||
+ NVNGXProxy::NVNGXModule(), "d4r_D3D12_InstallCommandBackend"));
|
||||
+ auto result = install ? install(realDevice ? realDevice : InDevice) : 0;
|
||||
+ LOG_INFO("External d4r command backend installed at device discovery: result={:X}", result);
|
||||
+ if (result != 1)
|
||||
+ LOG_ERROR("External d4r early command tracking failed; DLSS initialization must be checked");
|
||||
+ }
|
||||
+
|
||||
// hudless
|
||||
o_D3D12DeviceRelease = (PFN_Release) pVTable[2];
|
||||
o_CreateSampler = (PFN_CreateSampler) pVTable[22];
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
From 09488c47855aadbf85f68ab8c72c0f3ac5f915e9 Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 17:01:32 +0300
|
||||
Subject: [PATCH] Preserve legacy ProjectID initialization in the external
|
||||
backend
|
||||
|
||||
---
|
||||
OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
index 89c9ba8..1f723f1 100644
|
||||
--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
+++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
@@ -313,6 +313,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_ProjectID(const char* InProj
|
||||
|
||||
if (result == NVSDK_NGX_Result_Success)
|
||||
NVNGXProxy::SetDx12Inited(true);
|
||||
+ else if (State::Instance().dlssExternalBackend)
|
||||
+ return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +345,10 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_with_ProjectID(
|
||||
{
|
||||
LOG_FUNC();
|
||||
|
||||
+ if (State::Instance().dlssExternalBackend)
|
||||
+ return NVSDK_NGX_D3D12_Init_ProjectID(InProjectId, InEngineType, InEngineVersion,
|
||||
+ InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo);
|
||||
+
|
||||
LOG_INFO("InProjectId: {0}", InProjectId);
|
||||
LOG_INFO("InEngineType: {0}", (int) InEngineType);
|
||||
LOG_INFO("InEngineVersion: {0}", InEngineVersion);
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
diff --git a/OptiScaler/hooks/D3D12_Hooks.cpp b/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
index 3b757be..05fc194 100644
|
||||
--- a/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
+++ b/OptiScaler/hooks/D3D12_Hooks.cpp
|
||||
@@ -2217,11 +2217,20 @@ static HRESULT hkD3D12GetInterface(REFCLSID rclsid, REFIID riid, void** ppvDebug
|
||||
return result;
|
||||
}
|
||||
|
||||
+// Device discovery and NGX initialization can overlap. Installing d4r's
|
||||
+// command hooks can also create a device and re-enter this function. Never
|
||||
+// overwrite live Detours trampolines from a second installation attempt.
|
||||
+static std::recursive_mutex deviceHookMutex;
|
||||
+static bool deviceHookInstalling = false;
|
||||
static void HookToDevice(ID3D12Device* InDevice)
|
||||
{
|
||||
- if (o_CreateSampler != nullptr || InDevice == nullptr)
|
||||
+ std::lock_guard<std::recursive_mutex> lock(deviceHookMutex);
|
||||
+ if (o_CreateSampler != nullptr || InDevice == nullptr || deviceHookInstalling)
|
||||
return;
|
||||
|
||||
+ deviceHookInstalling = true;
|
||||
+ struct InstallationGuard { ~InstallationGuard() { deviceHookInstalling = false; } } installationGuard;
|
||||
+
|
||||
LOG_DEBUG("Dx12");
|
||||
|
||||
// Get the vtable pointer
|
||||
@@ -2334,6 +2343,7 @@ static void HookToDevice(ID3D12Device* InDevice)
|
||||
|
||||
static void UnhookDevice()
|
||||
{
|
||||
+ std::lock_guard<std::recursive_mutex> lock(deviceHookMutex);
|
||||
LOG_FUNC();
|
||||
|
||||
DetourTransactionBegin();
|
||||
@@ -1,22 +0,0 @@
|
||||
diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
index 1f723f1..ef5ec77 100644
|
||||
--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
+++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||
@@ -184,6 +184,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_Ext(unsigned long long InApp
|
||||
|
||||
if (result == NVSDK_NGX_Result_Success)
|
||||
NVNGXProxy::SetDx12Inited(true);
|
||||
+ else if (State::Instance().dlssExternalBackend)
|
||||
+ return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -254,6 +256,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init(unsigned long long InApplica
|
||||
|
||||
if (result == NVSDK_NGX_Result_Success)
|
||||
NVNGXProxy::SetDx12Inited(true);
|
||||
+ else if (State::Instance().dlssExternalBackend)
|
||||
+ return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
Subject: [PATCH] Resolve relative d4r library paths against the OptiScaler folder
|
||||
|
||||
Lets a drag-in d4r install ship OptiScaler.ini with NvngxPath=d4r instead of
|
||||
an absolute path that only an installer could know. Absolute paths behave
|
||||
exactly as before.
|
||||
---
|
||||
diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp
|
||||
--- a/OptiScaler/Config.cpp
|
||||
+++ b/OptiScaler/Config.cpp
|
||||
@@ -787,9 +787,17 @@
|
||||
{
|
||||
MainDllPath.set_from_config(readWString("Libraries", "OptiDllPath"));
|
||||
|
||||
- NvngxPath.set_from_config(readWString("Libraries", "NvngxPath"));
|
||||
- NVNGX_DLSS_Library.set_from_config(readWString("Libraries", "NvngxDlssPath"));
|
||||
- DLSSFeaturePath.set_from_config(readWString("Libraries", "NvngxFeaturePath"));
|
||||
+ // A relative library folder (d4r drag-in installs use "d4r") is resolved
|
||||
+ // against this DLL's folder, as LogFileName is. Absolute paths are unchanged.
|
||||
+ auto libraryPath = [](std::optional<std::wstring> value) -> std::optional<std::wstring>
|
||||
+ {
|
||||
+ if (!value.has_value() || value->empty() || std::filesystem::path(*value).is_absolute())
|
||||
+ return value;
|
||||
+ return (Util::DllPath().parent_path() / *value).lexically_normal().wstring();
|
||||
+ };
|
||||
+ NvngxPath.set_from_config(libraryPath(readWString("Libraries", "NvngxPath")));
|
||||
+ NVNGX_DLSS_Library.set_from_config(libraryPath(readWString("Libraries", "NvngxDlssPath")));
|
||||
+ DLSSFeaturePath.set_from_config(libraryPath(readWString("Libraries", "NvngxFeaturePath")));
|
||||
NvapiDllPath.set_from_config(readWString("Libraries", "NvapiPath"));
|
||||
|
||||
FfxDx12Path.set_from_config(readWString("Libraries", "FfxDx12Path"));
|
||||
@@ -1,62 +0,0 @@
|
||||
# Explicit Windows d4r backend
|
||||
|
||||
Base: OptiScaler `45a2001303ddff632e279f77aef85ceede5832cb` (2026-09-30).
|
||||
|
||||
`0004` serializes D3D12 device hook installation/removal and guards recursive
|
||||
device discovery while the d4r command backend installs. The previous frontend
|
||||
can start two installations, fail a Detours transaction with `0x10dd`, clear
|
||||
live original-function pointers and call null. This was reproduced in the
|
||||
numeric-ID standalone harness; no game was required.
|
||||
|
||||
`0005` propagates numeric Init/Init_Ext failures for the explicitly selected
|
||||
external backend, matching the existing ProjectID behavior. FeatureNotFound
|
||||
must fail initialization instead of being hidden until repeated CreateFeature
|
||||
attempts. `Libraries.NvngxDlssPath` takes a directory, not the DLL filename;
|
||||
the Windows installer and standalone runner now use that directory.
|
||||
The modifications to OptiScaler are GPL-3.0, matching its upstream license.
|
||||
No NVIDIA DLL is supplied or patched.
|
||||
|
||||
`0001` introduces `[DLSS] AllowExternalBackend=true`, default false. Set
|
||||
`[Libraries] NvngxPath` to the absolute directory containing the Windows d4r
|
||||
shim as `_nvngx.dll`. Discovery requires its GPU-free ABI-1 marker. Physical
|
||||
adapter support is queried through the bridge's D3D12 requirements API after
|
||||
DLL startup; vendor, LUID and HIP architecture are retained.
|
||||
|
||||
The configured `D4R_NGX_CORE` absolute path bypasses game-facing NGX DLL
|
||||
redirection. The external backend loads its own NVAPI provider. An explicitly
|
||||
selected external DLSS backend reports initialization/load errors instead of
|
||||
silently switching to FSR. The patch fixes the upstream loader's filesystem
|
||||
exception by making IsSubpath a lexical check, and retains a PDB for diagnosis.
|
||||
|
||||
`0002` installs the command backend at D3D12 device discovery, before NGX Init
|
||||
and before OptiScaler's own method hooks. Command signatures created during
|
||||
game startup therefore retain their public argument descriptions for replay.
|
||||
The standalone runner's `-EarlyIndirectProbe` exercises this startup order.
|
||||
|
||||
`0003` preserves the Project ID and engine identity through OptiScaler's
|
||||
older `Init_with_ProjectID` entry point used by its XeSS/FSR inputs. It also
|
||||
propagates an explicit external backend initialization failure. Reproduce
|
||||
with the D3D12 runner's `-NgxAbi project-legacy`.
|
||||
|
||||
Build with Microsoft Build Tools 2022, v143 and Windows SDK 10.0.26100:
|
||||
|
||||
```powershell
|
||||
.\scripts\windows\build-optiscaler-windows.ps1
|
||||
```
|
||||
|
||||
The script clones the pinned source if absent, checks/applies the patch,
|
||||
initializes pinned submodules, invokes MSBuild and writes the DLL, PDB, license
|
||||
and build metadata into `dist/optiscaler-windows-d4r`. Existing source is not
|
||||
reset. The upstream packaging post-build event is disabled for this build.
|
||||
The official bootstrapper is https://aka.ms/vs/17/release/vs_buildtools.exe;
|
||||
install the `Microsoft.VisualStudio.Workload.VCTools`,
|
||||
`Microsoft.VisualStudio.Component.VC.Tools.x86.x64` and
|
||||
`Microsoft.VisualStudio.Component.Windows11SDK.26100` components. The audited
|
||||
local build uses MSVC 14.44.35207 / Build Tools 17.14.
|
||||
|
||||
For standalone verification add
|
||||
`-OptiScalerDll <absolute-dist-path>/OptiScaler.dll -CaptureExceptions`
|
||||
to the documented K/M runner. Its private INI selects DLSS, disables frame
|
||||
generation and keeps RestoreComputeSignature, RestoreGraphicSignature and
|
||||
ExtendedStateRestore false. Use `-RequireNativeNetwork`; finite output alone
|
||||
cannot distinguish DLSS from an unwanted FSR replacement.
|
||||
@@ -1,25 +0,0 @@
|
||||
From: d4r Windows port
|
||||
Subject: [PATCH] Backport AMD external-memory view ownership fix to ROCm 7.2
|
||||
|
||||
Upstream fix: ROCm/clr 529f6b1641de436dafbb095d5438b0fbf773765d
|
||||
(ROCm/rocm-systems #3528). Already present in current upstream.
|
||||
The constructor owns reference 1. MemObjMap does not retain the view;
|
||||
hipFree removes the entry and releases reference 1. The extra retain
|
||||
leaks the view and its parent imported allocation.
|
||||
|
||||
Hardware regression: d4r_d3d12_hip_interop_probe --interop-mode map
|
||||
--iterations 64. Windows SDK 7.2.60201 leaks one HANDLE / 256 KiB each
|
||||
cycle; tested TheRock 10.2.0a20260929 (HIP 7.17.26386) stays bounded.
|
||||
This patch is for rebuilding old runtime sources, never binary patching.
|
||||
|
||||
diff --git a/hipamd/src/hip_memory.cpp b/hipamd/src/hip_memory.cpp
|
||||
--- a/hipamd/src/hip_memory.cpp
|
||||
+++ b/hipamd/src/hip_memory.cpp
|
||||
@@ -179,7 +179,6 @@ hipError_t hipExternalMemoryGetMappedBuffer(void** devPtr, hipExternalMemory_t extMem,
|
||||
// Map the device memory to the user pointer
|
||||
*devPtr = reinterpret_cast<void*>(devMem->virtualAddress());
|
||||
amd::MemObjMap::AddMemObj(*devPtr, view);
|
||||
- view->retain();
|
||||
|
||||
HIP_RETURN(hipSuccess);
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
diff --git a/include/vkd3d_device_vkd3d_ext.idl b/include/vkd3d_device_vkd3d_ext.idl
|
||||
index 680f2c96..1fddc28e 100644
|
||||
--- a/include/vkd3d_device_vkd3d_ext.idl
|
||||
+++ b/include/vkd3d_device_vkd3d_ext.idl
|
||||
@@ -104,6 +104,21 @@ interface ID3D12DXVKInteropDevice3 : ID3D12DXVKInteropDevice2
|
||||
UINT32 *vk_memory_type);
|
||||
}
|
||||
|
||||
+[
|
||||
+ uuid(5a7c8b3e-2f61-4d0e-9c1a-7e3b52d4a901),
|
||||
+ object,
|
||||
+ local,
|
||||
+ pointer_default(unique)
|
||||
+]
|
||||
+interface ID3D12DXVKInteropDeviceD4R : ID3D12DXVKInteropDevice3
|
||||
+{
|
||||
+ /* d4r: ends the command list's current Vulkan command buffer. Everything
|
||||
+ * recorded afterwards is submitted separately and waits (as a queue
|
||||
+ * submission wait, not a GPU-side wait) until the timeline semaphore
|
||||
+ * vk_semaphore reaches value. */
|
||||
+ HRESULT SplitCommandListForExternalWait(ID3D12CommandList *list, UINT64 vk_semaphore, UINT64 value);
|
||||
+}
|
||||
+
|
||||
[
|
||||
uuid(f3112584-41f9-348d-a59b-00b7e1d285d6),
|
||||
object,
|
||||
diff --git a/libs/vkd3d/command.c b/libs/vkd3d/command.c
|
||||
index 611e373b..23af78a5 100644
|
||||
--- a/libs/vkd3d/command.c
|
||||
+++ b/libs/vkd3d/command.c
|
||||
@@ -2557,6 +2557,38 @@ static void d3d12_command_list_consider_new_sequence(struct d3d12_command_list *
|
||||
}
|
||||
}
|
||||
|
||||
+HRESULT d3d12_command_list_split_for_external_wait(struct d3d12_command_list *list,
|
||||
+ VkSemaphore semaphore, uint64_t value)
|
||||
+{
|
||||
+ struct d3d12_command_list_iteration *iteration;
|
||||
+ unsigned int count;
|
||||
+
|
||||
+ if (!list->is_recording)
|
||||
+ return E_INVALIDARG;
|
||||
+ if (!d3d12_command_list_allows_new_sequence(list))
|
||||
+ {
|
||||
+ WARN("Cannot split command list for external wait with a running query.\n");
|
||||
+ return E_FAIL;
|
||||
+ }
|
||||
+
|
||||
+ /* Flush everything recorded so far into the current command buffer. */
|
||||
+ d3d12_command_list_decay_tracked_state(list);
|
||||
+ d3d12_command_list_invalidate_all_state(list);
|
||||
+
|
||||
+ count = list->cmd.iteration_count;
|
||||
+ d3d12_command_list_begin_new_sequence(list, false);
|
||||
+ if (list->cmd.iteration_count == count)
|
||||
+ {
|
||||
+ WARN("Out of command list sequences for external wait.\n");
|
||||
+ return E_FAIL;
|
||||
+ }
|
||||
+
|
||||
+ iteration = &list->cmd.iterations[list->cmd.iteration_count - 1];
|
||||
+ iteration->external_wait_semaphore = semaphore;
|
||||
+ iteration->external_wait_value = value;
|
||||
+ return S_OK;
|
||||
+}
|
||||
+
|
||||
static void d3d12_command_list_copy_queue_fallback(struct d3d12_command_list *list)
|
||||
{
|
||||
if (!list->cmd.iterations[list->cmd.iteration_count - 1].fallback)
|
||||
@@ -22769,6 +22801,7 @@ static void STDMETHODCALLTYPE d3d12_command_queue_ExecuteCommandLists(ID3D12Comm
|
||||
struct vkd3d_initial_transition *transitions;
|
||||
size_t num_transitions, num_command_buffers;
|
||||
VkCommandBufferSubmitInfo *buffers, *buffer;
|
||||
+ VkSemaphoreSubmitInfo *external_waits = NULL;
|
||||
struct d3d12_command_allocator **allocators;
|
||||
struct d3d12_command_queue_submission sub;
|
||||
unsigned int indirect_barrier_hoist_index;
|
||||
@@ -22954,6 +22987,7 @@ static void STDMETHODCALLTYPE d3d12_command_queue_ExecuteCommandLists(ID3D12Comm
|
||||
vkd3d_free(allocators);
|
||||
vkd3d_free(buffers);
|
||||
vkd3d_free(cmd_cost);
|
||||
+ vkd3d_free(external_waits);
|
||||
#ifdef VKD3D_ENABLE_BREADCRUMBS
|
||||
vkd3d_free(breadcrumb_indices);
|
||||
#endif
|
||||
@@ -23036,6 +23070,21 @@ static void STDMETHODCALLTYPE d3d12_command_queue_ExecuteCommandLists(ID3D12Comm
|
||||
buffer->commandBuffer = cmd_list->cmd.iterations[iter].vk_command_buffer;
|
||||
/* Pilfer this for purposes of signalling aux data. No need to allocate more stuff just to signal this. */
|
||||
buffer->deviceMask = cmd_list->cmd.iterations[iter].fallback ? VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_FALLBACK_QUEUE : 0;
|
||||
+
|
||||
+ if (cmd_list->cmd.iterations[iter].external_wait_semaphore)
|
||||
+ {
|
||||
+ if (!external_waits)
|
||||
+ external_waits = vkd3d_calloc(num_command_buffers, sizeof(*external_waits));
|
||||
+ if (external_waits)
|
||||
+ {
|
||||
+ VkSemaphoreSubmitInfo *wait = &external_waits[cmd_submit_count - 1];
|
||||
+ wait->sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
|
||||
+ wait->semaphore = cmd_list->cmd.iterations[iter].external_wait_semaphore;
|
||||
+ wait->value = cmd_list->cmd.iterations[iter].external_wait_value;
|
||||
+ wait->stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
+ buffer->deviceMask |= VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_EXTERNAL_WAIT;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
need_suspend_fixup = cmd_list->cmd.suspend_resume.suspend.vk_fixup_cmd_buffer && (i + 1 == command_list_count ||
|
||||
@@ -23155,6 +23204,7 @@ static void STDMETHODCALLTYPE d3d12_command_queue_ExecuteCommandLists(ID3D12Comm
|
||||
sub.type = VKD3D_SUBMISSION_EXECUTE;
|
||||
sub.execute.cmd = buffers;
|
||||
sub.execute.cmd_cost = cmd_cost;
|
||||
+ sub.execute.external_waits = external_waits;
|
||||
sub.execute.cmd_count = cmd_submit_count;
|
||||
sub.execute.command_allocators = allocators;
|
||||
sub.execute.num_command_allocators = command_list_count;
|
||||
@@ -24377,6 +24427,8 @@ static void d3d12_command_queue_execute(struct d3d12_command_queue *command_queu
|
||||
VkSemaphoreSubmitInfo *binary_semaphore_info;
|
||||
bool stagger_submissions, is_first, is_last;
|
||||
VkSemaphoreSubmitInfo wait_semaphore_info;
|
||||
+ VkSemaphoreSubmitInfo combined_wait_infos[2];
|
||||
+ bool external_wait;
|
||||
uint32_t cmd_index, cmd_count, total_cost;
|
||||
struct vkd3d_fence_wait_info fence_info;
|
||||
VkSubmitInfo2 submit_desc[2], *submit;
|
||||
@@ -24516,7 +24568,15 @@ static void d3d12_command_queue_execute(struct d3d12_command_queue *command_queu
|
||||
total_cost = 0;
|
||||
|
||||
for (i = cmd_index + cmd_count; i < exec->cmd_count && total_cost < VKD3D_COMMAND_COST_MERGE_THRESHOLD; i++)
|
||||
+ {
|
||||
+ /* d4r: never merge across an external wait. */
|
||||
+ if (exec->cmd[i].deviceMask & VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_EXTERNAL_WAIT)
|
||||
+ {
|
||||
+ total_cost = VKD3D_COMMAND_COST_MERGE_THRESHOLD;
|
||||
+ break;
|
||||
+ }
|
||||
total_cost += exec->cmd_cost[i];
|
||||
+ }
|
||||
|
||||
if (total_cost < VKD3D_COMMAND_COST_MERGE_THRESHOLD)
|
||||
cmd_count = exec->cmd_count - cmd_index;
|
||||
@@ -24525,8 +24585,14 @@ static void d3d12_command_queue_execute(struct d3d12_command_queue *command_queu
|
||||
{
|
||||
/* Submit everything at once, until we hit a fallback queue submission. Pick that up next iteration. */
|
||||
for (cmd_count = 0; cmd_index + cmd_count < exec->cmd_count; cmd_count++)
|
||||
+ {
|
||||
if (exec->cmd[cmd_index + cmd_count].deviceMask & VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_FALLBACK_QUEUE)
|
||||
break;
|
||||
+ /* d4r: an external wait starts a new submit. */
|
||||
+ if (cmd_count && (exec->cmd[cmd_index + cmd_count].deviceMask &
|
||||
+ VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_EXTERNAL_WAIT))
|
||||
+ break;
|
||||
+ }
|
||||
}
|
||||
|
||||
submit = &submit_desc[num_submits++];
|
||||
@@ -24536,6 +24602,9 @@ static void d3d12_command_queue_execute(struct d3d12_command_queue *command_queu
|
||||
submit->signalSemaphoreInfoCount = 1;
|
||||
submit->pSignalSemaphoreInfos = signal_semaphore_infos;
|
||||
|
||||
+ external_wait = cmd_count && exec->external_waits &&
|
||||
+ (exec->cmd[cmd_index].deviceMask & VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_EXTERNAL_WAIT);
|
||||
+
|
||||
/* Clear out any shenanigans we added due to fallback submit. */
|
||||
for (i = 0; i < cmd_count; i++)
|
||||
exec->cmd[cmd_index + i].deviceMask = 0;
|
||||
@@ -24555,6 +24624,23 @@ static void d3d12_command_queue_execute(struct d3d12_command_queue *command_queu
|
||||
submit->pWaitSemaphoreInfos = &wait_semaphore_info;
|
||||
}
|
||||
|
||||
+ if (external_wait)
|
||||
+ {
|
||||
+ /* d4r: a queue-level wait-before-signal; the host signals the value later. */
|
||||
+ if (submit->waitSemaphoreInfoCount)
|
||||
+ {
|
||||
+ combined_wait_infos[0] = submit->pWaitSemaphoreInfos[0];
|
||||
+ combined_wait_infos[1] = exec->external_waits[cmd_index];
|
||||
+ submit->waitSemaphoreInfoCount = 2;
|
||||
+ submit->pWaitSemaphoreInfos = combined_wait_infos;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ submit->waitSemaphoreInfoCount = 1;
|
||||
+ submit->pWaitSemaphoreInfos = &exec->external_waits[cmd_index];
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
cmd_index += cmd_count;
|
||||
is_last = cmd_index == exec->cmd_count;
|
||||
|
||||
@@ -25347,6 +25433,7 @@ static void *d3d12_command_queue_submission_worker_main(void *userdata)
|
||||
* On error, the counters are freed early, so there is no risk of leak. */
|
||||
vkd3d_free(submission.execute.cmd);
|
||||
vkd3d_free(submission.execute.cmd_cost);
|
||||
+ vkd3d_free(submission.execute.external_waits);
|
||||
vkd3d_free(submission.execute.transitions);
|
||||
#ifdef VKD3D_ENABLE_BREADCRUMBS
|
||||
for (i = 0; i < submission.execute.breadcrumb_indices_count; i++)
|
||||
diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c
|
||||
index f6db6954..06955941 100644
|
||||
--- a/libs/vkd3d/device.c
|
||||
+++ b/libs/vkd3d/device.c
|
||||
@@ -4657,7 +4657,8 @@ HRESULT STDMETHODCALLTYPE d3d12_device_QueryInterface(d3d12_device_iface *iface,
|
||||
if (IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice)
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice1)
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice2)
|
||||
- || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice3))
|
||||
+ || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice3)
|
||||
+ || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R))
|
||||
{
|
||||
d3d12_dxvk_interop_device_AddRef(&device->ID3D12DXVKInteropDevice_iface);
|
||||
*object = &device->ID3D12DXVKInteropDevice_iface;
|
||||
@@ -11379,7 +11380,7 @@ static void d3d12_device_replace_vtable(struct d3d12_device *device)
|
||||
}
|
||||
|
||||
extern CONST_VTBL struct ID3D12DeviceExt5Vtbl d3d12_device_vkd3d_ext_vtbl;
|
||||
-extern CONST_VTBL struct ID3D12DXVKInteropDevice3Vtbl d3d12_dxvk_interop_device_vtbl;
|
||||
+extern CONST_VTBL struct ID3D12DXVKInteropDeviceD4RVtbl d3d12_dxvk_interop_device_vtbl;
|
||||
extern CONST_VTBL struct ID3DLowLatencyDeviceVtbl d3d_low_latency_device_vtbl;
|
||||
extern CONST_VTBL struct IAmdExtAntiLagApiVtbl d3d_amd_ext_anti_lag_vtbl;
|
||||
|
||||
diff --git a/libs/vkd3d/device_vkd3d_ext.c b/libs/vkd3d/device_vkd3d_ext.c
|
||||
index 94818b9a..bd5dd7a5 100644
|
||||
--- a/libs/vkd3d/device_vkd3d_ext.c
|
||||
+++ b/libs/vkd3d/device_vkd3d_ext.c
|
||||
@@ -1145,7 +1145,19 @@ static HRESULT STDMETHODCALLTYPE d3d12_dxvk_interop_device_GetVulkanHeapInfo(d3d
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
-CONST_VTBL struct ID3D12DXVKInteropDevice3Vtbl d3d12_dxvk_interop_device_vtbl =
|
||||
+static HRESULT STDMETHODCALLTYPE d3d12_dxvk_interop_device_SplitCommandListForExternalWait(
|
||||
+ d3d12_dxvk_interop_device_iface *iface, ID3D12CommandList *list, UINT64 vk_semaphore, UINT64 value)
|
||||
+{
|
||||
+ struct d3d12_command_list *cmd_list = d3d12_command_list_from_iface(list);
|
||||
+
|
||||
+ TRACE("iface %p, list %p, vk_semaphore 0x%"PRIx64", value %"PRIu64".\n", iface, list, vk_semaphore, value);
|
||||
+
|
||||
+ if (!cmd_list || !vk_semaphore)
|
||||
+ return E_INVALIDARG;
|
||||
+ return d3d12_command_list_split_for_external_wait(cmd_list, (VkSemaphore)vk_semaphore, value);
|
||||
+}
|
||||
+
|
||||
+CONST_VTBL struct ID3D12DXVKInteropDeviceD4RVtbl d3d12_dxvk_interop_device_vtbl =
|
||||
{
|
||||
/* IUnknown methods */
|
||||
d3d12_dxvk_interop_device_QueryInterface,
|
||||
@@ -1177,6 +1189,9 @@ CONST_VTBL struct ID3D12DXVKInteropDevice3Vtbl d3d12_dxvk_interop_device_vtbl =
|
||||
|
||||
/* ID3D12DXVKInteropDevice3 methods */
|
||||
d3d12_dxvk_interop_device_GetVulkanHeapInfo,
|
||||
+
|
||||
+ /* ID3D12DXVKInteropDeviceD4R methods */
|
||||
+ d3d12_dxvk_interop_device_SplitCommandListForExternalWait,
|
||||
};
|
||||
|
||||
static inline struct d3d12_device *d3d12_device_from_ID3DLowLatencyDevice(d3d_low_latency_device_iface *iface)
|
||||
diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h
|
||||
index a2a78000..64338585 100644
|
||||
--- a/libs/vkd3d/vkd3d_private.h
|
||||
+++ b/libs/vkd3d/vkd3d_private.h
|
||||
@@ -3207,6 +3207,9 @@ struct d3d12_command_list_iteration
|
||||
uint32_t estimated_cost;
|
||||
bool fallback; /* Only applies to vk_command_buffer. */
|
||||
struct d3d12_command_list_iteration_indirect_meta indirect_meta;
|
||||
+ /* d4r: vk_command_buffer is submitted separately, after this timeline value is reached. */
|
||||
+ VkSemaphore external_wait_semaphore;
|
||||
+ uint64_t external_wait_value;
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -3216,9 +3219,12 @@ enum
|
||||
* without having to add more allocations. */
|
||||
VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_DEFAULT = 0,
|
||||
VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_FALLBACK_QUEUE = 1u << 0,
|
||||
+ /* d4r: starts a new submit that waits for submission_execute::external_waits[index]. */
|
||||
+ VKD3D_COMMAND_BUFFER_SUBMIT_INFO_DEVICE_MASK_EXTERNAL_WAIT = 1u << 1,
|
||||
};
|
||||
|
||||
-#define VKD3D_MAX_COMMAND_LIST_SEQUENCES 2
|
||||
+/* d4r: 4 so an external-wait split fits next to DGC and fallback splits. */
|
||||
+#define VKD3D_MAX_COMMAND_LIST_SEQUENCES 4
|
||||
|
||||
#define VKD3D_COMMAND_COST_LOW (1u)
|
||||
#define VKD3D_COMMAND_COST_HIGH (16u)
|
||||
@@ -3737,6 +3743,8 @@ struct d3d12_command_queue_submission_execute
|
||||
{
|
||||
VkCommandBufferSubmitInfo *cmd;
|
||||
uint32_t *cmd_cost;
|
||||
+ /* d4r: indexed like cmd, only allocated when a command buffer waits externally. */
|
||||
+ VkSemaphoreSubmitInfo *external_waits;
|
||||
struct d3d12_command_allocator **command_allocators;
|
||||
UINT cmd_count;
|
||||
UINT num_command_allocators;
|
||||
@@ -5463,7 +5471,10 @@ struct vkd3d_timestamp_profiler;
|
||||
typedef ID3D12DeviceExt5 d3d12_device_vkd3d_ext_iface;
|
||||
|
||||
/* ID3D12DXVKInteropDevice */
|
||||
-typedef ID3D12DXVKInteropDevice3 d3d12_dxvk_interop_device_iface;
|
||||
+HRESULT d3d12_command_list_split_for_external_wait(struct d3d12_command_list *list,
|
||||
+ VkSemaphore semaphore, uint64_t value);
|
||||
+
|
||||
+typedef ID3D12DXVKInteropDeviceD4R d3d12_dxvk_interop_device_iface;
|
||||
|
||||
/* ID3DLowLatencyDevice */
|
||||
typedef ID3DLowLatencyDevice d3d_low_latency_device_iface;
|
||||
@@ -1,148 +0,0 @@
|
||||
diff --git a/include/vkd3d_device_vkd3d_ext.idl b/include/vkd3d_device_vkd3d_ext.idl
|
||||
--- a/include/vkd3d_device_vkd3d_ext.idl
|
||||
+++ b/include/vkd3d_device_vkd3d_ext.idl
|
||||
@@ -120,6 +120,19 @@
|
||||
}
|
||||
|
||||
[
|
||||
+ uuid(4f8c9462-8197-4dc1-a584-cf44d9706db2),
|
||||
+ object,
|
||||
+ local,
|
||||
+ pointer_default(unique)
|
||||
+]
|
||||
+interface ID3D12DXVKInteropDeviceD4R2 : ID3D12DXVKInteropDeviceD4R
|
||||
+{
|
||||
+ /* Retain external resources until the allocator can safely reset or is
|
||||
+ * destroyed. Pending submissions already retain the allocator. */
|
||||
+ HRESULT RetainExternalResources(ID3D12CommandList *list, IUnknown *resources);
|
||||
+}
|
||||
+
|
||||
+[
|
||||
uuid(f3112584-41f9-348d-a59b-00b7e1d285d6),
|
||||
object,
|
||||
local,
|
||||
diff --git a/libs/vkd3d/command.c b/libs/vkd3d/command.c
|
||||
--- a/libs/vkd3d/command.c
|
||||
+++ b/libs/vkd3d/command.c
|
||||
@@ -2791,6 +2791,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
+HRESULT d3d12_command_list_retain_external_resources(struct d3d12_command_list *list, IUnknown *resources)
|
||||
+{
|
||||
+ struct d3d12_command_allocator *allocator = list->allocator;
|
||||
+ size_t i;
|
||||
+
|
||||
+ if (!list->is_recording || !allocator)
|
||||
+ return E_INVALIDARG;
|
||||
+ for (i = 0; i < allocator->external_resources_count; i++)
|
||||
+ if (allocator->external_resources[i] == resources)
|
||||
+ return S_OK;
|
||||
+ if (!vkd3d_array_reserve((void **)&allocator->external_resources, &allocator->external_resources_size,
|
||||
+ allocator->external_resources_count + 1, sizeof(*allocator->external_resources)))
|
||||
+ return E_OUTOFMEMORY;
|
||||
+ IUnknown_AddRef(resources);
|
||||
+ allocator->external_resources[allocator->external_resources_count++] = resources;
|
||||
+ return S_OK;
|
||||
+}
|
||||
+
|
||||
static bool d3d12_command_allocator_add_view(struct d3d12_command_allocator *allocator,
|
||||
struct vkd3d_view *view)
|
||||
{
|
||||
@@ -2858,6 +2876,10 @@
|
||||
struct d3d12_device *device = allocator->device;
|
||||
const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
|
||||
unsigned int i;
|
||||
+
|
||||
+ for (i = 0; i < allocator->external_resources_count; ++i)
|
||||
+ IUnknown_Release(allocator->external_resources[i]);
|
||||
+ allocator->external_resources_count = 0;
|
||||
|
||||
for (i = 0; i < allocator->buffer_view_count; ++i)
|
||||
{
|
||||
@@ -2993,6 +3015,7 @@
|
||||
d3d12_command_list_allocator_destroyed(allocator->current_command_list);
|
||||
|
||||
d3d12_command_allocator_free_resources(allocator);
|
||||
+ vkd3d_free(allocator->external_resources);
|
||||
vkd3d_free(allocator->buffer_views);
|
||||
vkd3d_free(allocator->views);
|
||||
vkd3d_free(allocator->pipelines);
|
||||
diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c
|
||||
--- a/libs/vkd3d/device.c
|
||||
+++ b/libs/vkd3d/device.c
|
||||
@@ -4658,7 +4658,8 @@
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice1)
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice2)
|
||||
|| IsEqualGUID(riid, &IID_ID3D12DXVKInteropDevice3)
|
||||
- || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R))
|
||||
+ || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R)
|
||||
+ || IsEqualGUID(riid, &IID_ID3D12DXVKInteropDeviceD4R2))
|
||||
{
|
||||
d3d12_dxvk_interop_device_AddRef(&device->ID3D12DXVKInteropDevice_iface);
|
||||
*object = &device->ID3D12DXVKInteropDevice_iface;
|
||||
@@ -11380,7 +11381,7 @@
|
||||
}
|
||||
|
||||
extern CONST_VTBL struct ID3D12DeviceExt5Vtbl d3d12_device_vkd3d_ext_vtbl;
|
||||
-extern CONST_VTBL struct ID3D12DXVKInteropDeviceD4RVtbl d3d12_dxvk_interop_device_vtbl;
|
||||
+extern CONST_VTBL struct ID3D12DXVKInteropDeviceD4R2Vtbl d3d12_dxvk_interop_device_vtbl;
|
||||
extern CONST_VTBL struct ID3DLowLatencyDeviceVtbl d3d_low_latency_device_vtbl;
|
||||
extern CONST_VTBL struct IAmdExtAntiLagApiVtbl d3d_amd_ext_anti_lag_vtbl;
|
||||
|
||||
diff --git a/libs/vkd3d/device_vkd3d_ext.c b/libs/vkd3d/device_vkd3d_ext.c
|
||||
--- a/libs/vkd3d/device_vkd3d_ext.c
|
||||
+++ b/libs/vkd3d/device_vkd3d_ext.c
|
||||
@@ -1157,7 +1157,16 @@
|
||||
return d3d12_command_list_split_for_external_wait(cmd_list, (VkSemaphore)vk_semaphore, value);
|
||||
}
|
||||
|
||||
-CONST_VTBL struct ID3D12DXVKInteropDeviceD4RVtbl d3d12_dxvk_interop_device_vtbl =
|
||||
+static HRESULT STDMETHODCALLTYPE d3d12_dxvk_interop_device_RetainExternalResources(
|
||||
+ d3d12_dxvk_interop_device_iface *iface, ID3D12CommandList *list, IUnknown *resources)
|
||||
+{
|
||||
+ struct d3d12_command_list *cmd_list = d3d12_command_list_from_iface(list);
|
||||
+ if (!cmd_list || !resources)
|
||||
+ return E_INVALIDARG;
|
||||
+ return d3d12_command_list_retain_external_resources(cmd_list, resources);
|
||||
+}
|
||||
+
|
||||
+CONST_VTBL struct ID3D12DXVKInteropDeviceD4R2Vtbl d3d12_dxvk_interop_device_vtbl =
|
||||
{
|
||||
/* IUnknown methods */
|
||||
d3d12_dxvk_interop_device_QueryInterface,
|
||||
@@ -1192,6 +1201,9 @@
|
||||
|
||||
/* ID3D12DXVKInteropDeviceD4R methods */
|
||||
d3d12_dxvk_interop_device_SplitCommandListForExternalWait,
|
||||
+
|
||||
+ /* ID3D12DXVKInteropDeviceD4R2 methods */
|
||||
+ d3d12_dxvk_interop_device_RetainExternalResources,
|
||||
};
|
||||
|
||||
static inline struct d3d12_device *d3d12_device_from_ID3DLowLatencyDevice(d3d_low_latency_device_iface *iface)
|
||||
diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h
|
||||
--- a/libs/vkd3d/vkd3d_private.h
|
||||
+++ b/libs/vkd3d/vkd3d_private.h
|
||||
@@ -2758,6 +2758,10 @@
|
||||
LONG refcount;
|
||||
LONG internal_refcount;
|
||||
|
||||
+ IUnknown **external_resources;
|
||||
+ size_t external_resources_size;
|
||||
+ size_t external_resources_count;
|
||||
+
|
||||
D3D12_COMMAND_LIST_TYPE type;
|
||||
|
||||
struct vkd3d_view **views;
|
||||
@@ -5474,7 +5478,9 @@
|
||||
HRESULT d3d12_command_list_split_for_external_wait(struct d3d12_command_list *list,
|
||||
VkSemaphore semaphore, uint64_t value);
|
||||
|
||||
-typedef ID3D12DXVKInteropDeviceD4R d3d12_dxvk_interop_device_iface;
|
||||
+HRESULT d3d12_command_list_retain_external_resources(struct d3d12_command_list *list, IUnknown *resources);
|
||||
+
|
||||
+typedef ID3D12DXVKInteropDeviceD4R2 d3d12_dxvk_interop_device_iface;
|
||||
|
||||
/* ID3DLowLatencyDevice */
|
||||
typedef ID3DLowLatencyDevice d3d_low_latency_device_iface;
|
||||
@@ -1,128 +0,0 @@
|
||||
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
|
||||
index e619b186d..dd3e9d92b 100644
|
||||
--- a/llvm/include/llvm/BinaryFormat/ELF.h
|
||||
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
|
||||
@@ -856,7 +856,7 @@ enum : unsigned {
|
||||
EF_AMDGPU_MACH_AMDGCN_GFX11_GENERIC = 0x054,
|
||||
EF_AMDGPU_MACH_AMDGCN_GFX1152 = 0x055,
|
||||
EF_AMDGPU_MACH_AMDGCN_RESERVED_0X56 = 0x056,
|
||||
- EF_AMDGPU_MACH_AMDGCN_RESERVED_0X57 = 0x057,
|
||||
+ EF_AMDGPU_MACH_AMDGCN_GFX1154 = 0x057,
|
||||
EF_AMDGPU_MACH_AMDGCN_GFX1153 = 0x058,
|
||||
EF_AMDGPU_MACH_AMDGCN_GFX12_GENERIC = 0x059,
|
||||
EF_AMDGPU_MACH_AMDGCN_GFX1251 = 0x05a,
|
||||
diff --git a/llvm/include/llvm/TargetParser/TargetParser.h b/llvm/include/llvm/TargetParser/TargetParser.h
|
||||
index aad985926..c0aaa465b 100644
|
||||
--- a/llvm/include/llvm/TargetParser/TargetParser.h
|
||||
+++ b/llvm/include/llvm/TargetParser/TargetParser.h
|
||||
@@ -109,6 +109,7 @@ enum GPUKind : uint32_t {
|
||||
GK_GFX1151,
|
||||
GK_GFX1152,
|
||||
GK_GFX1153,
|
||||
+ GK_GFX1154,
|
||||
|
||||
GK_GFX1200,
|
||||
GK_GFX1201,
|
||||
diff --git a/llvm/lib/Object/ELFObjectFile.cpp b/llvm/lib/Object/ELFObjectFile.cpp
|
||||
index f9fda2346..70ddaae22 100644
|
||||
--- a/llvm/lib/Object/ELFObjectFile.cpp
|
||||
+++ b/llvm/lib/Object/ELFObjectFile.cpp
|
||||
@@ -591,6 +591,8 @@ StringRef ELFObjectFileBase::getAMDGPUCPUName() const {
|
||||
return "gfx1152";
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1153:
|
||||
return "gfx1153";
|
||||
+ case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1154:
|
||||
+ return "gfx1154";
|
||||
|
||||
// AMDGCN GFX12.
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1200:
|
||||
diff --git a/llvm/lib/ObjectYAML/ELFYAML.cpp b/llvm/lib/ObjectYAML/ELFYAML.cpp
|
||||
index d9cce1eb7..7936876f4 100644
|
||||
--- a/llvm/lib/ObjectYAML/ELFYAML.cpp
|
||||
+++ b/llvm/lib/ObjectYAML/ELFYAML.cpp
|
||||
@@ -632,6 +632,7 @@ void ScalarBitSetTraits<ELFYAML::ELF_EF>::bitset(IO &IO,
|
||||
BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1151, EF_AMDGPU_MACH);
|
||||
BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1152, EF_AMDGPU_MACH);
|
||||
BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1153, EF_AMDGPU_MACH);
|
||||
+ BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1154, EF_AMDGPU_MACH);
|
||||
BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1200, EF_AMDGPU_MACH);
|
||||
BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1201, EF_AMDGPU_MACH);
|
||||
BCaseMask(EF_AMDGPU_MACH_AMDGCN_GFX1250, EF_AMDGPU_MACH);
|
||||
diff --git a/llvm/lib/Target/AMDGPU/GCNProcessors.td b/llvm/lib/Target/AMDGPU/GCNProcessors.td
|
||||
index c6fb31f9c..5d21894a3 100644
|
||||
--- a/llvm/lib/Target/AMDGPU/GCNProcessors.td
|
||||
+++ b/llvm/lib/Target/AMDGPU/GCNProcessors.td
|
||||
@@ -304,7 +304,12 @@ def : ProcessorModel<"gfx1153", GFX11SpeedModel,
|
||||
FeatureISAVersion11_5_3.Features
|
||||
>;
|
||||
|
||||
-// [gfx1100, gfx1101, gfx1102, gfx1103, gfx1150, gfx1151, gfx1152, gfx1153]
|
||||
+// Backported from LLVM 7a0829e41228: actual gfx1154 features and identity.
|
||||
+def : ProcessorModel<"gfx1154", GFX11SpeedModel,
|
||||
+ FeatureISAVersion11_5_Common.Features
|
||||
+>;
|
||||
+
|
||||
+// [gfx1100, gfx1101, gfx1102, gfx1103, gfx1150, gfx1151, gfx1152, gfx1153, gfx1154]
|
||||
def : ProcessorModel<"gfx11-generic", GFX11SpeedModel,
|
||||
FeatureISAVersion11_Generic.Features
|
||||
>;
|
||||
diff --git a/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp b/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
|
||||
index 5a0857322..c506fc308 100644
|
||||
--- a/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
|
||||
+++ b/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
|
||||
@@ -114,6 +114,7 @@ StringRef AMDGPUTargetStreamer::getArchNameFromElfMach(unsigned ElfMach) {
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1151: AK = GK_GFX1151; break;
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1152: AK = GK_GFX1152; break;
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1153: AK = GK_GFX1153; break;
|
||||
+ case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1154: AK = GK_GFX1154; break;
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1200: AK = GK_GFX1200; break;
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1201: AK = GK_GFX1201; break;
|
||||
case ELF::EF_AMDGPU_MACH_AMDGCN_GFX1250: AK = GK_GFX1250; break;
|
||||
@@ -201,6 +202,7 @@ unsigned AMDGPUTargetStreamer::getElfMach(StringRef GPU) {
|
||||
case GK_GFX1151: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1151;
|
||||
case GK_GFX1152: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1152;
|
||||
case GK_GFX1153: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1153;
|
||||
+ case GK_GFX1154: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1154;
|
||||
case GK_GFX1200: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1200;
|
||||
case GK_GFX1201: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1201;
|
||||
case GK_GFX1250: return ELF::EF_AMDGPU_MACH_AMDGCN_GFX1250;
|
||||
diff --git a/llvm/lib/TargetParser/TargetParser.cpp b/llvm/lib/TargetParser/TargetParser.cpp
|
||||
index 34b09b14b..39acbb942 100644
|
||||
--- a/llvm/lib/TargetParser/TargetParser.cpp
|
||||
+++ b/llvm/lib/TargetParser/TargetParser.cpp
|
||||
@@ -172,6 +172,7 @@ constexpr GPUInfo AMDGCNGPUs[] = {
|
||||
{{"gfx1151"}, {"gfx1151"}, GK_GFX1151, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32|FEATURE_WGP},
|
||||
{{"gfx1152"}, {"gfx1152"}, GK_GFX1152, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32|FEATURE_WGP},
|
||||
{{"gfx1153"}, {"gfx1153"}, GK_GFX1153, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32|FEATURE_WGP},
|
||||
+ {{"gfx1154"}, {"gfx1154"}, GK_GFX1154, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32|FEATURE_WGP},
|
||||
{{"gfx1200"}, {"gfx1200"}, GK_GFX1200, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32|FEATURE_WGP},
|
||||
{{"gfx1201"}, {"gfx1201"}, GK_GFX1201, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32|FEATURE_WGP},
|
||||
{{"gfx1250"}, {"gfx1250"}, GK_GFX1250, FEATURE_FAST_FMA_F32|FEATURE_FAST_DENORMAL_F32|FEATURE_WAVE32},
|
||||
@@ -328,6 +329,7 @@ AMDGPU::IsaVersion AMDGPU::getIsaVersion(StringRef GPU) {
|
||||
case GK_GFX1151: return {11, 5, 1};
|
||||
case GK_GFX1152: return {11, 5, 2};
|
||||
case GK_GFX1153: return {11, 5, 3};
|
||||
+ case GK_GFX1154: return {11, 5, 4};
|
||||
case GK_GFX1200: return {12, 0, 0};
|
||||
case GK_GFX1201: return {12, 0, 1};
|
||||
case GK_GFX1250: return {12, 5, 0};
|
||||
@@ -475,6 +477,7 @@ static void fillAMDGCNFeatureMap(StringRef GPU, const Triple &T,
|
||||
Features["atomic-fmin-fmax-global-f32"] = true;
|
||||
break;
|
||||
case GK_GFX1153:
|
||||
+ case GK_GFX1154:
|
||||
case GK_GFX1152:
|
||||
case GK_GFX1151:
|
||||
case GK_GFX1150:
|
||||
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
|
||||
index ab9331690..0ade2d412 100644
|
||||
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
|
||||
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
|
||||
@@ -1655,6 +1655,7 @@ const EnumEntry<unsigned> ElfHeaderMipsFlags[] = {
|
||||
ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1151, "gfx1151"), \
|
||||
ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1152, "gfx1152"), \
|
||||
ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1153, "gfx1153"), \
|
||||
+ ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1154, "gfx1154"), \
|
||||
ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1200, "gfx1200"), \
|
||||
ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1201, "gfx1201"), \
|
||||
ENUM_ENT(EF_AMDGPU_MACH_AMDGCN_GFX1250, "gfx1250"), \
|
||||
@@ -1,24 +0,0 @@
|
||||
# LLVM source patches for the Windows compiler
|
||||
|
||||
Base: ZLUDA's LLVM submodule `ff4dc1f7c9e1c64d4d69e40f4ed30c2280a96dfd`
|
||||
(LLVM 22). `build-zluda-llvm.ps1` applies these patches idempotently before
|
||||
building. ZLUDA's parent source patches remain in `patches/zluda`.
|
||||
|
||||
`0001-backport-gfx1154.patch` adapts official LLVM
|
||||
[`7a0829e41228`](https://github.com/llvm/llvm-project/commit/7a0829e41228513299c5108685b0bc127463c6a1)
|
||||
to the older parser/ELF structures. It adds actual gfx1154 processor features,
|
||||
ISA 11.5.4, parser identity and ELF machine 0x57. It does not map gfx1154 onto
|
||||
another CPU, patch binary headers or alter existing processors' features.
|
||||
LLVM's gfx1154 uses `FeatureISAVersion11_5_Common` and the GFX11 speed model.
|
||||
LLVM code remains Apache-2.0 WITH LLVM-exception (see the dependency license).
|
||||
|
||||
The prior offline compiler aborts on a public PTX pattern for gfx1154 with
|
||||
unrecognized-processor diagnostics and exit `0xC0000409`. After rebuilding
|
||||
LLVM, rebuild/relink ZLUDA and its offline emitter; changing native Clang
|
||||
objects alone cannot fix the translated PTX path. Run
|
||||
`scripts/windows/test-zluda-target-compilation.ps1` to compile the public
|
||||
Driver API pattern and FP16 MMA fixtures (strict/reference and WMMA modes)
|
||||
for every target without GPU work. All 33 compilations pass with correct ELF
|
||||
targets and wave32. Strict gfx12 F16 reference retains its prior FP64 helper;
|
||||
WMMA mode here is a compilation check, not a default arithmetic change.
|
||||
This checks code generation and ELF identity, not hardware execution/numerics.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,772 +0,0 @@
|
||||
diff --git a/ptx/src/pass/mod.rs b/ptx/src/pass/mod.rs
|
||||
index 15dd9ba..cff04dd 100644
|
||||
--- a/ptx/src/pass/mod.rs
|
||||
+++ b/ptx/src/pass/mod.rs
|
||||
@@ -61,6 +61,42 @@ pub struct Attributes {
|
||||
pub clock_rate: u32,
|
||||
}
|
||||
|
||||
+/// Work-group size bound applied to kernels without `.maxntid`/`.reqntid`
|
||||
+/// (D4R_ZLUDA_IMPLICIT_MAX_BLOCK, 1..=1024; unset keeps LLVM's 1024 default).
|
||||
+///
|
||||
+/// Without a bound, LLVM must assume 1024-thread work-groups, which on gfx11
|
||||
+/// in CU mode caps each wave at 96 VGPRs and makes register-heavy kernels
|
||||
+/// (e.g. DLSS's tensor-core convolutions) spill kilobytes to scratch. CUDA's
|
||||
+/// own compiler gives unbounded kernels up to 255 registers, which likewise
|
||||
+/// limits them to 256-thread blocks; a launch exceeding the bound fails
|
||||
+/// (as it would on NVIDIA) instead of misbehaving.
|
||||
+/// Optional AMDGPU machine-scheduler strategy for kernels
|
||||
+/// (D4R_ZLUDA_SCHED_STRATEGY: max-ilp, max-memory-clause, iterative-ilp,
|
||||
+/// iterative-minreg or iterative-maxocc); unset keeps LLVM's default.
|
||||
+pub fn sched_strategy() -> Option<&'static str> {
|
||||
+ static VALUE: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
|
||||
+ VALUE
|
||||
+ .get_or_init(|| {
|
||||
+ std::env::var("D4R_ZLUDA_SCHED_STRATEGY").ok().filter(|value| {
|
||||
+ matches!(
|
||||
+ value.as_str(),
|
||||
+ "max-ilp" | "max-memory-clause" | "iterative-ilp" | "iterative-minreg" | "iterative-maxocc"
|
||||
+ )
|
||||
+ })
|
||||
+ })
|
||||
+ .as_deref()
|
||||
+}
|
||||
+
|
||||
+pub fn implicit_max_block() -> Option<u32> {
|
||||
+ static VALUE: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
|
||||
+ *VALUE.get_or_init(|| {
|
||||
+ std::env::var("D4R_ZLUDA_IMPLICIT_MAX_BLOCK")
|
||||
+ .ok()
|
||||
+ .and_then(|value| value.trim().parse::<u32>().ok())
|
||||
+ .filter(|value| (1..=1024).contains(value))
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
pub fn to_llvm_module<'input>(
|
||||
ast: ast::Module<'input>,
|
||||
attributes: Attributes,
|
||||
diff --git a/ptx/src/lib.rs b/ptx/src/lib.rs
|
||||
index 8daa637..79ed812 100644
|
||||
--- a/ptx/src/lib.rs
|
||||
+++ b/ptx/src/lib.rs
|
||||
@@ -4,4 +4,6 @@ mod test;
|
||||
|
||||
pub use pass::to_llvm_module;
|
||||
pub use pass::Attributes;
|
||||
+pub use pass::implicit_max_block;
|
||||
+pub use pass::sched_strategy;
|
||||
pub use pass::TranslateError;
|
||||
diff --git a/ptx/src/pass/llvm/emit.rs b/ptx/src/pass/llvm/emit.rs
|
||||
index 5ab8985..e891c34 100644
|
||||
--- a/ptx/src/pass/llvm/emit.rs
|
||||
+++ b/ptx/src/pass/llvm/emit.rs
|
||||
@@ -147,6 +147,23 @@ impl<'a, 'input> ModuleEmitContext<'a, 'input> {
|
||||
}
|
||||
self.emit_target_features(fn_);
|
||||
self.emit_tuning(fn_, &method.tuning);
|
||||
+ if method.is_kernel()
|
||||
+ && !method.tuning.iter().any(|tuning| {
|
||||
+ matches!(
|
||||
+ tuning,
|
||||
+ ptx_parser::TuningDirective::MaxNtid(..) | ptx_parser::TuningDirective::ReqNtid(..)
|
||||
+ )
|
||||
+ })
|
||||
+ {
|
||||
+ if let Some(size) = crate::pass::implicit_max_block() {
|
||||
+ self.emit_fn_attribute_string(fn_, "amdgpu-flat-work-group-size", &format!("1,{size}"));
|
||||
+ }
|
||||
+ }
|
||||
+ if method.is_kernel() {
|
||||
+ if let Some(strategy) = crate::pass::sched_strategy() {
|
||||
+ self.emit_fn_attribute_string(fn_, "amdgpu-sched-strategy", strategy);
|
||||
+ }
|
||||
+ }
|
||||
if let Some(kernel_attrs) = &method.kernel_attributes {
|
||||
self.emit_fn_attribute_string(
|
||||
fn_,
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index a6adeef..0fbc7f7 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -260,6 +260,9 @@ fn get_best_ptx_and_compile(
|
||||
let attributes = ExtraCacheAttributes {
|
||||
clock_rate: hip_properties.clockRate as u32,
|
||||
is_debug: cfg!(debug_assertions),
|
||||
+ implicit_max_block: ptx::implicit_max_block(),
|
||||
+ sched_strategy: ptx::sched_strategy(),
|
||||
+ wmma: llvm_zluda::compile::wmma_rewrite_requested(),
|
||||
};
|
||||
let mut cache_with_key = match (text, global_state.cache_path.as_ref()) {
|
||||
(Some(text), Some(p)) => (|| {
|
||||
@@ -296,6 +299,9 @@ pub(crate) fn load_hip_module(
|
||||
struct ExtraCacheAttributes {
|
||||
is_debug: bool,
|
||||
clock_rate: u32,
|
||||
+ implicit_max_block: Option<u32>,
|
||||
+ sched_strategy: Option<&'static str>,
|
||||
+ wmma: bool,
|
||||
}
|
||||
|
||||
fn get_hip_properties<'a>() -> Result<hipDeviceProp_tR0600, CUerror> {
|
||||
diff --git a/llvm_zluda/src/compile.rs b/llvm_zluda/src/compile.rs
|
||||
index b80262b..1284cbc 100644
|
||||
--- a/llvm_zluda/src/compile.rs
|
||||
+++ b/llvm_zluda/src/compile.rs
|
||||
@@ -87,6 +87,18 @@ fn create_oclc_constants(ctx: &Context, gcn_arch: &str) -> Result<Module, String
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
+/// D4R_ZLUDA_WMMA=1 lowers f16 m16n8k16 MMAs to gfx11 WMMA (see
|
||||
+/// LLVMZludaRewriteMmaF16Wmma); anything else keeps the dot-product fallback.
|
||||
+pub fn wmma_rewrite_requested() -> bool {
|
||||
+ static VALUE: OnceLock<bool> = OnceLock::new();
|
||||
+ *VALUE.get_or_init(|| std::env::var("D4R_ZLUDA_WMMA").map_or(false, |value| value.trim() == "1"))
|
||||
+}
|
||||
+
|
||||
+fn wmma_rewrite_enabled(gcn_arch: &str) -> bool {
|
||||
+ // The helpers assume RDNA3's wave32 WMMA register layout.
|
||||
+ wmma_rewrite_requested() && gcn_arch.starts_with("gfx11")
|
||||
+}
|
||||
+
|
||||
pub fn compile(
|
||||
ctx: &Context,
|
||||
gcn_arch: &str,
|
||||
@@ -106,6 +118,9 @@ pub fn compile(
|
||||
|
||||
let oclc_constants = create_oclc_constants(ctx, gcn_arch)?;
|
||||
|
||||
+ if wmma_rewrite_enabled(gcn_arch) {
|
||||
+ unsafe { crate::ffi::LLVMZludaRewriteMmaF16Wmma(main.get()) };
|
||||
+ }
|
||||
linked.link(main)?;
|
||||
linked.link(attributes)?;
|
||||
linked.link(oclc_constants)?;
|
||||
diff --git a/llvm_zluda/src/ffi.rs b/llvm_zluda/src/ffi.rs
|
||||
index 7ca4289..6bf0d30 100644
|
||||
--- a/llvm_zluda/src/ffi.rs
|
||||
+++ b/llvm_zluda/src/ffi.rs
|
||||
@@ -42,6 +42,9 @@ pub const LLVMZludaFastMathAll: ::std::ffi::c_uint = LLVMZludaFastMathAllowReass
|
||||
pub type LLVMZludaFastMathFlags = std::ffi::c_uint;
|
||||
|
||||
extern "C" {
|
||||
+ /// See LLVMZludaRewriteMmaF16Wmma in lib.cpp.
|
||||
+ pub fn LLVMZludaRewriteMmaF16Wmma(M: LLVMModuleRef) -> u32;
|
||||
+
|
||||
pub fn LLVMZludaBuildAlloca(
|
||||
B: LLVMBuilderRef,
|
||||
Ty: LLVMTypeRef,
|
||||
diff --git a/llvm_zluda/src/lib.cpp b/llvm_zluda/src/lib.cpp
|
||||
index 14b4f40..30d5490 100644
|
||||
--- a/llvm_zluda/src/lib.cpp
|
||||
+++ b/llvm_zluda/src/lib.cpp
|
||||
@@ -9,6 +9,11 @@
|
||||
#include <llvm/Support/raw_ostream.h>
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
+#include <llvm/ADT/DenseMap.h>
|
||||
+#include <llvm/ADT/DenseSet.h>
|
||||
+#include <llvm/ADT/SmallVector.h>
|
||||
+#include <llvm/IR/Module.h>
|
||||
+#include <llvm/IR/InstIterator.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <mutex>
|
||||
@@ -255,6 +260,367 @@ int LLVMZludaLinkWithLLD(uint32_t inputs, const char **input_paths, const char *
|
||||
return 0;
|
||||
}
|
||||
|
||||
+// gfx11 WMMA lowering of mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16,
|
||||
+// run on ZLUDA's emitted module before linking, while PTX registers are still
|
||||
+// allocas. Each MMA becomes
|
||||
+// d = d4r_wmma_t_to_nv(d4r_wmma_t_mma_f16(a_from_b(B), b_from_a(A), C'))
|
||||
+// (see zluda_ptx_impl.cpp for the layouts). Register pairs used as MMA
|
||||
+// accumulators get a shadow alloca holding the pair in WMMA layout: an MMA
|
||||
+// whose C is a plain load of such a pair reads the shadow, an MMA whose D is
|
||||
+// stored to it writes the shadow, and any other store to either register
|
||||
+// refreshes the shadow with d4r_wmma_t_from_nv. The invariant "shadow ==
|
||||
+// WMMA layout of the pair" thus holds at every program point, and once
|
||||
+// mem2reg promotes the allocas the layout conversions between chained MMAs
|
||||
+// become dead code. Returns the number of MMAs rewritten.
|
||||
+namespace
|
||||
+{
|
||||
+constexpr const char *kMmaF16 = "__zluda_ptx_impl_mma_sync_aligned_m16n8k16_row_col_f16_f16_f16_f16";
|
||||
+
|
||||
+struct RegisterPair
|
||||
+{
|
||||
+ AllocaInst *Low = nullptr;
|
||||
+ AllocaInst *High = nullptr;
|
||||
+ bool operator==(const RegisterPair &Other) const { return Low == Other.Low && High == Other.High; }
|
||||
+};
|
||||
+
|
||||
+// Only allocas used purely as the address of loads and stores are tracked.
|
||||
+static bool isPlainRegister(AllocaInst *Alloca)
|
||||
+{
|
||||
+ for (User *U : Alloca->users())
|
||||
+ {
|
||||
+ if (isa<LoadInst>(U))
|
||||
+ continue;
|
||||
+ auto *Store = dyn_cast<StoreInst>(U);
|
||||
+ if (Store == nullptr || Store->getPointerOperand() != Alloca)
|
||||
+ return false;
|
||||
+ }
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+// Matches insertelement(insertelement(undef, load Low, 0), load High, 1) where
|
||||
+// the loads sit in the call's block with no store to either register between
|
||||
+// them and the call.
|
||||
+static bool matchLoadedPair(Value *Vector, CallInst *Call, RegisterPair &Pair)
|
||||
+{
|
||||
+ auto *Second = dyn_cast<InsertElementInst>(Vector);
|
||||
+ if (Second == nullptr)
|
||||
+ return false;
|
||||
+ auto *First = dyn_cast<InsertElementInst>(Second->getOperand(0));
|
||||
+ auto *SecondIndex = dyn_cast<ConstantInt>(Second->getOperand(2));
|
||||
+ if (First == nullptr || SecondIndex == nullptr || SecondIndex->getZExtValue() != 1)
|
||||
+ return false;
|
||||
+ auto *FirstIndex = dyn_cast<ConstantInt>(First->getOperand(2));
|
||||
+ if (FirstIndex == nullptr || FirstIndex->getZExtValue() != 0 || !isa<UndefValue>(First->getOperand(0)))
|
||||
+ return false;
|
||||
+ auto *LowLoad = dyn_cast<LoadInst>(First->getOperand(1));
|
||||
+ auto *HighLoad = dyn_cast<LoadInst>(Second->getOperand(1));
|
||||
+ if (LowLoad == nullptr || HighLoad == nullptr || LowLoad->getParent() != Call->getParent() ||
|
||||
+ HighLoad->getParent() != Call->getParent())
|
||||
+ return false;
|
||||
+ Pair.Low = dyn_cast<AllocaInst>(LowLoad->getPointerOperand());
|
||||
+ Pair.High = dyn_cast<AllocaInst>(HighLoad->getPointerOperand());
|
||||
+ if (Pair.Low == nullptr || Pair.High == nullptr || Pair.Low == Pair.High ||
|
||||
+ !Pair.Low->getAllocatedType()->isIntegerTy(32) || !Pair.High->getAllocatedType()->isIntegerTy(32) ||
|
||||
+ !isPlainRegister(Pair.Low) || !isPlainRegister(Pair.High))
|
||||
+ return false;
|
||||
+ Instruction *Earliest = LowLoad->comesBefore(HighLoad) ? LowLoad : HighLoad;
|
||||
+ for (Instruction *I = Earliest->getNextNode(); I != Call; I = I->getNextNode())
|
||||
+ if (auto *Store = dyn_cast<StoreInst>(I))
|
||||
+ if (Store->getPointerOperand() == Pair.Low || Store->getPointerOperand() == Pair.High)
|
||||
+ return false;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+// Finds the stores of both lanes of an MMA result into a register pair.
|
||||
+static bool matchStoredPair(CallInst *Call, RegisterPair &Pair, StoreInst *Stores[2])
|
||||
+{
|
||||
+ Stores[0] = Stores[1] = nullptr;
|
||||
+ for (User *U : Call->users())
|
||||
+ {
|
||||
+ auto *Extract = dyn_cast<ExtractElementInst>(U);
|
||||
+ auto *Index = Extract != nullptr ? dyn_cast<ConstantInt>(Extract->getIndexOperand()) : nullptr;
|
||||
+ if (Index == nullptr || Index->getZExtValue() > 1 || !Extract->hasOneUse())
|
||||
+ return false;
|
||||
+ auto *Store = dyn_cast<StoreInst>(Extract->user_back());
|
||||
+ if (Store == nullptr || Store->getValueOperand() != Extract || Stores[Index->getZExtValue()] != nullptr)
|
||||
+ return false;
|
||||
+ Stores[Index->getZExtValue()] = Store;
|
||||
+ }
|
||||
+ if (Stores[0] == nullptr || Stores[1] == nullptr)
|
||||
+ return false;
|
||||
+ Pair.Low = dyn_cast<AllocaInst>(Stores[0]->getPointerOperand());
|
||||
+ Pair.High = dyn_cast<AllocaInst>(Stores[1]->getPointerOperand());
|
||||
+ return Pair.Low != nullptr && Pair.High != nullptr && Pair.Low != Pair.High;
|
||||
+}
|
||||
+
|
||||
+// Matches an N-element vector built as a chain of insertelements (indices
|
||||
+// 0..N-1 into undef) of loads from plain i32 registers; returns the registers.
|
||||
+static bool matchLoadedVector(Value *Vector, unsigned Elements, SmallVectorImpl<AllocaInst *> &Registers)
|
||||
+{
|
||||
+ Registers.assign(Elements, nullptr);
|
||||
+ for (unsigned Remaining = Elements; Remaining > 0; --Remaining)
|
||||
+ {
|
||||
+ auto *Insert = dyn_cast<InsertElementInst>(Vector);
|
||||
+ auto *Index = Insert != nullptr ? dyn_cast<ConstantInt>(Insert->getOperand(2)) : nullptr;
|
||||
+ if (Index == nullptr || Index->getZExtValue() != Remaining - 1)
|
||||
+ return false;
|
||||
+ auto *Load = dyn_cast<LoadInst>(Insert->getOperand(1));
|
||||
+ auto *Register = Load != nullptr ? dyn_cast<AllocaInst>(Load->getPointerOperand()) : nullptr;
|
||||
+ if (Register == nullptr || !Register->getAllocatedType()->isIntegerTy(32) || !isPlainRegister(Register))
|
||||
+ return false;
|
||||
+ Registers[Remaining - 1] = Register;
|
||||
+ Vector = Insert->getOperand(0);
|
||||
+ }
|
||||
+ return isa<UndefValue>(Vector);
|
||||
+}
|
||||
+
|
||||
+// The registers are plain allocas (only loaded and stored directly), so only
|
||||
+// explicit stores can change them.
|
||||
+static bool storesBetween(Instruction *From, Instruction *To, ArrayRef<AllocaInst *> Registers)
|
||||
+{
|
||||
+ for (Instruction *I = From->getNextNode(); I != nullptr && I != To; I = I->getNextNode())
|
||||
+ if (auto *Store = dyn_cast<StoreInst>(I))
|
||||
+ if (is_contained(Registers, Store->getPointerOperand()))
|
||||
+ return true;
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+struct Helpers
|
||||
+{
|
||||
+ FunctionCallee AFromB, BFromA, Mma, FromNv, ToNv, AFromBPair, MmaPair;
|
||||
+};
|
||||
+
|
||||
+static Value *loadPair(IRBuilder<> &Builder, const RegisterPair &Pair)
|
||||
+{
|
||||
+ Type *I32 = Builder.getInt32Ty();
|
||||
+ Value *Vector = UndefValue::get(FixedVectorType::get(I32, 2));
|
||||
+ Vector = Builder.CreateInsertElement(Vector, Builder.CreateLoad(I32, Pair.Low), Builder.getInt8(0));
|
||||
+ return Builder.CreateInsertElement(Vector, Builder.CreateLoad(I32, Pair.High), Builder.getInt8(1));
|
||||
+}
|
||||
+
|
||||
+static unsigned rewriteFunction(Function &F, Function *Mma, const Helpers &H)
|
||||
+{
|
||||
+ SmallVector<CallInst *, 64> Calls;
|
||||
+ for (Instruction &I : instructions(F))
|
||||
+ if (auto *Call = dyn_cast<CallInst>(&I))
|
||||
+ if (Call->getCalledFunction() == Mma && Call->arg_size() == 3)
|
||||
+ Calls.push_back(Call);
|
||||
+ if (Calls.empty())
|
||||
+ return 0;
|
||||
+
|
||||
+ struct Site
|
||||
+ {
|
||||
+ CallInst *Call;
|
||||
+ bool HasC;
|
||||
+ RegisterPair C;
|
||||
+ bool HasD;
|
||||
+ RegisterPair D;
|
||||
+ StoreInst *DStores[2];
|
||||
+ };
|
||||
+ SmallVector<Site, 64> Sites;
|
||||
+ for (CallInst *Call : Calls)
|
||||
+ {
|
||||
+ Site S{Call, false, {}, false, {}, {nullptr, nullptr}};
|
||||
+ S.HasC = matchLoadedPair(Call->getArgOperand(2), Call, S.C);
|
||||
+ S.HasD = matchStoredPair(Call, S.D, S.DStores);
|
||||
+ Sites.push_back(S);
|
||||
+ }
|
||||
+
|
||||
+ // Accumulator pairs whose registers belong to no other pair are replaced
|
||||
+ // outright by a shadow in WMMA layout.
|
||||
+ SmallVector<RegisterPair, 32> Shadowed;
|
||||
+ DenseMap<AllocaInst *, int> Owner; // register -> shadow index, -1 if ambiguous
|
||||
+ auto consider = [&](const RegisterPair &Pair) {
|
||||
+ for (const RegisterPair &Known : Shadowed)
|
||||
+ if (Known == Pair)
|
||||
+ return;
|
||||
+ int Index = static_cast<int>(Shadowed.size());
|
||||
+ for (AllocaInst *Member : {Pair.Low, Pair.High})
|
||||
+ {
|
||||
+ auto Found = Owner.find(Member);
|
||||
+ if (Found != Owner.end())
|
||||
+ Found->second = -1;
|
||||
+ else
|
||||
+ Owner[Member] = Index;
|
||||
+ }
|
||||
+ Shadowed.push_back(Pair);
|
||||
+ };
|
||||
+ for (const Site &S : Sites)
|
||||
+ if (S.HasC)
|
||||
+ consider(S.C);
|
||||
+ auto shadowOf = [&](const RegisterPair &Pair) -> int {
|
||||
+ auto Low = Owner.find(Pair.Low);
|
||||
+ auto High = Owner.find(Pair.High);
|
||||
+ if (Low == Owner.end() || High == Owner.end() || Low->second < 0 || Low->second != High->second)
|
||||
+ return -1;
|
||||
+ return Shadowed[Low->second] == Pair ? Low->second : -1;
|
||||
+ };
|
||||
+
|
||||
+ Type *Pair32 = FixedVectorType::get(Type::getInt32Ty(F.getContext()), 2);
|
||||
+ SmallVector<AllocaInst *, 32> Shadows;
|
||||
+ {
|
||||
+ IRBuilder<> Builder(&*F.getEntryBlock().getFirstInsertionPt());
|
||||
+ for (size_t Index = 0; Index < Shadowed.size(); ++Index)
|
||||
+ Shadows.push_back(
|
||||
+ shadowOf(Shadowed[Index]) == static_cast<int>(Index)
|
||||
+ ? Builder.CreateAlloca(Pair32, Shadowed[Index].Low->getAddressSpace(), nullptr, "wmma.shadow")
|
||||
+ : nullptr);
|
||||
+ }
|
||||
+ auto shadowFor = [&](const RegisterPair &Pair) -> AllocaInst * {
|
||||
+ int Index = shadowOf(Pair);
|
||||
+ return Index >= 0 ? Shadows[Index] : nullptr;
|
||||
+ };
|
||||
+
|
||||
+ // Snapshot every access to shadowed registers before rewriting anything.
|
||||
+ SmallVector<LoadInst *, 64> Loads;
|
||||
+ SmallVector<StoreInst *, 64> Stores;
|
||||
+ for (auto &[Register, Index] : Owner)
|
||||
+ {
|
||||
+ if (Index < 0 || Shadows[Index] == nullptr)
|
||||
+ continue;
|
||||
+ for (User *U : Register->users())
|
||||
+ {
|
||||
+ if (auto *Load = dyn_cast<LoadInst>(U))
|
||||
+ Loads.push_back(Load);
|
||||
+ else
|
||||
+ Stores.push_back(cast<StoreInst>(U));
|
||||
+ }
|
||||
+ }
|
||||
+ DenseSet<StoreInst *> ResultStores; // MMA result stores replaced by shadow stores
|
||||
+
|
||||
+ auto accumulator = [&](const Site &S, IRBuilder<> &Builder) -> Value * {
|
||||
+ if (S.HasC)
|
||||
+ if (AllocaInst *Shadow = shadowFor(S.C))
|
||||
+ return Builder.CreateLoad(Pair32, Shadow);
|
||||
+ return Builder.CreateCall(H.FromNv, {S.Call->getArgOperand(2)});
|
||||
+ };
|
||||
+ auto finish = [&](const Site &S, Value *D, IRBuilder<> &Builder) {
|
||||
+ if (S.HasD)
|
||||
+ if (AllocaInst *Shadow = shadowFor(S.D))
|
||||
+ {
|
||||
+ // The result goes to the shadow where its register stores were.
|
||||
+ StoreInst *Last = S.DStores[0]->comesBefore(S.DStores[1]) ? S.DStores[1] : S.DStores[0];
|
||||
+ IRBuilder<> At(Last);
|
||||
+ At.CreateStore(D, Shadow);
|
||||
+ ResultStores.insert(S.DStores[0]);
|
||||
+ ResultStores.insert(S.DStores[1]);
|
||||
+ }
|
||||
+ return Builder.CreateCall(H.ToNv, {D});
|
||||
+ };
|
||||
+ // Two MMAs that share A (same registers, no store in between to anything
|
||||
+ // the second one reads) run as one 16x16x16 WMMA placed at the first.
|
||||
+ auto pairable = [&](const Site &First, const Site &Second) {
|
||||
+ SmallVector<AllocaInst *, 4> AFirst, ASecond, BSecond;
|
||||
+ if (First.Call->getParent() != Second.Call->getParent() || !Second.HasC ||
|
||||
+ shadowFor(Second.C) == nullptr || !First.Call->comesBefore(Second.Call) ||
|
||||
+ !matchLoadedVector(First.Call->getArgOperand(0), 4, AFirst) ||
|
||||
+ !matchLoadedVector(Second.Call->getArgOperand(0), 4, ASecond) || AFirst != ASecond ||
|
||||
+ !matchLoadedVector(Second.Call->getArgOperand(1), 2, BSecond))
|
||||
+ return false;
|
||||
+ // The second B is reloaded at the first MMA, so it must not live in a
|
||||
+ // shadow (those registers are no longer kept up to date).
|
||||
+ for (AllocaInst *Register : BSecond)
|
||||
+ {
|
||||
+ auto Found = Owner.find(Register);
|
||||
+ if (Found != Owner.end() && Found->second >= 0 && Shadows[Found->second] != nullptr)
|
||||
+ return false;
|
||||
+ }
|
||||
+ SmallVector<AllocaInst *, 8> Read(AFirst.begin(), AFirst.end());
|
||||
+ Read.append(BSecond.begin(), BSecond.end());
|
||||
+ Read.push_back(Second.C.Low);
|
||||
+ Read.push_back(Second.C.High);
|
||||
+ return !storesBetween(First.Call, Second.Call, Read);
|
||||
+ };
|
||||
+ for (size_t Index = 0; Index < Sites.size(); ++Index)
|
||||
+ {
|
||||
+ const Site &S = Sites[Index];
|
||||
+ CallInst *Call = S.Call;
|
||||
+ IRBuilder<> Builder(Call);
|
||||
+ Value *BTransposed = Builder.CreateCall(H.BFromA, {Call->getArgOperand(0)});
|
||||
+ if (Index + 1 < Sites.size() && pairable(S, Sites[Index + 1]))
|
||||
+ {
|
||||
+ const Site &Next = Sites[Index + 1];
|
||||
+ SmallVector<AllocaInst *, 2> BNext;
|
||||
+ matchLoadedVector(Next.Call->getArgOperand(1), 2, BNext);
|
||||
+ Value *B1 = loadPair(Builder, RegisterPair{BNext[0], BNext[1]});
|
||||
+ Value *ATransposed = Builder.CreateCall(H.AFromBPair, {Call->getArgOperand(1), B1});
|
||||
+ Value *C0 = accumulator(S, Builder);
|
||||
+ Value *C1 = accumulator(Next, Builder);
|
||||
+ Value *D = Builder.CreateCall(H.MmaPair, {ATransposed, BTransposed, C0, C1});
|
||||
+ Value *D0 = Builder.CreateShuffleVector(D, ArrayRef<int>{0, 1});
|
||||
+ Value *D1 = Builder.CreateShuffleVector(D, ArrayRef<int>{2, 3});
|
||||
+ Call->replaceAllUsesWith(finish(S, D0, Builder));
|
||||
+ Next.Call->replaceAllUsesWith(finish(Next, D1, Builder));
|
||||
+ Call->eraseFromParent();
|
||||
+ Next.Call->eraseFromParent();
|
||||
+ ++Index;
|
||||
+ continue;
|
||||
+ }
|
||||
+ Value *ATransposed = Builder.CreateCall(H.AFromB, {Call->getArgOperand(1)});
|
||||
+ Value *D = Builder.CreateCall(H.Mma, {ATransposed, BTransposed, accumulator(S, Builder)});
|
||||
+ Call->replaceAllUsesWith(finish(S, D, Builder));
|
||||
+ Call->eraseFromParent();
|
||||
+ }
|
||||
+
|
||||
+ // Every other access now goes through the shadow.
|
||||
+ for (LoadInst *Load : Loads)
|
||||
+ {
|
||||
+ auto *Register = cast<AllocaInst>(Load->getPointerOperand());
|
||||
+ const RegisterPair &Pair = Shadowed[Owner[Register]];
|
||||
+ IRBuilder<> Builder(Load);
|
||||
+ Value *Nv = Builder.CreateCall(H.ToNv, {Builder.CreateLoad(Pair32, shadowFor(Pair))});
|
||||
+ Load->replaceAllUsesWith(Builder.CreateExtractElement(Nv, Builder.getInt8(Register == Pair.High)));
|
||||
+ Load->eraseFromParent();
|
||||
+ }
|
||||
+ for (StoreInst *Store : Stores)
|
||||
+ {
|
||||
+ if (!ResultStores.contains(Store))
|
||||
+ {
|
||||
+ auto *Register = cast<AllocaInst>(Store->getPointerOperand());
|
||||
+ const RegisterPair &Pair = Shadowed[Owner[Register]];
|
||||
+ AllocaInst *Shadow = shadowFor(Pair);
|
||||
+ IRBuilder<> Builder(Store);
|
||||
+ Value *Nv = Builder.CreateCall(H.ToNv, {Builder.CreateLoad(Pair32, Shadow)});
|
||||
+ Nv = Builder.CreateInsertElement(Nv, Store->getValueOperand(), Builder.getInt8(Register == Pair.High));
|
||||
+ Builder.CreateStore(Builder.CreateCall(H.FromNv, {Nv}), Shadow);
|
||||
+ }
|
||||
+ auto *Operand = dyn_cast<Instruction>(Store->getValueOperand());
|
||||
+ Store->eraseFromParent();
|
||||
+ if (Operand != nullptr && Operand->use_empty() && !Operand->mayHaveSideEffects())
|
||||
+ Operand->eraseFromParent();
|
||||
+ }
|
||||
+ return Calls.size();
|
||||
+}
|
||||
+} // namespace
|
||||
+
|
||||
+unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef)
|
||||
+{
|
||||
+ Module *M = unwrap(ModuleRef);
|
||||
+ Function *Mma = M->getFunction(kMmaF16);
|
||||
+ if (Mma == nullptr || Mma->use_empty())
|
||||
+ return 0;
|
||||
+ LLVMContext &Context = M->getContext();
|
||||
+ Type *I32 = Type::getInt32Ty(Context);
|
||||
+ Type *V2 = FixedVectorType::get(I32, 2);
|
||||
+ Type *V4 = FixedVectorType::get(I32, 4);
|
||||
+ Type *V8 = FixedVectorType::get(I32, 8);
|
||||
+ AttributeList Attributes = AttributeList::get(Context, AttributeList::FunctionIndex,
|
||||
+ AttrBuilder(Context, Mma->getAttributes().getFnAttrs()));
|
||||
+ Helpers H;
|
||||
+ H.AFromB = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_a_from_b", Attributes, V8, V2);
|
||||
+ H.BFromA = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_b_from_a", Attributes, V8, V4);
|
||||
+ H.Mma = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_mma_f16", 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("__zluda_ptx_impl_d4r_wmma_t_a_from_b_pair", Attributes, V8, V2, V2);
|
||||
+ H.MmaPair = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_mma_f16_pair", Attributes, V4, V8, V8, V2, V2);
|
||||
+ unsigned Rewritten = 0;
|
||||
+ for (Function &F : *M)
|
||||
+ if (!F.isDeclaration())
|
||||
+ Rewritten += rewriteFunction(F, Mma, H);
|
||||
+ return Rewritten;
|
||||
+}
|
||||
+
|
||||
bool LLVMZludaParseCommandLineOptions(int argc, const char *const *argv,
|
||||
char **ErrorMessage)
|
||||
{
|
||||
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
|
||||
index 69d1faa..c11e06a 100644
|
||||
--- a/ptx/lib/zluda_ptx_impl.cpp
|
||||
+++ b/ptx/lib/zluda_ptx_impl.cpp
|
||||
@@ -1235,6 +1235,184 @@ extern "C"
|
||||
}
|
||||
}
|
||||
|
||||
+// gfx11 WMMA lowering of mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16.
|
||||
+//
|
||||
+// llvm_zluda's MMA rewrite (LLVMZludaRewriteMmaF16Wmma) replaces each such MMA
|
||||
+// with these helpers. It computes D^T = B^T A^T + C^T with
|
||||
+// v_wmma_f32_16x16x16_f16, so the 16x8 accumulator occupies WMMA result
|
||||
+// rows 0..7, i.e. VGPRs 0..3 of the f32 result: lane l holds D[m][n] for
|
||||
+// m = l % 16 and n = 2v + l / 16, v = 0..3 (the "transposed WMMA layout").
|
||||
+// Those four values are kept as two f16x2 words (v0 | v1 << 16,
|
||||
+// v2 | v3 << 16), the same register footprint as NVIDIA's own fragment.
|
||||
+// Converting between the two layouts is pure 16-bit data movement, so the
|
||||
+// rewrite keeps accumulators in WMMA layout across chained MMAs and converts
|
||||
+// only where other code reads or writes them. Each MMA still rounds its result
|
||||
+// to f16, as the .f16 accumulator requires; only the f32 summation order
|
||||
+// within one MMA differs from the dot-product fallback.
|
||||
+typedef _Float16 wmma_half16 __attribute__((ext_vector_type(16)));
|
||||
+typedef float wmma_float8 __attribute__((ext_vector_type(8)));
|
||||
+typedef uint32_t wmma_uint8 __attribute__((ext_vector_type(8)));
|
||||
+__device__ wmma_float8 __zluda_wmma_f32_16x16x16_f16(wmma_half16 a, wmma_half16 b, wmma_float8 c) __asm("llvm.amdgcn.wmma.f32.16x16x16.f16.v8f32.v16f16");
|
||||
+
|
||||
+__device__ static uint32_t wmma_lane()
|
||||
+{
|
||||
+ return __builtin_amdgcn_mbcnt_lo(~0u, 0u);
|
||||
+}
|
||||
+
|
||||
+__device__ static uint32_t wmma_from_lane(uint32_t lane, uint32_t value)
|
||||
+{
|
||||
+ return uint32_t(__builtin_amdgcn_ds_bpermute(int(lane << 2), int(value)));
|
||||
+}
|
||||
+
|
||||
+// Gathers WMMA operand dword q for every lane from two NVIDIA fragment
|
||||
+// registers: rows r < 8 take `low` from lane 4r+q, rows r >= 8 take `high`
|
||||
+// from lane 4(r-8)+q (r = lane % 16). Swapping `high` between quad halves
|
||||
+// (DPP quad_perm [2,3,0,1]) parks the value rows r >= 8 need in lane
|
||||
+// 4(r-8)+(q^2), so one bpermute serves both halves.
|
||||
+__device__ static uint32_t wmma_gather_rows(uint32_t low, uint32_t high_swapped, uint32_t q)
|
||||
+{
|
||||
+ uint32_t lane = wmma_lane();
|
||||
+ uint32_t row = lane % 16;
|
||||
+ uint32_t source = lane % 4 == q ? low : high_swapped;
|
||||
+ uint32_t from = row < 8 ? row * 4 + q : (row - 8) * 4 + (q ^ 2);
|
||||
+ return wmma_from_lane(from, source);
|
||||
+}
|
||||
+
|
||||
+__device__ static uint32_t wmma_swap_quad_halves(uint32_t value)
|
||||
+{
|
||||
+ return uint32_t(__builtin_amdgcn_mov_dpp(int(value), 0b01'00'11'10, 0xf, 0xf, true));
|
||||
+}
|
||||
+
|
||||
+extern "C"
|
||||
+{
|
||||
+ // WMMA A operand (lane l holds row l % 16 over k) = B^T, from an NVIDIA B
|
||||
+ // fragment (lane 4n+q: B[2q..2q+1][n] in .x, B[2q+8..2q+9][n] in .y).
|
||||
+ // Rows 8..15 (padding columns of the 16x8 B) are zero.
|
||||
+ wmma_uint8 FUNC(d4r_wmma_t_a_from_b)(uint2::Native_vec_ b)
|
||||
+ {
|
||||
+ // Keep the scheduler from hoisting these shuffles (and the second
|
||||
+ // candidate of each select) far ahead of the WMMA that consumes them,
|
||||
+ // which otherwise spills them to scratch.
|
||||
+ __builtin_amdgcn_sched_barrier(0);
|
||||
+ uint32_t n = wmma_lane() % 16;
|
||||
+ uint32_t source = (n % 8) * 4;
|
||||
+ wmma_uint8 result;
|
||||
+ for (uint32_t q = 0; q < 4; q++)
|
||||
+ {
|
||||
+ uint32_t low = wmma_from_lane(source + q, b.x);
|
||||
+ uint32_t high = wmma_from_lane(source + q, b.y);
|
||||
+ result[q] = n < 8 ? low : 0u;
|
||||
+ result[q + 4] = n < 8 ? high : 0u;
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ // WMMA B operand (lane l holds column l % 16 over k) = A^T, i.e. row
|
||||
+ // m = l % 16 of the NVIDIA A fragment (lane 4g+t: rows g/g+8, k 2t..2t+1 in
|
||||
+ // .x/.y and k 2t+8..2t+9 in .z/.w).
|
||||
+ wmma_uint8 FUNC(d4r_wmma_t_b_from_a)(uint4::Native_vec_ a)
|
||||
+ {
|
||||
+ // Keep the scheduler from hoisting these shuffles (and the second
|
||||
+ // candidate of each select) far ahead of the WMMA that consumes them,
|
||||
+ // which otherwise spills them to scratch.
|
||||
+ __builtin_amdgcn_sched_barrier(0);
|
||||
+ uint32_t ay = wmma_swap_quad_halves(a.y);
|
||||
+ uint32_t aw = wmma_swap_quad_halves(a.w);
|
||||
+ wmma_uint8 result;
|
||||
+ for (uint32_t p = 0; p < 4; p++)
|
||||
+ {
|
||||
+ result[p] = wmma_gather_rows(a.x, ay, p);
|
||||
+ result[p + 4] = wmma_gather_rows(a.z, aw, p);
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ // Paired form: the WMMA A operand rows 0..7 are B0^T and rows 8..15 are
|
||||
+ // B1^T, so one WMMA computes two m16n8k16 MMAs that share A.
|
||||
+ wmma_uint8 FUNC(d4r_wmma_t_a_from_b_pair)(uint2::Native_vec_ b0, uint2::Native_vec_ b1)
|
||||
+ {
|
||||
+ // Keep the scheduler from hoisting these shuffles (and the second
|
||||
+ // candidate of each select) far ahead of the WMMA that consumes them,
|
||||
+ // which otherwise spills them to scratch.
|
||||
+ __builtin_amdgcn_sched_barrier(0);
|
||||
+ uint32_t b1x = wmma_swap_quad_halves(b1.x);
|
||||
+ uint32_t b1y = wmma_swap_quad_halves(b1.y);
|
||||
+ wmma_uint8 result;
|
||||
+ for (uint32_t q = 0; q < 4; q++)
|
||||
+ {
|
||||
+ result[q] = wmma_gather_rows(b0.x, b1x, q);
|
||||
+ result[q + 4] = wmma_gather_rows(b0.y, b1y, q);
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ // Two accumulators (for B0 and B1) in transposed WMMA layout: .xy is the
|
||||
+ // first, .zw the second; they are WMMA result VGPRs 0..3 and 4..7.
|
||||
+ uint4::Native_vec_ FUNC(d4r_wmma_t_mma_f16_pair)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c0_t, uint2::Native_vec_ c1_t)
|
||||
+ {
|
||||
+ f16x2 c01 = std::bit_cast<f16x2>(c0_t.x);
|
||||
+ f16x2 c23 = std::bit_cast<f16x2>(c0_t.y);
|
||||
+ f16x2 c45 = std::bit_cast<f16x2>(c1_t.x);
|
||||
+ f16x2 c67 = std::bit_cast<f16x2>(c1_t.y);
|
||||
+ wmma_float8 c = {float(c01.x), float(c01.y), float(c23.x), float(c23.y),
|
||||
+ float(c45.x), float(c45.y), float(c67.x), float(c67.y)};
|
||||
+ wmma_float8 d = __zluda_wmma_f32_16x16x16_f16(std::bit_cast<wmma_half16>(a_t), std::bit_cast<wmma_half16>(b_t), c);
|
||||
+ f16x2 d01{static_cast<f16>(d[0]), static_cast<f16>(d[1])};
|
||||
+ f16x2 d23{static_cast<f16>(d[2]), static_cast<f16>(d[3])};
|
||||
+ f16x2 d45{static_cast<f16>(d[4]), static_cast<f16>(d[5])};
|
||||
+ f16x2 d67{static_cast<f16>(d[6]), static_cast<f16>(d[7])};
|
||||
+ return {std::bit_cast<uint32_t>(d01), std::bit_cast<uint32_t>(d23),
|
||||
+ std::bit_cast<uint32_t>(d45), std::bit_cast<uint32_t>(d67)};
|
||||
+ }
|
||||
+
|
||||
+ // D = round_f16(A B + C) with C and D in the transposed WMMA layout.
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma_t_mma_f16)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c_t)
|
||||
+ {
|
||||
+ f16x2 c01 = std::bit_cast<f16x2>(c_t.x);
|
||||
+ f16x2 c23 = std::bit_cast<f16x2>(c_t.y);
|
||||
+ wmma_float8 c = {float(c01.x), float(c01.y), float(c23.x), float(c23.y), 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
+ wmma_float8 d = __zluda_wmma_f32_16x16x16_f16(std::bit_cast<wmma_half16>(a_t), std::bit_cast<wmma_half16>(b_t), c);
|
||||
+ f16x2 d01{static_cast<f16>(d[0]), static_cast<f16>(d[1])};
|
||||
+ f16x2 d23{static_cast<f16>(d[2]), static_cast<f16>(d[3])};
|
||||
+ return {std::bit_cast<uint32_t>(d01), std::bit_cast<uint32_t>(d23)};
|
||||
+ }
|
||||
+
|
||||
+ // NVIDIA accumulator fragment (lane 4g+t: D[g][2t..2t+1] in .x,
|
||||
+ // D[g+8][2t..2t+1] in .y) -> transposed WMMA layout.
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma_t_from_nv)(uint2::Native_vec_ c)
|
||||
+ {
|
||||
+ uint32_t lane = wmma_lane();
|
||||
+ uint32_t m = lane % 16;
|
||||
+ uint32_t shift = (lane / 16) * 16;
|
||||
+ uint32_t source = (m % 8) * 4;
|
||||
+ uint32_t halves[4];
|
||||
+ for (uint32_t v = 0; v < 4; v++)
|
||||
+ {
|
||||
+ uint32_t low_rows = wmma_from_lane(source + v, c.x);
|
||||
+ uint32_t high_rows = wmma_from_lane(source + v, c.y);
|
||||
+ halves[v] = ((m >= 8 ? high_rows : low_rows) >> shift) & 0xffffu;
|
||||
+ }
|
||||
+ return {halves[0] | (halves[1] << 16), halves[2] | (halves[3] << 16)};
|
||||
+ }
|
||||
+
|
||||
+ // Transposed WMMA layout -> NVIDIA accumulator fragment.
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma_t_to_nv)(uint2::Native_vec_ d)
|
||||
+ {
|
||||
+ uint32_t lane = wmma_lane();
|
||||
+ uint32_t g = lane / 4;
|
||||
+ uint32_t t = lane % 4;
|
||||
+ uint32_t sources[4] = {g, g + 16, g + 8, g + 24}; // D[g][2t], D[g][2t+1], D[g+8][2t], D[g+8][2t+1]
|
||||
+ uint32_t halves[4];
|
||||
+ for (uint32_t i = 0; i < 4; i++)
|
||||
+ {
|
||||
+ uint32_t v01 = wmma_from_lane(sources[i], d.x);
|
||||
+ uint32_t v23 = wmma_from_lane(sources[i], d.y);
|
||||
+ halves[i] = ((t >= 2 ? v23 : v01) >> ((t % 2) * 16)) & 0xffffu;
|
||||
+ }
|
||||
+ return {halves[0] | (halves[1] << 16), halves[2] | (halves[3] << 16)};
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
template <typename T, typename A, typename B, int BOffset, typename OcklFunc>
|
||||
__device__ T dp2a_impl(T a, T b, T c, OcklFunc ockl_func)
|
||||
{
|
||||
diff --git a/zluda_common/examples/d4r_fatbin_ptx.rs b/zluda_common/examples/d4r_fatbin_ptx.rs
|
||||
new file mode 100644
|
||||
index 0000000..50f7a09
|
||||
--- /dev/null
|
||||
+++ b/zluda_common/examples/d4r_fatbin_ptx.rs
|
||||
@@ -0,0 +1,36 @@
|
||||
+// Extracts (and decompresses) every PTX file from captured CUDA fatbin images.
|
||||
+// usage: d4r_fatbin_ptx <outdir> <image>...
|
||||
+use cuda_types::dark_api::FatbinFileHeader;
|
||||
+use std::{env, fs, path::PathBuf};
|
||||
+use zluda_common::{CodeLibraryRef, CodeModuleRef};
|
||||
+
|
||||
+fn main() {
|
||||
+ let mut args = env::args().skip(1);
|
||||
+ let outdir = PathBuf::from(args.next().expect("output directory"));
|
||||
+ fs::create_dir_all(&outdir).expect("create output directory");
|
||||
+ for path in args {
|
||||
+ let mut bytes = fs::read(&path).expect("read image");
|
||||
+ bytes.extend_from_slice(&[0; 16]);
|
||||
+ let stem = PathBuf::from(&path).file_stem().unwrap().to_string_lossy().into_owned();
|
||||
+ let library = unsafe { CodeLibraryRef::try_load(bytes.as_ptr().cast()) }.expect("load image");
|
||||
+ let mut count = 0;
|
||||
+ unsafe {
|
||||
+ library.iterate_modules(|_, module| {
|
||||
+ let text = match module {
|
||||
+ Ok(CodeModuleRef::Text(ptx)) => Some(ptx.to_string()),
|
||||
+ Ok(CodeModuleRef::File(file)) if file.header.kind == FatbinFileHeader::HEADER_KIND_PTX => file
|
||||
+ .get_or_decompress_content(true)
|
||||
+ .ok()
|
||||
+ .map(|content| String::from_utf8_lossy(&content).trim_end_matches('\0').to_string()),
|
||||
+ _ => None,
|
||||
+ };
|
||||
+ if let Some(text) = text {
|
||||
+ let name = outdir.join(format!("{stem}-{count:02}.ptx"));
|
||||
+ fs::write(&name, text).expect("write PTX");
|
||||
+ count += 1;
|
||||
+ }
|
||||
+ })
|
||||
+ };
|
||||
+ println!("{path}: {count} PTX modules");
|
||||
+ }
|
||||
+}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,40 +0,0 @@
|
||||
From: d4r
|
||||
Subject: [PATCH] null texture objects read as zero
|
||||
|
||||
CUDA texture object 0 reads as zeros on NVIDIA GPUs, and DLSS samples absent optional inputs
|
||||
(its exposure scales, with the flags Ghost of Tsushima uses) that way. A ZLUDA texture object is a
|
||||
descriptor pointer, so handle 0 made the GPU read address 0 and fault. Handle 0 now points at an
|
||||
all-zero descriptor, which AMD hardware treats as a null resource. Rebuild ptx/lib/*.bc after
|
||||
applying (scripts/build_zluda_ptx_helpers.sh).
|
||||
---
|
||||
--- a/ptx/lib/zluda_ptx_impl.cpp 2026-09-27 16:47:02.815623944 -0400
|
||||
+++ b/ptx/lib/zluda_ptx_impl.cpp 2026-09-27 16:47:02.815623944 -0400
|
||||
@@ -1725,9 +1725,18 @@
|
||||
c;
|
||||
}
|
||||
|
||||
+ // d4r: CUDA texture object 0 reads as zeros on NVIDIA GPUs, and DLSS samples absent optional inputs
|
||||
+ // (exposure scales) that way. A ZLUDA texture object is a descriptor pointer, so 0 is redirected to
|
||||
+ // an all-zero descriptor, which AMD hardware treats as a null resource (reads return 0).
|
||||
+ __device__ static unsigned int d4r_null_texture[32];
|
||||
+ static uint64_t texture_or_null(uint64_t texobj) __device__
|
||||
+ {
|
||||
+ return texobj != 0 ? texobj : uint64_t(uintptr_t(d4r_null_texture));
|
||||
+ }
|
||||
+
|
||||
static std::pair<GLOBAL_SPACE void *, GLOBAL_SPACE void *> get_image_and_sampler(uint64_t texobj) __device__
|
||||
{
|
||||
- unsigned int GLOBAL_SPACE *image = (unsigned int GLOBAL_SPACE *)texobj;
|
||||
+ unsigned int GLOBAL_SPACE *image = (unsigned int GLOBAL_SPACE *)texture_or_null(texobj);
|
||||
unsigned int GLOBAL_SPACE *sampler = image + HIP_SAMPLER_OBJECT_OFFSET_DWORD;
|
||||
return {image, sampler};
|
||||
}
|
||||
@@ -1868,7 +1877,7 @@
|
||||
#define tex_2d_gather4(COMPONENT) \
|
||||
v4f32 FUNC(texobj_gather4##COMPONENT##_2d_v4_f32_f32)(uint64_t texobj, v2f32 coord) \
|
||||
{ \
|
||||
- auto image = reinterpret_cast<unsigned int CONSTANT_SPACE *>(static_cast<uintptr_t>(texobj)); \
|
||||
+ auto image = reinterpret_cast<unsigned int CONSTANT_SPACE *>(static_cast<uintptr_t>(texture_or_null(texobj))); \
|
||||
auto sampler = image + HIP_SAMPLER_OBJECT_OFFSET_DWORD; \
|
||||
float2 coordinates{coord.x, coord.y}; \
|
||||
auto result = __ockl_image_gather4##COMPONENT##_2D(image, sampler, get_native_vector(coordinates)); \
|
||||
@@ -1,444 +0,0 @@
|
||||
diff --git a/llvm_zluda/src/compile.rs b/llvm_zluda/src/compile.rs
|
||||
index 5d8c0a5..b2870e5 100644
|
||||
--- a/llvm_zluda/src/compile.rs
|
||||
+++ b/llvm_zluda/src/compile.rs
|
||||
@@ -70,8 +70,10 @@ fn create_oclc_constants(ctx: &Context, gcn_arch: &str) -> Result<Module, String
|
||||
let module = Module::new(ctx, c"oclc_constants");
|
||||
|
||||
// used by ockl
|
||||
- add_constant(ctx, &module, c"__oclc_wavefrontsize64", 0);
|
||||
- add_constant(ctx, &module, c"__oclc_wavefrontsize_log2", 5);
|
||||
+ // d4r: D4R_ZLUDA_WAVE64=1 (see ptx::pass::wave64_requested)
|
||||
+ let wave64 = std::env::var("D4R_ZLUDA_WAVE64").map_or(false, |v| v.trim() == "1");
|
||||
+ add_constant(ctx, &module, c"__oclc_wavefrontsize64", if wave64 { 1 } else { 0 });
|
||||
+ add_constant(ctx, &module, c"__oclc_wavefrontsize_log2", if wave64 { 6 } else { 5 });
|
||||
add_constant(ctx, &module, c"__oclc_ABI_version", 500);
|
||||
add_constant(
|
||||
ctx,
|
||||
@@ -174,7 +176,13 @@ pub fn compile(
|
||||
|
||||
let triple = c"amdgcn-amd-amdhsa";
|
||||
let cpu = std::ffi::CString::new(gcn_arch).map_err(|_| ("invalid gcn_arch").to_string())?;
|
||||
- let features = c"-wavefrontsize64,+cumode";
|
||||
+ // functions without their own target features (the linked helper libraries) take these, so under
|
||||
+ // D4R_ZLUDA_WAVE64 they must be optimised as wave64 too (lane-id and ballot folding)
|
||||
+ let features = if std::env::var("D4R_ZLUDA_WAVE64").map_or(false, |v| v.trim() == "1") {
|
||||
+ c"-wavefrontsize32,+wavefrontsize64,+cumode"
|
||||
+ } else {
|
||||
+ c"-wavefrontsize64,+cumode"
|
||||
+ };
|
||||
|
||||
let mut target = unsafe { std::mem::zeroed() };
|
||||
let mut err = std::ptr::null_mut();
|
||||
diff --git a/llvm_zluda/src/lib.cpp b/llvm_zluda/src/lib.cpp
|
||||
index 88848b1..0d1f9cf 100644
|
||||
--- a/llvm_zluda/src/lib.cpp
|
||||
+++ b/llvm_zluda/src/lib.cpp
|
||||
@@ -869,10 +869,111 @@ unsigned LLVMZludaSplitWmmaM16n16k16(LLVMModuleRef ModuleRef)
|
||||
return static_cast<unsigned>(Calls.size());
|
||||
}
|
||||
|
||||
+// d4r: an m16n8k8 f16 MMA is an m16n8k16 one whose second K half is zero (its A fragment is the k16
|
||||
+// A's registers 0-1, its B the k16 B's register 0), and adding zero products is exact. Each k8 call
|
||||
+// becomes a k16 call so the WMMA rewrite below lowers it too. Zero halves are loads of one zeroed
|
||||
+// register, so the operands stay "loads of plain registers" and two k8 MMAs that share A still fuse
|
||||
+// into one WMMA. Returns the number of calls converted.
|
||||
+static unsigned widenMmaK8(Module *M)
|
||||
+{
|
||||
+ Function *K8 = M->getFunction("__zluda_ptx_impl_mma_sync_aligned_m16n8k8_row_col_f16_f16_f16_f16");
|
||||
+ if (K8 == nullptr || K8->use_empty())
|
||||
+ return 0;
|
||||
+ LLVMContext &Context = M->getContext();
|
||||
+ Type *I32 = Type::getInt32Ty(Context);
|
||||
+ Type *V2 = FixedVectorType::get(I32, 2);
|
||||
+ Type *V4 = FixedVectorType::get(I32, 4);
|
||||
+ AttributeList Attributes = AttributeList::get(Context, AttributeList::FunctionIndex,
|
||||
+ AttrBuilder(Context, K8->getAttributes().getFnAttrs()));
|
||||
+ FunctionCallee K16 = M->getOrInsertFunction(kMmaF16, Attributes, V2, V4, V2, V2);
|
||||
+ SmallVector<CallInst *, 64> Calls;
|
||||
+ for (User *U : K8->users())
|
||||
+ if (auto *Call = dyn_cast<CallInst>(U); Call != nullptr && Call->getCalledFunction() == K8)
|
||||
+ Calls.push_back(Call);
|
||||
+ DenseMap<Function *, AllocaInst *> Zeros;
|
||||
+ for (CallInst *Call : Calls)
|
||||
+ {
|
||||
+ Function *F = Call->getFunction();
|
||||
+ AllocaInst *&Zero = Zeros[F];
|
||||
+ if (Zero == nullptr)
|
||||
+ {
|
||||
+ // an alloca like ZLUDA's register allocas (same address space), stored once at entry
|
||||
+ AllocaInst *Like = nullptr;
|
||||
+ for (Instruction &I : F->getEntryBlock())
|
||||
+ if ((Like = dyn_cast<AllocaInst>(&I)) != nullptr)
|
||||
+ break;
|
||||
+ IRBuilder<> Entry(&*F->getEntryBlock().getFirstInsertionPt());
|
||||
+ Zero = Entry.CreateAlloca(I32, Like != nullptr ? Like->getAddressSpace() : 5, nullptr, "d4r.k8.zero");
|
||||
+ Entry.CreateStore(Entry.getInt32(0), Zero);
|
||||
+ }
|
||||
+ IRBuilder<> Builder(Call);
|
||||
+ SmallVector<AllocaInst *, 2> ARegisters, BRegisters;
|
||||
+ auto build = [&](ArrayRef<Value *> Elements) {
|
||||
+ Value *Vector = UndefValue::get(FixedVectorType::get(I32, Elements.size()));
|
||||
+ for (unsigned Index = 0; Index < Elements.size(); ++Index)
|
||||
+ Vector = Builder.CreateInsertElement(Vector, Elements[Index], Builder.getInt8(Index));
|
||||
+ return Vector;
|
||||
+ };
|
||||
+ auto zero = [&]() { return Builder.CreateLoad(I32, Zero); };
|
||||
+ // the registers may be reloaded at the call only if no store reaches them after their original loads
|
||||
+ auto unchangedSinceLoads = [&](Value *Vector) {
|
||||
+ for (Value *V = Vector; auto *Insert = dyn_cast<InsertElementInst>(V); V = Insert->getOperand(0))
|
||||
+ {
|
||||
+ auto *Load = cast<LoadInst>(Insert->getOperand(1));
|
||||
+ if (Load->getParent() != Call->getParent() ||
|
||||
+ storesBetween(Load, Call, {cast<AllocaInst>(Load->getPointerOperand())}))
|
||||
+ return false;
|
||||
+ }
|
||||
+ return true;
|
||||
+ };
|
||||
+ // B is one register: a <1 x i32> vector or a plain i32
|
||||
+ Value *BArg = Call->getArgOperand(1);
|
||||
+ auto bRegister = [&]() {
|
||||
+ if (BArg->getType()->isVectorTy())
|
||||
+ return matchLoadedVector(BArg, 1, BRegisters) && unchangedSinceLoads(BArg);
|
||||
+ auto *Load = dyn_cast<LoadInst>(BArg);
|
||||
+ auto *Register = Load != nullptr ? dyn_cast<AllocaInst>(Load->getPointerOperand()) : nullptr;
|
||||
+ if (Register == nullptr || !Register->getAllocatedType()->isIntegerTy(32) || !isPlainRegister(Register) ||
|
||||
+ Load->getParent() != Call->getParent() || storesBetween(Load, Call, {Register}))
|
||||
+ return false;
|
||||
+ BRegisters.assign(1, Register);
|
||||
+ return true;
|
||||
+ };
|
||||
+ auto element = [&](Value *V, unsigned Index) -> Value * {
|
||||
+ return V->getType()->isVectorTy() ? Builder.CreateExtractElement(V, Builder.getInt8(Index)) : V;
|
||||
+ };
|
||||
+ Value *A, *B;
|
||||
+ if (matchLoadedVector(Call->getArgOperand(0), 2, ARegisters) && bRegister() &&
|
||||
+ unchangedSinceLoads(Call->getArgOperand(0)))
|
||||
+ {
|
||||
+ // reload the same registers right at the call (nothing lies between these loads and it)
|
||||
+ Value *A0 = Builder.CreateLoad(I32, ARegisters[0]), *A1 = Builder.CreateLoad(I32, ARegisters[1]);
|
||||
+ Value *B0 = Builder.CreateLoad(I32, BRegisters[0]);
|
||||
+ A = build({A0, A1, zero(), zero()});
|
||||
+ B = build({B0, zero()});
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ Value *A0 = Builder.CreateExtractElement(Call->getArgOperand(0), Builder.getInt8(0));
|
||||
+ Value *A1 = Builder.CreateExtractElement(Call->getArgOperand(0), Builder.getInt8(1));
|
||||
+ Value *B0 = element(BArg, 0);
|
||||
+ A = build({A0, A1, Builder.getInt32(0), Builder.getInt32(0)});
|
||||
+ B = build({B0, Builder.getInt32(0)});
|
||||
+ }
|
||||
+ CallInst *Wide = Builder.CreateCall(K16, {A, B, Call->getArgOperand(2)});
|
||||
+ Call->replaceAllUsesWith(Wide);
|
||||
+ Call->eraseFromParent();
|
||||
+ }
|
||||
+ return static_cast<unsigned>(Calls.size());
|
||||
+}
|
||||
+
|
||||
// Flags bit 0: also lower m16n8k32 e4m3 MMAs (D4R_ZLUDA_WMMA_FP8=1).
|
||||
unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef, unsigned Flags)
|
||||
{
|
||||
Module *M = unwrap(ModuleRef);
|
||||
+ // D4R_ZLUDA_WMMA_K8=0 keeps m16n8k8 on the dot-product fallback
|
||||
+ if (const char *K8Env = getenv("D4R_ZLUDA_WMMA_K8"); K8Env == nullptr || K8Env[0] != '0')
|
||||
+ widenMmaK8(M);
|
||||
Function *Mma = M->getFunction(kMmaF16);
|
||||
Function *MmaE4m3 = (Flags & 1) != 0 ? M->getFunction(kMmaE4m3) : nullptr;
|
||||
if (Mma != nullptr && Mma->use_empty())
|
||||
diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp
|
||||
index cdefdf1..74cf36f 100644
|
||||
--- a/ptx/lib/zluda_ptx_impl.cpp
|
||||
+++ b/ptx/lib/zluda_ptx_impl.cpp
|
||||
@@ -127,9 +127,32 @@ extern "C"
|
||||
{
|
||||
extern "C" __attribute__((constant)) uint32_t __oclc_ISA_version __device__;
|
||||
|
||||
+ // d4r: kernels may be compiled as wave64 (D4R_ZLUDA_WAVE64, offline texture-kernel builds, linked with
|
||||
+ // the zluda_ptx_impl*_w64.bc helpers built with -DD4R_WAVE64), where each wave holds two CUDA warps:
|
||||
+ // lanes 0-31 and 32-63. Warp-level helpers then act on the lane's own 32-lane half.
|
||||
+#ifdef D4R_WAVE64
|
||||
+ __device__ static inline uint32_t d4r_wave_lane()
|
||||
+ {
|
||||
+ return __builtin_amdgcn_mbcnt_hi(~0u, __builtin_amdgcn_mbcnt_lo(~0u, 0u));
|
||||
+ }
|
||||
+ __device__ static inline uint32_t d4r_warp_base()
|
||||
+ {
|
||||
+ return d4r_wave_lane() & 32u;
|
||||
+ }
|
||||
+#else
|
||||
+ __device__ static inline uint32_t d4r_warp_base()
|
||||
+ {
|
||||
+ return 0u;
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
uint32_t FUNC(activemask)()
|
||||
{
|
||||
+#ifdef D4R_WAVE64
|
||||
+ return (uint32_t)(__builtin_amdgcn_read_exec() >> d4r_warp_base());
|
||||
+#else
|
||||
return __builtin_amdgcn_read_exec_lo();
|
||||
+#endif
|
||||
}
|
||||
|
||||
size_t __ockl_get_local_id(uint32_t) __device__;
|
||||
@@ -158,7 +181,11 @@ extern "C"
|
||||
|
||||
uint32_t FUNC(sreg_laneid)()
|
||||
{
|
||||
+#ifdef D4R_WAVE64
|
||||
+ return d4r_wave_lane() & 31u;
|
||||
+#else
|
||||
return __lane_id();
|
||||
+#endif
|
||||
}
|
||||
|
||||
uint32_t FUNC(sreg_lanemask_le)()
|
||||
@@ -322,7 +349,7 @@ extern "C"
|
||||
int32_t section_mask = (opts >> 8) & 0b11111; \
|
||||
int32_t warp_end = opts & 0b11111; \
|
||||
delta &= 0b11111; \
|
||||
- int32_t self = (int32_t)__lane_id(); \
|
||||
+ int32_t self = (int32_t)__zluda_ptx_impl_sreg_laneid(); \
|
||||
int32_t subsection = section_mask & self; \
|
||||
int32_t subsection_end = subsection | (~section_mask & warp_end); \
|
||||
int32_t idx = calculate_index; \
|
||||
@@ -331,7 +358,7 @@ extern "C"
|
||||
{ \
|
||||
idx = self; \
|
||||
} \
|
||||
- int32_t output = __builtin_amdgcn_ds_bpermute(idx << 2, (int32_t)input); \
|
||||
+ int32_t output = __builtin_amdgcn_ds_bpermute((int32_t)(d4r_warp_base() | (uint32_t)idx) << 2, (int32_t)input); \
|
||||
return {(uint32_t)output, uint32_t(!out_of_bounds)}; \
|
||||
} \
|
||||
\
|
||||
@@ -850,7 +877,11 @@ extern "C"
|
||||
__device__ static inline uint32_t ballot(bool value, bool negate)
|
||||
{
|
||||
__builtin_amdgcn_wave_barrier();
|
||||
+#ifdef D4R_WAVE64
|
||||
+ return (uint32_t)(__builtin_amdgcn_ballot_w64(negate ? !value : value) >> d4r_warp_base());
|
||||
+#else
|
||||
return __builtin_amdgcn_ballot_w32(negate ? !value : value);
|
||||
+#endif
|
||||
}
|
||||
|
||||
bool FUNC(vote_sync_any_pred)(bool value, uint32_t membermask __attribute__((unused)))
|
||||
@@ -866,7 +897,7 @@ extern "C"
|
||||
// IMPORTANT: exec mask must be a subset of membermask, the behavior is undefined otherwise
|
||||
bool FUNC(vote_sync_all_pred)(bool value, uint32_t membermask __attribute__((unused)))
|
||||
{
|
||||
- return ballot(value, false) == __builtin_amdgcn_read_exec_lo();
|
||||
+ return ballot(value, false) == __zluda_ptx_impl_activemask();
|
||||
}
|
||||
|
||||
// also known as "none"
|
||||
diff --git a/ptx/src/pass/llvm/emit.rs b/ptx/src/pass/llvm/emit.rs
|
||||
index 8294bb4..6af2495 100644
|
||||
--- a/ptx/src/pass/llvm/emit.rs
|
||||
+++ b/ptx/src/pass/llvm/emit.rs
|
||||
@@ -397,7 +397,12 @@ impl<'a, 'input> ModuleEmitContext<'a, 'input> {
|
||||
|
||||
fn emit_target_features(&mut self, fn_: LLVMValueRef) {
|
||||
let value = format!(
|
||||
- "+wavefrontsize32,-wavefrontsize64,+cumode{}",
|
||||
+ "{},+cumode{}",
|
||||
+ if crate::pass::wave64_requested() {
|
||||
+ "-wavefrontsize32,+wavefrontsize64"
|
||||
+ } else {
|
||||
+ "+wavefrontsize32,-wavefrontsize64"
|
||||
+ },
|
||||
if cfg!(debug_assertions) {
|
||||
",+precise-memory"
|
||||
} else {
|
||||
diff --git a/ptx/src/pass/mod.rs b/ptx/src/pass/mod.rs
|
||||
index 63aa98c..e3baa72 100644
|
||||
--- a/ptx/src/pass/mod.rs
|
||||
+++ b/ptx/src/pass/mod.rs
|
||||
@@ -37,6 +37,18 @@ mod test;
|
||||
static ZLUDA_PTX_IMPL: &'static [u8] = include_bytes!("../../lib/zluda_ptx_impl.bc");
|
||||
static ZLUDA_PTX_IMPL_CONSTRAINED: &'static [u8] =
|
||||
include_bytes!("../../lib/zluda_ptx_impl_constrained.bc");
|
||||
+static ZLUDA_PTX_IMPL_W64: &'static [u8] = include_bytes!("../../lib/zluda_ptx_impl_w64.bc");
|
||||
+static ZLUDA_PTX_IMPL_CONSTRAINED_W64: &'static [u8] =
|
||||
+ include_bytes!("../../lib/zluda_ptx_impl_constrained_w64.bc");
|
||||
+
|
||||
+/// d4r: D4R_ZLUDA_WAVE64=1 compiles kernels as wave64: each wave runs two CUDA warps (lanes 0-31 and
|
||||
+/// 32-63; the warp-level helpers act on the lane's own half) and RDNA3 issues most FP32 VALU work for all
|
||||
+/// 64 lanes at once. Meant for offline builds of texture kernels whose warp operations are limited to
|
||||
+/// lane ids, shuffles and votes; not for MMA/matrix kernels (their helpers assume one warp per wave).
|
||||
+pub fn wave64_requested() -> bool {
|
||||
+ static VALUE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
+ *VALUE.get_or_init(|| std::env::var("D4R_ZLUDA_WAVE64").map_or(false, |v| v.trim() == "1"))
|
||||
+}
|
||||
const ZLUDA_PTX_PREFIX: &'static str = "__zluda_ptx_impl_";
|
||||
|
||||
quick_error! {
|
||||
@@ -194,10 +206,11 @@ pub struct Module {
|
||||
|
||||
impl Module {
|
||||
pub fn linked_bitcode(&self) -> &'static [u8] {
|
||||
- if self.constrained_fp {
|
||||
- ZLUDA_PTX_IMPL_CONSTRAINED
|
||||
- } else {
|
||||
- ZLUDA_PTX_IMPL
|
||||
+ match (self.constrained_fp, wave64_requested()) {
|
||||
+ (true, false) => ZLUDA_PTX_IMPL_CONSTRAINED,
|
||||
+ (false, false) => ZLUDA_PTX_IMPL,
|
||||
+ (true, true) => ZLUDA_PTX_IMPL_CONSTRAINED_W64,
|
||||
+ (false, true) => ZLUDA_PTX_IMPL_W64,
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/zluda/src/impl/function.rs b/zluda/src/impl/function.rs
|
||||
index bacb93d..033d9a0 100644
|
||||
--- a/zluda/src/impl/function.rs
|
||||
+++ b/zluda/src/impl/function.rs
|
||||
@@ -18,9 +18,14 @@ pub(crate) struct Function {
|
||||
/// d4r native override: persistent kernels launch this many workgroups in x (0 = as launched)
|
||||
pub(crate) grid_x: u32,
|
||||
/// d4r native override: byte offset of the u64 parameter that keys the prep kernel's output
|
||||
- /// (0 = run the prep before every launch) and the key of the last prep launched
|
||||
- pub(crate) prep_key: u32,
|
||||
+ /// (None = run the prep before every launch) and the key of the last prep launched
|
||||
+ pub(crate) prep_key: Option<u32>,
|
||||
pub(crate) prep_last: std::sync::atomic::AtomicU64,
|
||||
+ /// d4r native override: with N > 0 prep slots the kernel keeps one prepared image per key for
|
||||
+ /// up to N keys, so the prep runs only for keys not seen yet (and for every key beyond N, which
|
||||
+ /// the kernel prepares into a shared overflow slot). 0 = only the last key is kept.
|
||||
+ pub(crate) prep_slots: u32,
|
||||
+ pub(crate) prep_seen: std::sync::Mutex<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl<'a, E: zluda_common::CudaErrorType> zluda_common::FromCuda<'a, CUfunction, E>
|
||||
@@ -140,7 +145,7 @@ pub(crate) fn launch_kernel(
|
||||
translated_extra_storage.as_mut_ptr()
|
||||
};
|
||||
// the parameter block: kernel_params[0] (a single struct parameter) or the extra buffer
|
||||
- let prep_key = if f.prep.is_some() && f.prep_key != 0 {
|
||||
+ let prep_key = if let (Some(_), Some(key_offset)) = (f.prep, f.prep_key) {
|
||||
let mut block: *const u8 = std::ptr::null();
|
||||
if !extra.is_null() {
|
||||
let mut i = 0usize;
|
||||
@@ -160,12 +165,27 @@ pub(crate) fn launch_kernel(
|
||||
if block.is_null() {
|
||||
0
|
||||
} else {
|
||||
- unsafe { std::ptr::read_unaligned(block.add(f.prep_key as usize) as *const u64) }
|
||||
+ unsafe { std::ptr::read_unaligned(block.add(key_offset as usize) as *const u64) }
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
- let run_prep = prep_key == 0 || f.prep_last.load(std::sync::atomic::Ordering::Relaxed) != prep_key;
|
||||
+ let run_prep = if prep_key == 0 {
|
||||
+ true
|
||||
+ } else if f.prep_slots == 0 {
|
||||
+ f.prep_last.load(std::sync::atomic::Ordering::Relaxed) != prep_key
|
||||
+ } else {
|
||||
+ // prepared once per key while slots last; keys beyond them are prepared on every launch
|
||||
+ let mut seen = f.prep_seen.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
+ if seen.contains(&prep_key) {
|
||||
+ false
|
||||
+ } else {
|
||||
+ if seen.len() < f.prep_slots as usize {
|
||||
+ seen.push(prep_key);
|
||||
+ }
|
||||
+ true
|
||||
+ }
|
||||
+ };
|
||||
if let (Some((prep, blocks)), true) = (f.prep, run_prep) {
|
||||
unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 5db5ace..c5a39c9 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -37,12 +37,12 @@ impl ModuleMutable {
|
||||
Ok(match self.functions.entry(name) {
|
||||
hash_map::Entry::Occupied(entry) => &*entry.into_mut(),
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
- let (func_handle, prep, block_z, grid_x, prep_key) = match native_override(entry.key()) {
|
||||
+ let (func_handle, prep, block_z, grid_x, prep_key, prep_slots) = match native_override(entry.key()) {
|
||||
Some(native) => native,
|
||||
None => {
|
||||
let mut func_handle = unsafe { std::mem::zeroed() };
|
||||
unsafe { hipModuleGetFunction(&mut func_handle, module, entry.key().as_ptr()) }?;
|
||||
- (func_handle, None, 0, 0, 0)
|
||||
+ (func_handle, None, 0, 0, None, 0)
|
||||
}
|
||||
};
|
||||
let func = Box::new(Function {
|
||||
@@ -54,6 +54,8 @@ impl ModuleMutable {
|
||||
grid_x,
|
||||
prep_key,
|
||||
prep_last: std::sync::atomic::AtomicU64::new(0),
|
||||
+ prep_slots,
|
||||
+ prep_seen: std::sync::Mutex::new(Vec::new()),
|
||||
});
|
||||
&*entry.insert(func)
|
||||
}
|
||||
@@ -67,7 +69,9 @@ impl ModuleMutable {
|
||||
/// same stream with `d4r_prep_blocks` (a u32 global in the code object) blocks of 128 threads.
|
||||
/// An optional u32 global `d4r_block_z` replaces the launch's block z dimension, and `d4r_grid_x`
|
||||
/// (persistent kernels, which derive the original grid from their parameters) the grid.
|
||||
-fn native_override(name: &CStr) -> Option<(hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, u32)> {
|
||||
+fn native_override(
|
||||
+ name: &CStr,
|
||||
+) -> Option<(hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, Option<u32>, u32)> {
|
||||
let dir = std::env::var_os("D4R_ZLUDA_NATIVE_DIR")?;
|
||||
let path = std::path::Path::new(&dir).join(format!("{}.hsaco", name.to_str().ok()?));
|
||||
let image = fs::read(&path).ok()?;
|
||||
@@ -103,34 +107,38 @@ fn native_override(name: &CStr) -> Option<(hipFunction_t, Option<(hipFunction_t,
|
||||
} else {
|
||||
None
|
||||
};
|
||||
- let read_u32 = |symbol: &CStr| -> u32 {
|
||||
+ let read_u32_opt = |symbol: &CStr| -> Option<u32> {
|
||||
let mut ptr = unsafe { mem::zeroed() };
|
||||
let mut size = 0usize;
|
||||
let mut value = 0u32;
|
||||
- if unsafe { hipModuleGetGlobal(&mut ptr, &mut size, native_module, symbol.as_ptr()) }.is_ok()
|
||||
+ (unsafe { hipModuleGetGlobal(&mut ptr, &mut size, native_module, symbol.as_ptr()) }.is_ok()
|
||||
&& size == 4
|
||||
- && unsafe { hipMemcpyDtoH((&mut value as *mut u32).cast(), ptr, 4) }.is_ok()
|
||||
- {
|
||||
- value
|
||||
- } else {
|
||||
- 0
|
||||
- }
|
||||
+ && unsafe { hipMemcpyDtoH((&mut value as *mut u32).cast(), ptr, 4) }.is_ok())
|
||||
+ .then_some(value)
|
||||
};
|
||||
+ let read_u32 = |symbol: &CStr| -> u32 { read_u32_opt(symbol).unwrap_or(0) };
|
||||
let block_z = read_u32(c"d4r_block_z");
|
||||
let grid_x = read_u32(c"d4r_grid_x");
|
||||
// byte offset of a u64 in the parameter block that fully determines the prep kernel's output
|
||||
- // (the weights pointer): the prep is skipped while it repeats (0 = prep before every launch)
|
||||
- let prep_key = read_u32(c"d4r_prep_key_offset");
|
||||
+ // (the weights pointer): the prep is skipped while it repeats. d4r_prep_key_at holds offset + 1
|
||||
+ // (so a key at offset 0 is possible); the older d4r_prep_key_offset uses 0 for "no key".
|
||||
+ let prep_key = match read_u32_opt(c"d4r_prep_key_at") {
|
||||
+ Some(at) if at > 0 => Some(at - 1),
|
||||
+ _ => Some(read_u32(c"d4r_prep_key_offset")).filter(|&offset| offset != 0),
|
||||
+ };
|
||||
+ // d4r_prep_key_slots: the kernel keeps one prepared image per key (see Function::prep_slots)
|
||||
+ let prep_slots = read_u32(c"d4r_prep_key_slots");
|
||||
eprintln!(
|
||||
- "[d4r-native] {:?} -> {} (prep: {:?}, block_z: {}, grid_x: {}, prep_key: {})",
|
||||
+ "[d4r-native] {:?} -> {} (prep: {:?}, block_z: {}, grid_x: {}, prep_key: {:?}, prep_slots: {})",
|
||||
name,
|
||||
path.display(),
|
||||
prep.map(|p| p.1),
|
||||
block_z,
|
||||
grid_x,
|
||||
- prep_key
|
||||
+ prep_key,
|
||||
+ prep_slots
|
||||
);
|
||||
- Some((func, prep, block_z, grid_x, prep_key))
|
||||
+ Some((func, prep, block_z, grid_x, prep_key, prep_slots))
|
||||
}
|
||||
|
||||
impl ZludaObject for Module {
|
||||
@@ -1,551 +0,0 @@
|
||||
--- a/llvm_zluda/src/compile.rs
|
||||
+++ b/llvm_zluda/src/compile.rs
|
||||
@@ -113,9 +113,57 @@
|
||||
*VALUE.get_or_init(|| std::env::var("D4R_ZLUDA_PARAM_PROVENANCE").map_or(true, |value| value.trim() != "0"))
|
||||
}
|
||||
|
||||
-fn wmma_rewrite_enabled(gcn_arch: &str) -> bool {
|
||||
- // The helpers assume RDNA3's wave32 WMMA register layout.
|
||||
- wmma_rewrite_requested() && gcn_arch.starts_with("gfx11")
|
||||
+/// D4R_ZLUDA_WMMA_FP8_NATIVE=1: on gfx12 (RDNA4), e4m3 MMAs use the native FP8 WMMA instead of widening
|
||||
+/// to f16. Ignored on every other GPU (gfx11 has no FP8 WMMA).
|
||||
+pub fn wmma_fp8_native_requested() -> bool {
|
||||
+ static VALUE: OnceLock<bool> = OnceLock::new();
|
||||
+ *VALUE.get_or_init(|| std::env::var("D4R_ZLUDA_WMMA_FP8_NATIVE").map_or(false, |value| value.trim() == "1"))
|
||||
+}
|
||||
+
|
||||
+/// The WMMA register layout family of a GPU: 11 (RDNA3) or 12 (RDNA4); the MMA helpers exist for these.
|
||||
+/// D4R_ZLUDA_WMMA_LAYOUT=12 on gfx11 is for tests only: it takes the gfx12 helper names, which then must
|
||||
+/// come from a helper library built with D4R_WMMA12_SHIM (gfx12 data movement, gfx11 WMMAs).
|
||||
+fn wmma_family(gcn_arch: &str) -> Option<u32> {
|
||||
+ let family = if gcn_arch.starts_with("gfx11") {
|
||||
+ 11
|
||||
+ } else if gcn_arch.starts_with("gfx12") {
|
||||
+ 12
|
||||
+ } else {
|
||||
+ return None;
|
||||
+ };
|
||||
+ if family == 11 && std::env::var("D4R_ZLUDA_WMMA_LAYOUT").map_or(false, |value| value.trim() == "12") {
|
||||
+ return Some(12);
|
||||
+ }
|
||||
+ Some(family)
|
||||
+}
|
||||
+
|
||||
+/// Whether e4m3 MMAs compiled for this GPU use native FP8 WMMA (the setting only acts on gfx12).
|
||||
+pub fn wmma_fp8_native_effective(gcn_arch: &str) -> bool {
|
||||
+ wmma_rewrite_flags(gcn_arch).map_or(false, |flags| flags & 4 != 0)
|
||||
+}
|
||||
+
|
||||
+/// Flags for LLVMZludaRewriteMmaF16Wmma, or None to keep the dot-product fallback (logged once when the
|
||||
+/// rewrite was requested but this GPU has no WMMA lowering).
|
||||
+fn wmma_rewrite_flags(gcn_arch: &str) -> Option<u32> {
|
||||
+ if !wmma_rewrite_requested() {
|
||||
+ return None;
|
||||
+ }
|
||||
+ let Some(family) = wmma_family(gcn_arch) else {
|
||||
+ static LOGGED: OnceLock<()> = OnceLock::new();
|
||||
+ LOGGED.get_or_init(|| {
|
||||
+ eprintln!("[ZLUDA] D4R_ZLUDA_WMMA: no WMMA lowering for {gcn_arch}; MMAs use the dot-product fallback")
|
||||
+ });
|
||||
+ return None;
|
||||
+ };
|
||||
+ let mut flags = if wmma_fp8_rewrite_requested() { 1 } else { 0 };
|
||||
+ if family == 12 {
|
||||
+ flags |= 2;
|
||||
+ // 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;
|
||||
+ }
|
||||
+ }
|
||||
+ Some(flags)
|
||||
}
|
||||
|
||||
pub fn compile(
|
||||
@@ -139,8 +187,7 @@
|
||||
|
||||
// wmma.mma m16n16k16 always becomes m16n8k16 MMAs (there is no helper for it).
|
||||
unsafe { crate::ffi::LLVMZludaSplitWmmaM16n16k16(main.get()) };
|
||||
- if wmma_rewrite_enabled(gcn_arch) {
|
||||
- let flags = if wmma_fp8_rewrite_requested() { 1 } else { 0 };
|
||||
+ if let Some(flags) = wmma_rewrite_flags(gcn_arch) {
|
||||
unsafe { crate::ffi::LLVMZludaRewriteMmaF16Wmma(main.get(), flags) };
|
||||
}
|
||||
linked.link(main)?;
|
||||
--- a/llvm_zluda/src/lib.cpp
|
||||
+++ b/llvm_zluda/src/lib.cpp
|
||||
@@ -968,6 +968,9 @@
|
||||
}
|
||||
|
||||
// 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.
|
||||
+// The helper sets share one contract (see zluda_ptx_impl.cpp), so only their names change here.
|
||||
unsigned LLVMZludaRewriteMmaF16Wmma(LLVMModuleRef ModuleRef, unsigned Flags)
|
||||
{
|
||||
Module *M = unwrap(ModuleRef);
|
||||
@@ -990,33 +993,39 @@
|
||||
Type *V8 = FixedVectorType::get(I32, 8);
|
||||
AttributeList Attributes = AttributeList::get(Context, AttributeList::FunctionIndex,
|
||||
AttrBuilder(Context, Reference->getAttributes().getFnAttrs()));
|
||||
+ // arch-specific helpers (operand gathers, WMMAs), e4m3 MMAs, e4m3 fragment widening; the conversions
|
||||
+ // between NVIDIA fragments, the transposed layout and f32 shadows are shared
|
||||
+ const bool Gfx12 = (Flags & 2) != 0, NativeFp8 = Gfx12 && (Flags & 4) != 0;
|
||||
+ 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_";
|
||||
Helpers H;
|
||||
- H.AFromB = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_a_from_b", Attributes, V8, V2);
|
||||
- H.BFromA = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_b_from_a", Attributes, V8, V4);
|
||||
- H.Mma = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_mma_f16", Attributes, V2, V8, V8, V2);
|
||||
+ 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.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("__zluda_ptx_impl_d4r_wmma_t_a_from_b_pair", Attributes, V8, V2, V2);
|
||||
- H.MmaPair = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_mma_f16_pair", Attributes, V4, V8, V8, 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);
|
||||
{
|
||||
Type *F = Type::getFloatTy(Context);
|
||||
Type *V4F = FixedVectorType::get(F, 4);
|
||||
Type *V8F = FixedVectorType::get(F, 8);
|
||||
H.SFromT = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_s_from_t", Attributes, V4F, V2, F);
|
||||
H.TFromS = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_from_s", Attributes, V2, V4F, F);
|
||||
- H.MmaS = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_s_mma_f16", Attributes, V4F, V8, V8, V4F, F, F);
|
||||
- H.MmaPairS = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_s_mma_f16_pair", Attributes, V8F, V8, V8, V4F, V4F, F, F);
|
||||
- H.MmaE4m3S = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_s_mma_e4m3", Attributes, V4F, V8, V8, V8, V8, V4F);
|
||||
+ H.MmaS = M->getOrInsertFunction((Arch + "s_mma_f16").c_str(), Attributes, V4F, V8, V8, V4F, F, F);
|
||||
+ H.MmaPairS = M->getOrInsertFunction((Arch + "s_mma_f16_pair").c_str(), Attributes, V8F, V8, V8, V4F, V4F, F, F);
|
||||
+ H.MmaE4m3S = M->getOrInsertFunction((E4m3 + "s_mma_e4m3").c_str(), Attributes, V4F, V8, V8, V8, V8, V4F);
|
||||
H.MmaE4m3PairS =
|
||||
- M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_s_mma_e4m3_pair", Attributes, V8F, V8, V8, V8, V8, V4F, V4F);
|
||||
+ M->getOrInsertFunction((E4m3 + "s_mma_e4m3_pair").c_str(), Attributes, V8F, V8, V8, V8, V8, V4F, V4F);
|
||||
}
|
||||
if (MmaE4m3 != nullptr)
|
||||
{
|
||||
- H.E4m3AToF16 = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_e4m3_a_to_f16", Attributes, V8, V4);
|
||||
- H.E4m3BToF16 = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_e4m3_b_to_f16", Attributes, V4, V2);
|
||||
- H.MmaE4m3 = M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_mma_e4m3", Attributes, V2, V8, V8, V8, V8, V2);
|
||||
+ H.E4m3AToF16 = M->getOrInsertFunction((Widen + "e4m3_a_to_f16").c_str(), Attributes, V8, V4);
|
||||
+ H.E4m3BToF16 = M->getOrInsertFunction((Widen + "e4m3_b_to_f16").c_str(), Attributes, V4, V2);
|
||||
+ H.MmaE4m3 = M->getOrInsertFunction((E4m3 + "t_mma_e4m3").c_str(), Attributes, V2, V8, V8, V8, V8, V2);
|
||||
H.MmaE4m3Pair =
|
||||
- M->getOrInsertFunction("__zluda_ptx_impl_d4r_wmma_t_mma_e4m3_pair", Attributes, V4, V8, V8, V8, V8, V2, V2);
|
||||
+ M->getOrInsertFunction((E4m3 + "t_mma_e4m3_pair").c_str(), Attributes, V4, V8, V8, V8, V8, V2, V2);
|
||||
}
|
||||
unsigned Rewritten = 0;
|
||||
for (Function &F : *M)
|
||||
--- a/ptx/examples/d4r_emit.rs
|
||||
+++ b/ptx/examples/d4r_emit.rs
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::{env, fs, path::PathBuf};
|
||||
use ptx_parser::parse_module_checked;
|
||||
|
||||
+// usage: d4r_emit PTX OUT_DIR [GPU_ARCH]: translates and compiles a PTX module offline (no GPU needed) for
|
||||
+// GPU_ARCH (default gfx1101), with the same D4R_ZLUDA_* code-generation switches as the runtime, and writes
|
||||
+// the IR stages, asm.s and module.hsaco to OUT_DIR
|
||||
fn main() {
|
||||
let input = env::args().nth(1).expect("PTX path");
|
||||
let outdir = PathBuf::from(env::args().nth(2).unwrap_or_else(|| "/tmp/d4r-autoexposure-emitted".into()));
|
||||
+ let arch = env::args().nth(3).unwrap_or_else(|| "gfx1101".into());
|
||||
fs::create_dir_all(&outdir).expect("create output directory");
|
||||
let bytes = fs::read(&input).expect("read PTX");
|
||||
let text = std::str::from_utf8(&bytes).expect("PTX UTF-8").trim_end_matches('\0');
|
||||
@@ -18,13 +22,14 @@
|
||||
"opt.ll" => "opt.ll",
|
||||
"asm" => "asm.s",
|
||||
"o" => "object.o",
|
||||
+ "elf" => "linked.elf", // the code object compile() returns (written again as module.hsaco)
|
||||
other => panic!("unexpected stage: {other}"),
|
||||
};
|
||||
fs::write(outdir.join(filename), data).expect("write compiler stage");
|
||||
};
|
||||
let elf = llvm_zluda::compile(
|
||||
&module.context,
|
||||
- "gfx1101",
|
||||
+ &arch,
|
||||
module.llvm_ir,
|
||||
linked_helpers,
|
||||
module.attributes_ir,
|
||||
--- a/ptx/lib/zluda_ptx_impl.cpp
|
||||
+++ b/ptx/lib/zluda_ptx_impl.cpp
|
||||
@@ -1696,6 +1696,349 @@
|
||||
}
|
||||
}
|
||||
|
||||
+// gfx12 (RDNA4) lowering of the same MMAs (LLVMZludaRewriteMmaF16Wmma with the gfx12 flag: helpers
|
||||
+// d4r_wmma12_*, and d4r_wmma12f8_* for the e4m3 ones with native FP8 WMMA). The contract is the gfx11 one
|
||||
+// above: NVIDIA fragments in, the transposed accumulator layout (lane l holds D[l % 16][2v + l / 16] as two
|
||||
+// f16x2 words, or four floats for the f32 shadows), so the rewrite only switches helper names.
|
||||
+//
|
||||
+// A gfx12 WMMA operand holds 8 of the 16 K values per lane: half h = lane / 16 its own 8 (4 dwords, returned
|
||||
+// in the low half of a wmma_uint8). With A' = B^T and B' = A^T as above, dword j of half h holds the k-pair of
|
||||
+// NVIDIA thread t = 2h + (j >> 1): the low pair (k 2t, 2t + 1) for even j, the high pair (k 2t + 8, 2t + 9)
|
||||
+// for odd j. Then every lane of a gather reads the same source register (4 bpermutes per operand; B' also
|
||||
+// needs a DPP swap per dword for rows 8..15). For e4m3 fragments the same slots hold, after the widening
|
||||
+// above, e4m3 k 8h .. 8h + 7 in order.
|
||||
+// The result D' has VGPR i = row n = i + 8h (columns m = lane % 16): unpaired, rows 0..7 are the h = 0 lanes
|
||||
+// and the h = 1 lanes are padding; paired (A' rows 8..15 = B1^T), D1 is in the h = 1 lanes. Converting from
|
||||
+// and to the transposed layout takes 2 permlanex16 each way (4 for f32 shadows).
|
||||
+//
|
||||
+// D4R_WMMA12_SHIM (tests only): these helpers compiled for gfx11, every gfx12 WMMA done by the gfx11 one on
|
||||
+// operands rebuilt in the gfx11 helpers' K order (and for e4m3 with the same scaled widening), so the results
|
||||
+// are bit-identical to the gfx11 helpers and the gfx12 data movement can be checked on RDNA3.
|
||||
+typedef _Float16 wmma_half8 __attribute__((ext_vector_type(8)));
|
||||
+typedef uint32_t wmma_uint4 __attribute__((ext_vector_type(4)));
|
||||
+typedef int wmma_int2 __attribute__((ext_vector_type(2)));
|
||||
+#ifndef D4R_WMMA12_SHIM
|
||||
+__device__ wmma_float8 __zluda_wmma12_f32_16x16x16_f16(wmma_half8 a, wmma_half8 b, wmma_float8 c) __asm("llvm.amdgcn.wmma.f32.16x16x16.f16.v8f32.v8f16");
|
||||
+__device__ wmma_float8 __zluda_wmma12_f32_16x16x16_fp8_fp8(wmma_int2 a, wmma_int2 b, wmma_float8 c) __asm("llvm.amdgcn.wmma.f32.16x16x16.fp8.fp8.v8f32.v2i32");
|
||||
+#endif
|
||||
+
|
||||
+__device__ static uint32_t wmma12_half()
|
||||
+{
|
||||
+ return wmma_lane() / 16;
|
||||
+}
|
||||
+
|
||||
+__device__ static uint32_t wmma12_other_half(uint32_t value)
|
||||
+{
|
||||
+ return uint32_t(__builtin_amdgcn_permlanex16(int(value), int(value), 0x76543210, 0xfedcba98, false, false));
|
||||
+}
|
||||
+
|
||||
+__device__ static float wmma12_other_half_f(float value)
|
||||
+{
|
||||
+ return std::bit_cast<float>(wmma12_other_half(std::bit_cast<uint32_t>(value)));
|
||||
+}
|
||||
+
|
||||
+// dword j of an operand gathered from two candidate registers: rows r < 8 take `low` from lane 4r + t, rows
|
||||
+// r >= 8 take `high` from lane 4(r - 8) + t (t = 2h + (j >> 1)). The source lane is in the first half-wave,
|
||||
+// while t differs between the two destination half-waves, so the two candidates must be gathered independently.
|
||||
+__device__ static uint32_t wmma12_gather2(uint32_t low, uint32_t high, uint32_t j)
|
||||
+{
|
||||
+ const uint32_t row = wmma_lane() % 16;
|
||||
+ const uint32_t t = (j >> 1) + 2 * wmma12_half();
|
||||
+ const uint32_t lo = wmma_from_lane(row * 4 + t, low);
|
||||
+ const uint32_t hi = wmma_from_lane((row % 8) * 4 + t, high);
|
||||
+ return row < 8 ? lo : hi;
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_uint4 wmma12_low4(wmma_uint8 v)
|
||||
+{
|
||||
+ return {v[0], v[1], v[2], v[3]};
|
||||
+}
|
||||
+
|
||||
+// f32 D' = A' B' + C' (f16 operands in the slot order above)
|
||||
+__device__ static wmma_float8 wmma12_f16(wmma_uint8 a, wmma_uint8 b, wmma_float8 c)
|
||||
+{
|
||||
+#ifndef D4R_WMMA12_SHIM
|
||||
+ return __zluda_wmma12_f32_16x16x16_f16(std::bit_cast<wmma_half8>(wmma12_low4(a)), std::bit_cast<wmma_half8>(wmma12_low4(b)), c);
|
||||
+#else
|
||||
+ // gfx11 operand dword p (K 2p, 2p + 1 in the gfx11 helpers' order) is gfx12 dword jp of half hp
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ wmma_uint8 a11, b11;
|
||||
+ for (uint32_t p = 0; p < 8; p++)
|
||||
+ {
|
||||
+ const uint32_t hp = (p & 3) >> 1, jp = 2 * (p & 1) + (p >> 2);
|
||||
+ const uint32_t ao = wmma12_other_half(a[jp]), bo = wmma12_other_half(b[jp]);
|
||||
+ a11[p] = (hp != 0) == high ? a[jp] : ao;
|
||||
+ b11[p] = (hp != 0) == high ? b[jp] : bo;
|
||||
+ }
|
||||
+ // accumulators: gfx12 VGPR i = row i + 8h, gfx11 VGPR i = row 2i + h
|
||||
+ wmma_float8 co, c11;
|
||||
+ for (uint32_t i = 0; i < 8; i++)
|
||||
+ co[i] = wmma12_other_half_f(c[i]);
|
||||
+ for (uint32_t i = 0; i < 8; i++)
|
||||
+ c11[i] = high ? (i < 4 ? co[2 * i + 1] : c[2 * i - 7]) : (i < 4 ? c[2 * i] : co[2 * i - 8]);
|
||||
+ wmma_float8 d11 = __zluda_wmma_f32_16x16x16_f16(std::bit_cast<wmma_half16>(a11), std::bit_cast<wmma_half16>(b11), c11);
|
||||
+ wmma_float8 dout, d;
|
||||
+ for (uint32_t i = 0; i < 8; i++)
|
||||
+ dout[i] = wmma12_other_half_f(d11[i]);
|
||||
+ for (uint32_t i = 0; i < 8; i++)
|
||||
+ d[i] = high ? ((i & 1) ? d11[(i >> 1) + 4] : dout[(i >> 1) + 4]) : ((i & 1) ? dout[i >> 1] : d11[i >> 1]);
|
||||
+ return d;
|
||||
+#endif
|
||||
+}
|
||||
+
|
||||
+// 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)
|
||||
+{
|
||||
+ const wmma_int2 a8 = {int(__builtin_amdgcn_perm(a[1], a[0], 0x05040100u)), int(__builtin_amdgcn_perm(a[3], a[2], 0x05040100u))};
|
||||
+ const wmma_int2 b8 = {int(__builtin_amdgcn_perm(b[1], b[0], 0x05040100u)), int(__builtin_amdgcn_perm(b[3], b[2], 0x05040100u))};
|
||||
+#ifndef D4R_WMMA12_SHIM
|
||||
+ return __zluda_wmma12_f32_16x16x16_fp8_fp8(a8, b8, c);
|
||||
+#else
|
||||
+ // the gfx11 helpers' scaled widening (value * 2^-8 per operand, C * 2^-16): exact powers of two
|
||||
+ auto widen = [](wmma_int2 q) {
|
||||
+ auto lo = e4m3x4_to_scaled_f16x2x2(uint32_t(q.x)), hi = e4m3x4_to_scaled_f16x2x2(uint32_t(q.y));
|
||||
+ return wmma_uint8{lo.x, lo.y, hi.x, hi.y, 0u, 0u, 0u, 0u};
|
||||
+ };
|
||||
+ return wmma12_f16(widen(a8), widen(b8), c * 0x1p-16f) * 0x1p16f;
|
||||
+#endif
|
||||
+}
|
||||
+
|
||||
+// transposed f16 accumulator(s) <-> D' / C'
|
||||
+__device__ static wmma_float8 wmma12_c_from_t(uint2::Native_vec_ c_t)
|
||||
+{
|
||||
+ // lane h = 0 needs rows 0..7: its own rows 0, 2, 4, 6 and the other half's 1, 3, 5, 7 (h = 1 lanes: padding)
|
||||
+ const uint32_t o0 = wmma12_other_half(c_t.x), o1 = wmma12_other_half(c_t.y);
|
||||
+ f16x2 e01 = std::bit_cast<f16x2>(c_t.x), e23 = std::bit_cast<f16x2>(c_t.y);
|
||||
+ f16x2 d01 = std::bit_cast<f16x2>(o0), d23 = std::bit_cast<f16x2>(o1);
|
||||
+ return {float(e01.x), float(d01.x), float(e01.y), float(d01.y), float(e23.x), float(d23.x), float(e23.y), float(d23.y)};
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_float8 wmma12_c_from_t_pair(uint2::Native_vec_ c0_t, uint2::Native_vec_ c1_t)
|
||||
+{
|
||||
+ // h = 0: C0 rows 0..7 (own even, other odd); h = 1: C1 rows 0..7 (other even, own odd)
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ const uint2::Native_vec_ own = high ? c1_t : c0_t, send = high ? c0_t : c1_t;
|
||||
+ const uint32_t r0 = wmma12_other_half(send.x), r1 = wmma12_other_half(send.y);
|
||||
+ f16x2 o01 = std::bit_cast<f16x2>(own.x), o23 = std::bit_cast<f16x2>(own.y);
|
||||
+ f16x2 p01 = std::bit_cast<f16x2>(r0), p23 = std::bit_cast<f16x2>(r1);
|
||||
+ f16x2 e01 = high ? p01 : o01, e23 = high ? p23 : o23, d01 = high ? o01 : p01, d23 = high ? o23 : p23;
|
||||
+ return {float(e01.x), float(d01.x), float(e01.y), float(d01.y), float(e23.x), float(d23.x), float(e23.y), float(d23.y)};
|
||||
+}
|
||||
+
|
||||
+__device__ static void wmma12_rows_f16(wmma_float8 d, uint2::Native_vec_ &even, uint2::Native_vec_ &odd)
|
||||
+{
|
||||
+ f16x2 e01{static_cast<f16>(d[0]), static_cast<f16>(d[2])}, e23{static_cast<f16>(d[4]), static_cast<f16>(d[6])};
|
||||
+ f16x2 o01{static_cast<f16>(d[1]), static_cast<f16>(d[3])}, o23{static_cast<f16>(d[5]), static_cast<f16>(d[7])};
|
||||
+ even = {std::bit_cast<uint32_t>(e01), std::bit_cast<uint32_t>(e23)};
|
||||
+ odd = {std::bit_cast<uint32_t>(o01), std::bit_cast<uint32_t>(o23)};
|
||||
+}
|
||||
+
|
||||
+__device__ static uint2::Native_vec_ wmma12_t_from_d(wmma_float8 d)
|
||||
+{
|
||||
+ // h = 0 keeps rows 0, 2, 4, 6; h = 1 takes rows 1, 3, 5, 7 from the other half
|
||||
+ uint2::Native_vec_ even, odd;
|
||||
+ wmma12_rows_f16(d, even, odd);
|
||||
+ const uint2::Native_vec_ other = {wmma12_other_half(odd.x), wmma12_other_half(odd.y)};
|
||||
+ return wmma12_half() != 0 ? other : even;
|
||||
+}
|
||||
+
|
||||
+__device__ static uint4::Native_vec_ wmma12_t_from_d_pair(wmma_float8 d)
|
||||
+{
|
||||
+ // h = 0 holds D0 rows 0..7, h = 1 D1 rows 0..7; lane h wants rows 2v + h of both
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ uint2::Native_vec_ even, odd;
|
||||
+ wmma12_rows_f16(d, even, odd);
|
||||
+ const uint2::Native_vec_ send = high ? even : odd;
|
||||
+ const uint2::Native_vec_ recv = {wmma12_other_half(send.x), wmma12_other_half(send.y)};
|
||||
+ return high ? uint4::Native_vec_{recv.x, recv.y, odd.x, odd.y} : uint4::Native_vec_{even.x, even.y, recv.x, recv.y};
|
||||
+}
|
||||
+
|
||||
+// the same for f32 shadows (element v = row 2v + h)
|
||||
+__device__ static wmma_float8 wmma12_c_from_s(wmma_float4 c)
|
||||
+{
|
||||
+ wmma_float4 o;
|
||||
+ for (uint32_t v = 0; v < 4; v++)
|
||||
+ o[v] = wmma12_other_half_f(c[v]);
|
||||
+ return {c[0], o[0], c[1], o[1], c[2], o[2], c[3], o[3]};
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_float8 wmma12_c_from_s_pair(wmma_float4 c0, wmma_float4 c1)
|
||||
+{
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ wmma_float8 r;
|
||||
+ for (uint32_t v = 0; v < 4; v++)
|
||||
+ {
|
||||
+ const float own = high ? c1[v] : c0[v], recv = wmma12_other_half_f(high ? c0[v] : c1[v]);
|
||||
+ r[2 * v] = high ? recv : own;
|
||||
+ r[2 * v + 1] = high ? own : recv;
|
||||
+ }
|
||||
+ return r;
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_float4 wmma12_s_from_d(wmma_float8 d)
|
||||
+{
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ wmma_float4 r;
|
||||
+ for (uint32_t v = 0; v < 4; v++)
|
||||
+ {
|
||||
+ const float odd = wmma12_other_half_f(d[2 * v + 1]);
|
||||
+ r[v] = high ? odd : d[2 * v];
|
||||
+ }
|
||||
+ return r;
|
||||
+}
|
||||
+
|
||||
+__device__ static wmma_float8 wmma12_s_from_d_pair(wmma_float8 d)
|
||||
+{
|
||||
+ const bool high = wmma12_half() != 0;
|
||||
+ wmma_float8 r;
|
||||
+ for (uint32_t v = 0; v < 4; v++)
|
||||
+ {
|
||||
+ const float recv = wmma12_other_half_f(high ? d[2 * v] : d[2 * v + 1]);
|
||||
+ r[v] = high ? recv : d[2 * v];
|
||||
+ r[4 + v] = high ? d[2 * v + 1] : recv;
|
||||
+ }
|
||||
+ return r;
|
||||
+}
|
||||
+
|
||||
+extern "C"
|
||||
+{
|
||||
+ wmma_uint8 FUNC(d4r_wmma12_t_a_from_b)(uint2::Native_vec_ b)
|
||||
+ {
|
||||
+ __builtin_amdgcn_sched_barrier(0);
|
||||
+ uint32_t n = wmma_lane() % 16;
|
||||
+ wmma_uint8 result = {};
|
||||
+ for (uint32_t j = 0; j < 4; j++)
|
||||
+ {
|
||||
+ uint32_t value = wmma_from_lane((n % 8) * 4 + (j >> 1) + 2 * wmma12_half(), (j & 1) ? b.y : b.x);
|
||||
+ result[j] = n < 8 ? value : 0u;
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ wmma_uint8 FUNC(d4r_wmma12_t_b_from_a)(uint4::Native_vec_ a)
|
||||
+ {
|
||||
+ __builtin_amdgcn_sched_barrier(0);
|
||||
+ wmma_uint8 result = {};
|
||||
+ for (uint32_t j = 0; j < 4; j++)
|
||||
+ result[j] = wmma12_gather2((j & 1) ? a.z : a.x, (j & 1) ? a.w : a.y, j);
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ wmma_uint8 FUNC(d4r_wmma12_t_a_from_b_pair)(uint2::Native_vec_ b0, uint2::Native_vec_ b1)
|
||||
+ {
|
||||
+ __builtin_amdgcn_sched_barrier(0);
|
||||
+ wmma_uint8 result = {};
|
||||
+ for (uint32_t j = 0; j < 4; j++)
|
||||
+ result[j] = wmma12_gather2((j & 1) ? b0.y : b0.x, (j & 1) ? b1.y : b1.x, j);
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma12_t_mma_f16)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c_t)
|
||||
+ {
|
||||
+ return wmma12_t_from_d(wmma12_f16(a_t, b_t, wmma12_c_from_t(c_t)));
|
||||
+ }
|
||||
+
|
||||
+ uint4::Native_vec_ FUNC(d4r_wmma12_t_mma_f16_pair)(wmma_uint8 a_t, wmma_uint8 b_t, uint2::Native_vec_ c0_t, uint2::Native_vec_ c1_t)
|
||||
+ {
|
||||
+ return wmma12_t_from_d_pair(wmma12_f16(a_t, b_t, wmma12_c_from_t_pair(c0_t, c1_t)));
|
||||
+ }
|
||||
+
|
||||
+ 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;
|
||||
+ }
|
||||
+
|
||||
+ wmma_float8 FUNC(d4r_wmma12_s_mma_f16_pair)(wmma_uint8 a_t, wmma_uint8 b_t, wmma_float4 c0, wmma_float4 c1, float up,
|
||||
+ float down)
|
||||
+ {
|
||||
+ return wmma12_s_from_d_pair(wmma12_f16(a_t, b_t, wmma12_c_from_s_pair(c0 * up, c1 * up))) * down;
|
||||
+ }
|
||||
+
|
||||
+ // e4m3 with the f16 widening (d4r_wmma_e4m3_a_to_f16 / b_to_f16 above): two chained f16 WMMAs in the
|
||||
+ // 2^-16 scaled domain, like the gfx11 helpers
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma12_t_mma_e4m3)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, uint2::Native_vec_ c_t)
|
||||
+ {
|
||||
+ wmma_float8 c = wmma12_c_from_t(c_t) * 0x1p-16f;
|
||||
+ c = wmma12_f16(a_t_high, b_t_high, wmma12_f16(a_t_low, b_t_low, c));
|
||||
+ return wmma12_t_from_d(c * 0x1p16f);
|
||||
+ }
|
||||
+
|
||||
+ uint4::Native_vec_ FUNC(d4r_wmma12_t_mma_e4m3_pair)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, uint2::Native_vec_ c0_t,
|
||||
+ uint2::Native_vec_ c1_t)
|
||||
+ {
|
||||
+ wmma_float8 c = wmma12_c_from_t_pair(c0_t, c1_t) * 0x1p-16f;
|
||||
+ c = wmma12_f16(a_t_high, b_t_high, wmma12_f16(a_t_low, b_t_low, c));
|
||||
+ return wmma12_t_from_d_pair(c * 0x1p16f);
|
||||
+ }
|
||||
+
|
||||
+ wmma_float4 FUNC(d4r_wmma12_s_mma_e4m3)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, wmma_float4 c)
|
||||
+ {
|
||||
+ return wmma12_s_from_d(wmma12_f16(a_t_high, b_t_high, wmma12_f16(a_t_low, b_t_low, wmma12_c_from_s(c))));
|
||||
+ }
|
||||
+
|
||||
+ wmma_float8 FUNC(d4r_wmma12_s_mma_e4m3_pair)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, wmma_float4 c0, wmma_float4 c1)
|
||||
+ {
|
||||
+ return wmma12_s_from_d_pair(
|
||||
+ wmma12_f16(a_t_high, b_t_high, wmma12_f16(a_t_low, b_t_low, wmma12_c_from_s_pair(c0, c1))));
|
||||
+ }
|
||||
+
|
||||
+ // Native FP8 (d4r_wmma12f8_*, D4R_ZLUDA_WMMA_FP8_NATIVE): the "widening" keeps the e4m3 bytes, two per
|
||||
+ // dword in the low 16 bits and in the same slots the f16 widening fills, so the f16 gathers above move them;
|
||||
+ // the MMA packs this half's 8 bytes (e4m3 k 8h .. 8h + 7 in order) into the FP8 operand. OCP e4m3 (gfx12)
|
||||
+ // is NVIDIA's e4m3, so the bytes need no conversion. Shadows keep the 2^-16 scaled domain of the e4m3
|
||||
+ // functions (exact power-of-two rescaling around the WMMA).
|
||||
+ __device__ static uint2::Native_vec_ e4m3x4_split(uint32_t packed)
|
||||
+ {
|
||||
+ return {__builtin_amdgcn_perm(0u, packed, 0x0c0c0100u), __builtin_amdgcn_perm(0u, packed, 0x0c0c0302u)};
|
||||
+ }
|
||||
+
|
||||
+ wmma_uint8 FUNC(d4r_wmma12f8_e4m3_a_to_f16)(uint4::Native_vec_ a)
|
||||
+ {
|
||||
+ auto x = e4m3x4_split(a.x), y = e4m3x4_split(a.y), z = e4m3x4_split(a.z), w = e4m3x4_split(a.w);
|
||||
+ return {x.x, y.x, x.y, y.y, z.x, w.x, z.y, w.y};
|
||||
+ }
|
||||
+
|
||||
+ uint4::Native_vec_ FUNC(d4r_wmma12f8_e4m3_b_to_f16)(uint2::Native_vec_ b)
|
||||
+ {
|
||||
+ auto x = e4m3x4_split(b.x), y = e4m3x4_split(b.y);
|
||||
+ return {x.x, x.y, y.x, y.y};
|
||||
+ }
|
||||
+
|
||||
+ uint2::Native_vec_ FUNC(d4r_wmma12f8_t_mma_e4m3)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, uint2::Native_vec_ c_t)
|
||||
+ {
|
||||
+ return wmma12_t_from_d(wmma12_e4m3(a_t_high, b_t_high, wmma12_e4m3(a_t_low, b_t_low, wmma12_c_from_t(c_t))));
|
||||
+ }
|
||||
+
|
||||
+ uint4::Native_vec_ FUNC(d4r_wmma12f8_t_mma_e4m3_pair)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, uint2::Native_vec_ c0_t,
|
||||
+ uint2::Native_vec_ c1_t)
|
||||
+ {
|
||||
+ return wmma12_t_from_d_pair(
|
||||
+ wmma12_e4m3(a_t_high, b_t_high, wmma12_e4m3(a_t_low, b_t_low, wmma12_c_from_t_pair(c0_t, c1_t))));
|
||||
+ }
|
||||
+
|
||||
+ wmma_float4 FUNC(d4r_wmma12f8_s_mma_e4m3)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, wmma_float4 c)
|
||||
+ {
|
||||
+ wmma_float8 d = wmma12_e4m3(a_t_high, b_t_high, wmma12_e4m3(a_t_low, b_t_low, wmma12_c_from_s(c * 0x1p16f)));
|
||||
+ return wmma12_s_from_d(d) * 0x1p-16f;
|
||||
+ }
|
||||
+
|
||||
+ wmma_float8 FUNC(d4r_wmma12f8_s_mma_e4m3_pair)(wmma_uint8 a_t_low, wmma_uint8 b_t_low, wmma_uint8 a_t_high,
|
||||
+ wmma_uint8 b_t_high, wmma_float4 c0, wmma_float4 c1)
|
||||
+ {
|
||||
+ wmma_float8 d = wmma12_e4m3(a_t_high, b_t_high,
|
||||
+ wmma12_e4m3(a_t_low, b_t_low, wmma12_c_from_s_pair(c0 * 0x1p16f, c1 * 0x1p16f)));
|
||||
+ return wmma12_s_from_d_pair(d) * 0x1p-16f;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
template <typename T, typename A, typename B, int BOffset, typename OcklFunc>
|
||||
__device__ T dp2a_impl(T a, T b, T c, OcklFunc ockl_func)
|
||||
{
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -356,6 +356,7 @@
|
||||
wmma: llvm_zluda::compile::wmma_rewrite_requested(),
|
||||
wmma_fp8: llvm_zluda::compile::wmma_fp8_rewrite_requested(),
|
||||
param_provenance: llvm_zluda::compile::param_provenance_enabled(),
|
||||
+ wmma_fp8_native: llvm_zluda::compile::wmma_fp8_native_effective(gcn_arch),
|
||||
};
|
||||
let mut cache_with_key = match (text, global_state.cache_path.as_ref()) {
|
||||
(Some(text), Some(p)) => (|| {
|
||||
@@ -397,6 +398,10 @@
|
||||
wmma: bool,
|
||||
wmma_fp8: bool,
|
||||
param_provenance: bool,
|
||||
+ // d4r: native FP8 WMMA where it takes effect (gfx12); absent from the key everywhere else, so the setting
|
||||
+ // (d4r.ini NativeFp8) never forces a recompile on GPUs that ignore it
|
||||
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
|
||||
+ wmma_fp8_native: bool,
|
||||
}
|
||||
|
||||
fn get_hip_properties<'a>() -> Result<hipDeviceProp_tR0600, CUerror> {
|
||||
@@ -430,6 +435,7 @@
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
"D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA"
|
||||
+ | "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
.map(|(key, value)| format!("{key}={}", value.trim()))
|
||||
@@ -1,203 +0,0 @@
|
||||
Windows LLVM-MinGW build, portable helper assertions and optional diagnostic UI.
|
||||
Apply after upstream d4r patches 0002 through 0007, on ZLUDA ee2f25a.
|
||||
|
||||
diff --git a/ext/highs-sys/build.rs b/ext/highs-sys/build.rs
|
||||
--- a/ext/highs-sys/build.rs
|
||||
+++ b/ext/highs-sys/build.rs
|
||||
@@ -66,11 +66,12 @@
|
||||
let linux = target.contains("linux");
|
||||
let mingw = target.contains("pc-windows-gnu");
|
||||
if apple {
|
||||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
} else if linux || mingw {
|
||||
- println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||
+ let cxx_library = env::var("CXXSTDLIB").unwrap_or_else(|_| "stdc++".to_owned());
|
||||
+ println!("cargo:rustc-link-lib=dylib={cxx_library}");
|
||||
}
|
||||
println!("cargo:rerun-if-changed=../HiGHS/highs/interfaces/highs_c_api.h");
|
||||
|
||||
true
|
||||
}
|
||||
diff --git a/ext/hip_runtime-sys/build.rs b/ext/hip_runtime-sys/build.rs
|
||||
--- a/ext/hip_runtime-sys/build.rs
|
||||
+++ b/ext/hip_runtime-sys/build.rs
|
||||
@@ -1,8 +1,12 @@
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
- println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") {
|
||||
+ println!("cargo:rustc-link-arg=-Wl,--delayload=amdhip64_7.dll");
|
||||
+ } else {
|
||||
+ println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ }
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=dylib=amdhip64");
|
||||
println!("cargo:rustc-link-search=native=/opt/rocm/lib/");
|
||||
}
|
||||
}
|
||||
diff --git a/llvm_zluda/build.rs b/llvm_zluda/build.rs
|
||||
--- a/llvm_zluda/build.rs
|
||||
+++ b/llvm_zluda/build.rs
|
||||
@@ -19,10 +19,18 @@
|
||||
|
||||
fn main() {
|
||||
let bc_path1 = "src/device-libs/ockl.bc";
|
||||
println!("cargo:rerun-if-changed={}", bc_path1);
|
||||
check_lfs_file(bc_path1);
|
||||
+
|
||||
+ println!("cargo:rerun-if-env-changed=ZLUDA_LLVM_BUILD_DIR");
|
||||
+ if let Some(directory) = env::var_os("ZLUDA_LLVM_BUILD_DIR") {
|
||||
+ let llvm_build_path = PathBuf::from(directory);
|
||||
+ println!("cargo:build-path={}", llvm_build_path.display());
|
||||
+ link_prebuilt_llvm(&llvm_build_path);
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
let mut cmake = Config::new(r"../ext/llvm-project/llvm");
|
||||
try_use_sccache(&mut cmake);
|
||||
try_use_ninja(&mut cmake);
|
||||
cmake
|
||||
@@ -57,16 +65,31 @@
|
||||
cmake.build();
|
||||
}
|
||||
let cmake_profile = cmake.get_profile();
|
||||
let llvm_build_path = get_llvm_build_path(&llvm_dir, cmake_profile);
|
||||
println!("cargo:build-path={}", llvm_build_path.display());
|
||||
+ link_prebuilt_llvm(&llvm_build_path);
|
||||
+}
|
||||
+
|
||||
+fn link_prebuilt_llvm(llvm_build_path: &PathBuf) {
|
||||
let (cxxflags, ldflags, libdir, lib_names, system_libs) =
|
||||
llvm_config(&llvm_build_path.join("bin/llvm-config")).unwrap();
|
||||
println!("cargo:rustc-link-arg={ldflags}");
|
||||
println!("cargo:rustc-link-search=native={libdir}");
|
||||
for lib in system_libs.split_ascii_whitespace() {
|
||||
- println!("cargo:rustc-link-arg={lib}");
|
||||
+ if let Some(name) = lib.strip_prefix("-l") {
|
||||
+ println!("cargo:rustc-link-lib={name}");
|
||||
+ } else {
|
||||
+ println!("cargo:rustc-link-arg={lib}");
|
||||
+ }
|
||||
+ }
|
||||
+ // llvm-config omits system libraries on Windows. Propagate these through
|
||||
+ // this rlib to its final cdylib consumer (link-arg alone cannot do that).
|
||||
+ if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
|
||||
+ for library in ["psapi", "shell32", "ole32", "uuid", "advapi32", "ws2_32", "ntdll"] {
|
||||
+ println!("cargo:rustc-link-lib={library}");
|
||||
+ }
|
||||
}
|
||||
link_lld_components();
|
||||
link_llvm_components(lib_names);
|
||||
compile_cxx_lib(cxxflags, &llvm_build_path);
|
||||
}
|
||||
diff --git a/zluda/build.rs b/zluda/build.rs
|
||||
--- a/zluda/build.rs
|
||||
+++ b/zluda/build.rs
|
||||
@@ -1,11 +1,15 @@
|
||||
use vergen_gix::{Emitter, Gix};
|
||||
|
||||
fn main() {
|
||||
if cfg!(windows) {
|
||||
- println!("cargo:rustc-link-arg=delayimp.lib");
|
||||
- println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") {
|
||||
+ println!("cargo:rustc-link-arg=-Wl,--delayload=amdhip64_7.dll");
|
||||
+ } else {
|
||||
+ println!("cargo:rustc-link-arg=delayimp.lib");
|
||||
+ println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll");
|
||||
+ }
|
||||
let dll_path = "bin/nvcudart_hybrid64.dll";
|
||||
println!("cargo:rerun-if-changed={}", dll_path);
|
||||
check_lfs_file(dll_path);
|
||||
}
|
||||
let git = Gix::builder().sha(false).build();
|
||||
diff --git a/zluda_windows/src/lib.rs b/zluda_windows/src/lib.rs
|
||||
--- a/zluda_windows/src/lib.rs
|
||||
+++ b/zluda_windows/src/lib.rs
|
||||
@@ -11,27 +11,27 @@
|
||||
use uuid::uuid;
|
||||
use widestring::{u16str, U16Str, U16String};
|
||||
use windows::{
|
||||
core::{w, HRESULT, PCWSTR},
|
||||
Win32::{
|
||||
- Foundation::{HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, S_OK, WPARAM},
|
||||
+ Foundation::{FreeLibrary, HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, S_OK, WPARAM},
|
||||
System::{
|
||||
JobObjects::{
|
||||
AssignProcessToJobObject, JobObjectExtendedLimitInformation,
|
||||
SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
|
||||
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
|
||||
},
|
||||
LibraryLoader::{
|
||||
- GetModuleFileNameA, GetModuleFileNameW, GetModuleHandleExW, LoadLibraryExW,
|
||||
+ GetModuleFileNameA, GetModuleFileNameW, GetModuleHandleExW, GetProcAddress, LoadLibraryExW,
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
|
||||
- LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
|
||||
+ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, LOAD_LIBRARY_SEARCH_SYSTEM32,
|
||||
},
|
||||
},
|
||||
UI::{
|
||||
Controls::{
|
||||
- TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1,
|
||||
+ TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1,
|
||||
TASKDIALOG_NOTIFICATIONS, TDCBF_CLOSE_BUTTON, TDF_ENABLE_HYPERLINKS,
|
||||
TDN_HYPERLINK_CLICKED, TD_ERROR_ICON,
|
||||
},
|
||||
WindowsAndMessaging::IDCLOSE,
|
||||
},
|
||||
@@ -487,11 +487,23 @@
|
||||
pszFooter: PCWSTR::null(),
|
||||
pfCallback: Some(task_dialog_callback),
|
||||
lpCallbackData: 0,
|
||||
cxWidth: 0,
|
||||
};
|
||||
- TaskDialogIndirect(&config, None, None, None).ok();
|
||||
+ // A CUDA DLL may be loaded by an executable with no Common Controls
|
||||
+ // v6 activation manifest. Do not make its diagnostic UI an eager DLL
|
||||
+ // import: comctl32 v5 has no TaskDialogIndirect export.
|
||||
+ if let Ok(controls) = LoadLibraryExW(w!("comctl32.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32) {
|
||||
+ if let Some(raw) = GetProcAddress(controls, windows::core::s!("TaskDialogIndirect")) {
|
||||
+ let show: unsafe extern "system" fn(*const TASKDIALOGCONFIG, *mut i32, *mut i32, *mut i32) -> HRESULT =
|
||||
+ mem::transmute(raw);
|
||||
+ let _ = show(&config, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
|
||||
+ } else {
|
||||
+ eprintln!("ZLUDA failed to load {}; set HIP_PATH to the selected HIP SDK", redirect_names[0]);
|
||||
+ }
|
||||
+ let _ = FreeLibrary(controls);
|
||||
+ }
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
unsafe extern "system" fn task_dialog_callback(
|
||||
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
|
||||
@@ -405,11 +405,26 @@
|
||||
uint32_t line,
|
||||
uint64_t function,
|
||||
uint64_t char_size)
|
||||
{
|
||||
(void)char_size;
|
||||
+#if defined(_WIN32) || defined(_WIN64)
|
||||
+ // HIP on Windows only supplies the wide _wassert entry point. Keep the
|
||||
+ // CUDA assertion's ASCII diagnostics and trap through public OCKL APIs.
|
||||
+ auto output = __ockl_fprintf_stderr_begin();
|
||||
+ const char* parts[] = {"CUDA assertion: ", (const char*)message, " at ", (const char*)file,
|
||||
+ " in ", (const char*)function, "\n"};
|
||||
+ for (const char* part : parts) {
|
||||
+ const char* end = part;
|
||||
+ while (*end) ++end;
|
||||
+ output = __ockl_fprintf_append_string_n(output, part, end - part, 0);
|
||||
+ }
|
||||
+ __ockl_fprintf_append_args(output, 1, line, 0, 0, 0, 0, 0, 0, 1);
|
||||
+ __builtin_trap();
|
||||
+#else
|
||||
[[clang::noinline]] __assert_fail((const char *)message, (const char *)file, line, (const char *)function);
|
||||
+#endif
|
||||
}
|
||||
|
||||
// * Smallest denormal is 1.4 × 10^-45
|
||||
// * Smallest normal is ~1.175494351 × 10^(-38)
|
||||
// * Now, 1.175494351×10^-38 / 1.4 × 10^-45 = 8396388 + 31/140
|
||||
@@ -1,124 +0,0 @@
|
||||
Implement the CUDA texture resource descriptor query required by NGX Evaluate.
|
||||
Regression: tools/windows/cuda_image_probe.cpp in the d4r Windows port.
|
||||
|
||||
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
|
||||
--- a/zluda/src/lib.rs
|
||||
+++ b/zluda/src/lib.rs
|
||||
@@ -67,12 +67,14 @@
|
||||
cuda_macros::cuda_function_declarations!(
|
||||
unimplemented,
|
||||
implemented
|
||||
<= [
|
||||
cuArray3DCreate_v2,
|
||||
+ cuArray3DGetDescriptor_v2,
|
||||
cuArrayCreate_v2,
|
||||
cuArrayDestroy,
|
||||
+ cuArrayGetDescriptor_v2,
|
||||
cuCtxCreate_v2,
|
||||
cuCtxCreate_v4,
|
||||
cuCtxDestroy_v2,
|
||||
cuCtxGetApiVersion,
|
||||
cuCtxGetCurrent,
|
||||
@@ -203,10 +205,11 @@
|
||||
cuSurfObjectCreate,
|
||||
cuSurfObjectDestroy,
|
||||
cuSurfObjectGetResourceDesc,
|
||||
cuTexObjectCreate,
|
||||
cuTexObjectDestroy,
|
||||
+ cuTexObjectGetResourceDesc,
|
||||
cuTexRefSetAddress_v2,
|
||||
cuTexRefSetAddressMode,
|
||||
cuTexRefSetArray,
|
||||
cuTexRefSetFilterMode,
|
||||
cuTexRefSetFlags,
|
||||
diff --git a/zluda/src/impl/tex.rs b/zluda/src/impl/tex.rs
|
||||
--- a/zluda/src/impl/tex.rs
|
||||
+++ b/zluda/src/impl/tex.rs
|
||||
@@ -1,19 +1,58 @@
|
||||
use crate::r#impl::hipfix;
|
||||
use hip_runtime_sys::*;
|
||||
+use std::collections::HashMap;
|
||||
+use std::sync::{Mutex, OnceLock};
|
||||
+
|
||||
+fn object_resources() -> &'static Mutex<HashMap<usize, Vec<u8>>> {
|
||||
+ static RESOURCES: OnceLock<Mutex<HashMap<usize, Vec<u8>>>> = OnceLock::new();
|
||||
+ RESOURCES.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
+}
|
||||
|
||||
pub(crate) unsafe fn object_create(
|
||||
p_tex_object: *mut hipTextureObject_t,
|
||||
p_res_desc: *const HIP_RESOURCE_DESC,
|
||||
p_tex_desc: *const HIP_TEXTURE_DESC,
|
||||
p_res_view_desc: *const HIP_RESOURCE_VIEW_DESC,
|
||||
) -> hipError_t {
|
||||
- hipTexObjectCreate(p_tex_object, p_res_desc, p_tex_desc, p_res_view_desc)
|
||||
+ let output = p_tex_object.as_mut().ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ let descriptor = p_res_desc.as_ref().ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ hipTexObjectCreate(output, descriptor, p_tex_desc, p_res_view_desc)?;
|
||||
+ let bytes = std::slice::from_raw_parts(p_res_desc.cast::<u8>(), std::mem::size_of::<HIP_RESOURCE_DESC>()).to_vec();
|
||||
+ match object_resources().lock() {
|
||||
+ Ok(mut resources) => {
|
||||
+ resources.insert(*output as usize, bytes);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+ Err(_) => {
|
||||
+ let _ = hipDestroyTextureObject(*output);
|
||||
+ *output = std::ptr::null_mut();
|
||||
+ Err(hipErrorCode_t::Unknown)
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn object_destroy(tex_object: hipTextureObject_t) -> hipError_t {
|
||||
- hipDestroyTextureObject(tex_object)
|
||||
+ let mut resources = object_resources().lock().map_err(|_| hipErrorCode_t::Unknown)?;
|
||||
+ if !resources.contains_key(&(tex_object as usize)) { return Err(hipErrorCode_t::InvalidValue); }
|
||||
+ hipDestroyTextureObject(tex_object)?;
|
||||
+ resources.remove(&(tex_object as usize));
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
+pub(crate) unsafe fn object_get_resource_desc(
|
||||
+ descriptor: &mut HIP_RESOURCE_DESC,
|
||||
+ tex_object: hipTextureObject_t,
|
||||
+) -> hipError_t {
|
||||
+ // NGX probes surface handles as textures before trying the surface API.
|
||||
+ // Passing that foreign handle to HIP dereferences invalid host metadata.
|
||||
+ // Return a CUDA error for unknown handles and preserve the complete CUDA
|
||||
+ // descriptor (HIP's query leaves the flags/reserved bytes unspecified).
|
||||
+ let resources = object_resources().lock().map_err(|_| hipErrorCode_t::Unknown)?;
|
||||
+ let bytes = resources.get(&(tex_object as usize)).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ std::ptr::copy_nonoverlapping(bytes.as_ptr(), (descriptor as *mut HIP_RESOURCE_DESC).cast::<u8>(), bytes.len());
|
||||
+ Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn ref_set_array(
|
||||
texref: *mut textureReference,
|
||||
array: hipArray_t,
|
||||
diff --git a/zluda/src/impl/array.rs b/zluda/src/impl/array.rs
|
||||
--- a/zluda/src/impl/array.rs
|
||||
+++ b/zluda/src/impl/array.rs
|
||||
@@ -70,5 +70,23 @@
|
||||
handle: *mut hipArray_t,
|
||||
desc: *const HIP_ARRAY3D_DESCRIPTOR,
|
||||
) -> hipError_t {
|
||||
hipArray3DCreate(handle, desc)
|
||||
}
|
||||
+
|
||||
+pub(crate) unsafe fn get_descriptor_v2(
|
||||
+ output: &mut HIP_ARRAY_DESCRIPTOR,
|
||||
+ array: hipArray_t,
|
||||
+) -> hipError_t {
|
||||
+ hipArrayGetDescriptor(output, array)?;
|
||||
+ if let Some(format) = original_format(array)? {
|
||||
+ output.Format = hipArray_Format(format);
|
||||
+ }
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
+pub(crate) unsafe fn _3d_get_descriptor_v2(
|
||||
+ output: &mut HIP_ARRAY3D_DESCRIPTOR,
|
||||
+ array: hipArray_t,
|
||||
+) -> hipError_t {
|
||||
+ hipArray3DGetDescriptor(output, array)
|
||||
+}
|
||||
@@ -1,440 +0,0 @@
|
||||
Subject: [PATCH] Guard and own native overrides; capture K replay on Windows
|
||||
|
||||
Native replacements require the upstream PTX identity manifest. Preserve native
|
||||
module/image ownership, bypass redundant JIT for completely replaced modules,
|
||||
include the loaded Windows DLL fingerprint in cache identity, and expose opt-in
|
||||
private real-weight replay captures with explicit native/translated launch logs.
|
||||
|
||||
diff --git a/zluda/src/impl/d4r_replay.rs b/zluda/src/impl/d4r_replay.rs
|
||||
new file mode 100644
|
||||
index 0000000..875b2e3
|
||||
--- /dev/null
|
||||
+++ b/zluda/src/impl/d4r_replay.rs
|
||||
@@ -0,0 +1,109 @@
|
||||
+//! Opt-in, synchronous diagnostic captures. Never enabled in the game fast path.
|
||||
+//! Captures can contain proprietary network weights; keep them out of releases.
|
||||
+use hip_runtime_sys::*;
|
||||
+use std::{collections::HashMap, ffi::{c_void, CStr}, fs, path::PathBuf, sync::{Mutex, OnceLock}};
|
||||
+
|
||||
+struct Config { directory: PathBuf, filter: String, limit: u32 }
|
||||
+static CONFIG: OnceLock<Option<Config>> = OnceLock::new();
|
||||
+static COUNTS: OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new();
|
||||
+
|
||||
+pub(crate) struct Capture { directory: PathBuf, allocations: Vec<(usize, usize)> }
|
||||
+
|
||||
+fn io_error(error: impl std::fmt::Display) -> hipErrorCode_t {
|
||||
+ eprintln!("[d4r-replay] capture failed: {error}");
|
||||
+ hipErrorCode_t::r#Unknown
|
||||
+}
|
||||
+
|
||||
+impl Capture {
|
||||
+ fn save(&self, destination: &std::path::Path) -> hipError_t {
|
||||
+ fs::create_dir_all(destination).map_err(io_error)?;
|
||||
+ for (id, &(base, size)) in self.allocations.iter().enumerate() {
|
||||
+ let mut bytes = vec![0u8; size];
|
||||
+ unsafe { hipMemcpyDtoH(bytes.as_mut_ptr().cast(), hipDeviceptr_t(base as *mut c_void), size) }?;
|
||||
+ fs::write(destination.join(format!("alloc-{id}.bin")), bytes).map_err(io_error)?;
|
||||
+ }
|
||||
+ Ok(())
|
||||
+ }
|
||||
+
|
||||
+ pub(crate) fn finish(self, stream: hipStream_t) -> hipError_t {
|
||||
+ unsafe { hipStreamSynchronize(stream) }?;
|
||||
+ self.save(&self.directory.join("gpu-output"))?;
|
||||
+ eprintln!("[d4r-replay] complete path={}", self.directory.display());
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+pub(crate) unsafe fn begin(
|
||||
+ name: &CStr, stream: hipStream_t, parameters: *mut *mut c_void,
|
||||
+ extra: *mut *mut c_void, launch: [u32; 7],
|
||||
+) -> Result<Option<Capture>, hipErrorCode_t> {
|
||||
+ let Some(config) = CONFIG.get_or_init(|| std::env::var_os("D4R_CUDA_REPLAY_DUMP_DIR").map(|directory| Config {
|
||||
+ directory: directory.into(),
|
||||
+ filter: std::env::var("D4R_CUDA_REPLAY_DUMP_FILTER").unwrap_or_default(),
|
||||
+ limit: std::env::var("D4R_CUDA_REPLAY_DUMP_LIMIT").ok().and_then(|s| s.parse().ok()).unwrap_or(1),
|
||||
+ })) else { return Ok(None) };
|
||||
+ let name = name.to_str().map_err(io_error)?;
|
||||
+ // Only the audited, single 176-byte K argument ABI is captured here.
|
||||
+ if !name.starts_with("dltss_pwin_") || !name.ends_with("_layer") || !name.contains(&config.filter) {
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ let mut block = std::ptr::null::<u8>();
|
||||
+ let mut bytes = None;
|
||||
+ if !extra.is_null() {
|
||||
+ for i in (0..6).step_by(2) {
|
||||
+ match *extra.add(i) as usize {
|
||||
+ 1 => block = (*extra.add(i + 1)).cast(),
|
||||
+ 2 => bytes = Some(*(*extra.add(i + 1) as *const usize)),
|
||||
+ 0 => break,
|
||||
+ _ => return Err(hipErrorCode_t::r#InvalidValue),
|
||||
+ }
|
||||
+ }
|
||||
+ } else if !parameters.is_null() {
|
||||
+ block = (*parameters).cast();
|
||||
+ bytes = Some(176);
|
||||
+ }
|
||||
+ if block.is_null() || bytes != Some(176) {
|
||||
+ return Err(io_error(format!("unexpected K parameter ABI for {name}: {bytes:?}")));
|
||||
+ }
|
||||
+ let sequence = {
|
||||
+ let mut counts = COUNTS.get_or_init(|| Mutex::new(HashMap::new())).lock().map_err(io_error)?;
|
||||
+ let count = counts.entry(name.to_owned()).or_default();
|
||||
+ if *count >= config.limit { return Ok(None); }
|
||||
+ *count += 1;
|
||||
+ *count
|
||||
+ };
|
||||
+ hipStreamSynchronize(stream)?;
|
||||
+ let arguments = std::slice::from_raw_parts(block, 176);
|
||||
+ let directory = config.directory.join(format!("replay-{sequence:06}-{name}"));
|
||||
+ fs::create_dir_all(&directory).map_err(io_error)?;
|
||||
+ fs::write(directory.join("args.bin"), arguments).map_err(io_error)?;
|
||||
+ let mut manifest = format!("kernel {name}\nlaunch {} {} {} {} {} {} {}\nargs 176\n",
|
||||
+ launch[0], launch[1], launch[2], launch[3], launch[4], launch[5], launch[6]);
|
||||
+ let mut allocations = Vec::<(usize, usize)>::new();
|
||||
+ for offset in (0..176).step_by(8) {
|
||||
+ let pointer = u64::from_le_bytes(arguments[offset..offset + 8].try_into().unwrap()) as usize;
|
||||
+ if pointer == 0 { continue; }
|
||||
+ let mut base = hipDeviceptr_t(std::ptr::null_mut());
|
||||
+ let mut size = 0;
|
||||
+ if hipMemGetAddressRange(&mut base, &mut size, hipDeviceptr_t(pointer as *mut c_void)).is_err() { continue; }
|
||||
+ let base = base.0 as usize;
|
||||
+ if pointer < base || pointer - base >= size { continue; }
|
||||
+ if size > 512 * 1024 * 1024 { return Err(io_error("allocation exceeds diagnostic 512 MiB limit")); }
|
||||
+ let id = match allocations.iter().position(|&(b, _)| b == base) {
|
||||
+ Some(id) => id,
|
||||
+ None => {
|
||||
+ let id = allocations.len();
|
||||
+ allocations.push((base, size));
|
||||
+ manifest.push_str(&format!("alloc {id} 0x{base:x} {size}\n"));
|
||||
+ id
|
||||
+ }
|
||||
+ };
|
||||
+ manifest.push_str(&format!("pointer {offset} {id} {}\n", pointer - base));
|
||||
+ }
|
||||
+ if allocations.is_empty() { return Err(io_error("no K allocation pointers resolved")); }
|
||||
+ fs::write(directory.join("manifest.txt"), manifest).map_err(io_error)?;
|
||||
+ let capture = Capture { directory, allocations };
|
||||
+ capture.save(&capture.directory)?;
|
||||
+ eprintln!("[d4r-replay] begin kernel={name} allocations={} path={}", capture.allocations.len(), capture.directory.display());
|
||||
+ Ok(Some(capture))
|
||||
+}
|
||||
diff --git a/zluda/src/impl/function.rs b/zluda/src/impl/function.rs
|
||||
index 033d9a0..7a7470a 100644
|
||||
--- a/zluda/src/impl/function.rs
|
||||
+++ b/zluda/src/impl/function.rs
|
||||
@@ -7,6 +7,8 @@ use hip_runtime_sys::*;
|
||||
use std::mem;
|
||||
|
||||
pub(crate) struct Function {
|
||||
+ pub(crate) name: std::ffi::CString,
|
||||
+ pub(crate) native_owner: Option<super::module::NativeModule>,
|
||||
pub(crate) base: hipFunction_t,
|
||||
pub(crate) sm_version: u32,
|
||||
pub(crate) explicit_args_size_align: Option<Vec<FunctionArgInfo>>,
|
||||
@@ -144,6 +146,8 @@ pub(crate) fn launch_kernel(
|
||||
}
|
||||
translated_extra_storage.as_mut_ptr()
|
||||
};
|
||||
+ let replay = unsafe { super::d4r_replay::begin(&f.name, stream, kernel_params, extra,
|
||||
+ [grid_dim_x, grid_dim_y, grid_dim_z, block_dim_x, block_dim_y, block_dim_z, shared_mem_bytes]) }?;
|
||||
// the parameter block: kernel_params[0] (a single struct parameter) or the extra buffer
|
||||
let prep_key = if let (Some(_), Some(key_offset)) = (f.prep, f.prep_key) {
|
||||
let mut block: *const u8 = std::ptr::null();
|
||||
@@ -206,7 +210,11 @@ pub(crate) fn launch_kernel(
|
||||
}
|
||||
let block_dim_z = if f.block_z != 0 { f.block_z } else { block_dim_z };
|
||||
let (grid_dim_x, grid_dim_y, grid_dim_z) = if f.grid_x != 0 { (f.grid_x, 1, 1) } else { (grid_dim_x, grid_dim_y, grid_dim_z) };
|
||||
- unsafe {
|
||||
+ if std::env::var_os("D4R_ZLUDA_VERBOSE").is_some() {
|
||||
+ eprintln!("[d4r-launch] kernel={:?} backend={} grid={grid_dim_x},{grid_dim_y},{grid_dim_z} block={block_dim_x},{block_dim_y},{block_dim_z}",
|
||||
+ f.name, if f.native_owner.is_some() { "native" } else { "translated" });
|
||||
+ }
|
||||
+ let result = unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
f.base,
|
||||
grid_dim_x,
|
||||
@@ -220,7 +228,10 @@ pub(crate) fn launch_kernel(
|
||||
kernel_params,
|
||||
extra_for_hip,
|
||||
)
|
||||
- }
|
||||
+ };
|
||||
+ result?;
|
||||
+ if let Some(replay) = replay { replay.finish(stream)?; }
|
||||
+ Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_attribute(
|
||||
diff --git a/zluda/src/impl/library.rs b/zluda/src/impl/library.rs
|
||||
index 98be33e..1319792 100644
|
||||
--- a/zluda/src/impl/library.rs
|
||||
+++ b/zluda/src/impl/library.rs
|
||||
@@ -21,8 +21,8 @@ impl Library {
|
||||
let module_lock = self.modules.get(device).ok_or(CUerror::INVALID_DEVICE)?;
|
||||
*module_lock.get_or_init(|| match self.data {
|
||||
LibraryData::Lazy(lib) => {
|
||||
- let (module, sm_version, meta32) = module::load_hip_module(lib)?;
|
||||
- Ok(Module::new(module, sm_version, meta32).wrap())
|
||||
+ let (module, sm_version, meta32, ptx_hash, native_image) = module::load_hip_module(lib)?;
|
||||
+ Ok(Module::new(module, sm_version, meta32, ptx_hash, native_image).wrap())
|
||||
}
|
||||
LibraryData::Eager(()) => Err(CUerror::NOT_SUPPORTED),
|
||||
})
|
||||
diff --git a/zluda/src/impl/mod.rs b/zluda/src/impl/mod.rs
|
||||
index 5329d32..14e0aee 100644
|
||||
--- a/zluda/src/impl/mod.rs
|
||||
+++ b/zluda/src/impl/mod.rs
|
||||
@@ -5,6 +5,7 @@ pub(super) mod array;
|
||||
pub(super) mod context;
|
||||
pub(super) mod device;
|
||||
pub(super) mod driver;
|
||||
+pub(super) mod d4r_replay;
|
||||
pub(super) mod event;
|
||||
pub(super) mod function;
|
||||
pub(super) mod graph;
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 9f72f2f..5a79368 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -19,6 +19,8 @@ pub(crate) struct Module {
|
||||
pub(crate) base: hipModule_t,
|
||||
pub(crate) sm_version: u32,
|
||||
pub(crate) bit32: Option<Metadata32Bit>,
|
||||
+ ptx_hash: Option<u64>,
|
||||
+ _native_base_image: Option<Vec<u8>>,
|
||||
mutable: Mutex<ModuleMutable>,
|
||||
}
|
||||
|
||||
@@ -33,12 +35,14 @@ impl ModuleMutable {
|
||||
sm_version: u32,
|
||||
name: CString,
|
||||
explicit_args_size_align: Option<Vec<FunctionArgInfo>>,
|
||||
+ ptx_hash: Option<u64>,
|
||||
) -> Result<&Function, CUerror> {
|
||||
Ok(match self.functions.entry(name) {
|
||||
hash_map::Entry::Occupied(entry) => &*entry.into_mut(),
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
- let (func_handle, prep, block_z, grid_x, prep_key, prep_slots) = match native_override(entry.key()) {
|
||||
- Some(native) => native,
|
||||
+ let mut native_owner = None;
|
||||
+ let (func_handle, prep, block_z, grid_x, prep_key, prep_slots) = match native_override(entry.key(), ptx_hash) {
|
||||
+ Some((native, owner)) => { native_owner = Some(owner); native },
|
||||
None => {
|
||||
let mut func_handle = unsafe { std::mem::zeroed() };
|
||||
unsafe { hipModuleGetFunction(&mut func_handle, module, entry.key().as_ptr()) }?;
|
||||
@@ -46,6 +50,8 @@ impl ModuleMutable {
|
||||
}
|
||||
};
|
||||
let func = Box::new(Function {
|
||||
+ name: entry.key().clone(),
|
||||
+ native_owner,
|
||||
base: func_handle,
|
||||
sm_version,
|
||||
explicit_args_size_align,
|
||||
@@ -69,19 +75,48 @@ impl ModuleMutable {
|
||||
/// same stream with `d4r_prep_blocks` (a u32 global in the code object) blocks of 128 threads.
|
||||
/// An optional u32 global `d4r_block_z` replaces the launch's block z dimension, and `d4r_grid_x`
|
||||
/// (persistent kernels, which derive the original grid from their parameters) the grid.
|
||||
+pub(crate) struct NativeModule {
|
||||
+ module: hipModule_t,
|
||||
+ _image: Vec<u8>,
|
||||
+}
|
||||
+
|
||||
+impl Drop for NativeModule {
|
||||
+ fn drop(&mut self) {
|
||||
+ if self.module.0.is_null() { return; }
|
||||
+ if let Err(error) = unsafe { hipModuleUnload(self.module) } {
|
||||
+ eprintln!("[d4r-native] unload failed: {error:?}");
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
fn native_override(
|
||||
name: &CStr,
|
||||
-) -> Option<(hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, Option<u32>, u32)> {
|
||||
+ ptx_hash: Option<u64>,
|
||||
+) -> Option<((hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, Option<u32>, u32), NativeModule)> {
|
||||
let dir = std::env::var_os("D4R_ZLUDA_NATIVE_DIR")?;
|
||||
+ let manifest = std::path::Path::new(&dir).join("d4r-kernels.txt");
|
||||
+ let allowed = fs::read_to_string(&manifest).ok().is_some_and(|text| text.lines().any(|line| {
|
||||
+ let mut fields = line.split_whitespace();
|
||||
+ fields.next() == name.to_str().ok()
|
||||
+ && fields.next().and_then(|s| u64::from_str_radix(s, 16).ok()) == ptx_hash
|
||||
+ && ptx_hash.is_some()
|
||||
+ }));
|
||||
+ if !allowed {
|
||||
+ eprintln!("[d4r-native] miss kernel={name:?} reason=ptx_identity hash={ptx_hash:016x?} fallback=translated");
|
||||
+ return None;
|
||||
+ }
|
||||
let path = std::path::Path::new(&dir).join(format!("{}.hsaco", name.to_str().ok()?));
|
||||
- let image = fs::read(&path).ok()?;
|
||||
- // HIP may reference the image after loading; the override lives for the whole process.
|
||||
- let image: &'static [u8] = Box::leak(image.into_boxed_slice());
|
||||
+ let image = match fs::read(&path) {
|
||||
+ Ok(image) => image,
|
||||
+ Err(error) => { eprintln!("[d4r-native] miss kernel={name:?} reason=object_missing error={error} fallback=translated"); return None; }
|
||||
+ };
|
||||
let mut native_module = unsafe { mem::zeroed() };
|
||||
if unsafe { hipModuleLoadData(&mut native_module, image.as_ptr().cast()) }.is_err() {
|
||||
eprintln!("[d4r-native] failed to load {}", path.display());
|
||||
return None;
|
||||
}
|
||||
+ // Keep the image and module owned by the CUfunction; release at module unload.
|
||||
+ let owner = NativeModule { module: native_module, _image: image };
|
||||
let mut func = unsafe { mem::zeroed() };
|
||||
if unsafe { hipModuleGetFunction(&mut func, native_module, name.as_ptr()) }.is_err() {
|
||||
eprintln!("[d4r-native] {} has no kernel {:?}", path.display(), name);
|
||||
@@ -138,7 +173,7 @@ fn native_override(
|
||||
prep_key,
|
||||
prep_slots
|
||||
);
|
||||
- Some((func, prep, block_z, grid_x, prep_key, prep_slots))
|
||||
+ Some(((func, prep, block_z, grid_x, prep_key, prep_slots), owner))
|
||||
}
|
||||
|
||||
impl ZludaObject for Module {
|
||||
@@ -229,7 +264,7 @@ pub(crate) struct Global32Bit {
|
||||
}
|
||||
|
||||
impl Module {
|
||||
- pub(crate) fn new(base: hipModule_t, sm_version: u32, bit32: Option<Metadata32Bit>) -> Self {
|
||||
+ pub(crate) fn new(base: hipModule_t, sm_version: u32, bit32: Option<Metadata32Bit>, ptx_hash: Option<u64>, native_base_image: Option<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
base,
|
||||
sm_version,
|
||||
@@ -237,6 +272,8 @@ impl Module {
|
||||
functions: FxHashMap::default(),
|
||||
}),
|
||||
bit32,
|
||||
+ ptx_hash,
|
||||
+ _native_base_image: native_base_image,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +285,7 @@ impl Module {
|
||||
.map(Vec::clone)
|
||||
});
|
||||
mutable
|
||||
- .get_function(self.base, self.sm_version, name.to_owned(), explicit_args)
|
||||
+ .get_function(self.base, self.sm_version, name.to_owned(), explicit_args, self.ptx_hash)
|
||||
.map(|f| unsafe { (f as *const Function).as_ref().unwrap() })
|
||||
}
|
||||
}
|
||||
@@ -256,7 +293,7 @@ impl Module {
|
||||
fn get_best_ptx_and_compile(
|
||||
global_state: &GlobalState,
|
||||
image: CodeLibraryRef<'_>,
|
||||
-) -> Result<(hipModule_t, u32, Option<Metadata32Bit>), CUerror> {
|
||||
+) -> Result<(hipModule_t, u32, Option<Metadata32Bit>, Option<u64>, Option<Vec<u8>>), CUerror> {
|
||||
let mut ptx_modules = Vec::new();
|
||||
unsafe {
|
||||
CodeLibraryRef::iterate_modules(image, |_, module| match module {
|
||||
@@ -280,7 +317,7 @@ fn get_best_ptx_and_compile(
|
||||
let dump_dir = std::path::PathBuf::from(dump_dir);
|
||||
let _ = fs::create_dir_all(&dump_dir);
|
||||
for (index, source) in ptx_modules.iter().enumerate() {
|
||||
- let stem = format!("module-{index:04}");
|
||||
+ let stem = format!("module-{:016x}-{index:04}", ptx_identity(source));
|
||||
let _ = fs::write(dump_dir.join(format!("{stem}.ptx")), source.as_bytes());
|
||||
let diagnostic = match ptx_parser::parse_module_checked(source.as_ref()) {
|
||||
Ok(ast) => format!(
|
||||
@@ -345,6 +382,32 @@ fn get_best_ptx_and_compile(
|
||||
)
|
||||
}
|
||||
};
|
||||
+ let ptx_hash = text.map(|source| ptx_identity(source));
|
||||
+ // A fully replaced, single-entry module needs no translated binary. LDS
|
||||
+ // declarations belong to the replaced kernel; exported globals do not.
|
||||
+ let only_kernel_and_lds = module.directives.iter().all(|directive| match directive {
|
||||
+ ptx_parser::Directive::Method(_, method) => method.func_directive.name.is_kernel(),
|
||||
+ ptx_parser::Directive::Variable(_, variable) => matches!(variable.info.state_space, ptx_parser::StateSpace::Shared),
|
||||
+ });
|
||||
+ let entries: Vec<_> = module.directives.iter().filter_map(|directive| match directive {
|
||||
+ ptx_parser::Directive::Method(_, method) => Some(method), _ => None,
|
||||
+ }).collect();
|
||||
+ if module.address_size == 64 && only_kernel_and_lds && entries.len() == 1 {
|
||||
+ let method = entries[0];
|
||||
+ {
|
||||
+ if method.func_directive.name.is_kernel() {
|
||||
+ if let Ok(name) = CString::new(method.func_directive.name.text()) {
|
||||
+ if let Some((_, mut owner)) = native_override(&name, ptx_hash) {
|
||||
+ let base = owner.module;
|
||||
+ owner.module = hipModule_t(std::ptr::null_mut());
|
||||
+ let image = mem::take(&mut owner._image);
|
||||
+ eprintln!("[d4r-native] module_direct kernel={name:?} translated_jit=skipped");
|
||||
+ return Ok((base, module.sm_version, None, ptx_hash, Some(image)));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
// TODO: get this information on initialization
|
||||
let hip_properties = get_hip_properties()?;
|
||||
let gcn_arch = get_gcn_arch(&hip_properties)?;
|
||||
@@ -372,7 +435,12 @@ fn get_best_ptx_and_compile(
|
||||
.or_else(|_| compile_and_cache(gcn_arch, attributes, module, &mut cache_with_key))?;
|
||||
let mut hip_module = unsafe { mem::zeroed() };
|
||||
unsafe { hipModuleLoadData(&mut hip_module, elf_module.as_ptr().cast()) }?;
|
||||
- Ok((hip_module, sm_version, zluda32))
|
||||
+ Ok((hip_module, sm_version, zluda32, ptx_hash, None))
|
||||
+}
|
||||
+
|
||||
+fn ptx_identity(text: &str) -> u64 {
|
||||
+ text.trim_end_matches('\0').bytes().fold(0xcbf29ce484222325u64, |hash, byte|
|
||||
+ (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3))
|
||||
}
|
||||
|
||||
fn cow_bytes_to_str<'a>(data: Cow<'a, [u8]>) -> Option<Cow<'a, str>> {
|
||||
@@ -384,7 +452,7 @@ fn cow_bytes_to_str<'a>(data: Cow<'a, [u8]>) -> Option<Cow<'a, str>> {
|
||||
|
||||
pub(crate) fn load_hip_module(
|
||||
library: CodeLibraryRef,
|
||||
-) -> Result<(hipModule_t, u32, Option<Metadata32Bit>), CUerror> {
|
||||
+) -> Result<(hipModule_t, u32, Option<Metadata32Bit>, Option<u64>, Option<Vec<u8>>), CUerror> {
|
||||
let global_state = driver::global_state()?;
|
||||
get_best_ptx_and_compile(global_state, library)
|
||||
}
|
||||
@@ -434,7 +502,7 @@ fn zluda_build_version() -> &'static str {
|
||||
key.starts_with("D4R_ZLUDA_")
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
- "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA"
|
||||
+ "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE"
|
||||
| "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
@@ -469,7 +537,10 @@ fn loaded_library_fingerprint() -> Option<String> {
|
||||
|
||||
#[cfg(windows)]
|
||||
fn loaded_library_fingerprint() -> Option<String> {
|
||||
- None
|
||||
+ let path = zluda_windows::get_module_path_for_function(zluda_build_version as *const () as usize)?;
|
||||
+ let metadata = fs::metadata(path).ok()?;
|
||||
+ let timestamp = metadata.modified().ok()?.duration_since(std::time::UNIX_EPOCH).ok()?;
|
||||
+ Some(format!("{:x}.{:x}.{:x}", metadata.len(), timestamp.as_secs(), timestamp.subsec_nanos()))
|
||||
}
|
||||
|
||||
fn get_cache_key<'a, 'b>(
|
||||
@@ -577,16 +648,16 @@ pub(crate) fn load(module: &mut CUmodule, fname: &CStr) -> CUresult {
|
||||
image.push(0);
|
||||
let library = unsafe { CodeLibraryRef::try_load(image.as_ptr() as *const std::ffi::c_void) }
|
||||
.map_err(|_| CUerror::NO_BINARY_FOR_GPU)?;
|
||||
- let (hip_module, sm_version, meta32) = load_hip_module(library)?;
|
||||
- *module = Module::new(hip_module, sm_version, meta32).wrap();
|
||||
+ let (hip_module, sm_version, meta32, ptx_hash, native_image) = load_hip_module(library)?;
|
||||
+ *module = Module::new(hip_module, sm_version, meta32, ptx_hash, native_image).wrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_data(module: &mut CUmodule, image: &std::ffi::c_void) -> CUresult {
|
||||
let library =
|
||||
unsafe { CodeLibraryRef::try_load(image) }.map_err(|_| CUerror::NO_BINARY_FOR_GPU)?;
|
||||
- let (hip_module, sm_version, meta32) = load_hip_module(library)?;
|
||||
- *module = Module::new(hip_module, sm_version, meta32).wrap();
|
||||
+ let (hip_module, sm_version, meta32, ptx_hash, native_image) = load_hip_module(library)?;
|
||||
+ *module = Module::new(hip_module, sm_version, meta32, ptx_hash, native_image).wrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
diff --git a/zluda/src/impl/d4r_replay.rs b/zluda/src/impl/d4r_replay.rs
|
||||
index 875b2e3..d491ae2 100644
|
||||
--- a/zluda/src/impl/d4r_replay.rs
|
||||
+++ b/zluda/src/impl/d4r_replay.rs
|
||||
@@ -7,6 +7,18 @@ struct Config { directory: PathBuf, filter: String, limit: u32 }
|
||||
static CONFIG: OnceLock<Option<Config>> = OnceLock::new();
|
||||
static COUNTS: OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new();
|
||||
|
||||
+fn parameter_size(name: &str) -> Option<usize> {
|
||||
+ match name {
|
||||
+ "dltss_pwin_enc0_layer" | "dltss_pwin_enc1_layer" | "dltss_pwin_enc2_layer" |
|
||||
+ "dltss_pwin_enc3_layer" | "dltss_pwin_enc4_layer" | "dltss_pwin_dec5_layer" |
|
||||
+ "dltss_pwin_dec4_layer" | "dltss_pwin_dec3_layer" | "dltss_pwin_dec2_layer" |
|
||||
+ "dltss_pwin_dec1_layer" | "dltss_pwin_dec0_layer" => Some(176),
|
||||
+ "rrlite_enc1_4x4" | "rrlite_enc2_4x4" | "rrlite_dec2_4x4" | "rrlite_dec1_4x4" => Some(56),
|
||||
+ "rrlite_enc3_tube_4x4" => Some(88),
|
||||
+ _ => None,
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
pub(crate) struct Capture { directory: PathBuf, allocations: Vec<(usize, usize)> }
|
||||
|
||||
fn io_error(error: impl std::fmt::Display) -> hipErrorCode_t {
|
||||
@@ -43,8 +55,10 @@ pub(crate) unsafe fn begin(
|
||||
limit: std::env::var("D4R_CUDA_REPLAY_DUMP_LIMIT").ok().and_then(|s| s.parse().ok()).unwrap_or(1),
|
||||
})) else { return Ok(None) };
|
||||
let name = name.to_str().map_err(io_error)?;
|
||||
- // Only the audited, single 176-byte K argument ABI is captured here.
|
||||
- if !name.starts_with("dltss_pwin_") || !name.ends_with("_layer") || !name.contains(&config.filter) {
|
||||
+ // Only audited single-by-value K/M argument blocks. Never infer the size
|
||||
+ // from a prefix: reading a different kernel's parameters can overrun host memory.
|
||||
+ let Some(parameter_bytes) = parameter_size(name) else { return Ok(None); };
|
||||
+ if !name.contains(&config.filter) {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut block = std::ptr::null::<u8>();
|
||||
@@ -60,10 +74,10 @@ pub(crate) unsafe fn begin(
|
||||
}
|
||||
} else if !parameters.is_null() {
|
||||
block = (*parameters).cast();
|
||||
- bytes = Some(176);
|
||||
+ bytes = Some(parameter_bytes);
|
||||
}
|
||||
- if block.is_null() || bytes != Some(176) {
|
||||
- return Err(io_error(format!("unexpected K parameter ABI for {name}: {bytes:?}")));
|
||||
+ if block.is_null() || bytes != Some(parameter_bytes) {
|
||||
+ return Err(io_error(format!("unexpected parameter ABI for {name}: {bytes:?}, expected {parameter_bytes}")));
|
||||
}
|
||||
let sequence = {
|
||||
let mut counts = COUNTS.get_or_init(|| Mutex::new(HashMap::new())).lock().map_err(io_error)?;
|
||||
@@ -73,14 +87,14 @@ pub(crate) unsafe fn begin(
|
||||
*count
|
||||
};
|
||||
hipStreamSynchronize(stream)?;
|
||||
- let arguments = std::slice::from_raw_parts(block, 176);
|
||||
+ let arguments = std::slice::from_raw_parts(block, parameter_bytes);
|
||||
let directory = config.directory.join(format!("replay-{sequence:06}-{name}"));
|
||||
fs::create_dir_all(&directory).map_err(io_error)?;
|
||||
fs::write(directory.join("args.bin"), arguments).map_err(io_error)?;
|
||||
- let mut manifest = format!("kernel {name}\nlaunch {} {} {} {} {} {} {}\nargs 176\n",
|
||||
+ let mut manifest = format!("kernel {name}\nlaunch {} {} {} {} {} {} {}\nargs {parameter_bytes}\n",
|
||||
launch[0], launch[1], launch[2], launch[3], launch[4], launch[5], launch[6]);
|
||||
let mut allocations = Vec::<(usize, usize)>::new();
|
||||
- for offset in (0..176).step_by(8) {
|
||||
+ for offset in (0..parameter_bytes).step_by(8) {
|
||||
let pointer = u64::from_le_bytes(arguments[offset..offset + 8].try_into().unwrap()) as usize;
|
||||
if pointer == 0 { continue; }
|
||||
let mut base = hipDeviceptr_t(std::ptr::null_mut());
|
||||
@@ -100,10 +114,23 @@ pub(crate) unsafe fn begin(
|
||||
};
|
||||
manifest.push_str(&format!("pointer {offset} {id} {}\n", pointer - base));
|
||||
}
|
||||
- if allocations.is_empty() { return Err(io_error("no K allocation pointers resolved")); }
|
||||
+ if allocations.is_empty() { return Err(io_error("no allocation pointers resolved")); }
|
||||
fs::write(directory.join("manifest.txt"), manifest).map_err(io_error)?;
|
||||
let capture = Capture { directory, allocations };
|
||||
capture.save(&capture.directory)?;
|
||||
eprintln!("[d4r-replay] begin kernel={name} allocations={} path={}", capture.allocations.len(), capture.directory.display());
|
||||
Ok(Some(capture))
|
||||
}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ #[test]
|
||||
+ fn only_audited_argument_sizes() {
|
||||
+ assert_eq!(super::parameter_size("dltss_pwin_enc0_layer"), Some(176));
|
||||
+ assert_eq!(super::parameter_size("rrlite_dec2_4x4"), Some(56));
|
||||
+ assert_eq!(super::parameter_size("rrlite_enc3_tube_4x4"), Some(88));
|
||||
+ assert_eq!(super::parameter_size("dltss_pwin_enc0_layer_prep"), None);
|
||||
+ assert_eq!(super::parameter_size("rrlite_enc3_tube_4x4_prep"), None);
|
||||
+ assert_eq!(super::parameter_size("dltss_pwin_unknown_layer"), None);
|
||||
+ }
|
||||
+}
|
||||
@@ -1,65 +0,0 @@
|
||||
diff --git a/zluda_cache/src/lib.rs b/zluda_cache/src/lib.rs
|
||||
index acd262e..c3ac1be 100644
|
||||
--- a/zluda_cache/src/lib.rs
|
||||
+++ b/zluda_cache/src/lib.rs
|
||||
@@ -22,18 +22,36 @@ pub struct ModuleCache(SqliteConnection);
|
||||
|
||||
impl ModuleCache {
|
||||
pub fn create_cache_dir_and_get_path() -> Option<String> {
|
||||
- let mut cache_dir = dirs::cache_dir()?;
|
||||
- cache_dir.extend(["zluda", "ComputeCache"]);
|
||||
+ let override_dir = std::env::var_os("ZLUDA_CACHE_DIR");
|
||||
+ let cache_dir = match override_dir {
|
||||
+ Some(path) => {
|
||||
+ let path = std::path::PathBuf::from(path);
|
||||
+ if !path.is_absolute() {
|
||||
+ eprintln!("[zluda-cache] ZLUDA_CACHE_DIR must be absolute; cache disabled");
|
||||
+ return None;
|
||||
+ }
|
||||
+ path
|
||||
+ }
|
||||
+ None => dirs::cache_dir()?.join("zluda").join("ComputeCache"),
|
||||
+ };
|
||||
+ Self::cache_path(&cache_dir)
|
||||
+ }
|
||||
+
|
||||
+ fn cache_path(cache_dir: &std::path::Path) -> Option<String> {
|
||||
// We ensure that the cache directory exists
|
||||
- std::fs::create_dir_all(&cache_dir).ok()?;
|
||||
+ if let Err(error) = std::fs::create_dir_all(cache_dir) {
|
||||
+ eprintln!("[zluda-cache] cannot create {}: {error}; cache disabled", cache_dir.display());
|
||||
+ return None;
|
||||
+ }
|
||||
// No need to create the file, it will be created by SQLite on first access
|
||||
// zluda.db might be in use by older versions of ZLUDA
|
||||
- cache_dir.push("zluda2.db");
|
||||
- Some(cache_dir.to_string_lossy().into())
|
||||
+ Some(cache_dir.join("zluda2.db").to_string_lossy().into())
|
||||
}
|
||||
|
||||
pub fn open(file_path: &str) -> Option<Self> {
|
||||
- let mut conn = SqliteConnection::establish(file_path).ok()?;
|
||||
+ let mut conn = SqliteConnection::establish(file_path).map_err(|error| {
|
||||
+ eprintln!("[zluda-cache] cannot open {file_path}: {error}; cache disabled");
|
||||
+ }).ok()?;
|
||||
conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")
|
||||
.ok()?;
|
||||
conn.run_pending_migrations(MIGRATIONS).ok()?;
|
||||
@@ -86,6 +104,17 @@ mod tests {
|
||||
use arrayvec::ArrayString;
|
||||
use diesel::prelude::*;
|
||||
|
||||
+ #[test]
|
||||
+ fn explicit_directory_creates_persistent_database() {
|
||||
+ let directory = std::env::temp_dir().join(format!("zluda-cache-test-{}-{}", std::process::id(), ModuleCache::time_now()));
|
||||
+ let path = ModuleCache::cache_path(&directory).unwrap();
|
||||
+ assert_eq!(std::path::Path::new(&path), directory.join("zluda2.db"));
|
||||
+ let cache = ModuleCache::open(&path).unwrap();
|
||||
+ drop(cache);
|
||||
+ assert!(std::path::Path::new(&path).is_file());
|
||||
+ std::fs::remove_dir_all(directory).unwrap();
|
||||
+ }
|
||||
+
|
||||
#[derive(Queryable, Selectable)]
|
||||
#[diesel(table_name = crate::schema::modules)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
@@ -1,151 +0,0 @@
|
||||
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,148 +0,0 @@
|
||||
From b0161a4166652eee86a8113a38d042b02403db6e Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 18:07:07 +0300
|
||||
Subject: [PATCH] Add opt-in Windows HIP event and occupancy diagnostics
|
||||
|
||||
---
|
||||
zluda/src/impl/d4r_profile.rs | 60 +++++++++++++++++++++++++++++++++++
|
||||
zluda/src/impl/function.rs | 9 ++++++
|
||||
zluda/src/impl/mod.rs | 1 +
|
||||
zluda/src/impl/module.rs | 2 +-
|
||||
4 files changed, 71 insertions(+), 1 deletion(-)
|
||||
create mode 100644 zluda/src/impl/d4r_profile.rs
|
||||
|
||||
diff --git a/zluda/src/impl/d4r_profile.rs b/zluda/src/impl/d4r_profile.rs
|
||||
new file mode 100644
|
||||
index 0000000..33ba5f6
|
||||
--- /dev/null
|
||||
+++ b/zluda/src/impl/d4r_profile.rs
|
||||
@@ -0,0 +1,60 @@
|
||||
+//! Optional, deliberately serializing HIP-event diagnostics. Not a production
|
||||
+//! scheduler or a claim about application FPS / measured hardware occupancy.
|
||||
+use hip_runtime_sys::*;
|
||||
+use std::{ffi::CStr, ptr, time::Instant};
|
||||
+
|
||||
+pub(super) struct Launch {
|
||||
+ start: hipEvent_t,
|
||||
+ end: hipEvent_t,
|
||||
+ host: Instant,
|
||||
+}
|
||||
+
|
||||
+impl Drop for Launch {
|
||||
+ fn drop(&mut self) {
|
||||
+ unsafe {
|
||||
+ if !self.end.is_null() { let _ = hipEventDestroy(self.end); }
|
||||
+ if !self.start.is_null() { let _ = hipEventDestroy(self.start); }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl Launch {
|
||||
+ pub(super) unsafe fn begin(stream: hipStream_t) -> Result<Option<Self>, hipErrorCode_t> {
|
||||
+ if std::env::var_os("D4R_ZLUDA_PROFILE").is_none() { return Ok(None); }
|
||||
+ let mut result = Self { start: ptr::null_mut(), end: ptr::null_mut(), host: Instant::now() };
|
||||
+ hipEventCreateWithFlags(&mut result.start, 0)?;
|
||||
+ hipEventCreateWithFlags(&mut result.end, 0)?;
|
||||
+ hipEventRecord(result.start, stream)?;
|
||||
+ result.host = Instant::now();
|
||||
+ Ok(Some(result))
|
||||
+ }
|
||||
+
|
||||
+ pub(super) unsafe fn finish(self, function: hipFunction_t, name: &CStr, backend: &str,
|
||||
+ phase: &str, grid: [u32;3], block: [u32;3], shared: u32, stream: hipStream_t) -> hipError_t {
|
||||
+ let enqueue_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
+ hipEventRecord(self.end, stream)?;
|
||||
+ hipEventSynchronize(self.end)?;
|
||||
+ let completion_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
+ let mut gpu_ms = 0.;
|
||||
+ hipEventElapsedTime(&mut gpu_ms, self.start, self.end)?;
|
||||
+ // Module function handles use the Driver API attribute query. The
|
||||
+ // plural Runtime API expects a registered host function address.
|
||||
+ let mut lds = 0;
|
||||
+ let mut regs = 0;
|
||||
+ let mut private = 0;
|
||||
+ let mut max_threads = 0;
|
||||
+ hipFuncGetAttribute(&mut lds, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut regs, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS, function)?;
|
||||
+ hipFuncGetAttribute(&mut private, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut max_threads, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, function)?;
|
||||
+ let threads = block.iter().try_fold(1u32, |a, b| a.checked_mul(*b))
|
||||
+ .filter(|n| *n <= i32::MAX as u32).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ let mut blocks_per_multiprocessor = 0;
|
||||
+ hipModuleOccupancyMaxActiveBlocksPerMultiprocessor(&mut blocks_per_multiprocessor,
|
||||
+ function, threads as i32, shared as usize)?;
|
||||
+ eprintln!("D4R_KERNEL_PROFILE kernel={name:?} backend={backend} phase={phase} gpu_ms={gpu_ms:.6} enqueue_ms={enqueue_ms:.6} completion_ms={completion_ms:.6} grid={},{},{} block={},{},{} static_lds_bytes={} dynamic_lds_bytes={shared} regs={} private_bytes={} max_threads={} predicted_blocks_per_multiprocessor={blocks_per_multiprocessor} serializing=1",
|
||||
+ grid[0], grid[1], grid[2], block[0], block[1], block[2], lds,
|
||||
+ regs, private, max_threads);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
diff --git a/zluda/src/impl/function.rs b/zluda/src/impl/function.rs
|
||||
index 7a7470a..c456dfe 100644
|
||||
--- a/zluda/src/impl/function.rs
|
||||
+++ b/zluda/src/impl/function.rs
|
||||
@@ -191,6 +191,7 @@ pub(crate) fn launch_kernel(
|
||||
}
|
||||
};
|
||||
if let (Some((prep, blocks)), true) = (f.prep, run_prep) {
|
||||
+ let profile = unsafe { super::d4r_profile::Launch::begin(stream) }?;
|
||||
unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
prep,
|
||||
@@ -206,6 +207,9 @@ pub(crate) fn launch_kernel(
|
||||
extra_for_hip,
|
||||
)
|
||||
}?;
|
||||
+ if let Some(profile) = profile {
|
||||
+ unsafe { profile.finish(prep, &f.name, "native", "prep", [blocks,1,1], [128,1,1], 0, stream) }?;
|
||||
+ }
|
||||
f.prep_last.store(prep_key, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
let block_dim_z = if f.block_z != 0 { f.block_z } else { block_dim_z };
|
||||
@@ -214,6 +218,7 @@ pub(crate) fn launch_kernel(
|
||||
eprintln!("[d4r-launch] kernel={:?} backend={} grid={grid_dim_x},{grid_dim_y},{grid_dim_z} block={block_dim_x},{block_dim_y},{block_dim_z}",
|
||||
f.name, if f.native_owner.is_some() { "native" } else { "translated" });
|
||||
}
|
||||
+ let profile = unsafe { super::d4r_profile::Launch::begin(stream) }?;
|
||||
let result = unsafe {
|
||||
hipModuleLaunchKernel(
|
||||
f.base,
|
||||
@@ -230,6 +235,10 @@ pub(crate) fn launch_kernel(
|
||||
)
|
||||
};
|
||||
result?;
|
||||
+ if let Some(profile) = profile {
|
||||
+ unsafe { profile.finish(f.base, &f.name, if f.native_owner.is_some() { "native" } else { "translated" },
|
||||
+ "main", [grid_dim_x, grid_dim_y, grid_dim_z], [block_dim_x, block_dim_y, block_dim_z], shared_mem_bytes, stream) }?;
|
||||
+ }
|
||||
if let Some(replay) = replay { replay.finish(stream)?; }
|
||||
Ok(())
|
||||
}
|
||||
diff --git a/zluda/src/impl/mod.rs b/zluda/src/impl/mod.rs
|
||||
index 14e0aee..380eb6e 100644
|
||||
--- a/zluda/src/impl/mod.rs
|
||||
+++ b/zluda/src/impl/mod.rs
|
||||
@@ -6,6 +6,7 @@ pub(super) mod context;
|
||||
pub(super) mod device;
|
||||
pub(super) mod driver;
|
||||
pub(super) mod d4r_replay;
|
||||
+pub(super) mod d4r_profile;
|
||||
pub(super) mod event;
|
||||
pub(super) mod function;
|
||||
pub(super) mod graph;
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 5a79368..232f7d4 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -502,7 +502,7 @@ fn zluda_build_version() -> &'static str {
|
||||
key.starts_with("D4R_ZLUDA_")
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
- "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE"
|
||||
+ "D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE" | "D4R_ZLUDA_PROFILE"
|
||||
| "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
From a1c506fc956cfc347c56c44311669700c0d500ac Mon Sep 17 00:00:00 2001
|
||||
From: Codex <codex@localhost>
|
||||
Date: Thu, 1 Oct 2026 22:16:19 +0300
|
||||
Subject: [PATCH] Profile CUDA host API duration without serializing GPU
|
||||
launches
|
||||
|
||||
---
|
||||
zluda/src/d4r_api_profile.rs | 34 ++++++++++++++++++++++++++++++++++
|
||||
zluda/src/lib.rs | 3 +++
|
||||
2 files changed, 37 insertions(+)
|
||||
create mode 100644 zluda/src/d4r_api_profile.rs
|
||||
|
||||
diff --git a/zluda/src/d4r_api_profile.rs b/zluda/src/d4r_api_profile.rs
|
||||
new file mode 100644
|
||||
index 0000000..ef2da1b
|
||||
--- /dev/null
|
||||
+++ b/zluda/src/d4r_api_profile.rs
|
||||
@@ -0,0 +1,34 @@
|
||||
+//! Host API timings only: no HIP events, stream waits, or altered scheduling.
|
||||
+use std::{cell::RefCell, collections::BTreeMap, sync::OnceLock, time::Instant};
|
||||
+
|
||||
+#[derive(Default)]
|
||||
+struct Row { calls: u64, total_ms: f64, max_ms: f64 }
|
||||
+struct Samples { since: Instant, rows: BTreeMap<&'static str, Row> }
|
||||
+thread_local! {
|
||||
+ static SAMPLES: RefCell<Samples> = RefCell::new(Samples { since: Instant::now(), rows: BTreeMap::new() });
|
||||
+}
|
||||
+pub(crate) struct Call { name: &'static str, start: Instant }
|
||||
+impl Call {
|
||||
+ pub(crate) fn begin(name: &'static str) -> Option<Self> {
|
||||
+ static ENABLED: OnceLock<bool> = OnceLock::new();
|
||||
+ if !*ENABLED.get_or_init(|| std::env::var_os("D4R_ZLUDA_PROFILE_API").is_some()) { return None; }
|
||||
+ Some(Self { name, start: Instant::now() })
|
||||
+ }
|
||||
+}
|
||||
+impl Drop for Call {
|
||||
+ fn drop(&mut self) {
|
||||
+ let ms = self.start.elapsed().as_secs_f64() * 1000.;
|
||||
+ SAMPLES.with(|samples| {
|
||||
+ let mut samples = samples.borrow_mut();
|
||||
+ let row = samples.rows.entry(self.name).or_default();
|
||||
+ row.calls += 1; row.total_ms += ms; row.max_ms = row.max_ms.max(ms);
|
||||
+ let period_ms = samples.since.elapsed().as_secs_f64() * 1000.;
|
||||
+ if period_ms < 2000. { return; }
|
||||
+ for (name, row) in &samples.rows {
|
||||
+ eprintln!("D4R_CUDA_API_PROFILE thread={:?} api={name} calls={} total_ms={:.6} max_ms={:.6} period_ms={period_ms:.6} serializing=0",
|
||||
+ std::thread::current().id(), row.calls, row.total_ms, row.max_ms);
|
||||
+ }
|
||||
+ samples.rows.clear(); samples.since = Instant::now();
|
||||
+ });
|
||||
+ }
|
||||
+}
|
||||
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
|
||||
index ecdeb3e..31bfa42 100644
|
||||
--- a/zluda/src/lib.rs
|
||||
+++ b/zluda/src/lib.rs
|
||||
@@ -2,6 +2,7 @@ use cuda_types::cuda::CUerror;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub(crate) mod r#impl;
|
||||
+mod d4r_api_profile;
|
||||
#[cfg_attr(windows, path = "os_win.rs")]
|
||||
#[cfg_attr(not(windows), path = "os_unix.rs")]
|
||||
mod os;
|
||||
@@ -40,6 +41,7 @@ macro_rules! implemented {
|
||||
if !initialized() {
|
||||
return Err(CUerror::DEINITIALIZED);
|
||||
}
|
||||
+ let _profile = d4r_api_profile::Call::begin(stringify!($fn_name));
|
||||
cuda_macros::cuda_normalize_fn!( crate::r#impl::$fn_name ) ($(zluda_common::FromCuda::<_, CUerror>::from_cuda(&$arg_id)?),*)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -57,6 +59,7 @@ macro_rules! implemented_in_function {
|
||||
if !initialized() {
|
||||
return Err(CUerror::DEINITIALIZED);
|
||||
}
|
||||
+ let _profile = d4r_api_profile::Call::begin(stringify!($fn_name));
|
||||
cuda_macros::cuda_normalize_fn!( crate::r#impl::function::$fn_name ) ($(zluda_common::FromCuda::<_, CUerror>::from_cuda(&$arg_id)?),*)?;
|
||||
Ok(())
|
||||
}
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
From 26a43f7047ab933a4afb5ebdd26d5ba9e51e58a3 Mon Sep 17 00:00:00 2001
|
||||
From: xdfnx-dev <[email protected]>
|
||||
Date: Fri, 2 Oct 2026 11:50:45 +0300
|
||||
Subject: [PATCH 16/17] Sample HIP kernel events without per-launch completion
|
||||
waits
|
||||
|
||||
---
|
||||
zluda/src/impl/context.rs | 4 +-
|
||||
zluda/src/impl/d4r_profile.rs | 204 ++++++++++++++++++++++++++++------
|
||||
zluda/src/impl/module.rs | 1 +
|
||||
3 files changed, 171 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/zluda/src/impl/context.rs b/zluda/src/impl/context.rs
|
||||
index 9ccb372..374d383 100644
|
||||
--- a/zluda/src/impl/context.rs
|
||||
+++ b/zluda/src/impl/context.rs
|
||||
@@ -165,7 +165,7 @@ pub(crate) fn set_cache_config(config: CUfunc_cache) -> CUresult {
|
||||
}
|
||||
|
||||
pub(crate) fn synchronize() -> hipError_t {
|
||||
- unsafe { hipDeviceSynchronize() }
|
||||
+ unsafe { hipDeviceSynchronize()?; super::d4r_profile::collect_completed() }
|
||||
}
|
||||
|
||||
pub(crate) fn synchronize_v2(ctx: &Context) -> hipError_t {
|
||||
@@ -177,7 +177,7 @@ pub(crate) fn synchronize_v2(ctx: &Context) -> hipError_t {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
- unsafe { hipDeviceSynchronize() }
|
||||
+ unsafe { hipDeviceSynchronize()?; super::d4r_profile::collect_completed() }
|
||||
}
|
||||
|
||||
struct SetDeviceOnDrop(i32);
|
||||
diff --git a/zluda/src/impl/d4r_profile.rs b/zluda/src/impl/d4r_profile.rs
|
||||
index 33ba5f6..1b9d248 100644
|
||||
--- a/zluda/src/impl/d4r_profile.rs
|
||||
+++ b/zluda/src/impl/d4r_profile.rs
|
||||
@@ -1,15 +1,29 @@
|
||||
-//! Optional, deliberately serializing HIP-event diagnostics. Not a production
|
||||
-//! scheduler or a claim about application FPS / measured hardware occupancy.
|
||||
+//! Optional HIP-event diagnostics. Deferred mode never waits for its events,
|
||||
+//! skips the legacy default stream and bounds outstanding events per thread.
|
||||
+//! Timestamp insertion still adds overhead; this is not a production scheduler.
|
||||
use hip_runtime_sys::*;
|
||||
-use std::{ffi::CStr, ptr, time::Instant};
|
||||
+use std::{cell::RefCell, ffi::{CStr, CString}, ptr, sync::OnceLock, time::Instant};
|
||||
|
||||
-pub(super) struct Launch {
|
||||
- start: hipEvent_t,
|
||||
- end: hipEvent_t,
|
||||
- host: Instant,
|
||||
+#[derive(Clone, Copy)]
|
||||
+enum Mode { Off, Serial, Deferred(u64) }
|
||||
+fn mode() -> Mode {
|
||||
+ static MODE: OnceLock<Mode> = OnceLock::new();
|
||||
+ *MODE.get_or_init(|| {
|
||||
+ if std::env::var_os("D4R_ZLUDA_PROFILE_DEFERRED").is_some() {
|
||||
+ let every = std::env::var("D4R_ZLUDA_PROFILE_EVERY").ok()
|
||||
+ .and_then(|v| v.parse::<u64>().ok()).filter(|n| *n > 0).unwrap_or(17);
|
||||
+ Mode::Deferred(every)
|
||||
+ } else if std::env::var_os("D4R_ZLUDA_PROFILE").is_some() { Mode::Serial }
|
||||
+ else { Mode::Off }
|
||||
+ })
|
||||
+}
|
||||
+fn allow_legacy() -> bool {
|
||||
+ static ALLOW: OnceLock<bool> = OnceLock::new();
|
||||
+ *ALLOW.get_or_init(|| std::env::var_os("D4R_ZLUDA_PROFILE_ALLOW_LEGACY").is_some())
|
||||
}
|
||||
|
||||
-impl Drop for Launch {
|
||||
+struct Events { start: hipEvent_t, end: hipEvent_t }
|
||||
+impl Drop for Events {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.end.is_null() { let _ = hipEventDestroy(self.end); }
|
||||
@@ -17,44 +31,162 @@ impl Drop for Launch {
|
||||
}
|
||||
}
|
||||
}
|
||||
+impl Events {
|
||||
+ unsafe fn new() -> Result<Self, hipErrorCode_t> {
|
||||
+ let mut result = Self { start: ptr::null_mut(), end: ptr::null_mut() };
|
||||
+ hipEventCreateWithFlags(&mut result.start, 0)?;
|
||||
+ hipEventCreateWithFlags(&mut result.end, 0)?;
|
||||
+ Ok(result)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+struct Description {
|
||||
+ name: CString, backend: String, phase: String, grid: [u32;3], block: [u32;3],
|
||||
+ shared: u32, lds: i32, regs: i32, private: i32, max_threads: i32, blocks_per_multiprocessor: i32,
|
||||
+}
|
||||
+impl Description {
|
||||
+ unsafe fn new(function: hipFunction_t, name: &CStr, backend: &str, phase: &str,
|
||||
+ grid: [u32;3], block: [u32;3], shared: u32) -> Result<Self, hipErrorCode_t> {
|
||||
+ let mut d = Self { name: name.to_owned(), backend: backend.to_owned(), phase: phase.to_owned(),
|
||||
+ grid, block, shared, lds: 0, regs: 0, private: 0, max_threads: 0, blocks_per_multiprocessor: 0 };
|
||||
+ // Copy metadata while the function/module is alive. Collection never
|
||||
+ // dereferences a function handle that the application may have unloaded.
|
||||
+ hipFuncGetAttribute(&mut d.lds, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut d.regs, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS, function)?;
|
||||
+ hipFuncGetAttribute(&mut d.private, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, function)?;
|
||||
+ hipFuncGetAttribute(&mut d.max_threads, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, function)?;
|
||||
+ let threads = block.iter().try_fold(1u32, |a, b| a.checked_mul(*b))
|
||||
+ .filter(|n| *n <= i32::MAX as u32).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
+ hipModuleOccupancyMaxActiveBlocksPerMultiprocessor(&mut d.blocks_per_multiprocessor,
|
||||
+ function, threads as i32, shared as usize)?;
|
||||
+ Ok(d)
|
||||
+ }
|
||||
+ fn print(&self, gpu_ms: f32, enqueue_ms: f64, completion_ms: f64, serializing: bool, stream: usize) {
|
||||
+ if !gpu_ms.is_finite() || gpu_ms < 0. {
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_INVALID kernel={:?} backend={} phase={} gpu_ms={gpu_ms:.6} reason=invalid_elapsed_time serializing={} deferred={} stream=0x{stream:x}",
|
||||
+ self.name, self.backend, self.phase, u32::from(serializing), u32::from(!serializing));
|
||||
+ return;
|
||||
+ }
|
||||
+ eprintln!("D4R_KERNEL_PROFILE kernel={:?} backend={} phase={} gpu_ms={gpu_ms:.6} enqueue_ms={enqueue_ms:.6} completion_ms={completion_ms:.6} grid={},{},{} block={},{},{} static_lds_bytes={} dynamic_lds_bytes={} regs={} private_bytes={} max_threads={} predicted_blocks_per_multiprocessor={} serializing={} deferred={} stream=0x{stream:x} legacy_stream={}",
|
||||
+ self.name, self.backend, self.phase, self.grid[0], self.grid[1], self.grid[2],
|
||||
+ self.block[0], self.block[1], self.block[2], self.lds, self.shared, self.regs,
|
||||
+ self.private, self.max_threads, self.blocks_per_multiprocessor,
|
||||
+ u32::from(serializing), u32::from(!serializing), u32::from(stream <= 1));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+struct Pending { launch: Launch, description: Description, enqueue_ms: f64, stream: usize }
|
||||
+const CAPACITY: usize = 64;
|
||||
+struct Deferred {
|
||||
+ calls: u64, skipped: u64, pending: Vec<Pending>, free: Vec<(i32, Events)>,
|
||||
+}
|
||||
+thread_local! {
|
||||
+ static DEFERRED: RefCell<Deferred> = RefCell::new(Deferred {
|
||||
+ calls: 0, skipped: 0, pending: Vec::new(), free: Vec::new(),
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+impl Deferred {
|
||||
+ unsafe fn collect(&mut self, device: i32) -> hipError_t {
|
||||
+ let mut index = 0;
|
||||
+ while index < self.pending.len() {
|
||||
+ let p = &self.pending[index];
|
||||
+ if p.launch.device != device { index += 1; continue; }
|
||||
+ match hipEventQuery(p.launch.events.end) {
|
||||
+ Err(e) if e == hipErrorCode_t::NotReady => { index += 1; continue; }
|
||||
+ result => result?,
|
||||
+ }
|
||||
+ let mut gpu_ms = 0.;
|
||||
+ hipEventElapsedTime(&mut gpu_ms, p.launch.events.start, p.launch.events.end)?;
|
||||
+ p.description.print(gpu_ms, p.enqueue_ms, p.launch.host.elapsed().as_secs_f64() * 1000., false, p.stream);
|
||||
+ let p = self.pending.swap_remove(index);
|
||||
+ // Re-record only after the old timestamps have been collected.
|
||||
+ self.free.push((device, p.launch.events));
|
||||
+ }
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/// Called only after the application's existing context synchronization, or
|
||||
+/// before a selected launch. No event/stream/device wait is added here.
|
||||
+pub(super) unsafe fn collect_completed() -> hipError_t {
|
||||
+ if !matches!(mode(), Mode::Deferred(_)) { return Ok(()); }
|
||||
+ DEFERRED.with(|state| {
|
||||
+ let mut state = state.borrow_mut();
|
||||
+ if state.pending.is_empty() { return Ok(()); }
|
||||
+ let mut device = 0; hipGetDevice(&mut device)?;
|
||||
+ state.collect(device)
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
+pub(super) struct Launch {
|
||||
+ events: Events,
|
||||
+ host: Instant,
|
||||
+ device: i32,
|
||||
+ deferred: bool,
|
||||
+}
|
||||
|
||||
impl Launch {
|
||||
pub(super) unsafe fn begin(stream: hipStream_t) -> Result<Option<Self>, hipErrorCode_t> {
|
||||
- if std::env::var_os("D4R_ZLUDA_PROFILE").is_none() { return Ok(None); }
|
||||
- let mut result = Self { start: ptr::null_mut(), end: ptr::null_mut(), host: Instant::now() };
|
||||
- hipEventCreateWithFlags(&mut result.start, 0)?;
|
||||
- hipEventCreateWithFlags(&mut result.end, 0)?;
|
||||
- hipEventRecord(result.start, stream)?;
|
||||
- result.host = Instant::now();
|
||||
- Ok(Some(result))
|
||||
+ let mode = mode();
|
||||
+ if matches!(mode, Mode::Off) { return Ok(None); }
|
||||
+ if let Mode::Deferred(every) = mode {
|
||||
+ return DEFERRED.with(|state| {
|
||||
+ let mut state = state.borrow_mut();
|
||||
+ state.calls = state.calls.wrapping_add(1);
|
||||
+ if (state.calls - 1) % every != 0 { return Ok(None); }
|
||||
+ // HIP timing events on the legacy default stream can order
|
||||
+ // other streams. Do not introduce that dependency in this mode.
|
||||
+ if stream.0 as usize <= 1 && !allow_legacy() {
|
||||
+ state.skipped += 1;
|
||||
+ if state.skipped.is_power_of_two() {
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_SKIPPED reason=legacy_default_stream count={} serializing=0", state.skipped);
|
||||
+ }
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ let mut device = 0; hipGetDevice(&mut device)?;
|
||||
+ state.collect(device)?;
|
||||
+ let mut capture = hipStreamCaptureStatus::hipStreamCaptureStatusNone;
|
||||
+ hipStreamIsCapturing(stream, &mut capture)?;
|
||||
+ if capture != hipStreamCaptureStatus::hipStreamCaptureStatusNone {
|
||||
+ state.skipped += 1;
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_SKIPPED reason=stream_capture count={} serializing=0", state.skipped);
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ let events = if let Some(index) = state.free.iter().position(|(d, _)| *d == device) {
|
||||
+ state.free.swap_remove(index).1
|
||||
+ } else if state.pending.len() + state.free.len() < CAPACITY {
|
||||
+ Events::new()?
|
||||
+ } else {
|
||||
+ state.skipped += 1;
|
||||
+ eprintln!("D4R_KERNEL_PROFILE_SKIPPED reason=capacity count={} capacity={CAPACITY} serializing=0", state.skipped);
|
||||
+ return Ok(None);
|
||||
+ };
|
||||
+ hipEventRecord(events.start, stream)?;
|
||||
+ Ok(Some(Self { events, host: Instant::now(), device, deferred: true }))
|
||||
+ });
|
||||
+ }
|
||||
+ let events = Events::new()?;
|
||||
+ hipEventRecord(events.start, stream)?;
|
||||
+ Ok(Some(Self { events, host: Instant::now(), device: -1, deferred: false }))
|
||||
}
|
||||
|
||||
pub(super) unsafe fn finish(self, function: hipFunction_t, name: &CStr, backend: &str,
|
||||
phase: &str, grid: [u32;3], block: [u32;3], shared: u32, stream: hipStream_t) -> hipError_t {
|
||||
let enqueue_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
- hipEventRecord(self.end, stream)?;
|
||||
- hipEventSynchronize(self.end)?;
|
||||
+ hipEventRecord(self.events.end, stream)?;
|
||||
+ let description = Description::new(function, name, backend, phase, grid, block, shared)?;
|
||||
+ if self.deferred {
|
||||
+ DEFERRED.with(|state| state.borrow_mut().pending.push(Pending {
|
||||
+ launch: self, description, enqueue_ms, stream: stream.0 as usize,
|
||||
+ }));
|
||||
+ return Ok(());
|
||||
+ }
|
||||
+ hipEventSynchronize(self.events.end)?;
|
||||
let completion_ms = self.host.elapsed().as_secs_f64() * 1000.;
|
||||
let mut gpu_ms = 0.;
|
||||
- hipEventElapsedTime(&mut gpu_ms, self.start, self.end)?;
|
||||
- // Module function handles use the Driver API attribute query. The
|
||||
- // plural Runtime API expects a registered host function address.
|
||||
- let mut lds = 0;
|
||||
- let mut regs = 0;
|
||||
- let mut private = 0;
|
||||
- let mut max_threads = 0;
|
||||
- hipFuncGetAttribute(&mut lds, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, function)?;
|
||||
- hipFuncGetAttribute(&mut regs, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS, function)?;
|
||||
- hipFuncGetAttribute(&mut private, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, function)?;
|
||||
- hipFuncGetAttribute(&mut max_threads, hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, function)?;
|
||||
- let threads = block.iter().try_fold(1u32, |a, b| a.checked_mul(*b))
|
||||
- .filter(|n| *n <= i32::MAX as u32).ok_or(hipErrorCode_t::InvalidValue)?;
|
||||
- let mut blocks_per_multiprocessor = 0;
|
||||
- hipModuleOccupancyMaxActiveBlocksPerMultiprocessor(&mut blocks_per_multiprocessor,
|
||||
- function, threads as i32, shared as usize)?;
|
||||
- eprintln!("D4R_KERNEL_PROFILE kernel={name:?} backend={backend} phase={phase} gpu_ms={gpu_ms:.6} enqueue_ms={enqueue_ms:.6} completion_ms={completion_ms:.6} grid={},{},{} block={},{},{} static_lds_bytes={} dynamic_lds_bytes={shared} regs={} private_bytes={} max_threads={} predicted_blocks_per_multiprocessor={blocks_per_multiprocessor} serializing=1",
|
||||
- grid[0], grid[1], grid[2], block[0], block[1], block[2], lds,
|
||||
- regs, private, max_threads);
|
||||
+ hipEventElapsedTime(&mut gpu_ms, self.events.start, self.events.end)?;
|
||||
+ description.print(gpu_ms, enqueue_ms, completion_ms, true, stream.0 as usize);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs
|
||||
index 232f7d4..8d43abf 100644
|
||||
--- a/zluda/src/impl/module.rs
|
||||
+++ b/zluda/src/impl/module.rs
|
||||
@@ -503,6 +503,7 @@ fn zluda_build_version() -> &'static str {
|
||||
&& !matches!(
|
||||
key.as_str(),
|
||||
"D4R_ZLUDA_NATIVE_DIR" | "D4R_ZLUDA_DIR" | "D4R_ZLUDA_DUMP_DIR" | "D4R_ZLUDA_DUMP_PTX_DIR" | "D4R_ZLUDA_LIBCUDA" | "D4R_ZLUDA_VERBOSE" | "D4R_ZLUDA_PROFILE"
|
||||
+ | "D4R_ZLUDA_PROFILE_API" | "D4R_ZLUDA_PROFILE_DEFERRED" | "D4R_ZLUDA_PROFILE_EVERY" | "D4R_ZLUDA_PROFILE_ALLOW_LEGACY"
|
||||
| "D4R_ZLUDA_WMMA_FP8_NATIVE" // keyed by effect (ExtraCacheAttributes)
|
||||
)
|
||||
})
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
From 67127dde599879ada022d6439383d682e09e0b36 Mon Sep 17 00:00:00 2001
|
||||
From: xdfnx-dev <[email protected]>
|
||||
Date: Fri, 2 Oct 2026 11:50:45 +0300
|
||||
Subject: [PATCH 17/17] Implement asynchronous 2D array copies for Windows
|
||||
input batching
|
||||
|
||||
---
|
||||
zluda/src/impl/memory.rs | 13 +++++++++++++
|
||||
zluda/src/lib.rs | 1 +
|
||||
2 files changed, 14 insertions(+)
|
||||
|
||||
diff --git a/zluda/src/impl/memory.rs b/zluda/src/impl/memory.rs
|
||||
index b724942..42facb3 100644
|
||||
--- a/zluda/src/impl/memory.rs
|
||||
+++ b/zluda/src/impl/memory.rs
|
||||
@@ -469,6 +469,19 @@ pub(crate) unsafe fn copy_2d_v2(memcpy: hip_Memcpy2D) -> CUresult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+pub(crate) unsafe fn copy_2d_async_v2(memcpy: hip_Memcpy2D, stream: hipStream_t) -> CUresult {
|
||||
+ // The format-80 emulation in copy_2d_v2 uses temporary CPU storage.
|
||||
+ // Reject it explicitly here: an asynchronous copy cannot outlive those
|
||||
+ // temporaries, and the Windows fast path uses canonical float arrays.
|
||||
+ let src_packed = memcpy.srcMemoryType == hipMemoryType::hipMemoryTypeArray
|
||||
+ && array::original_format(memcpy.srcArray)? == Some(80);
|
||||
+ let dst_packed = memcpy.dstMemoryType == hipMemoryType::hipMemoryTypeArray
|
||||
+ && array::original_format(memcpy.dstArray)? == Some(80);
|
||||
+ if src_packed || dst_packed { return Err(CUerror::NOT_SUPPORTED); }
|
||||
+ hipMemcpyParam2DAsync(&memcpy, stream)?;
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
pub(crate) unsafe fn copy_2d_unaligned_v2(memcpy: hip_Memcpy2D) -> CUresult {
|
||||
hipDrvMemcpy2DUnaligned(&memcpy)?;
|
||||
Ok(())
|
||||
diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs
|
||||
index 31bfa42..939981e 100644
|
||||
--- a/zluda/src/lib.rs
|
||||
+++ b/zluda/src/lib.rs
|
||||
@@ -150,6 +150,7 @@ cuda_macros::cuda_function_declarations!(
|
||||
cuMemAlloc_v2,
|
||||
cuMemAllocPitch_v2,
|
||||
cuMemcpy2D_v2,
|
||||
+ cuMemcpy2DAsync_v2,
|
||||
cuMemcpy2DUnaligned_v2,
|
||||
cuMemcpy3D_v2,
|
||||
cuMemcpyAsync,
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
From fb0adc8714f57647320f0601533ec50331e378b0 Mon Sep 17 00:00:00 2001
|
||||
From: xdfnx-dev <[email protected]>
|
||||
Date: Fri, 2 Oct 2026 14:40:56 +0300
|
||||
Subject: [PATCH] Add opt-in RDNA CU/WGP placement for matched native
|
||||
experiments
|
||||
|
||||
---
|
||||
llvm_zluda/src/compile.rs | 42 ++++++++++++++++++++++++++++++++++-----
|
||||
ptx/src/pass/llvm/emit.rs | 3 ++-
|
||||
2 files changed, 39 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/llvm_zluda/src/compile.rs b/llvm_zluda/src/compile.rs
|
||||
index 90c0034..6a2f452 100644
|
||||
--- a/llvm_zluda/src/compile.rs
|
||||
+++ b/llvm_zluda/src/compile.rs
|
||||
@@ -24,6 +24,37 @@ const OCKL_MODULE: &[u8] = include_bytes!("device-libs/ockl.bc");
|
||||
// https://llvm.org/docs/AMDGPUUsage.html#address-spaces
|
||||
const CONSTANT_ADDRESS_SPACE: u32 = 4;
|
||||
|
||||
+/// Opt-in RDNA scheduling experiment; changes placement, not FP semantics.
|
||||
+pub fn wgp_requested() -> bool {
|
||||
+ std::env::var("D4R_ZLUDA_WGP").is_ok_and(|value| value.trim() == "1")
|
||||
+}
|
||||
+
|
||||
+fn target_features(wave64: bool, wgp: bool) -> &'static CStr {
|
||||
+ match (wave64, wgp) {
|
||||
+ (false, false) => c"-wavefrontsize64,+cumode",
|
||||
+ (true, false) => c"-wavefrontsize32,+wavefrontsize64,+cumode",
|
||||
+ (false, true) => c"-wavefrontsize64,-cumode",
|
||||
+ (true, true) => c"-wavefrontsize32,+wavefrontsize64,-cumode",
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod shader_mode_tests {
|
||||
+ use super::target_features;
|
||||
+
|
||||
+ #[test]
|
||||
+ fn wgp_changes_only_cu_placement_in_both_wave_modes() {
|
||||
+ for wave64 in [false, true] {
|
||||
+ let cu = target_features(wave64, false).to_str().unwrap();
|
||||
+ let wgp = target_features(wave64, true).to_str().unwrap();
|
||||
+ assert_eq!(cu.replace("+cumode", "-cumode"), wgp);
|
||||
+ assert_eq!(cu.contains("+wavefrontsize64"), wave64);
|
||||
+ assert_eq!(wgp.contains("+wavefrontsize64"), wave64);
|
||||
+ }
|
||||
+ assert_eq!(target_features(false, false), c"-wavefrontsize64,+cumode");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
fn load_module(ctx: &Context, bc: &[u8], name: &std::ffi::CStr) -> Result<Module, String> {
|
||||
let module = Module::try_from_bitcode(ctx, bc, Some(name))
|
||||
.ok_or(("Failed to parse bitcode").to_string())?;
|
||||
@@ -229,11 +260,12 @@ pub fn compile(
|
||||
let cpu = std::ffi::CString::new(gcn_arch).map_err(|_| ("invalid gcn_arch").to_string())?;
|
||||
// functions without their own target features (the linked helper libraries) take these, so under
|
||||
// D4R_ZLUDA_WAVE64 they must be optimised as wave64 too (lane-id and ballot folding)
|
||||
- let features = if std::env::var("D4R_ZLUDA_WAVE64").map_or(false, |v| v.trim() == "1") {
|
||||
- c"-wavefrontsize32,+wavefrontsize64,+cumode"
|
||||
- } else {
|
||||
- c"-wavefrontsize64,+cumode"
|
||||
- };
|
||||
+ let wgp = wgp_requested();
|
||||
+ if wgp && !gcn_arch.starts_with("gfx11") && !gcn_arch.starts_with("gfx12") {
|
||||
+ return Err(format!("D4R_ZLUDA_WGP requires RDNA3/4, got {gcn_arch}"));
|
||||
+ }
|
||||
+ let features = target_features(
|
||||
+ std::env::var("D4R_ZLUDA_WAVE64").is_ok_and(|v| v.trim() == "1"), wgp);
|
||||
|
||||
let mut target = unsafe { std::mem::zeroed() };
|
||||
let mut err = std::ptr::null_mut();
|
||||
diff --git a/ptx/src/pass/llvm/emit.rs b/ptx/src/pass/llvm/emit.rs
|
||||
index 6af2495..8b67c93 100644
|
||||
--- a/ptx/src/pass/llvm/emit.rs
|
||||
+++ b/ptx/src/pass/llvm/emit.rs
|
||||
@@ -397,12 +397,13 @@ impl<'a, 'input> ModuleEmitContext<'a, 'input> {
|
||||
|
||||
fn emit_target_features(&mut self, fn_: LLVMValueRef) {
|
||||
let value = format!(
|
||||
- "{},+cumode{}",
|
||||
+ "{},{}{}",
|
||||
if crate::pass::wave64_requested() {
|
||||
"-wavefrontsize32,+wavefrontsize64"
|
||||
} else {
|
||||
"+wavefrontsize32,-wavefrontsize64"
|
||||
},
|
||||
+ if llvm_zluda::compile::wgp_requested() { "-cumode" } else { "+cumode" },
|
||||
if cfg!(debug_assertions) {
|
||||
",+precise-memory"
|
||||
} else {
|
||||
--
|
||||
2.55.0.windows.5
|
||||
|
||||
Reference in new issue
Block a user