Local LLM with depth - full transparency instead of a black box

Local LLM with depth - full transparency instead of a black box
By Matthias Petermann / on 18.11.2025

Introduction

In my previous articles Local LLM – AI language models without cloud and Local AI in practice – Redmine ticket analysis with Ollama I showed how uncomplicated local AI is today. Simply drag the model, start it, use it – done.

But while I certainly enjoy this lightness, one thought remains: How much of this do I really understand?

Because Ollama’s comfort layer covers a complex infrastructure. And that’s exactly where the exciting part begins: We go below the surface.

ℹ️ Why this article?
Many teams start with Ollama – and get stuck there. Most people don’t even know which layers, libraries and build processes are running in the background. This article helps fill this gap.
📝 demarcation
This article does not cover training processes. Training becomes a separate, detailed topic.

The stack under Ollama – what actually happens there?

Ollama is often perceived as “runtime”. In reality, it is a convenient orchestrator that builds on several other projects - particularly llama.cpp.

What Ollama really takes over

  • Scheduler & resource management
  • Model bundles and registry
  • Prompt/template handling
  • Daemon + HTTP API
  • Backend selection (CUDA/Metal/ggml)
  • Cache management
ℹ️ Fun Fact: Ollama is not a fork of llama.cpp
Many believe Ollama contains its own inference engine. In fact, it uses the llama.cpp backend largely unchanged - just wrapped in a convenient daemon structure.

What llama.cpp does

llama.cpp is deliberately minimalist:

  • pure C/C++ engine
  • open GGUF models
  • no bundles, no registry
  • full control over quantization, tokenizer, build flags

Ollama expands, abstracts and packages – llama.cpp executes.


Why the manual route is important

Anyone who operates AI in sensitive or productive areas needs transparency:

  • Which components are executed?
  • Which compilers were used?
  • Which quantized variant did I actually load?
  • What dependencies exist?
  • How trustworthy is the supply chain?
ℹ️ Transparency as a safety factor
The less black box, the lower the risk of unnoticed manipulation - be it in the build process, in the model or in the runtime environment.

Step 1: Build llama.cpp yourself

To have complete transparency about the inference engine, we build llama.cpp directly from source code. The process consists of three clearly defined sub-steps:


1.1 Clone repository

We get the full, unmodified source code from GitHub:

git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp

This means that exactly the version that was published on GitHub is available - including commit hashes and build scripts.


1.2 Install dependencies

Using Ubuntu as an example, the build environment required for llama.cpp can be set up with just a few packages. The advantage: The entire toolchain remains extremely slim and transparent.

sudo apt update
sudo apt install -y \
    build-essential \
    cmake \
    libcurl4-openssl-dev

What’s behind it?

  • build-essential Installs the basic compiler toolchain (gcc/g++, make).

  • cmake Used by llama.cpp as a build system.

  • libcurl4-openssl-dev Small, non-critical system library – necessary for HTTP functionality of individual llama.cpp tools.

With this, the Ubuntu environment is already fully prepared to compile llama.cpp from source code.


1.3 Build build

We create a reproducible release build variant:

cmake -B build
cmake --build build --config Release

Optional: Make binary available globally:

export PATH=$PATH:$(pwd)/build/bin

ℹ️ Tip: Reproducible builds

For every build you should document:

  • Commit hash
  • CMake version
  • Compiler version (gcc/clang)
  • flags used

This creates a fully verifiable and reproducible inference environment that remains auditable and documentable in the long term.


Step 2: Load models without Ollama

Ollama uses its own, complex bundle formats. For a completely transparent stack, we load pure GGUF models instead - with no additional layers, no hidden metadata and no proprietary structure.

The process consists of three clear individual steps:


2.1 Build download tool

We use a small, lightweight CLI (“hfdownloader”) that only loads HuggingFace’s actual artifacts – recursively, reproducibly and without dependency on Python ecosystems.

git clone https://github.com/bodaay/HuggingFaceModelDownloader.git
cd HuggingFaceModelDownloader

# Binary bauen
make

# Binary systemweit verfügbar machen
cp output/hfdownloader_linux_amd64_2.0.0 ~/bin/hfdownloader
ℹ️ Why not just 'huggingface-cli'?
The HuggingFace Python CLI is functional but comes with many dependencies. hfdownloader is a single static binary → ideal for reproducible systems and minimal containers.

2.2 Set access token

Private or large models require a valid HuggingFace access token:

export HF_TOKEN=hf_xxxxxxxxx

2.3 Download GGUF model

Now we get the model exactly as it is on HuggingFace - without conversion or additional data:

hfdownloader download Qwen/Qwen2.5-3B-Instruct-GGUF -o ./models

The folder structure corresponds exactly to the repository. Important: nothing will be unpacked, merged or changed here.

ℹ️ Why GGUF?
GGUF is an open, documented exchange format - completely transparent, without black box metadata and optimized for local inference. This makes it ideal for reproducible, testable LLM pipelines.

Step 3: Your own API – with llama-server

With the model and engine ready, all we need is a lightweight, local API that applications can use to communicate with the model. This is exactly the gap that llama-server closes.

llama-server is the official HTTP server component of llama.cpp and provides:

  • an OpenAI compatible REST API
  • without proprietary layers or bundles
  • directly executable as a single binary

This means that any GGUF model can be addressed immediately via curl, script or client library.

llama-server   -m ./models/Qwen/Qwen2.5-3B-Instruct-GGUF/qwen2.5-3b-instruct-q5_k_m.gguf   --host 0.0.0.0   --port 8080

Step 4: Test API

Once llama-server is running, it provides an OpenAI compatible REST API. This means: existing tools, scripts and clients work without adjustments - only the host changes.

The key endpoints for initial testing are:


4.1 Chat Completion (multiple messages, roles)

The chat endpoint reflects the classic ChatGPT behavior:

curl http://localhost:8080/v1/chat/completions \
  -d '{
        "model": "qwen2.5-3b",
        "messages": [
          { "role": "user", "content": "Erkläre lokale LLMs." }
        ]
      }'

4.2 Text Completion (simple prompt → response)

For classic prompt-in/text-out cases:

curl http://localhost:8080/v1/completions \
  -d '{
        "model": "qwen2.5-3b",
        "prompt": "Schreibe ein Gedicht."
      }'

This endpoint behaves like the original GPT-3 API model: A pure prompt without roles - ideal for short texts, code, extractions and structured output.

ℹ️ Note: API compatibility
Many tools that support OpenAI APIs (LangChain, LlamaIndex, N8N, Flowise, etc.) work directly with llama-server - often without modification.

Step 5: Bonus – the new web GUI from llama-server

llama-server comes with a lightweight UI:

LLama WebUI

Features:

  • Browser-based chat interface
  • multiple sessions, history, parameter control
  • completely local
  • Document processing (PDF, TXT, MD, JSON)
ℹ️ Performance tip
The web UI uses the same engine as the API. If maximum performance is required, large context windows or complex system prompts should be disabled in the UI.

Conclusion

We opened the Ollama stack layer by layer and built a completely transparent local LLM workflow:

  • the inference engine self-compiled
  • Models loaded directly and comprehensibly
  • Quantization consciously controlled
  • Provided our own, minimalistic OpenAI compatible API
  • and added local ease of use with the web UI

Result: Instead of an abstracted black box, there is now a clear, auditable and reproducible AI stack that can be understood and adapted at every level.


ℹ️ What I want to work on next

In the next step I would like to take a moment to look at the inner mechanisms of a model. We plan to take a look at:

  • the creation of your own tokenizer
  • the definition of a small, comprehensible model architecture
  • first experiments on training & fine-tuning
  • the conversion to GGUF
  • simple evaluation and testing procedures

The aim is not a full-fledged guide, but rather a understandable mini-LLM stack as a learning project, which can be used to explore the central building blocks of a model.