Recursive AI

Load a prepared speech model offline in C#

OneSpeech is a free, open-source speech recognition library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Prepare model files once, then load them locally without downloads.

Terminal
dotnet add package RecursiveAI.OneSpeech

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneSpeech.Catalog;
using OneSpeech.Moonshine;
 
// Prepare once with the full program before disconnecting.
var store = new ModelStore(source: new NoNetwork());
var resolved = await store.PullAsync("moonshine-ai/moonshine-tiny");
using var recognizer = MoonshineRecognizer.Load(resolved);
Console.WriteLine("Loaded prepared model.");
 
sealed class NoNetwork : ModelSource
{
    public override ValueTask<Stream> OpenReadAsync(
        CatalogEntry model, CatalogFile file, CancellationToken cancellationToken)
        => throw new IOException("Model is not prepared: " + file.Name);
}

Preparation downloads about 0.11 GB of Moonshine tiny publisher files on first use and caches them and locally converted graphs in ~/.cache/onespeech/models on Linux (change the cache location); the offline snippet refuses downloads.

Text
Loaded prepared model.

Unzip the example, then dotnet run.

Full program

The zip includes sample.flac, the unmodified mono 16 kHz LibriSpeech test-clean utterance 1089-134686-0001: “Stuff it into you, his belly counseled him.” Attribution: Vassil Panayotov, Guoguo Chen, Daniel Povey and Sanjeev Khudanpur (2015), CC BY 4.0. Recognition output can differ from that reference.

The full program prepares once unless --offline is passed, then resolves with missing-file requests refused before calling Load. Keep the prepared model cache for the offline run.

Preparation downloads about 0.11 GB of Moonshine tiny publisher files on first use and caches them and locally converted graphs in ~/.cache/onespeech/models on Linux (change the cache location); the offline snippet refuses downloads.

C#
using OneSpeech;
using OneSpeech.Audio;
using OneSpeech.Catalog;
using OneSpeech.Moonshine;
using OneSpeech.Vad;
using OneSpeech.Pipeline;
 
if (!args.Contains("--offline"))
    await ModelStore.Default.PullAsync("moonshine-ai/moonshine-tiny");
var store = new ModelStore(source: new NoNetwork());
var resolved = await store.PullAsync("moonshine-ai/moonshine-tiny");
// Preparation ends here. Load opens resolved local files without network access.
using var recognizer = MoonshineRecognizer.Load(resolved);
using var vad = new SileroVad();
var pipeline = new VadTranscriptionPipeline(recognizer, vad);
Console.WriteLine(pipeline.Transcribe("sample.flac", "en").Text);
 
sealed class NoNetwork : ModelSource
{
    public override ValueTask<Stream> OpenReadAsync(
        CatalogEntry model, CatalogFile file, CancellationToken cancellationToken)
        => throw new IOException("Model is not prepared: " + file.Name);
}

Output:

Text
Duff it into you, his belly counseled him.

When a required source file is missing in offline mode, NoNetwork throws IOException instead of downloading it.

Variations

Skip preparation when offline

After the first successful run, reuse the same cache and run:

Terminal
dotnet run -- --offline
Text
Duff it into you, his belly counseled him.

The short answer

Use ModelStore.PullAsync during preparation to obtain a ResolvedModel, then call MoonshineRecognizer.Load(resolved) to open those files. The canonical Load(ResolvedModel, ...) path does not use the network. The full program additionally supplies a ModelSource that refuses missing files, so an offline run fails instead of fetching a replacement.

How do I load a local speech model without a network in C#?

Separate model acquisition from model loading. LoadAsync is convenient when an application is allowed to pull missing publisher files, verify them and convert them locally. Use Load(resolved), not LoadAsync, when you must not download. The synchronous canonical loader takes the directory and provenance together in a ResolvedModel.

The snippet at the top resolves an already-prepared model through a source that refuses missing-file downloads, then opens it locally. Run the full program once while connected before using that snippet offline. The full program makes the boundary explicit: it prepares with the default store, resolves again through a store backed by NoNetwork, and loads that result. The second store can read prepared files, but its custom source throws if any required source file is absent. Its successful result is not a guessed directory or a manually reconstructed cache filename.

Run after disconnecting

Run the example once with access to the publisher, then keep the prepared cache. On later runs, use the --offline variation to skip preparation. Restore the .NET project before disconnecting too: NuGet package acquisition is a separate step from the model loader. The zip includes source and sample audio, not the model weights or an installed .NET runtime.

The sample’s offline run used the prepared Moonshine tiny model and printed the actual recognition text shown above: the model mishears “Stuff” as “Duff.” The custom source was configured to refuse all missing-file requests. This demonstrates the library’s model-fetch boundary; it does not claim that arbitrary code elsewhere in your application cannot open a network connection.

Preserve verified files and provenance

ModelStore verifies present files on the first open for each new store. Local conversion is part of preparation, not permission to substitute an arbitrary graph for a catalog checkpoint. Copying a single weight file into a directory with a plausible name is not equivalent to keeping a prepared model. Preserve the publisher and runtime files needed by that catalog entry, and obtain the resolved directory/provenance pair through the store.

If you use a nondefault cache location, configure both preparation and offline resolution to use it. An empty cache is not a fallback request in the offline example: it becomes an I/O failure from NoNetwork. A corrupt cached file is reported through the integrity contract rather than silently accepted as an offline model.

Load from your own folder

Your own cache folder is supported: pass its path to ModelStore during both preparation and offline resolution, as shown in the cache directory recipe. Defaults are ~/.cache/onespeech/models on Linux (or $XDG_CACHE_HOME/onespeech/models when set), ~/Library/Caches/OneSpeech/models on macOS, and LocalApplicationData/OneSpeech/models on Windows. The pinned loading docs also retain string-directory overloads for manually converted or custom graphs; those require the documented family graph format, not arbitrary downloaded weights. Use the store’s ResolvedModel for normal catalog checkpoints.

Model access rules still apply

The example uses an ungated English checkpoint. A local cache does not imply access rights for every model family. Gated catalog entries still require explicit authorization, and the store checks credentials even for cached gated files. Do not use the prepared-cache workflow to bypass publisher access conditions or redistribute restricted model graphs.

For the canonical loader pair and ownership rules, see loading and composing roles. The offline speech recognition recipe covers a different deployment choice: restoring packaged weights beside the application rather than resolving an already-prepared catalog cache.

All onespeech recipes