Medgemma / models /medgemma.py
anzhi2710gmailcom's picture
Upload folder using huggingface_hub
fedd8d3 verified
Raw
History Blame Contribute Delete
7.75 kB
# MedGemma 主模型类
# 继承 OneScience Module 基类,集成到 OneScience 框架
import logging
from typing import Any, Dict, List, Optional
import torch
import torch.nn as nn
from onescience.modules.module import Module
from models.model_runner import VLLMModelRunner, TransformersModelRunner
from models.predictor_wrapper import MedGemmaPredictor
logger = logging.getLogger(__name__)
class MedGemma(Module):
"""
MedGemma: 医学大语言模型
支持:
- 4B 多模态模型(文本 + 医学图像)
- 27B 文本模型
- DICOM/CT/CXR/WSI 图像输入
- OpenAI Chat Completion API 格式
继承 OneScience Module 基类,提供统一接口
"""
def __init__(self, configs: Any) -> None:
"""
初始化 MedGemma 模型
Args:
configs: 配置对象(ConfigDict)
"""
super(MedGemma, self).__init__()
self.configs = configs
# 模型变体(4B 或 27B)
self.model_variant = configs.model.variant
self.is_multimodal = configs.model.is_multimodal
logger.info(f"Initializing MedGemma {self.model_variant} model")
logger.info(f"Multimodal: {self.is_multimodal}")
# 初始化模型运行器
self._init_model_runner()
# 初始化推理包装器
self.predictor = MedGemmaPredictor(
model_runner=self.model_runner,
configs=configs,
)
logger.info("MedGemma model initialized successfully")
def _init_model_runner(self):
"""初始化模型运行器(vLLM 或 Transformers)"""
if self.configs.inference.use_vllm:
try:
self.model_runner = VLLMModelRunner(
model_path=self.configs.model.model_path,
tokenizer_path=self.configs.model.tokenizer_path,
gpu_memory_utilization=self.configs.inference.gpu_memory_utilization,
max_model_len=self.configs.inference.max_model_len,
tensor_parallel_size=self.configs.inference.tensor_parallel_size,
)
logger.info("Using vLLM model runner")
except Exception as e:
logger.warning(f"vLLM initialization failed: {e}")
logger.info("Falling back to Transformers model runner")
self._init_transformers_runner()
else:
self._init_transformers_runner()
def _init_transformers_runner(self):
"""初始化 Transformers 运行器"""
device = "cuda" if torch.cuda.is_available() else "cpu"
self.model_runner = TransformersModelRunner(
model_path=self.configs.model.model_path,
tokenizer_path=self.configs.model.tokenizer_path,
device=device,
)
logger.info("Using Transformers model runner")
def forward(
self,
messages: List[Dict[str, Any]],
max_tokens: Optional[int] = None,
temperature: float = 0.7,
top_p: float = 0.9,
n: int = 1,
) -> Dict[str, Any]:
"""
前向传播(使用 OpenAI Chat Completion 格式)
Args:
messages: 消息列表,格式为 [{"role": "user", "content": "..."}]
max_tokens: 最大生成 token 数
temperature: 采样温度(0-2)
top_p: Nucleus 采样参数(0-1)
n: 生成数量
Returns:
OpenAI 格式的响应字典
"""
if max_tokens is None:
max_tokens = self.configs.inference.default_max_tokens
return self.predictor.predict(
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
n=n,
)
@torch.no_grad()
def inference(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
推理方法(兼容 BiologyInferenceRunner)
Args:
data: 输入数据字典,可包含:
- messages: 消息列表
- instances: 实例列表(用于批处理)
- parameters: 推理参数
Returns:
预测结果字典
"""
# 转换数据格式
if "instances" in data:
messages = self._convert_instances_to_messages(data["instances"])
elif "messages" in data:
messages = data["messages"]
else:
raise ValueError("Input data must contain 'messages' or 'instances'")
# 提取推理参数
parameters = data.get("parameters", {})
max_tokens = parameters.get("max_tokens", self.configs.inference.default_max_tokens)
temperature = parameters.get("temperature", self.configs.inference.temperature)
top_p = parameters.get("top_p", self.configs.inference.top_p)
n = parameters.get("n", 1)
# 运行推理
return self.forward(
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
n=n,
)
def _convert_instances_to_messages(
self,
instances: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
将实例列表转换为消息格式
Args:
instances: 实例列表
Returns:
消息列表
"""
messages = []
for instance in instances:
if "role" in instance and "content" in instance:
messages.append(instance)
elif "text" in instance:
messages.append({"role": "user", "content": instance["text"]})
elif "question" in instance:
messages.append({"role": "user", "content": instance["question"]})
else:
logger.warning(f"Unknown instance format: {instance}")
return messages
def predict_text(
self,
text: str,
max_tokens: int = 500,
temperature: float = 0.7,
) -> str:
"""
简化的文本预测接口
Args:
text: 输入文本
max_tokens: 最大生成 token 数
temperature: 采样温度
Returns:
生成的文本
"""
messages = [{"role": "user", "content": text}]
response = self.forward(messages, max_tokens, temperature)
if response["choices"]:
return response["choices"][0]["message"]["content"]
return ""
def predict_multimodal(
self,
text: str,
images: List[Any],
max_tokens: int = 500,
temperature: float = 0.7,
) -> str:
"""
多模态预测接口(文本 + 图像)
Args:
text: 输入文本
images: 图像列表
max_tokens: 最大生成 token 数
temperature: 采样温度
Returns:
生成的文本
"""
if not self.is_multimodal:
logger.warning("Model is not multimodal, ignoring images")
return self.predict_text(text, max_tokens, temperature)
# TODO: 实现多模态推理
# 需要集成图像编码器和多模态提示格式
messages = [{"role": "user", "content": text}]
response = self.predictor.predict_with_images(
messages=messages,
images=images,
max_tokens=max_tokens,
temperature=temperature,
)
if response["choices"]:
return response["choices"][0]["message"]["content"]
return ""
def __repr__(self) -> str:
return f"MedGemma(variant={self.model_variant}, multimodal={self.is_multimodal})"