voice_audio_multi/include/engine/community_models/lfm2_audio/session.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

248 lines
11 KiB
C++

#pragma once
// LFM2.5-Audio sessions. ASR: audio -> FastConformer -> adapter -> LFM2
// backbone, prompted with the ASR system prompt from liquid-audio's README
// and decoded greedily like LFM2AudioModel.generate_sequential
// (model/lfm2_audio.py). TTS: text -> backbone -> depthformer, one audio
// frame per step, -> detokenizer -> 24 kHz audio. S2S: audio in as for ASR,
// a reply of text and audio out as for TTS, interleaved, after the earlier
// turns of the conversation that the request carries (chat.h).
#include "engine/community_models/lfm2_audio/asr_inputs.h"
#include "engine/community_models/lfm2_audio/assets.h"
#include "engine/community_models/lfm2_audio/audio_encoder.h"
#include "engine/community_models/lfm2_audio/backbone.h"
#include "engine/community_models/lfm2_audio/chat.h"
#include "engine/community_models/lfm2_audio/depthformer.h"
#include "engine/community_models/lfm2_audio/detokenizer.h"
#include "engine/community_models/lfm2_audio/interleaved.h"
#include "engine/community_models/lfm2_audio/tokenizer.h"
#include "engine/community_models/lfm2_audio/tts.h"
#include "engine/framework/text/chunking.h"
#include "engine/framework/model_spec/metadata.h"
#include "engine/framework/runtime/model.h"
#include "engine/framework/runtime/session_base.h"
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace engine::community_models::lfm2_audio {
std::shared_ptr<runtime::IVoiceModelLoader> make_lfm2_audio_loader();
class Lfm2AudioSession final : public runtime::RuntimeSessionBase, public runtime::IOfflineVoiceTaskSession {
public:
Lfm2AudioSession(
runtime::TaskSpec task,
runtime::SessionOptions options,
std::shared_ptr<const Lfm2AudioAssets> assets,
std::shared_ptr<const engine::model_spec::ModelContract> contract);
~Lfm2AudioSession() override;
std::string family() const override;
runtime::VoiceTaskKind task_kind() const override;
runtime::RunMode run_mode() const override;
void prepare(const runtime::SessionPreparationRequest & request) override;
runtime::TaskResult run(const runtime::TaskRequest & request) override;
// Whether the last run() cut a chunk's transcript off at max_tokens.
[[nodiscard]] bool reached_max_tokens() const;
private:
struct RequestOptions {
int64_t max_tokens = 512;
};
RequestOptions parse_request_options(const runtime::TaskRequest & request) const;
std::vector<runtime::TimeSpan> plan_chunks(const runtime::TaskRequest & request, const std::vector<float> & samples);
runtime::IOfflineVoiceTaskSession & vad_session();
std::string transcribe(const std::vector<float> & samples, const runtime::TimeSpan & span, const RequestOptions & options);
runtime::TaskSpec task_;
std::shared_ptr<const Lfm2AudioAssets> assets_;
std::shared_ptr<const engine::model_spec::ModelContract> contract_;
bool cpu_repack_; // lfm2_audio.cpu_repack, for the runtimes below
std::shared_ptr<const Lfm2AudioComponents> components_;
Lfm2TextTokenizer tokenizer_;
Lfm2AudioFeatureExtractor features_;
Lfm2FastConformerEncoderRuntime encoder_;
Lfm2BackboneRuntime backbone_;
std::string language_;
Lfm2AsrPrompt prompt_;
std::filesystem::path vad_model_path_;
double max_pass_seconds_;
std::unique_ptr<runtime::ILoadedVoiceModel> vad_model_;
std::unique_ptr<runtime::IOfflineVoiceTaskSession> vad_session_;
bool reached_max_tokens_ = false;
};
// Streaming TTS pulls events: each carries the audio of the next
// stream_frames_per_event frames, decoded as they come, the way liquid-audio's
// demo decodes each frame; together they are the offline speech.
class Lfm2AudioTtsSession final : public runtime::RuntimeSessionBase,
public runtime::IOfflineVoiceTaskSession,
public runtime::IStreamingVoiceTaskSession {
public:
Lfm2AudioTtsSession(
runtime::TaskSpec task,
runtime::SessionOptions options,
std::shared_ptr<const Lfm2AudioAssets> assets,
std::shared_ptr<const engine::model_spec::ModelContract> contract);
~Lfm2AudioTtsSession() override;
std::string family() const override;
runtime::VoiceTaskKind task_kind() const override;
runtime::RunMode run_mode() const override;
void prepare(const runtime::SessionPreparationRequest & request) override;
runtime::TaskResult run(const runtime::TaskRequest & request) override;
runtime::StreamingPolicy streaming_policy() const override;
void start_stream(const runtime::TaskRequest & request) override;
std::optional<runtime::StreamEvent> next_stream_event() override;
void set_stream_event_sink(runtime::StreamEventCallback sink) override;
runtime::TaskResult finish_stream() override;
void reset() override;
runtime::StreamEvent process_audio_chunk(const runtime::AudioChunk & chunk) override;
runtime::TaskResult finalize() override;
// Whether max_tokens cut a text chunk's speech off in the last run(), or
// in the stream since start_stream().
[[nodiscard]] bool reached_max_tokens() const;
private:
struct RequestOptions {
std::string system_prompt;
Lfm2SpeechOptions speech;
int64_t text_chunk_size = 0;
text::TextChunkMode text_chunk_mode = text::TextChunkMode::Default;
int64_t stream_frames_per_event = 0;
uint64_t seed = 0;
std::vector<std::string> texts; // the text chunks, one turn each
};
struct Stream;
RequestOptions parse_request(const runtime::TaskRequest & request) const;
std::unique_ptr<Lfm2SpeechGenerator> start_turn(const RequestOptions & options, size_t turn);
std::vector<float> speak(const RequestOptions & options, size_t turn);
runtime::TaskSpec task_;
std::shared_ptr<const Lfm2AudioAssets> assets_;
std::shared_ptr<const engine::model_spec::ModelContract> contract_;
bool cpu_repack_; // lfm2_audio.cpu_repack, for the runtimes below
std::shared_ptr<const Lfm2AudioComponents> components_;
std::shared_ptr<const Lfm2AudioOutputComponents> output_;
Lfm2TextTokenizer tokenizer_;
Lfm2BackboneRuntime backbone_;
Lfm2DepthformerRuntime depthformer_;
Lfm2DetokenizerRuntime detokenizer_;
std::string language_;
std::unique_ptr<Stream> stream_;
bool reached_max_tokens_ = false;
};
// Speech-to-speech (s2s): a spoken user turn in, a reply of interleaved text
// and audio out (LFM2AudioModel.generate_interleaved) under the system prompt
// of liquid-audio's chat demo, which text_input replaces when given. A
// request's input artifacts carry the conversation's earlier turns, which
// the prompt replays in full as liquid-audio does, and with return_codes its
// result carries the reply to send back with the next turn. What a request
// gives depends on that request alone; to save time on the next turn, for
// each of up to lfm2_audio.conversation_cache_slots conversations (four by
// default), the session keeps the questions of its last request with history
// or return_codes, with their encoder output, and the backbone the full
// prefill blocks of its last turn with history. Streaming takes the user's
// audio in chunks and, once it has all come, pulls the reply as events: the
// audio of the next stream_frames_per_event frames and the text written since
// the last event.
class Lfm2AudioChatSession final : public runtime::RuntimeSessionBase,
public runtime::IOfflineVoiceTaskSession,
public runtime::IStreamingVoiceTaskSession {
public:
Lfm2AudioChatSession(
runtime::TaskSpec task,
runtime::SessionOptions options,
std::shared_ptr<const Lfm2AudioAssets> assets,
std::shared_ptr<const engine::model_spec::ModelContract> contract);
~Lfm2AudioChatSession() override;
std::string family() const override;
runtime::VoiceTaskKind task_kind() const override;
runtime::RunMode run_mode() const override;
void prepare(const runtime::SessionPreparationRequest & request) override;
runtime::TaskResult run(const runtime::TaskRequest & request) override;
runtime::StreamingPolicy streaming_policy() const override;
void start_stream(const runtime::TaskRequest & request) override;
std::optional<runtime::StreamEvent> next_stream_event() override;
void set_stream_event_sink(runtime::StreamEventCallback sink) override;
runtime::TaskResult finish_stream() override;
void reset() override;
runtime::StreamEvent process_audio_chunk(const runtime::AudioChunk & chunk) override;
runtime::TaskResult finalize() override;
// Whether max_tokens cut the reply off, in the last run() or in the
// stream since start_stream().
[[nodiscard]] bool reached_max_tokens() const;
// Of the last reply's prompt: the steps the backbone restored from the
// turns before rather than ran (Lfm2BackboneRuntime::resumed_prefill_steps),
// and the earlier questions that took the encoder output kept from them.
[[nodiscard]] int64_t resumed_prefill_steps() const;
[[nodiscard]] int64_t reused_questions() const;
private:
struct RequestOptions {
std::string system_prompt;
Lfm2InterleavedOptions reply;
int64_t stream_frames_per_event = 0;
bool return_codes = false;
std::vector<Lfm2ConversationTurn> history;
int64_t text_steps = 0; // with history: prompt steps besides the questions' audio
};
// A question as the encoder took it, mono 16 kHz, and what it gave. Neither
// changes once encoded, so a request that takes a kept question shares
// both with the table before rather than copying them.
struct EncodedQuestion {
std::shared_ptr<const std::vector<float>> samples;
std::shared_ptr<const Lfm2AudioEmbeddings> encoded;
};
struct Stream;
RequestOptions parse_request(const runtime::TaskRequest & request) const;
std::unique_ptr<Lfm2InterleavedGenerator> start_reply(const RequestOptions & options, const runtime::AudioBuffer & audio);
void keep_questions(std::vector<EncodedQuestion> questions);
[[nodiscard]] Lfm2ReplyCheckpoint reply_checkpoint() const;
runtime::TaskSpec task_;
std::shared_ptr<const Lfm2AudioAssets> assets_;
std::shared_ptr<const engine::model_spec::ModelContract> contract_;
bool cpu_repack_; // lfm2_audio.cpu_repack, for the runtimes below
// lfm2_audio.conversation_cache_slots, for the backbone below and questions_
size_t conversation_slots_;
std::shared_ptr<const Lfm2AudioComponents> components_;
std::shared_ptr<const Lfm2AudioOutputComponents> output_;
Lfm2TextTokenizer tokenizer_;
Lfm2AudioFeatureExtractor features_;
Lfm2FastConformerEncoderRuntime encoder_;
Lfm2BackboneRuntime backbone_;
Lfm2DepthformerRuntime depthformer_;
Lfm2DetokenizerRuntime detokenizer_;
std::string language_;
double max_pass_seconds_;
std::unique_ptr<Stream> stream_;
bool reached_max_tokens_ = false;
// The questions of the last request with history or return_codes of each
// of up to conversation_slots_ conversations, the most recent first.
std::vector<std::vector<EncodedQuestion>> questions_;
int64_t reused_questions_ = 0;
};
} // namespace engine::community_models::lfm2_audio