File size: 5,301 Bytes
1e05592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
VLM模型加载和LoRA配置
支持多种VLM架构
"""

import torch
from transformers import (
    AutoModelForVision2Seq,
    AutoProcessor,
    AutoTokenizer
)
from peft import LoraConfig, get_peft_model, TaskType
from config import ModelConfig


def load_qwen25_vl_model(config: ModelConfig):
    """加载Qwen2.5-VL模型"""
    print(f"加载模型: {config.model_path}")
    
    # 加载processor
    processor = AutoProcessor.from_pretrained(
        config.model_path,
        trust_remote_code=True
    )
    
    # 加载模型 - 使用AutoModelForVision2Seq而不是特定类
    model_kwargs = {
        "trust_remote_code": True,
        "torch_dtype": torch.bfloat16,
    }
    
    if config.load_in_4bit:
        from transformers import BitsAndBytesConfig
        model_kwargs["quantization_config"] = BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_compute_dtype=torch.bfloat16,
            bnb_4bit_use_double_quant=True,
            bnb_4bit_quant_type="nf4"
        )
    elif config.load_in_8bit:
        model_kwargs["load_in_8bit"] = True
    
    # 使用AutoModelForVision2Seq自动识别模型类型
    model = AutoModelForVision2Seq.from_pretrained(
        config.model_path,
        **model_kwargs
    )
    
    try:
        model.config.use_cache = False
    except Exception:
        pass
    if hasattr(model, "gradient_checkpointing_enable"):
        model.gradient_checkpointing_enable()
    if hasattr(model, "enable_input_require_grads"):
        model.enable_input_require_grads()
    
    # 应用LoRA
    if config.use_lora:
        print("应用LoRA配置...")
        lora_config = LoraConfig(
            r=config.lora_r,
            lora_alpha=config.lora_alpha,
            target_modules=config.lora_target_modules,
            lora_dropout=config.lora_dropout,
            bias="none",
            task_type=TaskType.CAUSAL_LM
        )
        model = get_peft_model(model, lora_config)
        model.print_trainable_parameters()
    
    return model, processor


def prepare_qwen25_vl_inputs(processor, images, text_prompts, device):
    """
    准备Qwen2.5-VL的输入
    
    Args:
        processor: Qwen2VL processor
        images: List of PIL Images or List of List of PIL Images (for sequences)
        text_prompts: List of text prompts
        device: torch device
    
    Returns:
        inputs: 模型输入字典
    """
    messages_batch = []
    
    for i, (img, prompt) in enumerate(zip(images, text_prompts)):
        if isinstance(img, list):
            # 序列输入(任务3)
            content = []
            for frame in img:
                content.append({"type": "image", "image": frame})
            content.append({"type": "text", "text": prompt})
        else:
            # 单帧输入(任务1和2)
            content = [
                {"type": "image", "image": img},
                {"type": "text", "text": prompt}
            ]
        
        messages = [{"role": "user", "content": content}]
        messages_batch.append(messages)
    
    # 1) 只做“提示”(不包含答案),用于训练时对齐 labels
    texts = [
        processor.apply_chat_template(msg, tokenize=False, add_generation_prompt=True)
        for msg in messages_batch
    ]

    # 2) 图像必须是“按样本的列表”,多帧用 list-of-images
    images_nested = []
    for img in images:
        images_nested.append(img if isinstance(img, list) else [img])

   # 3) 构造模型输入
    inputs = processor(
        text=texts,
        images=images_nested,
        return_tensors="pt",
        padding=True,
        truncation=True,
    )

    # 保证有 pad_token_id
    tok = processor.tokenizer
    if tok.pad_token_id is None:
        tok.pad_token = tok.eos_token

    inputs = {k: v.to(device) for k, v in inputs.items()}
    # 同时把“提示文本”返回,后面构造对齐的 labels 要用
    inputs["__prompt_texts__"] = texts  # 仅供上层用,不会传给 model.forward
    return inputs
    
    # # 使用processor处理
    # texts = [
    #     processor.apply_chat_template(msg, tokenize=False, add_generation_prompt=True)
    #     for msg in messages_batch
    # ]
    
    # # 准备所有图像
    # all_images = []
    # for img in images:
    #     if isinstance(img, list):
    #         all_images.extend(img)
    #     else:
    #         all_images.append(img)
    
    # # 处理输入
    # inputs = processor(
    #     text=texts,
    #     images=all_images if all_images else None,
    #     return_tensors="pt",
    #     padding=True
    # )
    
    # return {k: v.to(device) for k, v in inputs.items()}


def load_model_and_processor(config: ModelConfig):
    """
    根据模型类型加载模型和processor
    """
    if config.model_type == "qwen2.5-vl":
        return load_qwen25_vl_model(config)
    else:
        raise ValueError(f"不支持的模型类型: {config.model_type}")


def prepare_model_inputs(processor, model_type, images, text_prompts, device):
    """
    根据模型类型准备输入
    """
    if model_type == "qwen2.5-vl":
        return prepare_qwen25_vl_inputs(processor, images, text_prompts, device)
    else:
        raise ValueError(f"不支持的模型类型: {model_type}")