Skip to content

Commit d5551e4

Browse files
committed
cupti: be resilient against cupti oom errors
1 parent 02c2ef9 commit d5551e4

1 file changed

Lines changed: 51 additions & 16 deletions

File tree

src/pc_sampling.cpp

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,11 @@ void disablePCSampling(CUcontext context) {
114114
proton::cupti::pcSamplingDisable<true>(&params);
115115
}
116116

117-
// Returns true if data was retrieved successfully, false on error.
118-
bool getPCSamplingData(CUcontext context,
119-
CUpti_PCSamplingData *pcSamplingData) {
117+
// Returns CUPTI_SUCCESS on success, or the raw CUptiResult on failure so
118+
// callers can distinguish CUPTI_ERROR_OUT_OF_MEMORY (error 8) — a wedged
119+
// internal-buffer condition we recover from — from other errors.
120+
CUptiResult getPCSamplingData(CUcontext context,
121+
CUpti_PCSamplingData *pcSamplingData) {
120122
CUpti_PCSamplingGetDataParams params = {
121123
/*size=*/CUpti_PCSamplingGetDataParamsSize,
122124
/*pPriv=*/NULL,
@@ -127,9 +129,8 @@ bool getPCSamplingData(CUcontext context,
127129
if (result != CUPTI_SUCCESS) {
128130
DEBUG_PRINTF("cuptiPCSamplingGetData failed: error %d (ctx=%p)\n", result,
129131
context);
130-
return false;
131132
}
132-
return true;
133+
return result;
133134
}
134135

135136
void setConfigurationAttribute(
@@ -379,6 +380,11 @@ void ConfigureData::initialize(CUcontext context) {
379380
configurationInfos.emplace_back(configureCollectionMode());
380381
configurationInfos.emplace_back(configureStartStopControl());
381382
configurationInfos.emplace_back(configureSamplingBuffer());
383+
// Bigger scratch + hardware buffers so a busy workload (PyTorch-class)
384+
// doesn't overflow CUPTI's defaults within minutes and start returning
385+
// CUPTI_ERROR_OUT_OF_MEMORY from cuptiPCSamplingGetData.
386+
configurationInfos.emplace_back(configureScratchBuffer());
387+
configurationInfos.emplace_back(configureHardwareBufferSize());
382388
// Don't set sampling period — let CUPTI use its default.
383389
// Explicit period values silently break sampling on some GPUs (e.g.
384390
// Blackwell).
@@ -744,16 +750,44 @@ void PCSampling::collectData(CUcontext context) {
744750
// tens of thousands. Failing to drain leaves data in CUPTI's internal
745751
// buffers, which eventually causes CUPTI_ERROR_OUT_OF_MEMORY (error 8).
746752
do {
747-
bool ok = getPCSamplingData(context, &configureData->outputData);
748-
DEBUG_PRINTF("getData: ok=%d output total=%zu remaining=%zu "
753+
CUptiResult res = getPCSamplingData(context, &configureData->outputData);
754+
DEBUG_PRINTF("getData: res=%d output total=%zu remaining=%zu "
749755
"cfg total=%zu remaining=%zu\n",
750-
ok, configureData->outputData.totalNumPcs,
756+
res, configureData->outputData.totalNumPcs,
751757
configureData->outputData.remainingNumPcs,
752758
configureData->pcSamplingData.totalNumPcs,
753759
configureData->pcSamplingData.remainingNumPcs);
754-
if (!ok)
755-
break;
756-
processPCSamplingData(configureData);
760+
if (res == CUPTI_SUCCESS) {
761+
processPCSamplingData(configureData);
762+
continue;
763+
}
764+
if (res == CUPTI_ERROR_OUT_OF_MEMORY) {
765+
// CUPTI's PC-sampling state for this context is wedged — stop+start
766+
// alone does NOT unwedge it (confirmed empirically: 7800 starts
767+
// produced 0 samples after the first OOM). The fix is a full reset:
768+
// disable + erase tracking + re-enable + re-configure, which is
769+
// exactly what finalize() + initialize() do back-to-back.
770+
//
771+
// try_lock-probe on pcSamplingMutex first: collectData is sometimes
772+
// called from inside PCSampling::stop() which already holds the
773+
// mutex. Calling finalize() in that re-entry would self-deadlock
774+
// on the non-recursive mutex (finalize re-takes it). If the probe
775+
// fails (someone holds it), bail; the next callback not nested
776+
// under stop()/start() will retry.
777+
{
778+
std::unique_lock<std::mutex> probe(pcSamplingMutex, std::try_to_lock);
779+
if (!probe.owns_lock()) {
780+
break;
781+
}
782+
}
783+
// probe released; finalize() + initialize() each take their own
784+
// locks (contextMutex, and pcSamplingMutex internally).
785+
DEBUG_PRINTF("Recovering from CUPTI_ERROR_OUT_OF_MEMORY: full reinit on "
786+
"ctx=%p (disable+enable to clear wedged state)\n", context);
787+
finalize(context);
788+
initialize(context);
789+
}
790+
break;
757791
} while (configureData->outputData.remainingNumPcs > 0);
758792
}
759793

@@ -771,10 +805,10 @@ void PCSampling::collectAllData() {
771805
DEBUG_PRINTF("Draining PC sampling data for context %u\n", contextId);
772806
// Fetch and drain all pending data from CUPTI.
773807
do {
774-
bool ok = getPCSamplingData(configureData->context,
775-
&configureData->outputData);
776-
if (!ok)
808+
if (getPCSamplingData(configureData->context,
809+
&configureData->outputData) != CUPTI_SUCCESS) {
777810
break;
811+
}
778812
processPCSamplingData(configureData);
779813
} while (configureData->outputData.remainingNumPcs > 0);
780814
}
@@ -814,9 +848,10 @@ void PCSampling::finalize(CUcontext context) {
814848
// Drain all remaining PC data before disabling.
815849
auto *configureData = getConfigureData(contextId);
816850
do {
817-
bool ok = getPCSamplingData(context, &configureData->outputData);
818-
if (!ok)
851+
if (getPCSamplingData(context, &configureData->outputData) !=
852+
CUPTI_SUCCESS) {
819853
break;
854+
}
820855
processPCSamplingData(configureData);
821856
} while (configureData->outputData.remainingNumPcs > 0);
822857

0 commit comments

Comments
 (0)