voice_audio_multi/include/engine/community_models/lfm2_audio/assets.h
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

165 lines
6.3 KiB
C++

#pragma once
// LFM2.5-Audio assets. The model ships as llama.cpp-format GGUF components in
// one directory: the LFM2 backbone with its text tokenizer, an mmproj file
// with the FastConformer encoder and the audio adapter, and for speech output
// a vocoder file (depthformer) and a tokenizer file (audio detokenizer). The
// session picks each component through a session option, so quantizations can
// be mixed without renaming the published files.
//
// Reference implementation: liquid-audio v1.3.0,
// https://github.com/Liquid4All/liquid-audio/tree/v1.3.0. File paths in the
// lfm2_audio comments are relative to its src/liquid_audio/.
#include "engine/framework/assets/tensor_source.h"
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
namespace engine::community_models::lfm2_audio {
// LFM2 hybrid backbone. A layer with zero KV heads is a gated short-conv
// block; the others are GQA attention blocks.
struct Lfm2BackboneConfig {
int64_t vocab_size = 0;
int64_t hidden_size = 0;
int64_t intermediate_size = 0;
int64_t num_attention_heads = 0;
int64_t head_dim = 0;
int64_t conv_kernel_size = 0;
int64_t context_length = 0;
std::vector<int64_t> kv_heads; // one entry per layer
float rms_norm_eps = 1e-5f;
float rope_theta = 1e6f;
[[nodiscard]] int64_t num_layers() const noexcept { return static_cast<int64_t>(kv_heads.size()); }
[[nodiscard]] bool is_attention_layer(int64_t layer) const { return kv_heads.at(static_cast<size_t>(layer)) > 0; }
};
// NeMo FastConformer encoder (dw_striding x8) followed by the audio adapter
// MLP that maps encoder frames to backbone embeddings.
struct Lfm2FastConformerEncoderConfig {
int64_t n_mels = 0;
int64_t hidden_size = 0;
int64_t num_layers = 0;
int64_t num_heads = 0;
int64_t intermediate_size = 0;
int64_t conv_kernel_size = 0;
int64_t subsampling_channels = 0;
int64_t adapter_hidden_size = 0;
int64_t output_size = 0;
float layer_norm_eps = 1e-5f;
};
// The depthformer in the vocoder GGUF: from the backbone's hidden state it
// predicts the codes of one audio frame, one codebook after another
// (LFM2AudioModel._sample_audio_frame, model/lfm2_audio.py). Its blocks are
// pre-norm GQA attention with QK-norm and interleaved RoPE, then SwiGLU, with
// no final norm (RawLMBackbone(has_embedding=False), model/transformer.py).
struct Lfm2DepthformerConfig {
int64_t input_size = 0; // the backbone hidden size
int64_t hidden_size = 0;
int64_t num_layers = 0;
int64_t num_heads = 0;
int64_t num_kv_heads = 0;
int64_t head_dim = 0;
int64_t intermediate_size = 0;
int64_t codebooks = 0;
int64_t audio_vocab_size = 0; // the codes plus end-of-audio, which is the last
float rms_norm_eps = 1e-5f;
float rope_theta = 1e6f;
[[nodiscard]] int32_t end_of_audio() const noexcept { return static_cast<int32_t>(audio_vocab_size - 1); }
};
// The audio detokenizer (detokenizer.py): the mean of the frame's code
// embeddings, repeated `upsample` times, an LFM2 hybrid with causal
// sliding-window attention, and a linear head to log-magnitude and phase for
// an ISTFT.
struct Lfm2DetokenizerConfig {
Lfm2BackboneConfig lfm; // vocab_size unused
int64_t sliding_window = 0;
int64_t output_size = 0;
int64_t codebooks = 0;
int64_t codebook_size = 0;
int64_t upsample = 0;
int64_t n_fft = 0;
int64_t hop_length = 0;
int sample_rate = 0;
};
struct Lfm2TextVocabulary {
std::vector<std::string> tokens;
std::vector<std::string> merges;
std::vector<int32_t> token_types;
std::string pre_tokenizer;
};
// The model root and whatever the loader can check without choosing a
// component: every file the session could select lives under model_root.
struct Lfm2AudioAssets {
std::filesystem::path model_root;
// Set when --model named a backbone GGUF file instead of a directory.
std::string default_model_gguf;
};
// The components chosen for one session.
struct Lfm2AudioComponents {
std::filesystem::path model_path;
std::filesystem::path mmproj_path;
std::shared_ptr<const assets::TensorSource> model;
std::shared_ptr<const assets::TensorSource> mmproj;
Lfm2BackboneConfig backbone;
Lfm2FastConformerEncoderConfig encoder;
Lfm2TextVocabulary vocabulary;
std::vector<std::string> languages;
};
std::shared_ptr<const Lfm2AudioAssets> load_lfm2_audio_assets(const std::filesystem::path & model_path);
// Whether `model_path` (a directory or a GGUF file) holds any LFM2-Audio
// component: an LFM2 backbone or an lfm2a mmproj GGUF, complete or not. The
// loader claims such paths so that load_lfm2_audio_assets can say what is
// missing. Never throws.
bool has_lfm2_audio_component(const std::filesystem::path & model_path);
// Resolves the backbone and mmproj GGUFs. An empty name means the default:
// the only backbone GGUF in the model root, and "mmproj-<backbone file>" or
// else the only mmproj GGUF.
std::shared_ptr<const Lfm2AudioComponents> load_lfm2_audio_components(
const Lfm2AudioAssets & assets,
const std::string & model_gguf,
const std::string & mmproj_gguf);
// Interleaved generation alternates blocks of this many text tokens and
// audio frames.
struct Lfm2InterleaveConfig {
int64_t text_steps = 0;
int64_t audio_steps = 0;
};
// The components speech output adds: the vocoder GGUF (depthformer and the
// detokenizer's code embedding) and the detokenizer GGUF. The backbone's
// audio-frame input embedding stays in the mmproj file.
struct Lfm2AudioOutputComponents {
std::filesystem::path vocoder_path;
std::filesystem::path detokenizer_path;
std::shared_ptr<const assets::TensorSource> vocoder;
std::shared_ptr<const assets::TensorSource> detokenizer;
Lfm2DepthformerConfig depthformer;
Lfm2DetokenizerConfig detokenizer_config;
Lfm2InterleaveConfig interleave;
};
// An empty name means "vocoder-<backbone file>" / "tokenizer-<backbone file>",
// or else the only vocoder- / tokenizer- GGUF in the model root.
std::shared_ptr<const Lfm2AudioOutputComponents> load_lfm2_audio_output_components(
const Lfm2AudioAssets & assets,
const Lfm2AudioComponents & components,
const std::string & vocoder_gguf,
const std::string & detokenizer_gguf);
} // namespace engine::community_models::lfm2_audio