File size: 7,745 Bytes
fedd8d3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | # 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})"
|