Ассистент памяти, который не сливает ваши данные в облако
Представьте: вы работаете за ноутбуком, пишете код, читаете документацию. Вдруг вспоминаете, что вчера обсуждали архитектуру микросервисов — и вам нужно то самое решение из чата. Запускаете локального ассистента, он быстро пересказывает контекст. Никаких запросов на сервер, никаких утечек приватности. Звучит как мечта? Реальность упирается в железо: чтобы модель 4B параметров работала на устройстве с минимальной задержкой, одного инференса мало. Ей нужно знать ваши данные. Но как обучить её на личной истории, если у вас не 48 ГБ RAM и не GPU на 80 ГБ HBM? Ответ — distill-on-idle.
Distill-on-idle — это не просто техника дистилляции. Это пайплайн, который использует простаивающие ресурсы устройства: когда вы ушли на обед, ноутбук не простаивает, а потихоньку дообучает модель-студента на собранных логах, используя учителя размером 8–14B, запущенного на том же CPU/GPU. Результат — компактный ассистент памяти, который знает ваши привычки, но не требует интернета. Звучит почти как магия? Давайте разберем, как это собрать своими руками.
Важно: не путайте distill-on-idle с обычной дистилляцией перед деплоем. Здесь дистилляция происходит асинхронно, циклами, на том же устройстве, что и инференс. Это накладывает жесткие ограничения по памяти и времени.
Почему 4B? Почему нельзя взять 1.5B?
На первый взгляд, 1.5B-модели (например, Qwen2.5-1.5B) работают на любом смартфоне. Но для ассистента памяти — задачи, где нужно удерживать длинный контекст (история переписок, заметки, код), — 1.5B катастрофически не хватает ёмкости. Модели 4B-диапазона (Phi-3-mini 3.8B, Qwen2.5-4B, новейшая Gemma 2 4B 2026 года) — это золотая середина: они помещаются в 4–6 ГБ RAM в 4-битном квантовании и при этом способны обрабатывать контекст до 64–128K токенов. Для сравнения, запуск 9B модели в 4 бита требует уже ~6 ГБ VRAM, как описано в гайде по 9B на 6GB VRAM, но на CPU это будет в разы медленнее. 4B — компромисс между качеством и скоростью на обычном железе.
Но есть нюанс: 4B модель, обученная только на общих данных, — это просто болванка. Чтобы она стала ассистентом памяти, нужно передать ей знания о ваших данных, и сделать это незаметно. Тут и приходит distill-on-idle.
Архитектура пайплайна: три демона, которые работают, пока вы курите
Distill-on-idle — это не одна программа, а связка из трех фоновых сервисов:
- Collector — собирает контекст использования: какие файлы вы открываете, о чем чаты, какие команды выполняете.
- Trainer — в idle-время запускает дистилляцию: teacher-модель (обычно 7–14B) генерирует логиты на собранных данных, student (4B) учится их повторять.
- Server — держит актуальную версию student-модели и отвечает на запросы ассистента с минимальной задержкой.
Звучит громоздко? На практике каждый компонент можно реализовать на Python с использованием ONNX Runtime и Hugging Face Transformers. А пример распределённого асинхронного обучения можно подсмотреть в гайде по MoE-моделям на 8 ГБ VRAM, там похожая логика загрузки по частям.
Шаг 1. Выбираем учителя и ученика
Для teacher модели я рекомендую Qwen2.5-7B-Instruct или Gemma 2 9B — они отлично работают в 8-битном квантовании и занимают ~9–10 ГБ RAM. Если у вас всего 8 ГБ, можно взять Qwen2.5-4B как teacher, но качество передачи знаний упадёт (буквально, учитель будет не намного умнее ученика). Лучше teacher от 7B. Ещё вариант — использовать cloud-модель как teacher, но тогда теряется смысл локальности. Мы же делаем всё на устройстве.
Student — модель 3.8–4B: Phi-3-mini-4k-instruct (ещё актуальна на 2026) или Qwen2.5-4B-Instruct.
Подсказка: квантуйте teacher в int8, student — в int4 (QLoRA). Это снизит нагрузку на RAM. Для инференса student можно держать в 4-битном GGUF — так он запускается даже на Raspberry Pi, как показано в гайде по Gemma 4 на Raspberry Pi 5.
1 Подготовка датасета из логов пользователя
Collector должен собирать пары (запрос, ответ) из вашей активности. Например, для ассистента памяти: вы пишете заметку — лог: ["Вчера обсуждали архитектуру"]. Или открываете файл — контекст: ["Файл: /src/main.py", строки кода]. Не нужно хранить всё — достаточно последних 10 тысяч токенов в скользящем окне. Можно использовать File Change Log и обращения к LLM. Не советую собирать данные, пока вы не дали согласие — это уже вопрос этики. Сделайте кнопку «Обучить модель на моих данных».
import os
import json
from collections import deque
class MemoryCollector:
def __init__(self, max_tokens=10000):
self.buffer = deque(maxlen=max_tokens)
def add_event(self, text):
self.buffer.append(text)
def dump(self, path="idle_data.jsonl"):
with open(path, "a") as f:
for item in self.buffer:
f.write(json.dumps({"text": item}) + "\n")
Важно: не пишите в файл каждую секунду — накапливайте и сбрасывайте раз в минуту, чтобы не убить SSD.
2 Расписание idle-trigger
Главный вопрос: когда запускать дистилляцию? Ответ: когда система неактивна более 5 минут и уровень заряда батареи >30% (для ноутбука). На десктопе — всегда, но с пониженным приоритетом процесса. Код на Python с psutil:
import psutil, time, torch
def is_idle(threshold_minutes=5):
return time.time() - psutil.boot_time() > threshold_minutes * 60 \
and psutil.cpu_percent(interval=2) < 10
def idle_loop(student, teacher, dataloader):
while True:
if is_idle():
print("Distill-on-idle: запуск")
train_one_epoch(student, teacher, dataloader)
time.sleep(60)
Ошибка новичка: запускать дистилляцию, когда человек работает. При этом студент начинает занимать процессор, лаги появляются, пользователь бесится, выключает всё. Обязательно проверяйте не только загрузку CPU, но и активность ввода (клавиатура/мышь).
3 Квантование и дообучение student
Дистилляция — это не просто скопировать ответы учителя. Нужно минимизировать KL-дивергенцию между распределениями. Для 4B модели на CPU можно использовать QLoRA (4-битную адаптацию). Пример на основе библиотеки peft и bitsandbytes:
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import get_peft_model, LoraConfig
import torch
# Загружаем student в 4-bit
student = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-4B-Instruct",
load_in_4bit=True,
device_map="auto"
)
# LoRA конфиг
lora_config = LoraConfig(
r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], lora_dropout=0.1
)
student = get_peft_model(student, lora_config)
# Загружаем teacher (7B в 8-bit)
teacher = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
load_in_8bit=True,
device_map="auto"
)
Затем на каждой idle-сессии прогоняем 10–100 шагов (чтобы не нагружать батарею). Сохраняем чекпоинты раз в 10 минут.
Типичные грабли и как их избежать
| Проблема | Решение |
|---|---|
| Количество итераций за idle-сессию слишком мало (модель не успевает учиться) | Накопить данные за день, а дистилляцию запускать ночью (одна большая сессия). Ограничьте общее время 30 минутами. |
| Учитель и студент конфликтуют по памяти | Выгружайте учителя из памяти сразу после генерации логитов. Обработайте батч, сохраните логиты на диск, затем учитель del teacher. |
| Студент переобучается на одном типе данных (только заметки, только код) | Добавляйте в датасет универсальные примеры из общей базы (например, Alpaca). Или используйте регуляризацию — не обновляйте все веса, только LoRA. |
Кстати, если ваш девайс совсем слабый (например, ноутбук с 8 ГБ RAM и без GPU), посмотрите руководство по CPU-only инференсу — там много хаков для ускорения на процессоре. А для Android есть специализированный гайд, где distill-on-idle можно адаптировать под мобильный NPU.
Запуск ассистента: инференс без тормозов
После нескольких циклов дистилляции student уже неплохо отвечает на вопросы по вашей истории. Теперь нужно сделать так, чтобы он отвечал мгновенно. Используем 4-битный GGUF версию модели, загруженную через llama-cpp-python с 4 потоками CPU. Для сравнения: запуск 4B модели в FP16 на CPU даёт ~2 токена/сек. В 4-битном GGUF — до 12 токенов/сек на нормальном ноутбуке. Этого хватает для ассистента, который отвечает фразами по 50–100 токенов.
from llama_cpp import Llama
llm = Llama(
model_path="qwen2.5-4b-instruct-q4_K_M.gguf",
n_ctx=8192,
n_threads=4,
n_batch=512
)
# Теперь можно отвечать на запросы
output = llm("У меня есть заметка: 'Купить молоко'. Что я забыл?")
print(output["choices"][0]["text"])
Живой пример: вы написали в заметки планы на день, модель уже знает контекст, и при простом вопросе «что я хотел сделать?» она выдаст не общую фразу, а конкретику из ваших логов. Именно ради этого и затевался distill-on-idle.
Бонус: обновление модели на лету
После каждого цикла дистилляции нужно перезагрузить инференс-сервер с новой версией. Чтобы не терять историю диалогов, можно реализовать hot-reload: держите две копии модели в памяти (новую и старую), и после завершения обучения атомарно переключайте указатель. Но проще и безопаснее — запустить второй процесс на другом порту (если не жалко RAM). Или используйте watchdog, который отслеживает появление нового .gguf файла.
Для тех, кто хочет запустить ассистента на совсем слабом железе, рекомендую ознакомиться с опытом запуска 3B-моделей из статьи Nanbeige 3B vs 30B — принципы те же, но 4B даёт больше контекстной ёмкости.
Не упрощайте: distill-on-idle — не серебряная пуля
Я не буду рассказывать, что это легко. Настройка требует терпения: collector может нахватать шума, дистилляция может съесть батарею, а учитель — вылететь по памяти. Но результат стоит того: персональный ассистент, который работает без интернета, с полным контролем данных. И да, вы будете первым, кто скажет друзьям: «Моя модель знает меня лучше, чем я сам — и это нормально».
Кстати, если вам кажется, что 4B — это мало, посмотрите на опыт запуска 70B моделей на 64 ГБ RAM + 16 ГБ VRAM. Но там другой класс задач. Для ассистента памяти на устройстве важно, чтобы модель была всегда рядом, а не только когда вы рядом с розеткой.