Medgemma / models /model_runner.py
anzhi2710gmailcom's picture
Upload folder using huggingface_hub
fedd8d3 verified
Raw
History Blame Contribute Delete
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