Skip to content

feat: reader-thread affinity clamp + distributed-load hardening & public API - #31

Closed
AlperenKonukbay wants to merge 3 commits into
mainfrom
speed/fleet-hardening-v1
Closed

feat: reader-thread affinity clamp + distributed-load hardening & public API#31
AlperenKonukbay wants to merge 3 commits into
mainfrom
speed/fleet-hardening-v1

Conversation

@AlperenKonukbay

@AlperenKonukbay AlperenKonukbay commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Two independent improvements, both measured on real registry apps and multi-GPU runners.

Reader-thread cap (oversubscription only)

Reader-thread requests on the raw file->GPU path are capped at max(cpu affinity, 16); the CPU-destination eager path keeps a pure affinity clamp. FLASHPACK_NO_THREAD_CLAMP=1 restores the raw request.

This section originally shipped a pure affinity clamp on all paths. A pre-merge regression gate (same-node interleaved A/B, 12-CPU H200, 38 GB pack, parity-checked) showed that clamping the raw path below the default starves the IO queue — reader threads there are mostly blocked in pread, so they are queue depth, not CPU consumers: 12 threads read 11.3–11.9 GB/s cold vs 13.2–14.2 at 16; on a 6-CPU cpuset the gap was 2x. The clamp now floors at the default and only caps oversubscribed requests, which is the case it measurably helps: 64 threads on 12 CPUs ran unstable (1.9–4.6 s swings) while capped-to-16 was 27% faster than unclamped-64 and steady. Re-receipt on the fixed head, same node: R = 1.04/1.00/1.00 across cold-native, 6-CPU, and hot cells (pass lines 1.05/1.10).

Distributed load: hardening + public API

The rank0-read + NCCL-broadcast path runs in several production apps but had no tests and two latent issues, and was unreachable for low-level callers.

  • _broadcast_storage broadcasts uint8 views. Torch's NCCL dtype map lacks float8_e8m0fnu (the mxfp8 scale dtype), so native-dtype broadcast of an mxfp8 pack cannot work (latent failure, found by inspection rather than a production crash); byte views are dtype-agnostic and copy-free. Verified byte-exact over NCCL on 8 GPUs, including the float8 dtypes.
  • New public read_flashpack_file_distributed(); assign_from_file delegates to it. This lets low-level callers (falcon's quantized loaders) opt in — today every rank of a world-size-N app re-reads the full pack, since the O_DIRECT reader shares nothing through the page cache.
  • Clear errors for missing process group and NCCL+CPU.
  • First tests for the path: 2-rank gloo, including float8 blocks and end-to-end assign_from_file.

Measured on the real 38 GB pack, 8× H200: all-ranks-read 25.8 s max/rank page-cold (4.89 s page-hot) → rank0+broadcast 2.44 s on all eight ranks (2.22 s hot); at world=2 the cold win is ~−25%. Cross-rank parity verified on every run. Cold-tier magnitude is node-dependent (rank self-contention on one node's disk); the mechanism — N duplicate O_DIRECT reads collapsed to one — is scale-invariant.

Review notes

Two independent fleet-facing improvements, both measured on real registry
apps and multi-GPU runners (receipts in the PR body):

Reader-thread affinity clamp
- effective_read_threads(): reader-pool sizes clamp to the process's CPU
  affinity mask. Prod runners execute in dedicated cpusets (measured: 12
  CPUs exposed where the default requested 16 threads); oversubscribed
  byte-copy threads measurably hurt (-18% read wall on a 38GB pack read,
  1.70s vs 2.07s on a 12-cpu H200 runner; app-level: id-lora request 4.9%
  faster with byte-identical output). FLASHPACK_NO_THREAD_CLAMP=1 restores
  the raw request.

Distributed (rank0-read + broadcast) load hardening + public API
- _broadcast_storage now broadcasts uint8 VIEWS of the macroblocks: the
  collective moves bits, and torch's NCCL dtype map lacks float8_e8m0fnu
  (the mxfp8 scale dtype; gloo lacks all float8s) — native-dtype broadcast
  of quantized packs crashes. Byte views are dtype-agnostic and copy-free
  (verified byte-exact on real NCCL across 8 GPUs).
- read_flashpack_file_distributed(): the rank0-read + broadcast load as a
  public storage-level API (assign_from_file's use_distributed_loading
  branch now delegates to it), so low-level callers — falcon's quantized
  loaders are the motivating case — can opt in with one line. Measured on
  8x H200: all-ranks-read 25.8s (page-cold) -> broadcast 2.44s on all
  eight ranks; clear errors for no-process-group and NCCL+CPU.
- first tests for the distributed path (2-rank gloo, CPU-only): byte-view
  broadcast incl. float8 blocks, end-to-end assign_from_file, and the new
  public API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AlperenKonukbay

Copy link
Copy Markdown
Contributor Author

@claude review
@codex review
@cursor review

@AlperenKonukbay

Copy link
Copy Markdown
Contributor Author

@fal-9000 review

@jfischoff

Copy link
Copy Markdown
Contributor

Review by fal-9000 (GPT-5.6 Sol, xhigh) — reviewed at head 4a5778900cb122cb14147e5421e7a8570733f3cd, posted on the agent's behalf while its write access to this repo is being enabled (see FEA-8199).

No issues found. Checked:

  • reader-thread affinity clamping
  • byte-view distributed broadcast
  • public API delegation
  • distributed-load tests

Checks were green at review time.

…d reads regress when clamped below it

Pre-merge regression gate (same-node interleaved A/B, 12-CPU H200, 38 GB
pack, parity-checked): clamping the raw file->GPU path to the affinity
mask starved the IO queue at every tier -- cold 11.3-11.9 GB/s at 12
threads vs 13.2-14.2 at 16 (R=1.18), 2x on a 6-CPU cpuset, and 1.19x
even page-hot. Reader threads on that path are mostly blocked in pread:
they are the IO queue depth, not CPU consumers, so the affinity budget
does not apply. Oversubscribed requests still get capped (64 threads on
12 CPUs measured unstable: 1.9-4.6 s swings vs stable 3.1).

effective_read_threads gains a floor kwarg: the GPU path floors the
clamp at the 16-thread default (cap = max(affinity, default)); the
CPU-destination eager path keeps the pure affinity clamp -- its work is
minflt/memcpy-bound where oversubscription genuinely never helps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AlperenKonukbay

Copy link
Copy Markdown
Contributor Author

Post-review change in 949412b: a pre-merge regression gate showed the affinity clamp starving the IO-bound raw path (12T 11.3-11.9 GB/s vs 16T 13.2-14.2 cold, 2x on a 6-CPU cpuset). The raw path now floors the clamp at the default and only caps oversubscribed requests (t64-capped measured 27% faster than unclamped); CPU-destination reads keep the pure affinity clamp. Re-receipt same node: R=1.04/1.00/1.00. test_floor_keeps_default_on_small_affinity.

@AlperenKonukbay

Copy link
Copy Markdown
Contributor Author

@fal-9000 review

@AlperenKonukbay

Copy link
Copy Markdown
Contributor Author

@claude review
@codex review

@jfischoff

Copy link
Copy Markdown
Contributor

Closing as subsumed: this work is on main, it just did not land through this PR.

#36 was stacked on this branch, so its head contained these three commits (440413b, 4a57789, 949412b) verbatim. Merging #36 squashed the whole stack into a single commit, 99e0c70, whose diff is the union of both PRs. Your changes are in it:

file in 99e0c70 from
src/flashpack/utils.py +53 this PR (distributed-load hardening)
src/flashpack/parallel_read.py +10 / -2 this PR (reader-thread affinity clamp)
tests/test_guardrails.py +64 this PR
src/flashpack/__init__.py +2 this PR (read_flashpack_file_distributed export)
constants.py, deserialization.py, mixin.py, test_distributed_load.py, scripts/bench_distributed_shard_strategy.py rest #36

Nothing here was reverted on the way: the sharded rewrite in #36 replaced its own _read_storage_sharded, not your clamp, hardening, or the byte-view _broadcast_storage — that last one is still load-bearing and now has a test forcing the path gloo would otherwise skip.

GitHub shows this PR as CONFLICTING only because the squash rewrote the SHAs, so these commits are no longer ancestors of main. It is not a sign that anything is missing.

Two things worth flagging, since the squash cost you some credit:

  • The squash is attributed to you as PR author, but it collapsed all eight commits, so your three individual commit messages are no longer in the history. Sorry — merge commits are disabled on this repo and I did not catch that squash was the only path until after the fact. If you want the granular history back, the fix is to revert 99e0c70 and reland the stack with a rebase merge; say the word and I will do it.
  • speed/fleet-hardening-v1 is still around (auto-delete only fires on merge, and this is being closed). Safe to delete whenever you like.

Full context on what #36 became, including the measurements: #36.

@jfischoff jfischoff closed this Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants