diff --git a/autotest/interface/restful/test_restful_chat_completions_v1.py b/autotest/interface/restful/test_restful_chat_completions_v1.py index f5abc0ca1d..9a209d8997 100644 --- a/autotest/interface/restful/test_restful_chat_completions_v1.py +++ b/autotest/interface/restful/test_restful_chat_completions_v1.py @@ -44,10 +44,10 @@ def test_get_model(self, config, backend, model_case): def test_encode_s1(self, backend, model_case): api_client = APIClient(BASE_URL) input_ids1, length1 = api_client.encode('Hi, pls intro yourself') - input_ids2, length2 = api_client.encode('Hi, pls intro yourself', add_bos=False) + input_ids2, length2 = api_client.encode('Hi, pls intro yourself') input_ids3, length3 = api_client.encode('Hi, pls intro yourself', do_preprocess=True) - input_ids4, length4 = api_client.encode('Hi, pls intro yourself', do_preprocess=True, add_bos=False) - input_ids5, length5 = api_client.encode('Hi, pls intro yourself' * 100, add_bos=False) + input_ids4, length4 = api_client.encode('Hi, pls intro yourself', do_preprocess=True) + input_ids5, length5 = api_client.encode('Hi, pls intro yourself' * 100) assert len(input_ids1) == length1 and length1 > 0 assert len(input_ids2) == length2 and length2 > 0 @@ -64,10 +64,10 @@ def test_encode_s1(self, backend, model_case): def test_encode(self, backend, model_case): api_client = APIClient(BASE_URL) input_ids1, length1 = api_client.encode('Hi, pls intro yourself') - input_ids2, length2 = api_client.encode('Hi, pls intro yourself', add_bos=False) + input_ids2, length2 = api_client.encode('Hi, pls intro yourself') input_ids3, length3 = api_client.encode('Hi, pls intro yourself', do_preprocess=True) - input_ids4, length4 = api_client.encode('Hi, pls intro yourself', do_preprocess=True, add_bos=False) - input_ids5, length5 = api_client.encode('Hi, pls intro yourself' * 100, add_bos=False) + input_ids4, length4 = api_client.encode('Hi, pls intro yourself', do_preprocess=True) + input_ids5, length5 = api_client.encode('Hi, pls intro yourself' * 100) assert len(input_ids1) == length1 and length1 > 0 assert len(input_ids2) == length2 and length2 > 0 @@ -537,7 +537,7 @@ def test_ignore_eos_streaming(self, backend, model_case): for index in range(0, len(outputList) - 1): assert_chat_completions_stream_return(outputList[index], model_name) response += get_chat_delta_text(outputList[index].get('choices')[0]) - length = api_client.encode(response, add_bos=False)[1] + length = api_client.encode(response)[1] assert outputList[-1].get('choices')[0].get('finish_reason') == 'length' assert length >= 99 and length <= 101 @@ -623,7 +623,7 @@ def __test_max_tokens_streaming_or_max_completion_tokens_streaming( for index in range(0, len(outputList) - 1): assert_chat_completions_stream_return(outputList[index], model_name) response += get_chat_delta_text(outputList[index].get('choices')[0]) - length = api_client.encode(response, add_bos=False)[1] + length = api_client.encode(response)[1] assert outputList[-1].get('choices')[0].get('finish_reason') == 'length' assert length == 5 or length == 6 @@ -676,7 +676,7 @@ def test_logprobs_streaming(self, backend, model_case): for index in range(0, len(outputList) - 1): assert_chat_completions_stream_return(outputList[index], model_name, check_logprobs=True, logprobs_num=10) response += get_chat_delta_text(outputList[index].get('choices')[0]) - length = api_client.encode(response, add_bos=False)[1] + length = api_client.encode(response)[1] assert outputList[-1].get('choices')[0].get('finish_reason') == 'length' assert length == 5 or length == 6 @@ -1026,7 +1026,7 @@ def test_max_tokens_streaming(self, backend, model_case): assert_chat_completions_stream_return(outputList[index], model_name) response += get_chat_delta_text(outputList[index].get('choices')[0]) api_client = APIClient(BASE_URL) - length = api_client.encode(response, add_bos=False)[1] + length = api_client.encode(response)[1] assert outputList[-1].get('choices')[0].get('finish_reason') == 'length' assert length == 5 or length == 6 @@ -1080,7 +1080,7 @@ def test_logprobs_streaming(self, backend, model_case): assert_chat_completions_stream_return(outputList[index], model_name, check_logprobs=True, logprobs_num=10) response += get_chat_delta_text(outputList[index].get('choices')[0]) api_client = APIClient(BASE_URL) - length = api_client.encode(response, add_bos=False)[1] + length = api_client.encode(response)[1] assert outputList[-1].get('choices')[0].get('finish_reason') == 'length' assert length == 5 or length == 6 diff --git a/autotest/interface/restful/test_restful_completions_v1.py b/autotest/interface/restful/test_restful_completions_v1.py index bc19acd62d..443e289ee4 100644 --- a/autotest/interface/restful/test_restful_completions_v1.py +++ b/autotest/interface/restful/test_restful_completions_v1.py @@ -28,10 +28,10 @@ def test_encode(self, backend, model_case): print(f'[test_encode] backend={backend!r} model_case={model_case!r}') api_client = APIClient(BASE_URL) input_ids1, length1 = api_client.encode('Hi, pls intro yourself') - input_ids2, length2 = api_client.encode('Hi, pls intro yourself', add_bos=False) + input_ids2, length2 = api_client.encode('Hi, pls intro yourself') input_ids3, length3 = api_client.encode('Hi, pls intro yourself', do_preprocess=True) - input_ids4, length4 = api_client.encode('Hi, pls intro yourself', do_preprocess=True, add_bos=False) - input_ids5, length5 = api_client.encode('Hi, pls intro yourself' * 100, add_bos=False) + input_ids4, length4 = api_client.encode('Hi, pls intro yourself', do_preprocess=True) + input_ids5, length5 = api_client.encode('Hi, pls intro yourself' * 100) assert len(input_ids1) == length1 and length1 > 0 assert len(input_ids2) == length2 and length2 > 0 assert len(input_ids3) == length3 and length3 > 0 diff --git a/autotest/interface/restful/test_restful_generate.py b/autotest/interface/restful/test_restful_generate.py index 1202f680d5..1d379baaa3 100644 --- a/autotest/interface/restful/test_restful_generate.py +++ b/autotest/interface/restful/test_restful_generate.py @@ -944,7 +944,7 @@ def test_skip_special_tokens(self, config): def test_stop_token_ids(self): print(f'\n[Model: {self.model_name}] Running stop_token_ids test') api_client = APIClient(BASE_URL) - input_ids1, length1 = api_client.encode('.', add_bos=False) + input_ids1, length1 = api_client.encode('.') print(f'input_ids1={input_ids1}, length1={length1}') payload = { diff --git a/benchmark/profile_throughput.py b/benchmark/profile_throughput.py index 6e098bb55e..789b405f47 100644 --- a/benchmark/profile_throughput.py +++ b/benchmark/profile_throughput.py @@ -179,8 +179,6 @@ async def _inference(self, req_queue: Queue, session_id: int, temperature: float top_p=top_p, top_k=top_k, ignore_eos=True), - sequence_start=True, - sequence_end=True, stream_output=stream_output) try: async for outputs in generator: @@ -195,10 +193,6 @@ async def _inference(self, req_queue: Queue, session_id: int, temperature: float finally: await generator.aclose() - # for pytorch engine to restart a session - if self.backend == 'pytorch': - await model_inst.async_end(session_id) - self.pbar.update(1) session_id += concurrency diff --git a/lmdeploy/cli/chat.py b/lmdeploy/cli/chat.py index 099f0825cc..07af358dcb 100644 --- a/lmdeploy/cli/chat.py +++ b/lmdeploy/cli/chat.py @@ -16,9 +16,6 @@ def input_prompt(): def build_pipe(model_path, backend, trust_remote_code=False, **kwargs): engine_config = None - if kwargs.get('enable_prefix_caching', False): - print('interactive chat cannot be used when prefix caching is enabled') - exit(-1) if backend == 'turbomind': engine_config = TurbomindEngineConfig() for key, value in kwargs.items(): @@ -86,7 +83,7 @@ def main(model_path, backend, trust_remote_code=False, **kwargs): quit = True break if prompt == 'end': - sess.close() + sess.reset() break if prompt == 'exit': quit = True diff --git a/lmdeploy/messages.py b/lmdeploy/messages.py index 28a59739a6..1f7b3c14ea 100644 --- a/lmdeploy/messages.py +++ b/lmdeploy/messages.py @@ -529,7 +529,6 @@ class ResponseType(enum.Enum): INPUT_LENGTH_ERROR = enum.auto() INTERNAL_ENGINE_ERROR = enum.auto() CANCEL = enum.auto() - PREFIX_CACHE_CONFLICT_INTERACTIVE_MODE = enum.auto() NO_QUEUE = enum.auto() diff --git a/lmdeploy/model.py b/lmdeploy/model.py index d2394fec4c..f2cdbb8d6c 100644 --- a/lmdeploy/model.py +++ b/lmdeploy/model.py @@ -140,33 +140,26 @@ def __init__(self, self.stop_words = stop_words self.capability = capability - def get_prompt(self, prompt, sequence_start=True): + def get_prompt(self, prompt): """Return the prompt that is concatenated with other elements in the chat template. Args: prompt (str): user's input prompt - sequence_start (bool): indicator for the first round chat of a - session sequence Returns: str: the concatenated prompt """ if self.capability == 'completion': return prompt - if sequence_start: - # None is different from '' - if self.meta_instruction is not None: - return f'{self.system}{self.meta_instruction}{self.eosys}' \ - f'{self.user}{prompt}{self.eoh}' \ - f'{self.assistant}' - else: - return f'{self.user}{prompt}{self.eoh}' \ - f'{self.assistant}' - else: - return f'{self.separator}{self.user}{prompt}{self.eoh}' \ - f'{self.assistant}' - - def messages2prompt(self, messages, sequence_start=True, **kwargs): + # None is different from '' + if self.meta_instruction is not None: + return f'{self.system}{self.meta_instruction}{self.eosys}' \ + f'{self.user}{prompt}{self.eoh}' \ + f'{self.assistant}' + return f'{self.user}{prompt}{self.eoh}' \ + f'{self.assistant}' + + def messages2prompt(self, messages, **kwargs): """Return the prompt that is concatenated with other elements in the chat template. @@ -176,11 +169,11 @@ def messages2prompt(self, messages, sequence_start=True, **kwargs): str: the concatenated prompt """ if isinstance(messages, str): - return self.get_prompt(messages, sequence_start) + return self.get_prompt(messages) box_map = dict(user=self.user, assistant=self.assistant, system=self.system, tool=self.tool) eox_map = dict(user=self.eoh, assistant=self.eoa + self.separator, system=self.eosys, tool=self.eotool) ret = '' - if self.meta_instruction is not None and sequence_start: + if self.meta_instruction is not None: if len(messages) and messages[0]['role'] != 'system': ret += f'{self.system}{self.meta_instruction}{self.eosys}' for message in messages: @@ -261,15 +254,15 @@ def __init__( stop_words=stop_words, **kwargs) - def get_prompt(self, prompt, sequence_start=True): + def get_prompt(self, prompt): if self.capability == 'chat': - return super().get_prompt(prompt, sequence_start)[:-1] - return super().get_prompt(prompt, sequence_start) + return super().get_prompt(prompt)[:-1] + return super().get_prompt(prompt) - def messages2prompt(self, messages, sequence_start=True, **kwargs): + def messages2prompt(self, messages, **kwargs): if isinstance(messages, str): - return self.get_prompt(messages, sequence_start) - return super().messages2prompt(messages, sequence_start, **kwargs)[:-1] + return self.get_prompt(messages) + return super().messages2prompt(messages, **kwargs)[:-1] @classmethod def match(cls, model_path: str, **kwargs) -> str | None: @@ -426,11 +419,11 @@ def __init__(self, meta_instruction='', suffix_first=False, stop_words=None, **k if self.stop_words is None: self.stop_words = [''] - def get_prompt(self, prompt, sequence_start=True): + def get_prompt(self, prompt): if self.capability == 'infilling': return self._infill_prompt(prompt) elif self.capability == 'chat': - return super().get_prompt(prompt, sequence_start) + return super().get_prompt(prompt) else: # python speicalist return prompt @@ -466,7 +459,7 @@ def __init__(self, user='问:', eoh='\n\n', assistant='答:', eoa='\n\n', ** self._eoa = eoa self.count = 0 - def get_prompt(self, prompt, sequence_start=True): + def get_prompt(self, prompt): """Get prompt.""" # need more check # https://github.com/THUDM/ChatGLM2-6B/issues/48 @@ -477,10 +470,10 @@ def get_prompt(self, prompt, sequence_start=True): ret += f'{self._assistant}' return ret - def messages2prompt(self, messages, sequence_start=True, **kwargs): + def messages2prompt(self, messages, **kwargs): """Message to prompt.""" if isinstance(messages, str): - return self.get_prompt(messages, sequence_start) + return self.get_prompt(messages) ret = '' count = 0 for message in messages: @@ -539,15 +532,15 @@ class InternVLZH(BaseChatTemplate): def __init__(self, user=': ', eoh=' ', assistant=': ', eoa='', **kwargs): super().__init__(user=user, eoh=eoh, assistant=assistant, eoa=eoa, **kwargs) - def get_prompt(self, prompt, sequence_start=True): + def get_prompt(self, prompt): if self.capability == 'chat': - return super().get_prompt(prompt, sequence_start)[:-1] - return super().get_prompt(prompt, sequence_start) + return super().get_prompt(prompt)[:-1] + return super().get_prompt(prompt) - def messages2prompt(self, messages, sequence_start=True, **kwargs): + def messages2prompt(self, messages, **kwargs): if isinstance(messages, str): - return self.get_prompt(messages, sequence_start) - return super().messages2prompt(messages, sequence_start, **kwargs)[:-1] + return self.get_prompt(messages) + return super().messages2prompt(messages, **kwargs)[:-1] @classmethod def match(cls, model_path: str, **kwargs) -> str | None: @@ -581,15 +574,15 @@ def __init__( eoa=eoa, **kwargs) - def get_prompt(self, prompt, sequence_start=True): + def get_prompt(self, prompt): if self.capability == 'chat': - return super().get_prompt(prompt, sequence_start)[:-1] - return super().get_prompt(prompt, sequence_start) + return super().get_prompt(prompt)[:-1] + return super().get_prompt(prompt) - def messages2prompt(self, messages, sequence_start=True, **kwargs): + def messages2prompt(self, messages, **kwargs): if isinstance(messages, str): - return self.get_prompt(messages, sequence_start) - return super().messages2prompt(messages, sequence_start, **kwargs)[:-1] + return self.get_prompt(messages) + return super().messages2prompt(messages, **kwargs)[:-1] @classmethod def match(cls, model_path: str, **kwargs) -> str | None: @@ -622,13 +615,13 @@ def __init__(self, eoa=eoa, **kwargs) - def get_prompt(self, prompt, sequence_start=True): - return super().get_prompt(prompt, sequence_start)[:-1] + def get_prompt(self, prompt): + return super().get_prompt(prompt)[:-1] - def messages2prompt(self, messages, sequence_start=True, **kwargs): + def messages2prompt(self, messages, **kwargs): if isinstance(messages, str): - return self.get_prompt(messages, sequence_start) - return super().messages2prompt(messages, sequence_start, **kwargs)[:-1] + return self.get_prompt(messages) + return super().messages2prompt(messages, **kwargs)[:-1] @classmethod def match(cls, model_path: str, **kwargs) -> str | None: @@ -703,7 +696,6 @@ def __init__(self, model_path: str = '', trust_remote_code: bool = False, **kwar # Verify if the model can perform apply_chat_template with different roles. self.user_start, self.user_end, _, _ = self._user_instruction() self.assistant_start, self.assistant_end, _, _ = self._assistant_instruction() - _, _, self.sentinel_system_messages, self.sentinel_system_prompt = self._system_instruction() self.stop_words = [] if hasattr(self.tokenizer, 'eos_token') and self.tokenizer.eos_token is not None: self.stop_words.append(self.tokenizer.eos_token) @@ -716,11 +708,11 @@ def __init__(self, model_path: str = '', trust_remote_code: bool = False, **kwar except Exception as e: raise ValueError(f'Try apply_chat_template failed: {e}') - def get_prompt(self, prompt, sequence_start=True, **kwargs): + def get_prompt(self, prompt, **kwargs): messages = [{'role': 'user', 'content': prompt}] - return self.messages2prompt(messages, sequence_start, **kwargs) + return self.messages2prompt(messages, **kwargs) - def messages2prompt(self, messages, sequence_start=True, **kwargs): + def messages2prompt(self, messages, **kwargs): if isinstance(messages, str): messages = [{'role': 'user', 'content': messages}] assert all(isinstance(m, dict) and 'role' in m and 'content' in m for m in messages), \ @@ -733,21 +725,10 @@ def messages2prompt(self, messages, sequence_start=True, **kwargs): if 'reasoning_effort' in kwargs and kwargs['reasoning_effort'] is None: kwargs.pop('reasoning_effort') add_generation_prompt = messages[-1]['role'] != 'assistant' - if sequence_start: - prompt = self.tokenizer.apply_chat_template(messages, - tokenize=False, - add_generation_prompt=add_generation_prompt, - **kwargs) - else: - # Use a sentinel position to avoid the influence of default system role in the tokenizer's chat template - # in interactive chat mode - messages = self.sentinel_system_messages + messages if self.sentinel_system_messages else messages - prompt = self.tokenizer.apply_chat_template(messages, - tokenize=False, - add_generation_prompt=add_generation_prompt, - **kwargs) - # Remove the sentinel part. - prompt = prompt[len(self.sentinel_system_prompt):] if len(self.sentinel_system_prompt) > 0 else prompt + prompt = self.tokenizer.apply_chat_template(messages, + tokenize=False, + add_generation_prompt=add_generation_prompt, + **kwargs) if messages[-1]['role'] == 'assistant' and len(self.assistant_end) > 0: prompt = prompt[:-len(self.assistant_end)] # prefix of response to let the model complete the response if self.is_gpt_oss and not kwargs.get('tools'): @@ -783,22 +764,6 @@ def _assistant_instruction(self): assistant_end = prompt[assistant_pos + len('sentinel'):] return assistant_start, assistant_end, messages, prompt - def _system_instruction(self): - """Extract system message template markers from the tokenizer's chat - template.""" - messages = [{'role': 'system', 'content': 'sentinel'}] - try: - prompt = self.tokenizer.apply_chat_template(messages, tokenize=False) - system_pos = prompt.find('sentinel') - if system_pos == -1: - return None, None, [], self.tokenizer.bos_token or '' - system_start = prompt[:system_pos] - system_end = prompt[system_pos + len('sentinel'):] - return system_start, system_end, messages, prompt - except Exception: - # Some models, such as google/gemma-2-2b-it, do not support a system role in the message structure. - return None, None, [], self.tokenizer.bos_token or '' - @classmethod def match(cls, model_path: str, trust_remote_code: bool = False) -> str | None: try: diff --git a/lmdeploy/pipeline.py b/lmdeploy/pipeline.py index 23d80fd9db..73ac2b4505 100644 --- a/lmdeploy/pipeline.py +++ b/lmdeploy/pipeline.py @@ -177,6 +177,26 @@ def close(self): self.internal_thread.close() self.async_engine.close() + @staticmethod + def _append_prompt_to_messages(messages: list[dict], prompt) -> None: + """Append a prompt (str, tuple, or openai-style messages) to + messages.""" + formatted = MultimodalProcessor.format_prompts(prompt) + if isinstance(formatted[0], str): + messages.append(dict(role='user', content=formatted[0])) + elif isinstance(formatted[0], list): + messages.extend(formatted[0]) + else: + messages.extend(formatted) + + @staticmethod + def _history_to_messages(history: list[tuple]) -> list[dict]: + messages = [] + for user, assistant in history: + Pipeline._append_prompt_to_messages(messages, user) + messages.append(dict(role='assistant', content=assistant)) + return messages + def chat(self, prompt: str | tuple[str, Image | list[Image]], session=None, @@ -201,18 +221,15 @@ def chat(self, session = self.session_mgr.get() session.update(prompt=prompt, response=None) - prompt = MultimodalProcessor.format_prompts(prompt) + messages = self._history_to_messages(session.history) + self._append_prompt_to_messages(messages, prompt) - sequence_start = session.step == 0 - generator = self.stream_infer(prompts=prompt, + generator = self.stream_infer(prompts=messages, sessions=session, gen_config=gen_config, stream_response=stream_response, adapter_name=adapter_name, multiplex=True, - sequence_start=sequence_start, - sequence_end=False, - step=session.step, **kwargs) def _gen(): @@ -226,7 +243,6 @@ def _gen(): raise else: session.response = resp - session.step += resp.generate_token_len + resp.input_token_len session.history.append((session.prompt, resp.text)) if stream_response: diff --git a/lmdeploy/pytorch/engine/engine.py b/lmdeploy/pytorch/engine/engine.py index 81f0eb0ce0..2b2c9d4b2e 100644 --- a/lmdeploy/pytorch/engine/engine.py +++ b/lmdeploy/pytorch/engine/engine.py @@ -22,7 +22,7 @@ from ..adapter.adapter import AdapterManager from ..config import CacheConfig, ModelConfig -from ..messages import MessageStatus, SchedulerSequence, UpdateTokenMode +from ..messages import MessageStatus, SchedulerSequence from ..multimodal.data_type import ensure_multimodal_content_hashes from ..paging import Scheduler from ..strategies import build_strategy_factory @@ -331,10 +331,14 @@ def _on_stop_session(self, reqs: list[Request], **kwargs): if _resp is not None: stopped_resp_ids.add(id(_resp)) self.scheduler.stop_session(session_id) + msgs = list(session.sequences.values()) + preserve = msgs and msgs[0].preserve_cache for seq in session.sequences.values(): _resp: Response = getattr(seq, 'resp', None) if _resp is not None and id(_resp) not in stopped_resp_ids: self.req_manager.reject_request(_resp) + if not preserve: + self.end_session(session_id) resp_type = ResponseType.SUCCESS if resp: self._response(req.resp, resp_type) @@ -451,30 +455,19 @@ def __update_max_new_tokens(msg): continue # TODO: support 1 session n sequence sampling_param = req.data['sampling_param'] - if len(sess.sequences) == 0: - migration_request = req.data.get('migration_request') - assert len(req.data['token_ids']) > 0, ('Empty input is not allowed.') - sess.add_sequence(req.data['token_ids'], - sampling_param=sampling_param, - adapter_name=req.data['adapter_name'], - multimodals=req.data.get('input_multimodals'), - input_embeddings=req.data.get('input_embeddings', ), - migration_request=migration_request, - resp_cache=req.data.get('with_cache'), - preserve_cache=req.data.get('preserve_cache')) - msg = next(iter(sess.sequences.values())) - if migration_request: - self.migration_event.set() - else: - msg = next(iter(sess.sequences.values())) - msg.update_token_ids( - req.data['token_ids'], - multimodals=req.data.get('input_multimodals'), - embeddings=req.data.get('input_embeddings'), - mode=UpdateTokenMode.INPUTS, - ) - msg.sampling_param = sampling_param - msg.state.activate() + assert len(req.data['token_ids']) > 0, ('Empty input is not allowed.') + migration_request = req.data.get('migration_request') + sess.add_sequence(req.data['token_ids'], + sampling_param=sampling_param, + adapter_name=req.data['adapter_name'], + multimodals=req.data.get('input_multimodals'), + input_embeddings=req.data.get('input_embeddings', ), + migration_request=migration_request, + resp_cache=req.data.get('with_cache'), + preserve_cache=req.data.get('preserve_cache')) + msg = next(iter(sess.sequences.values())) + if migration_request: + self.migration_event.set() __update_max_new_tokens(msg) msg.resp = req.resp diff --git a/lmdeploy/pytorch/engine/engine_loop.py b/lmdeploy/pytorch/engine/engine_loop.py index 55b3aad2a8..7851033ad1 100644 --- a/lmdeploy/pytorch/engine/engine_loop.py +++ b/lmdeploy/pytorch/engine/engine_loop.py @@ -115,6 +115,7 @@ class EngineLoop: """Engine loop manager should be created in an async context.""" def __init__(self, + engine: 'Engine', req_manager: 'RequestManager', scheduler: 'Scheduler', executor: 'ExecutorBase', @@ -122,6 +123,7 @@ def __init__(self, inputs_maker: 'InputsMakerAsync', config: EngineLoopConfig, engine_conn: Optional['EngineP2PConnection'] = None): + self.engine = engine self.req_manager = req_manager self.scheduler = scheduler self.executor = executor @@ -224,6 +226,13 @@ def _send_resp(self, out: InferOutput): routed_experts=out.routed_experts, logprobs=logprobs, ce_loss=out.ce_loss)) + if out.finish: + session_id = out.session_id + session = self.scheduler.sessions.get(session_id) + if session is not None: + msgs = list(session.sequences.values()) + if not msgs or not msgs[0].preserve_cache: + self.engine.end_session(session_id) @staticmethod def _update_logprobs(step_outputs: list[InferOutput]): @@ -668,6 +677,7 @@ def build_engine_loop(engine: 'Engine'): config = EngineLoopConfig.from_engine(engine) inputs_maker = build_inputs_maker(engine) return EngineLoop( + engine=engine, req_manager=engine.req_manager, scheduler=engine.scheduler, executor=engine.executor, diff --git a/lmdeploy/serve/anthropic/adapter.py b/lmdeploy/serve/anthropic/adapter.py index 6975c7c8ad..d4bcc78515 100644 --- a/lmdeploy/serve/anthropic/adapter.py +++ b/lmdeploy/serve/anthropic/adapter.py @@ -363,8 +363,8 @@ def count_input_tokens(async_engine, messages: list[dict[str, str]]) -> int: """Approximate Anthropic token counting using LMDeploy tokenizer/template.""" - prompt = async_engine.chat_template.messages2prompt(messages, sequence_start=True) - token_ids = async_engine.tokenizer.encode(prompt, add_bos=True) + prompt = async_engine.chat_template.messages2prompt(messages) + token_ids = async_engine.tokenizer.encode(prompt) return len(token_ids) diff --git a/lmdeploy/serve/anthropic/endpoints/messages.py b/lmdeploy/serve/anthropic/endpoints/messages.py index 1f5b17a54b..40f0c22851 100644 --- a/lmdeploy/serve/anthropic/endpoints/messages.py +++ b/lmdeploy/serve/anthropic/endpoints/messages.py @@ -177,8 +177,6 @@ async def create_message(request: MessagesRequest, raw_request: Request): gen_config=to_generation_config(request), tools=parsed_request.tools, stream_response=True, - sequence_start=True, - sequence_end=True, do_preprocess=False if resolved_input_ids is not None else True, adapter_name=adapter_name, input_ids=resolved_input_ids, diff --git a/lmdeploy/serve/core/async_engine.py b/lmdeploy/serve/core/async_engine.py index 68d4c51f85..a9a6a98cc0 100644 --- a/lmdeploy/serve/core/async_engine.py +++ b/lmdeploy/serve/core/async_engine.py @@ -238,7 +238,7 @@ def _if_session_stale(self, session: Session, logger.info(f'[generate] drop stale session {session.session_id} ' f'(session.epoch={epoch}, async_engine.epoch={self.epoch})') return GenOut(response='', - history_token_len=session.step, + history_token_len=0, input_token_len=input_token_len, generate_token_len=0, finish_reason='abort', @@ -403,7 +403,7 @@ def wakeup(self, tags: list[str] | None = None): self.sleeping_tags = self.sleeping_tags - set(tags) self.is_sleeping = bool(self.sleeping_tags) - def _determine_gen_config(self, session, input_ids, gen_config: GenerationConfig | None = None) -> GenerationConfig: + def _determine_gen_config(self, input_ids, gen_config: GenerationConfig | None = None) -> GenerationConfig: """Determine the generation configuration.""" gen_config = deepcopy(gen_config) or GenerationConfig() gen_config.convert_stop_bad_words_to_ids(self.tokenizer) @@ -415,14 +415,13 @@ def _determine_gen_config(self, session, input_ids, gen_config: GenerationConfig # avoid unnecessary process gen_config.temperature = 1.0 gen_config.repetition_penalty = 1.0 - # set random if it is not set and sequence_start is True - elif gen_config.random_seed is None and session.step == 0: + elif gen_config.random_seed is None: gen_config.random_seed = random.getrandbits(64) if gen_config.n > 1: logger.warning(f'n({gen_config.n}) > 1 hasn\'t been supported yet. Fallback to 1') gen_config.n = 1 if gen_config.max_new_tokens is None: - gen_config.max_new_tokens = max(0, self.session_len - session.step - len(input_ids)) + gen_config.max_new_tokens = max(0, self.session_len - len(input_ids)) return gen_config @asynccontextmanager @@ -430,23 +429,12 @@ async def safe_run(self, handle, session, **kwargs): generator = handle.async_stream_infer(session.session_id, **kwargs) async def cleanup_after_exception(): - # Use asyncio.shield to protect cleanup coroutines from being cancelled. - # When a task is in cancelling state, bare `await` raises CancelledError - # immediately. shield ensures the inner coroutine runs to completion. try: await asyncio.shield(handle.async_cancel(session.session_id)) except asyncio.CancelledError: pass except Exception: logger.exception(f'[safe_run] session {session.session_id} async_cancel failed.') - if self.backend == 'pytorch': - logger.info(f'[safe_run] session {session.session_id} ending session') - try: - await asyncio.shield(handle.async_end(session.session_id)) - except asyncio.CancelledError: - pass - except Exception: - logger.exception(f'[safe_run] session {session.session_id} async_end failed.') try: metrics_processor.increase_api_routed_requests() @@ -483,9 +471,6 @@ async def generate( tools: list[object] | None = None, reasoning_effort: Literal['low', 'medium', 'high'] | None = None, stream_response: bool = True, - sequence_start: bool = True, - sequence_end: bool = True, # no interactive mode by default - step: int = 0, do_preprocess: bool = True, adapter_name: str | None = None, rewind_stop_tokens: bool = False, @@ -503,9 +488,6 @@ async def generate( gen_config (GenerationConfig | None): a instance of GenerationConfig. Default to None. stream_response (bool): whether return responses streamingly - sequence_start (bool): indicator for starting a sequence - sequence_end (bool): indicator for ending a sequence - step (int): the offset of the k/v cache do_preprocess (bool): whether pre-process the messages. Default to True, which means chat_template will be applied. """ @@ -516,7 +498,7 @@ async def generate( if isinstance(session_id, Session): session = session_id elif isinstance(session_id, int): - session = self.session_mgr.get(session_id, step=step) + session = self.session_mgr.get(session_id) else: raise ValueError(f'Invalid session_id: {session_id}. It should be an instance of Session or an integer.') session_id = session.session_id @@ -524,7 +506,7 @@ async def generate( def remove_session_once(): nonlocal session_removed - if sequence_end and not session_removed: + if not session_removed: self.session_mgr.remove(session) session_removed = True @@ -542,7 +524,6 @@ def remove_session_once(): self.request_logger.log_prompt(session, prompt=prompt) prompt_input = await self.prompt_processor.get_prompt_input(prompt=prompt, do_preprocess=do_preprocess, - sequence_start=sequence_start, adapter_name=adapter_name, tools=tools, reasoning_effort=reasoning_effort, @@ -566,7 +547,7 @@ def remove_session_once(): metrics_processor.increase_failed_requests('error') remove_session_once() yield GenOut(response='in prompt processing error', - history_token_len=session.step, + history_token_len=0, input_token_len=len(input_ids) if input_ids is not None else 0, generate_token_len=0, finish_reason='error', @@ -577,17 +558,14 @@ def remove_session_once(): # Figure out a graceful way to handle the invalid input prompt_input = dict(input_ids=input_ids) - gen_config = self._determine_gen_config(session, input_ids, gen_config=gen_config) + gen_config = self._determine_gen_config(input_ids, gen_config=gen_config) if gen_config.max_new_tokens == 0: logger.info(f'run out of tokens. session={session_id}.') metrics_processor.increase_failed_requests('error') - history_len = session.step - if sequence_end is True and sequence_start is False: - await session.async_close() remove_session_once() yield GenOut(response='', - history_token_len=history_len, + history_token_len=0, input_token_len=len(input_ids), generate_token_len=0, finish_reason='length', @@ -601,18 +579,16 @@ def remove_session_once(): metrics_processor.increase_failed_requests('error') remove_session_once() yield GenOut(response=errmsg, - history_token_len=session.step, + history_token_len=0, input_token_len=len(input_ids), generate_token_len=0, finish_reason='error', token_ids=[]) return logger.info(f'session={session_id}, ' - f'history_tokens={session.step}, ' f'input_tokens={len(input_ids)}, ' f'max_new_tokens={gen_config.max_new_tokens}, ' - f'seq_start={sequence_start}, seq_end={sequence_end}, ' - f'step={step}, prep={do_preprocess}') + f'prep={do_preprocess}') def is_error(status): return status not in [ResponseType.SUCCESS, ResponseType.FINISH, ResponseType.CANCEL] @@ -628,7 +604,6 @@ def is_error(status): remove_session_once() yield stale return - session._remove_on_request_exit = sequence_end async with session.request_handle() as handle: if session.epoch is not None and session.epoch != self.epoch: logger.info(f'[generate] session {session_id} got aborted before starting inference, ' @@ -643,7 +618,6 @@ def is_error(status): token_ids=[]) return token_ids = input_ids.copy() - history_len = session.step input_len = len(input_ids) output_len, gen_len = 0, 0 state = DetokenizeState(input_len) @@ -656,10 +630,7 @@ def is_error(status): **prompt_input, gen_config=gen_config, adapter_name=adapter_name, - stream_output=stream_response, - sequence_start=sequence_start, - sequence_end=sequence_end, - step=history_len) as gen: + stream_output=stream_response) as gen: logger.debug(f'[generate] session {session_id} started') hit_stop_token = 0 req_stats = RequestStats(prompt_tokens=input_len) # per-request stats @@ -699,7 +670,7 @@ def is_error(status): res = token_ids[ids_offset:] out = GenOut(response, - history_len, + 0, input_len, gen_len, finish_reason, @@ -750,7 +721,7 @@ def is_error(status): f'"{finish_reason}", input_tokens ' f'{len(input_ids)}, output_tokens {gen_len}') yield GenOut(response, - session.step, + 0, len(input_ids), gen_len, finish_reason, @@ -767,26 +738,12 @@ def is_error(status): 'reason "error"') metrics_processor.increase_failed_requests('error') yield GenOut(response=f'internal error happened, status code {outputs.status}', - history_token_len=session.step, + history_token_len=0, input_token_len=len(input_ids), generate_token_len=0, finish_reason='error', token_ids=[]) - # update step - if sequence_end: - if self.backend == 'pytorch': - # manually end pytorch session - # note: Using session.async_abort() here results in deadlock - # because it waits for session's _active event to be set, but the event won't be set - # until the session is finished, i.e., session.request_handle() context exits. - await handle.async_end(session.session_id) - remove_session_once() - # if sequence_end: - # if self.backend == 'pytorch': - # # manually end pytorch session. session cannot be ended until session.request_handle() - # # context exits - # await session.async_close() - # self.session_mgr.remove(session) + remove_session_once() def start_loop(self, loop, use_async_api=False): """Start engine loop. @@ -851,9 +808,7 @@ async def async_get_reward_score(self, input_ids: list) -> list[float]: async def async_get_logits(self, input_ids, - sessions: list['Session'] | None = None, - sequence_start: bool = True, - sequence_end: bool = True) -> list[torch.Tensor]: + sessions: list['Session'] | None = None) -> list[torch.Tensor]: assert input_ids and all(isinstance(_, list) for _ in input_ids) assert sessions is None or (len(sessions) == len(input_ids)) @@ -871,15 +826,10 @@ async def _proc(session, i): session=session, input_ids=input_ids[i], gen_config=gen_config, - stream_output=False, - sequence_start=sequence_start, - sequence_end=sequence_end, - step=session.step) as gen: + stream_output=False) as gen: async for outputs in gen: pass logits[i] = outputs.logits[:input_len, :] - if sequence_end and self.backend == 'pytorch': - await handle.async_end(session.session_id) create_sessions = False if sessions is None: @@ -887,7 +837,7 @@ async def _proc(session, i): sessions = [self.session_mgr.get() for _ in range(len(input_ids))] tasks = [_proc(session, i) for i, session in enumerate(sessions)] await asyncio.gather(*tasks) - if sequence_end and create_sessions: + if create_sessions: for session in sessions: self.session_mgr.remove(session) return logits @@ -923,15 +873,10 @@ async def async_get_ppl(self, input_ids: list[int]) -> float: session=session, input_ids=input_ids, gen_config=gen_config, - stream_output=False, - sequence_start=True, - sequence_end=True, - step=session.step) as gen: + stream_output=False) as gen: async for outputs in gen: pass ce_loss = outputs.ce_loss - if self.backend == 'pytorch': - await handle.async_end(session.session_id) finally: self.session_mgr.remove(session) if ce_loss is None: diff --git a/lmdeploy/serve/managers/session_manager.py b/lmdeploy/serve/managers/session_manager.py index 9bb07bb5c4..584be8a6d4 100644 --- a/lmdeploy/serve/managers/session_manager.py +++ b/lmdeploy/serve/managers/session_manager.py @@ -23,33 +23,28 @@ def __init__(self, session_id: int, session_mgr: SessionManager, **kwargs): self.response: Response | None = None self.history: list[tuple[Any, str]] = [] self.gen_config: GenerationConfig | None = None - self.step: int = 0 # Set by api_server to AsyncEngine.epoch when a request binds a session; # generate() drops work if stop_all_session() bumped epoch after bind. self.epoch: int | None = None - # event to wait for the session to be active - self._active: asyncio.Event | None = None self._handle = None # inference instance self._session_mgr: SessionManager = weakref.ref(session_mgr) - self._remove_on_request_exit = False self.update(**kwargs) def update(self, **kwargs): """Update the session.""" self.prompt = kwargs.get('prompt', self.prompt) self.gen_config = kwargs.get('gen_config', self.gen_config) - self.step = kwargs.get('step', self.step) def __repr__(self) -> str: """Return a string representation of the Session object.""" return (f'Session(session_id={self.session_id}, ' - f'step={self.step}, history_len={len(self.history)}, ' + f'history_len={len(self.history)}, ' f'has_response={self.response is not None}, ' f'has_gen_config={self.gen_config is not None})') def __str__(self) -> str: """Return a human-readable string representation of the Session.""" - res = f'Session(id={self.session_id}, step={self.step})' + res = f'Session(id={self.session_id})' if self.history: res += '\nHistory:\n' for user, assistant in self.history: @@ -67,12 +62,9 @@ def reset(self): self.response = None self.history = [] self.gen_config = None - self.step = 0 self.epoch = None - self._active = None self._handle = None self._session_mgr = None - self._remove_on_request_exit = False logger.debug(f'Session {self.session_id} has been reset.') @asynccontextmanager @@ -83,7 +75,6 @@ async def request_handle(self): hnd_pool = self._session_mgr().request_handle_pool self._handle = await hnd_pool.get() - self._active = asyncio.Event() logger.debug(f'[request_handle] session {self.session_id} acquired an instance') try: yield self._handle @@ -101,14 +92,6 @@ async def request_handle(self): if self._handle is not None: hnd_pool.put(self._handle) self._handle = None - # MUST set the signal after releasing the instance to avoid race condition - # refer to async_end method - self._active.set() - if self._remove_on_request_exit and self._session_mgr is not None: - self._remove_on_request_exit = False - session_mgr = self._session_mgr() - if session_mgr is not None: - session_mgr.remove(self) async def async_abort(self): """Abort the session.""" @@ -116,29 +99,14 @@ async def async_abort(self): if self._handle is not None: await self._handle.async_cancel(self.session_id) - async def async_close(self): - """End the session.""" - logger.info(f'[session] Ending session {self.session_id}') - if self._handle is None and self.step == 0: - return - if self._handle is not None: - await self._active.wait() - async with self.request_handle() as handle: - try: - await handle.async_end(self.session_id) - except (Exception, asyncio.CancelledError, GeneratorExit) as e: - logger.exception(f'[async_close] exception caught: {e}') - self.reset() - def abort(self): """Abort the session in sync mode.""" if self._session_mgr is not None: self._run(self.async_abort()).result() def close(self): - """End the session in sync mode.""" - if self._session_mgr is not None: - self._run(self.async_close()).result() + """Reset the session in sync mode.""" + self.reset() def _run(self, coro): assert self._session_mgr is not None, 'Session manager is not initialized' @@ -213,22 +181,6 @@ def __init__(self): self.session_id_generator = itertools.count(0) self.request_handle_pool = None self.loop = None - # user_session_id->session_id. If user specifies - # a session_id when visiting the api_server's endpoint, - # we map the user_session_id to the session_id to keep - # session's id globally identical across different requests. - self.user_session_id_map = {} - # session_id->user_session_id map. - self.session_id_map = {} - - def map_user_session_id(self, user_session_id: int) -> int: - """Map a user_session_id to a session_id.""" - if user_session_id in self.user_session_id_map: - raise ValueError(f'User session id {user_session_id} already exists') - session_id = next(self.session_id_generator) - self.user_session_id_map[user_session_id] = session_id - self.session_id_map[session_id] = user_session_id - return session_id def get(self, session_id: int | None = None, create_if_not_exists: bool = True, **kwargs) -> Session | None: """Get or create a session.""" @@ -260,12 +212,6 @@ async def async_abort_all(self): sessions_without_handle = [sid for sid, session in self.sessions.items() if session._handle is None] for session_id in sessions_without_handle: self.sessions.pop(session_id, None) - user_session_id = self.session_id_map.pop(session_id, None) - if user_session_id is not None: - self.user_session_id_map.pop(user_session_id, None) - - def has(self, session_id): - return session_id in self.sessions def remove(self, session: Session | int | None): """Remove a session and its user mapping. @@ -283,14 +229,9 @@ def remove(self, session: Session | int | None): if current is not None and current is not session: return self.sessions.pop(session_id, None) - user_session_id = self.session_id_map.pop(session_id, None) - if user_session_id is not None: - self.user_session_id_map.pop(user_session_id, None) def clear(self): self.sessions.clear() - self.user_session_id_map.clear() - self.session_id_map.clear() # reset the session id generator self.session_id_generator = itertools.count(0) diff --git a/lmdeploy/serve/openai/api_client.py b/lmdeploy/serve/openai/api_client.py index fbe5eaf8b8..cb2b92a0ce 100644 --- a/lmdeploy/serve/openai/api_client.py +++ b/lmdeploy/serve/openai/api_client.py @@ -67,20 +67,17 @@ def available_models(self): def encode(self, input: str | list[str], - do_preprocess: bool | None = False, - add_bos: bool | None = True): + do_preprocess: bool | None = False): """Encode prompts. Args: input: the prompt to be encoded. In str or list[str] format. do_preprocess: whether do preprocess or not. Default to False. - add_bos: True when it is the beginning of a conversation. False - when it is not. Default to True. Return: (input_ids, length) """ response = requests.post(self.encode_v1_url, headers=self.headers, - json=dict(input=input, do_preprocess=do_preprocess, add_bos=add_bos), + json=dict(input=input, do_preprocess=do_preprocess), stream=False) if hasattr(response, 'text'): output = json_loads(response.text) diff --git a/lmdeploy/serve/openai/api_server.py b/lmdeploy/serve/openai/api_server.py index cbbbcfbc38..51d51daa75 100644 --- a/lmdeploy/serve/openai/api_server.py +++ b/lmdeploy/serve/openai/api_server.py @@ -7,6 +7,7 @@ import os import re import time +import uuid from collections.abc import AsyncGenerator from contextlib import aclosing, asynccontextmanager from functools import partial @@ -113,32 +114,12 @@ class VariableInterface: response_parser_cls: type[ResponseParser] | None = None @classmethod - def create_session(cls, user_session_id: int | None = None) -> Session: + def create_session(cls) -> Session: session_mgr = cls.get_session_manager() - if user_session_id is None or user_session_id == -1: - # user doesn't input session_id, so we need to generate a new one - session = session_mgr.get() - else: - # find the inside session_id by user_session_id, create a new one - # if it doesn't exist and update the user_session_id_map - session_id = session_mgr.map_user_session_id(user_session_id) - session = session_mgr.get(session_id) - # Stamp epoch for ``stop_all_session`` / ``abort_all`` coordination in ``AsyncEngine.generate``. + session = session_mgr.get() session.epoch = cls.async_engine.epoch return session - @classmethod - def find_session(cls, user_session_id: int) -> Session | None: - """Find the session by user_session_id. - - Users cannot access inner session_id directly. - """ - session_mgr = cls.get_session_manager() - session_id = session_mgr.user_session_id_map.get(user_session_id, None) - if session_id is None: - return None - return session_mgr.get(session_id, create_if_not_exists=False) - @classmethod def get_session_manager(cls): return cls.async_engine.session_mgr @@ -415,7 +396,7 @@ async def chat_completions_v1(request: ChatCompletionRequest, raw_request: Reque error_check_ret = check_request(request) if error_check_ret is not None: return error_check_ret - session = VariableInterface.create_session(request.session_id) + session = VariableInterface.create_session() # Resolve input: messages has priority over input_ids/image_data messages_empty = (request.messages is None @@ -534,8 +515,6 @@ async def chat_completions_v1(request: ChatCompletionRequest, raw_request: Reque tools=request.tools, reasoning_effort=request.reasoning_effort, stream_response=True, # always use stream to enable batching - sequence_start=True, - sequence_end=True, do_preprocess=do_preprocess, adapter_name=adapter_name, chat_template_kwargs=chat_template_kwargs or None, @@ -819,15 +798,15 @@ async def completions_v1(request: CompletionRequest, raw_request: Request = None adapter_name = None if model_name != VariableInterface.async_engine.model_name: adapter_name = model_name # got a adapter name - request_id = str(request.session_id) + request_id = str(uuid.uuid4().hex) created_time = int(time.time()) sessions = [] if isinstance(request.prompt, str): request.prompt = [request.prompt] - sessions.append(VariableInterface.create_session(request.session_id)) + sessions.append(VariableInterface.create_session()) elif isinstance(request.prompt, list): - for i in range(len(request.prompt)): - sessions.append(VariableInterface.create_session(i + 1)) + for _ in range(len(request.prompt)): + sessions.append(VariableInterface.create_session()) if isinstance(request.stop, str): request.stop = [request.stop] random_seed = request.seed if request.seed is not None else None @@ -860,8 +839,6 @@ async def completions_v1(request: CompletionRequest, raw_request: Request = None session, gen_config=gen_config, stream_response=True, # always use stream to enable batching - sequence_start=True, - sequence_end=True, do_preprocess=False, adapter_name=adapter_name) generators.append(result_generator) @@ -945,7 +922,7 @@ async def _inner_call(i, generator, session): async for res in cleanup_generator: if await raw_request.is_disconnected(): # Abort the request if the client disconnects. - await VariableInterface.async_engine.stop_session(request.session_id) + await session.async_abort() return create_error_response(HTTPStatus.BAD_REQUEST, 'Client disconnected') final_res = res text += res.response @@ -996,7 +973,7 @@ async def generate(request: GenerateReqInput, raw_request: Request = None): if error_check_ret is not None: return error_check_ret - session = VariableInterface.create_session(request.session_id) + session = VariableInterface.create_session() prompt = request.prompt input_ids = request.input_ids @@ -1041,8 +1018,6 @@ async def generate(request: GenerateReqInput, raw_request: Request = None): input_ids=input_ids, gen_config=gen_config, stream_response=True, # always use stream to enable batching - sequence_start=True, - sequence_end=True, do_preprocess=False, media_io_kwargs=request.media_io_kwargs, mm_processor_kwargs=request.mm_processor_kwargs) @@ -1128,23 +1103,21 @@ async def encode(request: EncodeRequest, raw_request: Request = None): - **input**: the prompt to be encoded. In str or list[str] format. - **do_preprocess**: whether do preprocess or not. Default to False. - - **add_bos**: True when it is the beginning of a conversation. False when it - is not. Default to True. """ - def encode(prompt: str, do_preprocess: bool, add_bos: bool): + def encode(prompt: str, do_preprocess: bool): if do_preprocess: - prompt = VariableInterface.async_engine.chat_template.get_prompt(prompt, sequence_start=add_bos) - input_ids = VariableInterface.async_engine.tokenizer.encode(prompt, add_bos=add_bos) + prompt = VariableInterface.async_engine.chat_template.get_prompt(prompt) + input_ids = VariableInterface.async_engine.tokenizer.encode(prompt) return input_ids if isinstance(request.input, str): - encoded = encode(request.input, request.do_preprocess, request.add_bos) + encoded = encode(request.input, request.do_preprocess) return EncodeResponse(input_ids=encoded, length=len(encoded)) else: encoded, length = [], [] for prompt in request.input: - ids = encode(prompt, request.do_preprocess, request.add_bos) + ids = encode(prompt, request.do_preprocess) encoded.append(ids) length.append(len(ids)) return EncodeResponse(input_ids=encoded, length=length) @@ -1372,22 +1345,10 @@ async def abort_request(request: AbortRequest, raw_request: Request = None): if request.abort_all: await VariableInterface.async_engine.stop_all_session() else: - session = VariableInterface.find_session(request.session_id) - if session is None: - return create_error_response(HTTPStatus.BAD_REQUEST, f'Session {request.session_id} not found.') - await session.async_abort() - session_mgr = VariableInterface.get_session_manager() - session_mgr.remove(session) + return create_error_response(HTTPStatus.BAD_REQUEST, 'Only abort_all is supported.') return Response(status_code=200) -@router.post('/v1/chat/interactive', dependencies=[Depends(validate_json_request)], include_in_schema=False) -async def chat_interactive_v1(request, raw_request: Request = None): - return create_error_response( - HTTPStatus.BAD_REQUEST, 'v1/chat/interactive is deprecated, please launch server with --enable-prefix-cache ' - 'and use /v1/chat/completions instead.') - - def handle_torchrun(): """To disable mmengine logging logic when using torchrun.""" diff --git a/lmdeploy/serve/openai/protocol.py b/lmdeploy/serve/openai/protocol.py index 675cbf5103..8bb7090bd9 100644 --- a/lmdeploy/serve/openai/protocol.py +++ b/lmdeploy/serve/openai/protocol.py @@ -179,7 +179,6 @@ class ChatCompletionRequest(BaseModel): repetition_penalty: float | None = 1.0 repetition_ngram_size: int = Field(default=0, ge=0) repetition_ngram_threshold: int = Field(default=0, ge=0) - session_id: int | None = -1 ignore_eos: bool | None = False skip_special_tokens: bool | None = True spaces_between_special_tokens: bool | None = True @@ -378,7 +377,6 @@ class CompletionRequest(BaseModel): repetition_penalty: float | None = 1.0 repetition_ngram_size: int = Field(default=0, ge=0) repetition_ngram_threshold: int = Field(default=0, ge=0) - session_id: int | None = -1 ignore_eos: bool | None = False skip_special_tokens: bool | None = True spaces_between_special_tokens: bool | None = True @@ -486,7 +484,6 @@ class EncodeRequest(BaseModel): """Encode request.""" input: str | list[str] do_preprocess: bool | None = False - add_bos: bool | None = True class EncodeResponse(BaseModel): @@ -495,15 +492,6 @@ class EncodeResponse(BaseModel): length: int | list[int] -class GenerateResponse(BaseModel): - """Generate response.""" - text: str - tokens: int - input_tokens: int - history_tokens: int - finish_reason: Literal['stop', 'length', 'tool_calls', 'error', 'abort'] | None = None - - class UpdateParamsRequest(BaseModel): """Update weights request.""" serialized_named_tensors: str | list[str] | dict @@ -540,7 +528,6 @@ class DestroyWeightsUpdateGroupRequest(BaseModel): # /generate input class GenerateReqInput(BaseModel): - session_id: int | None = -1 prompt: str | None = None input_ids: list[int] | None = None image_data: ImageDataFormat | None = None @@ -594,5 +581,3 @@ class AbortRequest(BaseModel): # The finished reason data finished_reason: dict[str, Any] | None = None abort_message: str | None = None - # The session ID to abort. If `abort_all` is True, this field is ignored. - session_id: int | None = -1 diff --git a/lmdeploy/serve/openai/responses/serving.py b/lmdeploy/serve/openai/responses/serving.py index 2837e483c5..7167aa7325 100644 --- a/lmdeploy/serve/openai/responses/serving.py +++ b/lmdeploy/serve/openai/responses/serving.py @@ -67,7 +67,7 @@ def _build_parser(self, request: ResponsesRequest, model_name: str, messages: li return response_parser, None def _generate(self, model_name: str, parsed_request, gen_config): - session = self.server_context.create_session(-1) + session = self.server_context.create_session() adapter_name = None if model_name == self.server_context.async_engine.model_name else model_name result_generator = self.server_context.async_engine.generate( parsed_request.messages, @@ -75,8 +75,6 @@ def _generate(self, model_name: str, parsed_request, gen_config): gen_config=gen_config, tools=parsed_request.tools, stream_response=True, - sequence_start=True, - sequence_end=True, do_preprocess=True, adapter_name=adapter_name, ) diff --git a/lmdeploy/serve/openai/serving_chat_completion.py b/lmdeploy/serve/openai/serving_chat_completion.py index 362f0bf9e5..7d9a81d01a 100644 --- a/lmdeploy/serve/openai/serving_chat_completion.py +++ b/lmdeploy/serve/openai/serving_chat_completion.py @@ -9,7 +9,6 @@ def check_request(request: ChatCompletionRequest, server_context: 'VariableInterface') -> str: engine_config = server_context.get_engine_config() - session_manager = server_context.get_session_manager() try: # Check logprobs settings logprobs_mode = engine_config.logprobs_mode @@ -29,9 +28,6 @@ def check_request(request: ChatCompletionRequest, server_context: 'VariableInter except AttributeError: pass - if session_manager.has(request.session_id): - return f'The session_id {request.session_id!r} is occupied.' - # check sampling settings if request.n <= 0: return f'The n {request.n!r} must be a positive int.' diff --git a/lmdeploy/serve/openai/serving_completion.py b/lmdeploy/serve/openai/serving_completion.py index 759972db36..efe18bd4a3 100644 --- a/lmdeploy/serve/openai/serving_completion.py +++ b/lmdeploy/serve/openai/serving_completion.py @@ -9,7 +9,6 @@ def check_request(request: CompletionRequest, server_context: 'VariableInterface') -> str: engine_config = server_context.get_engine_config() - session_manager = server_context.get_session_manager() try: # Check logprobs settings logprobs_mode = engine_config.logprobs_mode @@ -21,9 +20,6 @@ def check_request(request: CompletionRequest, server_context: 'VariableInterface except AttributeError: pass - if session_manager.has(request.session_id): - return f'The session_id {request.session_id!r} is occupied.' - # check sampling settings if request.n <= 0: return f'The n {request.n!r} must be a positive int.' diff --git a/lmdeploy/serve/openai/serving_generate.py b/lmdeploy/serve/openai/serving_generate.py index 4615d9caea..5fba57d30f 100644 --- a/lmdeploy/serve/openai/serving_generate.py +++ b/lmdeploy/serve/openai/serving_generate.py @@ -9,7 +9,6 @@ def check_request(request: GenerateReqInput, server_context: 'VariableInterface') -> str: engine_config = server_context.get_engine_config() - session_manager = server_context.get_session_manager() try: # Check logprobs settings logprobs_mode = engine_config.logprobs_mode @@ -31,9 +30,6 @@ def check_request(request: GenerateReqInput, server_context: 'VariableInterface' if request.max_tokens is not None and request.max_tokens <= 0: return f'The max_tokens {request.max_tokens!r} must be a positive integer.' - if session_manager.has(request.session_id): - return f'The session_id {request.session_id!r} is occupied.' - # check sampling settings if not (0 < request.top_p <= 1): return f'The top_p {request.top_p!r} must be in (0, 1].' diff --git a/lmdeploy/serve/processors/multimodal.py b/lmdeploy/serve/processors/multimodal.py index c23765c293..e644506d52 100644 --- a/lmdeploy/serve/processors/multimodal.py +++ b/lmdeploy/serve/processors/multimodal.py @@ -196,7 +196,6 @@ async def async_parse_multimodal_item(messages: list[dict], async def get_prompt_input(self, prompt: str | list[dict], do_preprocess: bool, - sequence_start: bool, adapter_name: str, tools: list[object] | None = None, reasoning_effort: Literal['low', 'medium', 'high'] | None = None, @@ -212,7 +211,6 @@ async def get_prompt_input(self, Args: prompt: Input prompt as string or list of message dicts. do_preprocess: Whether to apply chat template preprocessing. - sequence_start: Indicator for starting a sequence. adapter_name: Adapter name for selecting chat template. tools: Optional list of tools. reasoning_effort: Optional reasoning effort level. @@ -229,7 +227,6 @@ async def get_prompt_input(self, if isinstance(prompt, str): return await self._get_text_prompt_input(prompt=prompt, do_preprocess=do_preprocess, - sequence_start=sequence_start, adapter_name=adapter_name, tools=tools, reasoning_effort=reasoning_effort, @@ -245,7 +242,6 @@ async def get_prompt_input(self, if not has_multimodal_input or self.vl_encoder is None: return await self._get_text_prompt_input(prompt=prompt, do_preprocess=do_preprocess, - sequence_start=sequence_start, adapter_name=adapter_name, tools=tools, reasoning_effort=reasoning_effort, @@ -255,7 +251,6 @@ async def get_prompt_input(self, # Process multimodal input return await self._get_multimodal_prompt_input(messages=prompt, do_preprocess=do_preprocess, - sequence_start=sequence_start, adapter_name=adapter_name, tools=tools, chat_template_kwargs=chat_template_kwargs, @@ -357,7 +352,6 @@ def _has_multimodal_input(self, messages: list[dict]) -> bool: async def _get_text_prompt_input(self, prompt: str | list[dict], do_preprocess: bool, - sequence_start: bool, adapter_name: str, tools: list[object] | None = None, reasoning_effort: Literal['low', 'medium', 'high'] | None = None, @@ -376,7 +370,6 @@ async def _get_text_prompt_input(self, chat_template = BaseChatTemplate() chat_template_kwargs = chat_template_kwargs or {} prompt = chat_template.messages2prompt(prompt, - sequence_start, tools=tools, reasoning_effort=reasoning_effort, **chat_template_kwargs) @@ -384,13 +377,12 @@ async def _get_text_prompt_input(self, raise ValueError( f'You are using base template to handle chat task. Please specify a `--chat-template` name chosen from `lmdeploy list` if you want to use OpenAI messages input.' # noqa ) - input_ids = self.tokenizer.encode(prompt, add_bos=sequence_start) + input_ids = self.tokenizer.encode(prompt) return {'prompt': prompt, 'input_ids': input_ids} async def _get_multimodal_prompt_input(self, messages: list[dict], do_preprocess: bool, - sequence_start: bool, adapter_name: str, tools: list[object] | None = None, chat_template_kwargs: dict | None = None, @@ -406,7 +398,6 @@ async def _get_multimodal_prompt_input(self, if self.vl_encoder._uses_new_preprocess: input_prompt = self.vl_encoder.model.get_input_prompt(messages=messages, chat_template=chat_template, - sequence_start=sequence_start, chat_template_kwargs=chat_template_kwargs) results = await self.vl_encoder.preprocess(messages, input_prompt=input_prompt, @@ -417,14 +408,12 @@ async def _get_multimodal_prompt_input(self, results = await self.vl_encoder.wrap_for_turbomind(messages=results, chat_template=chat_template, tokenizer=self.tokenizer, - sequence_start=sequence_start, tools=tools, chat_template_kwargs=chat_template_kwargs) elif self.backend == 'pytorch': if self.vl_encoder._uses_new_preprocess: input_prompt = self.vl_encoder.model.get_input_prompt(messages=messages, chat_template=chat_template, - sequence_start=sequence_start, chat_template_kwargs=chat_template_kwargs) results = await self.vl_encoder.preprocess(messages, input_prompt=input_prompt, @@ -435,7 +424,6 @@ async def _get_multimodal_prompt_input(self, results = await self.vl_encoder.wrap_for_pytorch(messages=results, chat_template=chat_template, tokenizer=self.tokenizer, - sequence_start=sequence_start, tools=tools, chat_template_kwargs=chat_template_kwargs) diff --git a/lmdeploy/tokenizer.py b/lmdeploy/tokenizer.py index df5329f423..9300e73819 100644 --- a/lmdeploy/tokenizer.py +++ b/lmdeploy/tokenizer.py @@ -66,6 +66,24 @@ def __init__(self, model_dir: str, trust_remote_code: bool = False): self._indexes_tokens_deque = deque(maxlen=10) self.max_indexes_num = 5 self.token2id = {} + self._warn_legacy() + + def _warn_legacy(self): + """Emit warnings for removed legacy APIs and behavior changes.""" + sample = 'Hello, world!' + try: + with_bos = self.model.encode(sample, add_special_tokens=True) + without_bos = with_bos + if self.bos_token_id is not None and with_bos and with_bos[0] == self.bos_token_id: + without_bos = with_bos[1:] + if with_bos != without_bos: + self.logger.warning( + 'Tokenizer encode results differ for legacy add_bos=True vs add_bos=False ' + f'on sample text {sample!r} (add_bos=True: {len(with_bos)} tokens, ' + f'add_bos=False: {len(without_bos)} tokens, bos_token_id={self.bos_token_id}). ' + 'LMDeploy no longer supports add_bos; use chat template for BOS formatting.') + except Exception: + pass def _check_transformers_version(self, model_dir: str, trust_remote_code: bool = False): import transformers @@ -181,7 +199,7 @@ def indexes_containing_token(self, token: str): f'indexes may decoding {token}, we will use {indexes} only') # there might be token id that exceeds self.vocab_size if len(indexes) == 0: - indexes = self.encode(token, False) + indexes = self.encode(token) if len(indexes) != 1: self.logger.warning(f'The token {token}, its length of indexes {indexes} is ' 'not 1. Currently, it can not be used as stop words') @@ -189,24 +207,18 @@ def indexes_containing_token(self, token: str): self._indexes_tokens_deque.append((token, indexes)) return indexes - def encode(self, s: str, add_bos: bool = True, add_special_tokens: bool = True, **kwargs): + def encode(self, s: str, add_special_tokens: bool = True, **kwargs): """Tokenize a prompt. Args: s: a prompt. - add_bos: Whether to add ``bos`` token id when encoding the prompt. add_special_tokens: Whether or not to add special tokens when encoding the prompt. Returns: list[int]: token ids. """ - encoded = self.model.encode(s, add_special_tokens=add_special_tokens, **kwargs) - if not add_bos: - # in the middle of a session - if len(encoded) and encoded[0] == self.bos_token_id: - encoded = encoded[1:] - return encoded + return self.model.encode(s, add_special_tokens=add_special_tokens, **kwargs) def decode(self, t: Sequence[int], offset: int | None = None, skip_special_tokens: bool = True): """De-tokenize. @@ -364,11 +376,11 @@ def __pad(*args, **kwargs): # fix for transformers>4.45.0 self.model._pad = __pad - def encode(self, s: str, add_bos: bool = True, add_special_tokens: bool = True, **kwargs): + def encode(self, s: str, add_special_tokens: bool = True, **kwargs): """Tokenize a prompt.""" # ChtGLM4Tokenizer hardcode `add_speical_tokens=False` when tokenizing # a prompt. Refer to https://huggingface.co/THUDM/glm-4-9b-chat/blob/main/tokenization_chatglm.py#L227 # noqa E501 - return super().encode(s, add_bos, add_special_tokens=False, **kwargs) + return super().encode(s, add_special_tokens=False, **kwargs) class ChatGLMTokenizer(HuggingFaceTokenizer): @@ -462,19 +474,18 @@ def get_vocab(self): """Get vocab.""" return self.model.get_vocab() - def encode(self, s: str, add_bos: bool = True, add_special_tokens: bool = True, **kwargs): + def encode(self, s: str, add_special_tokens: bool = True, **kwargs): """Tokenize a prompt. Args: s: a prompt. - add_bos: Whether to add ``bos`` token id when encoding the prompt. add_special_tokens: Whether or not to add special tokens when encoding the prompt. Returns: list[int]: token ids. """ - encoded = self.model.encode(s, add_bos, add_special_tokens, **kwargs) + encoded = self.model.encode(s, add_special_tokens=add_special_tokens, **kwargs) if encoded[:2] == [self.bos_token_id] * 2: self.logger.warning(f'Detected duplicate bos token {self.bos_token_id} in prompt, ' 'this will likely reduce response quality, one of them will be' @@ -542,7 +553,7 @@ def __call__(self, s: str | Sequence[str]): def indexes_containing_token(self, token): """Return all the possible indexes, whose decoding output may contain the input token.""" - encoded = self.encode(token, add_bos=False) + encoded = self.encode(token) if len(encoded) > 1: self.logger.warning(f'The token {token}, its length of indexes {encoded} is over ' 'than 1. Currently, it can not be used as stop words') diff --git a/lmdeploy/turbomind/turbomind.py b/lmdeploy/turbomind/turbomind.py index bd8b925e53..7e3de826df 100644 --- a/lmdeploy/turbomind/turbomind.py +++ b/lmdeploy/turbomind/turbomind.py @@ -574,7 +574,7 @@ def __init__(self, tm_model: 'TurboMind', cuda_stream_id: int = 0): 6: ResponseType.INPUT_LENGTH_ERROR, 7: ResponseType.FINISH, 8: ResponseType.CANCEL, - 9: ResponseType.PREFIX_CACHE_CONFLICT_INTERACTIVE_MODE, + 9: ResponseType.INTERNAL_ENGINE_ERROR, # kInconsistency, e.g. prefix caching + output_logits/return_ppl 10: ResponseType.NO_QUEUE, -1: ResponseType.INTERNAL_ENGINE_ERROR, } @@ -671,16 +671,6 @@ def prepare_inputs(self, async def async_cancel(self, session_id: int = None): self.model_inst.cancel() - def async_end_cb(self, fut: asyncio.Future, status: int): - """Executing on engine's signaling thread.""" - logger.info(f'[async_end_cb] session ended, status = {status}') - fut.get_loop().call_soon_threadsafe(fut.set_result, status) - - async def async_end(self, session_id): - fut = asyncio.get_running_loop().create_future() - self.model_inst.end(partial(self.async_end_cb, fut), session_id) - await fut - def async_signal_cb(self, s: StreamingSemaphore): """Executing on engine's signaling thread.""" s.loop.call_soon_threadsafe(s.release) @@ -692,9 +682,6 @@ async def async_stream_infer(self, input_embedding_ranges=None, input_meta: dict[str, Any] = None, multimodal: list[dict[str, Any]] = None, - sequence_start: bool = True, - sequence_end: bool = False, - step=0, gen_config: GenerationConfig = None, stream_output=False, **kwargs): @@ -706,10 +693,9 @@ async def async_stream_infer(self, input_embeddings (list[numpy.ndarray]): embeddings features input_embedding_ranges (list[tuple[int,int]]): the begin/end offsets of input_embeddings to input_ids - sequence_start (bool): indicator for starting a sequence - sequence_end (bool): indicator for ending a sequence - step (int): the offset of the k/v cache - stop (bool): indicator for cancelling the session + input_meta (dict[str, Any]): optional metadata such as mrope + position ids + multimodal (list[dict[str, Any]]): multimodal inputs gen_config (GenerationConfig): generation config stream_output (bool): indicator for stream output kwargs (dict): kwargs for backward compatibility @@ -758,7 +744,7 @@ async def async_stream_infer(self, f'disable guided decoding: {e}') gen_config.response_format = None - session = _tm.SessionParam(id=session_id, step=step, start=sequence_start, end=sequence_end) + session = _tm.SessionParam(id=session_id, step=0, start=True, end=True) inputs = _np_dict_to_tm_dict(inputs) mm_inputs = self.tm_model.mm_input_converter(multimodal) @@ -779,7 +765,7 @@ async def async_stream_infer(self, state = None output_ids = [] - prev_len = step + input_len + prev_len = input_len try: while True: await sem.acquire() diff --git a/lmdeploy/vl/engine.py b/lmdeploy/vl/engine.py index f2a5f62ccf..e7996dd9a7 100644 --- a/lmdeploy/vl/engine.py +++ b/lmdeploy/vl/engine.py @@ -139,7 +139,6 @@ async def wrap_for_pytorch( messages: list[dict], chat_template, tokenizer, - sequence_start, tools: list[object] | None = None, chat_template_kwargs: dict | None = None, ) -> list[dict]: @@ -166,7 +165,6 @@ async def wrap_for_pytorch( result = self.model.to_pytorch(messages, chat_template, tokenizer, - sequence_start, tools=tools, chat_template_kwargs=chat_template_kwargs) else: @@ -182,7 +180,6 @@ async def wrap_for_turbomind( messages: list[dict], chat_template, tokenizer, - sequence_start, tools: list[object] | None = None, chat_template_kwargs: dict | None = None, ) -> dict: @@ -206,7 +203,6 @@ async def wrap_for_turbomind( result = self.model.to_turbomind(messages, chat_template, tokenizer, - sequence_start, tools=tools, chat_template_kwargs=chat_template_kwargs) # clear data diff --git a/lmdeploy/vl/model/base.py b/lmdeploy/vl/model/base.py index 47de994b5a..a609a01123 100644 --- a/lmdeploy/vl/model/base.py +++ b/lmdeploy/vl/model/base.py @@ -262,7 +262,7 @@ def has_input_ids(messages: list[dict]) -> bool: return isinstance(first_item, dict) and isinstance(first_item.get('text'), list) @staticmethod - def get_input_prompt(messages: list[dict], chat_template, sequence_start: bool, + def get_input_prompt(messages: list[dict], chat_template, chat_template_kwargs: dict | None = None) -> str | list[int]: """Return the input prompt for the preprocessor. @@ -274,14 +274,13 @@ def get_input_prompt(messages: list[dict], chat_template, sequence_start: bool, Args: messages: Preprocessed message list. chat_template: Chat template used to render a text prompt. - sequence_start: Whether this is the start of a new sequence. chat_template_kwargs: Extra kwargs forwarded to ``messages2prompt``. Returns: A list of token ids when input_ids are embedded, otherwise a str. """ if VisionModel.has_input_ids(messages): return messages[0]['content'][0]['text'] - return chat_template.messages2prompt(messages, sequence_start, **(chat_template_kwargs or {})) + return chat_template.messages2prompt(messages, **(chat_template_kwargs or {})) def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: """Extract image feature. ONLY implement it when the backend is @@ -298,7 +297,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: if self.backend == 'turbomind': raise NotImplementedError() - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, chat_template_kwargs=None, **kwargs): + def to_pytorch(self, messages, chat_template, tokenizer, chat_template_kwargs=None, **kwargs): """Pack the preprocessing results in a format compatible with what is required by pytorch engine. ONLY implement it when the backend is pytorch engine. @@ -307,14 +306,13 @@ def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, chat_te messages(list[dict]): the output of `preprocess` chat_template: the chat template defined in `lmdeploy/model.py` tokenzer: the tokenizer model - sequence_start: starting flag of a sequence - chat_template_kwargs: additional arguments for chat template + chat_template_kwargs: additional arguments for chat template processing, such as `add_vision_id` and `enable_thinking` """ if self.backend == 'pytorch': raise NotImplementedError() - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, chat_template_kwargs=None, **kwargs): + def to_turbomind(self, messages, chat_template, tokenizer, chat_template_kwargs=None, **kwargs): """Pack the forwarding results in a format compatible with what is required by turbomind engine. ONLY implement it when the backend is turbomind engine. @@ -323,8 +321,7 @@ def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, chat_ messages(list[dict]): the output of `preprocess` chat_template: the chat template defined in `lmdeploy/model.py` tokenzer: the tokenizer model - sequence_start: starting flag of a sequence - chat_template_kwargs: additional arguments for chat template + chat_template_kwargs: additional arguments for chat template processing, such as `add_vision_id` and `enable_thinking` """ if self.backend == 'turbomind': @@ -418,7 +415,7 @@ def to_pytorch_with_input_ids(self, messages): return dict(prompt=None, input_ids=input_ids, multimodal=preps) - def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start): + def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer): """Auxiliary function to pack the preprocessing results in a format compatible with what is required by pytorch engine. @@ -428,7 +425,6 @@ def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_star IMAGE_TOKEN(str): a placeholder where image tokens will be inserted tokenzer: the tokenizer model - sequence_start: starting flag of a sequence """ # collect all preprocessing result from messages preps = [x['content'] for x in messages if x['role'] == 'preprocess'] @@ -448,12 +444,12 @@ def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_star image_tokens = preps[i - 1]['image_tokens'] assert self.image_token_id == preps[i - 1]['image_token_id'] input_ids.extend([self.image_token_id] * image_tokens) - token_ids = tokenizer.encode(seg, add_bos=((i == 0) and sequence_start)) + token_ids = tokenizer.encode(seg) input_ids.extend(token_ids) return dict(prompt=prompt, input_ids=input_ids, multimodal=preps) - def to_turbomind_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start): + def to_turbomind_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer): """Auxiliary function to pack the forwarding results in a format compatible with what is required by turbomind engine. @@ -463,7 +459,6 @@ def to_turbomind_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_st IMAGE_TOKEN(str): a placeholder where image tokens will be inserted tokenzer: the tokenizer model - sequence_start: starting flag of a sequence """ # collect image features from messages features = [x['content'] for x in messages if x['role'] == 'forward'] @@ -484,7 +479,7 @@ def to_turbomind_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_st begins.append(len(input_ids)) ends.append(begins[-1] + image_dim) input_ids.extend([self.image_token_id] * image_dim) - seg_ids = tokenizer.encode(seg, add_bos=((i == 0) and sequence_start)) + seg_ids = tokenizer.encode(seg) input_ids.extend(seg_ids) ranges = np.stack([begins, ends], axis=1).tolist() return dict(prompt=prompt, input_ids=input_ids, input_embeddings=features, input_embedding_ranges=ranges) diff --git a/lmdeploy/vl/model/cogvlm.py b/lmdeploy/vl/model/cogvlm.py index dd35c907b7..5f125ccda2 100644 --- a/lmdeploy/vl/model/cogvlm.py +++ b/lmdeploy/vl/model/cogvlm.py @@ -53,7 +53,7 @@ def preprocess(self, messages: list[dict]) -> list[dict]: return messages @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] for message in messages: @@ -75,14 +75,14 @@ def proc_messages(messages, chat_template, sequence_start): num_images = msg.pop('num_images', 0) if num_images == 0: role = msg['role'] - msg = llm_chat_template.messages2prompt([msg], sequence_start and i == 0) + msg = llm_chat_template.messages2prompt([msg]) msg = dict(role=role, content=msg) - prompt_i = chat_template.messages2prompt([msg], sequence_start and i == 0) + prompt_i = chat_template.messages2prompt([msg]) if num_images > 0: prompt_i = (IMAGE_TOKEN * num_images) + prompt_i prompt += prompt_i return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/deepseek.py b/lmdeploy/vl/model/deepseek.py index bd0f68c2e3..534fe66a5b 100644 --- a/lmdeploy/vl/model/deepseek.py +++ b/lmdeploy/vl/model/deepseek.py @@ -130,7 +130,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: return messages @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): # apply chat template to get the prompt prompt_messages = [] IMAGE_TOKEN = '' @@ -159,13 +159,13 @@ def proc_messages(messages, chat_template, sequence_start): else: content = ''.join([f'{IMAGE_TOKEN} is Figure {str(i)}.\n' for i in range(n_image)]) + content prompt_messages.append(dict(role='user', content=content)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/deepseek_vl2.py b/lmdeploy/vl/model/deepseek_vl2.py index c17b02c954..ed12f78a83 100644 --- a/lmdeploy/vl/model/deepseek_vl2.py +++ b/lmdeploy/vl/model/deepseek_vl2.py @@ -148,7 +148,7 @@ def proc_single_message(message): return content @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -157,13 +157,13 @@ def proc_messages(messages, chat_template, sequence_start): if content is None: continue prompt_messages.append(dict(role='user', content=content)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/gemma3_vl.py b/lmdeploy/vl/model/gemma3_vl.py index d957184621..357261c625 100644 --- a/lmdeploy/vl/model/gemma3_vl.py +++ b/lmdeploy/vl/model/gemma3_vl.py @@ -97,7 +97,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: raise NotImplementedError() @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -111,13 +111,13 @@ def proc_messages(messages, chat_template, sequence_start): content = [item['text'] for item in message['content'] if item['type'] == 'text'] prompt = ('\n\n' + IMAGE_TOKEN + '\n\n') * n_images + content[0] prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/glm4_v.py b/lmdeploy/vl/model/glm4_v.py index 0892b02dc2..0d6755dfa9 100644 --- a/lmdeploy/vl/model/glm4_v.py +++ b/lmdeploy/vl/model/glm4_v.py @@ -68,7 +68,7 @@ def preprocess(self, messages: list[dict]) -> list[dict]: return messages @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -83,9 +83,9 @@ def proc_messages(messages, chat_template, sequence_start): n_images = len([1 for x in content if x['type'] == 'image']) prompt = ''.join([f'{IMAGE_TOKEN}\n'] * n_images) + prompt[0] prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/internvl.py b/lmdeploy/vl/model/internvl.py index 1534ad3388..e9ce5ba620 100644 --- a/lmdeploy/vl/model/internvl.py +++ b/lmdeploy/vl/model/internvl.py @@ -239,7 +239,6 @@ def proc_messages( self, messages, chat_template, - sequence_start, tools: list[object] | None = None, chat_template_kwargs: dict | None = None, ): @@ -275,35 +274,31 @@ def proc_messages( else: raise ValueError(f'Unsupported message type: {item["type"]}') prompt_messages.append(dict(role='user', content=''.join(_content))) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start, tools=tools, **chat_template_kwargs) + prompt = chat_template.messages2prompt(prompt_messages, tools=tools, **chat_template_kwargs) return prompt, self.image_token def to_pytorch(self, messages, chat_template, tokenizer, - sequence_start, tools: list[object] | None = None, chat_template_kwargs: dict | None = None, **kwargs): prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, - sequence_start, tools=tools, chat_template_kwargs=chat_template_kwargs) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) def to_turbomind(self, messages, chat_template, tokenizer, - sequence_start, tools: list[object] | None = None, chat_template_kwargs: dict | None = None, **kwargs): prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, - sequence_start, tools=tools, chat_template_kwargs=chat_template_kwargs) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/llama4.py b/lmdeploy/vl/model/llama4.py index 92199f458c..368b4ec602 100644 --- a/lmdeploy/vl/model/llama4.py +++ b/lmdeploy/vl/model/llama4.py @@ -99,7 +99,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: raise NotImplementedError() @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -115,10 +115,10 @@ def proc_messages(messages, chat_template, sequence_start): if IMAGE_TOKEN not in prompt: prompt = f'{IMAGE_TOKEN * n_images}' + prompt prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start): + def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer): """Auxiliary function to pack the preprocessing results in a format compatible with what is required by pytorch engine. @@ -128,7 +128,6 @@ def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_star IMAGE_TOKEN(str): a placeholder where image tokens will be inserted tokenzer: the tokenizer model - sequence_start: starting flag of a sequence """ # collect all preprocessing result from messages preps = [x['content'] for x in messages if x['role'] == 'preprocess'] @@ -149,14 +148,14 @@ def to_pytorch_aux(self, messages, prompt, IMAGE_TOKEN, tokenizer, sequence_star prep.update(offset=len(input_ids) + 1) assert self.image_token_id == prep['image_token_id'] seg = image_prompts + seg - token_ids = tokenizer.encode(seg, add_bos=((i == 0) and sequence_start)) + token_ids = tokenizer.encode(seg) input_ids.extend(token_ids) return dict(prompt=prompt, input_ids=input_ids, multimodal=preps) - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/llava_hf.py b/lmdeploy/vl/model/llava_hf.py index 08dfa3dbc6..09b04b0ddc 100644 --- a/lmdeploy/vl/model/llava_hf.py +++ b/lmdeploy/vl/model/llava_hf.py @@ -104,7 +104,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: return messages @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -118,13 +118,13 @@ def proc_messages(messages, chat_template, sequence_start): content = [item['text'] for item in message['content'] if item['type'] == 'text'] prompt = (IMAGE_TOKEN + '\n') * n_images + content[0] prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/minicpmv.py b/lmdeploy/vl/model/minicpmv.py index e64278244a..367b3f839c 100644 --- a/lmdeploy/vl/model/minicpmv.py +++ b/lmdeploy/vl/model/minicpmv.py @@ -197,7 +197,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: messages.append(dict(role='forward', content=outputs)) return messages - def proc_messages(self, messages, chat_template, sequence_start): + def proc_messages(self, messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -229,13 +229,13 @@ def proc_messages(self, messages, chat_template, sequence_start): content = [x.get('text', '') for x in message['content'] if x['type'] == 'text'] prompt = ''.join(prompts) + content[0] prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/mllama.py b/lmdeploy/vl/model/mllama.py index b2801b2726..2024076412 100644 --- a/lmdeploy/vl/model/mllama.py +++ b/lmdeploy/vl/model/mllama.py @@ -45,7 +45,7 @@ def build_model(self, trust_remote_code: bool = False): raise NotImplementedError('turbomind has not supported mllama yet') @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '<|image|>' @@ -59,9 +59,9 @@ def proc_messages(messages, chat_template, sequence_start): content = [item['text'] for item in message['content'] if item['type'] == 'text'] prompt = (IMAGE_TOKEN) * n_images + content[0] prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/molmo.py b/lmdeploy/vl/model/molmo.py index 1bad7474cb..d675cb9ed7 100644 --- a/lmdeploy/vl/model/molmo.py +++ b/lmdeploy/vl/model/molmo.py @@ -145,10 +145,10 @@ def proc_messages(messages): prompt.append(' Assistant:') return ''.join(prompt) - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): assert 0, 'molmo is not supported by pytorch engine' - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): # results is a list of tuple(input_ids, embeddings) results = [] # Prepend BOS diff --git a/lmdeploy/vl/model/qwen.py b/lmdeploy/vl/model/qwen.py index 0402294ff4..a463ecf2b3 100644 --- a/lmdeploy/vl/model/qwen.py +++ b/lmdeploy/vl/model/qwen.py @@ -108,7 +108,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: return messages @staticmethod - def proc_messages(messages, chat_template, sequence_start): + def proc_messages(messages, chat_template): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -126,13 +126,13 @@ def proc_messages(messages, chat_template, sequence_start): else: prompt = ''.join([f'Picture {str(i)}:{IMAGE_TOKEN}\n' for i in range(n_images)]) + prompt prompt_messages.append(dict(role='user', content=prompt)) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/lmdeploy/vl/model/qwen2.py b/lmdeploy/vl/model/qwen2.py index 98e3f8cd09..e6d5af3839 100644 --- a/lmdeploy/vl/model/qwen2.py +++ b/lmdeploy/vl/model/qwen2.py @@ -122,7 +122,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: messages.append(dict(role='forward', content=outputs)) return messages - def proc_messages(self, messages, chat_template, sequence_start, chat_template_kwargs=None): + def proc_messages(self, messages, chat_template, chat_template_kwargs=None): """Apply chat template to get the prompt.""" chat_template_kwargs = chat_template_kwargs or {} prompt_messages = [] @@ -155,7 +155,7 @@ def proc_messages(self, messages, chat_template, sequence_start, chat_template_k raise ValueError(f'Unsupported message type: {item["type"]}') message = dict(role=role, content=''.join(_content)) prompt_messages.append(message) - prompt = chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = chat_template.messages2prompt(prompt_messages) return prompt, self.image_token @staticmethod @@ -183,14 +183,14 @@ def get_mrope_info(seq_len: int, mrope_position_delta = torch.tensor([st_idx - seq_len], dtype=torch.long) return mrope_position_ids, mrope_position_delta - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, chat_template_kwargs=None, **kwargs): + def to_pytorch(self, messages, chat_template, tokenizer, chat_template_kwargs=None, **kwargs): """Return to the information needed by pytorch engine.""" - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start, chat_template_kwargs) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, chat_template_kwargs) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, chat_template_kwargs=None, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start, chat_template_kwargs) - info = super().to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, chat_template_kwargs=None, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, chat_template_kwargs) + info = super().to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) inputs = [x['content'] for x in messages if x['role'] == 'preprocess'][0] grid_thws = [x['image_grid_thw'].tolist()[0] for x in inputs] seq_len = len(info['input_ids']) diff --git a/lmdeploy/vl/model/xcomposer2.py b/lmdeploy/vl/model/xcomposer2.py index 92d036c50d..f55726e27b 100644 --- a/lmdeploy/vl/model/xcomposer2.py +++ b/lmdeploy/vl/model/xcomposer2.py @@ -254,7 +254,7 @@ def forward(self, messages: list[dict], max_batch_size: int = 1) -> list[dict]: return messages @staticmethod - def proc_messages(messages, chat_template, sequence_start, model_type): + def proc_messages(messages, chat_template, model_type): """Apply chat template to get the prompt.""" prompt_messages = [] IMAGE_TOKEN = '' @@ -278,13 +278,13 @@ def proc_messages(messages, chat_template, sequence_start, model_type): else: prompt = content[0] prompt_messages.append(dict(role='user', content=prompt)) - prompt = prefix_image_token + chat_template.messages2prompt(prompt_messages, sequence_start) + prompt = prefix_image_token + chat_template.messages2prompt(prompt_messages) return prompt, IMAGE_TOKEN - def to_pytorch(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start, self.model_type) - return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_pytorch(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, self.model_type) + return self.to_pytorch_aux(messages, prompt, IMAGE_TOKEN, tokenizer) - def to_turbomind(self, messages, chat_template, tokenizer, sequence_start, **kwargs): - prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, sequence_start, self.model_type) - return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer, sequence_start) + def to_turbomind(self, messages, chat_template, tokenizer, **kwargs): + prompt, IMAGE_TOKEN = self.proc_messages(messages, chat_template, self.model_type) + return self.to_turbomind_aux(messages, prompt, IMAGE_TOKEN, tokenizer) diff --git a/tests/test_lmdeploy/serve/anthropic/test_endpoints.py b/tests/test_lmdeploy/serve/anthropic/test_endpoints.py index dde774479f..31a19e6408 100644 --- a/tests/test_lmdeploy/serve/anthropic/test_endpoints.py +++ b/tests/test_lmdeploy/serve/anthropic/test_endpoints.py @@ -56,16 +56,14 @@ def remove(self, session): class _FakeTokenizer: - def encode(self, text: str, add_bos: bool = True, **kwargs): + def encode(self, text: str, **kwargs): tokens = text.split() - if add_bos: - return [0] + list(range(1, len(tokens) + 1)) - return list(range(len(tokens))) + return [0] + list(range(1, len(tokens) + 1)) class _FakeChatTemplate: - def messages2prompt(self, messages, sequence_start: bool = True, **kwargs): + def messages2prompt(self, messages, **kwargs): parts = [f"{item['role']}:{item['content']}" for item in messages] return '\n'.join(parts) diff --git a/tests/test_lmdeploy/serve/test_session_cleanup.py b/tests/test_lmdeploy/serve/test_session_cleanup.py index ede43566ca..72db52ebe0 100644 --- a/tests/test_lmdeploy/serve/test_session_cleanup.py +++ b/tests/test_lmdeploy/serve/test_session_cleanup.py @@ -24,17 +24,13 @@ def create_instance(self): async def _run_request_handle_cleanup(raise_safe_run: bool = False): session_mgr = SessionManager() session_mgr.build_request_handle_pool(_FakeEngine(), 1) - session_id = session_mgr.map_user_session_id(260606) - session = session_mgr.get(session_id) - session._remove_on_request_exit = True + session = session_mgr.get(260606) async with session.request_handle(): if raise_safe_run: raise SafeRunException('cancelled') assert session_mgr.sessions == {} - assert session_mgr.user_session_id_map == {} - assert session_mgr.session_id_map == {} def test_terminal_request_handle_exit_removes_session_maps(): @@ -51,16 +47,10 @@ def test_stale_session_cleanup_does_not_remove_reused_session_id(): old_session = session_mgr.get(session_id) session_mgr.sessions.pop(session_id) new_session = session_mgr.get(session_id) - session_mgr.session_id_map[session_id] = 42 - session_mgr.user_session_id_map[42] = session_id - # Simulate a fast session-id reuse while an old request cleanup is still - # unwinding with a stale Session object. session_mgr.remove(old_session) assert session_mgr.sessions[session_id] is new_session - assert session_mgr.session_id_map[session_id] == 42 - assert session_mgr.user_session_id_map[42] == session_id async def _run_api_wrapper_cleanup_after_cancel(): @@ -218,26 +208,23 @@ async def _run_max_new_tokens_zero_keeps_history_len(): engine = AsyncEngine.__new__(AsyncEngine) engine.session_mgr = SessionManager() engine.session_mgr.build_request_handle_pool(_FakeEngine(), 1) - engine._determine_gen_config = lambda session, input_ids, gen_config=None: gen_config + engine._determine_gen_config = lambda input_ids, gen_config=None: gen_config - session = engine.session_mgr.get(260606, step=5) + session = engine.session_mgr.get(260606) generator = engine.generate(None, session, input_ids=[1, 2], - gen_config=GenerationConfig(max_new_tokens=0), - sequence_start=False, - sequence_end=True) + gen_config=GenerationConfig(max_new_tokens=0)) out = await generator.__anext__() - assert out.history_token_len == 5 + assert out.history_token_len == 0 assert out.input_token_len == 2 assert out.finish_reason == 'length' - assert session.step == 0 assert engine.session_mgr.sessions == {} finally: metrics_processor.scheduler_stats = old_stats -def test_max_new_tokens_zero_keeps_history_len_after_close(): +def test_max_new_tokens_zero_returns_length_finish(): asyncio.run(_run_max_new_tokens_zero_keeps_history_len()) diff --git a/tests/test_lmdeploy/test_messages.py b/tests/test_lmdeploy/test_messages.py index cb57079f07..e4431c4628 100644 --- a/tests/test_lmdeploy/test_messages.py +++ b/tests/test_lmdeploy/test_messages.py @@ -25,7 +25,7 @@ def test_chat_completion_request_repetition_ngram_ge_zero(): def test_engine_generation_config(): tokenizer = Tokenizer('internlm/internlm-chat-7b', trust_remote_code=True) config = GenerationConfig(n=3, stop_words=['']) - stop_token_ids = tokenizer.encode('', add_bos=False) + stop_token_ids = tokenizer.encode('') config.convert_stop_bad_words_to_ids(tokenizer) assert stop_token_ids == config.stop_token_ids assert isinstance(config.stop_token_ids, list) and \ diff --git a/tests/test_lmdeploy/test_model.py b/tests/test_lmdeploy/test_model.py index 6fc2ecd089..ad3b604dd2 100644 --- a/tests/test_lmdeploy/test_model.py +++ b/tests/test_lmdeploy/test_model.py @@ -56,7 +56,7 @@ @pytest.mark.parametrize('model_path', HF_MODELS_WITH_CHAT_TEMPLATES) -def test_HFChatTemplate_get_prompt_sequence_start_True(model_path): +def test_HFChatTemplate_get_prompt(model_path): model = MODELS.get('hf')(model_path=model_path, trust_remote_code=True) prompt = 'How to apply chat template using transformers?' messages = [{'role': 'user', 'content': prompt}] @@ -64,11 +64,11 @@ def test_HFChatTemplate_get_prompt_sequence_start_True(model_path): from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) - assert model.get_prompt(prompt, sequence_start=True) == expected + assert model.get_prompt(prompt) == expected @pytest.mark.parametrize('model_path', HF_MODELS_WITH_CHAT_TEMPLATES) -def test_HFChatTemplate_message2prompt_sequence_start_True(model_path): +def test_HFChatTemplate_message2prompt(model_path): model = MODELS.get('hf')(model_path=model_path, trust_remote_code=True) prompt = 'How to apply chat template using transformers?' messages = [{'role': 'user', 'content': prompt}] @@ -76,8 +76,8 @@ def test_HFChatTemplate_message2prompt_sequence_start_True(model_path): from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) - assert model.messages2prompt(prompt, sequence_start=True) == expected - assert model.messages2prompt(messages, sequence_start=True) == expected + assert model.messages2prompt(prompt) == expected + assert model.messages2prompt(messages) == expected def test_base_model(): @@ -90,18 +90,16 @@ def test_base_model(): def test_vicuna(): prompt = 'hello, can u introduce yourself' model = MODELS.get('vicuna')(capability='completion') - assert model.get_prompt(prompt, sequence_start=True) == prompt - assert model.get_prompt(prompt, sequence_start=False) == prompt + assert model.get_prompt(prompt) == prompt model = MODELS.get('vicuna')(capability='chat', system='Provide answers in Python') - assert model.get_prompt(prompt, sequence_start=True) != prompt - assert model.get_prompt(prompt, sequence_start=False) != prompt + assert model.get_prompt(prompt) != prompt assert model.system == 'Provide answers in Python' model = MODELS.get('vicuna')(capability='voice') _prompt = None with pytest.raises(AssertionError): - _prompt = model.get_prompt(prompt, sequence_start=True) + _prompt = model.get_prompt(prompt) assert _prompt is None @@ -115,52 +113,47 @@ def test_prefix_response(): def test_internlm_chat(): prompt = 'hello, can u introduce yourself' model = MODELS.get('internlm')(capability='completion') - assert model.get_prompt(prompt, sequence_start=True) == prompt - assert model.get_prompt(prompt, sequence_start=False) == prompt + assert model.get_prompt(prompt) == prompt assert model.stop_words is not None assert model.system == '<|System|>:' model = MODELS.get('internlm')(capability='chat', system='Provide answers in Python') - assert model.get_prompt(prompt, sequence_start=True) != prompt - assert model.get_prompt(prompt, sequence_start=False) != prompt + assert model.get_prompt(prompt) != prompt assert model.system == 'Provide answers in Python' model = MODELS.get('internlm')(capability='voice') _prompt = None with pytest.raises(AssertionError): - _prompt = model.get_prompt(prompt, sequence_start=True) + _prompt = model.get_prompt(prompt) assert _prompt is None def test_baichuan(): prompt = 'hello, can u introduce yourself' model = MODELS.get('baichuan2')(capability='completion') - assert model.get_prompt(prompt, sequence_start=True) == prompt - assert model.get_prompt(prompt, sequence_start=False) == prompt + assert model.get_prompt(prompt) == prompt assert model.stop_words is None model = MODELS.get('baichuan2')(capability='chat') - _prompt = model.get_prompt(prompt, sequence_start=True) + _prompt = model.get_prompt(prompt) assert _prompt == '' + prompt + '' def test_llama2(): prompt = 'hello, can u introduce yourself' model = MODELS.get('llama2')(capability='completion') - assert model.get_prompt(prompt, sequence_start=True) == prompt - assert model.get_prompt(prompt, sequence_start=False) == prompt + assert model.get_prompt(prompt) == prompt assert model.stop_words is None assert model.meta_instruction is not None model = MODELS.get('llama2')(capability='chat', meta_instruction='Provide answers in Python') - assert model.get_prompt(prompt, sequence_start=True) != prompt - assert model.get_prompt(prompt, sequence_start=False) != prompt + assert model.get_prompt(prompt) != prompt assert model.meta_instruction == 'Provide answers in Python' model = MODELS.get('llama2')(capability='voice') _prompt = None with pytest.raises(AssertionError): - _prompt = model.get_prompt(prompt, sequence_start=True) + _prompt = model.get_prompt(prompt) assert _prompt is None @@ -171,7 +164,6 @@ def test_codellama_completion(): def ping_exponential_backoff(host: str):""" assert model.get_prompt(prompt) == prompt - assert model.get_prompt(prompt, sequence_start=False) == prompt assert model.stop_words is None @@ -193,11 +185,8 @@ def test_codellama_infilling(): def test_codellama_chat(): model = MODELS.get('codellama')(capability='chat', system='Provide answers in Python') prompt = 'Write a function that computes the set of sums of all contiguous sublists of a given list.' # noqa: E501 - _prompt = model.get_prompt(prompt, sequence_start=True) + _prompt = model.get_prompt(prompt) assert _prompt.find('Provide answers in Python') != -1 - - _prompt = model.get_prompt(prompt, sequence_start=False) - assert _prompt.find('Provide answers in Python') == -1 assert model.stop_words is None @@ -206,8 +195,7 @@ def test_codellama_python_specialist(): prompt = """ def remove_non_ascii(s: str) -> str: """ - assert model.get_prompt(prompt, sequence_start=True) == prompt - assert model.get_prompt(prompt, sequence_start=False) == prompt + assert model.get_prompt(prompt) == prompt assert model.stop_words is None @@ -317,23 +305,29 @@ def test_qwen3(model_path, enable_thinking): @pytest.mark.parametrize('model_path', ['Qwen/Qwen1.5-7B-Chat', 'Qwen/Qwen2.5-7B-Instruct', 'Qwen/Qwen3-8B']) -def test_HFChatTemplate_get_prompt_sequence_start_False_Qwen(model_path): +def test_HFChatTemplate_get_prompt_Qwen(model_path): model = MODELS.get('hf')(model_path=model_path) assert model.stop_words == ['<|im_end|>'] prompt = 'How to apply chat template using transformers?' - assert model.get_prompt(prompt, - sequence_start=False) == f'<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n' + messages = [{'role': 'user', 'content': prompt}] + from transformers import AutoTokenizer + tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + assert model.get_prompt(prompt) == expected @pytest.mark.parametrize('model_path', ['Qwen/Qwen3.5-35B-A3B']) -def test_HFChatTemplate_get_prompt_sequence_start_False_Qwen3_5(model_path): +def test_HFChatTemplate_get_prompt_Qwen3_5(model_path): model = MODELS.get('hf')(model_path=model_path) assert model.stop_words == ['<|im_end|>'] prompt = 'How to apply chat template using transformers?' - assert model.get_prompt( - prompt, sequence_start=False) == f'<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n\n' + messages = [{'role': 'user', 'content': prompt}] + from transformers import AutoTokenizer + tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + assert model.get_prompt(prompt) == expected @pytest.mark.parametrize('model_path', ['deepseek-ai/DeepSeek-V3']) @@ -342,7 +336,11 @@ def test_HFChatTemplate_DeepSeek_V3(model_path): assert model.stop_words == ['<|end▁of▁sentence|>'] prompt = 'How to apply chat template using transformers?' - assert model.get_prompt(prompt, sequence_start=False) == f'<|User|>{prompt}<|Assistant|>' + messages = [{'role': 'user', 'content': prompt}] + from transformers import AutoTokenizer + tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + assert model.get_prompt(prompt) == expected @pytest.mark.parametrize('model_path', ['deepseek-ai/DeepSeek-R1']) @@ -351,7 +349,11 @@ def test_HFChatTemplate_DeepSeek_thinking(model_path): assert model.stop_words == ['<|end▁of▁sentence|>'] prompt = 'How to apply chat template using transformers?' - assert model.get_prompt(prompt, sequence_start=False) == f'<|User|>{prompt}<|Assistant|>\n' + messages = [{'role': 'user', 'content': prompt}] + from transformers import AutoTokenizer + tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + assert model.get_prompt(prompt) == expected @pytest.mark.parametrize('model_path', ['Qwen/Qwen3-VL-8B-Instruct', 'Qwen/Qwen3.5-35B-A3B']) @@ -430,12 +432,6 @@ def test_gemma_chat_template(model_path): assert expected == lm_res messages += [{'role': 'assistant', 'content': 'I am an AI'}, {'role': 'user', 'content': 'AGI is?'}] - lm_res = model.messages2prompt(messages, sequence_start=False) - assert lm_res == """user -who are you -model -I am an AI -user -AGI is? -model -""" + expected = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + lm_res = model.messages2prompt(messages) + assert expected == lm_res diff --git a/tests/test_lmdeploy/test_pipeline.py b/tests/test_lmdeploy/test_pipeline.py index 9774590cab..2015ba3636 100644 --- a/tests/test_lmdeploy/test_pipeline.py +++ b/tests/test_lmdeploy/test_pipeline.py @@ -124,43 +124,35 @@ def test_stream_infer_with_session(self, pipe): """Test stream_infer with session for multi-turn context.""" session = pipe.session() prompt1 = 'Hello! My name is Alice.' - step = 0 - # First turn generator = pipe.stream_infer(prompts=prompt1, sessions=session, gen_config=GenerationConfig(max_new_tokens=30), - sequence_start=True, - sequence_end=False, enable_thinking=False) resp = None for out in generator: resp = resp.extend(out) if resp else out - step += resp.generate_token_len + resp.input_token_len - response1 = resp.text - assert response1 + session.history.append((prompt1, response1)) - # Second turn should remember context prompt2 = 'What is my name?' - session.step = step - generator = pipe.stream_infer(prompts=prompt2, + messages = [ + dict(role='user', content=prompt1), + dict(role='assistant', content=response1), + dict(role='user', content=prompt2), + ] + generator = pipe.stream_infer(prompts=messages, sessions=session, gen_config=GenerationConfig(max_new_tokens=30), - sequence_start=False, - sequence_end=False, enable_thinking=False) resp = None for out in generator: resp = resp.extend(out) if resp else out - step += out.generate_token_len + out.input_token_len - response2 = resp.text - assert 'alice' in response2.lower() def test_chat_streaming(self, pipe): @@ -180,7 +172,6 @@ def test_chat_streaming(self, pipe): assert len(chunks) > 0 assert session.response is not None - assert session.step > 0 def test_chat_non_streaming(self, pipe): """Test chat method with non-streaming output.""" @@ -290,3 +281,37 @@ def test_infer_zero_tokens(self, pipe): response = pipe.infer(prompt, gen_config=gen_config, enable_thinking=False) assert isinstance(response, Response) assert response.generate_token_len == 0 + + +def test_history_to_messages_text(): + from lmdeploy.pipeline import Pipeline + + history = [('hello', 'hi'), ('how are you?', 'fine')] + messages = Pipeline._history_to_messages(history) + assert messages == [ + dict(role='user', content='hello'), + dict(role='assistant', content='hi'), + dict(role='user', content='how are you?'), + dict(role='assistant', content='fine'), + ] + + +def test_history_to_messages_multimodal_tuple(): + from PIL import Image + + from lmdeploy.pipeline import Pipeline + + img = Image.new('RGB', (10, 10)) + history = [ + (('describe this image', img), 'a small image'), + ('what did you see?', 'a small image'), + ] + messages = Pipeline._history_to_messages(history) + assert len(messages) == 4 + assert messages[0]['role'] == 'user' + assert isinstance(messages[0]['content'], list) + assert messages[0]['content'][0] == {'type': 'text', 'text': 'describe this image'} + assert messages[0]['content'][1]['type'] == 'image_data' + assert messages[1] == dict(role='assistant', content='a small image') + assert messages[2] == dict(role='user', content='what did you see?') + assert messages[3] == dict(role='assistant', content='a small image') diff --git a/tests/test_lmdeploy/test_tokenizer.py b/tests/test_lmdeploy/test_tokenizer.py index 5eb659de5e..4f23bf8740 100644 --- a/tests/test_lmdeploy/test_tokenizer.py +++ b/tests/test_lmdeploy/test_tokenizer.py @@ -16,7 +16,7 @@ @pytest.mark.parametrize('skip_special_tokens', [True, False]) def test_tokenizer(model_path, input, interval, add_special_tokens, skip_special_tokens): tokenizer = Tokenizer(model_path, trust_remote_code=True).model - encoded = tokenizer.encode(input, False, add_special_tokens=add_special_tokens) + encoded = tokenizer.encode(input, add_special_tokens=add_special_tokens) output = '' input = tokenizer.decode(encoded, skip_special_tokens=skip_special_tokens) state = DetokenizeState() @@ -54,7 +54,7 @@ def test_glm4_special_token(): speicial_token_ids = [i for i in range(151329, 151343)] for token, token_id in zip(special_tokens, speicial_token_ids): - _token_id = tokenizer.encode(token, add_bos=False) + _token_id = tokenizer.encode(token) assert len(_token_id) == 1 and _token_id[0] == token_id diff --git a/tests/test_lmdeploy/test_vl/test_hf_chat_template.py b/tests/test_lmdeploy/test_vl/test_hf_chat_template.py index b6eabf8a54..fa9f720482 100644 --- a/tests/test_lmdeploy/test_vl/test_hf_chat_template.py +++ b/tests/test_lmdeploy/test_vl/test_hf_chat_template.py @@ -80,7 +80,7 @@ def test_proc_messages(self, models, mock_messages): return_dict=True) # InternVL-HF and InternS1 models pad and internally reference = reference.replace('', '') - prompt, _ = model.proc_messages(mock_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_messages, chat_template) assert prompt == reference def test_proc_pure_img_messages(self, models, mock_pure_img_messages): @@ -92,7 +92,7 @@ def test_proc_pure_img_messages(self, models, mock_pure_img_messages): return_dict=True) # InternVL-HF and InternS1 models pad and internally reference = reference.replace('', '') - prompt, _ = model.proc_messages(mock_pure_img_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_pure_img_messages, chat_template) assert prompt == reference def test_proc_pure_text_messages(self, models, mock_pure_text_messages): @@ -102,7 +102,7 @@ def test_proc_pure_text_messages(self, models, mock_pure_text_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt, _ = model.proc_messages(mock_pure_text_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_pure_text_messages, chat_template) assert prompt == reference @@ -129,7 +129,7 @@ def test_proc_messages(self, models, mock_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt, _ = model.proc_messages(mock_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_messages, chat_template) assert prompt == reference def test_pure_img_messages(self, models, mock_pure_img_messages): @@ -139,7 +139,7 @@ def test_pure_img_messages(self, models, mock_pure_img_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt, _ = model.proc_messages(mock_pure_img_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_pure_img_messages, chat_template) assert prompt == reference def test_pure_text_messages(self, models, mock_pure_text_messages): @@ -149,7 +149,7 @@ def test_pure_text_messages(self, models, mock_pure_text_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt, _ = model.proc_messages(mock_pure_text_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_pure_text_messages, chat_template) assert prompt == reference @@ -181,7 +181,7 @@ def test_get_input_prompt(self, models, mock_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt = model.get_input_prompt(mock_messages, chat_template, sequence_start=True) + prompt = model.get_input_prompt(mock_messages, chat_template) assert prompt == reference def test_pure_img_messages(self, models, mock_pure_img_messages): @@ -191,7 +191,7 @@ def test_pure_img_messages(self, models, mock_pure_img_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt = model.get_input_prompt(mock_pure_img_messages, chat_template, sequence_start=True) + prompt = model.get_input_prompt(mock_pure_img_messages, chat_template) assert prompt == reference def test_pure_text_messages(self, models, mock_pure_text_messages): @@ -201,5 +201,5 @@ def test_pure_text_messages(self, models, mock_pure_text_messages): add_generation_prompt=True, tokenize=False, return_dict=True) - prompt = model.get_input_prompt(mock_pure_text_messages, chat_template, sequence_start=True) + prompt = model.get_input_prompt(mock_pure_text_messages, chat_template) assert prompt == reference diff --git a/tests/test_lmdeploy/test_vl/test_nonhf_chat_template.py b/tests/test_lmdeploy/test_vl/test_nonhf_chat_template.py index bf6399647b..e28702a62c 100644 --- a/tests/test_lmdeploy/test_vl/test_nonhf_chat_template.py +++ b/tests/test_lmdeploy/test_vl/test_nonhf_chat_template.py @@ -94,7 +94,7 @@ def test_internvl3_5(self, internvl3_5, mock_messages): <|im_start|>assistant """ for model, chat_template in internvl3_5: - prompt, _ = model.proc_messages(mock_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_messages, chat_template) assert prompt == reference @@ -107,7 +107,7 @@ def test_internvl3_5_backward_compatibility(self, internvl3_5, mock_IMAGE_TOKEN_ <|im_start|>assistant """ for model, chat_template in internvl3_5: - prompt, _ = model.proc_messages(mock_IMAGE_TOKEN_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_IMAGE_TOKEN_messages, chat_template) assert prompt == reference def test_internvl3(self, internvl3, mock_messages): @@ -120,7 +120,7 @@ def test_internvl3(self, internvl3, mock_messages): <|im_start|>assistant """ for model, chat_template in internvl3: - prompt, _ = model.proc_messages(mock_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_messages, chat_template) assert prompt == reference def test_internvl3_backward_compatibility(self, internvl3, mock_IMAGE_TOKEN_messages): @@ -132,7 +132,7 @@ def test_internvl3_backward_compatibility(self, internvl3, mock_IMAGE_TOKEN_mess <|im_start|>assistant """ for model, chat_template in internvl3: - prompt, _ = model.proc_messages(mock_IMAGE_TOKEN_messages, chat_template, sequence_start=True) + prompt, _ = model.proc_messages(mock_IMAGE_TOKEN_messages, chat_template) assert prompt == reference def test_internvl2(self, internvl2, mock_messages): @@ -143,8 +143,7 @@ def test_internvl2(self, internvl2, mock_messages): <|im_start|>assistant """ for model, chat_template in internvl2: - # Let sequence_start=False to avoid the begin-of-prompt token, such as <|begin_of_text|>, - prompt, _ = model.proc_messages(mock_messages, chat_template, sequence_start=False) + prompt, _ = model.proc_messages(mock_messages, chat_template) assert prompt == reference def test_internvl2_backward_compatibility(self, internvl2, mock_IMAGE_TOKEN_messages): @@ -156,6 +155,5 @@ def test_internvl2_backward_compatibility(self, internvl2, mock_IMAGE_TOKEN_mess <|im_start|>assistant """ for model, chat_template in internvl2: - # Let sequence_start=False to avoid the begin-of-prompt token, such as <|begin_of_text|>, - prompt, _ = model.proc_messages(mock_IMAGE_TOKEN_messages, chat_template, sequence_start=False) + prompt, _ = model.proc_messages(mock_IMAGE_TOKEN_messages, chat_template) assert prompt == reference diff --git a/tests/test_lmdeploy/test_vl/test_qwen3vl_processor.py b/tests/test_lmdeploy/test_vl/test_qwen3vl_processor.py index 5e13d4b4bf..5f4f2cb641 100644 --- a/tests/test_lmdeploy/test_vl/test_qwen3vl_processor.py +++ b/tests/test_lmdeploy/test_vl/test_qwen3vl_processor.py @@ -47,7 +47,7 @@ def _preprocess(model, messages, mm_processor_kwargs=None): """ from lmdeploy.model import MODELS chat_template = MODELS.module_dict['hf'](model_path=model.model_path) - input_prompt = model.get_input_prompt(messages, chat_template, sequence_start=True) + input_prompt = model.get_input_prompt(messages, chat_template) result = model.preprocess(messages=list(messages), input_prompt=input_prompt, mm_processor_kwargs=mm_processor_kwargs) return result['multimodal'][0] @@ -117,7 +117,7 @@ def _preprocess_by_modality(model, messages, mm_processor_kwargs=None): modality.""" from lmdeploy.model import MODELS chat_template = MODELS.module_dict['hf'](model_path=model.model_path) - input_prompt = model.get_input_prompt(messages, chat_template, sequence_start=True) + input_prompt = model.get_input_prompt(messages, chat_template) result = model.preprocess(messages=list(messages), input_prompt=input_prompt, mm_processor_kwargs=mm_processor_kwargs) by_modality = {}