Repository navigation
Conversation
With a deep queue a job-take costs a flat ~0.5s however many jobs it asks for (runpod#647), so a full worker idles its slots for that round trip on every cycle. With take_ahead (config key or RUNPOD_TAKE_AHEAD, default 0), a worker whose last take came back full also asks for up to take_ahead extra jobs and holds them until slots free, so the next take overlaps the running jobs. Shallow queues, the default, and refresh_worker configs behave as before. Stop signals still apply to held jobs, and shutdown drains them.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is a concrete option for question 2 in #647: SDK-side take-ahead, opt-in and off by default. I know you're assigned to #647 and may prefer a server-side fix, so please treat this as one possible approach; happy to change it or close it.
With jobs queued, a job-take costs a flat ~0.5s whatever its size, so even after #646 a saturated worker spends about half of every take/run cycle idle, waiting on the take. With
take_aheadset, a worker whose last take came back full also asks for up totake_aheadextra jobs and holds them injobs_queueuntil slots free. The next take is then in flight while those jobs run, instead of starting after them.take_aheadcomes from thestart()config orRUNPOD_TAKE_AHEAD, default0. With0nothing changes: the queue size, the take sizes and the perf(serverless): take a full batch once drained #646 full-worker wait are the same as onmain.get_jobswaits forrun_jobsto empty the buffer before it takes again. Concurrency still caps what runs.stop_jobmarks a held job andrun_jobsdrops it before it starts (and returnsTrue, so it is reported as stopped).refresh_workeris set in the config, since the worker exits after its first job. Docs say not to combine it with handlers that returnrefresh_workereither (same race as fix(serverless): stop taking jobs after a per-job refresh_worker (SLS-900) #663, with more jobs at stake).jobs_queueis sizedconcurrency + take_ahead, soset_scalekeeps working.Docs: new "Take-ahead" section and config row in
docs/serverless/worker.md.Benchmark
I don't have a GPU endpoint, so I couldn't rerun your vLLM setup. Instead
scripts/benchmark_job_take.pyruns realJobScalerinstances against an in-process fake queue that models the latency you measured in #647: a take with jobs queued costs 0.5s regardless of size, a take already long-polling an empty queue returns 50ms after a job arrives. This is a model of the scheduling, not a platform measurement.32 slots,
take_ahead=32, time until 99% of the burst is done, median of 5 runs, Apple Silicon arm64. Themaincolumn is the script run against unmodifiedmain.take_ahead=32On the deep-queue runs the job split across workers was identical with and without take-ahead (256 each with 4 workers). With
take_ahead=0the branch matchesmain(15.58s / 7.57s / 0.55s). The 2.0s-job scenario is noisier: baseline runs ranged from 19.6s to 24.6s across reps, so treat its -18% as approximate.The part I can't check here is how this behaves next to the autoscaler: a held job could have gone to a worker that was just scaled up. That is why it only kicks in while takes come back full, and why it is opt-in.
Test plan
tests/test_serverless/test_take_ahead.py(20 cases): config/env parsing and invalid values, refresh_worker disables it, queue sizing, default worker unchanged, deep queue takes ahead while slots are busy, no new take until the buffer drains, short take asks only for free slots, shallow queue never takes ahead, zero concurrency, stop signal drops a held job, shutdown runs held jobstest_rp_scale.py+test_take_ahead.pyrepeated 12x, no failurestest_apps/test_dispatch.py::TestSyncBridge::test_interrupt_*cases timed out under load on my machine and pass when run on their own; they don't touch the scaler.AI-assisted; tested locally with the commands above.