Skip to content

Installation

Paul Szabo edited this page Jul 13, 2026 · 32 revisions

Hardware information

I'm using Ubuntu (server) 24.04.4 LTS on a ~10 year old machine with 32GB 800MHz DDR3, Intel i7-4790K CPU @ 4.00GHz, SSD for the OS + models. The GPU is a NVIDIA GeForce GTX 1060 with 6GB VRAM, so I appear to be limited to LLMs of 8 billion parameters (or less). This information is provided to get a sense of timing, and to show that experimenting with LLMs can be achieved on older hardware.

Build 1 - Ollama and Airflow running on the same host

Ollama

The LLMs are running on Ollama.

1. Install Ollama

curl -fsSL https://ollama.com/install.sh | sh

Note: For security, this is not how it will be installed for in-production environments.

Ollama is a wrapper for llama.cpp by Georgi Gerganov. It abstracts away the configuration required to run llama.cpp via scripts which detect likely good choices and exposes OpenAI model compliant LLMs through a local API endpoint or chat interface directly on the terminal. Ollama also hosts LLMs and allows you to effortlessly pick and choose a (tagged) model that suits your needs. In my case 6GB VRAM is a bottleneck. Replicating the flow presented by mmajurski/NIST requires a model that implements "thinking" (reasoning tokens).

For me, that means nemotron-3-nano:4b. Here is a screenshot of the Ollama site, "Models" section:

A screenshot of the Ollama site, Models section

^^Underlined in red is the "thinking" tag we're after (reasoning tokens)

Another screenshot of the Ollama site, Models section

^^Underlined in red is the default context length, which effectively represents how much memory running the model will require

Note: Ollama will happily run a model that uses more memory than can be provided by your physical VRAM. It does this by 1. Swapping to disk in the event you are constrained by mainboard memory (RAM) or 2. Using a combination of mainboard memory and GPU memory. By adjusting the model context size via the OLLAMA_CONTEXT_LENGTH environment variable, you can fine tune Ollama so that it makes most efficient use of your fastest components.

The command ollama ps will show you how resources are being used, as in the following example output of the same:

NAME ID SIZE PROCESSOR CONTEXT
nemotron-3-nano:4b 6cc467f05439 5.0 GB 100% GPU 167936

In order to get the highest memory use entirely on GPU, I came to use the value (164 x 1024) = 167936 through trial and error. The number 167936 is tokens, not MB, and the length (size in bytes) of a token can vary from model to model.

More information on getting started with Ollama can be found here.

Note: Before installing Ollama, install the NVIDIA drivers recommended for your graphics card. The rule of thumb seems to be that "if nvidia-smi returns results (rather than an error), you should be good to use your GPU/VRAM with Ollama".

In my case, I installed Ollama before installing the NVIDIA drivers, assuming that they were detected by the Ubuntu installation process, since I selected "use third-party drivers" (enabling the restricted, multiverse apt package repositories), but this proved to be incorrect and Ollama threw an error explicitly identifying which drivers to install for my graphics card (580x), which is curious because ubuntu-drivers devices recommends the 535x "non-free" (NVIDIA). I have yet to benchmark performance on the 535x vs. the 580x (if they even work), because at this point I'm only looking for a working proof of concept.

Apache Airflow

uv is a fast Python package manager, written in Rust. Installation instructions in detail are available here. It enables you to fetch dependencies (libraries) from their official distribution sources faster than when using pip alone.

1. Install uv

curl -LsSf https://astral.sh/uv/install.sh | sh

Note: For security, this is not how it will be installed for in-production environments.

2. Create a directory for airflow at your desired location

mkdir airflow

3. Create virtual environment for airflow

uv venv --python=3.12

4. Activate the newly created venv

source .venv/bin/activate

5. Install airflow via uv

uv pip install "apache-airflow[celery]==3.2.2" --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-3.2.2/constraints-3.12.txt"

Note: For security, this is not how it will be installed for in-production environments.

6. Install openai provider for airflow

uv pip install apache-airflow-providers-openai

7. Install common ai module

uv pip install apache-airflow-providers-common-ai

8. Run airflow

airflow standalone

Note: On first run, the admin password will be echoed to terminal. After the first run, you can find it in the file simple_auth_manager_passwords.json.generated in the directory

9. Create Pydantic AI connection

Create a connection to your OpenAI model compliant LLM via the Apache Airflow UI at http://localhost:8080 (do not use "standalone" for production), using the following:

  • Connection Type: Pydantic AI
  • Connection ID: openai_default
  • Description: [optional]
  • Host: http://127.0.0.1:11434/v1
  • API Key: [not required but apparently cannot be left blank]
  • Extra JSON: { "extra": { "model": "openai:nemotron-3-nano:4b" } }

Screenshots:

Defining a Pydantic AI connection to your locally running LLM in Apache Airflow via the web UI

^^Defining a Pydantic AI connection to your locally running LLM in Apache Airflow via the web UI

Standard fields

^^Standard fields

Extra fields JSON

^^Extra fields JSON

Note: "model" in extra JSON must be prefixed with "openai:", but you can use whatever model is desired, as long as it supports "thinking"

10. Create your directed acyclic graph (DAG)

Copy the provided example to your dags path

Note: The default path for directed acyclic graphs (DAG) is "./dags/".

Apache Airflow will scan the configurable DAGs path on a schedule:

Apache Airflow Task SDK flow

Note: See Airflow 2x Architecture from the source for more information.

..and import newly added files in to its internal database (sqlite):

Airflow internal database for Tasks

After a few moments, your DAG should have been imported into Airflow's internal database. The airflow standalone command provides a number of example DAGs), click on Dags in the left hand menu, and search for it by name (dag_id), "llm_test":

Search for your DAG by name (dag_id) in the Airflow web UI

Note: The DAG version and a convenient button to rescan (reparse) the file for changes:

The Airflow DAG view with reparse button and version highlighted

Hit the Trigger button to manually trigger the task:

The Airflow DAG view with trigger button highlighted

Options can be specified once you expand the example, if desired. Click trigger to launch the task:

The trigger dialog

There are two subtasks defined in our DAG: run_analyst_agent and format_report, see the first task is running:

The running run_analyst_agent task

Then, once completed, click on the instance of the finished format_report task:

The completed run

The LLM response, formatted in JSON, for machine-readability:

The LLM response, formatted in JSON

The DAG script that generated the response:

The llm_example DAG code

Note: Ollama will automatically import the requested model in to memory and purge it if there are no requests after a delay which is configurable with the OLLAMA_KEEP_ALIVE environment variable, which you can set in the systemd unit file for the ollama service, or by passing it directly on launch. The duration of my manually triggered test run was a cold started 00:00:21.157, so ~21 seconds for a simple response from a prompted agent.

Ta-da 🎉

You can monitor your GPU resource usage using nvtop (for NVIDIA graphics cards), available from Ubuntu apt packages repository (top) and other system resources via htop directly at the terminal (bottom), multiplex the terminal windows using screen "split" for a quick dashboard, as shown in the following video:

Frame of video as preview showing the spike ^^The spike in the graph shows the Airflow DAG run

Download

Build 2 - Ollama, Airflow database and workers running on different hosts (distributed architecture)

Build 3 - Ollama, Airflow database and workers running as Kubernetes Pods (distributed, containerized architecture)

Build 4 - Hardening and preparing for in-production (extra security considerations/overhaul), deployed using Infrastructure-as-Code (Ansible + Terraform)

Build 5 - Running on Digital Ocean ($)

Build 6 - Running on Google Cloud Platform ($$)