[EPD][Feat]support adaptive forward (#18118)
This commit is contained in:
@@ -434,22 +434,29 @@ class MMReceiverBase(ABC):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
# Skip mm_pool if not adaptive dispatch to encoder
|
||||||
|
enable_adaptive_dispatch_to_encoder = (
|
||||||
|
server_args.enable_adaptive_dispatch_to_encoder
|
||||||
|
)
|
||||||
self.mm_processor = get_mm_processor(
|
self.mm_processor = get_mm_processor(
|
||||||
hf_config,
|
hf_config,
|
||||||
server_args,
|
server_args,
|
||||||
_processor,
|
_processor,
|
||||||
transport_mode,
|
transport_mode,
|
||||||
skip_mm_pool=True,
|
skip_mm_pool=not enable_adaptive_dispatch_to_encoder,
|
||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def process_waiting_requests(self, recv_reqs):
|
def process_waiting_requests(self, recv_reqs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def recv_mm_data(self, img_data, mm_processor, prompt):
|
async def recv_mm_data(
|
||||||
|
self, img_data, mm_processor, prompt, need_wait_for_image=True
|
||||||
|
):
|
||||||
req_id = None
|
req_id = None
|
||||||
try:
|
try:
|
||||||
if len(self.encode_urls) == 0:
|
if len(self.encode_urls) == 0 or not need_wait_for_image:
|
||||||
return None
|
return None
|
||||||
req_id = uuid.uuid4().hex
|
req_id = uuid.uuid4().hex
|
||||||
embedding_port, recv_socket = get_zmq_socket_on_host(self.context, zmq.PULL)
|
embedding_port, recv_socket = get_zmq_socket_on_host(self.context, zmq.PULL)
|
||||||
|
|||||||
@@ -503,6 +503,7 @@ class Envs:
|
|||||||
# EPD
|
# EPD
|
||||||
SGLANG_ENCODER_RECV_TIMEOUT = EnvFloat(180.0)
|
SGLANG_ENCODER_RECV_TIMEOUT = EnvFloat(180.0)
|
||||||
SGLANG_ENCODER_SEND_TIMEOUT = EnvFloat(180.0)
|
SGLANG_ENCODER_SEND_TIMEOUT = EnvFloat(180.0)
|
||||||
|
SGLANG_ENCODER_DISPATCH_MIN_ITEMS = EnvInt(2)
|
||||||
|
|
||||||
# Elastic EP Backup Port
|
# Elastic EP Backup Port
|
||||||
SGLANG_BACKUP_PORT_BASE = EnvInt(10000)
|
SGLANG_BACKUP_PORT_BASE = EnvInt(10000)
|
||||||
|
|||||||
@@ -1045,6 +1045,110 @@ def embed_mm_inputs(
|
|||||||
return input_embeds, other_info
|
return input_embeds, other_info
|
||||||
|
|
||||||
|
|
||||||
|
def _embed_mm_inputs_with_split(
|
||||||
|
mm_inputs_list: List[MultimodalInputs],
|
||||||
|
extend_prefix_lens: List[int],
|
||||||
|
extend_seq_lens: List[int],
|
||||||
|
input_ids: torch.Tensor,
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
input_embedding: nn.Embedding,
|
||||||
|
multimodal_model: nn.Module = None,
|
||||||
|
data_embedding_func_mapping: Dict[Modality, DataEmbeddingFunc] = None,
|
||||||
|
placeholder_tokens: dict[Modality, List[int]] = None,
|
||||||
|
use_deepstack: Dict[Modality, bool] = {},
|
||||||
|
):
|
||||||
|
"""Split batch into precomputed vs non-precomputed, embed each group, merge back."""
|
||||||
|
precomputed_req_indices = []
|
||||||
|
non_precomputed_req_indices = []
|
||||||
|
for idx, mm_input in enumerate(mm_inputs_list):
|
||||||
|
items = [item for item in mm_input.mm_items if item is not None]
|
||||||
|
if items and all(
|
||||||
|
getattr(item, "precomputed_embeddings", None) is not None for item in items
|
||||||
|
):
|
||||||
|
precomputed_req_indices.append(idx)
|
||||||
|
else:
|
||||||
|
non_precomputed_req_indices.append(idx)
|
||||||
|
|
||||||
|
embed_kwargs = dict(
|
||||||
|
multimodal_model=multimodal_model,
|
||||||
|
input_embedding=input_embedding,
|
||||||
|
data_embedding_func_mapping=data_embedding_func_mapping,
|
||||||
|
placeholder_tokens=placeholder_tokens,
|
||||||
|
use_deepstack=use_deepstack,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not precomputed_req_indices or not non_precomputed_req_indices:
|
||||||
|
return embed_mm_inputs(
|
||||||
|
mm_inputs_list=mm_inputs_list,
|
||||||
|
extend_prefix_lens=extend_prefix_lens,
|
||||||
|
extend_seq_lens=extend_seq_lens,
|
||||||
|
input_ids=input_ids,
|
||||||
|
**embed_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
all_seq_lens = forward_batch.extend_seq_lens_cpu
|
||||||
|
mm_batch_indices = [
|
||||||
|
i for i, mm in enumerate(forward_batch.mm_inputs) if mm is not None
|
||||||
|
]
|
||||||
|
token_starts = []
|
||||||
|
cumulative = 0
|
||||||
|
for sl in all_seq_lens:
|
||||||
|
token_starts.append(cumulative)
|
||||||
|
cumulative += sl
|
||||||
|
|
||||||
|
vocab_size = input_embedding.num_embeddings
|
||||||
|
input_embeds = input_embedding(input_ids.clamp(min=0, max=vocab_size - 1))
|
||||||
|
other_info = {}
|
||||||
|
|
||||||
|
input_deepstack_embeds = None
|
||||||
|
if use_deepstack and multimodal_model is not None:
|
||||||
|
num_deepstack_embeddings = len(multimodal_model.deepstack_visual_indexes)
|
||||||
|
input_deepstack_embeds = torch.zeros(
|
||||||
|
input_ids.shape[0],
|
||||||
|
input_embedding.embedding_dim * num_deepstack_embeddings,
|
||||||
|
device=input_ids.device,
|
||||||
|
dtype=input_embedding.weight.dtype,
|
||||||
|
)
|
||||||
|
other_info["input_deepstack_embeds"] = input_deepstack_embeds
|
||||||
|
|
||||||
|
for group_req_indices in [precomputed_req_indices, non_precomputed_req_indices]:
|
||||||
|
sub_mm_inputs = [mm_inputs_list[i] for i in group_req_indices]
|
||||||
|
sub_prefix_lens = [extend_prefix_lens[i] for i in group_req_indices]
|
||||||
|
sub_seq_lens = [extend_seq_lens[i] for i in group_req_indices]
|
||||||
|
group_batch_indices = [mm_batch_indices[i] for i in group_req_indices]
|
||||||
|
sub_slices = [
|
||||||
|
input_ids[token_starts[bi] : token_starts[bi] + all_seq_lens[bi]]
|
||||||
|
for bi in group_batch_indices
|
||||||
|
]
|
||||||
|
sub_input_ids = torch.cat(sub_slices)
|
||||||
|
|
||||||
|
sub_embeds, sub_info = embed_mm_inputs(
|
||||||
|
mm_inputs_list=sub_mm_inputs,
|
||||||
|
extend_prefix_lens=sub_prefix_lens,
|
||||||
|
extend_seq_lens=sub_seq_lens,
|
||||||
|
input_ids=sub_input_ids,
|
||||||
|
**embed_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
for bi in group_batch_indices:
|
||||||
|
req_len = all_seq_lens[bi]
|
||||||
|
start = token_starts[bi]
|
||||||
|
input_embeds[start : start + req_len] = sub_embeds[
|
||||||
|
offset : offset + req_len
|
||||||
|
]
|
||||||
|
if (
|
||||||
|
input_deepstack_embeds is not None
|
||||||
|
and "input_deepstack_embeds" in sub_info
|
||||||
|
):
|
||||||
|
input_deepstack_embeds[start : start + req_len] = sub_info[
|
||||||
|
"input_deepstack_embeds"
|
||||||
|
][offset : offset + req_len]
|
||||||
|
offset += req_len
|
||||||
|
|
||||||
|
return input_embeds, other_info
|
||||||
|
|
||||||
|
|
||||||
def general_mm_embed_routine(
|
def general_mm_embed_routine(
|
||||||
input_ids: torch.Tensor,
|
input_ids: torch.Tensor,
|
||||||
forward_batch: ForwardBatch,
|
forward_batch: ForwardBatch,
|
||||||
@@ -1091,17 +1195,34 @@ def general_mm_embed_routine(
|
|||||||
for i, seq_len in enumerate(forward_batch.extend_seq_lens_cpu)
|
for i, seq_len in enumerate(forward_batch.extend_seq_lens_cpu)
|
||||||
if forward_batch.mm_inputs[i] is not None
|
if forward_batch.mm_inputs[i] is not None
|
||||||
]
|
]
|
||||||
|
server_args = get_global_server_args()
|
||||||
|
if server_args and server_args.enable_adaptive_dispatch_to_encoder:
|
||||||
|
# Split by precomputed vs non-precomputed so get_embedding_and_mask only sees uniform batches
|
||||||
|
input_embeds, other_info = _embed_mm_inputs_with_split(
|
||||||
|
mm_inputs_list=mm_inputs_list,
|
||||||
|
extend_prefix_lens=extend_prefix_lens,
|
||||||
|
extend_seq_lens=extend_seq_lens,
|
||||||
|
input_ids=input_ids,
|
||||||
|
forward_batch=forward_batch,
|
||||||
|
input_embedding=embed_tokens,
|
||||||
|
multimodal_model=multimodal_model,
|
||||||
|
data_embedding_func_mapping=data_embedding_funcs,
|
||||||
|
placeholder_tokens=placeholder_tokens,
|
||||||
|
use_deepstack=use_deepstack,
|
||||||
|
)
|
||||||
|
else:
|
||||||
input_embeds, other_info = embed_mm_inputs(
|
input_embeds, other_info = embed_mm_inputs(
|
||||||
mm_inputs_list=mm_inputs_list,
|
mm_inputs_list=mm_inputs_list,
|
||||||
extend_prefix_lens=extend_prefix_lens,
|
extend_prefix_lens=extend_prefix_lens,
|
||||||
extend_seq_lens=extend_seq_lens,
|
extend_seq_lens=extend_seq_lens,
|
||||||
input_ids=input_ids,
|
input_ids=input_ids,
|
||||||
multimodal_model=multimodal_model,
|
|
||||||
input_embedding=embed_tokens,
|
input_embedding=embed_tokens,
|
||||||
|
multimodal_model=multimodal_model,
|
||||||
data_embedding_func_mapping=data_embedding_funcs,
|
data_embedding_func_mapping=data_embedding_funcs,
|
||||||
placeholder_tokens=placeholder_tokens,
|
placeholder_tokens=placeholder_tokens,
|
||||||
use_deepstack=use_deepstack,
|
use_deepstack=use_deepstack,
|
||||||
)
|
)
|
||||||
|
|
||||||
# add for qwen3_vl deepstack
|
# add for qwen3_vl deepstack
|
||||||
if use_deepstack:
|
if use_deepstack:
|
||||||
kwargs["input_deepstack_embeds"] = other_info["input_deepstack_embeds"]
|
kwargs["input_deepstack_embeds"] = other_info["input_deepstack_embeds"]
|
||||||
|
|||||||
@@ -726,6 +726,7 @@ class TokenizerManager(TokenizerCommunicatorMixin, TokenizerManagerMultiItemMixi
|
|||||||
img_data=obj.image_data,
|
img_data=obj.image_data,
|
||||||
mm_processor=self.mm_processor,
|
mm_processor=self.mm_processor,
|
||||||
prompt=(input_text or input_ids),
|
prompt=(input_text or input_ids),
|
||||||
|
need_wait_for_image=obj.need_wait_for_image,
|
||||||
)
|
)
|
||||||
if mm_inputs is None:
|
if mm_inputs is None:
|
||||||
mm_inputs: Dict = await self.mm_data_processor.process(
|
mm_inputs: Dict = await self.mm_data_processor.process(
|
||||||
@@ -735,6 +736,20 @@ class TokenizerManager(TokenizerCommunicatorMixin, TokenizerManagerMultiItemMixi
|
|||||||
request_obj=obj,
|
request_obj=obj,
|
||||||
max_req_input_len=self.max_req_input_len,
|
max_req_input_len=self.max_req_input_len,
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
self.server_args.language_only
|
||||||
|
and self.server_args.encoder_transfer_backend == "zmq_to_scheduler"
|
||||||
|
and not obj.need_wait_for_image
|
||||||
|
):
|
||||||
|
# In language_only mode with zmq_to_scheduler, if we didn't dispatch
|
||||||
|
# to encoder (e.g., only one image), process locally like non-language_only mode
|
||||||
|
mm_inputs: Dict = await self.mm_data_processor.process(
|
||||||
|
image_data=obj.image_data,
|
||||||
|
audio_data=obj.audio_data,
|
||||||
|
input_text_or_ids=(input_text or input_ids),
|
||||||
|
request_obj=obj,
|
||||||
|
max_req_input_len=self.max_req_input_len,
|
||||||
|
)
|
||||||
|
|
||||||
if mm_inputs and "input_ids" in mm_inputs:
|
if mm_inputs and "input_ids" in mm_inputs:
|
||||||
input_ids = mm_inputs["input_ids"]
|
input_ids = mm_inputs["input_ids"]
|
||||||
@@ -2294,16 +2309,60 @@ class TokenizerManager(TokenizerCommunicatorMixin, TokenizerManagerMultiItemMixi
|
|||||||
)
|
)
|
||||||
time_stats.set_created_time(created_time)
|
time_stats.set_created_time(created_time)
|
||||||
|
|
||||||
|
def _should_dispatch_to_encoder(
|
||||||
|
self, obj: Union[GenerateReqInput, EmbeddingReqInput]
|
||||||
|
) -> bool:
|
||||||
|
"""Check if the request should be dispatched to encoder for processing.
|
||||||
|
|
||||||
|
Returns True if the request should be dispatched to encoder (multiple multimodal items),
|
||||||
|
False if it should be processed locally (single multimodal item or no multimodal items).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: The request input object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if should dispatch to encoder, False otherwise
|
||||||
|
"""
|
||||||
|
if obj.batch_size > 1:
|
||||||
|
logger.warning(
|
||||||
|
"Batch request (batch_size=%d) is not supported in EPD disaggregation mode; skipping encoder dispatch.",
|
||||||
|
obj.batch_size,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
if not isinstance(obj, GenerateReqInput) or not obj.contains_mm_input():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Count image / video / audio items for dispatch threshold
|
||||||
|
def _count_mm_items(data):
|
||||||
|
return (
|
||||||
|
len(data) if isinstance(data, list) else (1 if data is not None else 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
total_mm_items = (
|
||||||
|
_count_mm_items(getattr(obj, "image_data", None))
|
||||||
|
+ _count_mm_items(getattr(obj, "video_data", None))
|
||||||
|
+ _count_mm_items(getattr(obj, "audio_data", None))
|
||||||
|
)
|
||||||
|
return total_mm_items >= envs.SGLANG_ENCODER_DISPATCH_MIN_ITEMS.get()
|
||||||
|
|
||||||
def _handle_epd_disaggregation_encode_request(
|
def _handle_epd_disaggregation_encode_request(
|
||||||
self, obj: Union[GenerateReqInput, EmbeddingReqInput]
|
self, obj: Union[GenerateReqInput, EmbeddingReqInput]
|
||||||
):
|
):
|
||||||
"""Handle EPD-disaggregation mode encoding request."""
|
"""Handle EPD-disaggregation mode encoding request."""
|
||||||
if (
|
if isinstance(obj, GenerateReqInput) and obj.contains_mm_input():
|
||||||
isinstance(obj, GenerateReqInput)
|
# dispatch to encoder by default
|
||||||
and self.server_args.encoder_transfer_backend == "zmq_to_scheduler"
|
should_dispatch = True
|
||||||
and obj.contains_mm_input()
|
if self.server_args.enable_adaptive_dispatch_to_encoder:
|
||||||
):
|
should_dispatch = self._should_dispatch_to_encoder(obj)
|
||||||
|
|
||||||
|
# Set need_wait_for_image flag based on whether we dispatch to encoder
|
||||||
|
# This flag will be used in _tokenize_one_request to determine processing path
|
||||||
|
if should_dispatch:
|
||||||
|
obj.need_wait_for_image = True
|
||||||
|
if self.server_args.encoder_transfer_backend == "zmq_to_scheduler":
|
||||||
self.mm_receiver.send_encode_request(obj)
|
self.mm_receiver.send_encode_request(obj)
|
||||||
|
else:
|
||||||
|
obj.need_wait_for_image = False
|
||||||
|
|
||||||
def convert_to_span_attrs(
|
def convert_to_span_attrs(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -685,6 +685,7 @@ class ServerArgs:
|
|||||||
language_only: bool = False
|
language_only: bool = False
|
||||||
encoder_transfer_backend: str = ENCODER_TRANSFER_BACKEND_CHOICES[0]
|
encoder_transfer_backend: str = ENCODER_TRANSFER_BACKEND_CHOICES[0]
|
||||||
encoder_urls: List[str] = dataclasses.field(default_factory=list)
|
encoder_urls: List[str] = dataclasses.field(default_factory=list)
|
||||||
|
enable_adaptive_dispatch_to_encoder: bool = False
|
||||||
|
|
||||||
# For model weight update and weight loading
|
# For model weight update and weight loading
|
||||||
custom_weight_loader: Optional[List[str]] = None
|
custom_weight_loader: Optional[List[str]] = None
|
||||||
@@ -5279,6 +5280,12 @@ class ServerArgs:
|
|||||||
default=[],
|
default=[],
|
||||||
help="List of encoder server urls.",
|
help="List of encoder server urls.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--enable-adaptive-dispatch-to-encoder",
|
||||||
|
default=ServerArgs.enable_adaptive_dispatch_to_encoder,
|
||||||
|
action="store_true",
|
||||||
|
help="When enabled, adaptively dispatch: multi-image requests go to encoder in language_only epd mode, single-image requests are processed locally.",
|
||||||
|
)
|
||||||
|
|
||||||
# Custom weight loader
|
# Custom weight loader
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|||||||
Reference in New Issue
Block a user