Запускаем Qwen/DeepSeek локально
Как выбрать модель под свою память, запустить её через Ollama или llama.cpp, поднять OpenAI-совместимый сервер и честно измерить токены в секунду на своём компьютере.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Локальная модельлокальная LLMEN local LLMМодель, которая работает на вашем компьютере без облака — бесплатно, офлайн и без передачи данных третьим лицам. работает бесплатно, без интернета и без отправки ваших данных кому-либо: код, документы, переписку можно давать ей без опаски. Её не ограничивают тарифы, лимиты и доступность облачных API в вашей стране. И она открыта для экспериментов: можно смотреть логиты, менять сэмплирование (урок 4.1.3), подключать свой LoRA-адаптер из урока 6.2.2. Весь код ступени 8 (RAG, агенты) тоже работает с локальной моделью.
В этом уроке вы соберёте всё изученное в модуле в практический навык:
- по формулам из уроков 7.1.1 и 7.2.1 выберете модель и квантование под своё железо;
- запустите Qwen3 через Ollama — за пять минут;
- запустите её же через llama.cpp — с полным контролем над памятью и GPU;
- подключитесь к OpenAI-совместимому APIOpenAI-совместимый APIEN OpenAI-compatible APIHTTP-интерфейс в формате OpenAI Chat Completions (/v1/chat/completions). Его отдают Ollama, llama-server, vLLM, SGLang, а также DeepSeek и Qwen в облаке — код клиента не меняется. и измерите скорость своим бенчмарком.
Карта инструментов
Заголовок раздела «Карта инструментов»| Инструмент | Что это | Для чего |
|---|---|---|
| Ollama | сервер с каталогом моделей, построенный на ggml/llama.cpp | быстрый старт, ступень 8 курса |
| llama.cpp | движок на C/C++: CPU, CUDA, Metal, Vulkan; формат GGUFGGUF (формат файлов llama.cpp)EN GGUFОдин файл с весами (обычно квантованными), токенизатором, шаблоном чата и метаданными. Типы квантования — Q8_0, Q6_K, Q4_K_M и др. Используют llama.cpp, Ollama, LM Studio. | полный контроль, замеры, выгрузка на CPU |
| LM Studio | графическое приложение (llama.cpp, на Mac ещё MLX) | если не хочется терминала |
| vLLM, SGLang | серверы для GPU (урок 7.1.2) | много пользователей, ваши 4 карты |
| LMDeploy | сервер Shanghai AI Lab | Qwen и InternLM в 4 битах на GPU |
| KTransformers | гибрид CPU и GPU для огромных MoE | DeepSeek-V3/R1 на одной GPU и большой RAM |
CUDA — интерфейс для видеокарт NVIDIA, Metal — для Mac на чипах Apple, Vulkan — универсальный интерфейс, через который работают и карты AMD и Intel; MLX — библиотека Apple для её собственных чипов.
Рекомендация: начните с Ollama, для экспериментов и замеров перейдите на llama.cpp, а для сервера на всю семью или команду — на vLLM или SGLang.
Сколько памяти нужно
Заголовок раздела «Сколько памяти нужно»Модели нужно уместить три вещи:
Пример: Qwen3-8B (8.2 млрд параметров; 36 слоёв, 8 KV-голов размера 128). Веса в Q4_K_M: ГБ. Кэш на токен: байт ≈ 144 КиБ. На 8 тысячах токенов контекста — 1.2 ГБ, на 32 тысячах — 4.8 ГБ. Итого 7–11 ГБ в зависимости от контекста.
Где всё это лежит, решает скорость. Из урока 7.1.1: decode читает все веса на каждый токен, так что скорость ≈ пропускная способность памяти / байт весов. Если модель не влезла в видеопамять, часть слоёв остаётся в оперативной памяти и считается на CPU — это выгрузка слоёвраспределение слоёв между GPU и CPU (offload)EN layer offloadingЧасть слоёв модели лежит в видеопамяти, остальные — в оперативной памяти. В llama.cpp регулируется флагом -ngl. Время токена складывается из чтения обеих частей, и медленная оперативная память быстро становится главным слагаемым.. Время токена тогда складывается из двух частей:
Видеопамять в 5–20 раз быстрее оперативной, поэтому даже небольшая доля слоёв на CPU становится главным слагаемым. Qwen3-32B в Q4_K_M (20 ГБ) на RTX 3060 с 12 ГБ: если бы влезла целиком, дала бы около 12 токенов/с, а с половиной слоёв в DDR4 — около 3. Маленькая модель целиком в видеопамяти обычно лучше большой, выгруженной наполовину. Исключение — MoE, о нём ниже.
Соберём это в планировщик: какая модель влезет и с какой скоростью пойдёт. Код на чистом Python:
# Какая модель поместится и с какой скоростью пойдёт: планировщик «на салфетке»BPW = {"Q8_0": 8.5, "Q6_K": 6.6, "Q5_K_M": 5.7, "Q4_K_M": 4.9, "Q3_K_M": 3.9} # ≈ бит на вес в GGUF
# имя: (всего параметров, млрд; активных, млрд; слоёв; KV-голов; размер головы)MODELS = { "Qwen3-0.6B": (0.6, 0.6, 28, 8, 128), "Qwen3-8B": (8.2, 8.2, 36, 8, 128), "Qwen3-14B": (14.8, 14.8, 40, 8, 128), "Qwen3-30B-A3B": (30.5, 3.3, 48, 4, 128), "Qwen3-32B": (32.8, 32.8, 64, 8, 128), "Llama-3.3-70B": (70.6, 70.6, 80, 8, 128),}
# видеопамять, ГБ; её пропускная способность, ГБ/с; свободная RAM, ГБ; пропускная способность RAM, ГБ/сMACHINES = { "ноутбук 16 ГБ": (0, 1, 12, 89.6), # DDR5, 2 канала; ~4 ГБ съедает система "RTX 3060 12 ГБ": (12, 360, 24, 51.2), # DDR4, 2 канала "4× RTX 4080 32 ГБ": (128, 717, 112, 204.8), # слои по картам: в один поток работает одна карта за раз}
def memory_gb(model, quant, context=8192, overhead_gb=1.0): """Веса + KV-кэш (FP16) на context токенов + буферы движка.""" total_b, _, layers, kv_heads, head_dim = MODELS[model] weights = total_b * BPW[quant] / 8 kv = 2 * layers * kv_heads * head_dim * context * 2 / 1e9 return weights + kv + overhead_gb
def tokens_per_sec(model, quant, machine, efficiency=0.7): """Скорость decode в один поток. None — модель не помещается в память совсем.""" vram, vram_bw, ram, ram_bw = MACHINES[machine] need = memory_gb(model, quant) if need > vram + ram: return None on_gpu = min(need, vram) / need # доля модели в видеопамяти total_b, active_b, *_ = MODELS[model] # *_ — «остальные поля кортежа нам здесь не нужны» read_gb = active_b * BPW[quant] / 8 # за токен читаем только активные веса (важно для MoE) seconds = read_gb * on_gpu / vram_bw + read_gb * (1 - on_gpu) / ram_bw return efficiency / seconds
print(f"{'модель':<14}{'квант':<8}{'ГБ':>6}" + "".join(f"{m:>20}" for m in MACHINES))for model, quant in [("Qwen3-0.6B", "Q8_0"), ("Qwen3-8B", "Q4_K_M"), ("Qwen3-14B", "Q4_K_M"), ("Qwen3-30B-A3B", "Q4_K_M"), ("Qwen3-32B", "Q4_K_M"), ("Llama-3.3-70B", "Q4_K_M")]: row = f"{model:<14}{quant:<8}{memory_gb(model, quant):>6.1f}" for machine in MACHINES: speed = tokens_per_sec(model, quant, machine) row += f"{'не влезет':>20}" if speed is None else f"{speed:>14.0f} ток/с" print(row)модель квант ГБ ноутбук 16 ГБ RTX 3060 12 ГБ 4× RTX 4080 32 ГБQwen3-0.6B Q8_0 2.6 98 ток/с 395 ток/с 787 ток/сQwen3-8B Q4_K_M 7.2 12 ток/с 50 ток/с 100 ток/сQwen3-14B Q4_K_M 11.4 7 ток/с 28 ток/с 55 ток/сQwen3-30B-A3B Q4_K_M 20.5 не влезет 36 ток/с 248 ток/сQwen3-32B Q4_K_M 23.2 не влезет 3 ток/с 25 ток/сLlama-3.3-70B Q4_K_M 46.9 не влезет не влезет 12 ток/сЭто оценки сверху с коэффициентом 0.7. Для крошечных моделей они особенно оптимистичны: там скорость упирается уже
не в память, а в накладные расходы на каждый шаг. Добавьте в MACHINES своё железо — пропускную способность памяти
найдёте в спецификации видеокарты или посчитаете для RAM: каналы × 8 байт × частота в МТ/с (мегатрансферах в секунду —
это число стоит в названии памяти: DDR5-5600 значит 5600 МТ/с). Проверка для ноутбука из таблицы:
МБ/с, то есть 89.6 ГБ/с.
MoE — лучший друг локального запуска. Посмотрите на Qwen3-30B-A3B: памяти ей нужно как 30B-модели, а скорость —
как у 3B, потому что на токен работают только 3.3 млрд активных параметровактивные параметрыEN activated (active) parametersСколько параметров MoE-модели реально участвует в обработке одного токена. «A22B» в названии Qwen3-235B-A22B — 22 млрд активных из 235 млрд.
(этот расчёт вы уже видели в уроке 7.1.1). Даже
на RTX 3060, где половина модели лежит в RAM, оценка — 36 токенов/с против 3 у плотной Qwen3-32B. Больше того, можно
выгружать в RAM именно экспертов, а внимание и общие слои держать на GPU — так и работают KTransformers и флаг -ot
в llama.cpp (ниже).
Ориентиры по железу (контекст 8–16 тысяч токенов, Q4_K_M, если не сказано иное):
| Железо | Комфортно | На пределе |
|---|---|---|
| Ноутбук 8–16 ГБ RAM без GPU | Qwen3-0.6B … 4B | Qwen3-8B, около 10 токенов/с |
| GPU 8 ГБ | Qwen3-4B; Qwen3-8B с контекстом до ~8K | Qwen3-30B-A3B с экспертами в RAM |
| GPU 12–16 ГБ | Qwen3-14B | Qwen3-30B-A3B с частью экспертов в RAM |
| GPU 24 ГБ | Qwen3-30B-A3B, Qwen3-14B в Q8_0 | Qwen3-32B с контекстом до 4–8K |
| Ваша машина, 4 × 32 ГБ | 70B в Q4–Q8; Qwen3-32B и 30B-A3B в BF16 через vLLM | Qwen3-235B-A22B в Q3 (≈115 ГБ) или Q4 (≈144 ГБ, часть в RAM); DeepSeek-V3/R1 — только версии около 2 бит и ниже, с частью весов в RAM |
На октябрь 2026 в тех же классах есть модели новее Qwen3 (апрель 2025): Qwen3.6-35B-A3B вместо Qwen3-30B-A3B
(ollama run qwen3.6:35b, около 24 ГБ в Q4_K_M), плотные Qwen3.6-27B и Qwen3.8-27B вместо Qwen3-32B (qwen3.6:27b,
около 19 ГБ), а также Gemma 4 от Google (gemma4). Команды урока те же, меняется имя модели; llama.cpp серию
Qwen3.5 и новее поддерживает (README QwenLM/Qwen3.8). Одна поправка к расчёту:
внимание у этих Qwen гибридное, и KV-кэш растёт только у каждого четвёртого слоя (у Qwen3.8-27B — у 16 из 64),
поэтому формула кэша выше для них даёт оценку с запасом.
Ollama: запуск за пять минут
Заголовок раздела «Ollama: запуск за пять минут»-
Установите. На Linux:
Окно терминала curl -fsSL https://ollama.com/install.sh | shДля Windows и macOS скачайте установщик с ollama.com. После установки в фоне работает сервер на порту 11434.
-
Запустите модель. Первый запуск скачает около 5 ГБ:
Окно терминала ollama run qwen3:8b --verboseОткроется чат в терминале. Флаг
--verboseпосле каждого ответа печатает статистику:prompt eval rate— скорость prefill,eval rate— скорость decode в токенах в секунду. Сравните её с оценкой планировщика. Qwen3 по умолчанию сначала «думает». Чтобы получить быстрый ответ без рассуждений, допишите в конец сообщения/no_think— это штатный переключатель моделей Qwen3. -
Проверьте, где модель. В другом терминале:
Окно терминала ollama ps # какие модели загружены и сколько процентов на GPU / CPUollama list # что скачаноСтрока
100% GPU— всё в видеопамяти. Что-то вроде45%/55% CPU/GPU— часть слоёв выгружена, ждите падения скорости. -
Настройте контекст. В чате:
/set parameter num_ctx 16384. Для сервера — параметрnum_ctxв запросе или в Modelfile (текстовом файле настроек модели в Ollama). Переменные окружения сервера:OLLAMA_NUM_PARALLEL(сколько запросов обслуживать параллельно),OLLAMA_FLASH_ATTENTION=1иOLLAMA_KV_CACHE_TYPE=q8_0(KV-кэш в 8 битах — вдвое меньше памяти; работает только вместе с Flash Attention).
llama.cpp: полный контроль
Заголовок раздела «llama.cpp: полный контроль»Ollama удобна, но скрывает настройки. llama.cpp даёт их все. Соберите его (нужны git, cmake и компилятор C++;
для GPU — CUDA Toolkit):
git clone https://github.com/ggml-org/llama.cppcd llama.cppcmake -B build -DGGML_CUDA=ON # без видеокарты NVIDIA: просто cmake -B buildcmake --build build --config Release -jЗапустите сервер, скачав GGUF прямо с Hugging Face (официальный репозиторий Qwen, квантование Q4_K_M):
./build/bin/llama-server -hf Qwen/Qwen3-8B-GGUF:Q4_K_M -ngl 99 -c 16384 --port 8080Откройте в браузере http://localhost:8080 — там встроенный веб-чат. Главные флаги:
| Флаг | Что делает |
|---|---|
-m файл.gguf / -hf репозиторий:квант | локальный файл или скачивание с Hugging Face |
-ngl N | сколько слоёв положить в видеопамять; 99 — все |
-c N | длина контекста (размер KV-кэша) |
-np N | число параллельных слотов (пользователей); контекст делится между ними |
-fa on | Flash Attention — меньше памяти и быстрее на длинном контексте (в свежих версиях по умолчанию auto) |
-ctk q8_0 -ctv q8_0 | KV-кэш в 8 битах (для V нужен -fa) |
-t N | число потоков CPU для слоёв, оставшихся в RAM |
-ts 1,1 / -sm row | как делить модель между несколькими GPU |
-ot "регулярка=CPU" | куда положить конкретные тензоры (по регулярному выражению для их имён), например экспертов MoE |
Пропускную способность своего железа меряйте штатным бенчмарком:
./build/bin/llama-bench -m qwen3-8b-q4_k_m.gguf -ngl 99 -p 512 -n 128В таблице будут строки pp512 — prefill промпта в 512 токенов — и tg128 — генерация 128 токенов (decode), обе
в токенах в секунду. pp будет в десятки раз больше tg: это две фазы из урока 7.1.1 в цифрах.
MoE с экспертами в RAM. Если Qwen3-30B-A3B не влезает в видеопамять, отправьте в оперативную память только
тензоры экспертов (их имена содержат _exps), а внимание и остальное оставьте на GPU. В регулярном выражении ниже
точка означает «любой символ», а .* — «любая последовательность символов», так что .ffn_.*_exps. совпадает с именами
вроде blk.5.ffn_up_exps.weight:
./build/bin/llama-server -hf Qwen/Qwen3-30B-A3B-GGUF:Q4_K_M -ngl 99 -ot ".ffn_.*_exps.=CPU" -c 16384В свежих версиях то же делает флаг --n-cpu-moe N (экспертов первых N слоёв — на CPU). Точный синтаксис проверяйте
в llama-server --help своей версии: проект развивается очень быстро.
OpenAI-совместимый сервер
Заголовок раздела «OpenAI-совместимый сервер»Ollama, llama-server, vLLM и SGLang говорят на одном языке — формате Chat Completions из урока 8.1.1. Меняется только адрес:
| Сервер | base_url по умолчанию |
|---|---|
| Ollama | http://localhost:11434/v1 |
| llama-server | http://localhost:8080/v1 |
| vLLM | http://localhost:8000/v1 |
| SGLang | http://localhost:30000/v1 |
Проверка из терминала (curl — утилита для HTTP-запросов; -H задаёт заголовок, -d — тело запроса в формате JSON):
curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" \ -d '{"model": "qwen3", "messages": [{"role": "user", "content": "Привет! Кто ты? /no_think"}]}'Из Python работает клиент openai из урока 8.1.1: OpenAI(base_url="http://localhost:11434/v1", api_key="local").
Ключ локальный сервер обычно не проверяет, но строка нужна библиотеке.
Бенчмарк на своём компьютере
Заголовок раздела «Бенчмарк на своём компьютере»llama-bench меряет движок «в вакууме». Пользователь же видит скорость через API: с HTTP, токенизацией и чат-шаблоном.
Напишем свой бенчмарк — только на стандартной библиотеке Python, чтобы он работал с любым сервером из таблицы выше.
Он отправляет запрос со стримингом и засекает:
- TTFT — от отправки до первого токена;
- prefill — входных токенов, делённых на TTFT (имеет смысл только для длинных промптов);
- decode — , где — число выходных токенов.
# bench_llm.py — замер TTFT и скорости генерации у любого OpenAI-совместимого сервера (только стандартная библиотека)import jsonimport sysimport timeimport urllib.request
BASE_URL = "http://localhost:11434/v1" # Ollama; llama-server — :8080/v1, vLLM — :8000/v1, SGLang — :30000/v1MODEL = sys.argv[1] if len(sys.argv) > 1 else "qwen3:8b"
def bench(prompt, max_tokens=256): body = { "model": MODEL, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens, "temperature": 0, "stream": True, "stream_options": {"include_usage": True}, # попросим счётчики токенов в последнем чанке } req = urllib.request.Request( BASE_URL + "/chat/completions", data=json.dumps(body).encode("utf-8"), headers={"Content-Type": "application/json", "Authorization": "Bearer local"}, ) t0 = time.perf_counter() t_first = t_last = None chunks, usage = 0, None with urllib.request.urlopen(req, timeout=600) as resp: for raw in resp: # ответ приходит строками Server-Sent Events line = raw.decode("utf-8").strip() if not line.startswith("data:"): continue data = line[len("data:"):].strip() if data == "[DONE]": break msg = json.loads(data) usage = msg.get("usage") or usage delta = msg["choices"][0].get("delta", {}) if msg.get("choices") else {} # у «думающих» моделей рассуждение может идти в отдельном поле — считаем и его if any(delta.get(k) for k in ("content", "reasoning_content", "reasoning")): t_last = time.perf_counter() t_first = t_first or t_last # время запоминаем только у первого чанка с текстом chunks += 1 n_out = usage["completion_tokens"] if usage else chunks n_in = usage["prompt_tokens"] if usage else None ttft = t_first - t0 decode = (n_out - 1) / (t_last - t_first) if n_out > 1 else float("nan") return n_in, n_out, ttft, decode
if __name__ == "__main__": long_text = "Карп прыгает через врата дракона и становится драконом. " * 300 tests = [("короткий промпт", "Назови три столицы Азии."), ("длинный промпт", "Перескажи в двух фразах: " + long_text)] for run in range(2): for name, prompt in tests: # номер прогона в начале промпта — чтобы сервер не взял готовый KV-кэш (урок 7.1.2) n_in, n_out, ttft, decode = bench(f"[{run}] {prompt}") prefill = f"{n_in / ttft:7.0f}" if n_in else " ?" # n_in!s — превратить в строку: если сервер не прислал usage, n_in = None print(f"{name:<16} вход {n_in!s:>5} ток. | TTFT {ttft:6.2f} с | prefill ≈{prefill} ток/с | " f"выход {n_out:>4} ток. | decode {decode:6.1f} ток/с")Запуск: python bench_llm.py qwen3:8b (для llama-server поменяйте BASE_URL и имя модели). Формат вывода
(здесь числа от тестового сервера-заглушки, а не от настоящей модели — у вас будут свои):
короткий промпт вход 7 ток. | TTFT 0.12 с | prefill ≈ 61 ток/с | выход 40 ток. | decode 48.8 ток/сдлинный промпт вход 4207 ток. | TTFT 0.31 с | prefill ≈ 13490 ток/с | выход 40 ток. | decode 48.6 ток/сЧего ждать на RTX 4080 с Qwen3-8B в Q4_K_M — по формулам урока 7.1.1, а не по замеру: потолок decode
токена/с, реально порядка 100–120; prefill длинного промпта — тысячи токенов в секунду; число входных токенов будет
больше, чем слов в промпте, потому что сервер добавляет chat-шаблон. Если ваш замер сильно ниже оценки, проверьте
ollama ps: возможно, часть слоёв уехала на CPU.
Как читать результаты:
- decode почти не зависит от длины промпта — это скорость «печати». Сравните её с потолком «пропускная способность / размер весов» и посчитайте эффективность: на GPU хорошие движки дают 60–85%.
- TTFT растёт с длиной промпта, а prefill на длинном промпте в десятки раз быстрее decode — та же асимметрия
фаз, что в
llama-bench. - Уберите номер прогона из промпта и запустите дважды: на втором запуске TTFT длинного промпта резко упадёт — сервер взял его KV-кэш готовым (префикс-кэш, урок 7.1.2).
Заведите в журнале таблицу: железо, модель, квантование, контекст, TTFT, decode, потолок, эффективность. Через неделю вы будете с первого взгляда понимать, что даст новая модель на вашем компьютере.
Проверь себя
Заголовок раздела «Проверь себя»- Память модели ≈ веса (параметры × бит на вес / 8) + KV-кэш на нужный контекст + буферы. Считайте до скачивания.
- Скорость decode ≈ пропускная способность памяти / байт весов. Выгрузка слоёв в RAM складывает времена, поэтому лучше меньшая модель целиком в видеопамяти. Исключение — MoE: экспертов можно держать в RAM.
- Ollama — старт за пять минут (
ollama run,ollama ps, следите заnum_ctx); llama.cpp — полный контроль (-ngl,-c,-ot,llama-bench). - Все локальные серверы отдают OpenAI-совместимый API — код из ступени 8 работает без изменений.
- Свой бенчмарк меряет TTFT, prefill и decode; сравнивайте с потолком и ведите журнал.
Дальше: модуль 7.3 — как честно измерить, насколько хорошо модель отвечает, а не только как быстро: бенчмарки, их ловушки и собственные эвалы.