Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7ab3d4b
fix(hrr): validate pitched D2H copies over the copied rows only
andraghetti Sep 30, 2026
8407310
test(hrr): cover pitched D2H copies with offsets and blob edits
andraghetti Sep 30, 2026
b1be9e3
fix(hrr): size and guard the hipMemcpy3D H2D source blob
andraghetti Sep 30, 2026
5fddcfd
fix(hrr): count short and overflowing pitched D2H checks as skipped
andraghetti Oct 1, 2026
63b9d44
fix(hrr): keep host bytes around pitched copy rows out of capture blobs
andraghetti Oct 1, 2026
57e9b51
fix(hrr): synchronise null-stream async D2H copies before the snapshot
andraghetti Oct 1, 2026
33bf3c1
fix(hrr): wait for the fill before the async pitched H2D copy
andraghetti Oct 1, 2026
bc32594
test(hrr): pitched D2H through the _spt 3D and Param2D copies
andraghetti Oct 1, 2026
fd6c94e
fix(hrr): count an unmapped 3D D2H source only where it would be vali…
andraghetti Oct 1, 2026
c2e2c7e
fix(hrr): validate a dense pitched D2H in place and pack pitched rows…
andraghetti Oct 1, 2026
c9b4d5e
fix(hrr): make the pitched D2H scratch a handled allocation
andraghetti Oct 1, 2026
cf7e80f
fix(hrr): count an unmapped D2H source only where replay would validate
andraghetti Oct 1, 2026
5242dcb
fix(hrr): keep hipMemcpy3DAsync_spt on the per-thread default stream
andraghetti Oct 1, 2026
1d614ef
fix(hrr): check the capture-side host rect footprint for overflow
andraghetti Oct 1, 2026
dcf60b3
fix(hrr): replay hipMemcpy3D_spt on the per-thread stream and cover t…
andraghetti Oct 1, 2026
dd59783
fix(hrr): skip a hipMemcpy2D H2D whose source blob is short of the rect
andraghetti Oct 5, 2026
a9660e7
Merge branch 'develop' into users/andraghetti/hrr-pitched-d2h
andraghetti Oct 5, 2026
434e353
test(hrr): cover dense D2H host rects that start past the base pointer
andraghetti Oct 5, 2026
3c43a89
fix(hrr): record only the copied rows of pitched host copies
andraghetti Oct 5, 2026
be4c959
Merge branch 'develop' into users/andraghetti/hrr-pitched-d2h
andraghetti Oct 6, 2026
cd314db
hrr: bump HRR_VERSION to 7 for packed host rects
andraghetti Oct 7, 2026
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
198 changes: 155 additions & 43 deletions projects/clr/hipamd/src/hrr/hip_capture.cpp

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion projects/clr/hipamd/src/hrr/hip_capture_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,9 @@ bool open(const char* output_dir) {
return false;
}

hrr_file_header fh{HRR_MAGIC, HRR_VERSION, 0};
// Pitched host copies record only their copied rows (hip_capture.cpp,
// write_host_rect_blob), which replay reads back by this flag.
hrr_file_header fh{HRR_MAGIC, HRR_VERSION, HRR_FILE_FLAG_PACKED_HOST_RECTS};
Comment thread
andraghetti marked this conversation as resolved.
Resolved
buffer_append_locked(&fh, sizeof(fh));
return true;
}
Expand Down
28 changes: 17 additions & 11 deletions projects/hrr/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ precisely the ones left without a trailer and absent from the root index, while
the parent that exited cleanly needs no repair. Sub-archives that already carry
a clean trailer are skipped without being read.

### Archive Format (v6)
### Archive Format (v7)
```
capture.hrr/
manifest.json { version, capture_mode, owner_pid, processes[] }
Expand Down Expand Up @@ -242,7 +242,7 @@ projects/hrr/ — standalone HRR project (portable layer)
Not used by capture — it is the format an
out-of-tree producer writes and playback reads
playback/
hrr_reader.h/.cpp — archive loader, v6 format; record framing
hrr_reader.h/.cpp — archive loader, v7 format; record framing
(read_raw_record / open_record_stream) shared
with the region sidecars
hrr_region_map.h/.cpp — region timeline: merge, cursor, live block set,
Expand Down Expand Up @@ -321,13 +321,13 @@ The generator classifies each API:
Generated capture shims for manual APIs are pass-throughs (no `write_event()`).
When adding HIP API support, update this script to classify the API in the appropriate capture and playback policy sets. APIs requiring non-trivial serialization or replay belong in `MANUAL_CAPTURE_APIS` and/or `MANUAL_PLAYBACK_APIS`; intentionally unsupported replay APIs belong in `NOOP_PLAYBACK_APIS`.

## Archive Format (v6)
## Archive Format (v7)

Single-authority definition in `hrr_api_args.h` (auto-generated):

```
HRR_MAGIC = 0x52524845 ("HRRE")
HRR_VERSION = 6
HRR_VERSION = 7
```

Version history, so an archive written by an older runtime can be placed:
Expand All @@ -352,12 +352,16 @@ Version history, so an archive written by an older runtime can be placed:
or a reader that translates the tail. Both cases assume the dispatch tables
only ever grow at the end; an insertion anywhere else moves the IDs after it.
A retired dispatch-table slot (nulled `void*`) still occupies an ID.
- **v7** packs the host blob of every pitched copy and sets
`HRR_FILE_FLAG_PACKED_HOST_RECTS` in `hrr_file_header.reserved`. A v6 reader
ignores that field and would replay a packed blob with the recorded pitch,
reading past its end. See 2D/3D Memcpy and Memset below.

```
<output_dir>/
manifest.json { version, capture_mode, owner_pid, processes[] }
(version here is the manifest schema = 1, distinct from the
events.bin HRR_VERSION = 6)
events.bin HRR_VERSION = 7)
pid-<pid>/
manifest.json { pid, parent_pid, complete, event_count, blob_count }
writer_state.json checkpoint cursor (next_seq, event/blob counts, events file
Expand All @@ -371,7 +375,7 @@ Version history, so an archive written by an older runtime can be placed:
### `events.bin` Layout

```
[0..7] hrr_file_header { magic:u32, version:u16, reserved:u16 }
[0..7] hrr_file_header { magic:u32, version:u16, reserved:u16 (HRR_FILE_FLAG_* bits) }
[8..] records, back-to-back, no padding:
hrr_event_header (32 bytes, pack(1)):
event_type u16 hrr_api_id_t (0..552)
Expand Down Expand Up @@ -1331,7 +1335,7 @@ The event wire format (finding H5):
shrinking `reserved` to 2 bytes), so kernel launches with large serialized payloads
(many args / long mangled names / large by-value structs) up to ~4 GiB are recorded
normally instead of being dropped at 65535 bytes. This is the change that bumped
`HRR_VERSION` to 4; the current version is 6, see Archive Format above. The
`HRR_VERSION` to 4; the current version is 7, see Archive Format above. The
writer's single-record buffer path now writes any oversized record straight through.
- **Per-argument size limit (64 KiB) now fails loudly.** Each kernel arg's size is still
a `uint16_t`. A by-value struct argument ≥ 64 KiB cannot be represented, so the launch
Expand All @@ -1348,11 +1352,15 @@ The event wire format (finding H5):

- `hipMemcpy2D` / `hipMemcpy2DAsync` are now blob-captured (finding H3), matching the 1D
and 3D families. They are `MANUAL_CAPTURE_APIS` / `MANUAL_PLAYBACK_APIS`: H2D snapshots
the pitched host `src` region (`spitch*(height-1)+width` bytes) as a blob, and at
the copied rows of the pitched host `src` (`width*height` bytes) as a blob, and at
replay the captured blob is substituted for the untranslatable capture-time host VA
and copied into the translated device `dst` with the recorded `dpitch`. D2H snapshots
the host `dst` after the copy and validates the device result against it at replay.
Row-padded image/tensor buffers are now handled.
- Every pitched host blob holds only the copied rows, packed end to end: `width*height*depth` bytes, whatever the pitch and offsets. That covers the D2H expected output of `hipMemcpy2D`, the four `hipMemcpy3D` spellings, `hipDrvMemcpy3D` / `hipDrvMemcpy3DAsync` and the three driver 2D spellings, and the H2D source of the same copies. Capture never reads the bytes between rows or before the first one. Unrelated host data cannot reach the archive, an unmapped gap (a guard page between rows) cannot fault the application, and a sparse pitch costs nothing: two rows gigabytes apart record two rows.
- `events.bin` marks such an archive with `HRR_FILE_FLAG_PACKED_HOST_RECTS` in `hrr_file_header.reserved`, and the layout came with the v7 bump, since a v6 reader ignores the flag. Replay then gives the host side of each pitched copy the dense layout of its blob (pitch equal to width, no offsets) and keeps the device side as recorded, so the copy moves the same bytes. A D2H check re-runs the copy into a scratch buffer of the blob's size and compares the two. Neither side of replay grows with the host pitch.
- An archive without the flag lays a pitched host blob out from the base pointer with the recorded pitch and offsets, or holds the flat `width*height*depth` bytes (3D and driver D2H, `hipMemcpy3D` H2D). Replay keeps the recorded layout for it and compares only the copied rows. It skips a copy whose blob does not span the recorded rect, such as the flat blob of a rect that is not dense from the base, rather than read past the blob or issue it from the capture-time host address. A recorded rect whose footprint overflows `size_t` is skipped too. An archive whose every D2H check is skipped fails the replay rather than passing as one with no validation blobs.
- The H2D source of the four `hipMemcpy3D` spellings is recorded only for a copy the runtime accepted.
- `hipMemset3D` / `hipMemset3DAsync` drop the destination pitched pointer/extent at
capture (`pitchedDevPtr = 0`) and no-op at replay, so 3D-memset-initialized regions
are invisible to replay.
Expand All @@ -1372,9 +1380,7 @@ dispatch before the create populates the translation map and silently

D2H validation can pass when replay actually diverged:

- **Length clamp.** Comparison uses `min(copy_size, blob_size)`; a truncated or
crash-recovered blob validates only a prefix (the corrupted tail is unchecked) and
still counts as PASS. A zero-length compare counts as pass.
- **Length clamp.** Linear copies compare `min(copy_size, blob_size)`; a truncated or crash-recovered blob validates only a prefix (the corrupted tail is unchecked) and still counts as PASS. A zero-length compare counts as pass. The 2D, 3D and driver copies do not clamp: a blob shorter than the host rect replay reads it as is not validated.
- **Float-dtype guessing.** Blobs carry no dtype. On a byte mismatch the validator
tries `{fp32, bf16, fp16, fp64}` and passes on the first encoding within tolerance,
so integer/index/pointer output buffers can silently false-pass; both-NaN counts as
Expand Down
19 changes: 16 additions & 3 deletions projects/hrr/include/hrr/hrr_api_args.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,31 @@
* v6: pointer arguments whose pointee used to be dropped now carry it inline
* (DEREF_FIELDS). Event payloads grew for ~50 APIs, so an archive written
* before v6 cannot be read by a v6 reader: re-capture rather than replay an
* old recording. */
#define HRR_VERSION ((uint16_t)6u)
* old recording.
* v7: the host blobs of the pitched copies are packed, and the writer sets
* HRR_FILE_FLAG_PACKED_HOST_RECTS. A v6 reader ignored the flag and replayed
* a packed blob with the recorded pitch, reading past its end. */
#define HRR_VERSION ((uint16_t)7u)

/* Written once at byte 0 of events.bin. */
#pragma pack(push, 1)
typedef struct {
uint32_t magic; /* HRR_MAGIC */
uint16_t version; /* HRR_VERSION */
uint16_t reserved; /* zero */
uint16_t reserved; /* HRR_FILE_FLAG_* bits */
} hrr_file_header;
#pragma pack(pop)

/* Bits of hrr_file_header.reserved in events.bin. A reader before v7 ignores
* them, which is why the packed layout came with the v7 bump.
* HRR_FILE_FLAG_PACKED_HOST_RECTS: the host blobs of the pitched copies
* (hipMemcpy2D, the hipMemcpy3D family and the driver 2D/3D copies) hold only
* the copied rows, packed end to end: width*height*depth bytes, whatever the
* pitch. Without it, a blob spans the host rect from its base pointer through
* the last copied byte, or holds the flat width*height*depth bytes from the
* base pointer (3D and driver D2H, hipMemcpy3D H2D). */
#define HRR_FILE_FLAG_PACKED_HOST_RECTS ((uint16_t)0x0001u)

#ifdef __cplusplus
static_assert(sizeof(hrr_file_header) == 8, "hrr_file_header must be 8 bytes");
#endif
Expand Down
Loading
Loading