Download models/model_runner.py from OneScience-Group/Medgemma: direct link, hf CLI and curl.
- Browser
- Download file 8.68 kB
-
https://huggingface.co/OneScience-Group/Medgemma/resolve/main/models/model_runner.py
- Command line
-
hf download hf://OneScience-Group/Medgemma/models/model_runner.py
-
curl -L -o model_runner.py https://huggingface.co/OneScience-Group/Medgemma/resolve/main/models/model_runner.py
8.68 kB
| # MedGemma vLLM 模型运行器 | |
| # 实现基于 vLLM 的推理引擎 | |
| 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"} | |
| # 提取 prompt | |
| if isinstance(model_input, dict): | |
| prompt = model_input.get("prompt", "") | |
| if isinstance(prompt, np.ndarray): | |
| prompt = prompt.tobytes().decode('utf-8') | |
| else: | |
| # 如果是 numpy 数组,解码为字符串 | |
| 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 | |