Summary.
_s3_stream_s3dlio() hardcodes skip_head=False for records ≥ 16 MiB
(dlio_benchmark/reader/_s3_iterable_mixin.py#L407). The HEAD supplies the object size
that activates s3dlio's concurrent range-split engine (own threshold: 32 MiB, S3DLIO_RANGE_THRESHOLD_MB), so every unet3d sample (139.8 MiB) becomes 1 HEAD + ~4–5 concurrent range-GETs instead of one whole-object GET.
Impact.
For the same configuration (same read_threads,prefetch_window, dataset), the forced HEAD+range path costs ~20 AU points vs whole-object GETs — the difference between unet3d passing and failing its 90% AU bar.
Actual finding
The comment above the heuristic justifies the HEAD as enabling range-split from epoch 1 instead of epoch 2+ (via s3dlio's size cache). Per-epoch AU shows otherwise: with skip_head=True both epochs run at identical AU (no automatic epoch-2 range-split — the per-process size cache does not survive DLIO's per-epoch DataLoader worker re-spawn), and with skip_head=False both epochs pay the full penalty. In practice the flag selects range-split always vs never, not epoch-1 vs epoch-2+. DLIO already parallelizes across objects (read_threads × prefetch_window); intra-object range parallelism only multiplies the request count ~5× in the case for unet3d and contends with the pipeline's own in-flight window. Since skip_head is not user-tunable and is in neither CLOSED nor OPEN allowed params, this default alone determines what every object-storage submission measures.
My suspicion is that the clearing caches call in between epochs the mlpstorage executable reverts the DLIO optimization and therefore the caches stay cold.
Proposed change.
In dlio_benchmark/reader/_s3_iterable_mixin.py, line 407 (mlcommons/DLIO_local_changes @ f6796ed): default to whole-object GETs for all record sizes; keep HEAD+range-split as an opt-in for stores where per-stream bandwidth is the bottleneck.
- # skip_head controls whether s3dlio issues a HEAD before each GET.
- # Default (True): skip HEAD, do plain GET, cache the size — epoch 2+
- # will range-split correctly for large objects automatically.
- # False: issue HEAD first so range splitting can fire from epoch 1.
- # We override to False only when we know objects are >= 16 MiB, since
- # those benefit most from parallel range GETs on the very first epoch.
- record_bytes = getattr(args, "record_length", 0) or 0
- skip_head = not (record_bytes >= 16 * 1024 * 1024)
+ skip_head = True
Summary.
_s3_stream_s3dlio()hardcodesskip_head=Falsefor records ≥ 16 MiB(
dlio_benchmark/reader/_s3_iterable_mixin.py#L407). The HEAD supplies the object sizethat activates s3dlio's concurrent range-split engine (own threshold: 32 MiB,
S3DLIO_RANGE_THRESHOLD_MB), so every unet3d sample (139.8 MiB) becomes 1 HEAD + ~4–5 concurrent range-GETs instead of one whole-object GET.Impact.
For the same configuration (same
read_threads,prefetch_window, dataset), the forced HEAD+range path costs ~20 AU points vs whole-object GETs — the difference between unet3d passing and failing its 90% AU bar.Actual finding
The comment above the heuristic justifies the HEAD as enabling range-split from epoch 1 instead of epoch 2+ (via s3dlio's size cache). Per-epoch AU shows otherwise: with
skip_head=Trueboth epochs run at identical AU (no automatic epoch-2 range-split — the per-process size cache does not survive DLIO's per-epoch DataLoader worker re-spawn), and withskip_head=Falseboth epochs pay the full penalty. In practice the flag selects range-split always vs never, not epoch-1 vs epoch-2+. DLIO already parallelizes across objects (read_threads × prefetch_window); intra-object range parallelism only multiplies the request count ~5× in the case for unet3d and contends with the pipeline's own in-flight window. Sinceskip_headis not user-tunable and is in neither CLOSED nor OPEN allowed params, this default alone determines what every object-storage submission measures.My suspicion is that the clearing caches call in between epochs the
mlpstorageexecutable reverts the DLIO optimization and therefore the caches stay cold.Proposed change.
In
dlio_benchmark/reader/_s3_iterable_mixin.py, line 407 (mlcommons/DLIO_local_changes @f6796ed): default to whole-object GETs for all record sizes; keep HEAD+range-split as an opt-in for stores where per-stream bandwidth is the bottleneck.