Offline-first AI: LLM, который работает без интернета
Как offline-first AI упаковывает квантованные модели, курируемые корпуса и поиск, чтобы ассистент работал без сети, и чего это стоит.

Электричество отключено уже девять часов, сотовая сеть перегружена, а морозилка в гараже забита мясом, которое вы купили в субботу. Его ещё можно есть? Обычно в такой ситуации спрашиваешь чат-бота. А сейчас первым сломался как раз интернет. Этот сценарий — отправная точка для растущего класса инструментов offline-first AI: ассистент и справочная библиотека, которые живут целиком на вашем ноутбуке. Модель, корпус, поисковый индекс — всё работает локально, так что вопрос «как очистить эту воду» срабатывает одинаково, даже если сетевой кабель выдернут. Мне это интереснее как архитектурная задача, чем как снаряжение для выживальщиков. Когда убираешь сеть, приходится принимать инженерные решения, которые облачный AI может не замечать, и эти решения полезны далеко за пределами аварийных наборов. Если вы думали о запуске больших языковых моделей на собственном железе, ограничения офлайн-ассистента — это самая острая часть той же задачи.
Что на самом деле должен включать offline-first AI
Облачный ассистент — тонкий клиент: ваше приложение отправляет текст, дата-центр возвращает токены. Офлайн-ассистенту приходится воспроизвести весь этот стек на потребительском железе, а значит, поставить три крупных компонента и заставить их работать согласованно. Во-первых, движок инференса. На практике это llama.cpp или одно из его производных: он работает на Apple Silicon, обычных x86-процессорах и потребительских GPU с одним набором зависимостей. Mac-приложение включает его в комплект, Windows-сборка подтягивает при установке. В любом случае нельзя рассчитывать, что он уже есть на машине пользователя — его нужно поставить самим, тихо, во время первого запуска. Во-вторых, модель. Здесь начинается самое интересное, и я к этому вернусь, потому что выбор правильной модели — центральная проблема дизайна. В-третьих, библиотека. Один офлайн-ассистент из этой области включает примерно 930 000 статей Википедии (самое читаемое подмножество), медицинские справочники вроде MedlinePlus и WikiMed, рекомендации FEMA и CDC по действиям в чрезвычайных ситуациях, военные руководства по выживанию, wikiHow, руководства по ремонту от iFixit и дампы Q&A со Stack Exchange по темам вроде домашнего ремонта и любительской радиосвязи. Всё в виде простого текста, лицензии сохранены и показываются для каждой статьи. Полная загрузка весит около 9 ГБ и разрастается до 19 ГБ после установки; урезанная версия без Википедии и iFixit занимает 1,3 ГБ. Это в той же мере кураторское решение, что и инженерное: кто-то решил, какие источники пройдут отбор. Всё это должно укладываться в трёхшаговую установку, которая занимает пять минут кликов и до двух с половиной часов загрузки. Последняя цифра важнее, чем кажется: окно загрузки — единственный шанс что-либо скачать, и навсегда. Та же дисциплина нужна для софта, который нельзя перезагрузить или до которого нельзя дотянуться, только недоступная среда здесь — тёмная кухня во время шторма.
Проблема выбора модели: одно приложение, много «мозгов»
Облачное ПО никогда не сталкивается с таким решением: вы не знаете, какое железо у пользователя, не можете проверить его объём ОЗУ и отправить его на более мощный кластер, а ответ меняет и размер загрузки, и качество каждого ответа на всё время работы. Решение в этой категории инструментов — подбор модели по железу. При первом запуске приложение проверяет объём системной памяти и выбирает подходящую модель: - 128 ГБ и больше: крупная модель флагманского уровня (одна сборка использует NVIDIA Nemotron 3.5 Super, ~80 ГБ на диске), набравшая 21 из 22 баллов в слепом тесте проекта на вопросы о блэкауте и первой помощи. - 32–96 ГБ: модель среднего размера около 27B параметров (~18 ГБ), тоже 21 из 22. - 32 ГБ на Windows: модель ~25 ГБ, настроенная на скорость, 19 из 22. - 16–31 ГБ: модель класса 8B (~6 ГБ), 14 из 22. Перечитайте последнюю строку. Машина, которой владеет большинство людей, — ноутбук с 16 ГБ, — получает мозг, который примерно в трети случаев упускает главную мысль официальных рекомендаций, причём в вопросах, где главная мысль звучит примерно так: «не ешьте курицу, которая шесть часов провела при комнатной температуре». Это неудобная арифметика offline-first AI. Квантование и маленькие модели стали по-настоящему хорошими: хорошо настроенная модель 8B — неплохой помощник для текстов и приличный поисковый интерфейс. Но кривая качества не подстраивается под ограничения вашего развёртывания. Когда облачному приложению нужна умная модель, оно переключает флаг в конфиге. Когда она нужна офлайн-приложению, пользователь должен был три года назад купить ноутбук побольше. Вы поставляете кривую, а кривая поставляет вас. Честное смягчение — то, что выбирают эти инструменты: относиться к маленькой модели как к интерфейсу к корпусу, а не как к авторитету о нём. Каждый ответ перечисляет, какие статьи были прочитаны, с подсвеченным фрагментом, потому что разработчики знают: модель уровня 8B иногда будет ошибаться.
Поиск как слой доверия, а не просто функция
Сейчас все системы retrieval-augmented generation показывают источники; в облаке это галочка в настройках, немного театра прозрачности. Офлайн поиск несёт нагрузку. Это единственное, что стоит между правдоподобной галлюцинацией и пользователем, который действует по ней в реальной чрезвычайной ситуации. Получающийся дизайн выглядит так: модель ищет по библиотеке в простом тексте, читает лучшие фрагменты и пишет ответ со ссылками на то, что прочитала. Если пользователь просит что-то найти, она ищет, даже если уже ответила из памяти, а интерфейс показывает, из какого режима пришёл каждый ответ: из памяти, из статей, которые модель открыла, или из результатов поиска, которые она не открывала. Последнее различие тонкое и важное. «Я нашёл подходящие фрагменты» и «я прочитал статью» — это разные утверждения с разной надёжностью, и интерфейс обращается с ними именно так. Ещё одна деталь отказов, которую я люблю: когда модель пошла искать, но так и не написала итоговый ответ — а маленькие модели так делают постоянно, — приложение заставляет её прочитать лучшие найденные статьи и попробовать ещё раз, вместо того чтобы не показать пользователю ничего. Офлайн нельзя откатиться к модели побольше или поставить запрос в очередь на повтор. Повтор приходится встраивать в цикл агента. Это компактный пример того, что имеют в виду, когда говорят, что LLM-агенты ведут себя как распределённые системы: частичные отказы повсюду, и задача слоя оркестрации — сделать так, чтобы с отказом можно было жить. Ещё один архитектурный выбор, который стоит позаимствовать: бюджет рассуждений. Приложение показывает, сколько токенов модель может потратить на размышления до ответа: выключено, 1 000, 4 000, 16 000 или без ограничений. По достижении лимита размышления обрываются, и модель отвечает на основе того, что успела. От батареи модель, которая ходит по кругу, не просто ошибается, а садит заряд. Жёсткий лимит превращает неограниченную вычислительную задачу в ограниченную — именно такой инстинкт нужен, когда розетка — понятие теоретическое.

Скептики правы (но лишь наполовину)
Самая резкая критика этого жанра: зачем вообще LLM, если можно поставить просто поисковый архив? Kiwix делает именно это уже много лет — Википедия и медицинские справочники, сжатые, проиндексированные, доступные офлайн для поиска, без риска галлюцинаций, работают даже на старом ноутбуке. И для курируемого текстового набора поисковик, пожалуй, надёжнее. Один тестировщик спросил офлайн-ассистента, как сделать лучковое сверло: детали он назвал верно, а движение — нет. Модель описала, как катают шпиндель между ладонями, вообще забыв про лук, и упомянула «раздувание искр», чего фрикционный огонь не даёт. Поисковик такую ошибку не совершит, потому что ничего не генерирует. Но, на мой взгляд, эта критика не замечает, для чего LLM на самом деле нужна в таких системах, и самая сильная защита дизайна — переосмыслить её роль: использовать модель как интеллект поиска, а не как оракула. Поиск по ключевым словам подводит, когда вы не знаете терминологии — когда у вас есть симптомы, а не диагноз; сломанная штука, а не название детали. Семантический поиск помогает с «близким по смыслу», но не с «связанным механизмом или причиной», а именно этот разрыв закрывает генеративная модель: она может взять «листья культуры желтеют по краям» и выдать пять путей поиска, до которых ключевое слово никогда бы не добралось. Задача модели — привести вас к нужным страницам. Задача страниц — быть правыми. Другая критика — «если в апокалипсисе нужна LLM, просто научитесь навыкам» — справедлива как жизненный совет и бесполезна как критика софта. Ничьи знания первой помощи не покрывают детские ожоги, химию очистки воды и ремонт генератора разом. Книги покрывают всё это; спор только об интерфейсе. Где скептики попадают точно в цель — так это в уровень 14 из 22. Если слой интерфейса ошибается в трети случаев, а каждый ответ сопровождается дисклеймером «проверьте источники», рано или поздно честным продуктом окажется архив с поисковой строкой. LLM оправдывает себя лишь настолько, насколько доказуемо помогает непрофессионалам быстрее найти нужный фрагмент, чем Ctrl+F. Это эмпирический вопрос о ваших пользователях, а не вопрос ощущений.
Минимальный стек offline-first AI
Если захотите собрать собственную версию — а стоит, это действительно познавательный проект на выходные, — скелет окажется меньше, чем вы ждёте. Интересны решения, а не код. Локальный ассистент на Python с приоритетом поиска выглядит примерно так:
import subprocess
from pathlib import Path
CORPUS_DIR = Path("./library") # plain-text articles, one file each
MODEL = "./models/granite-8b-q4.gguf" # quantized to fit 16 GB RAM
def search_corpus(query: str, k: int = 5) -> list[dict]:
"""Dumb but honest: rank articles by keyword overlap.
Swap in BM25 or embeddings later; the interface is the point."""
terms = set(query.lower().split())
hits = []
for f in CORPUS_DIR.rglob("*.txt"):
text = f.read_text(errors="ignore")
score = sum(text.lower().count(t) for t in terms)
if score:
hits.append({"path": f.name, "score": score, "text": text[:3000]})
return sorted(hits, key=lambda h: -h["score"])[:k]
def answer(question: str) -> str:
sources = search_corpus(question)
context = "\n\n".join(
f"[SOURCE {i}: {s['path']}]\n{s['text']}"
for i, s in enumerate(sources, 1)
)
prompt = (
"Answer ONLY from the sources below. Cite them as [SOURCE n]. "
"If they don't cover it, say so.\n\n"
f"{context}\n\nQuestion: {question}\nAnswer:"
)
out = subprocess.run(
["llama-cli", "-m", MODEL, "-p", prompt,
"-n", "512", "--ctx-size", "8192"],
capture_output=True, text=True,
)
citations = "\n".join(f"[{i}] {s['path']}" for i, s in enumerate(sources, 1))
return out.stdout + "\n\n--- Sources ---\n" + citations
print(answer("how long is food safe in a fridge without power"))
Всё, что делает это похожим на продукт, а не на игрушку, заложено в ограничениях внутри этих сорока строк: промпт запрещает отвечать вне источников, цитаты добавляются механически (модели никогда не доверяют форматирование — в одном реальном баге модели выдумывали строку ««Sources:»» с перечислением собственных поисковых команд), а длина генерации ограничена. Чего здесь нет, но что нужно для продакшена: индекс эмбеддингов для семантического поиска, бюджет токенов на рассуждения, определение объёма памяти для выбора файла модели и метаданные лицензии для каждого файла, чтобы интерфейс мог их показать. Обратите внимание, чего тут нет: никаких сетевых вызовов, никаких API-ключей, никакой телеметрии. Как только файл модели и корпус оказываются на диске, эта программа физически не способна утечь ни один вопрос. Приватность перестаёт быть политикой и становится следствием графа зависимостей.
Ограничения, которые возвращаются домой вместе с вами
Если строить offline-first, несколько неочевидных ограничений становятся постоянными жильцами вашей архитектуры. **Хранилище и окно установки — разовые.** Полной библиотеке во время установки нужно около 27 ГБ свободного места, прежде чем она займёт 19 ГБ, и всё должно прийти при первоначальной загрузке. Подгружать недостающий медицинский справочник во время урагана нельзя. Это подталкивает к жёсткой курации: 930 000 статей звучит много, пока не вспомнишь, что в Википедии миллионы; кто-то ранжировал по читаемости и отрезал хвост. **Питание — ресурс первого класса.** По замерам на MacBook Pro ответ потребляет около 100 Вт в течение 5–40 секунд на вопрос — полная батарея даёт примерно 100–300 ответов. Внезапно длинная цепочка рассуждений — не просто медленная, а решение о топливе. Бюджеты рассуждений и маленькие уровни моделей перестают быть оптимизациями и становятся самим продуктом. **Канал обновлений — ваша спасательная линия.** Приложение должно обновлять модели, библиотеку и бинарник одним кликом, не ломая локальное состояние, потому что следующая возможность скачать что-либо может появиться через месяцы. И дисциплина работы с фоновыми ресурсами важна по-новому: исправление в одном релизе наконец заставило Cmd+Q останавливать движок инференса, который после закрытия окна держал многогигабайтную модель в памяти. От батареи это не утечка памяти, а утечка тепла и часов работы. **Пользовательские файлы входят в границу доверия.** Когда пользователи загружают собственные PDF — руководство по генератору, семейный план действий при ЧС, — ваш конвейер поиска получает враждебно грязные входные данные: сканы, странные кодировки, огромные файлы. Офлайн ничего из этого не передать облачному парсеру.

Почему этот паттерн важен и за пределами чрезвычайных ситуаций
Приватность перестаёт быть обещанием политики и становится следствием графа зависимостей. Программа без сетевых вызовов не может ничего утечь — никогда.
Заголовки достаются эмоциональному сценарию ЧС, но история на самом деле в архитектуре. Тот же стек — локальный инференс, курируемый корпус, поиск с цитатами, жёсткие вычислительные бюджеты — нужен клинике с данными пациентов, которые по закону не могут покинуть здание, заводскому цеху с изолированными от сети машинами, журналисту в стране, где журнал запросов — это риск, или исследовательскому судну со спутниковым интернетом, оплачиваемым по мегабайтам. Один практик в обсуждении описал именно такой сценарий: вручную собранная библиотека и локальные модели на участке без сотовой связи, которые обновляются перед каждой поездкой и постепенно вытесняют большую часть повседневного облачного AI, даже когда есть оптоволокно. На эту траекторию я бы поставил. Разрыв между квантованной локальной моделью 8B и API-моделью переднего края реален, но достаточно узок, чтобы для задач, опирающихся на поиск — «найди нужный фрагмент и честно перескажи его», — локальный вариант часто оказывался достаточным. А достаточность плюс нулевая предельная стоимость плюс гарантированная приватность — убедительный набор. Фронтирные модели продолжают улучшаться, но и квантование тоже, а маленькие модели едут на той же волне масштабирования с отставанием. Если вас интересует эффективность на этом конце кривой, настоящая история эффективности инференса — в моделях класса flash. Мой совет, если вы проектируете что-то в этой области: берите паттерн, а не эстетику выживальщиков. Курируйте безжалостно — библиотека из 200 документов, которым вы доверяете, лучше 930 000, которые вы не читали. Делайте цитаты механическими, никогда не генерируемыми моделью. Ставьте модели жёсткий бюджет на размышления. И решите явно, оракул ваша модель или поисковый интерфейс. Выдерживают проверку те инструменты, которые достаточно честны, чтобы быть вторыми.
Итог
Offline-first AI — не о том, как пережить апокалипсис. Речь о том, как выглядит ваша архитектура, когда каждый запасной выход — модель побольше, облачный fallback, постоянная сеть, неограниченное питание — заварен наглухо. Остаются курируемый корпус, квантованная модель, подобранная под железо, которым вы не управляете, поиск, который несёт доверие, и цикл агента, созданный так, чтобы корректно отказывать. Эти дисциплины полезны и онлайн. Шторм просто заставляет выучить их быстрее.


