Skip to content

perf(runtime): upload stacked-tensor shards concurrently - #2292

Open
lterrac wants to merge 2 commits into
hw-native-sys:mainfrom
lterrac:perf/parallel-stacked-shard-upload
Open

perf(runtime): upload stacked-tensor shards concurrently#2292
lterrac wants to merge 2 commits into
hw-native-sys:mainfrom
lterrac:perf/parallel-stacked-shard-upload

Conversation

@lterrac

@lterrac lterrac commented Aug 5, 2026

Copy link
Copy Markdown

What

DistributedWorker.alloc_stacked_tensor uploads shard i to worker i in a serial loop, so a rank-stacked resident weight moves at single-chip H2D bandwidth no matter how many chips the group spans. Each shard targets a different chip worker and nothing orders them, so this drives them from a thread pool.

Measured

8 × 910B2, DeepSeek V4 Flash W8A8, 346 GB of rank-stacked resident weights (11.5 GB per shard), warm start:

before after
resident weight upload 57.1s 10.4s (6.0 → 33.2 GB/s)
engine startup 82.0s 35.1s

33 GB/s is what 8 independent processes reach on that node doing a raw cold H2D from a shared mapping, so the upload is now hardware-bound.

Depends on hw-native-sys/simpler#1702

Simpler holds one process-wide lock (_child_prov_lock) across the native half of malloc / copy_to, which serializes the whole group regardless of how the caller issues it — with the serial loop replaced by a thread pool and that lock still in place, the measurement is unchanged (57s). Simpler#1702 narrows it to a per-worker lock.

Without that change this commit is a no-op, not a regression — the threads simply queue on the same lock — so the merge order does not matter for correctness.

Rollback semantics

The serial loop could roll back by zipping shards with ids positionally, because the successes were always a prefix. A concurrent failure can land anywhere in the group, so successes are collected by index and freed against their own worker before the first error is re-raised. The pre-existing except clause remains as the backstop for anything that fails after the group is complete; shards is either empty or complete when it runs, so nothing is freed twice.

Threads only call alloc_tensor, whose malloc / copy_to bindings release the GIL, and each targets a distinct worker_id, so no new shared state is introduced on this path.

Validation

  • Full 43-layer DeepSeek V4 Flash W8A8 run on 8 cards with this exact commit: generated output unchanged ("Huawei is" → " a leading global information and communications technology (ICT) solutions provider. Through our dedication"), no errors.
  • Isolated harness (tiny 8-rank program + fresh shared-memory bands, so every page is read cold exactly once like a real upload): threaded 28.2 / 17.1 GB/s vs serial 8.6 / 8.1 GB/s.
  • ruff check passes.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bff1f0f5-3bc1-4bbf-b1c2-a07bf55e38fc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Stacked-tensor upload handling

Layer / File(s) Summary
Concurrent upload and failure cleanup
python/pypto/runtime/distributed_runner.py
ThreadPoolExecutor uploads shards concurrently. The runner tracks successful uploads by index. On failure, it releases completed uploads and re-raises the first error.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit watched the shards take flight,
In parallel, swift and light.
When one upload missed its mark,
Cleanup chased it through the dark.
Then the first error hopped back bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: concurrent upload of stacked-tensor shards at runtime.
Description check ✅ Passed The description directly explains the concurrent upload implementation, rollback handling, dependency, performance results, and validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80ad527baa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1858 to +1862
return self.alloc_tensor(
tuple(host.shape[1:]),
host.dtype,
init=host[i].contiguous(),
worker_id=w,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate stacked host before starting uploads

When host is not shared or registered, each submitted shard reaches Worker.alloc_tensor, which performs malloc before _prepare_init rejects the host buffer. Because the pool starts all shards at once, an invalid large stacked tensor can transiently allocate every shard across the group and surface backend OOM/free failures instead of the intended shared-memory ValueError; validate the stacked host/shard buffer before submitting the concurrent uploads.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Valid, and confirmed in the code: _require_forked_host_buffer is only reached from _prepare_init, which alloc_device_tensor runs after malloc(nbytes). Serially that cost one shard's malloc before raising; concurrently all N commit device memory first, so a bad 8 × 11.5 GB stacked tensor would surface an allocator failure instead of the shared-memory ValueError.

Fixed in a43d66d by validating the stacked host once before any upload starts:

self._require_forked_host_buffer(host, "alloc_stacked_tensor(host=...)", "read")

The check is storage-based and host[i] is a view of the same storage, so validating the stacked tensor covers every shard; alloc_tensor's per-shard check stays as-is. This also matches what copy_stacked_from already does for read-backs.

Verified it does not reject legitimate inputs: a full 8-card DeepSeek V4 Flash W8A8 run (whose stacked weights come in through inherited_host_tensors, not share_memory_()) still uploads clean — 346 GB in 9.3s, generated output unchanged, no errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
python/pypto/runtime/distributed_runner.py (1)

1857-1863: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Validate host before starting the thread pool.

alloc_stacked_tensor requires a CPU, contiguous, pre-fork host buffer. The current path checks this only after each task calls host[i].contiguous(). An invalid non-contiguous or non-CPU input can therefore create one temporary copy per shard before all tasks fail.

Call _require_forked_host_buffer(host, "alloc_stacked_tensor(host=...)", "read") before creating the pool. Valid inputs keep the same behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/pypto/runtime/distributed_runner.py` around lines 1857 - 1863, In
alloc_stacked_tensor, validate host with _require_forked_host_buffer(host,
"alloc_stacked_tensor(host=...)", "read") before creating the thread pool or
submitting _upload_shard tasks. Preserve the existing per-shard allocation
behavior for valid CPU, contiguous inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/pypto/runtime/distributed_runner.py`:
- Around line 1865-1879: Update the upload flow around uploaded, pool.submit,
and shards.extend so every submitted future is drained and successful tensors
remain rollback-tracked until ownership reaches shards. Handle submission and
future failures, including BaseException, with cleanup in finally; preserve and
re-raise the original upload error after rollback. Remove the inline Ruff noqa
and configure the intentional broad-exception ignore per file instead.

---

Nitpick comments:
In `@python/pypto/runtime/distributed_runner.py`:
- Around line 1857-1863: In alloc_stacked_tensor, validate host with
_require_forked_host_buffer(host, "alloc_stacked_tensor(host=...)", "read")
before creating the thread pool or submitting _upload_shard tasks. Preserve the
existing per-shard allocation behavior for valid CPU, contiguous inputs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e0322e5-da90-4553-85ce-13fc1448ae49

📥 Commits

Reviewing files that changed from the base of the PR and between 03751fe and 80ad527.

📒 Files selected for processing (1)
  • python/pypto/runtime/distributed_runner.py

Comment thread python/pypto/runtime/distributed_runner.py Outdated
lterrac added 2 commits August 6, 2026 12:27
alloc_stacked_tensor uploads shard i to worker i in a serial loop, so a
rank-stacked resident weight moves at single-chip H2D bandwidth no matter
how many chips the group spans. Each shard targets a different chip
worker and nothing orders them, so drive them from a thread pool.

Rolling back needs a little more care than the serial loop: a concurrent
failure can land anywhere in the group, so the successes are no longer a
prefix of ids. Collect them by index and free them against their own
worker before re-raising, instead of zipping shards with ids positionally.

Measured on 8 x 910B2 uploading DeepSeek V4 Flash W8A8's 346 GB of
rank-stacked weights (per-shard 11.5 GB):

  upload   57.1s -> 10.4s   (6.0 -> 33.2 GB/s)
  startup  82.0s -> 35.1s

This needs the matching Simpler change (hw-native-sys/simpler#1702) to
pay off: Simpler holds one process-wide lock across the native half of
malloc / copy_to, which serializes the group regardless of how the
caller issues it. Without that change this commit is a no-op, not a
regression.
…upload

Two problems the concurrent upload introduced, both from review:

`alloc_tensor` only rejects an unreachable host buffer from `_prepare_init`,
which runs *after* its device malloc. Serially an invalid host cost one
malloc before raising; concurrently every shard commits device memory
first, so a bad 8x11.5 GB stacked tensor would surface an allocator
failure instead of the intended ValueError. Validate the stacked host once
before any upload starts, mirroring what copy_stacked_from already does
for read-backs.

The collection loop also stopped draining at the first failed future, and
`except Exception` let a BaseException (or a failure while submitting)
escape with successful uploads still unowned — the rollback below walks
`shards`, which is only populated once the whole group succeeded. Submit
defensively, drain every future that was submitted, and roll back by index
against the owning worker before re-raising the first error.

The inline `# noqa: PERF203` is dropped rather than moved to a per-file
ignore: neither PERF nor BLE is in this repo's ruff select, so it was
suppressing nothing.

Re-validated on 8 x 910B2: DeepSeek V4 Flash W8A8 resident upload 9.3s
(346 GB), generated output unchanged, no errors; isolated harness threaded
22.7 / 30.7 GB/s vs serial 10.5 / 6.2 GB/s.
@lterrac
lterrac force-pushed the perf/parallel-stacked-shard-upload branch from a43d66d to d4ef2a9 Compare August 6, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant