Hyperfocus Bear vs. Lunar Lake: Ollama, IPEX-LLM, and OpenVINO benchmarked on Intel Arc and NPU
Finally I bought Lunar Lake.

I always wanted to say that, who can afford lunar lake?
No one. There are no lakes on the moon.
HP named that device properly using AI in very center: HP OmniBook X Flip AI Ultra 7–258V/32GB/1TB.
It is slim, solid, aluminium machine. It does not overheat. You see HP, you can do it right (HP Envy T02 was an accident I think).
It works 8 hours on one battery charge. It has a gorgeous OLED 16" touch display. It “flips” so it can even be your tablet if you are Arnold Schwarzenegger’s son (because HP is 1.7kg).
It hides Intel secrets inside: Intel calls it AI PC. Its CPU has 8 cores (no HT), good modern iGPU (Arc 140V), and even 4th generation NPU 48 TOPS (int8) — who knows what it tops?
The not so wide known fact can be that Lunar Lake architecture for laptops has Unified Memory Architecture (UMA) and very fast RAM memory.
My model has 32 GB LPDDR5x, 8533 MHz RAM. The only disadvantage of UMA is the fact you can’t extend it, you need to live with what you started with.
I heard people are buying Macs or AMD Ryzen AI CPUs because of UMA. Silly you go Intel, buy Lunar Lake laptop for local inference.
If you’re developer, Intel offers AI development kit or you can use Intel OVMS server for serving LLMs.
For many Ollama will be way to go or rather tool to use.
It is easy to download and run under Windows and Linux. I chose to install on this laptop Ubuntu 26.04 along preinstalled Windows 11 Pro.
For today’s test Ubuntu will be my choice. I went to Ollama’s web page and downloaded standard package for Linux (1.3GB size). I can tell you that 90% of content in this package is completely not needed on this Lunar Lake laptop. Why is that so? Because it is Nvidia CUDA and AMD ROCm code. I decided to use it anyway because I know there is Vulkan support which you activate with OLLAMA_IGPU_ENABLE=1 and thanks to that you can use Arc 140V in this laptop to run inference.
For this test I used qwen3:4b Q4_K_M (2.5 GB) run on CPU first to establish a baseline and then using iGPU to see improvement.
As expected, CPU run results were not splendid, I almost fell asleep waiting for those:
| Test | t/s | Peak t/s | TTFR |
|---|---|---|---|
| pp256 | 2.24 | — | 86.7s |
| pp512 | 2.18 | — | 156.6s |
| pp1024 | 1.72 | — | 283.0s |
It got much better results though with iGPU, I didn’t manage to take a nap:
| Test | t/s | Peak t/s | TTFR |
|---|---|---|---|
| pp256 | 14.58 | — | 2.81s |
| pp512 | 14.51 | — | 3.96s |
| pp1024 | 14.29 | — | 5.55s |
One parameter so much acceleration.
Great, test finished. I proved that Ollama works with Lunar Lake iGPU trough Vulkan. Not so fast ol’ Bear. Why were you so upset with this 1.1GB of unneeded software? Does your laptop deserve a lean, fast engine maybe, optimised for Intel hardware and software stack? Hell yeah, it does.
Let’s look into Intel’s IPEX-LLM project. Not good. It is archived already on Github. But I’ll try it anyway because size matters — small size in that particular case. This package is only 143MB.
Fact is that this package is in very old version and I wouldn’t bother to run it for anything more serious than test. It uses the same model blob that Ollama has downloaded. I ran it straight on iGPU without wasting my time on CPU crawl:
| Test | t/s | Peak t/s | TTFR |
|---|---|---|---|
| pp256 | 24.64 | — | 0.54s |
| pp512 | 23.83 | — | 0.86s |
| pp1024 | 25.22 | — | 1.70s |
Hmm, wait a minute. It is genuinely remarkable difference.
Token generation speed almost 70% faster and prompt processing is 3–5 times faster than on new Ollama through Vulkan. Not mentioning the size difference which is enormous (relatively plays no role with today flash fast internet but still).
Some of you will know where is the secret. IPEX-LLM uses SYCL and Level-Zero (Intel’s GPU compute stack) and thanks to this adapts like a glove to the hand to Intel’s hardware.
It is like F1 car built for the driver by the team of engineers who pay attention to every detail to squeeze performance from every piston stroke.
I like my Intel hardware. I have a lot of it. I have a strong motivation to adapt. I like F1, tight gloves and piston strokes. KTM pistons are damn hot one, angry and brutal. I’m only the hot one.
You might ask the question: what do you intend to actually do about it, naughty Bear?
I might answer: the four people who actually read my article about spies know already, you will need to read it yourself. Brief summary for those who didn’t: Rust + OpenVINO = RustedVINO.
I can show you the table. Same LunarLake, same class model Qwen3–4b-int4-ov prepared by the OpenVINO team (and yes I know that it is not exactly the same quantisation method), it is not compatible with Ollama though.
CPU first, it beats Ollama because of OpenVINO usage:
| Test | t/s | Peak t/s | TTFR |
|---|---|---|---|
| pp256 | 4.26 | — | 21.0s |
| pp512 | 3.85 | — | 42.9s |
| pp1024 | 3.34 | — | 96.4s |
GPU now, it catches up with OpenVINO to where IPEX-LLM landed with SYCL:
| Test | t/s | Peak t/s | TTFR |
|---|---|---|---|
| pp256 | 24.83 | — | 0.44s |
| pp512 | 24.71 | — | 0.49s |
| pp1024 | 24.55 | — | 0.76s |
You can notice though the prompt processing for longer prompts is much better than it was on IPEX-LLM, faster roughly twice. It matters trust me, especially with agents and tools usage which bloats prompts to the limit.
Now a bit more showing off.
I have Intel’s AI PC here so NPU is a power-savvy way to run inference and actually Lunar Lake 48 TOPS 4th generation NPU is in the top of those others sold on the market. You get almost the same power like in newer Panther Lake 5th generation NPU for around 20–30% price discount.
Ollama can’t use it. IPEX-LLM can, through its own NPU acceleration path — but not the way rustedVINO does: serving it live over an OpenAI-compatible API.
What’s more significant, my server can run different models on CPU, NPU, and iGPU at once — and swap one out live, without dropping inference on the other devices. I haven’t found another server that does the live hot-swap part without a restart.
We just need a different model which is prepared for NPU (channel-wise not group-wise compilation).
I had a bigger version on disk Qwen3–8b-int4-cw-ov, here we go:
| Test | t/s | Peak t/s | TTFR |
|---|---|---|---|
| pp256 | 17.24 | — | 1.50s |
| pp512 | 16.92 | — | 1.60s |
| pp1024 | failed | — | — |
Not bad for a model twice as big, isn’t it?
You will ask why there’s an error at 1024 token prompt.
It’s my programmer’s fault. Not really. Thanks to this test I discovered that my server should expose additional parameter at loading model on NPU, that is actually typical behavior when using NPU for inference, you need to set upfront the maximum size of the prompt so model can be compiled exactly to that limit. That’s the NPU’s world where energy saving matters more than flexibility.
What did we learn from all this? Step by step:
- Using Ollama on Intel Arc and Lunar Lake works better when Vulkan support gets enabled.
- Using OpenVino based server squeezes maximum performance from Intel’s hardware.
- IPEX-LLM works with the same models which Ollama uses.
- Models used for my OpenVINO server are not the same as models for Ollama, those are Intel prepared models downloaded from OpenVINO Toolkit account.
- Bear has some server he didn’t share …yet.
Now I will share this server’s capabilities because I tried them out, hardened them and tested, I’m in the process of building a package for Linux and Windows.
This server is fully OpenAI API compatible, plus it has “Bear’s extensions”.
| Feature | Status |
|---|---|
GET /health, GET /v1/models | ✅ |
GET /health_generate — 1-token GPU deep-readiness probe | ✅ |
GET /metrics — Prometheus endpoint (same port) | ✅ |
POST /v1/chat/completions (SSE streaming + non-streaming) | ✅ |
POST /v1/completions — legacy raw-prompt completions | ✅ |
POST /v1/embeddings — text embeddings (bge, e5, …) | ✅ |
POST /v1/rerank — cross-encoder document reranking | ✅ |
POST /v1/audio/transcriptions — STT via Whisper OpenVINO | ✅ |
POST /v1/audio/speech — TTS (SpeechT5, Coqui VITS) | ✅ |
POST /v1/images/generations — text-to-image (SDXL, FLUX.1-schnell) | ✅ |
POST /v1/images/edits — inpainting + img2img (SDXL) | ✅ |
POST /tokenize / POST /detokenize | ✅ |
GET /v1/realtime — WebSocket voice flow (STT → LLM → TTS pipeline, barge-in, session history/retrieval) | ✅ |
ContinuousBatchingPipeline engine — N concurrent streams in one GPU step | ✅ |
NPU LLM engine — static LLMPipeline single-stream path (Lunar Lake NPU) | ✅ |
VLM chat — vision-language models (/v1/chat/completions, image input or text-only) | ✅ |
Exact usage.prompt_tokens via tokenizer bridge | ✅ |
stream_options.include_usage — token counts on streaming final chunk | ✅ |
Sampling params: temperature, top_p, top_k, stop, seed, presence_penalty, frequency_penalty, repetition_penalty (extension field — server applies a 1.1 safety-net default unless the client sends 1.0 to opt out) | ✅ |
max_completion_tokens alias for max_tokens | ✅ |
Streaming <think> strip — ThinkFilter removes reasoning tokens from output | ✅ |
Non-streaming <think> strip — reasoning in reasoning_content, hidden from content | ✅ |
Per-model reasoning_parser flag — qwen3 / gpt_oss extractors | ✅ |
enable_thinking field — explicit thinking budget for Qwen3-style models | ✅ |
OpenAI tool/function calling — tools + tool_choice ("none"/"auto"/"required"/named function), stream + non-stream; native Qwen3/Phi/Llama3/Gemma dialect, plus Mistral and Qwen3-Coder XML dialects, auto-detected from the model's own chat template | ✅ |
Structured output — response_format ("text" / "json_object" / "json_schema") via OpenVINO GenAI's StructuredOutputConfig (xgrammar); chat only | ✅ |
KV prefix caching (enable_prefix_caching, default on) — reuses KV blocks across requests sharing a prompt prefix; 49–83% e2e TTFT reduction on deep multi-turn/RAG prompts, measured on B50 and OmniBook (140V iGPU) | ✅ |
MoE detection + max_num_seqs cap — prevents CB throughput collapse on sparse models | ✅ |
Response headers: x-request-id, x-server, x-api-version, x-server-version (admin-only); CORS config | ✅ |
| OpenAI-compatible error envelopes on all error paths | ✅ |
| Prometheus push metrics: tokens/sec EMA, TTFT histogram, request duration histogram | ✅ |
Fixed KV pool + slot-cap (max_num_seqs, cache_size_gb) → no deadlocks | ✅ |
Admission wait-queue (tokio::Semaphore, admission_queue_timeout_ms) — every engine kind (CB, VLM, embed, STT, TTS, NPU), cancellation-safe | ✅ |
Cross-pipeline device admission (device_budgets) — a second gate capping concurrency across different engine kinds sharing one physical device (e.g. LLM + VLM + STT + TTS + embedder all on one GPU); ships disabled by default (empty config ⇒ pure passthrough, zero overhead) | ✅ (opt-in) |
finish_reason correctly distinguishes "stop" (EOS) vs "length" (max_tokens) | ✅ |
Runtime model add — POST /v1/admin/models/add (registers + loads, persists to config) | ✅ |
Runtime model load — POST /v1/admin/models/{id}/load | ✅ |
Runtime model evict — DELETE /v1/admin/models/{id} | ✅ |
Runtime model deregister — DELETE /v1/admin/models/{id}/register | ✅ |
Config reload — POST /v1/admin/config/reload (diffs disk config vs. live registry) | ✅ |
Config/registry drift audit — GET /v1/admin/config/audit | ✅ |
Voice-flow pin — GET /v1/admin/voice-pin (shared STT/LLM/TTS set for realtime sessions) | ✅ |
Realtime session introspection/kill — GET/DELETE /v1/admin/realtime/sessions[/{id}] | ✅ |
| On-demand lazy load (all model kinds) — load fires automatically on first request | ✅ |
| VRAM budget tracking (internal; OV Cores are blind to each other) | ✅ |
| VRAM estimator: STT/embed/image charge weight-only (no KV pool reservation) | ✅ |
| LRU auto-eviction when a new model doesn't fit | ✅ |
domain_budgets — per-domain VRAM isolation (llm / embedding / stt / image) | ✅ |
| Size/EU-aware STT placement — heavy Whisper routes to CPU, light to NPU/iGPU | ✅ |
Capability-based device placement (placement.rs tier preference, no hardcoded names) | ✅ |
API key auth (api_keys); distinct admin-scope keys (admin_api_keys) | ✅ |
| Crash handler — logs a marker + backtrace on native SIGSEGV/SIGABRT/SIGBUS/SIGILL before the process dies | ✅ |
--supervise — crash auto-restart wrapper with backoff + restart cap (Unix only) | ✅ |
OpenVINO GenAI C++ bridge (ov_bridge/) | ✅ |
JSON config file (/opt/vino/config.json) | ✅ |
| No automatic fallback — wrong model ID → 404, not a guess | ✅ |
| 666 tests pass (583 lib + 78 integration + 2 golden + 3 supervisor integration; 5 ignored + 2 ignored doctests) | ✅ |
Clap if you like that idea.
I got sweat doing it.
Two long months and 536 commits.
Still counting: speculative decoding on the way, tested already with Qwen3 family gives 1.3 -1.7 generation acceleration for Qwen3-14B-int8 model on my Intel Arc PRO B70, so it’s worth it.
Disclaimer: All opinions, ideas, choices and rusted cans described here are exclusively my own. Intel makes good chips. I buy lakes, make trouble and solve some of it for myself. These are separate and independent activities.
Originally published on Medium, 17 July 2026.