Бесплатное дообучение медицинской 7B-модели: QLoRA, FAISS и защитные ограничения
Конвейер с нулевым бюджетом на Kaggle T4: синтетические данные, QLoRA, RAG по классическим текстам, проверенный слой знаний, жёстко заданные экстренные ограничения, релиз в GGUF и баги, на которые мы наткнулись.
Медицинские чат-боты обычно ошибаются одним из двух способов. Либо выдумывают лечение, либо смешивают традиционную и доказательную медицину в одну опасную кашу. Задача была в том, чтобы сделать модель, которая строго разделяет эти два мира, показывает, откуда берёт информацию, и отходит в сторону, когда человек описывает экстренную ситуацию. Она называется Expert Vaidya, отделяет аюрведу от современных клинических рекомендаций, и вся она сделана бесплатно.
Железо — две видеокарты Tesla T4 на бесплатном тарифе Kaggle. Модель — 7B с открытыми весами, а знания взяты из классических текстов в общественном достоянии. Результат работает офлайн на ноутбуке с 16 ГБ и ставится одной командой. Ниже весь конвейер.
Из чего всё состоит
- Базовая модель: Qwen2.5-7B-Instruct, дообученная с Unsloth и QLoRA
- Обучающие данные: 52 структурированных примера, 2 написаны вручную и 50 синтетических, прошедших фильтр качества
- Поиск: индекс FAISS по 6 765 чанкам из 1 492 страниц «Сушрута-самхиты» (общественное достояние на Archive.org) плюс проверенная база знаний из 22 записей на основе WHO, ADA, NICE и PubMed Central
- Безопасность: код ограничений на обычном Python, без всякой модели, который проверяет каждый вопрос до того, как тот попадёт в LLM
- Поставка: файл GGUF Q4_K_M (4,68 ГБ), опубликованный на Hugging Face и в реестре Ollama
Шаг 1: дешёвые синтетические данные
Размеченные медицинские данные стоят больше, чем позволяет бюджет хобби-проекта, поэтому примеры по 50 медицинским темам сгенерировала модель поменьше, Qwen2.5-3B в 4 битах, по строгому контракту на формат вывода. В каждом ответе должны были быть аюрведический раздел, современный раздел и исторический раздел, а всё неполное отбрасывалось автоматически:
python has_all_3 = all(x in response for x in ["Ayurvedic", "Modern", "Historical"])
if has_all_3 and len(response) > 200:
keep(example)
Все 50 сгенерированных примеров прошли проверку. Вместе с 2 написанными вручную затравками получилось 52 обучающих строки. Набор крошечный, но цель была не научить фактам. Цель была научить формату и дисциплине. Факты приходят из поиска.
Шаг 2: QLoRA на бесплатных T4
Unsloth на двух T4 в Kaggle обучал только адаптеры LoRA:
model = FastLanguageModel.get_peft_model(
model, r=16,
target_modules=["q_proj","k_proj","v_proj","o_proj",
"gate_proj","up_proj","down_proj"],
lora_alpha=16, lora_dropout=0,
use_gradient_checkpointing="unsloth",
)
Изменились лишь 0,53% весов: 40 370 176 обучаемых параметров из 7 655 986 688, 5 эпох, 35 шагов, эффективный размер батча 8 и итоговый loss на обучении 1,0493. По времени вышло меньше 10 минут. Медицине модель здесь не училась. Она научилась всегда отвечать в пятичастной структуре и никогда не смешивать области.
Шаг 3: 1 492 страницы в FAISS
Формат сам по себе не делает ответы обоснованными. Два английских перевода «Сушрута-самхиты» в общественном достоянии (812 и 680 страниц) взяты с Archive.org. PyMuPDF извлёк текст, рекурсивный сплиттер LangChain нарезал его на чанки по 500 символов с перекрытием 100, а all-MiniLM-L6-v2 построил эмбеддинги. Получилось 6 765 чанков, нормализованных и загруженных в FAISS IndexFlatIP.
При запросе система достаёт top-k чанков, но с порогом релевантности, оценкой не ниже 0,3–0,4, так что нерелевантные страницы в окно контекста не попадают.
Шаг 4: проверенный слой
Старые переводы, отсканированные с печатных изданий, — это шумный OCR, поэтому рядом с первым индексом стоит второй, тщательно отобранный. В нём 22 записи, проверенные вручную, у каждой указан источник и оценка уверенности. Среди источников «Чарака-самхита», «Сушрута-самхита» и «Аштанга-хридая», рекомендации WHO 2023 и 2024 годов, ADA Standards of Care 2025, рекомендации NICE по тревожным признакам (NG185 и NG173) и три исследования из PubMed Central: Gymnema PMC2915138, Boswellia PMC3251738 и Ashwagandha PMC3573577. Дополняет всё таблица взаимодействий трав и лекарств для варфарина и препаратов от диабета. Поиск оставляет только записи с verified == True и confidence >= 0.8.
Шаг 5: ограничения срабатывают первыми
Ключевое проектное решение: экстренные случаи никогда не доходят до модели. Обычный Python проверяет ввод раньше всего остального:
warnings, disclaimer = safety_guard.generate_safety_response(user_question)
if warnings and "EMERGENCY" in warnings[0]:
return warnings[0] + disclaimer # LLM never runs
Он отслеживает 14 признаков экстренной ситуации, например боль в груди, признаки инсульта, анафилаксию и суицидальные высказывания, плюс 7 опасных паттернов самолечения вроде "stop taking", "replace insulin" или "double the dose". На сообщение о боли в груди возвращается предупреждение для сортировки с экстренными номерами (112, 911, 999), каждый раз одно и то же.
Шаг 6: квантование, публикация, развёртывание
После слияния весов LoRA Unsloth записал GGUF на 4,68 ГБ в формате Q4_K_M, который отправился на Hugging Face и в реестр Ollama. Теперь запустить модель может кто угодно:
bash ollama run tejaas6/expert-vaidya
Системный промпт, температура и поведение ограничений поставляются внутри модели. Никакой настройки и никакого API-ключа не нужно, и всё работает полностью офлайн.
Что пошло не так
Вот часть, о которой мало кто пишет:
- Бесконечный цикл перечисления. При низкой температуре модель однажды выдала сотни вариантов "hyaluronan polymer...". Остановила это настройка
no_repeat_ngram_size=3. - Падение при загрузке на Hugging Face. Файл на 4,4 ГБ вызывал ImportError из
XetProgressReporter. Помогло задатьHF_HUB_DISABLE_XET=1, чтобы обойти передачу через xet, и очистить закэшированные модули перед повторным импортом. - Исчезающий /tmp в Kaggle. Unsloth отправил экспорт GGUF в
/tmp, потому что в рабочей папке не хватало места, а/tmpне сохраняется как вывод ядра. Решил проблему явныйshutil.copyв/kaggle/workingперед архивацией. - Утечка языка. Qwen иногда вставлял в ответы китайские иероглифы. Это убрали постобработка регулярным выражением и правило «только английский» в промпте.
Результаты
- Вопрос о диабете ссылается на классификацию Прамеха из «Чараки» и на диагностические пороги WHO и ADA, причём каждое отдельно.
- На вопрос, можно ли принимать Gymnema вместе с препаратами от диабета, модель указывает на суммарный риск гипогликемии и на протокол контроля.
- Описание давящей боли в груди полностью минует LLM и возвращает только рекомендации по сортировке.
- На выдуманную болезнь модель отвечает отказом, а не придуманным лечением.
Где найти
Модель можно скачать из реестра Ollama, веса GGUF лежат на Hugging Face под именем Expert-Vaidya-Qwen7B-GGUF. Это образовательный исследовательский проект. Это не медицинская консультация и не инструмент диагностики.