Add vLLM offline backend with micro-batching support#736
Conversation
fc01371 to
bbe2874
Compare
efa1d9e to
942fa2e
Compare
Implements standalone offline backend using vLLM's LLM class for micro-batching. Adapted to main's architecture without VLLMBackendBase, using main's import patterns (lazy loading via guidellm.extras, utils.audio/vision). Features: - Batch processing with configurable batch_size (default: 32) - Chat template support (plain, default-template, custom Jinja2) - Multimodal data handling (image/audio) - Single-process execution for batch coordination - Compatible with vLLM 0.21.0+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Maryam Tahhan <mtahhan@redhat.com>
942fa2e to
5d2304d
Compare
|
Hi @maryamtahhan, the DCO check has failed. Please click on DCO in the Checks section for instructions on how to resolve this. |
Signed-off-by: Maryam Tahhan <mtahhan@redhat.com>
664810c to
251eb67
Compare
Extract duplicated helper methods (_build_multi_modal_data_from_columns, _resolve_chat_template, _extract_prompt_chat_tokenizer, _create_sampling_params) into common.py to follow DRY principles. This addresses maintainer feedback about code reuse and abstraction. Both vllm_python and vllm_offline backends now share the same implementation for these helpers, reducing code duplication from ~400 lines to a single shared module. Signed-off-by: Maryam Tahhan <mtahhan@redhat.com>
Moved 5 additional helper methods to common.py that were duplicated between vllm_python and vllm_offline backends: - extract_text_from_content - build_placeholder_prefix - format_column_blocks - inject_placeholders_into_messages - extract_prompt_chat_plain Total duplication eliminated: ~450 lines across both backends. All helper logic is now centralized in common.py with both backends using thin wrapper methods that delegate to the shared implementation. Signed-off-by: Maryam Tahhan <mtahhan@redhat.com>
Add type: ignore comments for vllm.EngineArgs and vllm.LLM runtime usage since these are lazy-loaded and mypy can't resolve them at static analysis time. Use Any type for vllm.LLM annotations with inline comments documenting the actual type. Fixes CI type-check failures. Signed-off-by: Maryam Tahhan <mtahhan@redhat.com>
|
@sjmonson @jaredoconnell this PR has been rebased and is green again |
|
|
||
|
|
||
| def create_sampling_params( | ||
| vllm_module: Any, |
There was a problem hiding this comment.
Don't pass modules as arguments. Just move from guidellm.extras import vllm out of the TYPE_CHECKING block. guidellm.extras builds lazy stubs so it won't actually import vLLM until you call something on the module.
| if TYPE_CHECKING: | ||
| from guidellm.extras import vllm | ||
| else: | ||
| from guidellm.extras import vllm |
There was a problem hiding this comment.
Its funny this is not a linting error.
| if TYPE_CHECKING: | |
| from guidellm.extras import vllm | |
| else: | |
| from guidellm.extras import vllm | |
| from guidellm.extras import vllm |
| request_info.timings.request_end = time.time() | ||
|
|
||
| if batched_req.result is not None: | ||
| output = batched_req.result |
There was a problem hiding this comment.
So poking around the vLLM docs a bit it looks like vLLM will tell us a lot about the request from what it returns. Maybe take a look at batched_req.result.metrics which is a RequestStateStats and wire all of the latencies to the correct metrics. You can get pretty much all of the latency metrics we normally track. Start/end time I would probably keep how you currently have them though since that is what the "user" sees as start/end.
| @property | ||
| def info(self) -> dict[str, Any]: | ||
| """Get backend configuration details.""" | ||
| return self._args.model_dump() |
| @Backend.register("vllm_offline") | ||
| class VLLMOfflineBackend(Backend): |
There was a problem hiding this comment.
I didn't check too closely but it seems like even with common.py the normal and offline backends share a lot of code. Would it make more sense if the base class was the other vLLM backend? E.g.
| @Backend.register("vllm_offline") | |
| class VLLMOfflineBackend(Backend): | |
| @Backend.register("vllm_offline") | |
| class VLLMOfflineBackend(VLLMPythonBackend): |
Either way I think you should keep common.py as it makes the classes easier to read.
dbutenhof
left a comment
There was a problem hiding this comment.
I think the code is mostly OK (except it should be updated to match the current backend init conventions). The documentation needs to be updated to 0.7 CLI conventions.
| process_response(response) | ||
| await backend.process_shutdown() | ||
| """ | ||
|
|
There was a problem hiding this comment.
add
_args: VLLMOfflineBackendArgsand remove the self._args = arguments in __init__, along with removing the info method as Sam mentioned. This supports the new inherited info that correctly handles JSON-ifying the arguments in the generated report files.
|
|
||
| ```bash | ||
| guidellm benchmark run \ | ||
| --backend vllm_offline \ |
There was a problem hiding this comment.
This command is now obsolete. Replace it with:
guidellm run \
--backend kind=vllm_offline,model=Qwen/Quen3-0.6B,batch_size=64 \
--data kind=synthetic_text,prompt_tokens=256,output_tokens=128 \
--constraint kind=max_requests,count=1000|
|
||
| ## Backend Options | ||
|
|
||
| Configure the offline backend via `--backend-kwargs` with JSON: |
There was a problem hiding this comment.
Specify backend parameters through the --backend with kind=vllm_offline, for example
guidellm run \
--backend `{
"kind": "vllm_offline",
"model": ""meta-llama/Llama-2-7b-hf",
"batch_size": 64,
"vllm_config": {
"tensor_parallel_size": 2,
"gpu_memory_utilization": 0.9
}
}'|
|
||
| The offline backend uses a **micro-batching** approach: | ||
|
|
||
| 1. **Buffering**: As requests arrive via `resolve()`, they're added to a buffer |
There was a problem hiding this comment.
At this level, I don't think I'd use the resolve() method name: it won't mean much to most readers, and isn't obvious. Maybe, "as requests are generated from the dataset" ...
|
|
||
| ### Basic Throughput Benchmark | ||
|
|
||
| ```bash |
There was a problem hiding this comment.
Again, this (and the following examples) need to be updated to the new CLI model.
|
|
||
| ```bash | ||
| # Reduce memory usage | ||
| --backend-kwargs '{ |
There was a problem hiding this comment.
Again, these are all under --backend now, as I showed above.
| Reduce `batch_size` or `gpu_memory_utilization`: | ||
|
|
||
| ```bash | ||
| --backend-kwargs '{"batch_size": 16, "vllm_config": {"gpu_memory_utilization": 0.7}}' |
There was a problem hiding this comment.
And here... plus a few more below
Add vLLM Offline Backend with Shared Base Class
This PR implements offline/batch inference support for vLLM using a clean, extensible architecture that eliminates code duplication between vLLM backends.
Summary
Adds
VLLMOfflineBackendfor batch processing and refactors existing vLLM code into a sharedVLLMBackendBaseclass. This reduces code duplication by ~360 lines while adding new offline inference capabilities optimized for benchmarking scenarios.New Components
VLLMBackendBase (base.py)
Shared base class for all vLLM backends containing ~400 lines of common functionality:
_get_tokenizer()method for subclass implementationVLLMOfflineBackend (offline.py)
New backend for offline batch processing using vLLM's
LLMclass:batch_size(default: 32)LLM.generate()Refactored VLLMPythonBackend (vllm.py)
VLLMBackendBaseinstead ofBackenddirectly_get_tokenizer()forAsyncLLMEngineKey Benefits
Documentation
docs/guides/vllm-offline-backend.mddocs/guides/backends.mdwith offline backend sectionUsage Example
Test Plan
Unit Tests (✅ Passing)
VLLMOfflineBackendlifecycle (startup, shutdown, validate)VLLMBackendBaserequest resolution and formattingIntegration Tests (✅ Verified)
Backend.create()Manual Testing
Details
VLLMBackendBaseshared base class insrc/guidellm/backends/vllm_python/base.pyVLLMOfflineBackendandVLLMOfflineBackendArgsinsrc/guidellm/backends/vllm_python/offline.pyVLLMPythonBackendto extendVLLMBackendBase(eliminate duplication)_ResolvedRequest,_has_jinja2_markers) from base for backward compatibilityRuntimeErrorfrom torchcodec/PIL)tests/unit/backends/vllm_python/test_vllm.pydocs/guides/vllm-offline-backend.mddocs/guides/backends.mdwith offline backend documentationvllm_offlinebackend type in Backend registrytest_backend.pywith offline backend registration testUse of AI
git log
commit 5d2304d
Author: Maryam Tahhan mtahhan@redhat.com
Date: Thu Jun 25 11:35:31 2026 +0100
commit 251eb67
Author: Maryam Tahhan mtahhan@redhat.com
Date: Thu Jun 25 12:35:17 2026 +0100
commit bfbcad1
Author: Maryam Tahhan mtahhan@redhat.com
Date: Thu Jun 25 13:41:36 2026 +0100
commit 1b673a2
Author: Maryam Tahhan mtahhan@redhat.com
Date: Thu Jun 25 14:11:19 2026 +0100
commit f27b076
Author: Maryam Tahhan mtahhan@redhat.com
Date: Thu Jun 25 14:18:13 2026 +0100
Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com
Signed-off-by: Maryam Tahhan mtahhan@redhat.com