Ang pagsasama ng mga LLM sa mga kakayahan sa boses ay lumikha ng mga bagong pagkakataon sa mga personalized na pakikipag-ugnayan ng customer.
Gagabayan ka ng gabay na ito sa pag-set up ng isang lokal na LLM server na sumusuporta sa mga two-way na pakikipag-ugnayan ng boses gamit ang Python, Transformers, Qwen2-Audio-7B-Instruct, at Bark.
Bago tayo magsimula, mai-install mo ang sumusunod:
Maaaring i-install ang FFmpeg sa pamamagitan ng apt install ffmpeg
sa Linux o brew install ffmpeg
sa MacOS.
Maaari mong i-install ang Python dependencies gamit ang pip: pip install torch transformers accelerate pydub fastapi uvicorn bark python-multipart scipy
Una, i-set up natin ang ating Python environment at piliin ang ating PyTorch device:
import torch device = 'cuda' if torch.cuda.is_available() else 'cpu'
Tinitingnan ng code na ito kung available ang isang CUDA-compatible (Nvidia) GPU at itinatakda ang device nang naaayon.
Kung walang ganoong GPU, tatakbo ang PyTorch sa CPU na mas mabagal.
Para sa mga mas bagong Apple Silicon device, maaari ding itakda ang device sa
mps
para patakbuhin ang PyTorch on Metal, ngunit hindi komprehensibo ang pagpapatupad ng PyTorch Metal.
Karamihan sa mga open-source na LLM ay sumusuporta lamang sa text input at text output. Gayunpaman, dahil gusto naming gumawa ng voice-in-voice-out system, kakailanganin nitong gumamit kami ng dalawa pang modelo para (1) i-convert ang speech sa text bago ito i-feed sa aming LLM at (2) i-convert pabalik ang LLM output. sa pagsasalita.
Sa pamamagitan ng paggamit ng multimodal na LLM tulad ng Qwen Audio, makakaalis tayo sa isang modelo para iproseso ang speech input sa isang text response, at pagkatapos ay kailangan lang gumamit ng pangalawang modelo na i-convert ang LLM output pabalik sa speech.
Ang multimodal na diskarte na ito ay hindi lamang mas mahusay sa mga tuntunin ng oras ng pagpoproseso at (V) pagkonsumo ng RAM, ngunit kadalasan ay nagbubunga din ng mas mahusay na mga resulta dahil ang input audio ay ipinadala nang diretso sa LLM nang walang anumang friction.
Kung tumatakbo ka sa cloud GPU host tulad ng Runpod o Vast , gugustuhin mong itakda ang HuggingFace home & Bark directory sa iyong volume storage sa pamamagitan ng pagpapatakbo ng
export HF_HOME=/workspace/hf
&export XDG_CACHE_HOME=/workspace/bark
bago mag-download ang mga modelo.
from transformers import AutoProcessor, Qwen2AudioForConditionalGeneration model_name = "Qwen/Qwen2-Audio-7B-Instruct" processor = AutoProcessor.from_pretrained(model_name) model = Qwen2AudioForConditionalGeneration.from_pretrained(model_name, device_map="auto").to(device)
Pinili naming gamitin ang maliit na 7B na variant ng serye ng modelong Qwen Audio dito upang bawasan ang aming mga kinakailangan sa computational. Gayunpaman, maaaring naglabas si Qwen ng mas malakas at mas malalaking audio model sa oras na binabasa mo ang artikulong ito. Maaari mong tingnan ang lahat ng mga modelo ng Qwen sa HuggingFace upang matiyak na ginagamit mo ang kanilang pinakabagong modelo.
Para sa kapaligiran ng produksyon, maaaring gusto mong gumamit ng mabilis na inference engine tulad ng vLLM para sa mas mataas na throughput.
Ang Bark ay isang state-of-the-art na open-source na text-to-speech na modelo ng AI na sumusuporta sa maraming wika pati na rin ang mga sound effect.
from bark import SAMPLE_RATE, generate_audio, preload_models preload_models()
Bukod sa Bark, maaari ka ring gumamit ng iba pang open-source o proprietary text-to-speech na mga modelo. Tandaan na habang ang mga pinagmamay-ari ay maaaring mas mahusay na gumaganap, ang mga ito ay may mas mataas na halaga. Ang TTS arena ay nagpapanatili ng up-to-date na paghahambing .
Sa parehong Qwen Audio 7B at Bark na na-load sa memorya, ang tinatayang (V) RAM na paggamit ay 24GB, kaya siguraduhing sinusuportahan ito ng iyong hardware. Kung hindi, maaari kang gumamit ng quantized na bersyon ng modelong Qwen upang makatipid sa memorya.
Gagawa kami ng isang FastAPI server na may dalawang ruta para pangasiwaan ang mga papasok na audio o text input at ibalik ang mga audio na tugon.
from fastapi import FastAPI, UploadFile, Form from fastapi.responses import StreamingResponse import uvicorn app = FastAPI() @app.post("/voice") async def voice_interaction(file: UploadFile): # TODO return @app.post("/text") async def text_interaction(text: str = Form(...)): # TODO return if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)
Ang server na ito ay tumatanggap ng mga audio file sa pamamagitan ng POST na mga kahilingan sa /voice
& /text
endpoint.
Gagamitin namin ang ffmpeg para iproseso ang papasok na audio at ihanda ito para sa modelong Qwen.
from pydub import AudioSegment from io import BytesIO import numpy as np def audiosegment_to_float32_array(audio_segment: AudioSegment, target_rate: int = 16000) -> np.ndarray: audio_segment = audio_segment.set_frame_rate(target_rate).set_channels(1) samples = np.array(audio_segment.get_array_of_samples(), dtype=np.int16) samples = samples.astype(np.float32) / 32768.0 return samples def load_audio_as_array(audio_bytes: bytes) -> np.ndarray: audio_segment = AudioSegment.from_file(BytesIO(audio_bytes)) float_array = audiosegment_to_float32_array(audio_segment, target_rate=16000) return float_array
Gamit ang naprosesong audio, makakabuo tayo ng textual na tugon gamit ang modelong Qwen. Kakailanganin nitong pangasiwaan ang parehong mga input ng text at audio.
Iko-convert ng preprocessor ang aming input sa template ng chat ng modelo (ChatML sa kaso ni Qwen).
def generate_response(conversation): text = processor.apply_chat_template(conversation, add_generation_prompt=True, tokenize=False) audios = [] for message in conversation: if isinstance(message["content"], list): for ele in message["content"]: if ele["type"] == "audio": audio_array = load_audio_as_array(ele["audio_url"]) audios.append(audio_array) if audios: inputs = processor( text=text, audios=audios, return_tensors="pt", padding=True ).to(device) else: inputs = processor( text=text, return_tensors="pt", padding=True ).to(device) generate_ids = model.generate(**inputs, max_length=256) generate_ids = generate_ids[:, inputs.input_ids.size(1):] response = processor.batch_decode( generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False )[0] return response
Huwag mag-atubiling makipaglaro sa mga parameter ng henerasyon tulad ng temperatura sa
model.generate
function.
Panghuli, iko-convert namin ang nabuong tugon ng teksto pabalik sa pagsasalita.
from scipy.io.wavfile import write as write_wav def text_to_speech(text): audio_array = generate_audio(text) output_buffer = BytesIO() write_wav(output_buffer, SAMPLE_RATE, audio_array) output_buffer.seek(0) return output_buffer
I-update ang mga endpoint upang maproseso ang audio o text input, bumuo ng tugon, at ibalik ang synthesized na pagsasalita bilang isang WAV file.
@app.post("/voice") async def voice_interaction(file: UploadFile): audio_bytes = await file.read() conversation = [ { "role": "user", "content": [ { "type": "audio", "audio_url": audio_bytes } ] } ] response_text = generate_response(conversation) audio_output = text_to_speech(response_text) return StreamingResponse(audio_output, media_type="audio/wav") @app.post("/text") async def text_interaction(text: str = Form(...)): conversation = [ {"role": "user", "content": [{"type": "text", "text": text}]} ] response_text = generate_response(conversation) audio_output = text_to_speech(response_text) return StreamingResponse(audio_output, media_type="audio/wav")
Maaari mong piliing magdagdag ng mensahe ng system sa mga pag-uusap upang makakuha ng higit na kontrol sa mga tugon ng katulong.
Maaari naming gamitin curl
upang i-ping ang aming server tulad ng sumusunod:
# Audio input curl -X POST http://localhost:8000/voice --output output.wav -F "[email protected]" # Text input curl -X POST http://localhost:8000/text --output output.wav -H "Content-Type: application/x-www-form-urlencoded" -d "text=Hey"
Sa pamamagitan ng pagsunod sa mga hakbang na ito, nag-set up ka ng isang simpleng lokal na server na may kakayahang mag-two-way na pakikipag-ugnayan ng boses gamit ang mga makabagong modelo. Maaaring magsilbi ang setup na ito bilang pundasyon para sa pagbuo ng mas kumplikadong mga application na pinagana ng boses.
Kung nag-e-explore ka ng mga paraan para pagkakitaan ang mga modelo ng wikang pinapagana ng AI, isaalang-alang ang mga potensyal na application na ito:
import torch from fastapi import FastAPI, UploadFile, Form from fastapi.responses import StreamingResponse import uvicorn from transformers import AutoProcessor, Qwen2AudioForConditionalGeneration from bark import SAMPLE_RATE, generate_audio, preload_models from scipy.io.wavfile import write as write_wav from pydub import AudioSegment from io import BytesIO import numpy as np device = 'cuda' if torch.cuda.is_available() else 'cpu' model_name = "Qwen/Qwen2-Audio-7B-Instruct" processor = AutoProcessor.from_pretrained(model_name) model = Qwen2AudioForConditionalGeneration.from_pretrained(model_name, device_map="auto").to(device) preload_models() app = FastAPI() def audiosegment_to_float32_array(audio_segment: AudioSegment, target_rate: int = 16000) -> np.ndarray: audio_segment = audio_segment.set_frame_rate(target_rate).set_channels(1) samples = np.array(audio_segment.get_array_of_samples(), dtype=np.int16) samples = samples.astype(np.float32) / 32768.0 return samples def load_audio_as_array(audio_bytes: bytes) -> np.ndarray: audio_segment = AudioSegment.from_file(BytesIO(audio_bytes)) float_array = audiosegment_to_float32_array(audio_segment, target_rate=16000) return float_array def generate_response(conversation): text = processor.apply_chat_template(conversation, add_generation_prompt=True, tokenize=False) audios = [] for message in conversation: if isinstance(message["content"], list): for ele in message["content"]: if ele["type"] == "audio": audio_array = load_audio_as_array(ele["audio_url"]) audios.append(audio_array) if audios: inputs = processor( text=text, audios=audios, return_tensors="pt", padding=True ).to(device) else: inputs = processor( text=text, return_tensors="pt", padding=True ).to(device) generate_ids = model.generate(**inputs, max_length=256) generate_ids = generate_ids[:, inputs.input_ids.size(1):] response = processor.batch_decode( generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False )[0] return response def text_to_speech(text): audio_array = generate_audio(text) output_buffer = BytesIO() write_wav(output_buffer, SAMPLE_RATE, audio_array) output_buffer.seek(0) return output_buffer @app.post("/voice") async def voice_interaction(file: UploadFile): audio_bytes = await file.read() conversation = [ { "role": "user", "content": [ { "type": "audio", "audio_url": audio_bytes } ] } ] response_text = generate_response(conversation) audio_output = text_to_speech(response_text) return StreamingResponse(audio_output, media_type="audio/wav") @app.post("/text") async def text_interaction(text: str = Form(...)): conversation = [ {"role": "user", "content": [{"type": "text", "text": text}]} ] response_text = generate_response(conversation) audio_output = text_to_speech(response_text) return StreamingResponse(audio_output, media_type="audio/wav") if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)