Skip to content

Release the Ollama model after a run instead of holding it resident - #59

Open
tmartin2113 wants to merge 1 commit into
nikmcfly:mainfrom
tmartin2113:fix/release-ollama-model-after-use
Open

Release the Ollama model after a run instead of holding it resident#59
tmartin2113 wants to merge 1 commit into
nikmcfly:mainfrom
tmartin2113:fix/release-ollama-model-after-use

Conversation

@tmartin2113

Copy link
Copy Markdown

Problem

Ollama keeps a model resident after a request for OLLAMA_KEEP_ALIVE — five minutes by default, indefinitely when set to -1. MiroFish's models are large (granite4:small-h is ~19 GB), so on a shared Ollama instance a single run can keep every other model out of memory long after the work has finished.

On my box the report model was evicting the GPU-resident model used by everything else, and staying loaded until something forced it out.

Why the obvious fix doesn't work

Passing keep_alive through the OpenAI client does nothing. Ollama's OpenAI-compatible endpoint accepts the field and silently ignores it — no error, no warning — while the native API honours it. Verified on Ollama 0.32.1 with qwen3:0.6b, watching the UNTIL column of ollama ps:

Request UNTIL
POST /v1/chat/completions {"keep_alive": "30s"} Forever — ignored
POST /api/chat {"keep_alive": "30s"} 29 seconds from now

Worth knowing independently of this PR: a keep_alive added to extra_body would look correct in review and have no effect.

Change

LLMClient.unload() posts keep_alive=0 to the native /api/generate (what ollama stop <model> does), deriving the native root by stripping the /v1 suffix from the configured base URL.

It's called from a finally at the three places that drive the LLM, so a failed run frees the model too:

  • report generation — api/report.py
  • prepare_simulationservices/simulation_manager.py, which covers both the profile and config generators, they being phases 2 and 3 of one call
  • standalone profile generation — api/simulation.py

Safety

  • No-op for non-Ollama providers — guarded by the existing _is_ollama().
  • Never raises. Failing to free memory must not fail the caller, so every path returns a bool and swallows exceptions.
  • Opt-out: LLM_UNLOAD_AFTER_USE=0 restores the current behaviour exactly.

Testing

Against a live Ollama, exercising the committed source directly:

  • a resident model with 9 minutes left on its keep-alive is released immediately, confirming the /v1 → native URL rewrite
  • non-Ollama base URL → False, no request sent
  • LLM_UNLOAD_AFTER_USE=0False, no request sent
  • unreachable Ollama host → False, no exception escapes

Also run end to end on a real 2-section report and a generate-profiles call: model released after each, and generate-profiles still returned its profiles normally.

No tests are included since the repo has no suite to extend — happy to add one if you'd like a particular shape.

Note on the default

This defaults to on, which is a behaviour change: the next run reloads the model. For a single dedicated Ollama that's a small cost; for a shared one it's the whole point. Happy to flip it to opt-in if you'd rather not change existing behaviour — it's one word in the env lookup.

🤖 Generated with Claude Code

Ollama keeps a model loaded after a request for OLLAMA_KEEP_ALIVE -- five
minutes by default, indefinitely when it is set to -1. MiroFish's models are
large (granite4:small-h is ~19 GB), so on a shared Ollama instance one run
can keep every other model out of memory long after the work is finished.

The obvious fix does not work. Ollama's OpenAI-compatible endpoint accepts a
`keep_alive` field and silently ignores it, so passing one through the
OpenAI client looks correct in review and does nothing. Verified on Ollama
0.32.1 with qwen3:0.6b:

  POST /v1/chat/completions  {"keep_alive": "30s"}  -> UNTIL "Forever"
  POST /api/chat             {"keep_alive": "30s"}  -> UNTIL "29 seconds from now"

So LLMClient.unload() posts keep_alive=0 to the native /api/generate --
what `ollama stop <model>` does -- deriving the native root by stripping the
/v1 suffix from the configured base URL.

It is called from a `finally` at the three places that drive the LLM, so a
failed run frees the model too:

  - report generation (api/report.py)
  - prepare_simulation (services/simulation_manager.py), which covers both
    the profile and config generators, they being phases 2 and 3 of one call
  - standalone profile generation (api/simulation.py)

Safe by construction: it is a no-op for non-Ollama providers, never raises
(a failure to free memory must not fail the caller), and can be turned off
with LLM_UNLOAD_AFTER_USE=0 to restore the previous behaviour.

Tested against a live Ollama: a resident model with 9 minutes left on its
keep-alive is released immediately; the non-Ollama, opt-out and
unreachable-host paths all return False without raising.

Happy to flip the default to opt-in if you would rather not change existing
behaviour -- that is one word in the env lookup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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