-
Notifications
You must be signed in to change notification settings - Fork 765
NanoVDB: chain DeviceBuffer::recordUse and expose orderAfterPriorUses (CUDA) #2287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3890,6 +3890,79 @@ TEST(TestNanoVDBCUDA, DeviceBufferNonBlockingFreeOrdering) | |
| testDeviceBufferFreeOrdering(/*nonBlockingUser=*/true, /*registerUse=*/true); | ||
| }// DeviceBufferNonBlockingFreeOrdering | ||
|
|
||
| TEST(TestNanoVDBCUDA, DeviceBufferChainedRecordUse) | ||
| { | ||
| // Regression: recordUse re-records the single per-device tracking event, and re-recording | ||
| // MOVES an event. Two non-blocking streams recording uses in sequence must therefore CHAIN | ||
| // (the second record first waits on the first capture); otherwise the second record | ||
| // discards the only coverage of the first stream's in-flight work and the free races it. | ||
| // Same shape as testDeviceBufferFreeOrdering, with the late writer recorded FIRST and an | ||
| // idle second stream recorded after it. | ||
| const size_t N = size_t(64) << 20; | ||
| const unsigned char LATE = 0xAA, VICTIM = 0x55; | ||
| const unsigned long long CYCLES = 400000000ull;// parks 'userA' for O(100 ms) | ||
|
|
||
| cudaStream_t userA = nullptr, userB = nullptr, other = nullptr; | ||
| cudaCheck(cudaStreamCreateWithFlags(&userA, cudaStreamNonBlocking)); | ||
| cudaCheck(cudaStreamCreateWithFlags(&userB, cudaStreamNonBlocking)); | ||
| cudaCheck(cudaStreamCreate(&other)); | ||
| unsigned long long *bad = nullptr; | ||
| cudaCheck(cudaMallocManaged(&bad, sizeof(*bad))); | ||
|
|
||
| {// warm-up (see testDeviceBufferFreeOrdering) | ||
| unsigned char *w = nullptr; | ||
| cudaCheck(cudaMallocAsync((void**)&w, N, other)); | ||
| streamBusyWaitKernel<<<1,1,0,userA>>>(CYCLES/10); | ||
| deviceBufferFillKernel<<<1024,256,0,other>>>(w, N, 0); | ||
| deviceBufferCountKernel<<<1024,256,0,other>>>(w, N, 0, bad); | ||
| cudaCheck(cudaFreeAsync(w, other)); | ||
| cudaCheck(cudaDeviceSynchronize()); | ||
| } | ||
|
|
||
| void *devPtr = nullptr; | ||
| { | ||
| auto buf = nanovdb::cuda::DeviceBuffer::create(N, nullptr, 0, other);// device-only | ||
| devPtr = buf.deviceData(0); | ||
| ASSERT_TRUE(devPtr); | ||
| streamBusyWaitKernel<<<1,1,0,userA>>>(CYCLES);// park 'userA' | ||
| deviceBufferFillKernel<<<1024,256,0,userA>>>((unsigned char*)devPtr, N, LATE); | ||
| buf.recordUse(0, userA);// covers the in-flight write... | ||
| buf.recordUse(0, userB);// ...and must NOT be discarded by a later record | ||
| }// destroyed here; the free must still be ordered after 'userA' | ||
|
|
||
| unsigned char *victim = nullptr; | ||
| cudaCheck(cudaMallocAsync((void**)&victim, N, other)); | ||
| deviceBufferFillKernel<<<1024,256,0,other>>>(victim, N, VICTIM); | ||
| cudaCheck(cudaStreamSynchronize(other)); | ||
|
|
||
| const bool stillPending = (cudaStreamQuery(userA) == cudaErrorNotReady); | ||
| cudaGetLastError();// clear the cudaErrorNotReady left by the query above | ||
| const bool recycled = (victim == devPtr); | ||
|
|
||
| cudaCheck(cudaStreamSynchronize(userA));// let the late write land | ||
| *bad = 0; | ||
| deviceBufferCountKernel<<<1024,256>>>(victim, N, VICTIM, bad); | ||
| cudaCheck(cudaDeviceSynchronize()); | ||
| const unsigned long long clobbered = *bad; | ||
|
|
||
| cudaCheck(cudaFreeAsync(victim, other)); | ||
| cudaCheck(cudaStreamSynchronize(other)); | ||
| cudaCheck(cudaFree(bad)); | ||
| cudaCheck(cudaStreamDestroy(userA)); | ||
| cudaCheck(cudaStreamDestroy(userB)); | ||
| cudaCheck(cudaStreamDestroy(other)); | ||
|
|
||
| // Detection relies on the pool recycling the freed block into 'victim' (stream-ordered | ||
| // pools recycle WITH the dependency attached, so recycling is expected even with a | ||
| // correctly ordered free). If it did not recycle, the chain was never exercised -- make | ||
| // that visible instead of a vacuous pass. | ||
| if (!recycled) GTEST_SKIP() << "allocator did not recycle the block; ordering not exercised"; | ||
|
|
||
| EXPECT_EQ(0u, clobbered) << "a later recordUse on another stream discarded the tracking " | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The detection here depends on the freed block being recycled into
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adopted in 5237e3e, with one check first: I wanted to be sure non-recycling couldn't be a consequence of the fix (in which case the skip would fire precisely on success). Probed on sm_120 across repeated trials post-fix: |
||
| "event covering in-flight work (block recycled: " << recycled | ||
| << ", work still pending when it was reused: " << stillPending << ")"; | ||
| }// DeviceBufferChainedRecordUse | ||
|
|
||
| TEST(TestNanoVDBCUDA, RefineCoarsen_ValueOnIndex) | ||
| { | ||
| using BuildT = nanovdb::ValueOnIndex; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| NanoVDB: | ||
|
|
||
| Bug fixes: | ||
| - cuda::DeviceBuffer::recordUse now chains: recording a use on a second | ||
| stream first orders that stream after the previously recorded use, so the | ||
| single per-device tracking event transitively covers every recorded use | ||
| instead of only the most recent one. Previously, concurrent uses recorded | ||
| on streams A then B left only B's event, and the buffer's device free | ||
| could run while A's work was still in flight. | ||
|
|
||
| Improvements: | ||
| - cuda::DeviceBuffer::orderAfterPriorUses is now public — the consume-side | ||
| companion of recordUse, letting external consumers (e.g. zero-copy | ||
| array-interface exports) order their own stream after the buffer's | ||
| tracked uses before reading. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix is right for the motivating case (write on A, then record on B), but note it also changes
recordUsefrom an observer into an ordering mutator for concurrent readers: two readers recording uses on streams A then B were previously independent, and now B serializes behind A's capture point. The single-event design can't distinguish read‖read from write→read, and the doc note's "later consumers observing earlier writes is the expected ordering" only covers the latter. Correctness over concurrency is the right trade for a tracking convenience — but do the #2225 bindings (or other consumers) fan out concurrent readers today? If so it might deserve a sentence in the doc note; if a profile ever surfaces this, the fix would be a read/write-separated or per-record event scheme rather than a revert.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed on the trade, and to your question: no — the #2225 bindings never call
recordUseinternally. The CAI/DLPack exports callorderAfterPriorUses(the consume-side wait), which doesn't record and so doesn't serialize anything;recordUseis only ever user-invoked from Python. So the read‖read serialization is strictly opt-in today, and the cost is one event-wait on the recording stream. I've extended the doc note in 5237e3e to state the concurrent-reader serialization explicitly and name the upgrade path (read/write-separated or per-record events, not a revert).