| |
| |
|
|
| import logging |
| from typing import Any, Dict, List, Optional, Set, Mapping |
| import numpy as np |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class VLLMModelRunner: |
| """ |
| 基于 vLLM 的 MedGemma 模型运行器 |
| 兼容 MedGemma serving_framework 的 ModelRunner 接口 |
| """ |
|
|
| def __init__( |
| self, |
| model_path: str, |
| tokenizer_path: Optional[str] = None, |
| gpu_memory_utilization: float = 0.9, |
| max_model_len: Optional[int] = None, |
| tensor_parallel_size: int = 1, |
| trust_remote_code: bool = True, |
| ): |
| """ |
| 初始化 vLLM 模型运行器 |
| |
| Args: |
| model_path: 模型权重路径 |
| tokenizer_path: Tokenizer 路径(默认与 model_path 相同) |
| gpu_memory_utilization: GPU 内存使用率 |
| max_model_len: 最大序列长度 |
| tensor_parallel_size: Tensor 并行大小 |
| trust_remote_code: 是否信任远程代码 |
| """ |
| self.model_path = model_path |
| self.tokenizer_path = tokenizer_path or model_path |
|
|
| try: |
| from vllm import LLM |
| self.vllm_available = True |
| except ImportError: |
| logger.warning("vLLM not available. Install with: pip install vllm") |
| self.vllm_available = False |
| self.llm = None |
| return |
|
|
| logger.info(f"Initializing vLLM with model: {model_path}") |
| self.llm = LLM( |
| model=self.model_path, |
| tokenizer=self.tokenizer_path, |
| gpu_memory_utilization=gpu_memory_utilization, |
| max_model_len=max_model_len, |
| tensor_parallel_size=tensor_parallel_size, |
| trust_remote_code=trust_remote_code, |
| ) |
| logger.info("vLLM model loaded successfully") |
|
|
| def run_model_multiple_output( |
| self, |
| model_input: Mapping[str, np.ndarray] | np.ndarray, |
| model_name: str = "default", |
| model_version: Optional[int] = None, |
| model_output_keys: Optional[Set[str]] = None, |
| parameters: Optional[Mapping[str, Any]] = None, |
| ) -> Mapping[str, np.ndarray]: |
| """ |
| 运行推理(兼容 MedGemma ModelRunner 接口) |
| |
| Args: |
| model_input: 输入数据(字典或 numpy 数组) |
| model_name: 模型名称 |
| model_version: 模型版本 |
| model_output_keys: 输出键集合 |
| parameters: 推理参数 |
| |
| Returns: |
| 输出字典 |
| """ |
| if not self.vllm_available or self.llm is None: |
| raise RuntimeError("vLLM is not available") |
|
|
| from vllm import SamplingParams |
|
|
| parameters = parameters or {} |
| model_output_keys = model_output_keys or {"text_output"} |
|
|
| |
| if isinstance(model_input, dict): |
| prompt = model_input.get("prompt", "") |
| if isinstance(prompt, np.ndarray): |
| prompt = prompt.tobytes().decode('utf-8') |
| else: |
| |
| prompt = model_input.tobytes().decode('utf-8') |
|
|
| |
| sampling_params = SamplingParams( |
| max_tokens=parameters.get("max_tokens", 500), |
| temperature=parameters.get("temperature", 0.7), |
| top_p=parameters.get("top_p", 0.9), |
| top_k=parameters.get("top_k", -1), |
| n=parameters.get("n", 1), |
| ) |
|
|
| |
| outputs = self.llm.generate([prompt], sampling_params) |
|
|
| |
| result = {} |
| if "text_output" in model_output_keys: |
| result["text_output"] = np.array([ |
| output.text.encode('utf-8') for output in outputs[0].outputs |
| ]) |
| if "num_input_tokens" in model_output_keys: |
| result["num_input_tokens"] = np.array([len(outputs[0].prompt_token_ids)]) |
| if "num_output_tokens" in model_output_keys: |
| result["num_output_tokens"] = np.array([ |
| len(output.token_ids) for output in outputs[0].outputs |
| ]) |
|
|
| return result |
|
|
| def generate( |
| self, |
| prompts: List[str], |
| max_tokens: int = 500, |
| temperature: float = 0.7, |
| top_p: float = 0.9, |
| top_k: int = -1, |
| n: int = 1, |
| ) -> List[Dict[str, Any]]: |
| """ |
| 简化的生成接口 |
| |
| Args: |
| prompts: 输入提示列表 |
| max_tokens: 最大生成 token 数 |
| temperature: 采样温度 |
| top_p: Nucleus 采样参数 |
| top_k: Top-K 采样参数 |
| n: 生成数量 |
| |
| Returns: |
| 生成结果列表 |
| """ |
| if not self.vllm_available or self.llm is None: |
| raise RuntimeError("vLLM is not available") |
|
|
| from vllm import SamplingParams |
|
|
| sampling_params = SamplingParams( |
| max_tokens=max_tokens, |
| temperature=temperature, |
| top_p=top_p, |
| top_k=top_k, |
| n=n, |
| ) |
|
|
| outputs = self.llm.generate(prompts, sampling_params) |
|
|
| results = [] |
| for output in outputs: |
| result = { |
| "prompt": output.prompt, |
| "outputs": [ |
| { |
| "text": o.text, |
| "token_ids": o.token_ids, |
| "cumulative_logprob": o.cumulative_logprob, |
| "finish_reason": o.finish_reason, |
| } |
| for o in output.outputs |
| ], |
| "num_input_tokens": len(output.prompt_token_ids), |
| } |
| results.append(result) |
|
|
| return results |
|
|
|
|
| class TransformersModelRunner: |
| """ |
| 基于 Transformers 的备用模型运行器 |
| 当 vLLM 不可用时使用 |
| """ |
|
|
| def __init__( |
| self, |
| model_path: str, |
| tokenizer_path: Optional[str] = None, |
| device: str = "cuda", |
| torch_dtype: str = "auto", |
| ): |
| """ |
| 初始化 Transformers 模型运行器 |
| |
| Args: |
| model_path: 模型路径 |
| tokenizer_path: Tokenizer 路径 |
| device: 设备(cuda 或 cpu) |
| torch_dtype: 数据类型 |
| """ |
| import torch |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
|
|
| self.device = device |
| self.tokenizer_path = tokenizer_path or model_path |
|
|
| logger.info(f"Loading model with transformers: {model_path}") |
| self.tokenizer = AutoTokenizer.from_pretrained(self.tokenizer_path) |
| self.model = AutoModelForCausalLM.from_pretrained( |
| model_path, |
| torch_dtype=torch_dtype if torch_dtype != "auto" else "auto", |
| device_map=device, |
| ) |
| logger.info("Model loaded successfully") |
|
|
| def generate( |
| self, |
| prompts: List[str], |
| max_tokens: int = 500, |
| temperature: float = 0.7, |
| top_p: float = 0.9, |
| top_k: int = 50, |
| n: int = 1, |
| ) -> List[Dict[str, Any]]: |
| """ |
| 生成文本 |
| |
| Args: |
| prompts: 输入提示列表 |
| max_tokens: 最大生成 token 数 |
| temperature: 采样温度 |
| top_p: Nucleus 采样参数 |
| top_k: Top-K 采样参数 |
| n: 生成数量 |
| |
| Returns: |
| 生成结果列表 |
| """ |
| import torch |
|
|
| results = [] |
| for prompt in prompts: |
| inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device) |
|
|
| with torch.no_grad(): |
| outputs = self.model.generate( |
| **inputs, |
| max_new_tokens=max_tokens, |
| temperature=temperature, |
| top_p=top_p, |
| top_k=top_k, |
| num_return_sequences=n, |
| do_sample=temperature > 0, |
| ) |
|
|
| generated_texts = [ |
| self.tokenizer.decode(output, skip_special_tokens=True) |
| for output in outputs |
| ] |
|
|
| result = { |
| "prompt": prompt, |
| "outputs": [ |
| { |
| "text": text, |
| "token_ids": None, |
| "cumulative_logprob": None, |
| "finish_reason": "stop", |
| } |
| for text in generated_texts |
| ], |
| "num_input_tokens": len(inputs["input_ids"][0]), |
| } |
| results.append(result) |
|
|
| return results |
|
|