Repository: vllm-project/vllm
Pull request #14985: [V1] Avoid redundant input processing in n>1 case
Author: login: njhill
Labels: name: ready
description: ONLY add when PR is ready to merge/full CI is needed
name: v1
Created: 2025-03-17T21:47:16Z | Merged: 2025-03-21T05:24:10Z

Follow-on streamlining of V1 parallel sampling implementation. No need to repeat input processing for each sub-request.

Includes removing the unnecessary/unused request_id arg from tokenizer encode methods.

cc @markmc @afeldman-nm 


Diff:
diff --git a/tests/lora/test_tokenizer_group.py b/tests/lora/test_tokenizer_group.py
index 589167e801f1..d605ab734688 100644
--- a/tests/lora/test_tokenizer_group.py
+++ b/tests/lora/test_tokenizer_group.py
@@ -24,12 +24,10 @@ async def test_tokenizer_group_lora(sql_lora_files, tokenizer_group_type):
     )
     lora_request = LoRARequest("1", 1, sql_lora_files)
     assert reference_tokenizer.encode("prompt") == tokenizer_group.encode(
-        request_id="request_id", prompt="prompt", lora_request=lora_request)
+        prompt="prompt", lora_request=lora_request)
     assert reference_tokenizer.encode(
         "prompt") == await tokenizer_group.encode_async(
-            request_id="request_id",
-            prompt="prompt",
-            lora_request=lora_request)
+            prompt="prompt", lora_request=lora_request)
     assert isinstance(tokenizer_group.get_lora_tokenizer(None),
                       PreTrainedTokenizerBase)
     assert tokenizer_group.get_lora_tokenizer(
diff --git a/tests/tokenization/test_tokenizer_group.py b/tests/tokenization/test_tokenizer_group.py
index d1873823ac18..5b62f992c1be 100644
--- a/tests/tokenization/test_tokenizer_group.py
+++ b/tests/tokenization/test_tokenizer_group.py
@@ -41,10 +41,10 @@ async def test_tokenizer_group(tokenizer_group_type):
         max_input_length=None,
     )
     assert reference_tokenizer.encode("prompt") == tokenizer_group.encode(
-        request_id="request_id", prompt="prompt", lora_request=None)
+        prompt="prompt", lora_request=None)
     assert reference_tokenizer.encode(
-        "prompt") == await tokenizer_group.encode_async(
-            request_id="request_id", prompt="prompt", lora_request=None)
+        "prompt") == await tokenizer_group.encode_async(prompt="prompt",
+                                                        lora_request=None)
     assert isinstance(tokenizer_group.get_lora_tokenizer(None),
                       PreTrainedTokenizerBase)
     assert tokenizer_group.get_lora_tokenizer(
@@ -69,8 +69,7 @@ async def test_tokenizer_group_pool(tokenizer_group_type):
     # and check that all requests are processed correctly.
     num_requests = tokenizer_group_pool.pool_size * 5
     requests = [
-        tokenizer_group_pool.encode_async(request_id=str(i),
-                                          prompt=f"prompt {i}",
+        tokenizer_group_pool.encode_async(prompt=f"prompt {i}",
                                           lora_request=None)
         for i in range(num_requests)
     ]
@@ -161,12 +160,8 @@ class FailingRayTokenizerGroupPool(RayTokenizerGroupPool):
     fail_at[0] = 1000
 
     # We should recover successfully.
-    await tokenizer_group_pool.encode_async(request_id="1",
-                                            prompt="prompt",
-                                            lora_request=None)
-    await tokenizer_group_pool.encode_async(request_id="1",
-                                            prompt="prompt",
-                                            lora_request=None)
+    await tokenizer_group_pool.encode_async(prompt="prompt", lora_request=None)
+    await tokenizer_group_pool.encode_async(prompt="prompt", lora_request=None)
 
     # Check that we have a new actor
     assert len(tokenizer_group_pool.tokenizer_actors) == len(tokenizer_actors)
@@ -184,8 +179,7 @@ class FailingRayTokenizerGroupPool(RayTokenizerGroupPool):
 
     # We should fail after re-initialization.
     with pytest.raises(RuntimeError):
-        await tokenizer_group_pool.encode_async(request_id="1",
-                                                prompt="prompt",
+        await tokenizer_group_pool.encode_async(prompt="prompt",
                                                 lora_request=None)
 
     # check_health should raise the same thing
@@ -206,11 +200,8 @@ class FailingRayTokenizerGroupPool(RayTokenizerGroupPool):
 
     # Prompt too long error
     with pytest.raises(ValueError):
-        await tokenizer_group_pool.encode_async(request_id="1",
-                                                prompt="prompt" * 100,
+        await tokenizer_group_pool.encode_async(prompt="prompt" * 100,
                                                 lora_request=None)
-    await tokenizer_group_pool.encode_async(request_id="1",
-                                            prompt="prompt",
-                                            lora_request=None)
+    await tokenizer_group_pool.encode_async(prompt="prompt", lora_request=None)
     # Actors should stay the same.
     assert tokenizer_group_pool.tokenizer_actors == tokenizer_actors
diff --git a/vllm/engine/async_llm_engine.py b/vllm/engine/async_llm_engine.py
index c6fafbeea9c1..91b9cc62719a 100644
--- a/vllm/engine/async_llm_engine.py
+++ b/vllm/engine/async_llm_engine.py
@@ -492,7 +492,6 @@ async def add_request_async(
 
         preprocessed_inputs = await self.input_preprocessor.preprocess_async(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             prompt_adapter_request=prompt_adapter_request,
         )
diff --git a/vllm/engine/llm_engine.py b/vllm/engine/llm_engine.py
index 51a82c415a8a..b9a8b6a53065 100644
--- a/vllm/engine/llm_engine.py
+++ b/vllm/engine/llm_engine.py
@@ -783,7 +783,6 @@ def add_request(
 
         preprocessed_inputs = self.input_preprocessor.preprocess(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             prompt_adapter_request=prompt_adapter_request,
         )
diff --git a/vllm/engine/protocol.py b/vllm/engine/protocol.py
index be9f3af0b545..d2f2c226d2fc 100644
--- a/vllm/engine/protocol.py
+++ b/vllm/engine/protocol.py
@@ -81,10 +81,7 @@ async def beam_search(
         if is_explicit_encoder_decoder_prompt(prompt):
             raise NotImplementedError
         else:
-            processed_inputs = preprocessor._prompt_to_llm_inputs(
-                prompt,
-                request_id=request_id,
-            )
+            processed_inputs = preprocessor._prompt_to_llm_inputs(prompt)
 
         prompt_token_ids = processed_inputs["prompt_token_ids"]
         prompt_text = processed_inputs.get("prompt")
diff --git a/vllm/inputs/preprocess.py b/vllm/inputs/preprocess.py
index af35e43d825a..33f39bedea5b 100644
--- a/vllm/inputs/preprocess.py
+++ b/vllm/inputs/preprocess.py
@@ -182,7 +182,6 @@ def _apply_prompt_adapter(
     def _tokenize_prompt(
         self,
         prompt: str,
-        request_id: str,
         lora_request: Optional[LoRARequest],
     ) -> list[int]:
         """
@@ -202,15 +201,13 @@ def _tokenize_prompt(
                     "do_lower_case", False)):
             prompt = prompt.lower()
 
-        return tokenizer.encode(request_id=request_id,
-                                prompt=prompt,
+        return tokenizer.encode(prompt=prompt,
                                 lora_request=lora_request,
                                 add_special_tokens=add_special_tokens)
 
     async def _tokenize_prompt_async(
         self,
         prompt: str,
-        request_id: str,
         lora_request: Optional[LoRARequest],
     ) -> list[int]:
         """Async version of :meth:`_tokenize_prompt`."""
@@ -222,7 +219,6 @@ async def _tokenize_prompt_async(
             # appending an EOS token to the prompt which disrupts generation.
             add_special_tokens = False
         return await tokenizer.encode_async(
-            request_id=request_id,
             prompt=prompt,
             lora_request=lora_request,
             add_special_tokens=add_special_tokens)
@@ -309,7 +305,6 @@ async def _process_multimodal_async(
     def _prompt_to_llm_inputs(
         self,
         prompt: SingletonPrompt,
-        request_id: str,
         lora_request: Optional[LoRARequest] = None,
         return_mm_hashes: bool = False,
     ) -> SingletonInputs:
@@ -318,7 +313,6 @@ def _prompt_to_llm_inputs(
 
         Arguments:
 
-        * request_id
         * prompt: single encoder or decoder input prompt
         * lora_request: this is only valid for decoder prompts
         * return_mm_hashes: whether to return multimodal hashes
@@ -333,7 +327,6 @@ def _prompt_to_llm_inputs(
             prompt_text = parsed["content"]
             prompt_token_ids = self._tokenize_prompt(
                 prompt_text,
-                request_id=request_id,
                 lora_request=lora_request,
             )
 
@@ -384,7 +377,6 @@ def _prompt_to_llm_inputs(
 
             prompt_token_ids = self._tokenize_prompt(
                 prompt_text,
-                request_id=request_id,
                 lora_request=lora_request,
             )
 
@@ -400,7 +392,6 @@ def _prompt_to_llm_inputs(
     async def _prompt_to_llm_inputs_async(
         self,
         prompt: SingletonPrompt,
-        request_id: str,
         lora_request: Optional[LoRARequest] = None,
         return_mm_hashes: bool = False,
     ) -> SingletonInputs:
@@ -411,7 +402,6 @@ async def _prompt_to_llm_inputs_async(
             prompt_text = parsed["content"]
             prompt_token_ids = await self._tokenize_prompt_async(
                 prompt_text,
-                request_id=request_id,
                 lora_request=lora_request,
             )
 
@@ -460,7 +450,6 @@ async def _prompt_to_llm_inputs_async(
 
             prompt_token_ids = await self._tokenize_prompt_async(
                 prompt_text,
-                request_id=request_id,
                 lora_request=lora_request,
             )
 
@@ -560,7 +549,6 @@ def _separate_enc_dec_inputs_from_mm_processor_outputs(
     def _process_encoder_decoder_prompt(
         self,
         prompt: PromptType,
-        request_id: str,
     ) -> EncoderDecoderInputs:
         """
         For encoder/decoder models only:
@@ -587,7 +575,6 @@ def _process_encoder_decoder_prompt(
         Arguments:
 
         * prompt: an input prompt
-        * request_id
 
         Returns:
 
@@ -598,16 +585,11 @@ def _process_encoder_decoder_prompt(
 
         if is_explicit_encoder_decoder_prompt(prompt):
             encoder_inputs = self._prompt_to_llm_inputs(
-                prompt["encoder_prompt"],
-                request_id=request_id,
-            )
+                prompt["encoder_prompt"])
             if (decoder_input := prompt["decoder_prompt"]) is None:
                 decoder_inputs = None
             else:
-                decoder_inputs = self._prompt_to_llm_inputs(
-                    decoder_input,
-                    request_id=request_id,
-                )
+                decoder_inputs = self._prompt_to_llm_inputs(decoder_input)
             # For multimodal model, override decoder prompt from processor
             # with explicit decoder prompt.
             if self.model_config.is_multimodal_model and (
@@ -616,10 +598,7 @@ def _process_encoder_decoder_prompt(
                     self._separate_enc_dec_inputs_from_mm_processor_outputs(
                         encoder_inputs, decoder_inputs))
         else:
-            inputs = self._prompt_to_llm_inputs(
-                prompt,
-                request_id=request_id,
-            )
+            inputs = self._prompt_to_llm_inputs(prompt)
             if self.model_config.is_multimodal_model and (
                     self._can_process_multimodal()):
                 # Encoder-Decoder Multimodal model
@@ -636,7 +615,6 @@ def _process_encoder_decoder_prompt(
     async def _process_encoder_decoder_prompt_async(
         self,
         prompt: PromptType,
-        request_id: str,
     ) -> EncoderDecoderInputs:
         """Async version of :meth:`_process_encoder_decoder_prompt`."""
         encoder_inputs: SingletonInputs
@@ -644,18 +622,13 @@ async def _process_encoder_decoder_prompt_async(
 
         if is_explicit_encoder_decoder_prompt(prompt):
             encoder_task = self._prompt_to_llm_inputs_async(
-                prompt["encoder_prompt"],
-                request_id=request_id,
-            )
+                prompt["encoder_prompt"])
 
             if (decoder_input := prompt["decoder_prompt"]) is None:
                 encoder_inputs = await encoder_task
                 decoder_inputs = None
             else:
-                decoder_task = self._prompt_to_llm_inputs_async(
-                    decoder_input,
-                    request_id=request_id,
-                )
+                decoder_task = self._prompt_to_llm_inputs_async(decoder_input)
 
                 encoder_inputs, decoder_inputs = await asyncio.gather(
                     encoder_task, decoder_task)
@@ -668,10 +641,7 @@ async def _process_encoder_decoder_prompt_async(
                     self._separate_enc_dec_inputs_from_mm_processor_outputs(
                         encoder_inputs, decoder_inputs))
         else:
-            inputs = await self._prompt_to_llm_inputs_async(
-                prompt,
-                request_id=request_id,
-            )
+            inputs = await self._prompt_to_llm_inputs_async(prompt)
             if self.model_config.is_multimodal_model and (
                     self._can_process_multimodal()):
                 # Encoder-Decoder Multimodal model
@@ -704,7 +674,6 @@ def _build_decoder_only_llm_inputs(
     def _process_decoder_only_prompt(
         self,
         prompt: SingletonPrompt,
-        request_id: str,
         lora_request: Optional[LoRARequest] = None,
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         return_mm_hashes: bool = False,
@@ -716,7 +685,6 @@ def _process_decoder_only_prompt(
         Arguments:
 
         * prompt: input prompt
-        * request_id
         * lora_request
         * prompt_adapter_request
         * return_mm_hashes
@@ -728,7 +696,6 @@ def _process_decoder_only_prompt(
 
         prompt_comps = self._prompt_to_llm_inputs(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             return_mm_hashes=return_mm_hashes,
         )
@@ -741,7 +708,6 @@ def _process_decoder_only_prompt(
     async def _process_decoder_only_prompt_async(
         self,
         prompt: SingletonPrompt,
-        request_id: str,
         lora_request: Optional[LoRARequest] = None,
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         return_mm_hashes: bool = False,
@@ -749,7 +715,6 @@ async def _process_decoder_only_prompt_async(
         """Async version of :meth:`_process_decoder_only_prompt`."""
         prompt_comps = await self._prompt_to_llm_inputs_async(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             return_mm_hashes=return_mm_hashes,
         )
@@ -762,7 +727,6 @@ async def _process_decoder_only_prompt_async(
     def preprocess(
         self,
         prompt: PromptType,
-        request_id: str,
         lora_request: Optional[LoRARequest] = None,
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         return_mm_hashes: bool = False,
@@ -774,10 +738,7 @@ def preprocess(
                 "returned until they are supported on vLLM V1.")
             # Encoder-decoder model requires special mapping of
             # input prompts to encoder & decoder
-            return self._process_encoder_decoder_prompt(
-                prompt,
-                request_id=request_id,
-            )
+            return self._process_encoder_decoder_prompt(prompt)
 
         if is_explicit_encoder_decoder_prompt(prompt):
             raise ValueError("Cannot pass encoder-decoder prompt "
@@ -786,7 +747,6 @@ def preprocess(
         # Decoder-only operation
         return self._process_decoder_only_prompt(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             prompt_adapter_request=prompt_adapter_request,
             return_mm_hashes=return_mm_hashes,
@@ -795,7 +755,6 @@ def preprocess(
     async def preprocess_async(
         self,
         prompt: PromptType,
-        request_id: str,
         lora_request: Optional[LoRARequest] = None,
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         return_mm_hashes: bool = False,
@@ -807,10 +766,7 @@ async def preprocess_async(
                 "returned until they are supported on vLLM V1.")
             # Encoder-decoder model requires special mapping of
             # input prompts to encoder & decoder
-            return await self._process_encoder_decoder_prompt_async(
-                prompt,
-                request_id=request_id,
-            )
+            return await self._process_encoder_decoder_prompt_async(prompt)
 
         if is_explicit_encoder_decoder_prompt(prompt):
             raise ValueError("Cannot pass encoder-decoder prompt "
@@ -819,7 +775,6 @@ async def preprocess_async(
         # Decoder-only operation
         return await self._process_decoder_only_prompt_async(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             prompt_adapter_request=prompt_adapter_request,
             return_mm_hashes=return_mm_hashes,
diff --git a/vllm/transformers_utils/tokenizer_group/base_tokenizer_group.py b/vllm/transformers_utils/tokenizer_group/base_tokenizer_group.py
index fbdfa3e57e17..c5108a7fc6eb 100644
--- a/vllm/transformers_utils/tokenizer_group/base_tokenizer_group.py
+++ b/vllm/transformers_utils/tokenizer_group/base_tokenizer_group.py
@@ -33,7 +33,6 @@ def get_max_input_len(
     @abstractmethod
     def encode(self,
                prompt: str,
-               request_id: Optional[str] = None,
                lora_request: Optional[LoRARequest] = None,
                add_special_tokens: Optional[bool] = None) -> List[int]:
         """Encode a prompt using the tokenizer group."""
@@ -43,7 +42,6 @@ def encode(self,
     async def encode_async(
             self,
             prompt: str,
-            request_id: Optional[str] = None,
             lora_request: Optional[LoRARequest] = None,
             add_special_tokens: Optional[bool] = None) -> List[int]:
         """Encode a prompt using the tokenizer group."""
diff --git a/vllm/transformers_utils/tokenizer_group/ray_tokenizer_group.py b/vllm/transformers_utils/tokenizer_group/ray_tokenizer_group.py
index 30cab752ccf3..b048b8094174 100644
--- a/vllm/transformers_utils/tokenizer_group/ray_tokenizer_group.py
+++ b/vllm/transformers_utils/tokenizer_group/ray_tokenizer_group.py
@@ -113,7 +113,6 @@ def _finalize_encode(self, actor: ray.ObjectRef,
 
     def encode(self,
                prompt: str,
-               request_id: Optional[str] = None,
                lora_request: Optional[LoRARequest] = None,
                add_special_tokens: Optional[bool] = None) -> List[int]:
         """Encode a prompt using the tokenizer group.
@@ -133,8 +132,7 @@ def encode(self,
         original_actor = actor
         try:
             ret = ray.get(
-                actor.encode.remote(request_id=request_id,
-                                    prompt=prompt,
+                actor.encode.remote(prompt=prompt,
                                     lora_request=lora_request,
                                     add_special_tokens=add_special_tokens))
         except ActorDiedError as e:
@@ -145,8 +143,7 @@ def encode(self,
             actor = self._init_actor()
             try:
                 ret = ray.get(
-                    actor.encode.remote(request_id=request_id,
-                                        prompt=prompt,
+                    actor.encode.remote(prompt=prompt,
                                         lora_request=lora_request,
                                         add_special_tokens=add_special_tokens))
             except ActorDiedError as e:
@@ -164,7 +161,6 @@ def encode(self,
     async def encode_async(
             self,
             prompt: str,
-            request_id: Optional[str] = None,
             lora_request: Optional[LoRARequest] = None,
             add_special_tokens: Optional[bool] = None) -> List[int]:
         """Encode a prompt using the tokenizer group.
@@ -184,7 +180,6 @@ async def encode_async(
         original_actor = actor
         try:
             ret = await actor.encode.remote(
-                request_id=request_id,
                 prompt=prompt,
                 lora_request=lora_request,
                 add_special_tokens=add_special_tokens)
@@ -196,7 +191,6 @@ async def encode_async(
             actor = self._init_actor()
             try:
                 ret = await actor.encode.remote(
-                    request_id=request_id,
                     prompt=prompt,
                     lora_request=lora_request,
                     add_special_tokens=add_special_tokens)
diff --git a/vllm/transformers_utils/tokenizer_group/tokenizer_group.py b/vllm/transformers_utils/tokenizer_group/tokenizer_group.py
index 025971cb7e47..b6e9005bcd24 100644
--- a/vllm/transformers_utils/tokenizer_group/tokenizer_group.py
+++ b/vllm/transformers_utils/tokenizer_group/tokenizer_group.py
@@ -56,7 +56,6 @@ def _raise_if_input_too_long(self,
 
     def encode(self,
                prompt: str,
-               request_id: Optional[str] = None,
                lora_request: Optional[LoRARequest] = None,
                add_special_tokens: Optional[bool] = None) -> List[int]:
         tokenizer = self.get_lora_tokenizer(lora_request)
@@ -69,7 +68,6 @@ def encode(self,
     async def encode_async(
             self,
             prompt: str,
-            request_id: Optional[str] = None,
             lora_request: Optional[LoRARequest] = None,
             add_special_tokens: Optional[bool] = None) -> List[int]:
         tokenizer = await self.get_lora_tokenizer_async(lora_request)
diff --git a/vllm/v1/engine/async_llm.py b/vllm/v1/engine/async_llm.py
index 171c1c7da28e..e0169f1a4ded 100644
--- a/vllm/v1/engine/async_llm.py
+++ b/vllm/v1/engine/async_llm.py
@@ -4,6 +4,7 @@
 import logging
 import os
 from collections.abc import AsyncGenerator, Mapping
+from copy import copy
 from typing import Optional, Union
 
 import numpy as np
@@ -25,6 +26,7 @@
 from vllm.transformers_utils.tokenizer_group import init_tokenizer_from_configs
 from vllm.usage.usage_lib import UsageContext
 from vllm.utils import Device, cdiv, kill_process_tree
+from vllm.v1.engine import EngineCoreRequest
 from vllm.v1.engine.core_client import EngineCoreClient
 from vllm.v1.engine.output_processor import OutputProcessor
 from vllm.v1.engine.parallel_sampling import ParentRequest
@@ -177,33 +179,44 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
 
-            # 3) Convert Input --> Request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+            return queue
 
-            # 4) Add the request to OutputProcessor (this process).
-            self.output_processor.add_request(request, parent_req, idx, queue)
+        # Fan out child requests (for n>1).
+        parent_request = ParentRequest(request_id, params)
+        for idx in range(n):
+            request_id, params = parent_request.get_child_info(idx)
+            child_request = request if idx == n - 1 else copy(request)
+            child_request.request_id = request_id
+            child_request.sampling_params = params
+            await self._add_request(child_request, parent_request, idx, queue)
+        return queue
 
-            # 5) Add the EngineCoreRequest to EngineCore (separate process).
-            await self.engine_core.add_request_async(request)
+    async def _add_request(self, request: EngineCoreRequest,
+                           parent_req: Optional[ParentRequest], index: int,
+                           queue: asyncio.Queue[RequestOutput]):
 
-            if self.log_requests:
-                logger.info("Added request %s.", request_id)
+        # Add the request to OutputProcessor (this process).
+        self.output_processor.add_request(request, parent_req, index, queue)
 
-        return queue
+        # Add the EngineCoreRequest to EngineCore (separate process).
+        await self.engine_core.add_request_async(request)
+
+        if self.log_requests:
+            logger.info("Added request %s.", request.request_id)
 
     # TODO: we should support multiple prompts in one call, as you
     # can do with LLM.generate. So that for multi-prompt completion
diff --git a/vllm/v1/engine/llm_engine.py b/vllm/v1/engine/llm_engine.py
index 14338e5cbe88..7bda3a30d202 100644
--- a/vllm/v1/engine/llm_engine.py
+++ b/vllm/v1/engine/llm_engine.py
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 from collections.abc import Mapping
+from copy import copy
 from typing import Optional, Union
 
 from typing_extensions import TypeVar
@@ -179,25 +180,34 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
 
-            # 3) Make a new RequestState and queue.
-            self.output_processor.add_request(request, parent_req, idx)
+        n = params.n if isinstance(params, SamplingParams) else 1
 
-            # 3) Add the request to EngineCore.
+        if n == 1:
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(request, None, 0)
+            # Add the request to EngineCore.
             self.engine_core.add_request(request)
+            return
+
+        # Fan out child requests (for n>1).
+        parent_req = ParentRequest(request_id, params)
+        for idx in range(n):
+            request_id, params = parent_req.get_child_info(idx)
+            child_request = request if idx == n - 1 else copy(request)
+            child_request.request_id = request_id
+            child_request.sampling_params = params
+
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(child_request, parent_req, idx)
+            # Add the request to EngineCore.
+            self.engine_core.add_request(child_request)
 
     def step(self) -> list[RequestOutput]:
 
diff --git a/vllm/v1/engine/parallel_sampling.py b/vllm/v1/engine/parallel_sampling.py
index 0eeca657406e..4df7ca59731e 100644
--- a/vllm/v1/engine/parallel_sampling.py
+++ b/vllm/v1/engine/parallel_sampling.py
@@ -1,10 +1,9 @@
 # SPDX-License-Identifier: Apache-2.0
 
 from copy import copy
-from typing import Optional, Union
+from typing import Optional
 
 from vllm.outputs import CompletionOutput
-from vllm.pooling_params import PoolingParams
 from vllm.sampling_params import RequestOutputKind, SamplingParams
 from vllm.v1.metrics.stats import IterationStats
 
@@ -43,16 +42,6 @@ def __init__(self, request_id: str,
         self.max_num_generation_tokens = 0
         self.cached_child_sampling_params = None
 
-    @classmethod
-    def from_params(
-        cls,
-        request_id: str,
-        params: Union[SamplingParams, PoolingParams],
-    ) -> Optional['ParentRequest']:
-        if not isinstance(params, SamplingParams) or params.n == 1:
-            return None
-        return cls(request_id, params)
-
     def _get_child_sampling_params(
         self,
         index: int,
diff --git a/vllm/v1/engine/processor.py b/vllm/v1/engine/processor.py
index d823c45d5990..55e0fdcd65b5 100644
--- a/vllm/v1/engine/processor.py
+++ b/vllm/v1/engine/processor.py
@@ -173,7 +173,6 @@ def process_inputs(
         # 3. Apply prompt adapter to prompt token ids if one exists.
         processed_inputs: ProcessorInputs = self.input_preprocessor.preprocess(
             prompt,
-            request_id=request_id,
             lora_request=lora_request,
             prompt_adapter_request=prompt_adapter_request,
             return_mm_hashes=self.use_hash,


Reviews:
user: login: robertgshaw2-redhat
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T22:08:28Z
user: login: njhill
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T22:58:05Z
user: login: afeldman-nm
body: Thanks, it will be great to eliminate input processing redundancy. Mostly just nits in terms of feedback.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T18:49:05Z
user: login: njhill
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T19:07:01Z
user: login: njhill
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T19:09:43Z
user: login: njhill
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T19:09:52Z
user: login: njhill
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T19:10:39Z
user: login: afeldman-nm
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T20:27:49Z

Review comments:
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
 
-            # 3) Make a new RequestState and queue.
-            self.output_processor.add_request(request, parent_req, idx)
+        n = params.n if isinstance(params, SamplingParams) else 1
 
-            # 3) Add the request to EngineCore.
+        if n == 1:
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(request, None, 0)
+            # Add the request to EngineCore.
             self.engine_core.add_request(request)
 
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
+                request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/llm_engine.py
user: login: robertgshaw2-redhat
body: are we okay with shallow copy here?
created_at: 2025-03-17T22:08:28Z
author_association: COLLABORATOR
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
 
-            # 3) Make a new RequestState and queue.
-            self.output_processor.add_request(request, parent_req, idx)
+        n = params.n if isinstance(params, SamplingParams) else 1
 
-            # 3) Add the request to EngineCore.
+        if n == 1:
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(request, None, 0)
+            # Add the request to EngineCore.
             self.engine_core.add_request(request)
 
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
+                request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/llm_engine.py
user: login: njhill
body: Yes since the EngineCoreRequest is just immediately serialized. Even in the in-proc case, none of the contents are mutated.
created_at: 2025-03-17T22:58:05Z
author_association: MEMBER
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
 
-            # 3) Make a new RequestState and queue.
-            self.output_processor.add_request(request, parent_req, idx)
+        n = params.n if isinstance(params, SamplingParams) else 1
 
-            # 3) Add the request to EngineCore.
+        if n == 1:
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(request, None, 0)
+            # Add the request to EngineCore.
             self.engine_core.add_request(request)
 
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
+                request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
+                child_req.request_id = request_id
+                child_req.sampling_params = params
+
+                # 3) Make a new RequestState and queue.
+                self.output_processor.add_request(child_req, parent_req, idx)
+                # 3) Add the request to EngineCore.
+                self.engine_core.add_request(child_req)
path: vllm/v1/engine/llm_engine.py
user: login: afeldman-nm
body: Nit: it would seem that we are not enumerating processing steps in the prior comments, so "3)" can be eliminated.
created_at: 2025-03-20T18:30:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
+
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
                 request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/async_llm.py
user: login: afeldman-nm
body: Nit: why not

```suggestion
                child_req = request if idx == 0 else copy(request)
```

? Unless there is a specifically reason to pass the original request data structure to child `n-1`, why not simplify by passing it to child 0?
created_at: 2025-03-20T18:37:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
+
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+
+        else:
path: vllm/v1/engine/async_llm.py
user: login: afeldman-nm
body: Not sure if worthwhile, but perhaps you could rewrite this section to make the `else` redundant:

```
        if n == 1:
            await self._add_request(request, None, 0, queue)
            return queue
        
        # Fan out child requests (for n>1).
        parent_req = ParentRequest(request_id, params)
        for idx in range(n):
            request_id, params = parent_req.get_child_info(idx)
            child_req = request if idx == n - 1 else copy(request)
            child_req.request_id = request_id
            child_req.sampling_params = params
            await self._add_request(child_req, parent_req, idx, queue)
                
        return queue
```

I like this because it expresses that there is a "quick exit" when `n==1` and a longer process otherwise.
created_at: 2025-03-20T18:43:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
path: vllm/v1/engine/llm_engine.py
user: login: afeldman-nm
body: Nit: kwargs?
created_at: 2025-03-20T18:44:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
path: vllm/v1/engine/async_llm.py
user: login: afeldman-nm
body: Nit: kwargs?
created_at: 2025-03-20T18:45:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
+
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
                 request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/async_llm.py
user: login: afeldman-nm
body: Nit: maybe `child_request` instead of `child_req`, if you prefer not to use abbreviations?
created_at: 2025-03-20T18:46:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
+
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
path: vllm/v1/engine/async_llm.py
user: login: afeldman-nm
body: Nit: maybe `parent_request`, if you prefer not to use abbreviations?
created_at: 2025-03-20T18:47:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
 
-            # 3) Make a new RequestState and queue.
-            self.output_processor.add_request(request, parent_req, idx)
+        n = params.n if isinstance(params, SamplingParams) else 1
 
-            # 3) Add the request to EngineCore.
+        if n == 1:
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(request, None, 0)
+            # Add the request to EngineCore.
             self.engine_core.add_request(request)
 
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
path: vllm/v1/engine/llm_engine.py
user: login: afeldman-nm
body: Nit: maybe `parent_request`, if you prefer not to use abbreviations?
created_at: 2025-03-20T18:47:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
 
-            # 3) Make a new RequestState and queue.
-            self.output_processor.add_request(request, parent_req, idx)
+        n = params.n if isinstance(params, SamplingParams) else 1
 
-            # 3) Add the request to EngineCore.
+        if n == 1:
+            # Make a new RequestState and queue.
+            self.output_processor.add_request(request, None, 0)
+            # Add the request to EngineCore.
             self.engine_core.add_request(request)
 
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
+                request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/llm_engine.py
user: login: afeldman-nm
body: Nit: maybe `child_request`, if you prefer not to use abbreviations?
created_at: 2025-03-20T18:48:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
+
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
                 request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/async_llm.py
user: login: njhill
body: Just defensive again, we are dispatching each copy of the request so potentially better to not copy the ones that have already been sent.
created_at: 2025-03-20T19:07:01Z
author_association: MEMBER
diff_hunk: @@ -178,26 +179,35 @@ def add_request(
         prompt_adapter_request: Optional[PromptAdapterRequest] = None,
         priority: int = 0,
     ) -> None:
-        # 1) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
-        n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
-                request_id, params = parent_req.get_child_info(idx)
-
-            # 2) Process raw inputs into the request.
-            request = self.processor.process_inputs(request_id, prompt, params,
-                                                    arrival_time, lora_request,
-                                                    trace_headers,
-                                                    prompt_adapter_request,
-                                                    priority)
+        # Process raw inputs into the request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
path: vllm/v1/engine/llm_engine.py
user: login: njhill
body: I just moved the call which was already there :)
created_at: 2025-03-20T19:09:43Z
author_association: MEMBER
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
path: vllm/v1/engine/async_llm.py
user: login: njhill
body: same as above
created_at: 2025-03-20T19:09:52Z
author_association: MEMBER
diff_hunk: @@ -177,33 +179,45 @@ async def add_request(
     ) -> asyncio.Queue[RequestOutput]:
         """Add new request to the AsyncLLM."""
 
-        # 1) Create a new output queue for the request.
+        # Create a new output queue for the request.
         queue: asyncio.Queue[RequestOutput] = asyncio.Queue()
 
-        # 2) Fan out child requests (for n>1)
-        parent_req = ParentRequest.from_params(request_id, params)
+        # Convert Input --> Request.
+        request = self.processor.process_inputs(request_id, prompt, params,
+                                                arrival_time, lora_request,
+                                                trace_headers,
+                                                prompt_adapter_request,
+                                                priority)
+
         n = params.n if isinstance(params, SamplingParams) else 1
-        for idx in range(n):
-            if parent_req is not None:
+
+        if n == 1:
+            await self._add_request(request, None, 0, queue)
+
+        else:
+            # Fan out child requests (for n>1).
+            parent_req = ParentRequest(request_id, params)
+            for idx in range(n):
                 request_id, params = parent_req.get_child_info(idx)
+                child_req = request if idx == n - 1 else copy(request)
path: vllm/v1/engine/async_llm.py
user: login: njhill
body: Sure. I would typically abbreviate if it helped to avoid line wrapping.
created_at: 2025-03-20T19:10:38Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-17T21:47:25Z
body: 👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

Just a reminder: PRs would not trigger full CI run by default. Instead, it would only run `fastcheck` CI which starts running only a small and essential subset of CI tests to quickly catch errors. You can run other CI tests on top of those by going to your `fastcheck` build on Buildkite UI (linked in the PR checks section) and unblock them. If you do not have permission to unblock, ping `simon-mo` or `khluu` to add you in our Buildkite org.

Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add `ready` label to the PR or enable auto-merge.

🚀
author_association: NONE
user: login: mergify[bot]
created_at: 2025-03-19T18:00:21Z
body: This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @njhill.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
author_association: CONTRIBUTOR
user: login: WoosukKwon
created_at: 2025-03-21T03:27:39Z
body: @njhill Please let me know if this PR is good to merge!
author_association: COLLABORATOR
user: login: njhill
created_at: 2025-03-21T03:50:16Z
body: @WoosukKwon yes it's ready, thanks! I don't know why the CI tests have started randomly OOMing.
author_association: MEMBER