Skip to content

Add vLLM token-native generate endpoint#1113

Open
biswapanda wants to merge 2 commits into
ai-dynamo:mainfrom
biswapanda:bis/vllm-generate-endpoint
Open

Add vLLM token-native generate endpoint#1113
biswapanda wants to merge 2 commits into
ai-dynamo:mainfrom
biswapanda:bis/vllm-generate-endpoint

Conversation

@biswapanda

@biswapanda biswapanda commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a non-streaming AIPerf endpoint adapter for the canonical vLLM token-in/token-out API:

POST /inference/v1/generate

The adapter lets the same pretokenized request payload run against vLLM, Dynamo, or another server implementing the vLLM Generate contract. It exercises exact token IDs rather than chat messages and does not execute or emulate tools.

Changes

  • Register the vllm_generate endpoint plugin with /inference/v1/generate as its default path.
  • Read exact input token IDs from each turn's extra body.
  • Preserve model, sampling parameters, request ID, and additional endpoint fields.
  • Require non-streaming responses for deterministic benchmark accounting.
  • Parse completion token IDs and reconstruct prompt, completion, and total token counts.
  • Reject malformed, empty, boolean, or multi-turn token payloads with explicit errors.
  • Add endpoint and enum coverage.

This PR intentionally contains only the endpoint adapter. The earlier local frozen-trace commits are excluded because Exgentic replay support has since landed upstream through #1064 and #1078.

Validation

Validation on the exact publication head:

  • 23 tests passed.
  • Ruff lint passed.
  • Ruff formatting passed.
  • git diff --check passed.

Provenance

  • Upstream base: ai-dynamo/aiperf main at 1439ec2
  • Original Gitea branch: codex/vllm-generate-endpoint at 470f2a41e4306f8c992391b0423c7a60f2806f88
  • Endpoint source commit: 9ab4e6c9
  • Publication head: f30d835

Related work

Summary by CodeRabbit

  • New Features

    • Added support for a new non-streaming generation endpoint that accepts token IDs, returns completions, and reports token usage.
    • Registered the endpoint in the app so it is available with the expected path and metrics labeling.
  • Bug Fixes

    • Added validation to reject invalid or missing token ID inputs.
    • Improved response handling to safely return results only when the response format is valid.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Try out this PR

Quick install:

pip install --upgrade --force-reinstall git+https://github.com/ai-dynamo/aiperf.git@f30d835ff995caa665e75c12c3d266e348b9bfd3

Recommended with virtual environment (using uv):

uv venv --python 3.12 && source .venv/bin/activate
uv pip install --upgrade --force-reinstall git+https://github.com/ai-dynamo/aiperf.git@f30d835ff995caa665e75c12c3d266e348b9bfd3

Last updated for commit: f30d835Browse code

@datadog-official

datadog-official Bot commented Jul 6, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

🚦 3 Pipeline jobs failed

Lint PR | Validate PR title and add label   View in Datadog   GitHub Actions

Pre-commit | pre-commit   View in Datadog   GitHub Actions

Run Unit Tests | build (windows, 3.12)   View in Datadog   GitHub Actions

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: f30d835 | Docs | Give us feedback!

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds a new VllmGenerateEndpoint class implementing a non-streaming token-in/token-out /inference/v1/generate endpoint, registers it as vllm_generate in the plugin configuration, and adds corresponding unit tests for enum metadata and endpoint behavior.

Changes

vLLM Generate Endpoint

Layer / File(s) Summary
Request payload formatting and validation
src/aiperf/endpoints/vllm_generate.py
format_payload builds a non-streaming request payload, validating single-turn input, token_ids, and merged sampling params; extract_payload_inputs sets pretokenised token count.
Response parsing and usage computation
src/aiperf/endpoints/vllm_generate.py
extract_response_data, _parse_response, _prompt_token_count, and _valid_token_ids parse responses, validate token IDs, and compute prompt/completion/total token usage and metadata.
Plugin registration and tests
src/aiperf/plugin/plugins.yaml, tests/unit/common/enums/test_endpoints_enums.py, tests/unit/endpoints/test_vllm_generate.py
Registers the vllm_generate endpoint entry and adds tests covering enum metadata, payload formatting, validation errors, and response usage reconstruction.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Poem

A rabbit hops through tokens new,
Turning IDs into text that's true.
No stream this time, just one clean burst,
Prompt then completion, counted first.
With tests all green and YAML wired,
This bunny's generate endpoint is fully inspired! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a token-native vLLM generate endpoint.

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.

🧹 Nitpick comments (1)
src/aiperf/endpoints/vllm_generate.py (1)

98-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated token-id validation logic vs. _valid_token_ids.

The int/bool type-check here mirrors _valid_token_ids (lines 134-142) but is copy-pasted instead of reused (only difference is the non-empty check, since an empty completion should stay valid). Extract a shared helper for the type-check portion to avoid the two checks drifting apart.

♻️ Proposed refactor
     `@staticmethod`
-    def _valid_token_ids(value: Any) -> bool:
-        return (
-            isinstance(value, list)
-            and bool(value)
-            and all(
-                isinstance(token_id, int) and not isinstance(token_id, bool)
-                for token_id in value
-            )
-        )
+    def _is_int_list(value: Any) -> bool:
+        return isinstance(value, list) and all(
+            isinstance(token_id, int) and not isinstance(token_id, bool)
+            for token_id in value
+        )
+
+    `@classmethod`
+    def _valid_token_ids(cls, value: Any) -> bool:
+        return bool(value) and cls._is_int_list(value)

Then in _parse_response, replace lines 99-103 with if not self._is_int_list(completion_ids): return None.

🤖 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 `@src/aiperf/endpoints/vllm_generate.py` around lines 98 - 103, There is
duplicated token-id type validation in _parse_response that mirrors the logic
already present in _valid_token_ids, which risks the two checks drifting apart.
Extract the shared int/bool list validation into a helper on the same class (for
example, a reusable list-type check used by both _parse_response and
_valid_token_ids), then update _parse_response to call that helper while
preserving the separate empty-list behavior for completions.
🤖 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.

Nitpick comments:
In `@src/aiperf/endpoints/vllm_generate.py`:
- Around line 98-103: There is duplicated token-id type validation in
_parse_response that mirrors the logic already present in _valid_token_ids,
which risks the two checks drifting apart. Extract the shared int/bool list
validation into a helper on the same class (for example, a reusable list-type
check used by both _parse_response and _valid_token_ids), then update
_parse_response to call that helper while preserving the separate empty-list
behavior for completions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a6e5e673-f94e-473c-a570-270bf92e95d8

📥 Commits

Reviewing files that changed from the base of the PR and between 1439ec2 and f30d835.

📒 Files selected for processing (4)
  • src/aiperf/endpoints/vllm_generate.py
  • src/aiperf/plugin/plugins.yaml
  • tests/unit/common/enums/test_endpoints_enums.py
  • tests/unit/endpoints/test_vllm_generate.py

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.49180% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/aiperf/endpoints/vllm_generate.py 70.49% 8 Missing and 10 partials ⚠️

📢 Thoughts on this report? Let us know!

@biswapanda biswapanda changed the title [codex] add vLLM token-native generate endpoint Add vLLM token-native generate endpoint Jul 7, 2026
@biswapanda biswapanda self-assigned this Jul 7, 2026
@biswapanda biswapanda marked this pull request as ready for review July 7, 2026 01:09
}
return ParsedResponse(
perf_ns=response.perf_ns,
data=BaseResponseData(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint returns only empty BaseResponseData while use_server_token_count defaults to false, so the default parser path tokenizes an empty output instead of the reconstructed usage and drops token-throughput metrics. Fix: force server token-count parsing for vllm_generate or for token-native endpoints with produces_tokens=true and tokenizes_input=false.

🤖 AI Fix

Update src/aiperf/records/inference_result_parser.py in InferenceResultParser so endpoints with metadata produces_tokens=True and tokenizes_input=False use _compute_server_token_counts() by default, and add a unit test covering VllmGenerateEndpoint token counts without setting endpoint.use_server_token_count.

or not isinstance(choices[0], dict)
):
return None
completion_ids = choices[0].get("token_ids")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_parse_response only reads choices[0] even though sampling_params can request multiple choices, so n > 1 responses undercount generated tokens. Fix: reject sampling_params.n != 1 or aggregate token counts across every choice.

🤖 AI Fix

In src/aiperf/endpoints/vllm_generate.py VllmGenerateEndpoint.format_payload, validate sampling_params.get("n", 1) == 1 and raise ValueError otherwise, or update _parse_response() to validate and sum token_ids across all choices.

isinstance(value, list)
and bool(value)
and all(
isinstance(token_id, int) and not isinstance(token_id, bool)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_valid_token_ids accepts negative integers even though vLLM rejects negative token_ids, so invalid datasets fail only after dispatch as server errors. Fix: require every token id to be non-negative during local validation.

🤖 AI Fix

Update src/aiperf/endpoints/vllm_generate.py VllmGenerateEndpoint._valid_token_ids() to include token_id >= 0, update the validation error text in format_payload(), and add a unit test rejecting negative token ids.

@ajcasagrande

Copy link
Copy Markdown
Contributor

Hi @biswapanda, thanks for the PR.

We can add incremental support if it is easier, but I think there is more involved to get the ideal native solution. I think ideally this is how I would want aiperf to support something like this:

  • endpoint metadata includes a new field such as expects_raw_token_ids, or requires_raw_tokens, etc.
  • Turn object has new optional raw_token_ids field, which will be serialized into the mmap data
  • All token id verification/validation happens at dataset load time
  • Endpoint adapter lift and shift from turn's raw_token_ids into the vllm field (support of pre-formatted raw payload_bytes should ideally also come for free in case of single-turn datasets)
  • Response parsing handles raw token non-text outputs
  • Token counting logic updated to be able to handle counting the array
  • Synthetic data can be used by skipping the to-text decode transformation piece

I made a proof of concept of some of this a while back. It may be worth taking a look at it for inspiration:
main...ajc/in-engine-transport

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants