diff --git a/zstd/zstdgpu_ci_tests/main.cpp b/zstd/zstdgpu_ci_tests/main.cpp index 108bcd4..2483200 100644 --- a/zstd/zstdgpu_ci_tests/main.cpp +++ b/zstd/zstdgpu_ci_tests/main.cpp @@ -116,6 +116,8 @@ static void PrintUsage(const char* exe) << " within --gbv-max-mb). A positive N samples that many files by stride.\n" << " --gpu-name Adapter name of this machine. Consumed only by the manifest's\n" << " scenario_skips; if omitted, no scenario is skipped by GPU name.\n" + << " --gpu-ven-id Optional PCI vendor ID forwarded to zstdgpu_demo for adapter\n" + << " selection (for example, 10de selects NVIDIA).\n" << " --gbv-max-mb Max largest single frame decompressed size for GBV tests in MB (default: 4).\n" << " A value <= 0 disables the cap and runs GBV on files of any size.\n" << " --max-frame-mb Skip any .zst file whose largest single on-disk zstd frame exceeds N MB,\n" @@ -188,6 +190,22 @@ static bool ParseArgs(int argc, char** argv, TestConfig& config, bool& shouldExi { config.gpuName = argv[++i]; } + else if (std::strcmp(argv[i], "--gpu-ven-id") == 0) + { + if (i + 1 >= argc) + { + std::cerr << "Error: --gpu-ven-id requires a hexadecimal value." << std::endl; + return false; + } + std::string parseError; + const std::string value = argv[++i]; + if (!ParseGpuVendorId(value, config.gpuVendorId, parseError)) + { + std::cerr << "Error: invalid --gpu-ven-id '" << value << "': " + << parseError << "." << std::endl; + return false; + } + } else if (std::strcmp(argv[i], "--perf-min-mb") == 0 && i + 1 < argc) { config.perfMinMB = std::atoi(argv[++i]); @@ -261,6 +279,16 @@ static int ValidateAndDiscover(TestConfig& config) std::cout << "Discovered " << config.discoveredFiles.size() << " .zst file(s) at '" << config.contentPath << "'.\n"; + if (config.gpuVendorId != 0) + { + std::cout << "GPU vendor selection enabled: 0x" << std::hex + << config.gpuVendorId << std::dec << ".\n"; + } + else + { + std::cout << "GPU vendor selection not configured; demo default selection will be used.\n"; + } + if (config.logDir.empty()) { config.logDir = std::filesystem::current_path().string(); diff --git a/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.cpp b/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.cpp index 55ef7c8..33e4a11 100644 --- a/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.cpp +++ b/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.cpp @@ -34,15 +34,75 @@ #include "zstdgpu_ci_tests.h" #include "zstd_frame_size.h" #include +#include #include +#include #include #include #include +#include #include +#include #include #include #include +bool ParseGpuVendorId(std::string_view value, uint32_t& vendorId, std::string& error) +{ + vendorId = 0; + error.clear(); + + if (value.size() >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) + { + value.remove_prefix(2); + } + if (value.empty()) + { + error = "GPU vendor ID is empty"; + return false; + } + + uint32_t parsed = 0; + const auto result = std::from_chars(value.data(), value.data() + value.size(), parsed, 16); + if (result.ec == std::errc::result_out_of_range) + { + error = "GPU vendor ID is outside the 32-bit range"; + return false; + } + if (result.ec != std::errc{} || result.ptr != value.data() + value.size()) + { + error = "GPU vendor ID must contain only hexadecimal digits"; + return false; + } + if (parsed == 0) + { + error = "GPU vendor ID must be nonzero"; + return false; + } + + vendorId = parsed; + return true; +} + +void AppendGpuVendorArgs(std::vector& args, uint32_t vendorId) +{ + if (vendorId == 0) + { + return; + } + + static constexpr char digits[] = "0123456789abcdef"; + char buffer[8]{}; + size_t index = sizeof(buffer); + for (uint32_t remaining = vendorId; remaining != 0; remaining >>= 4) + { + buffer[--index] = digits[remaining & 0xF]; + } + + args.push_back("--gpu-ven-id"); + args.emplace_back(buffer + index, sizeof(buffer) - index); +} + // Internal types + forward declarations // // These are used only inside this translation unit; keeping them out of the @@ -747,6 +807,7 @@ std::vector BuildCorrectnessArgs( args.push_back("--idx-max"); args.push_back(std::to_string(g_testConfig.idxMax)); } + AppendGpuVendorArgs(args, g_testConfig.gpuVendorId); for (const auto& flag : scenarioFlags) { args.push_back(flag); @@ -781,6 +842,7 @@ std::vector BuildPerformanceArgs( args.push_back("--idx-max"); args.push_back(std::to_string(g_testConfig.idxMax)); } + AppendGpuVendorArgs(args, g_testConfig.gpuVendorId); for (const auto& flag : extraFlags) { args.push_back(flag); @@ -788,4 +850,81 @@ std::vector BuildPerformanceArgs( return args; } +TEST(GpuVendorArgsTests, ParsesHexadecimalVendorId) +{ + uint32_t vendorId = 0; + std::string error; + EXPECT_TRUE(ParseGpuVendorId("10de", vendorId, error)); + EXPECT_EQ(vendorId, 0x10deu); + EXPECT_TRUE(error.empty()); + + EXPECT_TRUE(ParseGpuVendorId("0X10DE", vendorId, error)); + EXPECT_EQ(vendorId, 0x10deu); +} + +TEST(GpuVendorArgsTests, RejectsMalformedZeroAndOverflowValues) +{ + for (const std::string value : {"", "0", "10de-tail", "100000000"}) + { + uint32_t vendorId = 123; + std::string error; + EXPECT_FALSE(ParseGpuVendorId(value, vendorId, error)) << value; + EXPECT_EQ(vendorId, 0u) << value; + EXPECT_FALSE(error.empty()) << value; + } +} + +TEST(GpuVendorArgsTests, OmitsUnsetVendorId) +{ + std::vector args{"--chk-gpu"}; + AppendGpuVendorArgs(args, 0); + EXPECT_EQ(args, std::vector({"--chk-gpu"})); +} + +TEST(GpuVendorArgsTests, AppendsNormalizedVendorId) +{ + std::vector args{"--chk-gpu"}; + AppendGpuVendorArgs(args, 0x10de); + EXPECT_EQ(args, std::vector({"--chk-gpu", "--gpu-ven-id", "10de"})); +} + +TEST(GpuVendorForwardingTests, CorrectnessArgsOmitUnsetVendor) +{ + const uint32_t originalVendorId = g_testConfig.gpuVendorId; + g_testConfig.gpuVendorId = 0; + + const auto args = BuildCorrectnessArgs("content.zst", {}); + + g_testConfig.gpuVendorId = originalVendorId; + EXPECT_EQ(std::find(args.begin(), args.end(), "--gpu-ven-id"), args.end()); +} + +TEST(GpuVendorForwardingTests, CorrectnessArgsIncludeConfiguredVendor) +{ + const uint32_t originalVendorId = g_testConfig.gpuVendorId; + g_testConfig.gpuVendorId = 0x10de; + + const auto args = BuildCorrectnessArgs("content.zst", {}); + + g_testConfig.gpuVendorId = originalVendorId; + const auto option = std::find(args.begin(), args.end(), "--gpu-ven-id"); + ASSERT_NE(option, args.end()); + ASSERT_NE(std::next(option), args.end()); + EXPECT_EQ(*std::next(option), "10de"); +} + +TEST(GpuVendorForwardingTests, PerformanceArgsIncludeConfiguredVendor) +{ + const uint32_t originalVendorId = g_testConfig.gpuVendorId; + g_testConfig.gpuVendorId = 0x10de; + + const auto args = BuildPerformanceArgs("content.zst", 1, 2, "results.csv", {}); + + g_testConfig.gpuVendorId = originalVendorId; + const auto option = std::find(args.begin(), args.end(), "--gpu-ven-id"); + ASSERT_NE(option, args.end()); + ASSERT_NE(std::next(option), args.end()); + EXPECT_EQ(*std::next(option), "10de"); +} + } // namespace diff --git a/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.h b/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.h index 9a4e527..08c5232 100644 --- a/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.h +++ b/zstd/zstdgpu_ci_tests/zstdgpu_ci_tests.h @@ -15,7 +15,9 @@ #pragma once +#include #include +#include #include #include "adversarial_manifest.h" @@ -31,6 +33,7 @@ struct TestConfig std::string adversarialManifestPath; // Optional path to adversarial_manifest.json (--adversarial-manifest) std::string gpuName; // Adapter name of the machine under test (--gpu-name). Used only for the // manifest's scenario skips. + uint32_t gpuVendorId = 0; // PCI vendor ID forwarded to zstdgpu_demo. 0 = unset. int runCount = 40; // Number of iterations for performance tests int timeoutSeconds = 0; // Max seconds before killing a demo process (0 = no timeout) int perfMinMB = 4; // Min .zst size (MB) required for perf tests. Smaller files skip perf (individually-compressed textures are not representative). @@ -55,6 +58,14 @@ struct TestConfig // from test bodies. extern TestConfig g_testConfig; +// Parses a nonzero PCI vendor ID written in hexadecimal. An optional 0x prefix +// is accepted. On failure, returns false and describes the invalid value. +bool ParseGpuVendorId(std::string_view value, uint32_t& vendorId, std::string& error); + +// Appends the zstdgpu_demo adapter selector when an explicit vendor was set. +// A zero ID means "use the demo's normal adapter selection". +void AppendGpuVendorArgs(std::vector& args, uint32_t vendorId); + // File discovery — scans a directory for *.zst files. Returns sorted full paths. // Called by main() during startup. std::vector DiscoverZstFiles(const std::string& contentPath);