Instructions to use LiquidAI/LFM3-Audio-2B-MLX-4bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use LiquidAI/LFM3-Audio-2B-MLX-4bit with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download LiquidAI/LFM3-Audio-2B-MLX-4bit --local-dir LFM3-Audio-2B-MLX-4bit
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Download modeling_liquid_codec.py from LiquidAI/LFM3-Audio-2B-MLX-4bit: direct link, hf CLI and curl.
- Browser
- Download file 17.9 kB
-
https://huggingface.co/LiquidAI/LFM3-Audio-2B-MLX-4bit/resolve/main/modeling_liquid_codec.py
- Command line
-
hf download hf://LiquidAI/LFM3-Audio-2B-MLX-4bit/modeling_liquid_codec.py
-
curl -L -o modeling_liquid_codec.py https://huggingface.co/LiquidAI/LFM3-Audio-2B-MLX-4bit/resolve/main/modeling_liquid_codec.py
17.9 kB
| # Copyright 2026 The HuggingFace Team. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import math | |
| from dataclasses import dataclass | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| from transformers import initialization as init | |
| from transformers.cache_utils import DynamicCache | |
| from transformers.conversion_mapping import register_checkpoint_conversion_mapping | |
| from transformers.core_model_loading import WeightRenaming | |
| from transformers.modeling_utils import PreTrainedAudioTokenizerBase | |
| from transformers.utils import ModelOutput, auto_docstring, can_return_tuple, torch_compilable_check | |
| from transformers.utils.import_utils import is_tracing | |
| from .configuration_liquid_codec import LiquidCodecConfig | |
| from .modeling_lfm3 import Lfm3Model | |
| class LiquidCodecEncoderOutput(ModelOutput): | |
| r""" | |
| audio_codes (`torch.LongTensor` of shape `(batch_size, num_codebooks, num_frames)`): | |
| The code of every codebook of every frame. | |
| """ | |
| audio_codes: torch.LongTensor = None | |
| class LiquidCodecDecoderOutput(ModelOutput): | |
| r""" | |
| audio_values (`torch.FloatTensor` of shape `(batch_size, num_samples)`): | |
| The decoded 24 kHz audio in float32. | |
| """ | |
| audio_values: torch.FloatTensor = None | |
| class LiquidCodecOutput(ModelOutput): | |
| r""" | |
| audio_values (`torch.FloatTensor` of shape `(batch_size, num_samples)`): | |
| The reconstructed audio, cut to the input length. | |
| audio_codes (`torch.LongTensor` of shape `(batch_size, num_codebooks, num_frames)`): | |
| The code of every codebook of every frame. | |
| """ | |
| audio_values: torch.FloatTensor = None | |
| audio_codes: torch.LongTensor = None | |
| class LiquidCodecStreamingCache: | |
| """Carries the decoder state between chunks: the LFM3 cache and the unfinished overlap-add tail.""" | |
| def __init__(self, config: LiquidCodecConfig): | |
| self.past_key_values = DynamicCache(config=config.lfm_config(config.decoder_window)) | |
| self.audio: torch.Tensor | None = None | |
| self.envelope: torch.Tensor | None = None | |
| self.started = False | |
| self.finished = False | |
| class LiquidCodecSTFT(nn.Module): | |
| def __init__(self, config: LiquidCodecConfig): | |
| super().__init__() | |
| self.n_fft = config.stft_n_fft | |
| self.hop_length = config.stft_hop_length | |
| self.win_length = config.stft_win_length | |
| self.window = nn.Buffer(torch.hann_window(self.win_length, dtype=torch.float32), persistent=False) | |
| self.num_features = 3 * (self.n_fft // 2 + 1) | |
| def forward(self, audio: torch.Tensor) -> torch.Tensor: | |
| total_pad = self.win_length - self.hop_length | |
| audio = F.pad(audio.float(), (total_pad // 2, total_pad - total_pad // 2), mode="reflect") | |
| spec = torch.stft( | |
| audio, | |
| n_fft=self.n_fft, | |
| hop_length=self.hop_length, | |
| win_length=self.win_length, | |
| window=self.window.float(), | |
| center=False, | |
| return_complex=True, | |
| ) | |
| magnitude = spec.abs() | |
| phase_denominator = magnitude.clamp_min(1e-5) | |
| return torch.cat((torch.log1p(magnitude), spec.real / phase_denominator, spec.imag / phase_denominator), dim=1) | |
| class LiquidCodecFrontEnd(nn.Module): | |
| def __init__(self, config: LiquidCodecConfig, num_features: int): | |
| super().__init__() | |
| self.frame_stack = config.stft_frame_stack | |
| self.in_proj = nn.Conv1d(num_features, config.encoder_conv_dim, kernel_size=1) | |
| self.conv = nn.Conv1d(config.encoder_conv_dim, config.encoder_conv_dim, kernel_size=3, padding=2) | |
| self.out = nn.Conv1d( | |
| config.encoder_conv_dim, config.encoder_dim, kernel_size=self.frame_stack, stride=self.frame_stack | |
| ) | |
| self.act = nn.SiLU() | |
| def forward(self, features: torch.Tensor) -> torch.Tensor: | |
| hidden_states = self.act(self.in_proj(features.to(self.in_proj.weight.dtype))) | |
| hidden_states = self.act(self.conv(hidden_states)[:, :, : hidden_states.shape[-1]]) | |
| return self.out(hidden_states) | |
| class LiquidCodecLfm(nn.Module): | |
| def __init__(self, config: LiquidCodecConfig, sliding_window: int | None = None): | |
| super().__init__() | |
| self.lfm = Lfm3Model(config.lfm_config(sliding_window)) | |
| self.lfm.register_module("embed_tokens", None) | |
| # the projections run in the model dtype but the rotary tables stay in float32; a named hook keeps the | |
| # module picklable | |
| self.lfm.rotary_emb.register_forward_pre_hook(self._rotary_in_float32) | |
| def _rotary_in_float32(module, args): | |
| return (args[0].float(), *args[1:]) | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| return self.lfm(inputs_embeds=hidden_states, use_cache=False).last_hidden_state | |
| class LiquidCodecQuantizer(nn.Module): | |
| def __init__(self, config: LiquidCodecConfig): | |
| super().__init__() | |
| levels = list(config.fsq_levels) | |
| self.num_codebooks = config.n_q | |
| self.codebook_dim = len(levels) | |
| self.levels = nn.Buffer(torch.tensor(levels, dtype=torch.int32), persistent=False) | |
| self.basis = nn.Buffer( | |
| torch.tensor([math.prod(levels[:index]) for index in range(self.codebook_dim)], dtype=torch.int32), | |
| persistent=False, | |
| ) | |
| effective_dim = self.codebook_dim * self.num_codebooks | |
| if config.quantizer_dim == effective_dim: | |
| self.project_in = nn.Identity() | |
| self.project_out = nn.Identity() | |
| else: | |
| self.project_in = nn.Linear(config.quantizer_dim, effective_dim) | |
| self.project_out = nn.Linear(effective_dim, config.quantizer_dim) | |
| def _bound(self, latents: torch.Tensor, eps: float = 1e-3) -> torch.Tensor: | |
| half_levels = (self.levels - 1) * (1 + eps) / 2 | |
| offset = torch.where(self.levels % 2 == 0, 0.5, 0.0) | |
| shift = torch.atanh(offset / half_levels) | |
| return torch.tanh(latents + shift) * half_levels - offset | |
| def encode(self, latents: torch.Tensor) -> torch.Tensor: | |
| # the quantizer runs outside `forward`, so no `device_map` hook moves its inputs (here and in `decode`) to the | |
| # device of its buffers | |
| latents = self.project_in(latents.movedim(1, -1).contiguous()).float().to(self.levels.device) | |
| if not is_tracing(latents): | |
| # finite audio far above full scale overflows the float32 STFT, and NaN latents round to INT64_MIN codes | |
| torch_compilable_check( | |
| torch.isfinite(latents), | |
| "The audio overflows the codec (non-finite latents); expected samples in [-1, 1]", | |
| ) | |
| latents = latents.reshape(*latents.shape[:-1], self.num_codebooks, self.codebook_dim) | |
| half_width = self.levels // 2 | |
| codes = self._bound(latents).round() / half_width | |
| indices = ((codes * half_width + half_width) * self.basis).sum(-1).round().long() | |
| return indices.transpose(1, 2) | |
| def decode(self, codes: torch.Tensor) -> torch.Tensor: | |
| indices = codes.to(self.basis.device).transpose(1, 2).unsqueeze(-1) | |
| half_width = self.levels // 2 | |
| latents = ((indices // self.basis) % self.levels - half_width) / half_width | |
| latents = latents.reshape(*latents.shape[:-2], -1) | |
| if isinstance(self.project_out, nn.Linear): | |
| latents = latents.to(self.project_out.weight.dtype) | |
| return self.project_out(latents).movedim(-1, 1) | |
| class LiquidCodecISTFT(nn.Module): | |
| def __init__(self, config: LiquidCodecConfig): | |
| super().__init__() | |
| self.n_fft = config.stft_n_fft | |
| self.hop_length = config.stft_hop_length | |
| self.win_length = config.stft_win_length | |
| self.window = nn.Buffer(torch.hann_window(self.win_length, dtype=torch.float32)) | |
| def forward(self, spec: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| num_frames = spec.shape[-1] | |
| frames = torch.fft.irfft(spec, self.n_fft, dim=1, norm="backward") * self.window[None, :, None] | |
| output_size = (num_frames - 1) * self.hop_length + self.win_length | |
| fold = {"output_size": (1, output_size), "kernel_size": (1, self.win_length), "stride": (1, self.hop_length)} | |
| audio = F.fold(frames, **fold)[:, 0, 0] | |
| envelope = F.fold(self.window.square().expand(1, num_frames, -1).transpose(1, 2), **fold)[0, 0, 0] | |
| return audio, envelope | |
| class LiquidCodecDecoder(nn.Module): | |
| def __init__(self, config: LiquidCodecConfig): | |
| super().__init__() | |
| self.frame_stack = config.stft_frame_stack | |
| self.lfm = LiquidCodecLfm(config, sliding_window=config.decoder_window).lfm | |
| self.lin = nn.Linear(config.encoder_dim, 2 * (config.stft_n_fft // 2 + 1)) | |
| self.istft = LiquidCodecISTFT(config) | |
| def forward( | |
| self, embeddings: torch.Tensor, past_key_values: DynamicCache | None = None | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| hidden_states = self.lfm( | |
| inputs_embeds=F.interpolate( | |
| embeddings.mT, embeddings.shape[1] * self.frame_stack, mode="nearest-exact" | |
| ).mT, | |
| past_key_values=past_key_values, | |
| use_cache=past_key_values is not None, | |
| ).last_hidden_state | |
| log_magnitude, angle = torch.chunk(self.lin(hidden_states).mT.contiguous(), 2, 1) | |
| return self.istft(torch.polar(log_magnitude.float().exp(), angle.float())) | |
| class LiquidCodecModel(PreTrainedAudioTokenizerBase): | |
| config: LiquidCodecConfig | |
| base_model_prefix = "codec" | |
| main_input_name = "input_values" | |
| # the analysis window is a non-persistent buffer cast in the forward; only the synthesis window is a weight | |
| _keep_in_fp32_modules_strict = ["istft"] | |
| _supports_flash_attn = False | |
| _supports_sdpa = True | |
| def __init__(self, config: LiquidCodecConfig): | |
| super().__init__(config) | |
| self.stft = LiquidCodecSTFT(config) | |
| self.frontend = LiquidCodecFrontEnd(config, self.stft.num_features) | |
| self.encoder = LiquidCodecLfm(config) | |
| self.to_quantizer = nn.Linear(config.encoder_dim, config.quantizer_dim) | |
| self.quantizer = LiquidCodecQuantizer(config) | |
| self.decoder = LiquidCodecDecoder(config) | |
| self.from_quantizer = nn.Linear(config.quantizer_dim, config.encoder_dim) | |
| self.post_init() | |
| def _init_weights(self, module): | |
| super()._init_weights(module) | |
| if isinstance(module, (LiquidCodecSTFT, LiquidCodecISTFT)): | |
| init.copy_(module.window, torch.hann_window(module.win_length, dtype=torch.float32)) | |
| elif isinstance(module, LiquidCodecQuantizer): | |
| levels = list(self.config.fsq_levels) | |
| init.copy_(module.levels, torch.tensor(levels, dtype=torch.int32)) | |
| init.copy_( | |
| module.basis, | |
| torch.tensor([math.prod(levels[:index]) for index in range(len(levels))], dtype=torch.int32), | |
| ) | |
| def encode(self, input_values: torch.FloatTensor, *, return_dict: bool | None = None) -> LiquidCodecEncoderOutput: | |
| r""" | |
| Non-finite audio, or audio that overflows the codec, is refused eagerly; a compiled `encode` skips the check. | |
| input_values (`torch.FloatTensor` of shape `(batch_size, num_samples)`): | |
| 24 kHz audio. The tail is zero-padded to a whole codec frame of 1920 samples. | |
| return_dict (`bool`, *optional*): | |
| Whether to return a [`~utils.ModelOutput`] instead of a plain tuple. | |
| """ | |
| if input_values.ndim != 2 or input_values.shape[-1] == 0: | |
| raise ValueError( | |
| f"Expected non-empty audio of shape (batch_size, num_samples), got {tuple(input_values.shape)}" | |
| ) | |
| if not is_tracing(input_values): | |
| # a non-finite sample would round to an INT64_MIN code that only fails far downstream; the tensor-valued | |
| # check would break a `fullgraph` compile or a CUDA-graph capture | |
| torch_compilable_check(torch.isfinite(input_values), "`input_values` must be finite") | |
| frame_size = self.config.frame_size | |
| num_samples = -(input_values.shape[-1] // -frame_size) * frame_size | |
| input_values = F.pad(input_values, (0, num_samples - input_values.shape[-1])) | |
| hidden_states = self.frontend(self.stft(input_values)).mT | |
| latents = self.to_quantizer(self.encoder(hidden_states)).mT | |
| return LiquidCodecEncoderOutput(audio_codes=self.quantizer.encode(latents)) | |
| def decode( | |
| self, | |
| audio_codes: torch.LongTensor, | |
| streaming_cache: LiquidCodecStreamingCache | None = None, | |
| finalize: bool = True, | |
| *, | |
| return_dict: bool | None = None, | |
| ) -> LiquidCodecDecoderOutput: | |
| r""" | |
| audio_codes (`torch.LongTensor` of shape `(batch_size, num_codebooks, num_frames)`): | |
| The codes to decode; every frame becomes 1920 samples. | |
| streaming_cache ([`LiquidCodecStreamingCache`], *optional*): | |
| The state carried between consecutive chunks of one stream. The chunks decode to the same audio as the | |
| concatenated codes. | |
| finalize (`bool`, *optional*, defaults to `True`): | |
| Whether this is the last chunk of the stream. Until then the trailing samples that later frames still | |
| contribute to are held back in the cache. | |
| return_dict (`bool`, *optional*): | |
| Whether to return a [`~utils.ModelOutput`] instead of a plain tuple. | |
| """ | |
| if audio_codes.ndim != 3 or audio_codes.shape[1] != self.config.n_q: | |
| raise ValueError( | |
| f"Expected codes of shape (batch_size, {self.config.n_q}, num_frames), got {tuple(audio_codes.shape)}" | |
| ) | |
| torch_compilable_check( | |
| (audio_codes >= 0) & (audio_codes < self.config.codebook_size), | |
| f"Codes must be in [0, {self.config.codebook_size})", | |
| ) | |
| if streaming_cache is not None and streaming_cache.finished: | |
| raise ValueError("This stream is finalized; start with a new cache") | |
| trim = (self.config.stft_win_length - self.config.stft_hop_length) // 2 | |
| tail = 2 * trim | |
| batch_size = audio_codes.shape[0] | |
| if not audio_codes.shape[-1] and not (finalize and streaming_cache is not None and streaming_cache.started): | |
| # no frames and no held-back tail to flush: the overlap-add state must stay untouched | |
| if streaming_cache is not None and finalize: | |
| streaming_cache.finished = True | |
| return LiquidCodecDecoderOutput(audio_values=audio_codes.new_zeros((batch_size, 0), dtype=torch.float32)) | |
| if audio_codes.shape[-1]: | |
| # `quantizer.decode` returns float32 latents when its projections are `nn.Identity` | |
| latents = self.quantizer.decode(audio_codes).mT.to(self.from_quantizer.weight.dtype) | |
| embeddings = self.from_quantizer(latents) | |
| past_key_values = None if streaming_cache is None else streaming_cache.past_key_values | |
| audio, envelope = self.decoder(embeddings, past_key_values) | |
| else: | |
| audio = self.decoder.istft.window.new_zeros(batch_size, tail) | |
| envelope = self.decoder.istft.window.new_zeros(tail) | |
| if streaming_cache is None: | |
| audio, envelope = audio[:, trim:-trim], envelope[trim:-trim] | |
| else: | |
| if streaming_cache.audio is not None: | |
| audio = torch.cat([audio[:, :tail] + streaming_cache.audio, audio[:, tail:]], dim=1) | |
| envelope = torch.cat([envelope[:tail] + streaming_cache.envelope, envelope[tail:]]) | |
| if not streaming_cache.started: | |
| audio, envelope = audio[:, trim:], envelope[trim:] | |
| streaming_cache.started = True | |
| if finalize: | |
| audio, envelope = audio[:, :-trim], envelope[:-trim] | |
| streaming_cache.audio, streaming_cache.envelope = None, None | |
| streaming_cache.finished = True | |
| else: | |
| streaming_cache.audio, streaming_cache.envelope = audio[:, -tail:], envelope[-tail:] | |
| audio, envelope = audio[:, :-tail], envelope[:-tail] | |
| return LiquidCodecDecoderOutput(audio_values=(audio / envelope).float()) | |
| def forward(self, input_values: torch.FloatTensor) -> LiquidCodecOutput: | |
| r""" | |
| input_values (`torch.FloatTensor` of shape `(batch_size, num_samples)`): | |
| 24 kHz audio. | |
| """ | |
| audio_codes = self.encode(input_values, return_dict=True).audio_codes | |
| audio = self.decode(audio_codes, return_dict=True).audio_values | |
| return LiquidCodecOutput(audio_values=audio[..., : input_values.shape[-1]], audio_codes=audio_codes) | |
| __all__ = ["LiquidCodecModel", "LiquidCodecStreamingCache"] | |
| register_checkpoint_conversion_mapping( | |
| "liquid_codec", | |
| [ | |
| WeightRenaming(source_patterns=r"^_frontend\.", target_patterns="frontend."), | |
| WeightRenaming(source_patterns=r"^quantizer\.fsq\.", target_patterns="quantizer."), | |
| ], | |
| overwrite=True, | |
| ) | |