Fine-tuning Qwen 14B для Discord автодополнения | Полный гайд 2026 | AiManual
AiManual Logo Ai / Manual.
11 Фев 2026 Гайд

Персональный автокомплит для Discord: как заставить Qwen 14B говорить вашими словами

Пошаговый гайд по созданию персонального автокомплита для Discord: скрапинг сообщений, QLoRA финтюнинг Qwen 14B, развертывание в Ollama и Chrome-расширение.

Зачем вам это нужно (и почему стандартные решения сосут)

Вы когда-нибудь замечали, что ChatGPT или Claude предлагают вам фразы, которые вы бы никогда не сказали? Слишком формально. Слишком вежливо. Слишком... не вы.

А теперь представьте: вы начинаете писать сообщение в Discord, и ИИ предлагает продолжение, которое звучит именно как вы. С вашими любимыми словечками, мемами, интонацией. Не общий шаблон, а ваша реальная манера общения.

Это не просто автодополнение. Это цифровой двойник вашего стиля общения. И самое приятное - все работает локально, без отправки ваших сообщений в облака OpenAI.

Что мы будем строить (и почему Qwen 14B в 2026 году)

Архитектура проекта выглядит так:

  1. Скрапер Discord сообщений (ваших, только ваших)
  2. Датасет в формате инструкций для финтюнинга
  3. QLoRA адаптер для Qwen 14B через Unsloth.ai
  4. Развертывание в Ollama с GGUF квантованием
  5. Chrome-расширение, которое перехватывает ввод в Discord

Почему Qwen 14B, а не что-то поменьше? Потому что на февраль 2026 года это оптимальный баланс между качеством и требованиями к железу. 14 миллиардов параметров достаточно для понимания контекста, но модель все еще помещается в 16 ГБ VRAM с QLoRA. Новые версии Qwen 2.5 14B показывают результаты близкие к GPT-3.5, но работают локально.

💡
Если у вас слабое железо, можно взять Qwen 7B. Но 14B дает заметно лучшие результаты для автодополнения - модель лучше улавливает стилистические нюансы.

1 Вытаскиваем ваши сообщения из Discord

Первая ошибка, которую все совершают - пытаются скрапить Discord через официальный API. Не делайте так. Вы получите только последние 100 сообщений из каждого канала, и это займет вечность.

Вместо этого используем Data Package Export - функцию, которую Discord добавил после GDPR. Заходите в настройки аккаунта → Privacy & Safety → Request Data. Ждете 30 дней (шутка, обычно 1-2 дня). Получаете ZIP со всеми вашими сообщениями, включая удаленные.

Внутри будет файл messages.csv. Вот как его правильно обработать:

import pandas as pd
import json
from datetime import datetime

# Загружаем данные Discord
df = pd.read_csv('messages.csv', low_memory=False)

# Фильтруем только свои сообщения
df = df[df['AuthorID'] == 'YOUR_USER_ID']

# Убираем команды ботов, системные сообщения
df = df[~df['Content'].str.contains('^!|^/|^\*\*\*', na=False)]

# Убираем слишком короткие сообщения (менее 10 символов)
df = df[df['Content'].str.len() > 10]

print(f"Найдено {len(df)} ваших сообщений")

Внимание: не используйте сообщения других людей без их согласия. Это не только неэтично, но и нарушает Discord ToS. Ваша модель должна учиться только на ваших сообщениях.

2 Готовим датасет для финтюнинга (здесь все ломается)

Стандартный подход - взять сообщения и сделать из них "вопрос-ответ". Не работает. Для автокомплита нам нужно нечто другое: модель должна уметь продолжать начатую фразу.

Правильный формат для Qwen 14B в 2026 году выглядит так:

def create_completion_pairs(messages):
    """Создаем пары "начало сообщения - продолжение""""
    pairs = []
    
    for message in messages:
        # Берем случайную точку разрыва в сообщении
        if len(message) > 20:
            split_point = random.randint(5, len(message) - 10)
            prefix = message[:split_point]
            completion = message[split_point:]
            
            # Форматируем в инструкционный формат Qwen
            formatted = {
                "instruction": f"Продолжи сообщение: {prefix}",
                "input": "",
                "output": completion,
                "system": "Ты - автокомплит для Discord. Продолжай сообщения в стиле пользователя."
            }
            pairs.append(formatted)
    
    return pairs

Сохраняем в JSONL формат, который понимает Unsloth. Нужно 1000-5000 примеров для хорошего результата. Меньше - модель не научится, больше - переобучится на конкретные фразы.

3 QLoRA финтюнинг на Unsloth (быстрее в 2 раза, серьезно)

Unsloth.ai - это не просто обертка над PyTorch. Они переписали ядро обучения для эффективной работы на потребительских GPU. На февраль 2026 года их последняя версия дает ускорение в 2-3 раза по сравнению с стандартным Hugging Face PEFT.

Установка (обновите, если у вас старая версия):

pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps xformers trl peft accelerate bitsandbytes

Конфигурация обучения - здесь большинство настраивает не те параметры:

from unsloth import FastLanguageModel
import torch

# Загружаем Qwen 14B с 4-битной квантовацией
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "Qwen/Qwen2.5-14B-Instruct",
    max_seq_length = 2048,  # Для автокомплита хватит
    dtype = torch.float16,
    load_in_4bit = True,  # Экономит память
)

# Добавляем QLoRA адаптеры
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.1,
    bias = "none",
    use_gradient_checkpointing = True,
    random_state = 42,
)

Параметры обучения, которые действительно работают для автокомплита:

from trl import SFTTrainer
from transformers import TrainingArguments

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    dataset_text_field = "text",
    max_seq_length = 2048,
    args = TrainingArguments(
        per_device_train_batch_size = 2,  # Для 16GB VRAM
        gradient_accumulation_steps = 4,
        warmup_steps = 50,
        num_train_epochs = 3,  # Больше не нужно!
        learning_rate = 2e-4,  # Выше, чем для чат-бота
        fp16 = not torch.cuda.is_bf16_supported(),
        bf16 = torch.cuda.is_bf16_supported(),
        logging_steps = 10,
        optim = "paged_adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "cosine",
        output_dir = "outputs",
        report_to = "none",
    ),
)
💡
Ключевое отличие от обычного финтюнинга: learning_rate 2e-4 вместо 1e-5 и всего 3 эпохи. Автокомплит не должен полностью переписывать модель, только адаптировать ее к вашему стилю.

4 Конвертируем в GGUF и запускаем в Ollama

После обучения у вас есть адаптеры LoRA. Но Ollama не умеет в них напрямую. Нужно слить адаптеры с базовой моделью и сконвертировать в GGUF.

Сначала сохраняем полную модель:

# Сливаем адаптеры с базовой моделью
model.save_pretrained_merged(
    "trained_model",
    tokenizer,
    save_method = "merged_16bit",  # Или "lora" если хотите оставить отдельно
)

Теперь конвертируем в GGUF через llama.cpp (обновите до последней версии 2026 года):

# Клонируем свежий llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make -j$(nproc)

# Конвертируем в GGUF
python convert.py ../trained_model --outtype q4_0
# q4_0 - хороший баланс качество/скорость
# q8_0 - если есть память, качество почти без потерь

Создаем Modelfile для Ollama:

FROM ./qwen-14b-discord.Q4_0.gguf

TEMPLATE """{{ .System }}

Продолжи сообщение: {{ .Prompt }}"""

PARAMETER temperature 0.3  # Низкая температура для консистентности
PARAMETER top_p 0.9
PARAMETER num_predict 50   # Длина автодополнения

SYSTEM """Ты - автокомплит для Discord. 
Твоя задача - продолжать сообщения в стиле пользователя.
Используй его характерные слова и выражения.
Не добавляй лишних объяснений, просто продолжай текст."""

Создаем модель в Ollama:

ollama create my-discord-autocomplete -f ./Modelfile
ollama run my-discord-autocomplete "привет, как дела"

5 Chrome-расширение, которое все связывает

Самая хитрая часть. Нам нужно перехватывать ввод в Discord, отправлять в Ollama, и вставлять предложения. Но Discord использует React и сложный DOM.

Вот рабочий манифест для 2026 года (manifest v3):

{
  "manifest_version": 3,
  "name": "Discord Autocomplete",
  "version": "1.0",
  "permissions": ["activeTab", "storage"],
  "host_permissions": ["http://localhost:11434/*"],
  "content_scripts": [{
    "matches": ["https://discord.com/*"],
    "js": ["content.js"],
    "run_at": "document_end"
  }]
}

И основной код content.js:

class DiscordAutocomplete {
    constructor() {
        this.debounceTimer = null;
        this.lastText = '';
        this.ollamaUrl = 'http://localhost:11434/api/generate';
        
        this.observeMessageInput();
    }
    
    observeMessageInput() {
        // Discord меняет DOM динамически, нужен MutationObserver
        const observer = new MutationObserver((mutations) => {
            const input = this.findMessageInput();
            if (input && !input.hasListener) {
                input.addEventListener('input', this.handleInput.bind(this));
                input.hasListener = true;
            }
        });
        
        observer.observe(document.body, { childList: true, subtree: true });
    }
    
    async handleInput(event) {
        const text = event.target.textContent.trim();
        
        // Дебаунс, чтобы не спамить запросами
        clearTimeout(this.debounceTimer);
        
        // Запускаем автодополнение только если:
        // 1. Текст больше 10 символов
        // 2. Пользователь не печатает быстро
        // 3. Курсор в конце строки
        if (text.length > 10 && text !== this.lastText) {
            this.debounceTimer = setTimeout(async () => {
                const completion = await this.getCompletion(text);
                if (completion) {
                    this.showSuggestion(completion);
                }
            }, 500);
        }
        
        this.lastText = text;
    }
    
    async getCompletion(text) {
        try {
            const response = await fetch(this.ollamaUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    model: 'my-discord-autocomplete',
                    prompt: text,
                    stream: false
                })
            });
            
            const data = await response.json();
            return data.response.trim();
        } catch (error) {
            console.error('Ollama error:', error);
            return null;
        }
    }
    
    showSuggestion(suggestion) {
        // Создаем всплывающую подсказку рядом с полем ввода
        // Реализация зависит от текущего DOM Discord
        // В 2026 году ищем элемент с классом .slateTextArea__container
    }
}

// Запускаем когда DOM готов
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
        new DiscordAutocomplete();
    });
} else {
    new DiscordAutocomplete();
}

Где все ломается: частые ошибки и как их избежать

Ошибка Почему происходит Как исправить
Модель предлагает слишком формальные ответы Системный промпт пересиливает финтюнинг Убрать "Ты полезный ассистент" из системного промпта
Автодополнение срабатывает на каждое нажатие клавиши Нет дебаунса или он слишком короткий Увеличить дебаунс до 500-800мс
Ollama не отвечает из расширения CORS политика браузера Добавить --host 0.0.0.0 при запуске Ollama
Модель повторяет одни и те же фразы Переобучение на маленьком датасете Увеличить датасет или добавить аугментацию

А если хочется проще? Альтернативные пути

Не хотите возиться с финтюнингом? Есть варианты:

  1. Использовать локальную модель без финтюнинга - просто запустите Qwen 14B в Ollama и настройте промпт. Будет работать, но без вашего стиля.
  2. Воспользоваться облачным API - но тогда ваши сообщения уйдут на чужой сервер. Идея теряет смысл.
  3. Собрать систему на базе готового решения для Git-коммитов - адаптировать под Discord.

Но если вы прошли весь путь - у вас теперь есть уникальная вещь. ИИ, который говорит вашими словами. Не общий шаблон, а именно ваша манера общения, сохраненная в 4-битных весах.

Важный нюанс: ваша обученная модель - это цифровой отпечаток вашего стиля общения. Не выкладывайте ее в открытый доступ и не делитесь без необходимости. Это персональные данные в самом буквальном смысле.

Что дальше? Модель можно дообучать по мере накопления новых сообщений. Раз в месяц запускаете скрипт, который добавляет новые сообщения в датасет и делает несколько шагов дообучения. Ваш цифровой двойник будет эволюционировать вместе с вами.

И да, теперь когда вы пишете "привет, как дела", а ИИ предлагает продолжение "норм, только кофе закончился" - вы понимаете, что это действительно вы. Только в 14 миллиардах параметров.