Skip to content

feat(mocker): add explicit max_model_len support to vLLM mocker#11069

Merged
dreamtalen merged 1 commit into
mainfrom
yongmingd/mocker-max-model-len-reject
Jul 1, 2026
Merged

feat(mocker): add explicit max_model_len support to vLLM mocker#11069
dreamtalen merged 1 commit into
mainfrom
yongmingd/mocker-max-model-len-reject

Conversation

@dreamtalen

@dreamtalen dreamtalen commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Add explicit max_model_len support to vLLM mocker and replay so they model the same total sequence-length behavior as Dynamo frontend/router plus a vLLM worker.

Requests with no output position remaining are rejected before generation. Requests that reach the context limit after generation starts complete normally with finish_reason="length".

Details

  • Add an optional, vLLM-only max_model_len scalar across the CLI, Python bindings, JSON engine args, and Rust mock engine args.
  • Keep resolution explicit-only. This PR does not infer an effective vLLM limit from architectural model metadata.
  • Reject fresh and materialized destination requests when prompt_len >= max_model_len.
  • Preserve the original requested output length and independently stop generation when either the requested output limit or total sequence limit is reached.
  • Prevent speculative decode bursts from crossing max_model_len.
  • Map context-capped live completions to the existing length finish reason instead of treating them as early-completion errors.
  • Preserve requested_output_length in replay while deriving actual output_length from emitted token signals.
  • Use actual output length for replay totals, throughput/goodput, SLA calculations, and aggregated/disaggregated planner traffic.
  • Publish ModelRuntimeConfig.context_length only when an explicit limit is enforced, while keeping physical max_kv_tokens separate.

Where should the reviewer start?

  • lib/mocker/src/scheduler/vllm/policy.rs and lib/mocker/src/scheduler/vllm/core.rs for admission and generation-stop semantics.
  • lib/llm/src/mocker.rs for live context-capped completion handling.
  • lib/mocker/src/replay/collector.rs and lib/mocker/src/replay/offline/ for requested-versus-actual output accounting.
  • components/src/dynamo/mocker/config.py and components/src/dynamo/replay/main.py for explicit scalar propagation and runtime capability publication.

Related Issues

This PR is NOT linked to an issue:

  • Confirmed - no related issue

Validation

  • cargo fmt --all -- --check
  • cargo clippy -p dynamo-mocker --lib -- -D warnings
  • cargo test -p dynamo-mocker (488 passed, 1 ignored)
  • dynamo/bin/pytest -q components/src/dynamo/mocker/tests/unit/test_config.py -k max_model_len (7 passed, 27 deselected)

Real-GPU validation on an H100 with Dynamo frontend + KV router + vLLM worker:

  • Image: nvcr.io/nvidia/ai-dynamo/vllm-runtime:1.3.0-dev.1-cuda13
  • Dynamo: 1.3.0.dev1
  • vLLM: 0.22.0
  • Model: Qwen/Qwen3-0.6B
  • Effective max_model_len: 32
Prompt tokens Requested output HTTP status Actual output Finish reason
28 8 200 4 length
31 2 200 1 length
32 1 400 0 rejected before generation

@copy-pr-bot

copy-pr-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the fix label Jun 30, 2026
@dreamtalen dreamtalen marked this pull request as ready for review June 30, 2026 00:41
@dreamtalen dreamtalen requested review from a team as code owners June 30, 2026 00:41
@dreamtalen dreamtalen changed the title fix(mocker): reject prompts above max model length feat(mocker): reject prompts above max_model_len Jun 30, 2026
@github-actions github-actions Bot added feat and removed fix labels Jun 30, 2026

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 4 potential issues.

Open in Devin Review

Comment thread lib/mocker/src/scheduler/vllm/core.rs Outdated
Comment thread components/src/dynamo/mocker/utils/kv_cache.py Outdated
Comment thread components/src/dynamo/mocker/config.py Outdated
Comment thread components/src/dynamo/replay/main.py Outdated
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds an optional max_model_len field to MockEngineArgs end-to-end: Rust protocol struct with vLLM-only validation, PyO3 bindings, CLI --max-model-len arg, Python config/replay resolution that auto-derives the value from model info, and vLLM scheduler admission enforcement that rejects prompts exceeding the limit.

Changes

max_model_len prompt-length enforcement

Layer / File(s) Summary
MockEngineArgs Rust field, validation, and serde
lib/mocker/src/common/protocols.rs
Adds max_model_len: Option<usize> to MockEngineArgs and MockEngineArgsSerde, builder min=1 constraint, vLLM-only engine validation, serde TryFrom conversion, and updated round-trip tests.
vLLM scheduler admission enforcement
lib/mocker/src/scheduler/vllm/policy.rs, lib/mocker/src/scheduler/vllm/core.rs, lib/mocker/src/scheduler/vllm/policy/tests.rs
Adds prompt_exceeds_max_model_len helper, reworks FreshKv admission in VllmCore to explicitly reject over-length prompts before calling decide_waiting_admission, updates rejection log fields, and adds unit and integration tests.
PyO3 bindings and .pyi stubs
lib/bindings/python/rust/llm/replay.rs, lib/bindings/python/src/dynamo/_core.pyi
Extends MockEngineArgs PyO3 constructor with max_model_len=None parameter, adds getter, and updates stub to expose max_model_len: Optional[int].
get_model_max_context_length utility
components/src/dynamo/mocker/utils/kv_cache.py
Adds get_model_max_context_length(model_path) that reads max_context_length from get_model_info, returns None on missing/invalid/exception.
CLI arg, config resolution, and runtime context_length
components/src/dynamo/mocker/args.py, components/src/dynamo/mocker/config.py
Adds --max-model-len CLI arg; auto-derives max_model_len in _resolve_raw_engine_args and build_mocker_engine_args via get_model_max_context_length; propagates into MockEngineArgs; sets runtime_config.context_length from max_model_len or 0.
Replay engine max_model_len resolution
components/src/dynamo/replay/main.py
Adds _resolve_max_model_len helper, calls it during engine-args loading, and updates _engine_caps to prefer args.max_model_len for context_length.
Python unit tests
components/src/dynamo/mocker/tests/unit/test_config.py
Extends make_args defaults and adds tests for CLI parsing, runtime config derivation, lookup success/failure, and max_model_len derivation/preservation in both build_mocker_engine_args and _load_engine_args.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 94.74% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description covers the required content with summary/details, review pointers, and the required related-issues section.
Title check ✅ Passed The title clearly matches the main change: adding explicit max_model_len support to the vLLM mocker.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
components/src/dynamo/mocker/utils/kv_cache.py (1)

40-46: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Use lazy logging for this warning. logger.warning("Could not determine max context length from model config: %s", e) avoids eager formatting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/mocker/utils/kv_cache.py` around lines 40 - 46, The
warning in the max context length lookup uses eager f-string formatting, so
update the exception handling in the helper around get_model_info to use lazy
logger formatting instead. In the function that returns max_context_length, keep
the same message context but pass the exception as an argument to logger.warning
rather than interpolating it into the string directly.

Sources: Coding guidelines, Path instructions, Linters/SAST tools

components/src/dynamo/mocker/tests/unit/test_config.py (1)

411-431: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move these imports to module scope.

Lines 412, 424, 464, 483, and 502 import regular test dependencies inside the test bodies. These are not optional-dependency collection guards, so they should live at the top of the file instead of hiding dependencies inside individual tests. As per coding guidelines, "Keep imports at the top of the file; always flag import statements inside function bodies." As per path instructions, .ai/python-guidelines.md says to "keep all imports at the top of the file (no in-function/class imports)."

Also applies to: 463-519

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/src/dynamo/mocker/tests/unit/test_config.py` around lines 411 -
431, The test file has several regular dependency imports inside individual test
bodies, which should be moved to module scope. Update the test module by
hoisting the imports used by these tests out of the functions and placing them
with the other top-level imports, then remove the in-test import statements from
the affected tests such as test_model_max_context_length_uses_shared_model_info
and test_model_max_context_length_returns_none_on_failure. Keep the tests using
the same symbols (for example kv_cache, SimpleNamespace, and any other shared
fixtures/helpers) but ensure all imports are declared once at the top of the
file.

Sources: Coding guidelines, Path instructions

lib/mocker/src/scheduler/vllm/core.rs (1)

1401-1422: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the max-model-length gate in policy.rs. This vLLM-only check still sits in core.rs, which splits the admission rules across files. Fold it into the policy-owned admission helper so all vLLM waiting admission paths go through one gate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mocker/src/scheduler/vllm/core.rs` around lines 1401 - 1422, Move the
vLLM max-model-length rejection out of the FreshKv branch in core.rs and into
the policy-owned waiting admission logic so all vLLM waiting admission decisions
flow through one place. Update the helper around
policy::decide_waiting_admission to perform the prompt_exceeds_max_model_len
check internally when scheduling_policy is SchedulingPolicy::Vllm, and keep
AdmissionStage::FreshKv in core.rs focused on delegating to policy.rs. Ensure
the existing behavior remains unchanged for non-vLLM policies while
consolidating the gate in policy.rs.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/src/dynamo/mocker/args.py`:
- Around line 214-220: The --max-model-len CLI option in the argument parser
currently accepts 0 and negative values, but MockEngineArgs only supports values
>= 1, so validate it during parsing instead of deferring the failure. Update the
parser setup in args.py where add_argument for --max-model-len is defined to
enforce a minimum of 1, using the existing argument parsing flow so invalid
input is rejected immediately. Keep the behavior for omitted values unchanged
and make sure the validation happens before MockEngineArgs construction.

---

Nitpick comments:
In `@components/src/dynamo/mocker/tests/unit/test_config.py`:
- Around line 411-431: The test file has several regular dependency imports
inside individual test bodies, which should be moved to module scope. Update the
test module by hoisting the imports used by these tests out of the functions and
placing them with the other top-level imports, then remove the in-test import
statements from the affected tests such as
test_model_max_context_length_uses_shared_model_info and
test_model_max_context_length_returns_none_on_failure. Keep the tests using the
same symbols (for example kv_cache, SimpleNamespace, and any other shared
fixtures/helpers) but ensure all imports are declared once at the top of the
file.

In `@components/src/dynamo/mocker/utils/kv_cache.py`:
- Around line 40-46: The warning in the max context length lookup uses eager
f-string formatting, so update the exception handling in the helper around
get_model_info to use lazy logger formatting instead. In the function that
returns max_context_length, keep the same message context but pass the exception
as an argument to logger.warning rather than interpolating it into the string
directly.

In `@lib/mocker/src/scheduler/vllm/core.rs`:
- Around line 1401-1422: Move the vLLM max-model-length rejection out of the
FreshKv branch in core.rs and into the policy-owned waiting admission logic so
all vLLM waiting admission decisions flow through one place. Update the helper
around policy::decide_waiting_admission to perform the
prompt_exceeds_max_model_len check internally when scheduling_policy is
SchedulingPolicy::Vllm, and keep AdmissionStage::FreshKv in core.rs focused on
delegating to policy.rs. Ensure the existing behavior remains unchanged for
non-vLLM policies while consolidating the gate in policy.rs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 75d2c6ff-84e8-45f6-a771-7f2a1f9983b4

📥 Commits

Reviewing files that changed from the base of the PR and between 4cba833 and 3121b8a.

📒 Files selected for processing (11)
  • components/src/dynamo/mocker/args.py
  • components/src/dynamo/mocker/config.py
  • components/src/dynamo/mocker/tests/unit/test_config.py
  • components/src/dynamo/mocker/utils/kv_cache.py
  • components/src/dynamo/replay/main.py
  • lib/bindings/python/rust/llm/replay.rs
  • lib/bindings/python/src/dynamo/_core.pyi
  • lib/mocker/src/common/protocols.rs
  • lib/mocker/src/scheduler/vllm/core.rs
  • lib/mocker/src/scheduler/vllm/policy.rs
  • lib/mocker/src/scheduler/vllm/policy/tests.rs

Comment thread components/src/dynamo/mocker/args.py
@dreamtalen dreamtalen force-pushed the yongmingd/mocker-max-model-len-reject branch from 3121b8a to 116822b Compare June 30, 2026 00:53
@datadog-official

This comment has been minimized.

@dreamtalen dreamtalen force-pushed the yongmingd/mocker-max-model-len-reject branch 2 times, most recently from aa4b293 to 660a386 Compare June 30, 2026 01:09
Comment thread components/src/dynamo/mocker/config.py Outdated
Comment thread components/src/dynamo/replay/main.py Outdated

@PeaBrane PeaBrane 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.

One caveat: this is based on a brief review and my current understanding of how these flows should work. Please double-check these conclusions against the upstream vLLM source and the semantics Dynamo intends to expose; semantic correctness for this integration should take priority if they differ.

I think the scalar plumbing is useful, but this needs one consistent semantic flow before merge:

requested max_model_len
→ backend-specific resolution
→ effective max_model_len
→ scalar passed to and enforced by mocker

The architectural context length is only an input to backend resolution; it is not necessarily the effective runtime limit. Physical max_kv_tokens is a separate worker-capacity concept. lib/mocker should remain scalar-only and must not acquire LLM/backend/runtime resolver dependencies.

At request level, max_model_len is a total prompt-plus-output limit. The request’s original max_output_tokens should remain unchanged, with generation stopping independently when either the requested output limit or effective max_model_len is reached.

A prompt with no output position remaining should be rejected before generation. Reaching the context limit after generation starts is instead a successful length-capped completion. No new public finish reason or replay terminal status is required: both output-limit and context-limit completion map to finish_reason="length", while replay records Completed.

Only an effective limit genuinely enforced by the running engine should be published as ModelRuntimeConfig.context_length. For a focused version of this PR, I suggest keeping the new scalar plumbing, accepting an explicitly resolved max_model_len, enforcing it end to end, and deferring automatic derivation until there is a canonical backend-compatible resolver.

That end-to-end enforcement also needs to cover the existing consumers: live mocker must not treat context-capped completion as an early-completion error, and replay must distinguish the requested output length from the number of tokens actually emitted.

Comment thread components/src/dynamo/mocker/config.py Outdated
Comment thread components/src/dynamo/mocker/config.py
Comment thread lib/mocker/src/scheduler/vllm/policy.rs Outdated
Comment thread lib/mocker/src/common/protocols.rs Outdated
Comment thread lib/mocker/src/scheduler/vllm/core.rs Outdated
Comment thread components/src/dynamo/replay/main.py Outdated
Comment thread lib/bindings/python/rust/llm/replay.rs
Comment thread lib/mocker/src/scheduler/vllm/policy/tests.rs
@PeaBrane PeaBrane dismissed their stale review June 30, 2026 06:10

Converting this review to non-blocking feedback at the reviewer's request.

PeaBrane

This comment was marked as resolved.

Signed-off-by: Yongming Ding <yongmingd@nvidia.com>
@dreamtalen dreamtalen force-pushed the yongmingd/mocker-max-model-len-reject branch from 660a386 to 162b60b Compare June 30, 2026 23:26
@dreamtalen dreamtalen changed the title feat(mocker): reject prompts above max_model_len feat(mocker): add explicit max_model_len support to vLLM mocker Jun 30, 2026

@PeaBrane PeaBrane 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.

Rechecked the updated head against the prior top-level architectural review and inline comments. The explicit-only derivation, total-sequence enforcement, live length completion, disaggregated validation, and replay accounting concerns are addressed.

@dreamtalen

Copy link
Copy Markdown
Contributor Author

/ok to test 162b60b

@dreamtalen dreamtalen enabled auto-merge (squash) July 1, 2026 00:39
@dreamtalen dreamtalen merged commit ec45adf into main Jul 1, 2026
101 checks passed
@dreamtalen dreamtalen deleted the yongmingd/mocker-max-model-len-reject branch July 1, 2026 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants