← All posts
Release

0.1.x in ten days: what fourteen versions built

From 0.1.0 to 0.1.14 in ten days — the foundation-laying sprint that cut memory in half, shipped 26 models, landed on eleven platform targets, and closed every idle-unload gap before moving on to 0.2.

0.1.x in ten days: what fourteen versions built

OpenASR 0.1.0 shipped ten days ago as a macOS-only app with a small model catalog and a promise: audio transcribed on your own machine, no exceptions. Fourteen versions later, 0.1.14 closes the series. The work between them was not dramatic — it was the kind of unglamorous foundation-laying that makes the dramatic stuff possible later. Here is what the sprint actually built, measured against what it set out to fix.

Memory: halve it

The single largest resource complaint in 0.1.0 was resident memory. A daemon that loaded both the offline and streaming paths for a model like qwen3-asr-0.6b held roughly 2.8 GiB of weights — twice, because the two stacks each brought their own executor. By 0.1.13, offline and streaming dispatch share one process-wide executor instance. Same model, same weights, one copy. Measured end to end: ~1.8 GiB, about 37% lower.

That was the big line item, but smaller cuts compounded. The ggml scheduler allocator is now reused across FireRed, Dolphin, and Moonshine graphs instead of allocating per-graph. The decode runtime cache is bounded. The logits-head cache is bounded. The capability probe caches parsed pack metadata instead of re-parsing every call (p50 down ~22%). None of these is a headline on its own; together they turned the daemon from something that grew into something that fits.

Idle unload: make it actually work

The 0.1.0 daemon loaded a model and held it forever. The design intent was always to unload after idle — free RAM when nobody is dictating — but the implementation had gaps. In 0.1.12, idle_unload got a default (10 minutes) and immediately exposed the gaps: Qwen3-ASR’s thread-local decoder cache survived unload, the streaming warm-up path did not re-warm after eviction, and the X-ASR pooled runtimes were never evicted at all.

Each gap was a version. By 0.1.14 the idle-unload cycle is verified end to end: a real unload frees ~765 MiB, the next request reloads at normal cold-load cost with no extra penalty, and a per-attach watchdog ensures a hung decode worker cannot wedge the unload timer or queue later sessions behind it. If a worker is abandoned, the daemon counts it and fails loud after repeated abandonments. The path from “feature exists” to “feature works everywhere it claims to” was five patch releases.

Twenty-six models, one pull chain

The 0.1.0 catalog was deliberately small. Over the series, model families grew to cover Whisper, Qwen3-ASR, Cohere, Dolphin, Moonshine, SenseVoice, X-ASR, FireRed, and several CTC families — twenty-six public packs in total. FireRedPunc brought Chinese punctuation restoration wired into both batch and streaming paths. Dolphin gained a size-generalized runtime that unlocked dolphin-cn-dialect-base. Every pack arrives through the same signed-catalog pull chain: explicit openasr pull, sha256-verified, no silent downloads.

A real-model CI regression net now transcribes every supported family against golden references on every commit. If accuracy regresses, the gate fails before release. That net did not exist in 0.1.0.

Eleven targets, one core

The first release shipped macOS arm64. By the end of 0.1.x, prebuilt binaries cover macOS arm64 and Intel, Linux x86_64 and arm64 (glibc and musl), and Windows x86_64 with CPU, Vulkan, CUDA, and AMD HIP/ROCm variants — eleven platform/GPU combinations. The desktop app followed: today’s Windows launch is its own post.

Release archives now carry SLSA build-provenance attestations (gh attestation verify), and the self-signed TLS identity persists across daemon restarts so a paired remote client’s TOFU pin survives a model switch.

The boring parts that matter

Some changes resist a narrative thread but carry real weight. The safetensors import parser — shared by fourteen model families — was hardened against crafted headers, duplicate keys, and offset/size mismatches. Five duplicated f16-to-f32 decode routines were consolidated into one correct implementation after two of five were found to mishandle subnormals. Three separate finalize call chains in native_transcribe collapsed into one. The verbose_json response now carries duration, segment IDs, and a words array with a stricter error envelope, matching the OpenAI contract more closely.

None of this is exciting. All of it is the kind of thing that, if wrong, makes exciting features unreliable.

0.1 did its job

The 0.1.x series was not about features — it was about making the existing features trustworthy. Memory is bounded. Idle unload works. The pull chain is tested against every family. The daemon fails loud instead of failing silent. The platform matrix went from one to eleven.

That is the foundation 0.2 gets to build on. What comes next is a different shape of work, and it starts from ground that has been walked over fourteen times.