FoundationVision/LlamaGen
64
1# Modified from:2# vLLM: https://github.com/vllm-project/vllm/blob/main/vllm/engine/llm_engine.py3import time4from typing import Iterable, List, Optional, Type, Union5import argparse6 7from transformers import GenerationConfig, PreTrainedTokenizer8 9import vllm10from vllm.config import (CacheConfig, DecodingConfig, DeviceConfig, LoadConfig,11 LoRAConfig, ModelConfig, ParallelConfig,12 SchedulerConfig, SpeculativeConfig,13 VisionLanguageConfig)14from vllm.core.scheduler import Scheduler, SchedulerOutputs15from vllm.engine.arg_utils import EngineArgs16from vllm.engine.metrics import StatLogger, Stats17from vllm.engine.output_processor.interfaces import (18 SequenceGroupOutputProcessor)19from vllm.engine.output_processor.stop_checker import StopChecker20from vllm.engine.output_processor.util import create_output_by_sequence_group21from vllm.engine.ray_utils import initialize_ray_cluster22from vllm.executor.executor_base import ExecutorBase23from vllm.logger import init_logger24from vllm.lora.request import LoRARequest25from vllm.outputs import RequestOutput26from vllm.sampling_params import SamplingParams27from vllm.sequence import (MultiModalData, SamplerOutput, Sequence,28 SequenceGroup)29from vllm.transformers_utils.detokenizer import Detokenizer30from vllm.transformers_utils.tokenizer_group import (BaseTokenizerGroup,31 get_tokenizer_group)32from vllm.usage.usage_lib import (UsageContext, is_usage_stats_enabled,33 usage_message)34from vllm.utils import Counter35 36logger = init_logger(__name__)37_LOCAL_LOGGING_INTERVAL_SEC = 538 39 40def _load_generation_config_dict(model_config: ModelConfig):41 try:42 return GenerationConfig.from_pretrained(43 model_config.model,44 revision=model_config.revision,45 ).to_diff_dict()46 except OSError:47 # Not found.48 return {}49 50 51class LLMEngine:52 """An LLM engine that receives requests and generates texts.53 54 This is the main class for the vLLM engine. It receives requests55 from clients and generates texts from the LLM. It includes a tokenizer, a56 language model (possibly distributed across multiple GPUs), and GPU memory57 space allocated for intermediate states (aka KV cache). This class utilizes58 iteration-level scheduling and efficient memory management to maximize the59 serving throughput.60 61 The `LLM` class wraps this class for offline batched inference and the62 `AsyncLLMEngine` class wraps this class for online serving.63 64 NOTE: The config arguments are derived from the `EngineArgs` class. For the65 comprehensive list of arguments, see `EngineArgs`.66 67 Args:68 model_config: The configuration related to the LLM model.69 cache_config: The configuration related to the KV cache memory70 management.71 parallel_config: The configuration related to distributed execution.72 scheduler_config: The configuration related to the request scheduler.73 device_config: The configuration related to the device.74 lora_config (Optional): The configuration related to serving multi-LoRA.75 vision_language_config (Optional): The configuration related to vision76 language models.77 speculative_config (Optional): The configuration related to speculative78 decoding.79 executor_class: The model executor class for managing distributed80 execution.81 log_stats: Whether to log statistics.82 usage_context: Specified entry point, used for usage info collection83 """84 85 def __init__(86 self,87 args: argparse.ArgumentParser,88 model_config: ModelConfig,89 cache_config: CacheConfig,90 parallel_config: ParallelConfig,91 scheduler_config: SchedulerConfig,92 device_config: DeviceConfig,93 load_config: LoadConfig,94 lora_config: Optional[LoRAConfig],95 vision_language_config: Optional[VisionLanguageConfig],96 speculative_config: Optional[SpeculativeConfig],97 decoding_config: Optional[DecodingConfig],98 executor_class: Type[ExecutorBase],99 log_stats: bool,100 usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,101 ) -> None:102 logger.info(103 f"Initializing an LLM engine (v{vllm.__version__}) with config: "104 f"model={model_config.model!r}, "105 f"speculative_config={speculative_config!r}, "106 f"tokenizer={model_config.tokenizer!r}, "107 f"skip_tokenizer_init={model_config.skip_tokenizer_init}, "108 f"tokenizer_mode={model_config.tokenizer_mode}, "109 f"revision={model_config.revision}, "110 f"tokenizer_revision={model_config.tokenizer_revision}, "111 f"trust_remote_code={model_config.trust_remote_code}, "112 f"dtype={model_config.dtype}, "113 f"max_seq_len={model_config.max_model_len}, "114 f"download_dir={load_config.download_dir!r}, "115 f"load_format={load_config.load_format}, "116 f"tensor_parallel_size={parallel_config.tensor_parallel_size}, "117 f"disable_custom_all_reduce="118 f"{parallel_config.disable_custom_all_reduce}, "119 f"quantization={model_config.quantization}, "120 f"enforce_eager={model_config.enforce_eager}, "121 f"kv_cache_dtype={cache_config.cache_dtype}, "122 f"quantization_param_path={model_config.quantization_param_path}, "123 f"device_config={device_config.device}, "124 f"decoding_config={decoding_config!r}, "125 f"seed={model_config.seed})")126 # TODO(woosuk): Print more configs in debug mode.127 128 self.model_config = model_config129 self.cache_config = cache_config130 self.lora_config = lora_config131 self.vision_language_config = vision_language_config132 self.parallel_config = parallel_config133 self.scheduler_config = scheduler_config134 self.device_config = device_config135 self.speculative_config = speculative_config136 self.load_config = load_config137 self.decoding_config = decoding_config or DecodingConfig()138 self.log_stats = log_stats139 140 if not self.model_config.skip_tokenizer_init:141 self.tokenizer: BaseTokenizerGroup142 self._init_tokenizer()143 self.detokenizer = Detokenizer(self.tokenizer)144 else:145 self.detokenizer = None146 self.tokenizer = None147 148 self.seq_counter = Counter()149 self.generation_config_fields = _load_generation_config_dict(150 model_config)151 152 self.model_executor = executor_class(153 args=args,154 model_config=model_config,155 cache_config=cache_config,156 parallel_config=parallel_config,157 scheduler_config=scheduler_config,158 device_config=device_config,159 lora_config=lora_config,160 vision_language_config=vision_language_config,161 speculative_config=speculative_config,162 load_config=load_config,163 )164 165 self._initialize_kv_caches()166 167 # If usage stat is enabled, collect relevant info.168 if is_usage_stats_enabled():169 from vllm.model_executor.model_loader import (170 get_architecture_class_name)171 usage_message.report_usage(172 get_architecture_class_name(model_config),173 usage_context,174 extra_kvs={175 # Common configuration176 "dtype":177 str(model_config.dtype),178 "tensor_parallel_size":179 parallel_config.tensor_parallel_size,180 "block_size":181 cache_config.block_size,182 "gpu_memory_utilization":183 cache_config.gpu_memory_utilization,184 185 # Quantization186 "quantization":187 model_config.quantization,188 "kv_cache_dtype":189 cache_config.cache_dtype,190 191 # Feature flags192 "enable_lora":193 bool(lora_config),194 "enable_prefix_caching":195 cache_config.enable_prefix_caching,196 "enforce_eager":197 model_config.enforce_eager,198 "disable_custom_all_reduce":199 parallel_config.disable_custom_all_reduce,200 })201 202 if self.tokenizer:203 # Ping the tokenizer to ensure liveness if it runs in a204 # different process.205 self.tokenizer.ping()206 207 # Create the scheduler.208 # NOTE: the cache_config here have been updated with the numbers of209 # GPU and CPU blocks, which are profiled in the distributed executor.210 self.scheduler = Scheduler(scheduler_config, cache_config, lora_config)211 212 # Metric Logging.213 if self.log_stats:214 self.stat_logger = StatLogger(215 local_interval=_LOCAL_LOGGING_INTERVAL_SEC,216 labels=dict(model_name=model_config.model))217 self.stat_logger.info("cache_config", self.cache_config)218 219 # Create sequence output processor, e.g. for beam search or220 # speculative decoding.221 self.output_processor = (222 SequenceGroupOutputProcessor.create_output_processor(223 self.scheduler_config,224 self.detokenizer,225 self.scheduler,226 self.seq_counter,227 self.get_tokenizer_for_seq,228 stop_checker=StopChecker(229 self.scheduler_config.max_model_len,230 self.get_tokenizer_for_seq,231 ),232 ))233 234 def _initialize_kv_caches(self) -> None:235 """Initialize the KV cache in the worker(s).236 237 The workers will determine the number of blocks in both the GPU cache238 and the swap CPU cache.239 """240 num_gpu_blocks, num_cpu_blocks = (241 self.model_executor.determine_num_available_blocks())242 243 if self.cache_config.num_gpu_blocks_override is not None:244 num_gpu_blocks_override = self.cache_config.num_gpu_blocks_override245 logger.info(f"Overriding {num_gpu_blocks=} with "246 f"{num_gpu_blocks_override=}")247 num_gpu_blocks = num_gpu_blocks_override248 249 self.cache_config.num_gpu_blocks = num_gpu_blocks250 self.cache_config.num_cpu_blocks = num_cpu_blocks251 252 self.model_executor.initialize_cache(num_gpu_blocks, num_cpu_blocks)253 254 @classmethod255 def from_engine_args(256 cls,257 engine_args: EngineArgs,258 usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,259 args: argparse.ArgumentParser = None,260 ) -> "LLMEngine":261 """Creates an LLM engine from the engine arguments."""262 # Create the engine configs.263 engine_config = engine_args.create_engine_config()264 265 # Initialize the cluster and specify the executor class.266 if engine_config.device_config.device_type == "neuron":267 from vllm.executor.neuron_executor import NeuronExecutor268 executor_class = NeuronExecutor269 elif engine_config.device_config.device_type == "cpu":270 from vllm.executor.cpu_executor import CPUExecutor271 executor_class = CPUExecutor272 elif engine_config.parallel_config.worker_use_ray:273 initialize_ray_cluster(engine_config.parallel_config)274 from vllm.executor.ray_gpu_executor import RayGPUExecutor275 executor_class = RayGPUExecutor276 else:277 assert engine_config.parallel_config.world_size == 1, (278 "Ray is required if parallel_config.world_size > 1.")279 # from vllm.executor.gpu_executor import GPUExecutor280 from serve.gpu_executor import GPUExecutor281 executor_class = GPUExecutor282 283 # Create the LLM engine.284 engine = cls(285 **engine_config.to_dict(),286 executor_class=executor_class,287 log_stats=not engine_args.disable_log_stats,288 usage_context=usage_context,289 args=args,290 )291 return engine292 293 def __reduce__(self):294 # This is to ensure that the LLMEngine is not referenced in295 # the closure used to initialize Ray worker actors296 raise RuntimeError("LLMEngine should not be pickled!")297 298 def get_tokenizer(self) -> "PreTrainedTokenizer":299 return self.tokenizer.get_lora_tokenizer(None)300 301 def get_tokenizer_for_seq(self,302 sequence: Sequence) -> "PreTrainedTokenizer":303 return self.tokenizer.get_lora_tokenizer(sequence.lora_request)304 305 def _init_tokenizer(self, **tokenizer_init_kwargs):306 init_kwargs = dict(307 tokenizer_id=self.model_config.tokenizer,308 enable_lora=bool(self.lora_config),309 max_num_seqs=self.scheduler_config.max_num_seqs,310 max_input_length=None,311 tokenizer_mode=self.model_config.tokenizer_mode,312 trust_remote_code=self.model_config.trust_remote_code,313 revision=self.model_config.tokenizer_revision)314 init_kwargs.update(tokenizer_init_kwargs)315 self.tokenizer = get_tokenizer_group(316 self.parallel_config.tokenizer_pool_config, **init_kwargs)317 318 def _verify_args(self) -> None:319 self.model_config.verify_with_parallel_config(self.parallel_config)320 self.cache_config.verify_with_parallel_config(self.parallel_config)321 if self.lora_config:322 self.lora_config.verify_with_model_config(self.model_config)323 self.lora_config.verify_with_scheduler_config(324 self.scheduler_config)325 326 def encode_request(327 self,328 request_id: str, # pylint: disable=unused-argument329 prompt: Optional[str],330 prompt_token_ids: Optional[List[int]] = None,331 lora_request: Optional[LoRARequest] = None,332 ):333 if prompt_token_ids is None:334 assert prompt is not None335 prompt_token_ids = self.tokenizer.encode(request_id=request_id,336 prompt=prompt,337 lora_request=lora_request)338 return prompt_token_ids339 340 def add_request(341 self,342 request_id: str,343 prompt: Optional[str],344 sampling_params: SamplingParams,345 prompt_token_ids: Optional[List[int]] = None,346 arrival_time: Optional[float] = None,347 lora_request: Optional[LoRARequest] = None,348 multi_modal_data: Optional[MultiModalData] = None,349 ) -> None:350 """Add a request to the engine's request pool.351 352 The request is added to the request pool and will be processed by the353 scheduler as `engine.step()` is called. The exact scheduling policy is354 determined by the scheduler.355 356 Args:357 request_id: The unique ID of the request.358 prompt: The prompt string. Can be None if prompt_token_ids is359 provided.360 sampling_params: The sampling parameters for text generation.361 prompt_token_ids: The token IDs of the prompt. If None, we362 use the tokenizer to convert the prompts to token IDs.363 arrival_time: The arrival time of the request. If None, we use364 the current monotonic time.365 multi_modal_data: Multi modal data per request.366 367 Details:368 - Set arrival_time to the current time if it is None.369 - Set prompt_token_ids to the encoded prompt if it is None.370 - Create `best_of` number of :class:`~vllm.Sequence` objects.371 - Create a :class:`~vllm.SequenceGroup` object372 from the list of :class:`~vllm.Sequence`.373 - Add the :class:`~vllm.SequenceGroup` object to the scheduler.374 375 Example:376 >>> # initialize engine377 >>> engine = LLMEngine.from_engine_args(engine_args)378 >>> # set request arguments379 >>> example_prompt = "Who is the president of the United States?"380 >>> sampling_params = SamplingParams(temperature=0.0)381 >>> request_id = 0382 >>>383 >>> # add the request to the engine384 >>> engine.add_request(385 >>> str(request_id),386 >>> example_prompt,387 >>> SamplingParams(temperature=0.0))388 >>> # continue the request processing389 >>> ...390 """391 if lora_request is not None and not self.lora_config:392 raise ValueError(f"Got lora_request {lora_request} but LoRA is "393 "not enabled!")394 max_logprobs = self.get_model_config().max_logprobs395 if (sampling_params.logprobs396 and sampling_params.logprobs > max_logprobs) or (397 sampling_params.prompt_logprobs398 and sampling_params.prompt_logprobs > max_logprobs):399 raise ValueError(f"Cannot request more than "400 f"{max_logprobs} logprobs.")401 if arrival_time is None:402 arrival_time = time.time()403 prompt_token_ids = self.encode_request(404 request_id=request_id,405 prompt=prompt,406 prompt_token_ids=prompt_token_ids,407 lora_request=lora_request)408 409 # Create the sequences.410 block_size = self.cache_config.block_size411 seq_id = next(self.seq_counter)412 eos_token_id = None413 if self.tokenizer:414 eos_token_id = self.tokenizer.get_lora_tokenizer(415 lora_request).eos_token_id416 else:417 logger.warning("Use None for EOS token id because tokenizer is "418 "not initialized")419 seq = Sequence(seq_id, prompt, prompt_token_ids, block_size,420 eos_token_id, lora_request)421 422 # Defensive copy of SamplingParams, which are used by the sampler,423 # this doesn't deep-copy LogitsProcessor objects424 sampling_params = sampling_params.clone()425 # Add the eos token id into the sampling_params to support min_tokens426 # processing427 if seq.eos_token_id is not None:428 sampling_params.all_stop_token_ids.add(seq.eos_token_id)429 sampling_params.update_from_generation_config(430 self.generation_config_fields)431 432 # Create the sequence group.433 seq_group = SequenceGroup(request_id, [seq], sampling_params,434 arrival_time, lora_request, multi_modal_data)435 436 # Add the sequence group to the scheduler.437 self.scheduler.add_seq_group(seq_group)438 439 def abort_request(self, request_id: Union[str, Iterable[str]]) -> None:440 """Aborts a request(s) with the given ID.441 442 Args:443 request_id: The ID(s) of the request to abort.444 445 Details:446 - Refer to the447 :meth:`~vllm.core.scheduler.Scheduler.abort_seq_group`448 from class :class:`~vllm.core.scheduler.Scheduler`.449 450 Example:451 >>> # initialize engine and add a request with request_id452 >>> request_id = str(0)453 >>> # abort the request454 >>> engine.abort_request(request_id)455 """456 self.scheduler.abort_seq_group(request_id)457 458 def get_model_config(self) -> ModelConfig:459 """Gets the model configuration."""460 return self.model_config461 462 def get_num_unfinished_requests(self) -> int:463 """Gets the number of unfinished requests."""464 return self.scheduler.get_num_unfinished_seq_groups()465 466 def has_unfinished_requests(self) -> bool:467 """Returns True if there are unfinished requests."""468 return self.scheduler.has_unfinished_seqs()469 470 def _process_model_outputs(471 self, output: List[SamplerOutput],472 scheduled_seq_groups: List[SequenceGroup],473 ignored_seq_groups: List[SequenceGroup]) -> List[RequestOutput]:474 """Apply the model output to the sequences in the scheduled seq groups.475 476 Returns RequestOutputs that can be returned to the client.477 """478 now = time.time()479 480 # Organize outputs by [sequence group][step] instead of481 # [step][sequence group].482 output_by_sequence_group = create_output_by_sequence_group(483 sampler_outputs=output, num_seq_groups=len(scheduled_seq_groups))484 485 # Update the scheduled sequence groups with the model outputs.486 for scheduled_seq_group, outputs in zip(scheduled_seq_groups,487 output_by_sequence_group):488 seq_group = scheduled_seq_group.seq_group489 seq_group.update_num_computed_tokens(490 scheduled_seq_group.token_chunk_size)491 # If uncomputed tokens > 0, it means prefill is chunked.492 # We don't need to process outputs in that case.493 if seq_group.get_num_uncomputed_tokens() == 0:494 self.output_processor.process_outputs(seq_group, outputs)495 496 # Free the finished sequence groups.497 self.scheduler.free_finished_seq_groups()498 499 # Create the outputs.500 request_outputs: List[RequestOutput] = []501 for scheduled_seq_group in scheduled_seq_groups:502 seq_group = scheduled_seq_group.seq_group503 seq_group.maybe_set_first_token_time(now)504 request_output = RequestOutput.from_seq_group(seq_group)505 request_outputs.append(request_output)506 for seq_group in ignored_seq_groups:507 request_output = RequestOutput.from_seq_group(seq_group)508 request_outputs.append(request_output)509 return request_outputs510 511 def step(self) -> List[RequestOutput]:512 """Performs one decoding iteration and returns newly generated results.513 514 .. figure:: https://i.imgur.com/sv2HssD.png515 :alt: Overview of the step function516 :align: center517 518 Overview of the step function.519 520 Details:521 - Step 1: Schedules the sequences to be executed in the next522 iteration and the token blocks to be swapped in/out/copy.523 524 - Depending on the scheduling policy,525 sequences may be `preempted/reordered`.526 - A Sequence Group (SG) refer to a group of sequences527 that are generated from the same prompt.528 529 - Step 2: Calls the distributed executor to execute the model.530 - Step 3: Processes the model output. This mainly includes:531 532 - Decodes the relevant outputs.533 - Updates the scheduled sequence groups with model outputs534 based on its `sampling parameters` (`use_beam_search` or not).535 - Frees the finished sequence groups.536 537 - Finally, it creates and returns the newly generated results.538 539 Example:540 >>> # Please see the example/ folder for more detailed examples.541 >>>542 >>> # initialize engine and request arguments543 >>> engine = LLMEngine.from_engine_args(engine_args)544 >>> example_inputs = [(0, "What is LLM?",545 >>> SamplingParams(temperature=0.0))]546 >>>547 >>> # Start the engine with an event loop548 >>> while True:549 >>> if example_inputs:550 >>> req_id, prompt, sampling_params = example_inputs.pop(0)551 >>> engine.add_request(str(req_id), prompt, sampling_params)552 >>>553 >>> # continue the request processing554 >>> request_outputs = engine.step()555 >>> for request_output in request_outputs:556 >>> if request_output.finished:557 >>> # return or show the request output558 >>>559 >>> if not (engine.has_unfinished_requests() or example_inputs):560 >>> break561 """562 seq_group_metadata_list, scheduler_outputs = self.scheduler.schedule()563 if not scheduler_outputs.is_empty():564 output = self.model_executor.execute_model(565 seq_group_metadata_list=seq_group_metadata_list,566 blocks_to_swap_in=scheduler_outputs.blocks_to_swap_in,567 blocks_to_swap_out=scheduler_outputs.blocks_to_swap_out,568 blocks_to_copy=scheduler_outputs.blocks_to_copy,569 num_lookahead_slots=scheduler_outputs.num_lookahead_slots)570 else:571 output = []572 573 request_outputs = self._process_model_outputs(574 output, scheduler_outputs.scheduled_seq_groups,575 scheduler_outputs.ignored_seq_groups)576 577 # Log stats.578 if self.log_stats:579 self.stat_logger.log(self._get_stats(scheduler_outputs))580 581 return request_outputs582 583 def do_log_stats(self) -> None:584 """Forced log when no requests active."""585 if self.log_stats:586 self.stat_logger.log(self._get_stats(scheduler_outputs=None))587 588 def _get_stats(self,589 scheduler_outputs: Optional[SchedulerOutputs]) -> Stats:590 """Get Stats to be Logged to Prometheus."""591 now = time.time()592 593 # KV Cache Usage in %.594 num_total_gpu = self.cache_config.num_gpu_blocks595 num_free_gpu = self.scheduler.block_manager.get_num_free_gpu_blocks()596 gpu_cache_usage = 1.0 - (num_free_gpu / num_total_gpu)597 598 num_total_cpu = self.cache_config.num_cpu_blocks599 cpu_cache_usage = 0.600 if num_total_cpu > 0:601 num_free_cpu = self.scheduler.block_manager.get_num_free_cpu_blocks(602 )603 cpu_cache_usage = 1.0 - (num_free_cpu / num_total_cpu)604 605 # Scheduler State606 num_running = len(self.scheduler.running)607 num_swapped = len(self.scheduler.swapped)608 num_waiting = len(self.scheduler.waiting)609 610 # Iteration stats if we have scheduler output.611 num_prompt_tokens = 0612 num_generation_tokens = 0613 time_to_first_tokens = []614 time_per_output_tokens = []615 time_e2e_requests = []616 if scheduler_outputs is not None:617 prompt_run = scheduler_outputs.num_prefill_groups > 0618 619 # Number of Tokens.620 if prompt_run:621 num_prompt_tokens = sum(622 len(scheduled_seq_group.seq_group.prompt_token_ids)623 for scheduled_seq_group in624 scheduler_outputs.scheduled_seq_groups)625 num_generation_tokens = sum(626 scheduled_seq_group.seq_group.num_seqs()627 for scheduled_seq_group in628 scheduler_outputs.scheduled_seq_groups)629 else:630 num_generation_tokens = scheduler_outputs.num_batched_tokens631 632 # Latency Timings.633 time_last_iters = []634 for scheduled_seq_group in scheduler_outputs.scheduled_seq_groups:635 seq_group = scheduled_seq_group.seq_group636 # Time since last token.637 # (n.b. updates seq_group.metrics.last_token_time)638 time_last_iters.append(seq_group.get_last_latency(now))639 # Time since arrival for all finished requests.640 if seq_group.is_finished():641 time_e2e_requests.append(now -642 seq_group.metrics.arrival_time)643 644 time_to_first_tokens = time_last_iters if prompt_run else []645 time_per_output_tokens = [] if prompt_run else time_last_iters646 647 return Stats(648 now=now,649 num_running=num_running,650 num_swapped=num_swapped,651 num_waiting=num_waiting,652 gpu_cache_usage=gpu_cache_usage,653 cpu_cache_usage=cpu_cache_usage,654 num_prompt_tokens=num_prompt_tokens,655 num_generation_tokens=num_generation_tokens,656 time_to_first_tokens=time_to_first_tokens,657 time_per_output_tokens=time_per_output_tokens,658 time_e2e_requests=time_e2e_requests,659 )660 661 def add_lora(self, lora_request: LoRARequest) -> bool:662 return self.model_executor.add_lora(lora_request)663 664 def remove_lora(self, lora_id: int) -> bool:665 return self.model_executor.remove_lora(lora_id)666 667 def list_loras(self) -> List[int]:668 return self.model_executor.list_loras()669 670 def check_health(self) -> None:671 self.model_executor.check_health()