Ollama on a 4 GB GPU vs Claude: when a local model stops being enough
I ran my RCA assistant with Ollama on a 4 GB laptop GPU, measured where it ran out, and moved its reasoning to Claude while keeping embeddings local.
KanbanAssist is my open-source root-cause-analysis (RCA) assistant for Azure DevOps tickets. A webhook brings in each new ticket, pgvector finds the five most similar past tickets, and a model writes the likely cause and fix. That model was qwen3:4b, running in Ollama on my laptop's 4 GB GPU. When I started building toward an autonomous agent, where the model has to reason across several steps, the local model stopped being a learning tool and became the bottleneck.
1. My laptop and Claude, side by side
This table is the short version of the story. Rows marked measured come from my own laptop; the sections after it explain the numbers that decided the switch.
| Metric | What it means | qwen3:4b in Ollama, my laptop | Claude Sonnet 5.5 |
|---|---|---|---|
| Hardware | |||
| Where it runs | The machine doing the work | Dell laptop, NVIDIA Quadro T1000, 4 GB | Anthropic's data centers |
| Model size | How many learned numbers (weights) it has | 4 billion weights, 2.44 GB after 4‑bit compression | Not published |
| Memory | |||
| GPU memory free | What the card offers after Windows takes its share | 3.2 GiB of 4 GB (measured) | Not my concern |
| Where the model sits | Layers on the fast GPU versus slow system RAM | 29 layers on GPU, 8 on CPU: 80% fit (measured) | All on Anthropic's hardware |
| Memory per token | KV cache: what the model keeps about each earlier token | 147,456 bytes | Handled by Anthropic |
| Tokens and context | |||
| Context window | Most tokens in one request, prompt plus answer | 262,144 trained; about 6,800 fit on my card | 1,000,000 |
| Longest answer | Most tokens one reply can contain | Limited by the same memory | 128,000 |
| Prompt too long | What happens past the limit | Ollama quietly cuts the prompt | The API returns an error |
| Speed | |||
| Reading the prompt | Tokens processed before the answer starts | 60.4 tokens/s: 109 tokens in 1.80 s (measured) | Not published |
| Writing the answer | Tokens generated per second | 15.9 tokens/s: 125 tokens in 7.86 s (measured) | About 42 tokens/s (third‑party) |
| One short answer | Total time for one request | 9.82 s (measured) | Fast on Investigate; Langfuse timing next |
| Parallel requests | Can it answer several at once? | One at a time; a second cache does not fit | Many, within rate limits |
| Cost | |||
| Price | What each token costs | $0; I pay for the GPU and power | $2 per million input, $10 per million output |
| One RCA call | 8,000 tokens in, 800 out | $0 | $0.024 |
| Discounts | Ways to pay less | Not needed | Cached input $0.10 per million; Batch API half price |
| Limits and quality | |||
| Rate limits | Caps on how much you can send | None; the GPU is the cap | Per minute: 1,000 requests, 2M input, 400K output tokens |
| Reasoning quality | How well it reasons over the retrieved tickets | Became my bottleneck while building the agent | Fast, usable answers on Investigate; formal evaluation next |
| Data and control | |||
| Data leaves my machine | Where ticket text goes | No | Yes, to Anthropic |
| Works offline | Runs without internet | Yes | No |
| Model changes | Can the model change under you? | Never; the file is pinned | Pinned IDs; old models are retired |
| Fine‑tuning | Training it further on your own data | Allowed: open weights, Apache 2.0 | No self‑serve option |
| Embeddings | Turning text into vectors for search | nomic‑embed‑text, local | Not offered; use another provider |
The measured column tells the real story. My card held only 80 percent of the model, so it wrote 15.9 tokens per second and took 9.82 seconds for a 125-token answer. Claude's constraints are money and rate limits, not memory, and at $0.024 per call the money is small.
2. The moment it stopped working
When an RCA answer from the local model was wrong, it had three possible causes, and I had no way to tell them apart. An agent that loops an Analyst and a Critic over several steps would multiply that doubt at every step.
A strong model removes the third suspect, so a failure points at my prompt or my code, which are the things I can fix. To see why the small model was weak and slow on my machine, I worked the numbers from the hardware up.
3. Why a 4 GB GPU stalls
To write one token, the GPU must read every weight of the model from its memory. My model has 4.02 billion weights, compressed to about 4.85 bits each, so every token moves 4.02 B × 4.85 ÷ 8 = 2.44 GB. A laptop card with a 128-bit bus at 12 Gbps moves 16 bytes × 12 billion = 192 GB/s.
The second cost is the KV cache, the model's memory of earlier tokens. Every token keeps a Key and a Value vector in each of 36 layers: 36 × 2 × (8 heads × 128 numbers) × 2 bytes = 147,456 bytes per token. My RCA prompt is about 8,000 tokens, so a context of 8,192 tokens needs 8,192 × 147,456 bytes = 1.21 GB.
When memory runs out, Ollama moves some layers to system RAM, where the CPU runs them. Laptop RAM moves 51.2 GB/s, a quarter of the GPU's speed, and my earlier article measured exactly this: only 80 percent of the model fit on the GPU.
Context tells the same story. The model was trained for 262,144 tokens, but my card can hold the cache for 6,780 of them. Claude Sonnet 5.5 accepts 1,000,000, and its cache lives on Anthropic's hardware, not mine.
4. Time to a correct answer
Claude writes about 42 tokens per second in third‑party measurements, close to my laptop's 40. Speed per token is the wrong metric. What matters is how long it takes to get an answer you can use, which adds the time to read the prompt and multiplies by the attempts.
Retries also compound. If each agent step succeeds 85 percent of the time, four steps succeed 0.85⁴ = 52 percent of the time; at 97 percent per step, 0.97⁴ = 88 percent.
5. What Claude costs, and where the limits bite
Claude Sonnet 5.5 costs $2 per million input tokens and $10 per million output tokens. Output costs five times more because input is read in one parallel pass, while every output token needs its own pass through the model. One RCA call sends 8,000 tokens and receives 800.
Once the Analyst and Critic loop is built, a ticket that takes three calls will cost 3 × $0.024 = $0.072. Money is not the hard limit, though. The hard limit is how much Anthropic lets one account send per minute.
What a rate limit is
Anthropic's servers are shared by every customer, so each account gets a fixed share per minute. On the starting tier, Claude Sonnet 5.5 allows three amounts per minute, and a request goes through only if it fits all three. Table 2 lists them, with the same number spread over a second.
| What is counted | Per minute | Per second |
|---|---|---|
| Requests | 1,000 | 16.7 |
| Input tokens: everything I send | 2,000,000 | 33,333 |
| Output tokens: everything Claude writes | 400,000 | 6,667 |
Anthropic enforces each amount like a bucket of tokens. The bucket refills a little every second instead of resetting once a minute, and every call takes tokens out. Figure 8 follows the input-token bucket through one cycle.
retry-after header with the seconds to wait, and Spring AI can retry the call automatically.What it means for KanbanAssist
For live tickets the limit never matters. One ticket through a three-call loop sends 3 × 8,000 = 24,000 input tokens, which is 1.2 percent of a minute's 2,000,000. My planned evaluation is different, because it replays hundreds of closed tickets in one go. Figure 9 shows what happens if I send 300 of them at the same moment, and three ways around it.
My laptop has no rate limit, but it has a ceiling of its own: one GPU answers one request at a time, because a second KV cache does not fit in its memory. At my measured 9.82 seconds for one short answer, 300 tickets in a row would take 300 × 9.82 = 2,946 seconds, or 49 minutes, and full RCA prompts would take longer still.
6. Embeddings stay local
Anthropic has no embedding model, and it does not need one here. Retrieval and generation meet only at plain text, so each side can use a different provider.
Vectors from different embedding models never mix. Each model learns its own coordinate system, so a 768-number nomic vector and a 1,024-number Voyage vector for the same sentence share no meaning, and switching models means re-embedding every stored ticket. Embedding models are also small, frequent and close in quality to paid APIs, which makes them the clearest case where a local model wins.
7. What the switch changed
The application code did not change. Everything happened in configuration.
The two properties that matter sit in application.yml. Both read from .env with no default, so a missing key stops the application at startup and names the key, instead of running on a hidden fallback.
spring:
ai:
model:
chat: ${CHAT_PROVIDER} # anthropic
embedding: ${EMBEDDING_PROVIDER} # ollama
# config/anthropic.yml
spring:
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
chat:
options:
model: ${ANTHROPIC_CHAT_MODEL} # claude-sonnet-5-5
max-tokens: 2048
8. How Spring Boot picks the model
Nothing in my code names Ollama or Anthropic. Each starter JAR lists its auto-configuration classes in a file named AutoConfiguration.imports, and each class runs only when the property spring.ai.model.chat names its provider.
CHAT_PROVIDER=ollama, the two middle boxes swap and nothing below them changes.At run time, the same interface call becomes two different HTTP requests. Spring AI translates the prompt into each provider's format and converts both replies back into one ChatResponse object.
9. Where a local model is the right call
Companies do run models on their own hardware, usually self-hosted in their cloud account with a serving engine such as vLLM rather than Ollama on a laptop. Figure 14 is the decision I would apply to any single step of an AI system.
Sending tickets to Claude also means their text leaves my machine, including any log line or secret pasted into a comment. Redacting secrets before the call, and reading the provider's data-retention terms, belongs in the first question of Figure 14.
What I learned
Running a model on a 4 GB GPU taught me more about how models run than any course, and it also showed me where that lesson ends. These are the points I would carry into any AI system:
- A small GPU is limited by memory, not by compute. My card held 80 percent of the model, so it wrote 15.9 tokens per second.
- A model's advertised context is not the context you get. Mine was trained for 262,144 tokens, and my card held about 6,800.
- For an agent, quality per step matters more than speed per token. A weak model turns every failure into a guess between my prompt, my code and the model.
- Local still wins where the task is small and frequent. My embeddings never left the laptop, and the stored vectors did not change when the chat model did.
- With Spring AI, the model is configuration. One property decided whether Ollama or Claude answered, and my Java code never knew.
KanbanAssist now reasons with Claude Sonnet 5.5 and embeds locally with nomic-embed-text. My next step is an automated evaluation over closed tickets, so that every change to the agent, and any future move of a step back to a smaller model, is decided by a score instead of by how an answer feels.