↓ Skip to main content
AI & Data

Colibri on my desk: a 30B model writes a network scanner, slowly

A new colleague who works slowly but knows a lot is still useful, as long as someone checks the work. I gave a 30-billion-parameter coding model, running entirely on my own desktop, one ordinary job and checked everything it handed back.
Illustration: a speed gauge reading 3.05 tokens per second against the model card’s 8.5 to 9.6, and two cards. Python as generated found 0 MAC addresses, Go as generated panicked; after fixes both found the same 16 devices
The whole experiment on one card.

Colibri is a small inference engine (the program that runs a model) built for one trick: a mixture-of-experts model only uses a few of its sub-networks, called experts, for each token, so Colibri keeps the busy experts in memory and reads the rest from disk. That lets a model that would not fit in RAM run on an ordinary machine. I wanted to know what that feels like on a real task, not a benchmark.

Everything here is reproducible: the prompts, the timing script, every raw answer and every fix are in samarthya/colibri-lan-scout.

The setup
#

MachineAMD Ryzen 9 7940HS (8 cores), 28 GB RAM, integrated Radeon 780M, NVMe SSD, Windows 11
EngineColibri 2.1.0, Vulkan backend on the integrated GPU
ModelQwen3-Coder-30B-A3B, 30.5B parameters, 3.3B used per token, in Colibri’s int4 build (19 GB on disk)
Memory planChosen automatically: 42 of the 128 experts per layer held in RAM

The model has 128 experts in each of its 48 layers and uses 8 of them per token, which is why this approach works at all. Colibri’s setup did the rest: it checked the hardware, downloaded the model and wrote a configuration. coli start --background had the API answering in 11 seconds, with the process holding under 3 GB. Its log explained the plan: all 48 attention layers on the Radeon 780M, 428 frequently used experts in GPU memory, the rest in RAM or on disk.

The task
#

A standard, slightly fiddly job: a program that lists the devices on my network, with their MAC addresses and names, marking the router and this machine. Standard library only, no administrator rights, Windows, Linux and macOS. The same prompt for Python and for Go.

I chose it because it looks simple and is full of small traps: ping takes different flags on every OS, and every OS prints its neighbour table (the ARP table, which maps IP addresses to hardware addresses) differently. A model that has seen a lot of code should know these.

To measure speed honestly I wrote a small script that streams the answer and records two things: time to first token, which includes reading the prompt, and decode speed in tokens per second, which is how fast the answer scrolls.

How fast
#

RunAnswerFirst tokenSpeedTotal
Python1,658 tokens20.8 s3.05 tok/s9 min 24 s
Go, first attempt2,289 tokens11.8 s0.56 tok/s68 min 47 s
Short test straight afterwards50 tokens9.0 s3.34 tok/s24 s
Go, same prompt again2,496 tokens19.6 s2.84 tok/s14 min 59 s

About 3 tokens per second: you watch the code being typed, roughly a line every few seconds. A complete program takes ten to fifteen minutes. That is a coffee, not a conversation.

The first Go run is the interesting row. It was more than five times slower, and Go had nothing to do with it. While it ran, Colibri’s own memory grew from 3 GB to 11 GB, and free memory on the machine fell to about 2 GB. The server’s statistics showed similar disk reads in both runs but about seven times longer per expert on the CPU in the slow one, which points at the operating system squeezing the engine’s memory. A short test straight afterwards was back to 3.34 tok/s, and the identical prompt re-run gave 2.84.

Then I tried the setting the model card recommends for speed: every expert in RAM, --cap 128, which it reports at 8.5 to 9.6 tok/s with about 15–17 GB. I gave it a smaller context to make room. Partway through the answer my machine ran critically low on memory and the run was stopped. On 28 GB with a desktop running, the automatic plan is the one that fits.

The lesson on speed: with Colibri, memory is the speed dial. Leave it headroom, and do not trust any run you made while something else was eating RAM, including the engine itself as it grew.

What it wrote
#

Both answers looked professional: typed functions, a thread pool or goroutines, OS switches, help text, and a tidy list of limitations at the end. Then I ran them.

PythonGo
As generatedRan first time; found 7 devices and 0 MAC addressesCompiled, then panicked before printing anything
After my fixes16 devices, 15 MACs, 9 sThe same 16 devices, 15 MACs, 12 s

The repository keeps the model’s code as the first commit and my fixes as later ones, so git diff shows exactly what a human had to change. Three bugs are worth telling.

1. The parser that only read the header. Python’s Windows ARP parser kept lines starting with Internet. In arp -a output the only such line is the column header, Internet Address Physical Address Type. Every MAC came back empty. On Linux the same function took the third word as the MAC address, and the third word is at. The Go version had the same idea from the other side: it assumed Windows prints IP Type MAC (it prints IP MAC Type), indexed a field without checking the line was long enough, and crashed with index out of range [2] with length 2.

2. The program that would have scanned the wrong network. Go found “this machine” by taking the first non-loopback address from the network interfaces. I checked on my machine: that is a second adapter, not the one that carries traffic. Even without the crash, it would have swept a /24 with nothing useful on it. The Python version, written from the same prompt, used the reliable method of asking the OS which address it would route from.

3. The data race. Go looked up names in parallel and wrote every answer into one ordinary map from many goroutines at once. Go’s own FAQ is blunt about this: “uncontrolled map access can crash the program”. The fix is a mutex. I could not run Go’s race detector, which needs a C toolchain, so that one is fixed from reading the code.

There was also plenty of smaller stuff: addresses sorted as text (so .10 came before .2), a 100 ms default ping timeout, a Linux timeout that rounded to zero, hand-written flag parsing, and a context that was created and never used. The first Go answer, from the slow run, did not even compile: it imported a package it never used, which Go refuses.

Two things surprised me. The same prompt at temperature 0 produced two quite different Go programs, so “set the temperature to zero” does not make this repeatable. And each answer ended with a list of eight limitations, each reasonable in general. The closest to the truth, Python’s “the ARP table parsing is simplified and may not work correctly on all systems”, was right, except that it failed on every system, including the one it was written for.

What no fix could solve: names
#

After the fixes, both programs found 16 devices. Only one had a name, and it was my own machine. Reverse DNS returns a name only if your router publishes one, and mine publishes none. Between 6 and 8 of the 16 did not answer ping either, depending on the run: phones and firewalled PCs ignore it, and the fixed versions find them in the ARP table instead.

The names really live in the router’s list of connected devices, behind a login. The model’s code was not wrong about this; the task, as I wrote it, asked for something the network does not give away for free.

Would I use it again?
#

ForAgainst
Runs entirely offline: my code and my network stay on my machineAbout 3 tok/s: a program takes 10–15 minutes
A 30B coding model on a machine that could never hold it in RAMSpeed depends heavily on free memory; the fast setting did not fit
Started in seconds, set up with no compilerConfident code with real bugs in both languages
An OpenAI-style API, so any tool can use itNot repeatable even at temperature 0

Yes, for a particular kind of work: a first draft of something I would review anyway, on code I do not want to send anywhere, when I can start it and come back. It is a capable, slow colleague. It is not something to type to and wait for.

What I will do differently next time: give it more memory headroom, ask for tests alongside the code, and treat its “limitations” section as decoration.

Takeaways
#

  • Colibri made a 30B coding model usable on a 28 GB desktop, at about 3 tokens per second.
  • Memory is the speed dial. The same prompt ran at 0.56 tok/s under memory pressure and 2.84 without it; keeping every expert in RAM did not fit.
  • The generated code looked right and was wrong: no MAC addresses in Python, a crash in Go, a wrong network, a data race. Every one was found only by running it.
  • Commit the model’s version first and fix in separate commits: the diff is the honest review.
  • Some problems are not code problems. Device names live in the router, not on the network.

References
#

Author
Samarthya
Engineer, learner, tinkerer. This is my trophy shelf of everyday work and leisure.

Related

Ollama or Colibri? A beginner's comparison on two of my machines

A Fuji and a Granny Smith are both apples, and you would still not bake with the one you snack on. Ollama and Colibri both run AI models on your own machine, but they were bred for different jobs. Both apples. Not the same apple. I have used Ollama for a while, and even built a small demo on it. Colibri is new to me: I installed it this week. So this is a comparison by someone who knows one tool well and is a beginner at the other, which is probably where you are too.

AI in practice, part 5: MCP, the Model Context Protocol

Before USB, every device needed its own port. Before MCP, every AI application needed its own connector for every data source. The Model Context Protocol is the common plug. The problem it solves # Part 4 showed how a model uses tools. The catch: each AI application (a chat app, an IDE assistant, an agent framework) described tools its own way. Connecting M applications to N data sources meant up to M × N custom integrations.

AI in practice, part 1: Choosing the right framework

“Which AI framework should I use?” has no single answer, because the names people throw around do completely different jobs. Put them on a map first, and the choice usually makes itself. This series picks up where my earlier posts left off. Those covered the ideas: what generative AI is, how transformers pay attention, why GPUs matter, and two hands-on builds (LangChain with Ollama and a RAG assistant). This part is the map that connects them.