/7 min read
why i built qenlo
by Akshat Kushwaha
it started with a pretty normal question. i was talking to a friend and asked him what vector database he uses.
"qdrant," he said.
okay, but why exactly?
"i mean, it's the best. it has good features, it works locally."
and that was it. a perfectly fine answer. but i kept thinking about it after that conversation, because i've been around vector databases for a while now. i found out about them about two years ago and i've spent at least a year and a half working with them since. at this point they show up in almost everything i build. search, memory for agents, retrieval for whatever side project i'm on that week. for me a vector database sits in the same place a normal database does. it's just there, under everything.
and for normal databases, i have a favourite. SQLite is the goat. i reach for it every single time i can. it's lightweight, it's one file, it runs inside your app instead of next to it, and it just feels like home.
yes, i know sqlite-vec exists. i've seen it. it works. but it doesn't have the coolness in it. it doesn't have that vibe. and when you're going to spend your evenings on something, the vibe is not a small thing.
so the question in my head became: why not build one?
the idea died fast#
i went down the rabbit hole, and the rabbit hole was deep.
a state of the art vector search engine isn't one clever data structure. the serious ones are years of work on approximate indexes, quantization, hand tuned kernels for every CPU instruction set, CUDA for NVIDIA, something else for AMD, something else again for Apple, and a whole zoo of drivers to keep it all running. some of the newer ideas even want you to train a neural network to learn the index, or load a pile of model weights just to decide where your vectors go.
i was looking at ten different kernels and a hundred different driver targets, alone, and i thought: ye idea shuru hone se pehle khatam ho gaya. the idea was over before it even started.
and honestly, that's usually where these things end. we all have that list of ideas we really wanted to work on and then quietly didn't. this was about to join it.
i chose not to let it.
webgpu#
the thing that saved it was webgpu.
(webgpu is a newer standard for talking to the graphics card. it was designed for browsers, so a web page can run real compute on your GPU, but it isn't stuck in the browser. through wgpu, the Rust implementation, the same code runs on Vulkan on Linux and Android, DirectX 12 on Windows and Metal on Macs and iPhones. you write your GPU program once, in a shader language called WGSL, and the library deals with whichever GPU the user actually has.)
that changed the math completely. i didn't need ten kernels and a hundred drivers. i needed one shader. the "support every GPU" problem, the one that had just killed the idea, was mostly somebody else's problem now.
the second thing that made it possible was being honest about what i was building. i wasn't going to beat qdrant at being qdrant. i didn't need billion vector approximate search across a cluster. i needed the SQLite version of a vector store: embedded, local, one process owns the data, and the results are exact.
the first commit went in on 27 august 2026. the same day i committed the first GPU experiment, a 124 line WGSL shader that does an exact, filtered search.
what qenlo actually is#
qenlo is a local, embedded vector store. you open a collection from your app, add records, and search them. every record has an ID, a vector, a user ID and a timestamp, and qenlo applies those metadata filters before it ranks anything. so "find the closest notes, but only this user's, only from last week" doesn't search everything and throw most of it away afterwards.
the design decision i care most about is that indexes are disposable and your data is not. there's one canonical store on disk with checksummed snapshots, a write-ahead log, and a generation number that says which version of the data is published. the CPU search, the GPU buffers, the optional USearch HNSW index and the optional PyTorch tensor path are all derived from that store. if any of them breaks or goes stale, you rebuild it, and the set of records that exist never changes.
the default path is plain exact search on the CPU. the GPU path is optional. that ordering is deliberate, and it's where the story got interesting.
the GPU didn't do what i expected#
the whole point of webgpu was speed, so i measured it. a lot.
the first surprise was that a faster kernel doesn't give you a faster search. before the GPU can do anything you have to filter on the host, copy data over, dispatch the work and read the results back, and for small filtered sets those fixed costs eat the win. so qenlo reports the search call's latency and the GPU's device time separately, because if you only look at one of them you end up lying to yourself.
then i tried to be clever. i fit a rule that would decide automatically, per query, whether to send it to the CPU or the GPU. before testing it i wrote down what counted as passing: on a held out set of runs it hadn't seen, the worst case couldn't be more than 25% slower than the best choice. it came out 235.7% slower. so i reverted it and shipped the simpler static routing instead. the failed run is still in the repo, with a script that checks the result, and the script exits nonzero on purpose.
the second surprise turned into a paper. i had two GPU paths that return identical results. the light one only touches the rows that pass the filter. the heavy one scans everything. the light one should obviously win. sometimes it did, and sometimes the same query, on the same machine, took up to 4.2 times longer in a fresh process, and up to 4.6 times across cloud machines with the same GPU. the heavy path stayed within 1 to 7%.
the reason turned out to be the GPU driver. the light path does so little work that the driver decides the card doesn't need to wake up properly, so it runs at low clocks. on my laptop's RTX 4050 that meant 585 MHz, when the heavy path held full boost. the efficient code was slow because it was efficient. i called the paper "the efficient kernel runs slow", ran it on the laptop, RTX 4090s on six cloud hosts and an H100, and released all the raw data with it.
i didn't expect a vector database project to teach me how GPU power governors work. but that's kind of the point. you only find this stuff by building the thing and then refusing to believe your own benchmarks.
where it is now#
qenlo is at 0.1.0-alpha.10. it has SDKs for Rust, Python and TypeScript on crates.io, PyPI and npm, preview SDKs for Go, Kotlin and Swift, and a terminal UI for poking at a collection. every evidence file in the research folder is hashed, 1,833 of them, and CI checks the hashes on every push so a number can't quietly change after i've quoted it.
it's also alpha, and i want to be clear about the limits. no automatic CPU/GPU router has passed a held out test yet. my CPU numbers don't tell you anything about how optimized CPU libraries perform. GPU support depends on your driver. mobile packaging isn't finished. and a collection is a small folder rather than a single file, so it isn't quite SQLite yet, even if that's the feeling i'm after.
it's not a qdrant replacement either. if you need replication, a hosted service, or billions of vectors, use qdrant. my friend's answer was correct.
it's just that the next time someone asks me what vector database i use, i'd like to answer with one i understand all the way down to the clock speed.
drafted with an LLM, then reviewed and edited by me before it went up.
0 responses
leave something useful, or usefully sharp.