Руководство по сборке локального AI-стека. Часть 3. Наполнение моделями

—

от автора

Руководство по сборке локального AI-стека

Часть 3. Наполнение моделями и HTML-шпаргалка

На этом шаге мы наполняем папку models/ моделями под разные задачи, раскладываем их по правилам (плоско vs подпапки с mmproj) и делаем HTML-шпаргалку — статическую страницу, которая отвечает на вопрос «какая модель для какой задачи», не требуя лезть в конфиги или документацию.

Зачем это нужно

После части 2 у нас есть одна тестовая модель (Qwen3.5-2B) и рабочий llama-server. Но для реальной работы нужен набор моделей: одна сильна в коде, другая — в ролевом общении, третья — в русском, четвёртая — в OCR. Держать это в голове сложно, а лезть в интернет за описанием каждой — долго.

HTML-шпаргалка решает две задачи:

  1. Быстрый выбор. Таблица «задача -> модель -> почему» вверху страницы: 80% случаев решаются за 5 секунд.

  2. Карточки моделей. Плюсы, минусы, «лучше всего для», кнопка «Копировать -m <модель>» — чтобы не набирать имя вручную.

Страница статическая, открывается через file:// в браузере, работает без сервера и без интернета.

Правила раскладки моделей

Из части 2: обычные модели — плоско, мультимодальные — в подпапке с mmproj. Финальная структура models/ в этом проекте выглядит так:

models/├── Blitzar-Coder-4B-F.1.Q4_K_M.gguf           <- кодинг, компактно├── MN-12B-Runeweaver-RP-RU.Q4_K_M.gguf        <- РП без цензуры├── OxCoder-9B.Q4_K_M.gguf                     <- кодинг, агентский├── qwen2.5-coder-7b-instruct-q4_k_m.gguf      <- кодинг, быстрый├── Qwen3-4B-Instruct-2507-Q4_K_M.gguf         <- чат, альтернатива├── RusMind-Momentum-4.7-Q4_K_M.gguf           <- русский, перевод│├── gemma-4-E2B-it-Q4_K_M/                     <- РП + vision│   ├── gemma-4-E2B-it-Q4_K_M.gguf│   └── mmproj-F16.gguf├── gemma-4-E4B-it-Q4_K_M/                     <- РП + vision (чемпион)│   ├── gemma-4-E4B-it-Q4_K_M.gguf│   └── mmproj-F16.gguf├── Qwen3.5-2B-Q4_K_M/                         <- сверхбыстрый чат + vision│   ├── Qwen3.5-2B-Q4_K_M.gguf│   └── mmproj-F16.gguf├── Qwen3.5-4B-Q4_K_M/                         <- быстрый чат + vision│   ├── Qwen3.5-4B-Q4_K_M.gguf│   └── mmproj-F16.gguf├── Qwen3.5-9B-Q4_K_M/                         <- универсал + vision│   ├── Qwen3.5-9B-Q4_K_M.gguf│   └── mmproj-F16.gguf├── Qwen3VL-4B-Instruct-Q4_K_M/                <- vision, OCR│   ├── Qwen3VL-4B-Instruct-Q4_K_M.gguf│   └── mmproj-Qwen3VL-4B-Instruct-F16.gguf    <- имя проектора другое — норм│└── media/                                     <- diffusion (часть 4)    ├── z_image_turbo-Q4_K.gguf    ├── mage_flow_turbo_bf16.safetensors    └── vae/

Важные моменты:

  • Имя mmproj не обязано точно совпадать с именем модели. Главное — начинается с mmproj. mmproj-Qwen3VL-4B-Instruct-F16.gguf и mmproj-F16.gguf одинаково валидны. Режим роутера подхватывает любой файл в папке модели, имя которого начинается с mmproj.

  • models/media/ исключается из списка LLM. Скрипт run_llama_mmap.sh явно отсекает эту папку (! -path "$MEDIA_MODELS_DIR/*"), чтобы диффузионные модели не попадали в дропдаун чата.

  • llama.cpp не проверяет «правильность» имени. Файл qwen2.5-coder-7b-instruct-q4_k_m.gguf в нижнем регистре — это нормально, скрипт найдёт его по *.gguf.

Каталог моделей по задачам

Ниже — что скачано и зачем. Все модели — в квантовании Q4_K_M (если не указано иное), это оптимум «качество/размер» для CPU.

Универсальные (чат, саммари)

Модель

Размер

Зачем

Qwen3.5-9B

~6 ГБ

Основная «рабочая лошадка». Лучший баланс качества и скорости, хороший русский, контекст до 160 страниц. Для большинства задач — первый выбор.

Qwen3.5-4B

~3 ГБ

Когда 9B долго думает. Достаточно умна для чата и лёгких саммари, в 2–3 раза быстрее.

Qwen3.5-2B

~1.5 ГБ

Сверхбыстрая: черновики, проверка идей, «спросить и забыть». Тестовая модель из части 2.

Qwen3-4B-Instruct-2507

~3 ГБ

Альтернатива Qwen3.5-4B с другим стилем ответа. Полезна для «второго мнения» на технических вопросах.

Gemma-4-E4B

~3 ГБ

Хорошо держит контекст и понимает смысл. В саммари на уровне qwen3.5-9b.

Кодинг

Модель

Размер

Зачем

Qwen2.5-Coder-7B

~4.5 ГБ

Быстрый и сильный в коде. HumanEval 76.0 — один из лучших в классе 7–8B. Для повседневных правок и объяснений.

OxCoder-9B

~6 ГБ

Специализация Qwen3.5-9B под агентский кодинг: планы рефакторинга, работа с проектами. Медленнее 7B, но глубже.

Blitzar-Coder-4B

~2.5 ГБ

Компактный кодер. Только с ручным контролем: зацикливается на генерации тестов, «тесты» в виде голых assert — мёртвый код. Для простых скриптов.

Ролевое общение

Модель

Размер

Зачем

Gemma-4-E4B

~3 ГБ

Чемпион по русскому РП. Живой язык, действия в скобках, без перебора с «йо-хо-хо». Также сильна в саммари (глубина вопросов) и анализе перевода с иронией. Vision (с mmproj).

Gemma-4-E2B

~1.5 ГБ

Лёгкая версия E4B: атмосферная, быстрая, но иногда забывает детали промпта. Для коротких РП-сессий. Vision (с mmproj).

MN-12B-Runeweaver-RP-RU

~7 ГБ

Специализированная русскоязычная RP-модель без цензуры. В тестах склонна к морализаторству («поговори с ребёнком о самооценке» вместо драмы) — требует точных промптов и temp 0.9+.

Русский язык

Модель

Размер

Зачем

RusMind-4.7-Momentum

~3.4 ГБ

Специализированная русскоязычная модель. Лучший перевод с сохранением иронии (8.5/10 в тестах). В саммари «додумывает» — не для фактологии, а для «живого» русского.

Vision (изображения, OCR)

Модель

Размер

Зачем

Qwen3VL-4B

~3 ГБ

Отдельная VL-модель: OCR (включая рукописный), описание фото, вопросы по скриншотам. Как текстовый чат слабее Qwen3.5-4B.

Qwen3.5-2B/4B/9B + mmproj

+300–600 МБ

Уже есть в списке «Универсальные». С mmproj становятся мультимодальными.

Gemma-4-E2B/E4B + mmproj

+300–600 МБ

Уже есть в списке «Ролевое». С mmproj видят изображения.

Что скачать в первую очередь: Qwen3.5-9B (универсал), OxCoder-9B (кодинг), Gemma-4-E4B (РП + саммари + vision), Qwen3VL-4B (OCR). Это 4 модели, которые покрывают 90% задач. Остальные — по мере необходимости.

HTML-шпаргалка

web/models.html — статическая страница. Устроена так:

  1. Таблица «Быстрый выбор по задаче» — 12 строк «задача -> модель -> почему». Самое полезное: не нужно читать карточки, чтобы найти «чем сделать OCR».

  2. Фильтры по задачам — кнопки «Чат», «Код», «РП», «Тексты», «Зрение», «Быстро». Нажатие скрывает карточки, не относящиеся к задаче.

  3. Карточки моделей — для каждой: описание, теги задач, плюсы/минусы, «лучше всего для», кнопка «Копировать -m <модель>».

  4. Адаптивность — на телефоне карточки в один столбец, таблица сжимается.

  5. Печать — ссылка в футере, если захотите распечатать.

Как открыть:

file:///path/to/llama_server/web/models.html

Или из плашки run_llama_mmap.sh — там есть строка Шпаргалка: file://....

Как добавить новую модель. В <script>, в массив MODELS, добавить объект:

{    name: 'Название-модели-Q4_K_M',    size: '7B', vision: false,    tasks: ['code', 'fast'],           // теги из TASKS    desc: 'Краткое описание одной строкой.',    pros: ['плюс 1', 'плюс 2'],    cons: ['минус 1'],    best_for: 'Для чего лучше всего'}

И, если нужно, добавить строку в <tbody> таблицы quick-table. Больше ничего править не надо — карточка и фильтры подхватят автоматически.

Как добавить новую задачу. В объект TASKS:

const TASKS = {    // ...    audio: { label: 'Аудио', color: '#9c27b0' }};

Кнопка фильтра появится сама.

Код HTML-шпаргалки (web/models.html)

<!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>LLM Cheat Sheet — выбор модели</title><style>:root {    --bg: #121212;    --surface: #1e1e1e;    --surface-hover: #2a2a2a;    --primary: #bb86fc;    --primary-dim: #9a67ea;    --text: #e1e1e1;    --text-dim: #9e9e9e;    --border: #333;    --green: #4caf7d;    --orange: #ffb74d;    --blue: #64b5f6;    --pink: #f06292;    --purple: #ba68c8;    --cyan: #4dd0e1;}* { box-sizing: border-box; margin: 0; padding: 0; }body {    background: var(--bg); color: var(--text);    font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;    line-height: 1.55;    padding: 32px 16px;}.container { max-width: 1200px; margin: 0 auto; }header {    margin-bottom: 32px; padding-bottom: 20px;    border-bottom: 1px solid var(--border);}h1 { font-size: 1.8rem; font-weight: 400; margin-bottom: 8px; }h1 .accent { color: var(--primary); }.subtitle { color: var(--text-dim); font-size: 0.95rem; }h2 {    font-size: 1.2rem; font-weight: 500;    margin: 32px 0 16px; color: var(--text);    display: flex; align-items: center; gap: 8px;}h2::before {    content: ''; width: 3px; height: 20px;    background: var(--primary); border-radius: 2px;}/* Quick picks table */.quick-table {    width: 100%; border-collapse: collapse;    background: var(--surface); border-radius: 12px; overflow: hidden;}.quick-table th, .quick-table td {    padding: 12px 16px; text-align: left;    border-bottom: 1px solid var(--border);    font-size: 0.92rem;}.quick-table th {    background: #252525; color: var(--text-dim);    font-weight: 500; font-size: 0.8rem;    text-transform: uppercase; letter-spacing: 0.5px;}.quick-table tr:last-child td { border-bottom: none; }.quick-table tr:hover { background: var(--surface-hover); }.quick-table .task { color: var(--primary); font-weight: 500; white-space: nowrap; }.quick-table .model { font-family: 'Consolas', monospace; font-size: 0.85rem; color: var(--text); }/* Filters */.filters {    display: flex; flex-wrap: wrap; gap: 8px;    margin-bottom: 20px;}.filter-btn {    background: var(--surface); color: var(--text-dim);    border: 1px solid var(--border); padding: 6px 14px;    border-radius: 16px; cursor: pointer;    font-size: 0.85rem; font-family: inherit;    transition: all 0.2s;}.filter-btn:hover { color: var(--text); border-color: var(--primary-dim); }.filter-btn.active {    background: var(--primary); color: #000;    border-color: var(--primary); font-weight: 500;}/* Cards */.models-grid {    display: grid;    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));    gap: 16px;}.card {    background: var(--surface); border-radius: 12px;    padding: 20px;    box-shadow: 0 2px 8px rgba(0,0,0,0.4);    transition: transform 0.15s, box-shadow 0.15s;    display: flex; flex-direction: column;}.card:hover {    transform: translateY(-2px);    box-shadow: 0 4px 16px rgba(0,0,0,0.6);}.card-header {    display: flex; justify-content: space-between;    align-items: flex-start; gap: 12px;    margin-bottom: 12px;}.card-title {    font-size: 1.05rem; font-weight: 500;    font-family: 'Consolas', monospace;    word-break: break-word;}.card-badges { display: flex; gap: 4px; flex-shrink: 0; }.badge {    font-size: 0.7rem; padding: 2px 8px;    border-radius: 10px; font-weight: 600;    text-transform: uppercase; letter-spacing: 0.3px;}.badge.size { background: #2a2a2a; color: var(--text-dim); }.badge.vision { background: var(--blue); color: #000; }.card-desc {    font-size: 0.9rem; color: var(--text-dim);    margin-bottom: 14px;}.section-label {    font-size: 0.75rem; text-transform: uppercase;    letter-spacing: 0.5px; color: var(--text-dim);    margin-bottom: 4px;}.pros-list, .cons-list {    list-style: none; margin-bottom: 12px;}.pros-list li, .cons-list li {    font-size: 0.87rem; padding-left: 18px;    position: relative; margin-bottom: 3px;}.pros-list li::before {    content: '+'; position: absolute; left: 4px;    color: var(--green); font-weight: bold;}.cons-list li::before {    content: '−'; position: absolute; left: 4px;    color: var(--orange); font-weight: bold;}.best-for {    background: #252525; border-radius: 8px;    padding: 10px 12px; margin-bottom: 12px;    font-size: 0.87rem;}.best-for strong { color: var(--primary); font-weight: 500; }.tasks-row {    display: flex; flex-wrap: wrap; gap: 4px;    margin-bottom: 12px;}.task-tag {    font-size: 0.72rem; padding: 2px 8px;    border-radius: 10px;    background: #252525; color: var(--text-dim);}.launch-row {    display: flex; align-items: center; gap: 8px;    padding-top: 12px; border-top: 1px solid var(--border);    margin-top: auto;}.launch-cmd {    flex: 1; font-family: 'Consolas', monospace;    font-size: 0.8rem; color: var(--text-dim);    overflow: hidden; text-overflow: ellipsis;    white-space: nowrap;}.copy-btn {    background: var(--surface-hover); color: var(--text-dim);    border: 1px solid var(--border); padding: 4px 10px;    border-radius: 6px; cursor: pointer;    font-size: 0.75rem; font-family: inherit;    transition: all 0.2s;}.copy-btn:hover { color: var(--primary); border-color: var(--primary); }.copy-btn.copied { color: var(--green); border-color: var(--green); }.hidden { display: none !important; }footer {    margin-top: 48px; padding-top: 24px;    border-top: 1px solid var(--border);    color: var(--text-dim); font-size: 0.85rem;    text-align: center;}@media (max-width: 600px) {    body { padding: 16px 8px; }    h1 { font-size: 1.4rem; }    .quick-table th, .quick-table td { padding: 8px 10px; font-size: 0.82rem; }    .models-grid { grid-template-columns: 1fr; }    .card { padding: 16px; }}</style></head><body><div class="container"><header>    <h1>LLM <span class="accent">Cheat Sheet</span></h1>    <div class="subtitle">Быстрый выбор модели по задаче. Все модели: Q4_K_M, запуск через <code>llama-server</code>.</div></header><h2>Быстрый выбор по задаче</h2><table class="quick-table">    <thead>        <tr><th>Задача</th><th>Модель</th><th>Почему</th></tr>    </thead>        <tbody>        <tr><td class="task">Универсальный чат</td><td class="model">Qwen3.5-9B</td><td>Лучший баланс качества/скорости, хороший русский</td></tr>        <tr><td class="task">Быстрый чат</td><td class="model">Qwen3.5-4B/2B</td><td>Когда нужен простой чат без сложных контекстов</td></tr>        <tr><td class="task">Ролевое (чемпион)</td><td class="model">Gemma-4-E4B</td><td>Живой русский, действия в скобках, без перебора</td></tr>        <tr><td class="task">Ролевое (лёгкое)</td><td class="model">Gemma-4-E2B</td><td>2B, но атмосферно — быстрая альтернатива</td></tr>        <tr><td class="task">Ролевое (без цензуры)</td><td class="model">MN-12B-Runeweaver</td><td>Поддерживает все кроме реальных правонарушений (план действий)</td></tr>        <tr><td class="task">Кодинг (агентский)</td><td class="model">OxCoder-9B</td><td>План рефакторинга 9/10, хороший кодинг</td></tr>        <tr><td class="task">Кодинг (быстро)</td><td class="model">Qwen2.5-Coder-7B</td><td>HumanEval 76, но нужно проверять вручную (есть упущения)</td></tr>        <tr><td class="task">Кодинг (компактно)</td><td class="model">Blitzar-Coder-4B</td><td>Только с контролем: зацикливается на тестах</td></tr>        <tr><td class="task">Саммари / ключевые мысли</td><td class="model">Qwen3.5 (9B, 4B), Gemma-4 (E2B, E4B)</td><td>9B, E4B - самые сильные кандидаты. 4B, E2B - для простых задач</td></tr>        <tr><td class="task">Перевод с иронией</td><td class="model">RusMind / Gemma-E4B</td><td>RusMind — лучше результат; Gemma — выдает варианты</td></tr>        <tr><td class="task">Изображения / OCR</td><td class="model">Qwen3VL-4B, Qwen3.5 (2B, 4B, 9B), Gemma (e2b, e4b)</td><td>VL, рукописный текст, контекст</td></tr>        <tr><td class="task">Сверхбыстро</td><td class="model">Gemma-4-E2B, Qwen3.5-2B</td><td>Мгновенно, минимум RAM</td></tr>    </tbody></table><h2>Все модели</h2><div class="filters" id="filters"></div><div class="models-grid" id="models-grid"></div><footer>    <div>Шпаргалка по моделям в <code>models/</code> · <a href="#" onclick="window.print(); return false;" style="color: var(--primary);">Печать</a></div></footer></div><script>const TASKS = {    chat:     { label: 'Чат',       color: '#64b5f6' },    code:     { label: 'Код',       color: '#4dd0e1' },    roleplay: { label: 'РП',        color: '#f06292' },    text:     { label: 'Тексты',    color: '#ba68c8' },    vision:   { label: 'Зрение',    color: '#ffb74d' },    fast:     { label: 'Быстро',    color: '#4caf7d' }};const MODELS = [    {        name: 'Qwen3.5-9B-Q4_K_M',        size: '9B', vision: true,        tasks: ['chat', 'text', 'vision'],        desc: 'Универсальная рабочая лошадка: связность, грамматика, длинный контекст.',        pros: ['Лучший баланс качества и скорости', 'Хороший русский', 'Точнее RusMind в саммари', 'Vision (с mmproj)'],        cons: ['Медленнее младших версий', 'В коде пропускает edge case с пустой строкой'],        best_for: 'Основной чат, саммари, перевод, обработка результатов поиска'    },    {        name: 'Qwen3.5-4B-Q4_K_M',        size: '4B', vision: true,        tasks: ['chat', 'text', 'fast', 'vision'],        desc: 'Хороший компромисс между скоростью и качеством.',        pros: ['Заметно быстрее 9B', 'Достаточно умен для большинства задач', 'Vision (с mmproj)'],        cons: ['Сложные тексты хуже, чем у 9B', 'В РП склонна к «маньячному» тону'],        best_for: 'Быстрый чат, простой перевод, лёгкие саммари'    },    {        name: 'Qwen3.5-2B-Q4_K_M',        size: '2B', vision: true,        tasks: ['fast', 'vision'],        desc: 'Сверхбыстрая, для черновиков и проверки идей.',        pros: ['Мгновенный отклик', 'Минимум RAM', 'Vision (с mmproj)'],        cons: ['Простое качество', 'Перевод с сарказмом проваливает (3/10)', 'Не для сложных рассуждений'],        best_for: 'Черновики, быстрые вопросы, проверка идей'    },    {        name: 'Qwen3-4B-Instruct-2507-Q4_K_M',        size: '4B', vision: false,        tasks: ['chat', 'text', 'fast'],        desc: 'Альтернатива Qwen3.5-4B с другим стилем ответа, только текст.',        pros: ['Другой стиль ответа', 'Хорош для технических вопросов'],        cons: ['Нет зрения', 'Слабее Qwen3.5-4B в целом'],        best_for: 'Текстовые задачи без vision, альтернативный взгляд'    },    {        name: 'qwen2.5-coder-7b-instruct-q4_k_m',        size: '7B', vision: false,        tasks: ['code'],        desc: 'Один из сильнейших в классе 7-8B для кодинга.',        pros: ['HumanEval 76.0', 'Хорош в Python, JS, C++, Go', 'Понимает длинные файлы'],        cons: ['Слабее в общем чате, чем 9B', 'Не защищает от пустой строки в edge case'],        best_for: 'Генерация, редактирование кода, объяснение кода'    },    {        name: 'OxCoder-9B',        size: '9B', vision: false,        tasks: ['code'],        desc: 'Специализация Qwen3.5-9B под агентский кодинг и длинные задачи.',        pros: ['Лучший в тестах: план рефакторинга 9/10, код 9/10', 'Обрабатывает edge cases', 'Не зацикливается'],        cons: ['Тяжелее 7B-кодеров', 'Избыточен для простых скриптов'],        best_for: 'Агентские задачи, рефакторинг больших проектов, сложный код'    },    {        name: 'Blitzar-Coder-4B-F.1',        size: '4B', vision: false,        tasks: ['code', 'fast'],        desc: 'Компактный кодер на базе Qwen3-4B, обучен на большом кодовом датасете.',        pros: ['4B — быстрый', 'Мультиязычный кодинг', 'Мало RAM', 'Защищает от пустой строки'],        cons: ['Зацикливается на генерации тестов', '«Тесты» в виде голых assert — мёртвый код', 'Просит запустить инструмент'],        best_for: 'Простые скрипты — только с ручным контролем и без автоматики'    },    {        name: 'Qwen3VL-4B-Instruct-Q4_K_M',        size: '4B', vision: true,        tasks: ['vision', 'chat'],        desc: 'VL-модель: читает изображения, OCR, отвечает по картинкам.',        pros: ['Понимает изображения', 'OCR (включая рукописный)', 'Отвечает по контексту картинки'],        cons: ['Как текстовик слабее Qwen3.5-4B'],        best_for: 'Описание фото, распознавание текста, вопросы по скриншотам'    },    {        name: 'Gemma-4-E4B-it-Q4_K_M',        size: '4B', vision: true,        tasks: ['roleplay', 'chat', 'fast', 'vision', 'text'],        desc: 'Лучшая для русского РП: живой язык, действия в скобках, без перебора.',        pros: ['Естественный диалог', 'Держит персонажа', 'Отличный анализ перевода с иронией', 'Сильна в саммари: глубина вопросов, точность терминов', 'Vision (с mmproj)'],        cons: ['Меньше знаний, чем у Qwen3.5-9B', 'Иногда переименовывает объекты из промпта'],        best_for: 'Ролевое общение, перевод с анализом нюансов, саммари, живой русский'    },    {        name: 'Gemma-4-E2B-it-Q4_K_M',        size: '2B', vision: true,        tasks: ['roleplay', 'fast', 'vision'],        desc: 'Сверхлёгкая Gemma для быстрых РП-сессий и черновиков.',        pros: ['Мгновенная', 'Минимум RAM', 'Атмосферная для 2B', 'Саммари на уровне 4B-моделей, несмотря на размер', 'Vision (с mmproj)'],        cons: ['Простое качество', 'Иногда забывает ключевые детали промпта'],        best_for: 'Быстрое РП, черновики, быстрые саммари, простые диалоги'    },    {        name: 'RusMind-4.7-Momentum',        size: '4.7B', vision: false,        tasks: ['text', 'chat'],        desc: 'Специализированная русскоязычная модель, высокие баллы MERA v2.',        pros: ['«Живой» русский', 'Лучший перевод с сохранением иронии (8.5/10)', 'Q4_K_M ~3.4 ГБ'],        cons: ['Слабее в коде и математике', 'В саммари «додумывает» (философия, сленг)'],        best_for: 'Перевод с сохранением тона, работа с русскими текстами (не фактология)'    },    {        name: 'Meta-Llama-3.1-8B-Instruct-abliterated',        size: '8B', vision: false,        tasks: ['roleplay', 'chat'],        desc: 'Ролевая модель без цензурных срывов. Официально русский не поддерживает.',        pros: ['Без фильтров', 'Сильна в англоязычном РП', 'Пишет стихи, сцены'],        cons: ['На русском промпте соскакивает на английский', 'Слабее в кодинге и математике'],        best_for: 'Англоязычное РП и творческие сценарии; для русского — не подходит'    },    {        name: 'MN-12B-Runeweaver-RP-RU',        size: '12B', vision: false,        tasks: ['roleplay'],        desc: 'Специализированная русскоязычная RP-модель без цензуры.',        pros: ['Без фильтров', 'Русский язык', 'Специально под РП'],        cons: ['Заговаривается', 'Иногда переименовывает объекты', 'Тяжелее 4–8B'],        best_for: 'РП без цензуры, требует точных промптов'    }];// --- Render filters ---const filtersEl = document.getElementById('filters');const allBtn = document.createElement('button');allBtn.className = 'filter-btn active';allBtn.textContent = 'Все';allBtn.dataset.filter = 'all';filtersEl.appendChild(allBtn);for (const [key, info] of Object.entries(TASKS)) {    const btn = document.createElement('button');    btn.className = 'filter-btn';    btn.textContent = info.label;    btn.dataset.filter = key;    btn.style.setProperty('--task-color', info.color);    filtersEl.appendChild(btn);}// --- Render model cards ---const grid = document.getElementById('models-grid');MODELS.forEach(m => {    const card = document.createElement('div');    card.className = 'card';    card.dataset.tasks = m.tasks.join(',');    const taskTags = m.tasks.map(t => `<span class="task-tag" style="color:${TASKS[t].color}">${TASKS[t].label}</span>`).join('');    const pros = m.pros.map(p => `<li>${p}</li>`).join('');    const cons = m.cons.map(c => `<li>${c}</li>`).join('');    const visionBadge = m.vision ? '<span class="badge vision">vision</span>' : '';    card.innerHTML = `        <div class="card-header">            <div class="card-title">${m.name}</div>            <div class="card-badges">                <span class="badge size">${m.size}</span>                ${visionBadge}            </div>        </div>        <div class="card-desc">${m.desc}</div>        <div class="tasks-row">${taskTags}</div>        <div class="section-label">Сильные стороны</div>        <ul class="pros-list">${pros}</ul>        <div class="section-label">Слабые стороны</div>        <ul class="cons-list">${cons}</ul>        <div class="best-for"><strong>Лучше всего для:</strong> ${m.best_for}</div>        <div class="launch-row">            <span class="launch-cmd">-m ${m.name}</span>            <button class="copy-btn" onclick="copyLaunch(this, '-m ${m.name}')">Копировать</button>        </div>    `;    grid.appendChild(card);});// --- Filter logic ---filtersEl.addEventListener('click', e => {    if (!e.target.classList.contains('filter-btn')) return;    const filter = e.target.dataset.filter;    filtersEl.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));    e.target.classList.add('active');    grid.querySelectorAll('.card').forEach(card => {        if (filter === 'all' || card.dataset.tasks.split(',').includes(filter)) {            card.classList.remove('hidden');        } else {            card.classList.add('hidden');        }    });});// --- Copy button ---function copyLaunch(btn, text) {    navigator.clipboard.writeText(text).then(() => {        const orig = btn.textContent;        btn.textContent = '✓';        btn.classList.add('copied');        setTimeout(() => {            btn.textContent = orig;            btn.classList.remove('copied');        }, 1200);    });}</script></body></html>

Проверка после наполнения

1. Все модели видны в роутере:

./run_llama_mmap.sh

В логе должно быть: Router mode: каталог ./models (N шт.). Число N — количество .gguf без mmproj и без media/. Для текущего листинга — 10 (Blitzar, MN-12B, OxCoder, qwen2.5-coder, Qwen3-4B, RusMind, Gemma-E2B, Gemma-E4B, Qwen3.5-2B, Qwen3.5-4B, Qwen3.5-9B, Qwen3VL-4B).

2. Модель с mmproj видит картинки:

./run_llama_mmap.sh -m Qwen3VL-4B-Instruct-Q4_K_M

В логе — Найден mmproj: mmproj-Qwen3VL-4B-Instruct-F16.gguf и clip_model_load: .... В UI прикрепите картинку — модель должна её описать.

3. Шпаргалка открывается:

xdg-open web/models.html     # или file:// в браузере

Проверьте: таблица сверху, фильтры работают, кнопка «Копировать» копирует -m <модель>.

4. Тест «какая модель для чего»:

  • Откройте шпаргалку, найдите «Кодинг (агентский)» -> OxCoder-9B. Скопируйте -m OxCoder-9B.Q4_K_M или любую другую модель.

  • Запустите ./run_llama_mmap.sh -m OxCoder-9B.Q4_K_M.

  • Отправте промпт для теста.

ссылка на оригинал статьи https://habr.com/ru/articles/1099492/