Liquid Audio

repository·main·Indexed 19 days ago

https://github.com/liquid4all/liquid-audio

Liquid Audio provides end-to-end speech-to-speech foundation models (LFM2-Audio) for low-latency, real-time conversational AI. It supports interleaved text/audio generation for chat and sequential generation for tasks such as Automatic Speech Recognition (ASR) and Text-to-Speech (TTS). The library includes LFM2AudioModel for token generation, LFM2AudioProcessor for data conversion, and ChatState for conversation management. It supports multi-modal chat, custom finetuning via LFM2AudioChatMapper, and provides specialized models for Japanese language support.

Tokens
12.6K
Snippets
35
Records
50
Agent score
69%

What's inside liquid-audio

  1. Understand LFM2-Audio generation modes

    main

    LFM2-Audio supports two distinct generation modes via LFM2AudioModel:

    1. Interleaved Generation (generate_interleaved): Outputs text and audio tokens in a fixed interleaved pattern. This minimizes time to first audio output and is ideal for real-time, low-latency speech-to-speech conversations.
    2. Sequential Generation (generate_sequential): The model decides when to switch modalities using special tokens. This is best suited for non-conversational tasks like Automatic Speech Recognition (ASR) or Text-to-Speech (TTS).

    Token Structure:

    • Text tokens: Represented by tensors with 1 entry.
    • Audio tokens: Represented by tensors with 8 entries (corresponding to 8 Mimi codebooks).
  2. Manage conversation state with ChatState

    main
    The ChatState helper class is used to build inputs for generation methods and apply correct chat templates. It allows you to structure turns (system, user, assistant) and add text or audio content to those turns.
  3. Use LFM2AudioModel and LFM2AudioProcessor

    main

    The library separates model logic from data processing:

    • LFM2AudioModel: Handles token-based generation. Use generate_interleaved or generate_sequential methods, which are generators yielding torch.Tensors.
    • LFM2AudioProcessor: Handles conversions between tokens and raw data. It converts strings to tokens (and back) and converts audio waveforms to log-mel features (and back via decode).
  4. Install liquid-audio via pip

    main

    Install the core package using pip. You can also install optional dependencies for the Gradio demo or Flash Attention 2 for improved performance.

    # Core installation
    pip install liquid-audio
    
    # Optional: Install demo dependencies
    pip install "liquid-audio [demo]"
    
    # Optional: Install flash-attn for Flash Attention 2 support
    # If not installed, the library falls back to torch SDPA
    pip install flash-attn --no-build-isolation
  5. Run the Liquid Audio Gradio demo

    main

    To launch the web-based demo interface, first install the demo dependencies, then run the CLI command.

    ```bash
    pip install "liquid-audio [demo]"
    liquid-audio-demo

    The demo will be available at http://localhost:7860.

  6. Perform multi-turn, multi-modal speech-to-speech chat

    main

    For real-time conversational interactions involving both text and audio, use generate_interleaved.

    Key Requirements:

    • Set the system prompt to: Respond with interleaved text and audio.
    • Use ChatState to manage turns.
    • When processing the generator output, check t.numel(): if it is 1, it is a text token; otherwise, it is an audio token.
    • Use processor.decode to convert audio tokens back to a waveform. Note that Mimi returns audio at 24kHz.
    import torch
    import soundfile as sf
    from liquid_audio import LFM2AudioModel, LFM2AudioProcessor, ChatState, LFMModality
    
    # Load models
    HF_REPO = "LiquidAI/LFM2.5-Audio-1.5B"
    processor = LFM2AudioProcessor.from_pretrained(HF_REPO).eval()
    model = LFM2AudioModel.from_pretrained(HF_REPO).eval()
    
    # Set up inputs
    chat = ChatState(processor)
    chat.new_turn("system")
    chat.add_text("Respond with interleaved text and audio.")
    chat.end_turn()
    
    chat.new_turn("user")
    # ... load wav and add to chat ...
    chat.end_turn()
    
    chat.new_turn("assistant")
    
    # Generate
    text_out, audio_out, modality_out = [], [], []
    for t in model.generate_interleaved(**chat, max_new_tokens=512, audio_temperature=1.0, audio_top_k=4):
        if t.numel() == 1:
            print(processor.text.decode(t), end="", flush=True)
            text_out.append(t)
            modality_out.append(LFMModality.TEXT)
        else:
            audio_out.append(t)
            modality_out.append(LFMModality.AUDIO_OUT)
    
    # Detokenize audio (removing last end-of-audio code)
    audio_codes = torch.stack(audio_out[:-1], 1).unsqueeze(0)
    waveform = processor.decode(audio_codes)
    sf.write("answer.wav", waveform.cpu()[0], 24_000)
    
    # Append to history for multi-turn
    chat.append(
        text = torch.stack(text_out, 1),
        audio_out = torch.stack(audio_out, 1),
        modality_flag = torch.tensor(modality_out),
    )
    chat.end_turn()
  7. Perform Automatic Speech Recognition (ASR)

    main

    To use the model for ASR (transcribing audio to text), use the model.generate_sequential(**chat) method and set the system prompt to exactly: Perform ASR in japanese.

    chat.new_turn("system")
    chat.add_text("Perform ASR in japanese.")
    chat.end_turn()
    
    chat.new_turn("user")
    wav, sampling_rate = sf.read("assets/asr_jp.wav", dtype="float32")
    wav = torch.from_numpy(wav).unsqueeze(0)
    chat.add_audio(wav, sampling_rate)
    chat.end_turn()
    
    chat.new_turn("assistant")
    
    # Generate text
    for t in model.generate_sequential(**chat, max_new_tokens=512):
        if t.numel() == 1:
            print(processor.text.decode(t), end="", flush=True)
  8. Preprocess a dataset for training

    main

    Before training, you must convert your dataset into the model's preprocessed training format. You need to define an iterator that yields one list[ChatMessage] per sample in your dataset. The LFM2AudioChatMapper will then handle the conversion into model-ready features.

    To preprocess the Jenny TTS Dataset as an example, run:

    python examples/preprocess_jenny_tts.py

    This command generates a preprocessed dataset located at data/jenny_tts/train.

  9. Perform Text-to-Speech (TTS)

    main

    To use the model for TTS, use generate_sequential. You can select one of four pre-defined voices by using one of the following system prompts:

    • Perform TTS. Use the US male voice.
    • Perform TTS. Use the US female voice.
    • Perform TTS. Use the UK male voice.
    • Perform TTS. Use the UK female voice.
    # ... setup processor, model, and chat ...
    chat.new_turn("system")
    chat.add_text("Perform TTS. Use the UK male voice.")
    chat.end_turn()
    
    chat.new_turn("user")
    chat.add_text("What is this obsession people have with books?")
    chat.end_turn()
    
    chat.new_turn("assistant")
    
    # Generate
    audio_out = []
    for t in model.generate_sequential(**chat, max_new_tokens=512, audio_temperature=0.8, audio_top_k=64):
        if t.numel() > 1:
            audio_out.append(t)
    
    # Detokenize
    audio_codes = torch.stack(audio_out[:-1], 1).unsqueeze(0)
    waveform = processor.decode(audio_codes)
    sf.write("tts.wav", waveform.cpu()[0], 24_000)
  10. Perform multi-turn, multi-modal chat

    main

    Multi-turn chat is managed using the ChatState class. You can build a conversation by defining turns (system, user, or assistant) and adding text or audio content to each turn.

    To generate interleaved text and audio responses, use model.generate_interleaved(**chat). This method yields tokens that can be either text (if t.numel() == 1) or audio (if t.numel() > 1).

    After generating a response, use chat.append(...) to add the newly generated tokens back into the chat history to maintain context for subsequent turns.

    Key details:

    • Audio Detokenization: To convert audio tokens to a waveform, stack the audio tokens (excluding the last 'end-of-audio' code), and use processor.decode(audio_codes).
    • Sampling Rate: The Mimi decoder returns audio at 24kHz.
    import torch
    import soundfile as sf
    from liquid_audio import LFM2AudioModel, LFM2AudioProcessor, ChatState, LFMModality
    
    # Load models
    HF_REPO = "LiquidAI/LFM2.5-Audio-1.5B-JP"
    processor = LFM2AudioProcessor.from_pretrained(HF_REPO).eval()
    model = LFM2AudioModel.from_pretrained(HF_REPO).eval()
    
    # Set up inputs
    chat = ChatState(processor)
    
    chat.new_turn("system")
    chat.add_text("Respond with interleaved text and audio.")
    chat.end_turn()
    
    chat.new_turn("user")
    wav, sampling_rate = sf.read("assets/question_jp.wav", dtype="float32")
    wav = torch.from_numpy(wav).unsqueeze(0)
    chat.add_audio(wav, sampling_rate)
    chat.end_turn()
    
    chat.new_turn("assistant")
    
    # Generate interleaved tokens
    text_out: list[torch.Tensor] = []
    audio_out: list[torch.Tensor] = []
    modality_out: list[LFMModality] = []
    for t in model.generate_interleaved(**chat, max_new_tokens=512, audio_temperature=1.0, audio_top_k=4):
        if t.numel() == 1:
            print(processor.text.decode(t), end="", flush=True)
            text_out.append(t)
            modality_out.append(LFMModality.TEXT)
        else:
            audio_out.append(t)
            modality_out.append(LFMModality.AUDIO_OUT)
    
    # Detokenize audio
    audio_codes = torch.stack(audio_out[:-1], 1).unsqueeze(0)
    waveform = processor.decode(audio_codes)
    sf.write("answer_jp1.wav", waveform.cpu()[0], 24_000)
    
    # Append to history for next turn
    chat.append(
        text = torch.stack(text_out, 1),
        audio_out = torch.stack(audio_out, 1),
        modality_flag = torch.tensor(modality_out),
    )
    chat.end_turn()
  11. Train a model from a preprocessed dataset

    main

    Once the dataset has been preprocessed, you can initiate training. The training process reads directly from the preprocessed dataset format created by LFM2AudioChatMapper.

    To finetune a model using the preprocessed Jenny TTS Dataset, run:

    python examples/train.py