Skip to content

Commit 366d879

Browse files
committed
fix(vllm): declare a weight version only when a caller supplies one
Review found that a successful update omitting weight_version still recorded the synthetic "unknown" placeholder, so get_weight_version could answer version_declared: true for a version nobody chose. The routes still respond "unknown" to keep the response shape stable, but only a caller-supplied tag now counts as a declaration. Also drop the defensive getattr in get_weight_version and initialize _weight_version in the test constructor that bypasses __init__, and describe the route in the RL guide as reporting the last version declared rather than the version being served. Regenerate the Python API reference pages. The freshness gate attributes drift to any branch touching components/src, which this branch does; the drift itself came from main (this branch's own head regenerates clean, and the four stale pages carry no vLLM or RL content). The generated output is byte-identical at the CI base and at current main, so regenerating settles it rather than racing the next merge. Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
1 parent 656d0de commit 366d879

7 files changed

Lines changed: 251 additions & 159 deletions

File tree

‎components/src/dynamo/vllm/handlers.py‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,10 +1731,13 @@ async def abort_request(self, body: dict) -> dict:
17311731
return {"status": "error", "message": str(e)}
17321732

17331733
def _declare_weight_version(self, version: Any) -> None:
1734-
"""Record the weight version this worker is now serving.
1734+
"""Record the weight version a caller has declared for this worker.
17351735
1736-
The single writer for the tracked version: both /engine weight-update
1737-
routes and the set_weight_version declaration route come through here.
1736+
The single writer for the tracked version: the set_weight_version
1737+
declaration route and both /engine weight-update routes come through
1738+
here. The update routes call it only when the request carried a
1739+
weight_version, so an update that declares nothing leaves the surface
1740+
undeclared rather than recording the placeholder they respond with.
17381741
"""
17391742
self._weight_version = version
17401743

@@ -1754,7 +1757,7 @@ async def get_weight_version(self, body: dict) -> dict:
17541757
"status": "error",
17551758
"message": "request body must be a JSON object",
17561759
}
1757-
version = getattr(self, "_weight_version", _WEIGHT_VERSION_UNDECLARED)
1760+
version = self._weight_version
17581761
declared = version is not _WEIGHT_VERSION_UNDECLARED
17591762
return {
17601763
"status": "ok",
@@ -1820,7 +1823,12 @@ async def update_weights_from_disk(self, body: dict) -> dict:
18201823
# weights is now stale and must not be reused. Invalidate it
18211824
# while still holding _pause_lock (generation is paused).
18221825
await self.engine_client.reset_prefix_cache()
1823-
self._declare_weight_version(version)
1826+
# Only a caller-supplied tag counts as a declaration. The
1827+
# "unknown" default is a response placeholder, not something a
1828+
# caller declared, and recording it would make version_declared
1829+
# true while get_weight_version reports a version nobody chose.
1830+
if "weight_version" in body:
1831+
self._declare_weight_version(version)
18241832
logger.info(
18251833
f"[RL] Weights loaded from {path} (version={version}, rpc={rpc})"
18261834
)
@@ -1883,7 +1891,10 @@ async def update_weights_from_distributed(self, body: dict) -> dict:
18831891
# Weights changed: stale prefix/KV cache must be invalidated
18841892
# before resume so it is not reused under the new weights.
18851893
await self.engine_client.reset_prefix_cache()
1886-
self._declare_weight_version(version)
1894+
# See update_weights_from_disk: the "unknown" default is a
1895+
# response placeholder, not a declaration.
1896+
if "weight_version" in body:
1897+
self._declare_weight_version(version)
18871898
logger.info(
18881899
f"[RL] Weights received via distributed "
18891900
f"(version={version}, rpc={rpc})"

‎components/src/dynamo/vllm/tests/test_vllm_worker_handler.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ def _make_handler(
9898
# BaseWorkerHandler.__init__ is bypassed above; the decode generate path
9999
# registers per-request deferred-abort guards here.
100100
handler._deferred_aborts = {}
101+
# Likewise the weight-version surface starts undeclared.
102+
handler._weight_version = mod._WEIGHT_VERSION_UNDECLARED
101103
return handler
102104

103105

@@ -1857,6 +1859,35 @@ async def test_set_weight_version_rejects_a_missing_version(self):
18571859
# A rejected declaration must not leave the surface claiming knowledge.
18581860
assert (await handler.get_weight_version({}))["version_declared"] is False
18591861

1862+
@pytest.mark.asyncio
1863+
async def test_update_without_a_version_declares_nothing(self):
1864+
"""An update that carries no ``weight_version`` leaves the surface
1865+
undeclared. The routes answer ``"unknown"`` so the response shape is
1866+
stable, but that placeholder is the route's, not the caller's, and
1867+
recording it would report a version nobody chose as declared.
1868+
"""
1869+
handler = self._make_rl_handler()
1870+
1871+
distributed = await handler.update_weights_from_distributed(
1872+
{
1873+
"allow_unpaused": True,
1874+
"reset_prefix_cache": False,
1875+
"engine_rpc": "update_weights",
1876+
}
1877+
)
1878+
handler._paused = True
1879+
disk = await handler.update_weights_from_disk(
1880+
{"model_path": "/models/checkpoint-42"}
1881+
)
1882+
1883+
assert distributed == {"status": "ok", "version": "unknown"}
1884+
assert disk == {"status": "ok", "version": "unknown"}
1885+
assert await handler.get_weight_version({}) == {
1886+
"status": "ok",
1887+
"version": None,
1888+
"version_declared": False,
1889+
}
1890+
18601891
@pytest.mark.asyncio
18611892
async def test_init_weights_update_group_succeeds_within_timeout(self):
18621893
handler = _make_handler()

‎docs/fern/pages/reference/api/python/README.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ subtitle: Public Python surfaces on the Dynamo distributed runtime, statically d
77

88
{/* GENERATED by docs/fern/scripts/gen_python_api.py -- do not edit. */}
99

10-
Dynamo publishes 11 Python modules with 362 public class and function symbols under `dynamo.*`.
10+
Dynamo publishes 11 Python modules with 365 public class and function symbols under `dynamo.*`.
1111

1212
<CardGroup cols={2}>
13-
<Card title="dynamo._core" href="python/_core">88 symbols. Rust-backed distributed runtime, KV router, and endpoint bindings.</Card>
13+
<Card title="dynamo._core" href="python/_core">89 symbols. Rust-backed distributed runtime, KV router, and endpoint bindings.</Card>
1414
<Card title="dynamo.runtime" href="python/runtime">17 symbols. Decorators and re-exports for defining Dynamo workers and endpoints.</Card>
15-
<Card title="dynamo.llm" href="python/llm">46 symbols. High-level LLM primitives for building request pipelines.</Card>
15+
<Card title="dynamo.llm" href="python/llm">47 symbols. High-level LLM primitives for building request pipelines.</Card>
1616
<Card title="dynamo.frontend" href="python/frontend">43 symbols. OpenAI-compatible HTTP frontend, argument parsing, and pre/post-processing.</Card>
17-
<Card title="dynamo.common" href="python/common">79 symbols. Shared configuration groups, storage adapters, and utility helpers.</Card>
17+
<Card title="dynamo.common" href="python/common">80 symbols. Shared configuration groups, storage adapters, and utility helpers.</Card>
1818
<Card title="dynamo.health_check" href="python/health_check">2 symbols. Health-check payload types and environment-driven configuration.</Card>
1919
<Card title="dynamo.logits_processing" href="python/logits_processing">4 symbols. Custom logits processors for LLM token generation.</Card>
2020
<Card title="dynamo.planner" href="python/planner">36 symbols. Scaling connectors, decision types, and Planner configuration.</Card>

0 commit comments

Comments
 (0)