Do not raise memory limits or keep shrinking context first. If an Ollama 0.34.0 DeepSeek-R1 workload leaves memory not released after a task, use keep_alive=0 or stop the model at a safe job boundary as temporary containment, then verify the Runner process, macOS memory pressure, and the next cold load. That can expose a recovery boundary, but it does not prove or repair a memory leak.
This week’s action: record one real request batch, unload the model, verify the old process state, and run the same batch again before changing system parameters. If an Agent service remains unstable after controlled recovery, move toward process isolation or an independent Mac compute node rather than repeatedly forcing memory behavior.
This guide is for batch developers who need model cleanup in the job lifecycle, Agent engineers whose service must stay available while models are recycled, and technical leads comparing cold-load cost with workload stability and node replacement.
Last updated September 15, 2026. Version and behavior checks are based on the Ollama API and troubleshooting documentation, Apple’s memory and Metal documentation, and the referenced open community issue.
Start with the object that is actually holding memory
A completed request does not necessarily mean that every allocation disappears immediately. Ollama may keep the model resident during its configured keep-alive period. The visible footprint can also contain model weights, KV cache allocations, mapped model files, the Runner process heap, and memory shared through Apple Silicon unified memory.
These objects have different meanings:
- Model residency: the model remains available for another request.
- KV cache: conversation or context state may remain associated with the loaded model.
- Mapped files: a file mapping can remain visible without being equivalent to newly committed private heap memory.
- Runner heap: allocations made during inference may continue growing if a component does not return memory as expected.
- Unified memory pressure: CPU and GPU work compete for the same physical pool on Apple Silicon. A large process view does not, by itself, identify a leak.
Apple defines unified memory at the Metal device level, while macOS Activity Monitor treats memory pressure as a system-level signal that includes compression and swap behavior. Those are related observations, not interchangeable measurements. See Apple’s explanation of Metal unified memory and its Activity Monitor memory pressure guidance.
Why does Ollama still use substantial Mac memory after a request ends?
The first explanation to test is intentional residency, not a confirmed leak. Check whether the model is still listed as running, inspect the service log, and compare the Runner process with the memory-pressure graph. If the model remains available and the system is not under pressure, immediate reclamation may not be the intended behavior.
The more serious pattern is different: the model disappears from the running-model list, but the same Runner process remains large, private memory continues to climb across request batches, or system pressure and swap activity worsen after each cycle. That pattern needs a reproducible test. It cannot be diagnosed from one Activity Monitor snapshot.
Use request shape to locate the growth source
Shortening context is useful only when memory growth follows context length. It does not address every source of accumulation.
We recommend changing one variable at a time across a small, repeatable workload:
- Keep the prompt and context length stable while changing request count.
- Keep request count stable while changing context length.
- Keep both stable while changing concurrency.
- Record whether the model remains listed after completion.
- Record the Runner process identifier and memory behavior before the next request.
The interpretation is conditional:
- Growth that follows context length points toward KV cache or working-set demand.
- Growth that follows concurrent requests points toward simultaneous inference state and service design.
- Growth that follows the number of completed requests, even with stable context and concurrency, makes process-heap accumulation a stronger hypothesis.
- Growth that disappears after a full service restart may indicate stale process state, but it still does not identify the defective allocation.
Apple’s Metal documentation describes a recommended maximum working-set size, but that metric should not be converted into an unverified universal limit for every DeepSeek-R1 quantization or Mac. The Metal working-set definition is useful for understanding pressure, not for inventing a sysctl value.
A referenced open community report describes macOS and Metal Runner heap growth while API-reported model usage appeared stable. That report concerns Ollama 0.32.15 and other models, not DeepSeek-R1 on Ollama 0.34.0. It remains a community troubleshooting signal, not an official confirmation of the same defect in the version covered here. The case is documented in the open Ollama issue.
Can setting keep_alive to zero fix an Ollama memory leak?
No. The Ollama API documents keep_alive=0 as an instruction to unload the model immediately after the request. It can contain model residency and help isolate whether the remaining footprint belongs to the loaded model. It cannot repair a Runner heap that fails to release allocations, and it cannot guarantee that every mapped file, child process, or Metal allocation vanishes at the same moment.
Verify unloading instead of trusting one screen
The unload test must separate API state, process state, and system state. A model disappearing from the running list proves only that Ollama no longer reports it as active. It does not prove that physical memory has returned to the system.
For an API request, send the unload instruction as part of a controlled request:
curl http://127.0.0.1:11434/api/generate \
-d '{
"model": "deepseek-r1",
"prompt": "",
"keep_alive": 0
}'
Use the exact local model name returned by the installation. The keep_alive behavior and response semantics should be checked against the current Ollama API documentation.
For a command-line stop, use the model-management command supplied by the installed Ollama CLI:
ollama ps
ollama stop deepseek-r1
ollama ps
The command-line path asks Ollama to stop the model. The API path attaches unload behavior to a request. They should not be treated as identical control planes when diagnosing a service that may have multiple callers.
Before and after either action, capture:
- The Runner process identifier.
- The process memory view from Activity Monitor or a process inspection command.
- The macOS memory-pressure state.
- Swap activity, if present.
- The model list returned by Ollama.
- Relevant Ollama service log lines.
The process identifier matters because a new Runner can make the model list look healthy while an old process remains alive. If the old process exits and memory pressure improves after the next allocation cycle, the unload boundary may be useful. If the old process remains, or the service enters a Metal error state, a complete service restart becomes the containment step. Preserve the logs before restarting.
For the Ollama application, inspect the application’s service behavior and use its normal quit and relaunch path. For a manually launched service, stop the process using the same shell or service manager that started it. For an API-managed deployment, drain callers before issuing an unload request. Ollama’s macOS troubleshooting documentation explains where to look for service logs and diagnostic material.
Compare recovery strategies before choosing one
A recovery boundary has a cost. Every unload can turn a memory-retention problem into a cold-load problem, especially when the workload repeatedly returns to the same DeepSeek-R1 model. The correct boundary may be a request, a batch, a project, or an idle queue period.
| Recovery strategy | Memory objective | Main operational cost | Best fit | Risk rating |
|---|---|---|---|---|
| Keep the model resident | Avoid repeated loading | Memory remains committed or mapped | Continuous conversation with stable pressure | Lower interruption risk, higher residency risk |
| Unload after each request | Isolate every request | Repeated cold loads and possible session disruption | Small diagnostic experiments | High cold-load risk |
| Unload at batch completion | Reclaim between independent jobs | The next batch waits for loading | Scheduled batch processing | Balanced |
| Drain queue, then unload | Protect active callers | Requires queue and lock coordination | Shared API or Agent service | Lower concurrency risk |
| Restart the service | Replace a suspect Runner | In-flight work is interrupted | Reproducible process growth or bad backend state | High interruption risk |
| Move the workload to an isolated node | Separate failures and capacity | Additional infrastructure or rental cost | Persistent Agent instability | Lower blast radius, higher platform cost |
Do not use a generic memory percentage as the decision rule. We instead compare task completion, recovery frequency, cold-load impact, and how easily the node can be replaced.
A batch job can usually place cleanup after successful output validation and artifact storage. A conversational Agent should not unload while a tool call, streaming response, or state write is active. If several callers share one model, one caller’s keep_alive=0 request can create a reload for everyone else.
Protect shared services from accidental unloads
For a shared API, the safe sequence is more important than the unload command itself:
- Mark the model as draining so new requests stop entering the queue.
- Wait for active generations and tool calls to finish.
- Acquire an exclusive model-maintenance lock.
- Record the Runner process and memory state.
- Issue the API unload or command-line stop.
- Confirm the model list and process transition.
- Release the lock only after health checks pass.
- Retry a failed request through a controlled policy, not through unlimited reload loops.
A health check should test more than whether the HTTP endpoint responds. It should confirm that the intended model loads, produces a valid response, and does not leave an older Runner process behind. A failed health check should preserve the failure evidence before a restart replaces it.
For scheduled batch processing, the validation focus is different. The batch should record whether all outputs were written before cleanup, whether the next batch loaded the intended model, and whether a failed unload caused duplicate work. Cleanup belongs after the job’s durable completion point, not merely after the final token arrives.
How often should a continuously running DeepSeek-R1 Runner be restarted?
There is no responsible universal interval from the evidence available here. Restarting on a timer may hide a defect, interrupt useful sessions, and create repeated cold loads. Set a restart or node-replacement trigger from your own controlled run: repeated growth across equivalent batches, failed unload verification, rising recovery frequency, or unacceptable task interruption. If none of those occurs, a timer alone is not a technical justification.
The decision worksheet for a controlled test
Use this checklist with one representative request sequence. Do not substitute a synthetic prompt if the production issue comes from tool calls, long documents, or Agent loops.
- [ ] Record the Ollama version and exact DeepSeek-R1 model tag.
- [ ] Record whether Ollama.app, a manually launched service, or an API service owns the Runner.
- [ ] Save the initial model list and Runner process identifier.
- [ ] Run one normal request sequence without changing context or concurrency.
- [ ] Record process memory, macOS memory pressure, swap activity, and service logs after completion.
- [ ] Repeat the sequence with
keep_alive=0at the job boundary. - [ ] Confirm whether the model disappears from the running list.
- [ ] Confirm whether the original Runner process exits or remains.
- [ ] Run the same request sequence again and record the cold-load effect.
- [ ] Compare task completion and recovery behavior, not only the memory graph.
- [ ] Drain shared callers before any unload in an API or Agent service.
- [ ] Stop the experiment if the service enters a repeated failure-and-reload loop.
- [ ] Remove any temporary environment or launch override after the test.
- [ ] Escalate to process isolation or an independent node if recovery is required too often.
This process gives us a decision record rather than a visual impression. It also makes rollback explicit: remove the temporary unload behavior, restore the original service launch method, and preserve the logs that explain why the test was stopped.
Choose the workload boundary, not a fashionable fix
The following table summarizes the practical decision in cost-analysis terms. It does not assume that a larger Mac or an isolated node automatically fixes a software defect.
| Observed result after controlled unload | Local action | Capacity decision | Confidence |
|---|---|---|---|
| Model unloads, old Runner exits, and the next job behaves normally | Keep cleanup at a batch or queue boundary | Continue on the current Mac if cold-load impact is acceptable | Strong operational evidence |
| Model unloads from the list but the old Runner remains | Capture logs and process state, then test a full service restart | Avoid claiming that keep_alive=0 repaired the problem |
Partial evidence |
| Memory growth follows context length | Review context and KV-cache demand | Evaluate a larger or better-isolated Mac only after workload reduction is tested | Conditional |
| Growth follows request count with stable request shape | Reproduce with a minimal case and compare process behavior | Consider process isolation before system-level overrides | Stronger leak hypothesis |
| Concurrent callers fail after one caller unloads | Add queue draining, locking, and health checks | Split shared workloads or isolate the service | Service-design issue |
| Recovery works only after frequent forced restarts | Stop tuning and measure replacement cost | Evaluate an independent cloud Mac node | Local process is not stable enough |
Our threshold is operational, not cosmetic. If tasks complete, cleanup is predictable, and the next load fits the service’s latency budget, staying local can be rational. If the team must repeatedly kill processes to keep an Agent alive, the system has exceeded a tuning problem. A replaceable node or isolated process boundary becomes more valuable than another experimental memory override.
For readers evaluating Mac capacity before deployment, our Mac hardware comparison for build and automation nodes provides a separate way to compare local hardware choices. Teams validating Xcode or CI-style workloads can also review the Mac node delivery and acceptance guide. Those pages should support capacity planning, not replace the unload test for this specific Runner problem.
When local recovery stops being economical
A local Apple Silicon Mac remains a good fit when the workload is intermittent, the model can stay resident safely, and the operator controls all callers. It is less attractive when one machine must host unrelated development work, multiple Agent sessions, and a model whose process state requires repeated intervention.
The current local approach has three concrete weaknesses in this scenario: memory pressure can affect unrelated applications, a shared Runner can expand the blast radius of one workload, and recovery may interrupt active sessions while forcing cold loads. A self-managed Mac also makes the operator responsible for collecting logs, coordinating restarts, and proving that an unload actually reclaimed the relevant process state.
Renting an isolated Mac through JexMac can be the cleaner experiment when the immediate need is temporary compute, compatibility testing, or a replaceable environment for a problematic Agent. It does not magically remove Ollama or Metal behavior, and it is not the right answer for a permanently heavy workload that needs dedicated hardware ownership or direct physical interfaces. But isolation can separate the DeepSeek-R1 Runner from a developer’s workstation, make failure recovery less disruptive, and let the team compare node replacement against repeated local restarts. Review the available JexMac rental options only after measuring the real request batch, unload result, and reload impact on the current Mac.
Before choosing that route, run one honest “load, complete, unload, reload” comparison with the production-shaped request sequence. If the model list clears but the process does not, if the same Runner grows across equivalent batches, or if shared callers cannot be drained safely, treat those findings as architecture signals. Continue locally for bounded batch work, split jobs across isolated processes when coordination is possible, and evaluate an independent cloud Mac node when recovery has become part of every successful run.
Run Memory-Heavy AI Jobs on a Dedicated Mac
Move demanding local inference workloads to a dedicated physical Mac mini M4 with 16 GB of unified memory.