@@ -114,9 +114,11 @@ void disablePCSampling(CUcontext context) {
114114 proton::cupti::pcSamplingDisable<true >(¶ms);
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
135136void 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