Views
No views yet

| Decoder | Vocoder | Size | Platform | Use Case |
|---|---|---|---|---|
| Q4 | Q4 | ~1.5GB | WebGPU, Server | Recommended for most uses |
| FP16 | FP16 | ~3.2GB | Server | Higher quality |
onnx/
├── decoder.onnx # LFM2 backbone (FP32)
├── decoder.onnx_data*
├── decoder_fp16.onnx # LFM2 backbone (FP16)
├── decoder_fp16.onnx_data*
├── decoder_q4.onnx # LFM2 backbone (Q4, recommended)
├── decoder_q4.onnx_data
├── audio_encoder.onnx # Conformer encoder for ASR (FP32)
├── audio_encoder.onnx_data
├── audio_encoder_fp16.onnx # Conformer encoder (FP16)
├── audio_encoder_fp16.onnx_data
├── audio_encoder_q4.onnx # Conformer encoder (Q4)
├── audio_encoder_q4.onnx_data
├── audio_embedding.onnx # Audio code embeddings (FP32)
├── audio_embedding_fp16.onnx # Audio code embeddings (FP16)
├── audio_embedding_q4.onnx # Audio code embeddings (Q4)
├── audio_detokenizer.onnx # Neural vocoder STFT (FP32)
├── audio_detokenizer.onnx_data
├── audio_detokenizer_fp16.onnx # Neural vocoder (FP16)
├── audio_detokenizer_fp16.onnx_data
├── audio_detokenizer_q4.onnx # Neural vocoder (Q4)
├── audio_detokenizer_q4.onnx_data
├── vocoder_depthformer.onnx # Audio codebook prediction (FP32)
├── vocoder_depthformer.onnx_data
├── vocoder_depthformer_fp16.onnx # Audio codebook prediction (FP16)
├── vocoder_depthformer_fp16.onnx_data
├── vocoder_depthformer_q4.onnx # Audio codebook prediction (Q4)
├── vocoder_depthformer_q4.onnx_data
├── embed_tokens.bin # Text embeddings (binary)
├── embed_tokens.json # Text embeddings metadata
├── audio_embedding.bin # Audio embeddings (binary, for direct lookup)
├── audio_embedding.json # Audio embeddings metadata
└── mel_config.json # Mel spectrogram configuration
* Large models (>2GB) split weights across multiple files:
decoder.onnx_data, decoder.onnx_data_1, decoder.onnx_data_2, etc.
All data files must be in the same directory as the .onnx file.1git clone https://github.com/Liquid4All/onnx-export.git
2cd onnx-export
3uv sync1uv run lfm2-audio-infer /path/to/LFM2.5-Audio-1.5B-ONNX \
2 --mode asr \
3 --audio input.wav \
4 --precision q41uv run lfm2-audio-infer /path/to/LFM2.5-Audio-1.5B-ONNX \
2 --mode tts \
3 --prompt "Hello, this is a test of text to speech synthesis." \
4 --output output.wav \
5 --precision q4--system "Perform TTS. Use the UK female voice." - Custom system prompt--audio-temperature 0.8 - Audio sampling temperature--audio-top-k 64 - Top-k sampling for audio1uv run lfm2-audio-infer /path/to/LFM2.5-Audio-1.5B-ONNX \
2 --mode interleaved \
3 --audio input.wav \
4 --output output.wav \
5 --precision q41uv run lfm2-audio-infer /path/to/LFM2.5-Audio-1.5B-ONNX \
2 --mode interleaved \
3 --prompt "Respond with audio" \
4 --output output.wav \
5 --precision q4uv run lfm2-audio-infer --help| Option | Description |
|---|---|
--mode | asr, tts, or interleaved |
--precision | fp16, q4, or q8 (default: fp32) |
--audio | Input audio file (WAV) |
--output | Output audio file (WAV) |
--prompt | Text prompt |
--system | System prompt |
--max-tokens | Maximum tokens to generate |
--temperature | Text sampling temperature |
--audio-temperature | Audio sampling temperature |
--audio-top-k | Top-k sampling for audio |
--seed | Random seed for reproducibility |
npm install onnxruntime-web @huggingface/transformerschrome://flags/#enable-unsafe-webgpu, enable, and restartchrome://gpu for "WebGPU" statusnavigator.gpu.requestAdapter() in DevTools console1import * as ort from "onnxruntime-web/webgpu";
2import { AutoTokenizer } from "@huggingface/transformers";
3
4// Check WebGPU availability
5if (!navigator.gpu) {
6 throw new Error("WebGPU not available. Enable at chrome://flags/#enable-unsafe-webgpu");
7}
8
9ort.env.wasm.numThreads = 1;
10
11const modelId = "LiquidAI/LFM2.5-Audio-1.5B-ONNX";
12const modelBase = `https://huggingface.co/${modelId}/resolve/main`;
13
14// Load tokenizer
15const tokenizer = await AutoTokenizer.from_pretrained(modelId);
16
17// Load ONNX sessions
18async function loadSession(name, dataFiles = 1) {
19 const onnxPath = `${modelBase}/onnx/${name}.onnx`;
20 const externalData = [];
21 for (let i = 0; i < dataFiles; i++) {
22 const suffix = i === 0 ? "" : `_${i}`;
23 const fileName = `${name}.onnx_data${suffix}`;
24 externalData.push({ path: fileName, data: `${modelBase}/onnx/${fileName}` });
25 }
26 return ort.InferenceSession.create(onnxPath, {
27 executionProviders: ["webgpu"],
28 externalData,
29 });
30}
31
32// Load models (Q4 recommended for WebGPU)
33const decoder = await loadSession("decoder_q4");
34const audioEmbedding = await loadSession("audio_embedding_q4");
35const detokenizer = await loadSession("audio_detokenizer_q4");
36const depthformer = await loadSession("vocoder_depthformer_q4");
37
38// Load text embeddings binary
39const embedResponse = await fetch(`${modelBase}/onnx/embed_tokens.bin`);
40const embedBuffer = await embedResponse.arrayBuffer();
41const embedMetaResponse = await fetch(`${modelBase}/onnx/embed_tokens.json`);
42const embedMeta = await embedMetaResponse.json();
43const embedWeight = new Float32Array(embedBuffer);
44
45function getTextEmbeddings(ids) {
46 const hiddenSize = embedMeta.hidden_size;
47 const embeds = new Float32Array(ids.length * hiddenSize);
48 for (let i = 0; i < ids.length; i++) {
49 const offset = ids[i] * hiddenSize;
50 embeds.set(embedWeight.subarray(offset, offset + hiddenSize), i * hiddenSize);
51 }
52 return new ort.Tensor("float32", embeds, [1, ids.length, hiddenSize]);
53}
54
55// Model config
56const hiddenSize = 2048;
57const numCodebooks = 8;
58const codebookVocab = 2049;
59
60// TTS example
61const text = "Hello, this is a test.";
62const prompt = `<|startoftext|><|im_start|>system
63Perform TTS. Use the UK female voice.<|im_end|>
64<|im_start|>user
65${text}<|im_end|>
66<|im_start|>assistant
67`;
68
69const inputIds = tokenizer.encode(prompt);
70let embeds = getTextEmbeddings(inputIds);
71
72// Initialize KV cache
73const cache = {};
74for (const name of decoder.inputNames) {
75 if (name.startsWith("past_conv")) {
76 cache[name] = new ort.Tensor("float32", new Float32Array(hiddenSize * 3), [1, hiddenSize, 3]);
77 } else if (name.startsWith("past_key_values")) {
78 cache[name] = new ort.Tensor("float32", new Float32Array(0), [1, 8, 0, 64]);
79 }
80}
81
82// Generation loop
83const audioCodes = [];
84let inAudioMode = false;
85let curLen = inputIds.length;
86
87for (let step = 0; step < 1024; step++) {
88 const attentionMask = new ort.Tensor("int64", new BigInt64Array(curLen).fill(1n), [1, curLen]);
89 const outputs = await decoder.run({ inputs_embeds: embeds, attention_mask: attentionMask, ...cache });
90
91 // Update cache
92 for (const [name, tensor] of Object.entries(outputs)) {
93 if (name.startsWith("present_conv")) {
94 cache[name.replace("present_conv", "past_conv")] = tensor;
95 } else if (name.startsWith("present.")) {
96 cache[name.replace("present.", "past_key_values.")] = tensor;
97 }
98 }
99
100 if (inAudioMode) {
101 // Use depthformer to generate audio codes
102 const hiddenStates = outputs.hidden_states;
103 const lastHidden = /* extract last position */;
104
105 // Autoregressive codebook generation (8 steps per frame)
106 const frameCodes = await generateAudioFrame(depthformer, lastHidden);
107
108 if (frameCodes[0] === 2048) {
109 // End of audio
110 break;
111 }
112
113 audioCodes.push(frameCodes);
114
115 // Get audio embeddings for feedback
116 const audioTokens = frameCodes.map((code, cb) => cb * codebookVocab + code);
117 const audioEmbedsResult = await audioEmbedding.run({
118 audio_codes: new ort.Tensor("int64", new BigInt64Array(audioTokens.map(BigInt)), [1, 8])
119 });
120 // Sum embeddings across codebooks
121 embeds = sumEmbeddings(audioEmbedsResult.audio_embeds);
122 } else {
123 // Text generation
124 const logits = outputs.logits;
125 const nextToken = argmax(logits);
126
127 if (nextToken === 128) {
128 // <|audio_start|> - switch to audio mode
129 inAudioMode = true;
130 }
131
132 embeds = getTextEmbeddings([nextToken]);
133 }
134
135 curLen++;
136}
137
138// Decode audio codes to waveform using detokenizer + ISTFT
139const waveform = await decodeAudio(detokenizer, audioCodes);