[None][perf] executor: in-process fast-path for rank-0 RPC submit - #15114
Closed
lancelly wants to merge 1 commit into
Closed
[None][perf] executor: in-process fast-path for rank-0 RPC submit#15114lancelly wants to merge 1 commit into
lancelly wants to merge 1 commit into
Conversation
On the RPC executor path, rank 0 is the sole RPC ingress for the whole instance and, under MpiCommSession, the rank-0 RpcWorker runs in a thread of the proxy process. Yet the proxy still reaches that local worker over a loopback ZMQ socket: pickle + HMAC + ZMQ + run_in_executor dispatch, all on rank 0's GIL, competing with the co-located executor loop. For a local worker this serialization is pure self-inflicted overhead, and it scales with concurrency (submit rate ~ C), starving the loop at high load. Add an opt-in in-process fast-path: the rank-0 worker registers itself in a per-process registry keyed by its rpc_addr (which the proxy also knows), and the proxy calls worker.submit() directly when a local worker is found, skipping pickle/HMAC/ZMQ/dispatch entirely. The worker-side logic is identical (same RpcWorker.submit), so result tracking and responses are unchanged. - utils.py: register/unregister/get_local_rpc_worker registry. - rpc_worker.py: rank-0 worker registers on start, unregisters on shutdown. - rpc_proxy_mixin.py: submit() uses the local worker when available; result is registered before dispatch. The decision is automatic per-process: in the spawn-proxy topology the worker lives in a different process, so the proxy's registry stays empty and it transparently falls back to RPC. Disabled by default (TLLM_RPC_LOCAL_FASTPATH=0); no behavior change unless opted in. Orthogonal to and stackable with batched submit (NVIDIA#15109). Follow-up: extend the same in-process path to the response stream (fetch_responses). Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
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.
Problem
On the RPC executor path (
GenerationExecutorRpcProxy/RpcWorker), rank 0 is the sole RPC ingress for the whole instance (RpcWorkerMixin.start_rpc_serverbinds the server only onrank == 0). UnderMpiCommSession(the common mpirun/srun launch), the rank-0RpcWorkerruns in a thread of the proxy process (MpiCommSession.submitdispatches rank 0 to a localThreadPoolExecutor; ranks 1..N-1 go through MPI).Yet the proxy still reaches that in-process worker over a loopback ZMQ socket: every
submitdoespickle+ HMAC on send, ZMQ round-trip, HMAC +pickle.loadson receive, and arun_in_executordispatch — all on rank 0's GIL, competing with the co-located executor loop (_executor_loop_overlap). For a local worker this serialization is pure self-inflicted overhead, and since submit rate scales with concurrency it starves the executor loop at high load (visible in nsys asRpcWorker.submitholding the GIL while the worker loop waits, e.g. at c2048).Note ranks 1..N-1 do not receive requests via this Python RPC — they are fed by the C++ executor's internal request broadcast — so the per-request Python pickle/HMAC/ZMQ is a single proxy→rank-0 loopback hop.
Change
Add an opt-in in-process fast-path that lets the proxy call the rank-0 worker directly, skipping pickle / HMAC / ZMQ / dispatch entirely:
utils.py— a per-process registry (register/unregister/get_local_rpc_worker) keyed byrpc_addr.rpc_worker.py— the rank-0 worker registers itself on start and unregisters on shutdown.rpc_proxy_mixin.py—submit()looks up the local worker byself.rpc_addrand, if present, callsworker.submit(request)directly; otherwise it uses RPC. TheGenerationResultis now registered before dispatch so a response can never race ahead of result tracking.The worker-side logic is unchanged (
RpcWorker.submit→BaseWorker.submit), so result tracking, responses, abort, and stats all behave identically.The local/remote decision is automatic per-process: the registry lives in the proxy's process, so in the spawn-proxy topology (worker in a different process) the lookup misses and it transparently falls back to RPC — no explicit topology detection.
Scope / knobs
TLLM_RPC_LOCAL_FASTPATH0(off)1enables the in-process submit fast-pathDisabled by default — no behavior change unless opted in. Orthogonal to and stackable with batched submit (#15109): batching cuts the count of RPC ops; this removes the per-op serialization for the local worker.
This PR covers the submit hot path (Phase 1). The response stream (
fetch_responses, O(C)/iter, also loopback-pickled today) can be moved to the same in-process path as a follow-up.What it does and does not remove
pickle/HMAC/ZMQ/run_in_executoron the proxy→rank-0 submit hop (the GIL-heavypickleis the main win; HMAC mostly releases the GIL).list(prompt_token_ids),GenerationResult, dict bookkeeping) — those still run, now on the proxy thread (same process/GIL), and are addressed by separate changes.Test plan
python-gil— worker0 GIL-wait per decode step, RPC vs fast-path; verifyRPCExecutor.submit.localreplaces the pickle/ZMQ ranges.GenerationRequestafter hand-off (no pickle copy on the local path).