Франкенштейн на 30 ГБ RAM: как мы пересадили мозг Gemma в скелет DeepSeek и сломали Transformers

Франкенштейн на 30 ГБ RAM: как мы пересадили мозг Gemma в скелет DeepSeek и сломали Transformers

У нас было две бесплатные видеокарты T4 в Kaggle, 30 ГБ оперативной памяти и безумная идея: что будет, если взять веса классической модели (Gemma-4-31B) и хирургическим путём, без дообучения, вшить их в MoE-архитектуру (DeepSeek-V4)?

Анатомия эксперимента

Наша цель — структурное сращивание (grafting):

  • Донор (Плоть): 4 слоя от 31-миллиардной модели Gemma, сжатой до 4-бит NF4.
  • Экзоскелет: Пустая архитектура DeepSeek-V4 с её роутером Mixture-of-Experts (MoE).

Казалось бы, достаточно загрузить обе модели, скопировать веса слоёв и запустить. Но библиотека transformers воспротивилась на каждом шаге.

Препятствие 1: Identity Theft и паранойя конфигов

Сначала библиотека отказалась признавать наш кастомный тип модели gemma4. Мы обошли это, принудительно зарегистрировав тип в глобальном словаре CONFIG_MAPPING.

Затем возникла ошибка: AttributeError: 'dict' object has no attribute 'to_dict'. Оказалось, внутри модуля generation конфигурация десериализуется в обычный словарь dict, после чего код падает, пытаясь вызвать у него методы объекта-конфига.

Решение (Monkey-patching): Мы создали прокси-класс, который имитирует объект конфигурации. При ошибке словарь оборачивается в этот класс, и выполнение продолжается.

Препятствие 2: Квантовый парадокс инициализации

Экзоскелет DeepSeek больше, чем 4 слоя Gemma. Библиотека попыталась заполнить недостающие слои случайными значениями через normal_(). Но PyTorch выдал: NotImplementedError: "normal_kernel_cuda" not implemented for 'Byte'.

Проблема в том, что веса Gemma загружены через bitsandbytes в 4-битном формате (как uint8), а normal_() работает только с float.

Решение: Мы перехватили вызов TORCH_INIT_FUNCTIONS["normal_"] и запретили инициализацию для сжатых тензоров.

Препятствие 3: Спрятанные эксперты и OOM

DeepSeek-V4 хранит MoE-экспертов не в ModuleList, а в монолитном классе DeepseekV2Experts. Python не мог по ним итерироваться. Кроме того, при распаковке весов Gemma-31B мы мгновенно исчерпали память в Kaggle.

Решение:

  1. Мы написали рекурсивный «сонар» — функцию, которая ищет слои по наличию атрибутов gate_proj и up_proj.
  2. Чтобы избежать OOM, разнесли модели по разным GPU. Веса переносили микро-порциями через CPU, сразу вызывая gc.collect() для освобождения памяти.

Идеальный скрипт некроманта

После десятков падений мы создали стабильный скрипт, который обходит все защиты и выполняет трансплантацию. Он стал шаблоном для скрещивания любых несовместимых моделей.

Что в итоге?

Без fine-tuning, согласования словарей и проекций скрытых состояний модель генерирует бессмыслицу:

The experimental hybrid AI said: vdotsD... Buddhist... tomato supervised...

Но цель эксперимента — не создать рабочий чат-бот. Цель — доказать: в машинном обучении нет непробиваемых архитектурных барьеров. При понимании тензоров, Python и с помощью «костылей» можно скрестить любые модели — даже в бесплатном ноутбуке Kaggle.

Добро пожаловать в Ghetto MLOps. Ломайте библиотеки с удовольствием!

Читать оригинал