Skip to content

Commit c6be27b

Browse files
authored
Merge branch 'argonne-lcf:devel' into devel
2 parents 3b248b0 + 70d90f7 commit c6be27b

5 files changed

Lines changed: 743 additions & 964 deletions

File tree

‎backends/mpi/tracer_mpi_helpers.include.c‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@ static void _load_tracer(void) {
88
int verbose = 0;
99

1010
s = getenv("LTTNG_UST_MPI_LIBMPI");
11+
/* RTLD_GLOBAL: promote the real libmpi's symbols into the global scope so
12+
* that symbols we do NOT wrap (e.g. MPIX_GPU_query_support) remain resolvable
13+
* by the traced application. With RTLD_LOCAL they were hidden, causing
14+
* "undefined symbol" errors at lookup time. Our own preloaded wrappers still
15+
* take precedence for the symbols we do wrap (they are earlier in the global
16+
* scope), and RTLD_DEEPBIND keeps the real lib's internal calls bound to
17+
* itself rather than re-entering our wrappers. */
1118
if (s)
12-
handle = dlopen(s, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
19+
handle = dlopen(s, RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND);
1320
else
14-
handle = dlopen("libmpi.so", RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
21+
handle = dlopen("libmpi.so", RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND);
1522
if (handle) {
1623
void *ptr = dlsym(handle, "MPI_Init");
1724
if (ptr == (void *)&MPI_Init) { // opening oneself

‎backends/ze/README.md‎

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Header Location
1+
# How to Update the Header
2+
3+
## Header Location
24

35
- Standard: `https://github.com/argonne-lcf/level-zero-spec/tree/ddi_ver`
46
- Loader: `https://github.com/oneapi-src/level-zero/tags`
@@ -165,3 +167,121 @@ PogrammableParamValueInfoExp>': uninitialized constant ZE::ZETMetricProgrammable
165167
```
166168
Update in `backends/ze/gen_ze_library.rb`, Module ZE.
167169
TODO: `h2yaml` should be able to find all the constants defined.
170+
171+
# Timestamp profiling architecture
172+
173+
This documents the runtime bookkeeping in `tracer_ze_helpers.include.c` /
174+
`ze_model.rb` that measures how long each submitted command takes.
175+
176+
## Why it needs bookkeeping at all
177+
178+
The goal is to measure each command submitted to the GPU. The only way is to
179+
read a profiling event the command signals. The subtlety: users are free to
180+
re-signal, share, and reset their events. So:
181+
182+
- **If the user passes no signal event**, we inject our own and signal it.
183+
- **If the user passes a signal event**, we swap in our injected event, then
184+
re-expose the user's event (a chained SignalEvent / Barrier) so their code
185+
still works.
186+
187+
At synchronization time we host-read our injected event to get the real kernel
188+
timing. Two API facts stop this from being a simple per-event list:
189+
190+
1. **Users sync more than events** — a whole command list, a queue, or a fence,
191+
not just `zeEventHostSynchronize`. So we must track, per command list, the
192+
set of outstanding profiled commands (and a live count, to know when a cl is
193+
fully drained).
194+
2. **A command signaled on one cl can be waited on from another** (cross-cl
195+
`phWaitEvents`). So the dependency chain spans command lists, and a command's
196+
storage must outlive its own cl being reset/destroyed while another cl still
197+
depends on it (the **detach** mechanism).
198+
199+
Each profiled Append becomes a **slot** (injected event + dependency edges +
200+
refcount). Slots are grouped per cl in fixed-size **slabs** (so cl/queue/fence
201+
sync can find them) but linked by raw pointers across cls (for the dependency
202+
chain).
203+
204+
## Ownership
205+
206+
```mermaid
207+
graph TD
208+
cls["_ze_cls (global uthash)"] --> cld["cl_data"]
209+
cld -- slabs (utlist DL) --> s1["slab"]
210+
s1 --> s2["slab"]
211+
s2 --> s3["slab (tail)"]
212+
s1 -- "slots[64] inline" --> slot["slot"]
213+
cld -. "live_slots (in-order only)" .-> slot
214+
slot -- "slab (back-link)" --> s1
215+
slot -- "preds[] (may cross cl)" --> other["slot in ANOTHER cl"]
216+
other -. "each edge: target->refs++" .-> slot
217+
```
218+
219+
Slabs grow by appending (never realloc), so a slot's address is stable for as
220+
long as it lives — required, because other slots hold raw pointers to it.
221+
222+
**Why the slot -> slab back-link:** `_slot_drain` / `_slot_release` are entered
223+
holding only a bare slot pointer (a pred in another cl). To reach the counters
224+
or the owning cl they walk up via `slot->slab->owner`. The slab knows its slots
225+
(inline array); the slot must know its slab for the reverse direction.
226+
227+
## Counters
228+
229+
| counter | on | meaning |
230+
|---|---|---|
231+
| `n_used` | slab | slots ever handed out (monotonic) |
232+
| `n_held` | slab | slots not yet `_slot_release`'d (`n_used` − releases) |
233+
| `n_pinned` | slab | >0 only when **detached**: # slots still referenced cross-cl |
234+
| `refs` | slot | # live downstream slots whose `preds[]` point at this slot |
235+
236+
`refs` is a **count, not a list** — we only ask "does anyone still depend on
237+
me?", never "who?". A slot is freed only when `live==0` (drained itself) AND
238+
`refs==0` (nobody points at it); the last dependent to drop the ref frees it.
239+
That is what lets us avoid a reverse index (see the detach note below).
240+
241+
## Slot state machine
242+
243+
```mermaid
244+
stateDiagram-v2
245+
[*] --> LIVE: _slot_instantiate<br/>(resolve preds, preds[i]->refs++)
246+
LIVE --> DRAINED: _slot_drain<br/>(recurse preds, emit tracepoint,<br/>--pred->refs each)
247+
DRAINED --> RELEASED: _slot_release<br/>(iff refs==0: dispose inj, --slab.n_held)
248+
RELEASED --> [*]: slab freed when<br/>n_held==0 && not tail
249+
```
250+
251+
## Teardown paths
252+
253+
```mermaid
254+
graph TD
255+
A["imm slot drains, unreferenced"] --> A1["_slot_drain -> _slot_release"]
256+
B["cl RESET / single DESTROY (ctx alive)"] --> B1["_cl_reclaim_slabs, per slab"]
257+
B1 --> B2{"any slot still ref'd cross-cl?"}
258+
B2 -- no --> B3["free slab now"]
259+
B2 -- yes --> B4["DETACH: owner=NULL, n_pinned;<br/>referrer's later drain frees it"]
260+
C["ctx DESTROY"] --> C1["_cl_data_destroy(ctx_destroy=1):<br/>destroy L0 event + free slot struct"]
261+
D["event DESTROY"] --> D1["_on_destroy_event: evict<br/>_ze_event_state entry (kts/latest)"]
262+
```
263+
264+
**Detach (`owner=NULL`).** Consider:
265+
266+
```
267+
CL1.Append(signal=e1) // slot P
268+
CL2.Append(signal=e2, wait=e1) // slot Q; Q.preds=[P], so P.refs=1
269+
CL1.destroy() // P still referenced by Q -> P DETACHED
270+
CL2.sync() // drains Q, --P.refs -> 0, frees P's slab
271+
```
272+
273+
At `CL1.destroy()`, `CL2` still has a pred pointing into `CL1`'s slab (legal L0 —
274+
there is no ordering rule for cross-cl event deps, unlike fences/queues), so
275+
freeing that slab would dangle `Q.preds[]`. Instead the slab is *detached*
276+
(unlinked, `owner=NULL`, `n_pinned` = referenced slots) and outlives `CL1`. Then
277+
`CL2.sync()` drains `Q`, drops the last ref on `P` (`--P.refs -> 0`), and *that*
278+
downstream drain frees the detached slab (`n_pinned -> 0`).
279+
280+
(If `CL1` were synced+destroyed *before* `CL2`'s Append, `e1` would already have
281+
drained, the `wait=e1` would resolve to nothing, and no cross-cl edge — hence no
282+
detach — would exist. Detach only matters while the dependency is still live.)
283+
284+
The alternative — severing `CL2`'s edges at `CL1` teardown — is rejected: edges
285+
are one-directional (`preds[]` + a `refs` count, no reverse index), so finding
286+
referrers would need an O(all-live-slots) global scan or a per-Append successor
287+
list. Detach is what keeps `refs` a mere count.

‎backends/ze/gen_ze.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
puts <<~EOF
44
#include <stdint.h>
55
#include <stddef.h>
6+
#include <stdbool.h>
67
#include "ze.h.include"
78
#include <dlfcn.h>
8-
#include <dlfcn.h>
99
#include <stdio.h>
1010
#include <stdlib.h>
1111
#include <alloca.h>

0 commit comments

Comments
 (0)