一个周末的AI项目:在树莓派上运行语音识别和LLaMA-2 GPT

更新时间:2024/01/23, 11:43

完全离线使用Whisper ASR和LLaMA-2 GPT模型

Raspberry Pi running a LLaMA model, Image by author

如今,在云中运行深度学习模型已经不足为奇。但在边缘设备或消费设备领域,情况可能更加复杂。这其中有几个原因。首先,使用云API要求设备始终在线。对于网络服务来说这不是问题,但对于需要在没有互联网访问的情况下正常工作的设备来说,这可能是个致命问题。其次,云API需要付费,而客户可能不愿意支付额外的订阅费用。最后但并非最不重要的是,几年后,项目可能已经完成,API端点将被关闭,昂贵的硬件将变成一块砖。这对于客户、生态系统和环境来说显然不友好。这就是为什么我坚信,终端用户硬件应该在离线状态下完全功能正常,不需要额外费用或使用在线API(当然,这可以是可选的,但不是强制的)。

在本文中,我将展示如何在树莓派上运行LLaMA GPT模型和自动语音识别(ASR)。这将使我们能够向树莓派提问并获得答案。并且如承诺的那样,所有这些都可以完全离线工作。

让我们开始吧!

本文中提供的代码旨在在树莓派上运行。但是,除了“显示”部分之外,大多数方法也适用于Windows、OSX或Linux笔记本电脑。因此,没有树莓派的读者可以轻松测试代码,没有任何问题。

硬件

对于这个项目,我将使用一台树莓派4。它是一台运行Linux的单板计算机,体积小,只需要5V直流电源,无需风扇和主动散热:

Raspberry Pi 4, Image source Wikipedia

更新的2023型号,树莓派5,应该更好;根据基准测试,它的速度几乎是2倍。但它的价格也几乎贵了50%,对于我们的测试来说,型号4已经足够好了。

至于RAM大小,我们有两个选择:

  • 有8GB RAM的树莓派可以运行一个7B LLaMA-2 GPT模型,其在4位量化模式下的内存占用约为5GB。

  • 2GB或4GB的设备可以运行一个更小的模型,如TinyLlama-1B。作为奖励,这个模型也更快,但正如我们后面将看到的那样,它的答案可能没有那么“聪明”。

这两个模型都可以从HuggingFace下载,一般来说,几乎不需要进行任何代码更改。

树莓派是一台完整的Linux计算机,我们可以通过SSH在终端上轻松查看输出。但这对于像机器人这样的移动设备来说并不那么有趣,也不太适合。对于树莓派,我将使用一块单色128x64 I2C OLED显示屏。这个显示屏只需要4根线连接:

I2C OLED display connection, Image made by author in Fritzing

显示屏和线材可以在亚马逊上购买,价格在5-10美元之间,无需焊接技能。在树莓派设置中,必须启用I2C接口;有足够的教程可以参考。出于简单起见,我将在这里省略硬件部分,只关注Python代码。

显示

我将从显示开始,因为在测试过程中在屏幕上看到一些东西会更好。Adafruit_CircuitPython_SSD1306库允许我们在OLED显示屏上显示任何图像。这个库有一个低级接口,它只能从内存缓冲区绘制像素或单色位图。为了使用可滚动的文本,我创建了一个存储文本缓冲区的数组和一个方法_display_update来绘制文本:

from PIL import Image, ImageDraw, ImageFont
try:
    import board
    import adafruit_ssd1306
    i2c = board.I2C()
    oled = adafruit_ssd1306.SSD1306_I2C(pixels_size[0], pixels_size[1], i2c)
except ImportError:
    oled = None


char_h = 11
rpi_font_poath = "DejaVuSans.ttf"
font = ImageFont.truetype(rpi_font_poath, char_h)
pixels_size = (128, 64)
max_x, max_y = 22, 5
display_lines = [""]

def _display_update():
    """ Show lines on the screen """
    global oled
    image = Image.new("1", pixels_size)
    draw = ImageDraw.Draw(image)
    for y, line in enumerate(display_lines):
        draw.text((0, y*char_h), line, font=font, fill=255, align="left")

    if oled:
        oled.fill(0)
        oled.image(image)
        oled.show()

这里,(22, 5)变量包含我们可以显示的行数和列数。如果ImportError发生,oled变量也可以是None;例如,如果我们在笔记本电脑上而不是树莓派上运行此代码。

为了模拟文本滚动,我还创建了两个辅助方法:

def add_display_line(text: str):
    """ Add new line with scrolling """
    global display_lines
    # Split line to chunks according to screen width
    text_chunks = [text[i: i+max_x] for i in range(0, len(text), max_x)]
    for text in text_chunks:
        for line in text.split("\n"):
            display_lines.append(line)
            display_lines = display_lines[-max_y:]
    _display_update()

def add_display_tokens(text: str):
    """ Add new tokens with or without extra line break """
    global display_lines
    last_line = display_lines.pop()
    new_line = last_line + text
    add_display_line(new_line)

第一个方法是向显示屏添加新行;如果字符串太长,该方法会自动将其拆分为多行。第二个方法是添加一个没有“换行符”的文本标记;我将用它来显示来自GPT模型的答案。add_display_line方法允许我们使用如下代码:

for p in range(20):
    add_display_line(f"{datetime.now().strftime('%H:%M:%S')}: Line-{p}")
    time.sleep(0.2)

如果一切都做得正确,输出应该是这样的:

OLED Display, Image by author

还有一些专门用于外部显示屏的库,比如Luma-oled,但我们的解决方案已经足够满足任务需求。

自动语音识别(ASR)

对于ASR,我将使用HuggingFace 🤗的Transformers库,它允许我们用几行Python代码实现语音识别:

from transformers import pipeline
from transformers.pipelines.audio_utils import ffmpeg_microphone_live


asr_model_id = "openai/whisper-tiny.en"
transcriber = pipeline("automatic-speech-recognition",
                       model=asr_model_id,
                       device="cpu")

在这里,我使用了Whisper-tiny-en模型,该模型在680K小时的语音数据上进行了训练。这是最小的Whisper模型,文件大小为151MB。当加载模型后,我们可以使用ffmpeg_microphone_live方法从麦克风获取数据:

def transcribe_mic(chunk_length_s: float) -> str:
    """ Transcribe the audio from a microphone """
    global transcriber
    sampling_rate = transcriber.feature_extractor.sampling_rate
    mic = ffmpeg_microphone_live(
            sampling_rate=sampling_rate,
            chunk_length_s=chunk_length_s,
            stream_chunk_s=chunk_length_s,
        )
    
    result = ""
    for item in transcriber(mic):
        result = item["text"]
        if not item["partial"][0]:
            break
    return result.strip()

实际上,5-10秒足以说出这个短语。树莓派没有麦克风,但任何USB麦克风都可以完成工作。这段代码也可以在笔记本电脑上进行测试;在这种情况下,将使用内置麦克风。

大型语言模型(LLM)

现在,让我们添加大型语言模型。首先,我们需要安装所需的库:

pip3 install llama-cpp-python
pip3 install huggingface-hub sentence-transformers langchain

在使用LLM之前,我们需要下载它。如前所述,我们有两个选择。对于8GB的树莓派,我们可以使用7B模型。对于2GB的设备,1B的“微型”模型是唯一可行的选择;更大的模型根本无法适应内存。要下载模型,我们可以使用huggingface-cli工具:

huggingface-cli download TheBloke/Llama-2-7b-Chat-GGUF llama-2-7b-chat.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False
OR
huggingface-cli download TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf --local-dir . --local-dir-use-symlinks False

如我们所见,我使用了Llama-2-7b-Chat-GGUFTinyLlama-1.1B-Chat-v1.0-GGUF模型。较小的模型运行速度更快,但较大的模型可能提供更好的结果。

当模型下载完成后,我们可以使用它:

from langchain.llms import LlamaCpp
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain.prompts import PromptTemplate
from langchain.schema.output_parser import StrOutputParser


llm: Optional[LlamaCpp] = None
callback_manager: Any = None```python
model_file = "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf"  # OR "llama-2-7b-chat.Q4_K_M.gguf"
template_tiny = """<|system|>
                   You are a smart mini computer named Raspberry Pi. 
                   Write a short but funny answer.</s>
                   <|user|>
                   {question}</s>
                   <|assistant|>"""
template_llama = """<s>[INST] <<SYS>>
                    You are a smart mini computer named Raspberry Pi.
                    Write a short but funny answer.</SYS>>
                    {question} [/INST]"""
template = template_tiny


def llm_init():
    """ Load large language model """
    global llm, callback_manager

    callback_manager = CallbackManager([StreamingCustomCallbackHandler()])
    llm = LlamaCpp(
        model_path=model_file,
        temperature=0.1,
        n_gpu_layers=0,
        n_batch=256,
        callback_manager=callback_manager,
        verbose=True,
    )


def llm_start(question: str):
    """ Ask LLM a question """
    global llm, template

    prompt = PromptTemplate(template=template, input_variables=["question"])
    chain = prompt | llm | StrOutputParser()
    chain.invoke({"question": question}, config={})


class StreamingCustomCallbackHandler(StreamingStdOutCallbackHandler):
    """ Callback handler for LLM streaming """

    def on_llm_start(
        self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
    ) -> None:
        """ Run when LLM starts running """
        print("<LLM Started>")

    def on_llm_end(self, response: Any, **kwargs: Any) -> None:
        """ Run when LLM ends running """
        print("<LLM Ended>")

    def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
        """ Run on new LLM token. Only available when streaming is enabled """
        print(f"{token}", end="")
        add_display_tokens(token)


if __name__ == "__main__":
    add_display_line("Init automatic speech recogntion...")
    asr_init()

    add_display_line("Init LLaMA GPT...")
    llm_init()

    while True:
        # Q-A loop:
        add_display_line("Start speaking")
        add_display_line("")
        question = transcribe_mic(chunk_length_s=5.0)
        if len(question) > 0:
            add_display_tokens(f"> {question}")
            add_display_line("")

            llm_start(question)

以上是将自动语音识别和大型语言模型运行在树莓派上的代码。这个代码可以在树莓派上运行一个完全自主的语音识别和问答系统。

测试结果

在这里,我们可以看到Raspberry Pi 4上的1B LLM推理速度。正如之前提到的,Raspberry Pi 5应该快30-40%。

我没有比较1B和7 B型号的质量使用任何“官方”基准,如蓝色或红色。主观上,7 B模型提供了更多正确和信息丰富的答案,但它也需要更多的RAM,更多的加载时间(文件大小分别为4.6和0.7GB),并且工作速度慢3- 5倍。至于功耗,Raspberry Pi 4平均需要3- 5 W的运行模式,连接的OLED屏幕和USB麦克风。

结论

在本文中,我们能够在Raspberry Pi上运行自动语音识别和大型语言模型,Raspberry Pi是一台可以完全自主运行的便携式Linux计算机。不同的模型可以在云中使用,但就我个人而言,当我可以触摸它并看到它如何工作时,与真实的东西一起工作更有趣。

像这样的原型也是使用GPT模型的一个有趣的里程碑。即使在1-2年前,也无法想象大型语言模型可以在廉价的消费者硬件上运行。我们正在进入智能设备的时代,这些设备将能够理解人类的语音,响应文本命令或执行不同的操作。也许,在未来,像电视或微波炉这样的设备将根本没有按钮,我们只会和它们说话。正如我们从视频中看到的,LLM仍然工作得有点慢。但我们都知道摩尔定律--显然,5-10年后,同样的模型将很容易在1美元的芯片上运行,就像现在我们可以在5美元的ESP 32板上运行一个成熟的PDP-11仿真器(PDP在80年代是一台10万美元的计算机)一样。

AI奇想空间
AI奇想空间
https://aimazing.site
AI惊奇站是一个汇聚人工智能工具、资源和教程的导航网站。 在这里,你可以发现最新的AI技术、工具和应用,学习如何使用各种AI平台和框架,获取丰富的AI资源。 欢迎广大AI爱好者加入我们的社区,开启你的AI之旅!
AI交流群
Copyright © 2024 AI奇想空间.微信