diff --git a/libkineto/include/Config.h b/libkineto/include/Config.h index 9835ce0a9..80fbec58c 100644 --- a/libkineto/include/Config.h +++ b/libkineto/include/Config.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -236,6 +237,16 @@ class Config : public AbstractConfig { return performanceMetricsDeviceId_; } + [[nodiscard]] std::optional + performanceMetricsSamplingInterval() const { + return performanceMetricsSamplingInterval_; + } + + [[nodiscard]] std::chrono::nanoseconds performanceMetricsLookbackWindow() + const { + return performanceMetricsLookbackWindow_; + } + [[nodiscard]] bool memoryProfilerEnabled() const { return memoryProfilerEnabled_; } @@ -375,6 +386,9 @@ class Config : public AbstractConfig { // Performance metrics std::vector performanceMetricNames_; int32_t performanceMetricsDeviceId_{-1}; + std::optional performanceMetricsSamplingInterval_; + std::chrono::nanoseconds performanceMetricsLookbackWindow_{ + std::chrono::seconds{1}}; // CUPTI Timestamp Format bool useTSCTimestamp_{true}; diff --git a/libkineto/src/Config.cpp b/libkineto/src/Config.cpp index b1644458e..2dc3e47d7 100644 --- a/libkineto/src/Config.cpp +++ b/libkineto/src/Config.cpp @@ -9,6 +9,8 @@ #include "Config.h" #include "ThrowUtil.h" +#include +#include #include #include @@ -20,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +58,10 @@ constexpr char kCuptiPerThreadBufferEnabledKey[] = constexpr char kPerformanceMetricsKey[] = "PERFORMANCE_METRICS"; constexpr char kPerformanceMetricsDeviceIdKey[] = "PERFORMANCE_METRICS_DEVICE_ID"; +constexpr char kPerformanceMetricsSamplingIntervalMsecsKey[] = + "PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS"; +constexpr char kPerformanceMetricsLookbackWindowMsecsKey[] = + "PERFORMANCE_METRICS_LOOKBACK_WINDOW_MS"; constexpr char kActivityTypesKey[] = "ACTIVITY_TYPES"; constexpr char kActivitiesLogFileKey[] = "ACTIVITIES_LOG_FILE"; constexpr char kActivitiesDurationKey[] = "ACTIVITIES_DURATION_SECS"; @@ -228,6 +235,24 @@ bool isAllowedOnDemandTraceFile(const string& path) { return path.starts_with(dir) && path.find("..") == string::npos; } +std::optional parseMilliseconds(const string& text) noexcept { + const char* const begin = text.c_str(); + char* parseEnd = nullptr; + errno = 0; + const double count = std::strtod(begin, &parseEnd); + if (errno == ERANGE || parseEnd == begin || parseEnd != begin + text.size()) { + return std::nullopt; + } + + const duration value{count}; + if (!std::isfinite(count) || value < nanoseconds{1} || + value >= nanoseconds::max()) { + return std::nullopt; + } + + return duration_cast(value); +} + } // namespace Config::Config() @@ -351,6 +376,14 @@ bool Config::handleOption(const std::string& name, std::string& val) { performanceMetricNames_ = splitAndTrim(val, ','); } else if (!name.compare(kPerformanceMetricsDeviceIdKey)) { performanceMetricsDeviceId_ = toInt32(val); + } else if (!name.compare(kPerformanceMetricsSamplingIntervalMsecsKey)) { + if (const auto value = parseMilliseconds(val)) { + performanceMetricsSamplingInterval_ = *value; + } + } else if (!name.compare(kPerformanceMetricsLookbackWindowMsecsKey)) { + if (const auto value = parseMilliseconds(val)) { + performanceMetricsLookbackWindow_ = *value; + } } else if (!name.compare(kProfileMemory)) { memoryProfilerEnabled_ = toBool(val); if (memoryProfilerEnabled_) { diff --git a/libkineto/src/CuptiPMSamplingApi.cpp b/libkineto/src/CuptiPMSamplingApi.cpp index dbe29e3c4..e77ab286f 100644 --- a/libkineto/src/CuptiPMSamplingApi.cpp +++ b/libkineto/src/CuptiPMSamplingApi.cpp @@ -8,6 +8,8 @@ #include "CuptiPMSamplingApi.h" +#include +#include #include #include @@ -47,7 +49,9 @@ namespace KINETO_NAMESPACE { namespace { constexpr size_t kHardwareBufferSizeBytes = 64 * 1024 * 1024; -constexpr uint32_t kMaxSamplesPerDecode = 1024; +// Counter-data images are fully allocated in host memory. Bound their size +// independently of CUPTI's uint32_t sample-count limit. +constexpr size_t kMaxCounterDataImageSizeBytes = 256 * 1024 * 1024; // GA100 only supports variable-frequency SYSCLK sampling. One option is to // measure hardware clock frequency and estimate the number of cycles needed to @@ -55,6 +59,23 @@ constexpr uint32_t kMaxSamplesPerDecode = 1024; // fixed. This is almost certainly something we'll need to revisit/tune. constexpr uint64_t kGa100SamplingIntervalCycles = 1'000'000; +uint32_t maxSamplesForDecode( + std::chrono::nanoseconds lookbackWindow, + std::chrono::nanoseconds samplingInterval) { + if (lookbackWindow.count() <= 0 || samplingInterval.count() <= 0) { + KINETO_THROW( + std::runtime_error, + "CUPTI PM sampling interval and lookback window must be positive"); + } + const auto samples = std::max(1, lookbackWindow / samplingInterval); + if (samples > std::numeric_limits::max()) { + KINETO_THROW( + std::runtime_error, + "CUPTI PM sampling lookback window exceeds CUPTI sample capacity"); + } + return static_cast(samples); +} + /* * ========================== CUPTI PM SAMPLING API ========================== * @@ -163,26 +184,55 @@ void CuptiPMSamplingApi::configureCupti() { } CUpti_PmSampling_TriggerMode triggerMode; uint64_t samplingInterval; + std::chrono::nanoseconds capacityInterval = + config_.samplingInterval.value_or(std::chrono::milliseconds{1}); // GPU_TIME_INTERVAL is unavailable on GA100. The wall-time duration of this // fixed SYSCLK interval varies with the GPU's clock frequency. if (deviceProperties.major == 8 && deviceProperties.minor == 0) { + if (config_.samplingInterval.has_value()) { + LOG(WARNING) + << "PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS is ignored on GA100; " + "continuing with the fixed 1,000,000-cycle SYSCLK sampling " + "interval. The time between samples varies with the GPU clock."; + } + int peakClockRateKHz = 0; + const cudaError_t clockRateStatus = cudaDeviceGetAttribute( + &peakClockRateKHz, cudaDevAttrClockRate, config_.deviceId); + if (clockRateStatus != cudaSuccess) { + KINETO_THROW( + std::runtime_error, + std::string{"cudaDeviceGetAttribute(cudaDevAttrClockRate) failed: "} + + cudaGetErrorString(clockRateStatus)); + } + if (peakClockRateKHz <= 0) { + KINETO_THROW( + std::runtime_error, "GA100 reported an invalid GPU clock rate"); + } triggerMode = CUPTI_PM_SAMPLING_TRIGGER_MODE_GPU_SYSCLK_INTERVAL; samplingInterval = kGa100SamplingIntervalCycles; + // Use the peak clock only for conservative decode capacity sizing; + // GA100's actual SYSCLK sampling period varies with the GPU clock. + capacityInterval = std::chrono::duration_cast( + std::chrono::duration{ + static_cast(kGa100SamplingIntervalCycles) / + (static_cast(peakClockRateKHz) * 1000.0)}); } else if ( deviceProperties.major > 8 || (deviceProperties.major == 8 && deviceProperties.minor >= 6)) { - if (config_.samplingInterval.count() <= 0) { + if (capacityInterval.count() <= 0) { KINETO_THROW( std::runtime_error, "CUPTI PM sampling interval must be positive"); } triggerMode = CUPTI_PM_SAMPLING_TRIGGER_MODE_GPU_TIME_INTERVAL; - samplingInterval = static_cast(config_.samplingInterval.count()); + samplingInterval = static_cast(capacityInterval.count()); } else { KINETO_THROW( std::runtime_error, "Minimum supported GPU for CUPTI PM sampling is GA100 (compute " "capability 8.0); other GPUs require compute capability 8.6 or newer"); } + const auto maxSamples = + maxSamplesForDecode(config_.lookbackWindow, capacityInterval); // Building the availability image. This is a CUPTI byte buffer describing // which raw hardware counters are available on this machine. The first call @@ -272,16 +322,25 @@ void CuptiPMSamplingApi::configureCupti() { CUPTI_PM_SAMPLING_HARDWARE_BUFFER_APPEND_MODE_KEEP_LATEST; CUPTI_CALL_THROW(cuptiPmSamplingSetConfig(&setConfig)); - // Asking CUPTI how large the (counter) data image should be. - // Since the data image has an opage CUPTI-defined layout, the size - // depends on sampling config, metrics, etc. + // Size the counter data image to hold the requested lookback window. CUPTI + // 12.x requires SetConfig to run before GetCounterDataSize. CUpti_PmSampling_GetCounterDataSize_Params counterDataSize{ CUpti_PmSampling_GetCounterDataSize_Params_STRUCT_SIZE}; counterDataSize.pPmSamplingObject = samplingObject_; counterDataSize.pMetricNames = metricNamePtrs_.data(); counterDataSize.numMetrics = metricNamePtrs_.size(); - counterDataSize.maxSamples = kMaxSamplesPerDecode; + counterDataSize.maxSamples = maxSamples; CUPTI_CALL_THROW(cuptiPmSamplingGetCounterDataSize(&counterDataSize)); + if (counterDataSize.counterDataSize > kMaxCounterDataImageSizeBytes) { + KINETO_THROW( + std::runtime_error, + "CUPTI PM sampling counter data image requires " + + std::to_string(counterDataSize.counterDataSize) + + " bytes, exceeding the 256 MiB limit. Reduce " + "PERFORMANCE_METRICS_LOOKBACK_WINDOW_MS, increase " + "PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS on non-GA100 GPUs, or " + "request fewer PERFORMANCE_METRICS."); + } counterDataImage_.resize(counterDataSize.counterDataSize); resetImage(); @@ -424,7 +483,8 @@ void CuptiPMSamplingApi::disable() { config_.deviceId = -1; metricNamePtrs_.clear(); config_.metricNames.clear(); - config_.samplingInterval = std::chrono::nanoseconds::zero(); + config_.samplingInterval.reset(); + config_.lookbackWindow = std::chrono::nanoseconds::zero(); } } // namespace KINETO_NAMESPACE diff --git a/libkineto/src/CuptiPMSamplingApi.h b/libkineto/src/CuptiPMSamplingApi.h index a38c3b1f2..64a708df2 100644 --- a/libkineto/src/CuptiPMSamplingApi.h +++ b/libkineto/src/CuptiPMSamplingApi.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -30,7 +31,9 @@ struct CuptiPMSample { struct CuptiPMSamplingConfig { int32_t deviceId{-1}; std::vector metricNames; - std::chrono::nanoseconds samplingInterval{0}; + // Unset selects 1 ms on GA10x and newer, or 1M SYSCLK cycles on GA100. + std::optional samplingInterval; + std::chrono::nanoseconds lookbackWindow{std::chrono::seconds{1}}; }; class CuptiPMSamplingApi { diff --git a/libkineto/src/CuptiPMSamplingProfiler.cpp b/libkineto/src/CuptiPMSamplingProfiler.cpp index 50e1e91a4..3f31847eb 100644 --- a/libkineto/src/CuptiPMSamplingProfiler.cpp +++ b/libkineto/src/CuptiPMSamplingProfiler.cpp @@ -26,9 +26,6 @@ namespace { const std::string kProfilerName{"CUPTI PM Sampling"}; const std::set kSupportedActivities{ libkineto::ActivityType::HARDWARE_COUNTERS}; -// TODO: This is a temporary constant -- we need to tune the interval by -// hardware type to prevent hardware buffer overflow and sample loss. -constexpr std::chrono::milliseconds kSamplingInterval{1}; } // namespace @@ -201,7 +198,10 @@ std::unique_ptr CuptiPMSamplingProfiler:: } const CuptiPMSamplingConfig pmConfig{ - deviceId, metricNames, kSamplingInterval}; + deviceId, + metricNames, + config.performanceMetricsSamplingInterval(), + config.performanceMetricsLookbackWindow()}; auto session = std::make_unique(pmConfig); if (!session->prepare()) { return nullptr; diff --git a/libkineto/test/ConfigTest.cpp b/libkineto/test/ConfigTest.cpp index ef4529cae..06d470933 100644 --- a/libkineto/test/ConfigTest.cpp +++ b/libkineto/test/ConfigTest.cpp @@ -143,21 +143,56 @@ TEST(ParseTest, PerformanceMetrics) { Config cfg; EXPECT_TRUE(cfg.performanceMetricNames().empty()); EXPECT_EQ(cfg.performanceMetricsDeviceId(), -1); + EXPECT_FALSE(cfg.performanceMetricsSamplingInterval().has_value()); + EXPECT_EQ(cfg.performanceMetricsLookbackWindow(), seconds(1)); EXPECT_TRUE( cfg.parse("PERFORMANCE_METRICS_DEVICE_ID=2\n" "PERFORMANCE_METRICS=" - "sm__cycles_active.avg,dram__bytes_read.sum")); + "sm__cycles_active.avg,dram__bytes_read.sum\n" + "PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS=0.5\n" + "PERFORMANCE_METRICS_LOOKBACK_WINDOW_MS=2000")); EXPECT_EQ( cfg.performanceMetricNames(), std::vector( {"sm__cycles_active.avg", "dram__bytes_read.sum"})); EXPECT_EQ(cfg.performanceMetricsDeviceId(), 2); + EXPECT_EQ(cfg.performanceMetricsSamplingInterval(), microseconds(500)); + EXPECT_EQ(cfg.performanceMetricsLookbackWindow(), seconds(2)); auto clone = cfg.clone(); EXPECT_EQ(clone->performanceMetricNames(), cfg.performanceMetricNames()); EXPECT_EQ(clone->performanceMetricsDeviceId(), 2); + EXPECT_EQ(clone->performanceMetricsSamplingInterval(), microseconds(500)); + EXPECT_EQ(clone->performanceMetricsLookbackWindow(), seconds(2)); +} + +TEST(ParseTest, PerformanceMetricsPreservesExplicitDefaultInterval) { + Config cfg; + EXPECT_FALSE(cfg.clone()->performanceMetricsSamplingInterval().has_value()); + + ASSERT_TRUE(cfg.parse("PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS=1")); + EXPECT_EQ(cfg.performanceMetricsSamplingInterval(), milliseconds(1)); + EXPECT_EQ(cfg.clone()->performanceMetricsSamplingInterval(), milliseconds(1)); +} + +TEST(ParseTest, PerformanceMetricsInvalidDurationsUseDefaults) { + Config cfg; + EXPECT_TRUE( + cfg.parse("PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS=invalid\n" + "PERFORMANCE_METRICS_LOOKBACK_WINDOW_MS=0")); + EXPECT_FALSE(cfg.performanceMetricsSamplingInterval().has_value()); + EXPECT_EQ(cfg.performanceMetricsLookbackWindow(), seconds(1)); +} + +TEST(ParseTest, PerformanceMetricsRejectDurationsAtNanosecondLimit) { + Config cfg; + EXPECT_TRUE( + cfg.parse("PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS=9223372036854.776\n" + "PERFORMANCE_METRICS_LOOKBACK_WINDOW_MS=9223372036854.776")); + EXPECT_FALSE(cfg.performanceMetricsSamplingInterval().has_value()); + EXPECT_EQ(cfg.performanceMetricsLookbackWindow(), seconds(1)); } TEST(ParseTest, ProfileStartTime) { diff --git a/libkineto/test/CuptiPMSamplingApiTest.cpp b/libkineto/test/CuptiPMSamplingApiTest.cpp index e23e6ca03..3f9784be4 100644 --- a/libkineto/test/CuptiPMSamplingApiTest.cpp +++ b/libkineto/test/CuptiPMSamplingApiTest.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -24,7 +26,12 @@ #include #include +#include "include/Config.h" #include "src/CuptiPMSamplingApi.h" +#include "src/CuptiPMSamplingProfiler.h" +#include "src/CuptiTimestamp.h" +#include "src/Logger.h" +#include "src/LoggerCollector.h" using namespace KINETO_NAMESPACE; using namespace std::chrono_literals; @@ -42,6 +49,9 @@ struct FakeSample { struct FakeCuptiState { int major{9}; int minor{0}; + int clockRate{1'500'000}; + int clockRateDevice{-1}; + cudaError_t clockRateResult{cudaSuccess}; size_t numPasses{1}; CUpti_PmSampling_DecodeStopReason decodeStopReason{ CUPTI_PM_SAMPLING_DECODE_STOP_REASON_END_OF_RECORDS}; @@ -58,6 +68,7 @@ struct FakeCuptiState { CUpti_PmSampling_HardwareBuffer_AppendMode appendMode{}; std::vector counterDataMetricNames; uint32_t maxSamples{0}; + size_t counterDataSize{1}; std::byte hostObject{}; std::byte samplingObject{}; @@ -101,17 +112,18 @@ std::vector copyMetricNames( } CuptiPMSamplingConfig makeConfig( - std::chrono::nanoseconds samplingInterval = 500us, + std::optional samplingInterval = 500us, int32_t deviceId = 0, - std::vector metricNames = {"sm__cycles_active.avg"}) { + std::vector metricNames = {"sm__cycles_active.avg"}, + std::chrono::nanoseconds lookbackWindow = 1s) { return CuptiPMSamplingConfig{ - deviceId, std::move(metricNames), samplingInterval}; + deviceId, std::move(metricNames), samplingInterval, lookbackWindow}; } void configureForDevice( int major, int minor, - std::chrono::nanoseconds samplingInterval) { + std::optional samplingInterval) { fakeCupti().major = major; fakeCupti().minor = minor; CuptiPMSamplingApi api; @@ -159,6 +171,14 @@ cudaGetDeviceProperties(cudaDeviceProp* properties, int device) { return cudaSuccess; } +cudaError_t CUDARTAPI +cudaDeviceGetAttribute(int* value, cudaDeviceAttr attribute, int device) { + EXPECT_EQ(attribute, cudaDevAttrClockRate); + fakeCupti().clockRateDevice = device; + *value = fakeCupti().clockRate; + return fakeCupti().clockRateResult; +} + const char* CUDARTAPI cudaGetErrorString(cudaError_t error) { static_cast(error); return "mock CUDA error"; @@ -227,7 +247,7 @@ CUptiResult CUPTIAPI cuptiPmSamplingGetCounterDataSize( fakeCupti().counterDataMetricNames = copyMetricNames(params->pMetricNames, params->numMetrics); fakeCupti().maxSamples = params->maxSamples; - params->counterDataSize = 1; + params->counterDataSize = fakeCupti().counterDataSize; return CUPTI_SUCCESS; } @@ -318,7 +338,8 @@ TEST_F(CuptiPMSamplingApiTest, ConfiguresDeviceMetricsAndBuffers) { const auto config = makeConfig( 250us, /*deviceId=*/2, - {"sm__cycles_active.avg", "dram__bytes_read.sum"}); + {"sm__cycles_active.avg", "dram__bytes_read.sum"}, + 2s); CuptiPMSamplingApi api; api.configure(config); @@ -334,17 +355,123 @@ TEST_F(CuptiPMSamplingApiTest, ConfiguresDeviceMetricsAndBuffers) { fakeCupti().appendMode, CUPTI_PM_SAMPLING_HARDWARE_BUFFER_APPEND_MODE_KEEP_LATEST); EXPECT_EQ(fakeCupti().counterDataMetricNames, config.metricNames); - EXPECT_EQ(fakeCupti().maxSamples, 1024); + EXPECT_EQ(fakeCupti().maxSamples, 8'000); api.disable(); } -TEST_F(CuptiPMSamplingApiTest, UsesFixedSysclkIntervalOnGa100) { - configureForDevice(8, 0, 0ns); +TEST_F(CuptiPMSamplingApiTest, KeepsAtLeastOneSample) { + CuptiPMSamplingApi api; + + api.configure(makeConfig( + 2ms, + /*deviceId=*/0, + {"sm__cycles_active.avg"}, + 1ms)); + + EXPECT_EQ(fakeCupti().maxSamples, 1); + api.disable(); +} +TEST_F(CuptiPMSamplingApiTest, SizesGa100CapacityFromPeakClock) { + fakeCupti().major = 8; + fakeCupti().minor = 0; + Config config; + ASSERT_TRUE( + config.parse("PERFORMANCE_METRICS_DEVICE_ID=2\n" + "PERFORMANCE_METRICS=sm__cycles_active.avg\n" + "PERFORMANCE_METRICS_LOOKBACK_WINDOW_MS=2000")); + configureCuptiTimestampSource(false); + CuptiPMSamplingProfiler profiler; + auto session = profiler.configure({ActivityType::HARDWARE_COUNTERS}, config); + ASSERT_NE(session, nullptr); + + EXPECT_EQ(fakeCupti().clockRateDevice, 2); EXPECT_EQ( fakeCupti().triggerMode, CUPTI_PM_SAMPLING_TRIGGER_MODE_GPU_SYSCLK_INTERVAL); EXPECT_EQ(fakeCupti().samplingInterval, 1'000'000); + // At the fake 1.5 GHz maximum clock, 1M cycles yields 3K samples in 2s. + EXPECT_EQ(fakeCupti().maxSamples, 3'000); +} + +TEST_F(CuptiPMSamplingApiTest, IgnoresExplicitTimeIntervalsOnGa100) { + for (const std::chrono::nanoseconds interval : + {0ns, 1ns, 500'000ns, 1'000'000ns}) { + SCOPED_TRACE(interval.count()); + EXPECT_NO_THROW(configureForDevice(8, 0, interval)); + EXPECT_EQ( + fakeCupti().triggerMode, + CUPTI_PM_SAMPLING_TRIGGER_MODE_GPU_SYSCLK_INTERVAL); + EXPECT_EQ(fakeCupti().samplingInterval, 1'000'000); + EXPECT_EQ(fakeCupti().maxSamples, 1'500); + } +} + +#if !USE_GOOGLE_LOG +TEST_F(CuptiPMSamplingApiTest, WarnsAndContinuesWithExplicitGa100Interval) { + fakeCupti().major = 8; + fakeCupti().minor = 0; + Config config; + ASSERT_TRUE( + config.parse("PERFORMANCE_METRICS_DEVICE_ID=0\n" + "PERFORMANCE_METRICS=sm__cycles_active.avg\n" + "PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS=1")); + configureCuptiTimestampSource(false); + CuptiPMSamplingProfiler profiler; + LoggerCollector collector; + const int previousSeverity = Logger::severityLevel(); + Logger::setSeverityLevel(LoggerOutputType::WARNING); + Logger::addLoggerObserver(&collector); + + auto session = profiler.configure({ActivityType::HARDWARE_COUNTERS}, config); + + Logger::removeLoggerObserver(&collector); + Logger::setSeverityLevel(previousSeverity); + EXPECT_NE(session, nullptr); + auto messages = collector.extractCollectorMetadata(); + EXPECT_TRUE(messages[LoggerOutputType::ERROR].empty()); + const auto& warnings = messages[LoggerOutputType::WARNING]; + ASSERT_EQ(warnings.size(), 1); + EXPECT_NE( + warnings.front().find("PERFORMANCE_METRICS_SAMPLING_INTERVAL_MS"), + std::string::npos); + EXPECT_NE(warnings.front().find("ignored"), std::string::npos); + EXPECT_NE(warnings.front().find("1,000,000-cycle"), std::string::npos); + EXPECT_EQ(fakeCupti().clockRateDevice, 0); + EXPECT_EQ(fakeCupti().samplingInterval, 1'000'000); + EXPECT_EQ(fakeCupti().maxSamples, 1'500); +} +#endif + +TEST_F(CuptiPMSamplingApiTest, CleansUpAfterGa100ClockQueryFailure) { + fakeCupti().major = 8; + fakeCupti().minor = 0; + fakeCupti().clockRateResult = cudaErrorInvalidValue; + CuptiPMSamplingApi api; + + EXPECT_THROW(api.configure(makeConfig(std::nullopt)), std::runtime_error); + + clearCalls(); + api.disable(); + expectCalls({"profilerDeInitialize"}); +} + +TEST_F(CuptiPMSamplingApiTest, RejectsNonpositiveGa100ClockRates) { + for (const int clockRate : {0, -1}) { + SCOPED_TRACE(clockRate); + fakeCupti().clockRate = clockRate; + EXPECT_THROW(configureForDevice(8, 0, std::nullopt), std::runtime_error); + } +} + +TEST_F(CuptiPMSamplingApiTest, UsesDefaultTimeIntervalOnGa10xAndNewer) { + for (const auto& [major, minor] : {std::pair{8, 6}, std::pair{9, 0}}) { + SCOPED_TRACE(testing::Message() << major << "." << minor); + configureForDevice(major, minor, std::nullopt); + + EXPECT_EQ(fakeCupti().samplingInterval, 1'000'000); + EXPECT_EQ(fakeCupti().maxSamples, 1'000); + } } TEST_F(CuptiPMSamplingApiTest, UsesRequestedTimeIntervalOnGa10xAndNewer) { @@ -356,6 +483,7 @@ TEST_F(CuptiPMSamplingApiTest, UsesRequestedTimeIntervalOnGa10xAndNewer) { fakeCupti().triggerMode, CUPTI_PM_SAMPLING_TRIGGER_MODE_GPU_TIME_INTERVAL); EXPECT_EQ(fakeCupti().samplingInterval, 500'000); + EXPECT_EQ(fakeCupti().clockRateDevice, -1); } } @@ -370,6 +498,41 @@ TEST_F(CuptiPMSamplingApiTest, RejectsNonpositiveTimeIntervals) { EXPECT_THROW(configureForDevice(8, 6, 0ns), std::runtime_error); } +TEST_F(CuptiPMSamplingApiTest, RejectsNonpositiveLookbackWindows) { + CuptiPMSamplingApi api; + EXPECT_THROW( + api.configure(makeConfig( + 500us, + /*deviceId=*/0, + {"sm__cycles_active.avg"}, + 0ns)), + std::runtime_error); +} + +TEST_F(CuptiPMSamplingApiTest, RejectsSampleCapacityAboveCuptiLimit) { + CuptiPMSamplingApi api; + constexpr auto kTooManySamples = + static_cast(std::numeric_limits::max()) + 1; + EXPECT_THROW( + api.configure(makeConfig( + 1ns, + /*deviceId=*/0, + {"sm__cycles_active.avg"}, + std::chrono::nanoseconds{kTooManySamples})), + std::runtime_error); +} + +TEST_F(CuptiPMSamplingApiTest, RejectsCounterDataImageAboveMemoryLimit) { + fakeCupti().counterDataSize = 256 * 1024 * 1024 + 1; + CuptiPMSamplingApi api; + + EXPECT_THROW(api.configure(makeConfig()), std::runtime_error); + + clearCalls(); + api.disable(); + expectCalls({"samplingDisable", "hostDeinitialize", "profilerDeInitialize"}); +} + TEST_F(CuptiPMSamplingApiTest, RejectsMultipassConfigurationBeforeEnabling) { fakeCupti().numPasses = 2; CuptiPMSamplingApi api; diff --git a/libkineto/test/CuptiPMSamplingProfilerTest.cpp b/libkineto/test/CuptiPMSamplingProfilerTest.cpp index 9a038181e..7717bd953 100644 --- a/libkineto/test/CuptiPMSamplingProfilerTest.cpp +++ b/libkineto/test/CuptiPMSamplingProfilerTest.cpp @@ -86,29 +86,17 @@ TEST(CuptiPMSamplingProfilerTest, ReportsNameAndSupportedActivity) { EXPECT_EQ(profiler.availableActivities(), kHardwareCounterActivities); } -TEST(CuptiPMSamplingProfilerTest, RequiresActivityMetricsAndDevice) { +TEST(CuptiPMSamplingProfilerTest, RequiresMetricsAndDevice) { CuptiPMSamplingProfiler profiler; - Config validConfig; - ASSERT_TRUE( - validConfig.parse("CUPTI_PM_SAMPLING_METRICS = sm__cycles_active.avg\n" - "CUPTI_PM_SAMPLING_DEVICE_ID = 0")); - EXPECT_EQ( - profiler.configure( - /*startTimeMs=*/123, - /*durationMs=*/456, - /*activityTypes=*/{}, - validConfig), - nullptr); - Config missingMetrics; - ASSERT_TRUE(missingMetrics.parse("CUPTI_PM_SAMPLING_DEVICE_ID = 0")); + ASSERT_TRUE(missingMetrics.parse("PERFORMANCE_METRICS_DEVICE_ID = 0")); EXPECT_EQ( profiler.configure(kHardwareCounterActivities, missingMetrics), nullptr); Config missingDevice; ASSERT_TRUE( - missingDevice.parse("CUPTI_PM_SAMPLING_METRICS = sm__cycles_active.avg")); + missingDevice.parse("PERFORMANCE_METRICS = sm__cycles_active.avg")); EXPECT_EQ( profiler.configure(kHardwareCounterActivities, missingDevice), nullptr); }