Skip to content

feat: faster parallel S3 file downloads - #5102

Merged
sivanantha321 merged 3 commits into
kserve:masterfrom
bluayer:feat/fast-s3-uploader
Feb 20, 2026
Merged

sivanantha321 merged 3 commits into
kserve:masterfrom
bluayer:feat/fast-s3-uploader

Conversation

@bluayer

@bluayer bluayer commented Feb 20, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

multiprocessing.Pool.map distributes tasks in batches (chunksize = ceil(N/(P*4))). When multiple large model shards end up in the same batch, they are downloaded sequentially by a single worker process while other processes sit idle.

For example, Qwen3-4B has 27 files with 4 worker processes. pool.map calculates chunksize = ceil(27/16) = 2, meaning tasks are dispatched in pairs. The files are listed in alphabetical order from S3:

task[0]:  preprocessor_config.json                                    (390 Bytes)
task[1]:  ~~.json                                                     (N bytes)
...
task[20]: model-00001-of-00003.safetensors                 (3,957 MB) - Same proccess
task[21]: model-00002-of-00003.safetensors                 (3,987 MB) - Same process
task[22]: model-00003-of-00003.safetensors                 (95 MB)
...

With chunksize=2, task[20] and task[21] can batched together and assigned to the same worker process. That single process downloads them sequentially (12s + 12s = 24s) while other processes finish their small files in under 1s and sit idle.

With imap_unordered(chunksize=1), each task is dispatched individually to the next available worker. The two large shards are picked up by different processes and downloaded in parallel (~12s total).

This pattern is common across LLM model repositories where large safetensor shards dominate the total download time alongside many small metadata/config files.

Which issue(s) this PR fixes:
Fixes #

Feature/Issue validation/testing:

  • Existing unit tests pass (37 passed)

  • E2E test on g6e.4xlarge with Qwen3-4B (~8GB, 3 shards): 20s → 11.5s

  • E2E test on g6e.4xlarge with Qwen3-VL-8B-Instruct (~16GB, 4 shards): 33s → 16s

  • Per-process analysis confirmed large shards are now assigned to different worker PIDs

  • Logs (pool.map)

PID=9  model-00001-of-00003.safetensors 3775MB 11.96s 316MB/s
PID=9  model-00002-of-00003.safetensors 3803MB 11.87s 320MB/s  ← same PID
TOTAL: 24.08s
  • Logs (imap_unordered)
PID=8  model-00001-of-00003.safetensors 3775MB 12.88s 293MB/s
PID=7  model-00002-of-00003.safetensors 3803MB 13.55s 281MB/s  ← different PID
TOTAL: 13.57s

Special notes for your reviewer:

  • imap_unordered returns an iterator instead of a list, so it is wrapped with list() to maintain the same behavior as pool.map (wait for all tasks to complete before processing results).
  • MockPool in test_s3_storage.py is updated with imap_unordered method.

Checklist:

  • Have you added unit/e2e tests that prove your fix is effective or that this feature works?
  • Has code been commented, particularly in hard-to-understand areas?
  • Have you made corresponding changes to the documentation?
Improve S3 model download speed using imap_unordered

@bluayer bluayer changed the title feat: use imap_unordered for faster parallel S3 file downloads feat: faster parallel S3 file downloads Feb 20, 2026
Replace pool.map with pool.imap_unordered(chunksize=1) to distribute
download tasks one at a time across worker processes. pool.map batches
tasks by chunksize, which can assign multiple large files to the same
process for sequential processing.

Benchmarked on g6e.4xlarge with Qwen3-4B (8GB) and Qwen3-VL-8B (16GB):
- Qwen3-4B: 20s → 11.5s (1.7x faster)
- Qwen3-VL-8B: 33s → 16s (2.1x faster)

Signed-off-by: Jungwoo Song <bluayer@gmail.com>
@bluayer
bluayer force-pushed the feat/fast-s3-uploader branch from e3441da to 88c19a3 Compare February 20, 2026 03:50
@sivanantha321

Copy link
Copy Markdown
Member

good find!
/lgtm
/approve

@github-actions github-actions Bot added the lgtm label Feb 20, 2026
@sivanantha321
sivanantha321 enabled auto-merge (squash) February 20, 2026 08:58
@sivanantha321
sivanantha321 merged commit b8fa0fb into kserve:master Feb 20, 2026
72 checks passed
spolti added a commit to spolti/kserve that referenced this pull request Mar 3, 2026
Commits in this batch:
a28ece4 feat: add annotation-based runtime defaults for MLServer (kserve#5064)
af8ce37 feat: make agent container resources configurable (kserve#5127)
aa153c3 ci: bump k8s & minikube version (kserve#5125)
c2517a5 ci: add workflow to check if tests are passed before merge (kserve#5092)
f5b211b fix(isvc): preserve reason/message on true conditions (kserve#5129)
e3a6afb feat: add schedulerName field to ServingRuntimePodSpec (kserve#5073)
15748dc feat: allow storage init to download only selected files (kserve#5119)
ce40a4b feat(llmisvc): add startupProbe to vLLM containers (kserve#5063)
972511a fix(llmisvc): preserve SchedulerConfigSpec conversion (kserve#5110)
ee4d115 fix: add no-cache option for generate-versions-from-gomod.py (kserve#5123)
9283c48 fix: skip validation for empty modelcar fields (kserve#5083)
2f87143 feat: new env var INFERENCE_SERVICE_NAME (kserve#5013)
2f87143 fix(llmisvc): unblocks certificate renewal (kserve#5106)
f650e31 fix: surface failure conditions in InferenceService status (kserve#5114)
c596075 feat: include all required files to installation script (kserve#5103)
f9a4a47 fix(llmisvc): return error from selectListener on mismatch  (kserve#5105)
849c0e3 fix(llmisvc): restricts GlobalConfig fields for templates (kserve#5111)
1cc689a chore: Update OWNERS file (kserve#5112)
352a689 fix(llmisvc): handle JSON marshal error in ReplaceVariables (kserve#5109)
d52e90c fix(llmisvc): return nil instead of empty InferencePoolSpec (kserve#5108)
b7ae7d6 feat(llmisvc): add versioned well-known config resolution (kserve#5096)
900ca56 feat: inf. log batching, external marshalling support (kserve#5061)
15d1a26 feat(llmisvc): add DNS/IP SAN & renewal to self-signed certs (kserve#5099)
b8fa0fb feat: faster parallel S3 file downloads (kserve#5102)
fa290c6 feat: llmisvc supports scaling and HA for scheduler (kserve#5032)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants