Skip to content

perf(image): parallelize image_generate batches - #33971

Closed
EndeavorYen wants to merge 2 commits into
NousResearch:mainfrom
EndeavorYen:feat/image-generate-batch-parallel
Closed

perf(image): parallelize image_generate batches#33971
EndeavorYen wants to merge 2 commits into
NousResearch:mainfrom
EndeavorYen:feat/image-generate-batch-parallel

Conversation

@EndeavorYen

@EndeavorYen EndeavorYen commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Small patch, large user-visible speedup:

  • 3 files changed, +137/-1
  • lets independent image_generate tool calls run concurrently
  • default bounded fanout: 4 image requests
  • real creative workloads improved from ~20-23 min for 8 images to ~5-8 min
  • no provider API changes, no image schema changes, no reference-image policy changes

This PR lets image_generate tool batches run through Hermes' existing concurrent tool executor instead of forcing image requests to execute one by one.

The change is intentionally small:

  • marks image_generate as parallel-safe in the tool dispatch gate
  • caps image-generation worker fanout separately from the global tool worker cap
  • adds regression coverage for dispatching image batches concurrently and for the default/tunable worker cap

Problem

When the model emits multiple image_generate calls in one assistant tool batch, Hermes currently treats image generation as a stateful/non-parallel tool and sends the calls through the sequential path. For creative workflows, that makes multi-variant generation feel much slower than necessary: each image waits for the previous image request even though the calls are independent.

Image generation is a good candidate for bounded parallelism because each request is independent, slow, and usually dominated by provider-side generation latency.

What changed

image_generate is added to the parallel-safe tool set.

For mixed or image-only concurrent batches, agent.tool_executor now uses a dedicated image cap:

image_gen:
  max_parallel_requests: 4

Behavior:

  • default: 4 concurrent image requests
  • configurable via image_gen.max_parallel_requests
  • bounded by Hermes' existing _MAX_TOOL_WORKERS cap
  • non-image parallel batches keep the existing global worker behavior
  • single tool calls and unsafe/stateful tool batches still use the existing sequential path

Performance evidence

These are real-world GPT Image 2 / Codex image-generation runs from an image-heavy creative workflow. They are not synthetic microbenchmarks; the intent is to show user-visible wall-clock behavior.

Batch Attempts Successful images Failed/rerun Actual wall-clock Estimated sequential Speedup Successful image throughput
Old low-concurrency / sequential baseline, 2026-05-27 18:00-19:10 19 19 0 69.1 min 54.4 min API-only generation ~1x, slower with real spacing 16.5 images/hour
Parallel 16-image run, 2026-05-28 03:34-03:47 18 16 2 12.84 min 42.59 min 3.32x 74.8 images/hour
Bright 8-image run with one rerun, 09:34-09:42 9 8 1 7.72 min 21.76 min 2.82x 62.2 images/hour
First 8-image pass only, no rerun included 8 7 1 5.12 min 19.31 min 3.77x 82.0 successful-image equivalent/hour

Observed behavior is roughly 4-slot rolling parallelism:

  • the first 4 image requests start together
  • new requests enter as slots complete
  • failures still require reruns, so wall-clock depends on provider success rate

Practical result:

  • 8 images: usually ~5-8 minutes
  • 16 images: roughly ~13-15 minutes
  • typical speedup: around 3x versus sequential image calls
  • best observed 8-image pass: ~3.7-4x faster

The main bottleneck moves from "Hermes queues every image one by one" to "provider image latency plus occasional failed/rerun requests."

Related work and duplicate check

I did not find an open or merged PR that directly parallelizes image_generate batches.

Nearby but not duplicate:

No issues are closed by this PR.

Scope and non-goals

Included:

  • bounded parallel dispatch for image_generate
  • configuration hook for image batch fanout
  • regression tests for dispatch and worker caps

Not included:

  • reference-image policy changes
  • local reference image allowlists
  • Baoyu skill/docs updates
  • image editing API/schema changes
  • retry/fallback orchestration for failed image requests
  • provider-specific rate-limit backoff

Those are intentionally left out so this PR stays reviewable.

Validation

scripts/run_tests.sh tests/run_agent/test_image_generate_parallel.py tests/run_agent/test_run_agent.py tests/agent/test_tool_dispatch_helpers.py

Result:

375 tests passed, 0 failed

@alt-glitch alt-glitch added type/perf Performance improvement or optimization comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/vision Vision analysis and image generation P3 Low — cosmetic, nice to have labels May 28, 2026
@EndeavorYen EndeavorYen changed the title feat(image): parallelize image_generate batches perf(image): parallelize image_generate batches May 28, 2026
@EndeavorYen
EndeavorYen marked this pull request as ready for review May 28, 2026 16:27

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for isolating a real image-batch bottleneck. The current main dispatcher still routes multi-image batches sequentially because image_generate is not in agent/tool_dispatch_helpers.py:46-58.

Problems

  • agent/tool_dispatch_helpers.py:48 classifies image_generate as parallel-safe, but tools/image_generation_tool.py:806-834 force-syncs remote artifacts through the task's shared environment. FileSyncManager.sync() mutates _synced_files, _pushed_hashes, and _last_sync_time without a lock (tools/environments/file_sync.py:151-160,162-235), so concurrent image results can race on SSH/Daytona/Modal-style backends.

Suggested changes

  • Preserve parallel provider requests, but serialize that shared artifact-sync path (or prove and gate a backend path that is safe). Add a regression test for two concurrent image results using a shared remote sync manager.
  • Document the new image_gen.max_parallel_requests knob alongside the existing configuration example in website/docs/user-guide/features/image-generation.md:61-67.

This is an automated hermes-sweeper review.

@@ -45,6 +45,7 @@
"ha_get_state",
"ha_list_entities",
"ha_list_services",
"image_generate",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

image_generate is not universally free of shared state: successful remote-backend results call sync_manager.sync(force=True) in tools/image_generation_tool.py:801, and FileSyncManager.sync() mutates shared sync state without a lock. Please serialize that artifact-sync path or gate this parallel classification to a proven-safe backend path before enabling concurrent image calls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ddf0cea after rebasing the PR onto current main.

Provider calls remain concurrent, while each FileSyncManager now owns a per-instance transaction lock covering complete sync() and sync_back() cycles. This prevents overlapping transport/state commits for a shared SSH/Daytona/Modal environment without serializing independent environments.

Regression coverage now includes:

  • two concurrent image results using the same real FileSyncManager, proving both artifact paths remain committed;
  • sync_back waiting for an active forward-sync transaction rather than observing partial manager state.

Also documented image_gen.max_parallel_requests (default 4, bounded by the global worker cap).

Verification on current main:

  • 108 focused sync/backend/artifact/dispatch tests passed;
  • 458 PR validation tests passed;
  • Ruff and git diff --check passed.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/vision Vision analysis and image generation type/perf Performance improvement or optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants