НейроАгент

Исправление IndexError при дообучении Qwen 2.5 VLM

Узнайте, как исправить IndexError при дообучении модели Qwen 2.5 3B VLM. Найдите решения для проблем с тензорами image_grid_thw, улучшенные функции collate и методы отладки для успешного обучения модели.

Я столкнулся с ошибкой IndexError при попытке дообучения модели Qwen 2.5 3B VLM на наборе данных Trelis/chess_pieces с Hugging Face. Ошибка возникает на строке trainer.train() со следующим трейсбэком:

IndexError: index 2 is out of bounds for dimension 0 with size 2

Ошибка происходит в файле processing_qwen2_5_vl.py на строке 177:

num_image_tokens = image_grid_thw[index].prod() // merge_length

Вот моя функция collate:

python
def collate_fn(samples):
    texts = []
    images = []

    for sample in samples:
        prompt_text = processor.apply_chat_template(sample['text'], tokenize=False)
        texts.append(prompt_text)
        images.append(sample['image'])


    inputs = processor(
        text=texts,
        images=images,
        return_tensors='pt',
        padding=True
    )

    labels=inputs['input_ids'].clone()
    labels[labels == processor.tokenizer.pad_token_id] = -100
    inputs['labels'] = labels

    return inputs

Я пробовал различные решения, но не смог решить эту проблему с индексацией. Может кто-нибудь помочь мне определить причину этой ошибки и как её исправить при дообучении Qwen 2.5 3B VLM на наборах изображений?

НейроАгент

IndexError, с которым вы сталкиваетесь при тонкой настройке Qwen 2.5 3B VLM, является распространенной проблемой, связанной с несоответствием размерностей тензора image_grid_thw. Эта ошибка обычно возникает, когда модель ожидает определенную структуру тензора, но получает данные с несовместимыми размерностями, что приводит к сбою операции индексации.


Содержание


Понимание происхождения ошибки

Ошибка index 2 is out of bounds for dimension 0 with size 2 происходит в скрипте обработки на строке 177:

python
num_image_tokens = image_grid_thw[index].prod() // merge_length

Как указано в GitHub issue, это происходит потому, что тензор image_grid_thw не имеет ожидаемой формы. Модель ожидает, что image_grid_thw будет содержать размеры сетки в формате (T, H, W), но ваш тензор может содержать только 2 элемента вместо ожидаемых 3.


Анализ основных причин

1. Отсутствие размеров сетки изображения

Тензор image_grid_thw критически важен для Qwen2.5-VL, так как он содержит пространственные размеры сетки, которые модель использует для обработки изображений. Когда этот тензор отсутствует или имеет неправильную форму, операция индексации завершается ошибкой.

2. Ограничения функции collate

Ваша текущая функция collate явно не обрабатывает image_grid_thw:

python
def collate_fn(samples):
    texts = []
    images = []
    
    for sample in samples:
        prompt_text = processor.apply_chat_template(sample['text'], tokenize=False)
        texts.append(prompt_text)
        images.append(sample['image'])
    
    inputs = processor(
        text=texts,
        images=images,
        return_tensors='pt',
        padding=True
    )

3. Проблемы с разрешением изображений

Изображения должны быть правильно изменены до размеров, кратных 14 (размер патча для Qwen2.5-VL). Если изображения имеют несовместимые размеры, расчет сетки даст неверные результаты.

4. Несогласованность пакетной обработки

Ошибка может возникать при обработке пакетов со смешанными типами изображений или когда некоторые выборки не содержат действительных данных изображений.


Решения и исправления

Решение 1: Улучшенная функция collate с обработкой image_grid_thw

Измените вашу функцию collate для правильной обработки image_grid_thw:

python
def collate_fn(samples):
    texts = []
    images = []
    valid_samples = []
    
    # Сначала отфильтровываем недействительные выборки
    for sample in samples:
        try:
            if 'image' in sample and sample['image'] is not None:
                prompt_text = processor.apply_chat_template(sample['text'], tokenize=False)
                texts.append(prompt_text)
                images.append(sample['image'])
                valid_samples.append(sample)
        except Exception as e:
            print(f"Пропуск недействительной выборки: {e}")
    
    if not valid_samples:
        raise ValueError("В пакете не найдено действительных выборок")
    
    # Обработка с помощью процессора
    inputs = processor(
        text=texts,
        images=images,
        return_tensors='pt',
        padding=True
    )
    
    # Рассчитываем image_grid_thw вручную, если он не предоставлен
    if 'image_grid_thw' not in inputs:
        batch_size = len(images)
        image_grid_thw = []
        
        for img in images:
            # Получаем размеры изображения
            if hasattr(img, 'size'):  # PIL Image
                w, h = img.size
            else:  # Tensor
                h, w = img.shape[-2:]
            
            # Рассчитываем размеры сетки (T=1 для одиночных изображений, H//14, W//14)
            grid_h = h // 14
            grid_w = w // 14
            image_grid_thw.append([1, grid_h, grid_w])
        
        inputs['image_grid_thw'] = torch.tensor(image_grid_thw, dtype=torch.long)
    
    # Настраиваем метки
    labels = inputs['input_ids'].clone()
    labels[labels == processor.tokenizer.pad_token_id] = -100
    inputs['labels'] = labels
    
    return inputs

Решение 2: Предварительная обработка и валидация изображений

Добавьте этапы валидации и предварительной обработки:

python
def preprocess_image(image):
    """Убедимся, что изображение имеет совместимые размеры для Qwen2.5-VL"""
    if hasattr(image, 'size'):  # PIL Image
        w, h = image.size
    else:  # Tensor
        h, w = image.shape[-2:]
    
    # Проверяем, кратны ли размеры 14
    if h % 14 != 0 or w % 14 != 0:
        # Изменяем размер до ближайшего кратного 14
        new_h = ((h + 13) // 14) * 14
        new_w = ((w + 13) // 14) * 14
        image = image.resize((new_w, new_h)) if hasattr(image, 'resize') else F.interpolate(image.unsqueeze(0), size=(new_h, new_w)).squeeze(0)
    
    return image

Решение 3: Обработка размера пакета и смешанной точности

python
# В вашем скрипте обучения
training_args = TrainingArguments(
    output_dir="./output",
    per_device_train_batch_size=1,  # Начните с размера пакета 1
    gradient_accumulation_steps=4,
    fp16=True,  # Смешанная точность может помочь с проблемами памяти
    remove_unused_columns=False,
    ...  # другие аргументы
)

# Переопределяем функцию сборки данных
data_collator = collate_fn  # Ваша улучшенная функция collate

Стратегии предотвращения

1. Валидация набора данных

Перед обучением валидируйте ваш набор данных:

python
def validate_dataset(dataset):
    issues = []
    
    for i, sample in enumerate(dataset):
        try:
            if 'image' not in sample or sample['image'] is None:
                issues.append(f"Выборка {i}: Отсутствует изображение")
                continue
            
            img = sample['image']
            if hasattr(img, 'size'):
                w, h = img.size
            else:
                h, w = img.shape[-2:]
            
            if h % 14 != 0 or w % 14 != 0:
                issues.append(f"Выборка {i}: Размеры изображения {h}x{w} не кратны 14")
                
        except Exception as e:
            issues.append(f"Выборка {i}: {str(e)}")
    
    if issues:
        print("Проблемы валидации набора данных:")
        for issue in issues[:10]:  # Показываем первые 10 проблем
            print(f"  {issue}")
        if len(issues) > 10:
            print(f"  ... и еще {len(issues) - 10} проблем")
    else:
        print("Валидация набора данных прошла успешно!")
    
    return len(issues) == 0

2. Управление памятью

python
# Используем контрольные точки градиента для экономии памяти
model.gradient_checkpointing_enable()

# Включаем эффективное внимание к памяти
from transformers import AutoModelForVision2Seq
model = AutoModelForVision2Seq.from_pretrained(
    "Qwen/Qwen2.5-VL-3B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto",
    use_cache=False  # Отключаем кэш для обучения
)

Подход к отладке

Пошаговая отладка

  1. Проверьте состав пакета:
python
# Добавьте отладку в вашу функцию collate
def debug_collate_fn(samples):
    print(f"Обработка пакета из {len(samples)} выборок")
    for i, sample in enumerate(samples):
        if 'image' in sample:
            img = sample['image']
            if hasattr(img, 'size'):
                print(f"Выборка {i}: Размер изображения {img.size}")
            else:
                print(f"Выборка {i}: Форма тензора {img.shape}")
  1. Исследуйте тензор image_grid_thw:
python
# Добавьте это после вызова вашей функции collate
batch = collate_fn(dataset[:2])  # Тестируем с небольшим пакетом
print(f"Форма image_grid_thw: {batch['image_grid_thw'].shape}")
print(f"Значения image_grid_thw: {batch['image_grid_thw']}")
  1. Проверьте вывод процессора:
python
# Тестируем процессор индивидуально
test_samples = dataset[:2]
processor_outputs = processor(
    text=[sample['text'] for sample in test_samples],
    images=[sample['image'] for sample in test_samples],
    return_tensors='pt'
)
print("Ключи процессора:", list(processor_outputs.keys()))

Полный пример реализации

Вот полный рабочий пример, включающий все исправления:

python
import torch
from transformers import AutoProcessor, AutoModelForVision2Seq, TrainingArguments, Trainer
from torch.utils.data import DataLoader
from PIL import Image
import torchvision.transforms as transforms

# Улучшенная функция collate
def safe_collate_fn(samples):
    texts = []
    images = []
    valid_indices = []
    
    # Фильтруем действительные выборки
    for i, sample in enumerate(samples):
        try:
            if ('image' in sample and sample['image'] is not None and 
                'text' in sample and sample['text'] is not None):
                
                # Валидируем изображение
                img = sample['image']
                if hasattr(img, 'size'):
                    w, h = img.size
                    if h % 14 != 0 or w % 14 != 0:
                        # Изменяем размер изображения
                        new_h = ((h + 13) // 14) * 14
                        new_w = ((w + 13) // 14) * 14
                        img = img.resize((new_w, new_h))
                        sample['image'] = img
                
                texts.append(processor.apply_chat_template(sample['text'], tokenize=False))
                images.append(img)
                valid_indices.append(i)
                
        except Exception as e:
            print(f"Пропуск выборки {i}: {e}")
    
    if not valid_indices:
        raise ValueError("В пакете нет действительных выборок")
    
    # Обработка с помощью процессора
    inputs = processor(
        text=texts,
        images=images,
        return_tensors='pt',
        padding=True,
        truncation=True,
        max_length=2048
    )
    
    # Убеждаемся, что image_grid_thw присутствует
    if 'image_grid_thw' not in inputs:
        batch_size = len(images)
        image_grid_thw = []
        
        for img in images:
            if hasattr(img, 'size'):
                w, h = img.size
            else:
                h, w = img.shape[-2:]
            
            grid_h = h // 14
            grid_w = w // 14
            image_grid_thw.append([1, grid_h, grid_w])
        
        inputs['image_grid_thw'] = torch.tensor(image_grid_thw, dtype=torch.long)
    
    # Настраиваем метки
    labels = inputs['input_ids'].clone()
    labels[labels == processor.tokenizer.pad_token_id] = -100
    inputs['labels'] = labels
    
    return inputs

# Настройка модели и процессора
model_name = "Qwen/Qwen2.5-VL-3B-Instruct"
processor = AutoProcessor.from_pretrained(model_name)
model = AutoModelForVision2Seq.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto",
    use_cache=False
)

# Конфигурация обучения
training_args = TrainingArguments(
    output_dir="./chess_finetuned",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    learning_rate=2e-5,
    num_train_epochs=3,
    fp16=True,
    logging_steps=10,
    save_steps=500,
    remove_unused_columns=False,
    report_to="none"
)

# Создаем тренер
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    data_collator=safe_collate_fn,
)

# Начинаем обучение
trainer.train()

Источники

  1. vLLM Forums - IndexError: list index out of range (Qwen/Qwen2.5-VL-3B-Instruct)
  2. QwenLM GitHub - Fine-tuning Qwen2.5-VL-7B using Llamafactory fails with IndexError
  3. F22 Labs - Complete Guide to Fine-tuning Qwen2.5 VL Model
  4. Roboflow - How to Fine-Tune Qwen2.5-VL with a Custom Dataset
  5. HuggingFace Transformers - Fine tuning qwen2.5 error

Заключение

IndexError, с которым вы сталкиваетесь при тонкой настройке Qwen 2.5 3B VLM, в основном вызван неправильной обработкой тензора image_grid_thw и несоответствием размерностей при обработке изображений. Реализовав улучшенную функцию collate, добавив правильную валидацию изображений и используя методы отладки, вы можете решить эту проблему и успешно настроить модель на вашем наборе данных шахматных фигур.

Ключевые выводы:

  • Всегда валидируйте ваш набор данных перед обучением, чтобы убедиться, что размеры изображений совместимы
  • Реализуйте правильную обработку ошибок в вашей функции collate для пропуска недействительных выборок
  • Рассчитывайте image_grid_thw вручную, если процессор не предоставляет его
  • Начинайте с меньших размеров пакетов и постепенно увеличивайте их по мере валидации процесса
  • Используйте методы смешанной точности и оптимизации памяти для эффективной обработки 3B модели

С этими исправлениями вы должны успешно настроить модель Qwen 2.5 3B VLM на вашем наборе данных шахматных фигур без проблем с IndexError.