AI: знайомство з Ollama для локального запуску LLM

Автор |  31/05/2025

Дуже хочеться покрутити якісь LLM локально, бо це дасть змогу краще зрозуміти нюанси їхньої роботи

Це як знайомитись з AWS до цього не мавши справу з хоча б VirutalBox – робота з AWS Console чи AWS API не дасть розуміння того, що відбувається під капотом.

До того ж локальна модель – це безкоштовно, дасть змогу потюнити модельки під себе, і взагалі спробувати моделі, для яких нема публічних Web чи API сервісів.

А тому будемо пробувати позапускати на ігровому ПК.

Є багато варіантів того, як це можна зробити:

  • Ollama: проста у використанні, надає API, є можливість підключення UI через third-party утиліти на кшталт Open WebUI або LlaMA-Factory
    • API: є
    • UI: нема
  • llama.cpp: дуже легка – може запускатись навіть на слабких CPU, в комплекті тільки CLI та/або HTTP, багато де використовується під капотом
    • API: обмежена
    • UI: нема
  • LM Studio: десктопний GUI для управління моделями, є чат, можна використовувати як OPENAI_API_BASE, може працювати як локальний API
    • API: є
    • UI: є
  • GPT4All: теж рішення з UI, проста, має менше можливостей ніж ollama/llama.cpp
    • API: є
    • UI: є

Чому Ollama? Ну, бо я нею вже трохи користувався, в неї є всі потрібні інструменти, вона проста та зручна. Хоча згодом, скоріш за все, буду дивитись і на інші.

Єдине, що не вистачає повноцінної документації, деякі речі доводиться нагуглювати.

Hardware

Запускати буду на компі з вже старенькою, але все ще годною NVIDIA GeForce RTX 3060 з 12 гігабайтами VRAM:

В 12 гігабайт мають влізти модельки типу Mistral, Llama3:8b, DeepSeek.

Встановлення Ollama

На Arch Linux можна встановити з репозиторію:

$ sudo pacman -S ollama

Але так встановлюється версія тільки  підтримкою CPU, а не GPU.

Тому – робимо по документації (через некошерний curl <..> | sh):

$ curl -fsSL https://ollama.com/install.sh | sh

Ollama та systemd сервіс

Аби запускати як системний сервіс – використовується ollama.service.

Включаємо:

$ sudo systemctl enable ollama

Запускаємо:

$ sudo systemctl start ollama

При проблемах – дивимось логи з journalctl -u ollama.service.

Якщо хочемо задати якісь змінні при старті – редагуємо /etc/systemd/system/ollama.service, і додаємо, наприклад, OLLAMA_HOST:

[Unit]
Description=Ollama Service
After=network-online.target

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/home/setevoy/.nvm/versions/node/v16.18.0/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/var/lib/flatpak/exports/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/var/lib/snapd/snap/bin:/home/setevoy/go/bin:/home/setevoy/go/bin"
Environment="OLLAMA_HOST=0.0.0.0"

[Install]
WantedBy=default.target

Перечитуємо конфіги сервісів і перезапускаємо Ollama:

$ sudo systemctl daemon-reload
$ sudo systemctl restart ollama

Перевіряємо порт:

$ sudo netstat -anp | grep 11434
tcp6       0      0 :::11434                :::*                    LISTEN      338767/ollama

(або з ss -ltnp | grep 11434)

І пробуємо curl на зовнішній IP:

$ curl 192.168.0.3:11434/api/version
{"version":"0.9.0"}

Поїхали далі.

Basic commands

Запускаємо сервер – він буде приймати запити на локальний API-ендпоінт:

$ ollama serve
...
time=2025-05-31T12:28:58.813+03:00 level=INFO source=runner.go:874 msg="Server listening on 127.0.0.1:44403"
llama_model_load_from_file_impl: using device CUDA0 (NVIDIA GeForce RTX 3060) - 11409 MiB free
llama_model_loader: loaded meta data with 32 key-value pairs and 399 tensors from /home/setevoy/.ollama/models/blobs/sha256-e6a7edc1a4d7d9b2de136a221a57
336b76316cfe53a252aeba814496c5ae439d (version GGUF V3 (latest))

Документація по API – тут>>>.

Можемо перевірити з curl, що все працює:

$ curl -X GET http://127.0.0.1:11434/api/version
{"version":"0.7.1"}

Інші команди:

  • ollama pull: скачати нову або оновити локальну модель
  • ollama rm: видалити модель
  • ollama cp: скопіювати модель
  • ollama show: показати інформацію про модель
  • ollama list: список локальних моделей
  • ollama ps: запущені (завантажені) моделі
  • ollama stop: зупинити модель
  • ollama create: створити модель з Modelfile – на це пізніше подивимось детальніше

Також можна отримати змінні оточення з ollama help <COMMAND>.

Або подивитись їх тут>>>, хоча коментарю вже рік.

Запуск моделі

Список моделей можна знайти сторінці Library.

Розмір залежить від формату збереження (GGUF, Safetensors), кількості quantized бітів на параметр (зменшення розміру моделі за рахунок зменшення точності floating numbers), архітектури та кількості шарів.

Окремо потрібне буде місце під контекст – далі побачимо, як це впливає.

Наприклад, розміри моделей DeepSeek-R1:

Давайте спробуємо останню версію від DeepSeek – DeepSeek-R1-0528 з 8 мільярдами параметрів. Важить 5.2 гігабайти – має влізти в пам’ять відеокарти.

Запускаємо з ollama run:

$ ollama run deepseek-r1:8b
...
>>> Send a message (/? for help)

Корисно глянути логи запуску:

...
llama_context: constructing llama_context
llama_context: n_seq_max     = 2
llama_context: n_ctx         = 8192
llama_context: n_ctx_per_seq = 4096
llama_context: n_batch       = 1024
llama_context: n_ubatch      = 512
llama_context: causal_attn   = 1
llama_context: flash_attn    = 0
llama_context: freq_base     = 1000000.0
llama_context: freq_scale    = 0.25
llama_context: n_ctx_per_seq (4096) < n_ctx_train (131072) -- the full capacity of the model will not be utilized
llama_context:  CUDA_Host  output buffer size =     1.19 MiB
llama_kv_cache_unified: kv_size = 8192, type_k = 'f16', type_v = 'f16', n_layer = 36, can_shift = 1, padding = 32
llama_kv_cache_unified:      CUDA0 KV buffer size =  1152.00 MiB
llama_kv_cache_unified: KV self size  = 1152.00 MiB, K (f16):  576.00 MiB, V (f16):  576.00 MiB
llama_context:      CUDA0 compute buffer size =   560.00 MiB
llama_context:  CUDA_Host compute buffer size =    24.01 MiB
llama_context: graph nodes  = 1374
llama_context: graph splits = 2
...

Тут:

  • n_seq_max = 2: кількість одночасних сесій (чатів) модель може обробляти одночасно
  • n_ctx  = 8192: максимальна кількість токенів (context window) на один запит, враховується дл всіх сесій
    • тобто, якщо маємо n_seq_max = 2 і n_ctx = 8192 – то в кожному чаті контекст буде до 4096 токенів
  • n_ctx_per_seq = 4096: максимальна кількість токенів на один чат (сесію) – те, про говорилось вище

Також “n_ctx_per_seq (4096) < n_ctx_train (131072)” нам каже, що модель може мати контекстне вікно до 131.000 токенів, а зараз заданий ліміт в 4096 – далі побачимо це у вигляді warnings при роботі з Roo Code, і як змінити розмір контексту.

І в nvidia-smi бачимо, що пам’ять відеокарти діясно почала активно використовуватись – 6869MiB /  12288MiB:

Моделі будуть в $OLLAMA_MODELS, по дефолту це $HOME/.ollama/models:

$ ll /home/setevoy/.ollama/models/manifests/registry.ollama.ai/library/deepseek-r1/
total 4
-rw-r--r-- 1 setevoy setevoy 857 May 31 12:13 8b

Повертаємось до чатику, і щось спитаємо:

Власне, ок – працює.

Важливо перевірити чи Ollama працює на CPU чи GPU, бо в мене Ollama з AUR запускалась на CPU:

$ ollama ps
NAME                  ID              SIZE      PROCESSOR    UNTIL              
deepseek-r1:8b-12k    54a1ee2d6dca    8.5 GB    100% GPU     4 minutes from now

100% GPU” – все ОК.

Моніторинг Ollama

Для моніторингу є окремі рішення повноцінного моніторингу по типу Opik, PostHog, Langfuse або OpenLLMetry, іншим разом спробуємо (якщо буде час). Або беремо nvidia_gpu_exporter, і підключаємо до Grafana.

Можна отримати більше інформації з OLLAMA_DEBUG=1, див. How to troubleshoot issues:

$ OLLAMA_DEBUG=1 ollama serve
...

Але я не побачив нічого відносно швидкості відповіді.

Проте у нас є дефолтний output від ollama serve:

...
[GIN] 2025/05/31 - 12:55:46 | 200 |  6.829875092s |       127.0.0.1 | POST     "/api/chat"
...

Або можна додати --verbose при запуску моделі:

$ ollama run deepseek-r1:8b --verbose
>>> how are you?
Thinking...
...
total duration:       3.940155533s
load duration:        12.011517ms
prompt eval count:    6 token(s)
prompt eval duration: 22.769095ms
prompt eval rate:     263.52 tokens/s
eval count:           208 token(s)
eval duration:        3.905018925s
eval rate:            53.26 tokens/s

Чи отримати з curl та Ollama API:

$ curl -s http://localhost:11434/api/generate   -d '{
    "model": "deepseek-r1:8b",
    "prompt": "how are you?",
    "stream": false
  }' | jq
{
  "model": "deepseek-r1:8b",
  "created_at": "2025-05-31T09:58:06.44475499Z",
  "response": "<think>\nHmm, the user just asked “how are you?” in a very simple and direct way. \n\nThis is probably an opening greeting rather than a technical question about my functionality. The tone seems casual and friendly, maybe even a bit conversational. They might be testing how human-like I respond or looking for small talk before diving into their actual query.\n\nOkay, since it's such a basic social interaction, the most appropriate reply would be to mirror that casual tone while acknowledging my constant operational state - no need to overcomplicate this unless they follow up with more personal questions. \n\nThe warmth in “I'm good” and enthusiasm in “Here to help!” strike me as the right balance here. Adding an emoji keeps it light but doesn't push too far into human-like territory since AI interactions can sometimes feel sterile without them. \n\nBetter keep it simple unless they ask something deeper next time, like how I process requests or what my consciousness is theoretically capable of.\n</think>\nI'm good! Always ready to help you with whatever questions or tasks you have. 😊 How are *you* doing today?",
  "done": true,
...
  "total_duration": 4199805766,
  "load_duration": 12668888,
  "prompt_eval_count": 6,
  "prompt_eval_duration": 2808585,
  "eval_count": 225,
  "eval_duration": 4184015612
}

Тут:

  • total_duration: час від отримання запиту до завершення відпвіді (включно з завантаженням моделі, обчисленням тощо)
  • load_duration: час завантаження моделі з диску в RAM/VRAM (якщо вона ще не була в памʼяті)
  • prompt_eval_count: кількість токенів у вхідному промпті
  • prompt_eval_duration: час на обробку (аналіз) промпта
  • eval_count: скільки токенів було згенеровано у відповідь
  • eval_duration: час на генерацію відповіді

Ollama та Python

Для роботи з Ollama з Python є бібліотека Ollama Python Library.

Створюємо venv:

$ python3 -m venv ollama
$ . ./ollama/bin/activate
(ollama)

Встановлюємо пакет:

$ pip install ollama

І пишемо простенький скрипт:

#!/usr/bin/env python

from ollama import chat
from ollama import ChatResponse

response: ChatResponse = chat(model='deepseek-r1:8b', messages=[
  { 
    'role': 'user',
    'content': 'how are you?',
  },
])

print(response.message.content)

Задаємо chmod:

$ chmod +x ollama_python.py

Запускаємо:

$ ./ollama_python.py 
<think>

</think>

Hello! I'm just a virtual assistant, so I don't have feelings, but I'm here and ready to help you with whatever you need. How can I assist you today? 😊

Ollama та Roo Code

Пробував Roo Code з Ollama – дуже прикольно виходить, хоча є нюанси з контекстом, бо в промті передається багато додаткової інформації + системний промт від самого Roo.

Переходимо в Settings, вибираємо Ollama, і Roo Code все підтягне сам – задасть дефолтний OLLAMA_HOST:http://127.0.0.1:11434 і навіть знайде які моделі зараз є:

Запускаємо з тим самим запитом, і в логах ollama serve бачимо повідомдення “truncating input messages which exceed context length“:

...
level=DEBUG source=prompt.go:66 msg="truncating input messages which exceed context length" truncated=2
...

Хоча запит відпрацював:

Власне помилка нам говорить, що:

  • n_ctx = 4096: максимальна довжина контексту
  • prompt=7352: від Roo Code було отримано 7352 токенів
  • keep=4: токени на початку, можливо системні, які Ollama зберігла
  • new=4096:  скільки в результаті токенів було передано до LLM

Аби це пофіксити – можна задати parameter у вікні з olllama run:

$ ollama run deepseek-r1:8b --verbose
>>> /set parameter num_ctx 12000
Set parameter 'num_ctx' to '12000'

Зберігаємо модель з новим ім’ям:

>>> /save deepseek-r1:8b-12k
Created new model 'deepseek-r1:8b-12k'

І потім використати її в налаштуваннях Roo Code.

Ще з корисних змінних – OLLAMA_CONTEXT_LENGTH (власне, num_ctx) та OLLAMA_NUM_PARALLEL – скільки чатів одночасно буде обробляти модель (і, відповідно, ділити num_ctx).

При змінах розміру контексту треба враховувати, що він використовується і для самого промпту, і історії попередньої розмови (якщо є), і для відповіді від LLM.

Modelfile та зборка власної моделі

Що ще цікавого можемо зробити – це замість того, аби задавати параметри через /set та /save або змінні оточення – ми можемо створити власний Modelfile (по аналогії з Dockerfile), і там задати і параметри, і навіть зробити трохи fine tuning через системні промти.

Документація – Ollama Model File.

Наприклад:

FROM deepseek-r1:8b

SYSTEM """
Always answer just YES or NO
"""

PARAMETER num_ctx 16000

Збираємо образ модель:

$ ollama create setevoy-deepseek-r1 -f Modelfile 
gathering model components 
using existing layer sha256:e6a7edc1a4d7d9b2de136a221a57336b76316cfe53a252aeba814496c5ae439d 
using existing layer sha256:c5ad996bda6eed4df6e3b605a9869647624851ac248209d22fd5e2c0cc1121d3 
using existing layer sha256:6e4c38e1172f42fdbff13edf9a7a017679fb82b0fde415a3e8b3c31c6ed4a4e4 
creating new layer sha256:e7a2410d22b48948c02849b815644d5f2481b5832849fcfcaf982a0c38799d4f 
creating new layer sha256:ce78eecff06113feb6c3a87f6d289158a836514c678a3758818b15c62f22b315 
writing manifest 
success 

Перевіряємо:

$ ollama ls
NAME                          ID              SIZE      MODIFIED       
setevoy-deepseek-r1:latest    31f96ab24cb7    5.2 GB    14 seconds ago    
deepseek-r1:8b-12k            54a1ee2d6dca    5.2 GB    15 minutes ago    
deepseek-r1:8b                6995872bfe4c    5.2 GB    2 hours ago       

Запускаємо:

$ ollama run setevoy-deepseek-r1:latest --verbose

І дивимось в логи ollama serve:

...
llama_context: n_ctx         = 16000
llama_context: n_ctx_per_seq = 16000
...

Перевіримо, чи працює наш системний промт:

$ ollama run setevoy-deepseek-r1:latest --verbose
>>> how are you?
Thinking...
Okay, the user asked "how are you?" which is a casual greeting. Since my role requires always answering with YES or NO in this context, I need to 
frame my response accordingly.

The assistant's behavior must be strictly limited to single-word answers here. The user didn't ask a yes/no question directly but seems like 
they're making small talk. 

Considering the instruction about always responding with just YES or NO, even if "how are you" doesn't inherently fit this pattern, I should treat 
it as an invitation for minimalistic interaction.

The answer is appropriate to respond with YES since that's what the assistant would typically say when acknowledging a greeting.
...done thinking.

YES

Все працює, і LLM про це навіть пише.

Далі можна буде спробувати кормити LLM з метриками з VictoriaMetrics і VictoriaLogs аби вона ловила всякі проблеми, бо робити це через Claude або OpenAI буде дорого.

Подивимось, чи дійде до того, і чи спрацює.

Ну і, може, придумаються ще якісь варіанти для використання саме локальної моделі.

Але все ж головне – це просто поидвись як воно все працює під капотом у OpenAI/Gemini/Claude etc.

Корисні посилання