
Мы обучили DoRA для персонального голоса за $1.50 — и она побила сток Qwen3-8B в 100% слепого A/B
Обучили DoRA-адаптер на 6128 личных сообщениях из Telegram. $1.50 на одной Vast.ai RTX 3090. Результат: 100% побед в слепом A/B против стока Qwen3-8B. Ноль катастрофического забывания. И один промпт, где DoRA звучала как сам человек лучше, чем он сам.
TL;DR. Обучили DoRA-адаптер на 6128 личных сообщениях из Telegram. Стоимость: $1.50 на одной Vast.ai RTX 3090. Результат: в слепом парном A/B-тесте DoRA-тюненная Qwen3-8B побила сток Qwen3-8B в 100% пар, где ни одна не сравнивалась с живым человеком. Ноль катастрофического забывания на 50 задачах общих знаний. Поворот сюжета: когда реальные ответы человека добавили третьим вариантом, человек победил в 71% случаев. Кроме одного раза, где DoRA звучала как сам человек лучше, чем он сам.
Это лонгрид о том, как мы это сделали, что нас удивило, что сломалось и почему мы считаем это важным для будущего персонального AI.
Зачем мы это сделали
Мы строим Aiconic как research-грейд инженерную AI-мастерскую, и один из наших повторяющихся тезисов — что персональный AI, а не дженерик-AI, это место, где живёт следующее десятилетие потребительских LLM. Чтобы доказать это себе, нам нужен был острый вопрос:
Может ли маленький адаптер, обученный на естественной истории переписки одного человека, заставить фронтир-модель отвечать как этот конкретный человек — не в игрушечных демках переноса стиля, а в повседневном чате «что хочешь на ужин»?
Это был Спринт S65 в нашем внутреннем research-логе. Это серверное продолжение Phase 0 спайка, который мы запускали двумя месяцами раньше на Mac M2 с Qwen3.5-0.8B — тот валидировал качественное направление, но маленькая модель не могла реально удержать голос на новых промптах.
Для S65 мы хотели реальную production-грейд базовую модель (Qwen3-8B), реальный метод обучения (DoRA, не LoRA) и реальный протокол оценки (слепая 3-сторонняя человеческая оценка). И мы хотели, чтобы всё это было воспроизводимо любым, у кого есть $5 на Vast.ai.
Что такое DoRA, кратко
LoRA замораживает базовую модель и обучает две низкоранговые матрицы, которые добавляются к выбранным весовым матрицам. Это дёшево, но может уступать полному файн-тюнингу, потому что смешивает две вещи, которые исходные веса представляют раздельно: магнитуду каждого весового вектора и его направление.
DoRA (Weight-Decomposed Low-Rank Adaptation, Liu et al. 2024) раскладывает предобученные веса на магнитуду и направление, затем применяет LoRA-style обновление только к компоненте направления, обучая магнитуду как отдельный обучаемый вектор. На практике DoRA ближе к полному файн-тюнингу, чем LoRA, при том же ранге, и обучается стабильно с тем же бюджетом гиперпараметров. Файл адаптера чуть больше, чем у LoRA (вы храните дополнительные векторы магнитуды), но история деплоя идентична.
Для задачи персонального голоса, где целевое распределение — это маленькое идиоматическое возмущение базового распределения, разделение магнитуды и направления в DoRA оказалось важнее, чем мы ожидали. Покажем почему.
Данные: 6128 чат-пар из Telegram
Обучающие данные — это просто личный экспорт автора из Telegram. Мы взяли полный DataExport JSON-дамп (331 MB по 1047 личным чатам) и прогнали кастомный экстрактор пар, который выбирает соседние пары сообщений, где автор был вторым говорящим. Каждая пара — это (сообщение_другого, ответ_автора) и становится одним обучающим примером.
Несколько решений, которые имели значение:
- Ограничение в 12 пар на чат. Без этого несколько чрезвычайно активных чатов доминируют в датасете, и модель учится отвечать так, как вы отвечаете одному конкретному другу, что обобщается хуже, чем звучит.
- Выбрасывание пар с пустыми ответами, только вложениями или ответами-системными сообщениями. Мусор на входе — мусорный голос.
- Дедупликация по точному совпадению. Люди переиспользуют фразы. Проход дедупа срезает ~7% пар.
- Автообнаружение нескольких экспортов. Люди делают несколько экспортов Telegram со временем, и ID странно конфликтуют. Мы сканируем все экспорты в рабочей директории и сливаем.
Финальный датасет: 6128 train + 322 валидационных пары в JSONL, отформатированные как Qwen3 chat templates. Это в шесть раз больше, чем было в Phase 0, и оказалось примерно sweet spot — больше помогло бы незначительно, но маржинальный выигрыш на доллар GPU-времени быстро падает.
Обучение: 3.5 часа, $1.50
Запускали на одной RTX 3090, арендованной на Vast.ai. Точный конфиг:
LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
use_dora=True, # ← the only line that turns LoRA into DoRA
task_type="CAUSAL_LM",
)
TrainingArguments(
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_steps=50,
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8, # effective batch = 16
max_seq_length=1024,
bf16=True,
gradient_checkpointing=True,
optim="adamw_torch_fused",
)Обучаемые параметры: ~30M / 8B = 0.4%. Веса адаптера на диске после обучения: 63 MB. Полный прогон занял 3 часа 30 минут и стоил примерно $1.50 кредита Vast.ai (spot-цена 3090 была около $0.30/ч на тот момент).
Тонкая, но критичная деталь: мы применяем loss только на assistant-токенах автора, не на промпте. Без этой маски модель тратит половину ёмкости на обучение предсказанию того, что говорят вам другие, что разбавляет сигнал голоса и даёт заметно более слабый адаптер. Это известный трюк в instruction-тюнинге; для работы с персональным голосом он не опционален.
Кривая loss:
| Эпоха | Train loss | Eval loss |
|---|---|---|
| 1.04 | ~2.40 | 2.519 |
| 1.83 | ~2.32 | 2.481 (мин) |
| 2.87 | 2.15 | 2.492 |
Train падает на 14%, eval — на 1.5%. Модель хорошо подгоняется под обучающий набор, но eval почти не двигается. Это центральный методологический урок всего проекта, и мы к нему вернёмся.
Оценка: слепой 3-сторонний A/B
Числа loss бесполезны для персонального голоса. Релевантный вопрос — считает ли человек, который вас знает, что ответ звучит как вы, и измерить это можно, только спросив его.
Мы построили маленький офлайн-пайплайн оценки:
- 30 отложенных промптов. Реальные недавние сообщения от реальных людей, где мы знали, что автор на самом деле ответил. Отложены из обучающего набора.
- Три ответа на промпт. Сгенерировали
stock(ванильная Qwen3-8B) иdora(Qwen3-8B + yuka-dora-v1) ответы. Скомбинировали сrealответом человека. - Рандомизированные метки. Каждый ответ получил случайную метку A/B/C на промпт.
secret.jsonмапил метки обратно на источники, скрытый от оценщика. - HTML-интерфейс оценки. Для каждого промпта: исходный контекст, затем три ответа с метками A/B/C, два вопроса: «Какой звучит больше всего как вы?» (выбрать 1) и «Какие ещё тоже хороши?» (мультивыбор).
- Проверка катастрофического забывания. Отдельный набор из 50 задач (столицы, базовая математика, код, переводы), чтобы убедиться, что адаптер не сломал общие способности базовой модели.
Автор оценила 7 промптов, прежде чем объявила себя полностью откалиброванной, а результат — статистически очевидным. (Мы понимаем, что n=7 — мало; парный паттерн DoRA против стока был достаточно решающим на 7, чтобы дальнейшая оценка ощущалась как формальность. Рецензенты, желающие больше образцов, могут перезапустить пайплайн на своих данных.)
Результаты: ожидаемая часть, потом поворот
Ожидаемая часть: DoRA размазывает сток
В прямом парном сравнении, где оценщик должен был выбрать только между DoRA и стоком (фактически 7 промптов, где оценщик не выбрал real), DoRA побеждала в 100% случаев. Сток — в 0%.
Качественный сигнал безошибочен. Сток выдаёт то, что ощущается как технарь-LLM, пытающийся быть дружелюбным — слишком объяснительно, слегка бодренько, чуть формально. DoRA выдаёт отрывистые, заточенные на планирование русско-английские code-switch ответы со смайликами из закрывающих скобок, «го», «на след неделе», приветствиями с именем в начале. Она схватила маленькие идиоматические поверхностные маркеры, из которых на самом деле состоит голос человека.
Пример. Контекст: «Спасибо за приглашение, но в пятницу не могу) Анжелика вообще живёт ближе к тебе...»
| Источник | Ответ (оригинал на русском) |
|---|---|
| Сток | ну ты и злой, я ж шучил))) а анжелика - да, она ближе, с ней можно пообщаться, если надо. |
| DoRA | ок, понял) ну тогда на след неделе, го на след неделю на субботу, ок? |
| Real | Ок спасибо Мафтуна) кстати а как короткая версия твоего имени? |
Сток считывает ситуацию неправильно. DoRA делает ровно то, что сделал бы автор — коротко подтверждает, переключается на планирование следующей недели. Real тоже побеждает здесь, но причина победы real в том, что real вводит совершенно тангенциальный вопрос об имени, который ни одна модель не могла предсказать из одного промпта.
Поворот: real побеждает в 71%, но DoRA однажды побила real
В полной 3-сторонней оценке (real, DoRA, сток) реальный ответ был выбран как наиболее похожий на автора 5 из 7 раз (71%). DoRA взяла второе место. Сток не побеждал никогда.
Ожидаемый результат. Автор всё ещё звучит больше как автор, чем её собственная DoRA. Калибровочная проверка пройдена.
Но был один промпт — помеченный p07 в оценке — где автор посмотрела на свой собственный реальный ответ, посмотрела на ответ DoRA и выбрала DoRA вместо себя. Её точный комментарий: «Честно, вариант DoRA звучит больше как репрезентативная вещь, которую я бы сказала, чем то, что я реально написала в тот день».
Это маленькая вещь, и мы не хотим переусердствовать в заявлениях. Это один промпт из семи. Но это первый сигнал, который мы видели, что адаптер выдаёт образец, более центральный для голоса человека, чем единичное шумное реальное наблюдение. Персональный AI наиболее интересен ровно там, где это происходит. Реальный человек пишет один конкретный ответ в одном конкретном настроении в один конкретный день; DoRA сэмплирует из сглаженного многообразия типичных ответов этого человека и может выдать экземпляр ближе к среднему, чем человек выдал во вторник днём.
Катастрофическое забывание: 0 п.п.
На наборе из 50 задач общих знаний сток Qwen3-8B ответил правильно на 49/50. yuka-dora-v1 ответила правильно на 49/50. Идентично. Адаптер вносит нулевую деградацию общих способностей, что является полом, который должен преодолеть любой серьёзный адаптер персонализации. Мы были готовы откатить ранг или эпохи, если появится забывание; оно не появилось.
Что не сработало
Три вещи сломались или удивили нас. Все три стоит знать, если вы воспроизводите это.
1. Qwen3 по умолчанию — reasoning-модель
Qwen3 выдаёт <think>...</think> reasoning-трейсы перед финальным ответом, если явно это не отключить. В наших обучающих данных ноль <think>-блоков — это просто человеческий чат. Поэтому при инференсе prior базовой модели тянет к выдаче <think>-трейсов, DoRA сдвигает распределение к стилю автора, и выход получается Франкенштейном из длинных reasoning-префиксов, за которыми следует короткий разговорный ответ.
Фикс — один keyword-аргумент в токенизаторе:
inputs = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
enable_thinking=False, # ← MANDATORY for chat-style adapters
return_tensors="pt",
)Мы пропустили это в первых прогонах инференса, и результаты были мусорными. Мы добавили это в оба eval-скрипта. Если вы обучаете любой chat-style адаптер на Qwen3, ставьте enable_thinking=False и в токенизации обучающих данных тоже — это выравнивает обучающий префикс с инференс-префиксом и, вероятно, дополнительно улучшает eval loss. У нас это в списке на ретрейн V2.
2. Танцы с версиями transformers
Поддержка Qwen3 появилась в transformers==4.51. Мы стартовали на 4.45.2 и получили KeyError: 'qwen3' при загрузке модели. Апгрейд до 4.55 внёс конфликт версий torch на Vast 3090 (torch был запинен на 2.4, transformers 4.55 хочет 2.5+). Рабочий пин: transformers==4.53.0. Скучно, но из тех вещей, что съедают два часа, если не знаешь.
3. Cerebras не умеет грузить адаптеры
Наш production-бэкбон работает на Cerebras (Qwen3-235B-A22B). Хостинговый инференс Cerebras не поддерживает runtime-загрузку LoRA/DoRA — адаптеры там нужно запекать в базовый чекпоинт через merge, а деплой-пайплайн Cerebras ожидает ванильные веса. Так что yuka-dora-v1, несмотря на чистую победу в эксперименте, для нас сейчас — research-артефакт, а не prod-замена.
Для production-персонализации у нас два пути, и мы идём по обоим:
- (A) Self-host Qwen3-8B + DoRA на vLLM или sglang. Стоит около $300/мес за одну 3090, работающую 24/7. Жизнеспособно для маленькой когорты power-юзеров.
- (B) Остаться на Cerebras 235B для базы, протолкнуть персонализацию в системный промпт и персональный RAG-буфер. Теряет глубокую достоверность голоса, которую достигает DoRA, выигрывает в 30× большей базовой модели. Это то, что мы отгружаем в v1.
Честный ответ: пока (B) — это то, что юзеры реально получают. DoRA — это то, что убеждает нас, что (A) стоит строить, как только спрос юзеров это оправдает.
Воспроизводимость
Полные конфиги, экстрактор данных, все пять eval-скриптов, слепой HTML-оценщик, набор на забывание и model card — в нашем research-репозитории. Сам адаптер — на HuggingFace:
Он гейтнут под CC BY-NC 4.0, потому что обучающие данные — это приватная история переписки одного конкретного человека; доступ требует подтверждения, что адаптер — research-артефакт и не должен использоваться для выдачи себя за неё. Для собственной DoRA персонального голоса вы бы обучались на своих данных; model card включает полный пайплайн, чтобы вы могли.
Список железа для воспроизведения:
- Одна RTX 3090 (24 GB VRAM) — около $0.30/ч spot на Vast.ai на момент написания
- 3.5 часа GPU-времени
- Ваш собственный экспорт Telegram (Настройки → Продвинутые → Экспортировать данные Telegram → JSON)
- Около 6000 пар сообщений для надёжного захвата голоса, 1000 минимум
Полная денежная стоимость воспроизведения на вашей истории переписки: от $1 до $3.
Что это значит для персонального AI
Тезис, который мы взялись проверить: маленький адаптер, обученный на сообщениях одного человека, может заставить фронтир-модель звучать как этот конкретный человек.
Результат: да, решительно, для самого распространённого распределения чат-ответов, и по стоимости, которая фактически бесплатна. Оставшийся разрыв до «неотличимо от реального человека» — это не проблема ёмкости (у DoRA полно запаса на ранге 16), это проблема распределения. Люди говорят странные, off-distribution вещи в определённые дни. Сглаженный сэмплер не может их предсказать. Он может попасть в среднее, иногда лучше, чем сам человек попадает в своё среднее.
Для следующего десятилетия потребительского AI важное для нас следствие такое: правильная гранулярность персонализации — это индивид, а не сегмент. Компании пытались делать «персонализированный AI», кластеризуя юзеров в 50 персон и роутя к слегка дотюненным базовым моделям. Это сегмент-уровневая персонализация. Это остановка по пути. Пункт назначения — один маленький адаптер на юзера, обученный на его собственном непрерывном потоке данных, деплоящийся per-conversation, принадлежащий юзеру.
Мы строим в эту сторону. yuka-dora-v1 — это первое конкретное доказательство, что экономика работает на уровне юнита: $1.50 GPU-времени превращают фронтир-модель в ваш конкретный голос без измеримой потери способностей. Умножьте это на число юзеров, которые заплатили бы за AI, звучащий как они, и структура затрат персонального AI начинает выглядеть совсем иначе, чем «арендуй OpenAI по токену».
Релиз Qwen3 — тоже часть этой истории. Open-weights фронтир-модели меняют то, что возможно для персонализации: вы можете обучить адаптер для приватного юзера и отгрузить его без того, чтобы какой-либо внешний API вообще видел данные. Комбинация open-weights фронтира + дешёвых персональных адаптеров + per-user деплоя — по-настоящему новая. Сложная работа теперь — это деплой-обвязка, а не моделирование.
Что бы мы изменили в v2
Короткий бэклог для следующей итерации, примерно по порядку ожидаемого эффекта:
- Обучать с
enable_thinking=Falseв chat template — выровнять обучающий префикс с инференс-префиксом. Должно дополнительно снизить eval loss и убрать остаточную утечку reasoning-трейсов. - Поднять ранг с 16 до 32. Текущий адаптер ощущается прямо на грани ёмкости для захвата более редких оборотов речи. Ранг 32 удваивает число параметров до ~60M обучаемых; всё ещё комфортно влезает в 3090.
- Добавить экспорты Claude.ai и комментарии GitHub PR в обучающую смесь с отдельным системным промптом. Даёт тому же адаптеру «tech-voice режим» для код-ревью и инженерного чата — тот же человек, другой регистр.
- NER-постфильтр для галлюцинированных имён контактов. DoRA иногда придумывает правдоподобно звучащие имена, которых нет среди реальных людей в сети автора. Дешёвый постпроцессинг с NER-моделью на выходе их ловит.
- Self-hosted vLLM эндпоинт для людей из нашей маленькой research-когорты, которые хотят реально пользоваться адаптером, а не только смотреть на оценку.
Заключение
Если вы дочитали досюда, вы, вероятно, тот тип инженера, который захотел бы попробовать это сам. Адаптер на HuggingFace, методология здесь, стоимость — одна чашка кофе. Самое интересное в персональном AI — что он перестаёт быть research-курьёзом ровно в той ценовой точке, где индивиды могут позволить себе обучить его для себя. Мы только что пересекли эту черту.
Если хотите кастомную DoRA, обученную под ваш продукт — адаптер голоса компании, адаптер в стиле саппорта, адаптер голоса фаундера — это часть того, что мы делаем в Aiconic. Пишите: [email protected].
Иначе — идите обучите его для себя. README на месте. GPU дешёвый. Результат того стоит.