Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ set(${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_

find_package(ROOT COMPONENTS RIO Tree REQUIRED)
find_package(Gaudi REQUIRED)
find_package(TBB REQUIRED)
find_package(podio 1.3 REQUIRED)
find_package(EDM4HEP 1.0)
if (NOT EDM4HEP_FOUND)
Expand Down
66 changes: 65 additions & 1 deletion doc/OverlayTiming.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ It uses [`UniqueIDGenSvc`](uniqueIDGen.md) to seed the internal random number ge

| Property | Default | Description |
|----------|---------|-------------|
| `BackgroundFileNames` | `[]` | List of groups of background input files, one group per overlay stream |
| `BackgroundFileNames` | `[]` | List of groups of background input files, one group per overlay stream. Entries may also be directories, in which case their `.root` files are used. |
| `RandomMixBackgroundFiles` | `false` | Treat each file in a background group as an independent pseudo-event source and pick a random file for every overlaid pseudo-event (one-event-per-file mixing) |
| `MergeMCParticles` | `true` | Merge background MCParticles into the output. If `false`, background particles are not stored: tracker hits keep the momentum of their originating particle instead of a particle link, and calorimeter contributions get an empty particle |
| `OverlayThreads` | `1` | Number of worker threads used to read and decompress background files within a single event (`1` = serial). Only the reading is parallelized; the merge stays serial and in-order, so the result is unchanged and deterministic. Most effective with `RandomMixBackgroundFiles` and many input files |
| `NumberBackground` | `[]` | Number of background events to overlay per stream (fixed or Poisson mean) |
| `Poisson_random_NOverlay` | `[]` | If true, draw the number of events from a Poisson distribution with mean `NumberBackground` |
| `NBunchtrain` | `1` | Number of bunch crossings in the bunch train |
Expand Down Expand Up @@ -93,3 +96,64 @@ ApplicationMgr(
OutputLevel=INFO,
)
```

## Random background mixing

For setups where the background is split across a large number of files, each
containing a single pseudo-event (e.g. Muon Collider beam-induced background), set
`RandomMixBackgroundFiles = True`. Each file in a group is then treated as an
independent event source, and a random number of files (set by NumberBackground)
is chosen for every overlaid event.
`BackgroundFileNames` entries may point at directories, whose `.root` files are
collected automatically:

```python
overlay.RandomMixBackgroundFiles = True
overlay.BackgroundFileNames = [["/path/to/bib_files/"]]
```

## Parallel background reading

With many large background files the algorithm is dominated by reading and
decompressing them. Set `OverlayThreads` to a value greater than 1 to read and
decompress the background files of a single event on several threads:
Comment on lines +117 to +119

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually this might interfere with Gaudis internal scheduling (even if we also use tbb to do our multithreading). It's unclear to me whether the Gaudi internal tbb bits communicate with the tbb bits here.

There is precedent for doing this though as the CKF in k4ActsTracking also does some internal multithreading. This might need some policy discussion as it could imply different usage patterns for different community (e.g. run the general chain on a single thread but branch out to multi-threading in dedicated algorithms vs. running the full chain on multiple threads with Gaudi scheduling but no algorithm-internal multi-threading).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. In the ideal world you might want to allow users to do a combination of both, if possible.
For now, especially in colliders that are computationally challenging per event, being able to use MT inside the same event is much more important than multi-threading over events, which can be done trivially in batch jobs anyway.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @jmcarcell knows if functional algorithms can already propagate that to the Gaudi scheduler somehow. Otherwise the potential interplay will for now just be another thing to document.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the help of Claude, I had a look at the Gaudi sources. My understanding is that there's no way for a functional algorithm to declare or hand its internal parallelism to the scheduler. But the two TBB layers aren't independent either.

The good news is we can't oversubscribe the machine; the bad news is the scheduler counts algorithms in flight, not threads, so it keeps dispatching while we're fanned out and conversely with ThreadPoolSize=1 the pipeline only gets ~2 threads regardless of OverlayThreads (ntokens bounds in-flight items, not concurrency).

One thing I've added as a precaution: the pipeline now runs inside tbb::this_task_arena::isolate(). The reasoning is that while the calling thread blocks in parallel_pipeline, TBB may steal another AlgTask onto it, and AlgTask::operator() sets the thread-local EventContext and calls whiteboard()->selectStore(slot) without restoring either. While I haven't observed this, it costs nothing, so I'd rather keep it than rely on arena timing.

I've documented the OverlayThreads / ThreadPoolSize interplay in doc/OverlayTiming.md: they draw from one pool, OverlayThreads > 1 pays off when ThreadPoolSize is small, and raising both just repartitions the same threads.


```python
overlay.RandomMixBackgroundFiles = True
overlay.OverlayThreads = 4
```

Only the reading is parallelized. The randomness (which files, how many, in
which bunch crossing) is drawn up front, and the merging of the background hits
into the output collections is always done serially and in the same order, so
the result is **identical and deterministic** regardless of `OverlayThreads`.
Because ROOT I/O is made thread-safe with `ROOT::EnableThreadSafety()`, the
algorithm also remains safe to run under Gaudi's intra-event multithreading.

The speed-up is largest when reading dominates (many large files, tight time
windows that keep the merge cheap); when the merge is the bottleneck the gain is
correspondingly smaller.

### Interplay with the Gaudi scheduler

`OverlayThreads` and the scheduler's `ThreadPoolSize` are not independent: they
draw from the same pool of threads. `ThreadPoolSvc` sets a process-wide TBB
`global_control` of `ThreadPoolSize + maxParallelismExtra + 1` and creates the
task arena into which `AvalancheSchedulerSvc` enqueues every algorithm, and the
parallel reading runs inside that same arena. This means the machine is never
oversubscribed, but it also means:

- Gaudi has no way of knowing about the extra parallelism. The scheduler counts
algorithms in flight, not threads, so it keeps dispatching other algorithms
while `OverlayTiming` is fanned out.
- `OverlayThreads` bounds the number of background reads in flight, not the
number of threads actually available. With `ThreadPoolSize = 1` the arena has
two threads, and a large `OverlayThreads` will not buy more than that.

There is currently no way for a functional algorithm to declare its internal
parallelism to the scheduler (`Asynchronous` is the Boost.Fiber path for
offloaded work, not this). In practice `OverlayThreads > 1` pays off when
`ThreadPoolSize` is small -- branching out inside a single event rather than
running many events concurrently -- and raising both just repartitions the same
threads. If the extra threads should come on top of the scheduler's pool,
`AvalancheSchedulerSvc.maxParallelismExtra` raises the TBB limit accordingly.
2 changes: 1 addition & 1 deletion k4FWCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ gaudi_add_module(k4FWCorePlugins
components/Reader.cpp
components/UniqueIDGenSvc.cpp
components/Writer.cpp
LINK Gaudi::GaudiKernel k4FWCore k4FWCore::k4Interface ROOT::Core ROOT::RIO ROOT::Tree EDM4HEP::edm4hep)
LINK Gaudi::GaudiKernel k4FWCore k4FWCore::k4Interface ROOT::Core ROOT::RIO ROOT::Tree EDM4HEP::edm4hep TBB::tbb)
Comment thread
tmadlener marked this conversation as resolved.

target_include_directories(k4FWCorePlugins PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
Expand Down
Loading
Loading