Repository navigation
[Model] GR00T-N1.7: support request-level batching - #6568
hyunnnchoi wants to merge 2 commits into
Conversation
…ault Since vllm-project#5676, initialize_diffusion_stage overwrites od_config.max_num_seqs with the AsyncOmni-level diffusion_batch_size. Nothing in the `vllm serve --omni` path sets diffusion_batch_size, so a per-stage max_num_seqs configured in a deploy YAML is built into od_config and then clobbered with the default 1: the engine logs `[RequestBatch] engine init max_num_seqs=1` for a stage configured with 8, and --request-batch-max-wait-ms has nothing to act on. Let an explicit diffusion_batch_size (> 1) keep winning, and stop the default from overwriting the YAML value. Pass the resolved capacity to the stage client so its init log reports the effective value. Overlap note: vllm-project#6405 wires the CLI --max-num-seqs into diffusion_batch_size and vllm-project#6484 removes diffusion_batch_size entirely; this is the minimal fix for the per-stage YAML knob documented in docs/configuration/stage_configs.md, and is easy to drop or rebase if either of those lands first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: hyunnnchoi <hyunmok.choi@gmail.com>
Gr00tN1d7Pipeline served strictly serially: it never declared supports_request_batch, so the engine refuses max_num_seqs > 1 at startup and every DiffusionRequestBatch reaching forward() holds one request. With N robots connected, throughput stays flat (~6.6 req/s on an A30) while latency grows linearly with N -- even though Gr00tPolicy._get_action is already written as a batch pipeline (unbatch -> per-sample preprocess -> collate -> one model forward). Implement the request-batch contract: forward() consumes the whole wave, concatenating per-request observations along the batch axis for a single Gr00tPolicy.get_action() call and slicing the batched actions back per request. Malformed requests fail individually instead of taking the wave down; structurally incompatible observations fall back to per-request policy calls; dummy/profile requests are answered per request. A single-request wave takes the exact previous path. The bundled deploy config keeps max_num_seqs: 1, so default behavior is unchanged: golden values reproduce (eef_9d 3.9e-3 / gripper 3.9e-3 / joint 7.5e-3 at atol 1e-2) and the concurrency sweep matches main. Opting in is a deploy overlay, documented in the recipe. Measured on 1xA30, eager, 8 closed-loop OpenPI clients: 13.84 req/s at 575.8 ms mean vs 6.77 req/s at 1164.5 ms serialized (+104% throughput, -51% latency); waves of 7 dominate under that load. Correctness of batched execution, policy-level on the real checkpoint: rows are bit-identical under neighbor swaps at equal padded length; batch-shape bf16 rounding shifts actions by <= 5.5e-2 (1.0e-5 when the same comparison runs in fp32), ~14x below one GR00T_NOISE_SEED step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: hyunnnchoi <hyunmok.choi@gmail.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
This PR appears to belong to: docs/design/module/diffusion/diffusion_model_integration.md. Module owners: @fhfuih @Bounty-hunter @wtomin @RuixiangMa @hyunnnchoi, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer. Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment. |
|
@hyunnnchoi Are you planning to continue work on this PR? Please reply by 2026-10-19 10:47:09 UTC. A yes or a brief plan is enough; code changes do not need to be finished within these three days. If you are waiting on a review or other maintainer action, please let us know. If there is no reply, new commit, or maintainer takeover by then, we will recheck and close this PR as inactive. It can be reopened when work resumes. |
|
@hsliuustc0106 I won't be able to get back to this for a while, so closing it is fine. I'll reopen when I have time to work on it again. |
Purpose
GR00T serving handles exactly one request at a time.
Gr00tN1d7Pipelinenever implemented the request-batch contract, somax_num_seqsis pinned to 1 and N connected robots just queue behind each other: throughput stays at ~6.6 req/s while latency grows linearly with N. The policy underneath doesn't have this limitation —Gr00tPolicy.get_actionwas written for batched(B, ...)observations from the start (unbatch, per-sample preprocess, collate, one model forward). This PR connects the two.forward()now takes the whole scheduler wave, concatenates the observations along the batch axis, runs oneget_action()call, and splits the actions back per request. Declaringsupports_request_batch = Truelets the engine acceptmax_num_seqs > 1. The bundled YAML keepsmax_num_seqs: 1, so the default deployment is unchanged; opting in is an overlay:A malformed request in a wave fails alone instead of failing its neighbors, observations that cannot be concatenated (mismatched shapes) fall back to per-request calls, and a single-request wave takes the exact old code path.
The first commit fixes a plumbing problem the overlay would otherwise hit: since #5676, stage init overwrites the YAML's
max_num_seqswithdiffusion_batch_size(default 1), andvllm serve --omnihas no way to setdiffusion_batch_size, so the overlay reaches the engine asmax_num_seqs=1. The fix is two guarded lines plus a regression test. I'm aware #6405 and #6484 touch the same area — happy to drop that commit and rebase on whichever lands first; the GR00T commit stands on its own.Test Plan
vLLM Version: 0.26.0+cu129
vLLM-Omni Commit: based on
0e4b28f41xA30, DP=1, eager, closed-loop OpenPI WebSocket clients, 30 requests/client after 20 warmup,
GR00T_NOISE_SEED=42. Actions were checked against the reference values intests/e2e/online_serving/test_gr00t_openpi_expansion.py(atol 1e-2) before each sweep. Unit tests:tests/diffusion/models/gr00t/test_pipeline.py,tests/engine/test_async_omni_engine_stage_init.py.Because batching changes what actually runs on the GPU, I also checked at the policy level (real checkpoint) that a request's actions do not depend on its batch neighbors: swap one observation's neighbor for a completely different one (images, state, prompt length) and the fixed row's actions come back bit-identical. Solo vs batched differs by up to 5.5e-2 in bf16 and by 1.0e-5 when the same comparison is re-run in fp32 — batch-size-dependent kernel rounding amplified by the 4 denoise steps, not a masking bug. For scale, one noise-seed step (42 to 43) moves the same actions by 7.9e-1.
Test Result
Bundled YAML on this branch: reference check passes (eef_9d 3.9e-3, gripper 3.9e-3, joint 7.5e-3) and the sweep reproduces main's serial behavior:
With the overlay (
max_num_seqs: 8), reference check still passes on a single session, and:At 8 clients that is 2.0x the throughput at half the mean latency. A separate debug-logging run under the same 8-client load shows waves of 7 observations dominating (10 of 14 batched waves). Policy-level ceiling for reference: 6.97 obs/s at B=1, 16.74 obs/s at B=8.
Notes
request_batch_max_wait_msdefaults to 0), so a single robot's latency is untouched — and two closed-loop clients still ping-pong at wave size 1, which is why the gain only shows from 4 clients up.GR00T_NOISE_SEED, CUDA's seededrandn((B, ...))does not reproduce the(1, ...)draw row by row, so bit-exact reproducibility holds per wave shape, not across load levels. Production serving runs unseeded. The CI golden test runs one session (wave size 1) and is unaffected.dynamic=Falsewould recompile per distinct wave size; whichever lands second should switch batched deployments todynamic=None. The recipe keeps the two opt-ins separate for now.