Asynchronously copy primaries to device in LocalTransporter#2406
Asynchronously copy primaries to device in LocalTransporter#2406esseivaju wants to merge 15 commits into
Conversation
Refactor primary insertion into a new stage_primaries API and keep the existing primary transport operator as staging followed by a step. Add focused Stepper coverage for staged primaries on host and device builds. Prompt: "PLEASE IMPLEMENT THIS PLAN: # Reviewable Async Primary Staging Plan ## Summary Implement the async primary-copy work as small, independently reviewable commits. Each chunk preserves existing behavior unless explicitly noted, and each commit should build and pass focused tests before moving on. ## Chunk 1: Add Stepper Staging API - Add `StepperInterface::stage_primaries(SpanConstPrimary)`. - Implement `Stepper<M>::stage_primaries` by moving the validation, `num_pending` counter update, and `ExtendFromPrimariesAction::insert` call out of `Stepper<M>::operator()(primaries)`. - Change `Stepper<M>::operator()(primaries)` to call `stage_primaries(primaries)` and then `(*this)()`. - No `LocalTransporter` behavior changes in this chunk. - Tests: add/update `Stepper.test.cc` coverage for `stage_primaries(...)` followed by `step()` on host and device where available. ## Chunk 2: Use Pinned LocalTransporter Buffers - Change `LocalTransporter` primary buffers from plain `std::vector<Primary>` to pinned vectors using `PinnedAllocator<Primary>`. - Keep current `Push` and `Flush` control flow unchanged: auto-flush still transports immediately. - Keep `dump_primaries_`, buffer accounting, and lost-primary accounting behavior unchanged. - Tests: compile accel target and run existing accel integration coverage available in the local build. ## Chunk 3: Add Copy Event Tracking - Introduce a small staged-primary state in `LocalTransporter` containing: - pinned host primaries, - copied buffer accounting, - optional `DeviceEvent copy_done`. - When staging to device, record `copy_done` after the async H2D copy on the state stream. - Use the event only to protect pinned host-buffer lifetime and reuse; do not use it to order stepping kernels on the same stream. - For host-only operation, keep the event null and behavior synchronous. - Tests: focused compile plus existing `DeviceEvent` and `Stepper` tests. ## Chunk 4: Defer Transport at Auto-Flush - Change device-mode `Push` so reaching `auto_flush_` stages the current pinned buffer asynchronously and keeps accepting Geant4 tracks. - If another full buffer appears while a staged batch is pending, call `Flush` first, then stage the new full buffer. - Change `Flush` so it stages any remaining current buffer, dumps from the staged host buffer if requested, then starts stepping with `(*step_)()` for staged primaries. - Clear/reuse staged host storage only after the copy event is ready or after stepping has returned. - Host-only behavior remains the current immediate transport path. ## Test Plan - After each chunk: run focused tests for touched code and confirm the target compiles. - Before each commit: run `pre-commit run`, re-add any modified files, and confirm the configured build still succeeds. - Commit each chunk separately with `scripts/dev/agent-commit.sh`. ## Assumptions - Only one staged primary batch is supported at a time. - Multi-batch device queueing is out of scope. - `DeviceEvent` is for host-buffer lifetime/progress tracking only; same-stream CUDA/HIP ordering handles copy-before-kernel execution." Assisted-by: codex (gpt-5)
Store LocalTransporter primaries in pinned host memory so existing stream-aware copies can run asynchronously on device builds. Preserve current flush timing and convert to the legacy event-writer vector type only when dumping primaries. Prompt: "PLEASE IMPLEMENT THIS PLAN: # Reviewable Async Primary Staging Plan ## Summary Implement the async primary-copy work as small, independently reviewable commits. Each chunk preserves existing behavior unless explicitly noted, and each commit should build and pass focused tests before moving on. ## Chunk 1: Add Stepper Staging API - Add `StepperInterface::stage_primaries(SpanConstPrimary)`. - Implement `Stepper<M>::stage_primaries` by moving the validation, `num_pending` counter update, and `ExtendFromPrimariesAction::insert` call out of `Stepper<M>::operator()(primaries)`. - Change `Stepper<M>::operator()(primaries)` to call `stage_primaries(primaries)` and then `(*this)()`. - No `LocalTransporter` behavior changes in this chunk. - Tests: add/update `Stepper.test.cc` coverage for `stage_primaries(...)` followed by `step()` on host and device where available. ## Chunk 2: Use Pinned LocalTransporter Buffers - Change `LocalTransporter` primary buffers from plain `std::vector<Primary>` to pinned vectors using `PinnedAllocator<Primary>`. - Keep current `Push` and `Flush` control flow unchanged: auto-flush still transports immediately. - Keep `dump_primaries_`, buffer accounting, and lost-primary accounting behavior unchanged. - Tests: compile accel target and run existing accel integration coverage available in the local build. ## Chunk 3: Add Copy Event Tracking - Introduce a small staged-primary state in `LocalTransporter` containing: - pinned host primaries, - copied buffer accounting, - optional `DeviceEvent copy_done`. - When staging to device, record `copy_done` after the async H2D copy on the state stream. - Use the event only to protect pinned host-buffer lifetime and reuse; do not use it to order stepping kernels on the same stream. - For host-only operation, keep the event null and behavior synchronous. - Tests: focused compile plus existing `DeviceEvent` and `Stepper` tests. ## Chunk 4: Defer Transport at Auto-Flush - Change device-mode `Push` so reaching `auto_flush_` stages the current pinned buffer asynchronously and keeps accepting Geant4 tracks. - If another full buffer appears while a staged batch is pending, call `Flush` first, then stage the new full buffer. - Change `Flush` so it stages any remaining current buffer, dumps from the staged host buffer if requested, then starts stepping with `(*step_)()` for staged primaries. - Clear/reuse staged host storage only after the copy event is ready or after stepping has returned. - Host-only behavior remains the current immediate transport path. ## Test Plan - After each chunk: run focused tests for touched code and confirm the target compiles. - Before each commit: run `pre-commit run`, re-add any modified files, and confirm the configured build still succeeds. - Commit each chunk separately with `scripts/dev/agent-commit.sh`. ## Assumptions - Only one staged primary batch is supported at a time. - Multi-batch device queueing is out of scope. - `DeviceEvent` is for host-buffer lifetime/progress tracking only; same-stream CUDA/HIP ordering handles copy-before-kernel execution." Assisted-by: codex (gpt-5)
Add staged primary state to LocalTransporter and route flushes through the Stepper staging API. Record a DeviceEvent after device primary copies so the pinned host buffer lifetime can be managed without changing flush timing. Prompt: "PLEASE IMPLEMENT THIS PLAN: Implement the async primary-copy work as small, independently reviewable commits. Each chunk preserves existing behavior unless explicitly noted, and each commit should build and pass focused tests before moving on. - Add `StepperInterface::stage_primaries(SpanConstPrimary)`. - Implement `Stepper<M>::stage_primaries` by moving the validation, `num_pending` counter update, and `ExtendFromPrimariesAction::insert` call out of `Stepper<M>::operator()(primaries)`. - Change `Stepper<M>::operator()(primaries)` to call `stage_primaries(primaries)` and then `(*this)()`. - No `LocalTransporter` behavior changes in this chunk. - Tests: add/update `Stepper.test.cc` coverage for `stage_primaries(...)` followed by `step()` on host and device where available. - Change `LocalTransporter` primary buffers from plain `std::vector<Primary>` to pinned vectors using `PinnedAllocator<Primary>`. - Keep current `Push` and `Flush` control flow unchanged: auto-flush still transports immediately. - Keep `dump_primaries_`, buffer accounting, and lost-primary accounting behavior unchanged. - Tests: compile accel target and run existing accel integration coverage available in the local build. - Introduce a small staged-primary state in `LocalTransporter` containing: - pinned host primaries, - copied buffer accounting, - optional `DeviceEvent copy_done`. - When staging to device, record `copy_done` after the async H2D copy on the state stream. - Use the event only to protect pinned host-buffer lifetime and reuse; do not use it to order stepping kernels on the same stream. - For host-only operation, keep the event null and behavior synchronous. - Tests: focused compile plus existing `DeviceEvent` and `Stepper` tests. - Change device-mode `Push` so reaching `auto_flush_` stages the current pinned buffer asynchronously and keeps accepting Geant4 tracks. - If another full buffer appears while a staged batch is pending, call `Flush` first, then stage the new full buffer. - Change `Flush` so it stages any remaining current buffer, dumps from the staged host buffer if requested, then starts stepping with `(*step_)()` for staged primaries. - Clear/reuse staged host storage only after the copy event is ready or after stepping has returned. - Host-only behavior remains the current immediate transport path. - After each chunk: run focused tests for touched code and confirm the target compiles. - Before each commit: run `pre-commit run`, re-add any modified files, and confirm the configured build still succeeds. - Commit each chunk separately with `scripts/dev/agent-commit.sh`. - Only one staged primary batch is supported at a time. - Multi-batch device queueing is out of scope. - `DeviceEvent` is for host-buffer lifetime/progress tracking only; same-stream CUDA/HIP ordering handles copy-before-kernel execution." Assisted-by: codex (gpt-5)
Change device-mode LocalTransporter auto-flush to stage full pinned primary buffers asynchronously and continue accepting tracks. Flush now drains staged and remaining buffered batches while preserving host-only immediate transport. Prompt: "PLEASE IMPLEMENT THIS PLAN: Implement the async primary-copy work as small, independently reviewable commits. Each chunk preserves existing behavior unless explicitly noted, and each commit should build and pass focused tests before moving on. - Add `StepperInterface::stage_primaries(SpanConstPrimary)`. - Implement `Stepper<M>::stage_primaries` by moving the validation, `num_pending` counter update, and `ExtendFromPrimariesAction::insert` call out of `Stepper<M>::operator()(primaries)`. - Change `Stepper<M>::operator()(primaries)` to call `stage_primaries(primaries)` and then `(*this)()`. - No `LocalTransporter` behavior changes in this chunk. - Tests: add/update `Stepper.test.cc` coverage for `stage_primaries(...)` followed by `step()` on host and device where available. - Change `LocalTransporter` primary buffers from plain `std::vector<Primary>` to pinned vectors using `PinnedAllocator<Primary>`. - Keep current `Push` and `Flush` control flow unchanged: auto-flush still transports immediately. - Keep `dump_primaries_`, buffer accounting, and lost-primary accounting behavior unchanged. - Tests: compile accel target and run existing accel integration coverage available in the local build. - Introduce a small staged-primary state in `LocalTransporter` containing: - pinned host primaries, - copied buffer accounting, - optional `DeviceEvent copy_done`. - When staging to device, record `copy_done` after the async H2D copy on the state stream. - Use the event only to protect pinned host-buffer lifetime and reuse; do not use it to order stepping kernels on the same stream. - For host-only operation, keep the event null and behavior synchronous. - Tests: focused compile plus existing `DeviceEvent` and `Stepper` tests. - Change device-mode `Push` so reaching `auto_flush_` stages the current pinned buffer asynchronously and keeps accepting Geant4 tracks. - If another full buffer appears while a staged batch is pending, call `Flush` first, then stage the new full buffer. - Change `Flush` so it stages any remaining current buffer, dumps from the staged host buffer if requested, then starts stepping with `(*step_)()` for staged primaries. - Clear/reuse staged host storage only after the copy event is ready or after stepping has returned. - Host-only behavior remains the current immediate transport path. - After each chunk: run focused tests for touched code and confirm the target compiles. - Before each commit: run `pre-commit run`, re-add any modified files, and confirm the configured build still succeeds. - Commit each chunk separately with `scripts/dev/agent-commit.sh`. - Only one staged primary batch is supported at a time. - Multi-batch device queueing is out of scope. - `DeviceEvent` is for host-buffer lifetime/progress tracking only; same-stream CUDA/HIP ordering handles copy-before-kernel execution." Assisted-by: codex (gpt-5)
Reserve both LocalTransporter pinned primary buffers to the auto-flush size at construction and reuse their storage with swaps. Avoid move assignment and temporary pinned buffers while draining staged work so stepping does not reallocate primary host storage. Prompt: "Allocating pinned memory is expensive. Can you pre-allocate both buffer to `auto_flush_` size, and make sure that we never need to re-allocate during the stepping" Assisted-by: codex (gpt-5)
Restore staged-batch transport semantics after the develop rebase and clear Geant track reconstruction state at the end of LocalTransporter flushes. Prompt: "I've had to rebase on develop which modified [LocalTransporter.cc](src/accel/LocalTransporter.cc) . Can you verify that the implementation is still correct?" Assisted-by: codex (gpt-5)
Clarify that Stepper primary staging does not execute transport, document the LocalTransporter copy-event lifetime contract, and mark the blocking backpressure point for future non-blocking stepper progress. Prompt: "Please add this documentation" Assisted-by: codex (gpt-5)
Keep Geant4 track reconstruction data alive after partial staged flushes until all buffered primaries with acquired reconstruction IDs have been transported. Prompt: "Please deffer clearing track_reconstruction until there are no remaining buffered primaries" Assisted-by: Codex (GPT-5)
Replace the ambiguous boolean argument to LocalTransporter::flush_impl with a scoped FlushMode enum so call sites identify whether only staged primaries or both staged and buffered primaries should be transported. Prompt: "Please use an enum as `flush_impl` argument" Assisted-by: Codex (GPT-5)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10631d2aad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR introduces an explicit “primary staging” phase to decouple primary upload from stepping, enabling double-buffered (pinned) host-side accumulation in LocalTransporter so GPU H2D copies can overlap with subsequent Geant4 Push calls.
Changes:
- Add
StepperInterface::stage_primariesand implement it inStepperto validate/insert primaries without executing a transport step. - Rework
LocalTransporterbuffering on GPU to use a staged pinned buffer plus aDeviceEventto protect host buffer lifetime during async H2D copies, and adjust flush behavior accordingly. - Add unit tests covering the new staged-primaries path for host and device steppers.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
test/celeritas/global/Stepper.test.cc |
Adds tests to verify primaries can be staged and then transported on the first subsequent step (host/device). |
src/celeritas/global/Stepper.hh |
Extends the stepper interface with stage_primaries. |
src/celeritas/global/Stepper.cc |
Implements stage_primaries and refactors operator()(primaries) to build on it. |
src/accel/LocalTransporter.hh |
Introduces pinned primary buffers, staged-buffer bookkeeping, and new flush modes. |
src/accel/LocalTransporter.cc |
Implements staging/clearing logic, double-buffered GPU behavior, and updated flush loop. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Handle lost-only LocalTransporter flushes so out-of-bounds primary counters are logged, accumulated, and reset even when no tracks were buffered. Validate that Stepper::stage_primaries is not called while another staged batch is pending, and cover that precondition in the Stepper tests. Prompt: "review and address the two comments in the PR" Assisted-by: Codex (GPT-5)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #2406 +/- ##
===========================================
- Coverage 87.23% 87.19% -0.04%
===========================================
Files 1396 1396
Lines 44049 44122 +73
Branches 13791 13343 -448
===========================================
+ Hits 38426 38473 +47
+ Misses 4548 4411 -137
- Partials 1075 1238 +163
🚀 New features to boost your workflow:
|
Test summary 5 702 files 9 243 suites 19m 10s ⏱️ Results for commit f405e4a. ♻️ This comment has been updated with latest results. |
sethrj
left a comment
There was a problem hiding this comment.
Still working through the LocalTransporter changes.
Could you please clarify (here and/or LT class docs) the nomenclature and steps for CPU and GPU:
G4 primary
-> validated and converted local Primary
-> push back onto "staged" (?)
-> asynchronous copy to "buffered" (?)
-> added to pending primaries buffer in GenPrimaryAction (?)
-> track initializer
-> track
There's too many staging/buffering steps to just simply call it buffer/stage now 😬
The |
Rename LocalTransporter host buffers around primary and staging semantics, share the same PrimaryBuffer bookkeeping structure for both, and add a helper for interpreting flush modes. Prompt: "Review and address the open comment by implementing a helper function for flush mode, and reorganizing `LocalTransporter` data members. Try to share struc between primary and staging buffer when possible" Assisted-by: codex (gpt-5)
Implements double buffering for staging Geant4 primaries before GPU transport.
Primaries are still accumulated until
auto_flush_entries have been buffered. On GPU builds, the filled buffer is then moved into a staged buffer and copied to device memory on the Celeritas stream. ADeviceEventprotects the pinned host buffer lifetime until the asynchronous copy completes.This changes the effective
auto_flush_behavior on GPU: transport is not forced when the first buffer fills. Instead, the first full buffer is staged, and transport begins when the next buffer fills or whenFlush()is called at the normal event flush point.