Skip to content

Release: Ollama as its own pinned service, GPU support, and bounded chat requests - #5

Merged
krlex merged 15 commits into
mainfrom
develop
Aug 20, 2026
Merged

Release: Ollama as its own pinned service, GPU support, and bounded chat requests#5
krlex merged 15 commits into
mainfrom
develop

Conversation

@krlex

@krlex krlex commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Brings develop to main: Ollama runs as its own pinned service instead of a binary lifted from another image, an optional GPU overlay, chat requests bounded in size and duration, and the documentation set rewritten for the two-service layout.

Release merge, no new code.

krlex and others added 15 commits July 27, 2026 12:15
forail-deploy no longer carries the guide's markdown in the repo -- the sources
live on the host and the site is what is published. Link the site page instead
of a file path that now 404s.
…ts image

The image copied /bin/ollama out of ollama/ollama:latest and nothing else.
That silently stopped working: Ollama keeps its inference engine in
/usr/lib/ollama (llama-server, libggml, the CUDA backends), so the copied
binary could start a server and answer /api/tags -- enough for the health
check to look fine -- while every generation failed with

    error starting llama-server: llama-server binary not found

Reproduced against the published 2026.06.0 image, which carries Ollama 0.30.8
without that directory. It has never been able to load a model.

Rather than chase upstream's internal file layout, Ollama now runs as its own
service:

- Pinned to ollama/ollama:0.30.10. Tracking `latest` is precisely what let an
  upstream change break inference without a line of our code changing.
- Only the model server benefits from a GPU, so only it has to carry that
  requirement -- in Kubernetes just that pod needs a GPU node.
- No published port. Ollama has no authentication, so it stays reachable only
  from the API, and the integration overlay keeps it off the Forail network.

The API now waits for Ollama on startup and pulls models over its HTTP API,
since the ollama CLI is no longer in the image. The wait is bounded at 300s
and exits with an actionable message instead of hanging forever.
Kept as an overlay instead of folded into docker-compose.yml because a devices
reservation is a hard requirement: on a host without a GPU the stack refuses to
start rather than quietly running on CPU. Opting in is explicit:

    docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d

Ollama falls back to CPU silently when it cannot see a device, so the file
documents how to confirm it actually took: `logs ollama | grep "inference
compute"` must report a CUDA library and non-zero VRAM, not library=cpu.

Measured on a Ryzen 9 5900X / RTX 3080 12GB with gemma3:1b, warm, same
questions against the same index: ~680 B/s on 24 CPU threads against
~3900 B/s on the GPU. Time to first token barely moves (~0.5s either way) --
that is RAG retrieval, not generation.
test_default_settings built a Settings() and checked its values, but
pydantic-settings reads FORAIL_ASSISTANT_* out of the environment first. The
shipped image sets those (it points the API at the ollama service rather than
localhost), so running the suite inside that image failed on a correct default.

Clear the prefix from the environment before constructing Settings, so the test
means what its name says wherever it runs.
The README still promised a single all-in-one container with Ollama bundled
inside, which is no longer true and was the arrangement that could not run a
model in the first place.

- New architecture diagram: API (FastAPI + embedded ChromaDB) alongside Ollama,
  with why they are apart -- GPU requirement on the model server alone, and no
  published port because Ollama has no authentication.
- Hardware section replaced with numbers actually measured here rather than
  estimates, plus the command to confirm Ollama took the GPU plus the toolkit
  prerequisite. It falls back to CPU silently, so "check, do not assume".
- FORAIL_ASSISTANT_RAG_TOP_K documented as 3, which is what the code has always
  defaulted to; the table said 5.
Records the failure mode explicitly -- health checks passing while every
generation returned 500 -- because that combination is what kept it hidden, and
because the published 2026.06.0 image is affected.
The diagram and the component notes still said FastAPI, Ollama and ChromaDB
share a container and that the entrypoint starts Ollama itself. None of that
has been true since the split. Also records why the copied-binary layout was
abandoned, so the pinned upstream image does not look like an arbitrary
preference.
The base-URL row explained localhost as "runs inside the same container",
which is exactly what stopped being true, and the model-change recipe told the
reader to exec the ollama CLI in the API container — it is not in that image
any more, so the command fails. Point both at the model server.
Four things in here were not just stale but actively wrong after the split: the
service list, the GPU section (it told the reader to uncomment a block that no
longer exists, on the wrong service), teardown that stops the API and leaves
the model server up, and a backup procedure that claimed one volume holds both
the index and the models.
The document opened by saying all persistent state lives in one place; since
the split there are two claims, and following it would have backed up the index
while silently skipping several GB of model blobs — the one thing an air-gapped
host cannot re-fetch. Adds the unreachable-model-server case, which is now a
distinct failure with its own message.
This file is part of docs_to_index/, so the assistant answers from it. Left
alone it would have kept telling users the model server ships inside the
application image.
`chat_max_concurrency` caps how many generations run at once and says nothing
about how large or how long any one of them is. Four callers could hold every
slot for the full Ollama timeout with a prompt the size of a book, and the
service would look healthy the whole time.

- The message is capped at 4000 characters (413 over it) and a blank one is
  rejected outright rather than sent to the model.
- History is trimmed to the most recent 20 turns and 16000 characters rather
  than rejected: dropping the oldest turns costs a little context, while a 413
  in the middle of a conversation ends it. History matters more than the message
  here -- every turn is re-sent to the model and paid for again on the next
  request.
- The page context is truncated to 200 characters. It is a route, not a payload.
- A generation gets a 180s deadline independent of the model's own timeout. One
  that will not stop still ends, because the slot it holds is one of only four.

Defaults are settings, so an operator who wants a longer conversation can have
one. Documented alongside the chat token, whose empty default leaves the endpoint
open -- the chart now always sets it.
Run Ollama as its own pinned service, not a binary lifted from its image
Bound the size and duration of a chat request
@krlex
krlex merged commit 5a31d6c into main Aug 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant