当前位置:AIGC资讯 > AIGC > 正文

whisper.cpp 学习笔记

whisper.cpp

whisper.cpp 学习笔记 whisper 介绍 源码下载 源码编译 支持的模型 优化/加速 生成库文件 使用 whispe.cpp 的 demo 参考文献

whisper.cpp 学习笔记

whisper 介绍

whisper 是基于 OpenAI 的自动语音识别(ASR)模型。他可以识别包括英语、普通话等在内多国语言。

whisper 分为 whisper (python 版本)和 whisper.cpp(C/C++ 版本)。

python 版本的 whisper 可以直接通过 pip install whisper 安装;whisper.cpp 可以通过源码进行安装。

以下主要介绍 whisper.cpp,因为其识别速度要快于 python 版本的 whisper。

源码下载

源码下载

网址:https://github.com/ggerganov/whisper.cpp

git clone https://github.com/ggerganov/whisper.cpp.git
下载模型
bash ./models/download-ggml-model.sh base.en

源码编译

方法一

直接在 whisper.cpp 目录下执行 make 命令就可以编译

# build the main example
make

# transcribe an audio file
./main -f samples/jfk.wav

默认会生成 main benchquantize 这三个命令:

main :whisper 的命令 quantize : 对 whisper 的模型进行量化处理 bench :性能测试

注:

使用此方法生成的可执行文件会包含 whisper 的代码,即没有生成 whisper 的库文件

whisper 只能识别音频采样频率为 16000 Hz 的声音数据,数据格式为 float类型——如果是 wav 文件则内部会自动转成 float 格式

方法二

make build
cd build
cmake xxx	#xxx 为 cmake 传入的参数,例如 prefix 等等,单独的 cmake 不能生成 Makefile
make

使用这种方式会在 build 目录下生成 liwhisper.so liwhisper.so.1liwhisper.so。1.5.5 库文件 —— 对应的头文件在 whisper.cpp 目录下 whisper.h 可以直接将这几个文件防盗 /usr/lib/usr/include 下即可使用。

支持的模型

whisper 目前支持:tiny、base、small、medium 以及 large 模型,其中带 .en 的表示之支持英文。

通过 make xxx 可以直接下载模型,例如:

make small

各种模型的内存使用情况:

Model Disk Mem tiny 75 MiB ~273 MB base 142 MiB ~388 MB small 466 MiB ~852 MB medium 1.5 GiB ~2.1 GB large 2.9 GiB ~3.9 GB

优化/加速

使用硬件加速

whipser 支持多种加速(详见 whisper.cpp 的 Readme)

OpenVINO NVIDIA GPU CLBlast OpenBLAS MKL …

这里只介绍 MKL。

首先通过 Intel® oneAPI Math Kernel Library 下载 intel 的 oneapi 数学库;

该数学库是二进制安装的,在安装完后需要执行一个脚本用于产生命令和连接库位置的环境

source /opt/intel/oneapi/setvars.sh 
mkdir build
cd build
cmake -DWHISPER_MKL=ON ..
WHISPER_MKL=1 make -j

通过上步可以生成依赖 intel oneapi 的 whisper 动态库。这里为了能够开机使用 intel oneapi,可以将 source /opt/intel/oneapi/setvars.sh 命令放到 ~/.bashrc 配置文件中——要想所有用户都可以使用,可以在 /ect/profile.d 目录下建一个 intel oneapi 的脚本,这样在用户登陆时可以自动配置环境变量。

注:

如果使用 root 权限安装 intel oneapi 则该软件安装在 /opt 目录下,否则安装在用户的 home 目录下。

量化模型

编译生成的可执行文件中有 quantize 这个命令,该命令用来量化模型可以减少模型的体积和加快运行的速度。

usage: ./quantize model-f32.bin model-quant.bin type
  type = "q2_k" or 10
  type = "q3_k" or 11
  type = "q4_0" or 2
  type = "q4_1" or 3
  type = "q4_k" or 12
  type = "q5_0" or 8
  type = "q5_1" or 9
  type = "q5_k" or 13
  type = "q6_k" or 14
  type = "q8_0" or 7

例如量化前 ggml-medium.bin 的大小为 1.5 G,采用 q4_k 量化后大小为 424 M。

执行速度:

量化前

time whisper --language chinese --model models/ggml-medium.bin output.wav
system_info: n_threads = 4 / 8 | AVX = 1 | AVX2 = 1 | AVX512 = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | METAL = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 | CUDA = 0 | COREML = 0 | OPENVINO = 0

main: processing 'output.wav' (42624 samples, 2.7 sec), 4 threads, 1 processors, 5 beams + best of 5, lang = chinese, task = transcribe, timestamps = 1 ...


[00:00:00.000 --> 00:00:02.000]  你好 你好 你好

whisper_print_timings:     load time =   606.10 ms
whisper_print_timings:     fallbacks =   0 p /   0 h
whisper_print_timings:      mel time =     6.12 ms
whisper_print_timings:   sample time =    12.43 ms /    24 runs (    0.52 ms per run)
whisper_print_timings:   encode time =  9836.69 ms /     1 runs ( 9836.69 ms per run)
whisper_print_timings:   decode time =    69.17 ms /     2 runs (   34.59 ms per run)
whisper_print_timings:   batchd time =   364.41 ms /    20 runs (   18.22 ms per run)
whisper_print_timings:   prompt time =     0.00 ms /     1 runs (    0.00 ms per run)
whisper_print_timings:    total time = 10899.75 ms

real    0m11.000s
user    0m44.862s
sys     0m2.151s

量化后

time whisper --language chinese --model models/ggml-medium_q4_k.bin output.wav
system_info: n_threads = 4 / 8 | AVX = 1 | AVX2 = 1 | AVX512 = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | METAL = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 | CUDA = 0 | COREML = 0 | OPENVINO = 0

main: processing 'output.wav' (42624 samples, 2.7 sec), 4 threads, 1 processors, 5 beams + best of 5, lang = chinese, task = transcribe, timestamps = 1 ...


[00:00:00.000 --> 00:00:02.000]  你好 你好 你好

whisper_print_timings:     load time =   303.66 ms
whisper_print_timings:     fallbacks =   0 p /   0 h
whisper_print_timings:      mel time =     6.37 ms
whisper_print_timings:   sample time =    13.74 ms /    27 runs (    0.51 ms per run)
whisper_print_timings:   encode time =  8979.33 ms /     1 runs ( 8979.33 ms per run)
whisper_print_timings:   decode time =    34.54 ms /     2 runs (   17.27 ms per run)
whisper_print_timings:   batchd time =   289.84 ms /    23 runs (   12.60 ms per run)
whisper_print_timings:   prompt time =     0.00 ms /     1 runs (    0.00 ms per run)
whisper_print_timings:    total time =  9632.14 ms

real    0m9.702s
user    0m42.165s
sys     0m1.342s

从量化前后的执行速度来看,执行时间提升了1秒左右。

生成库文件

在前面我们介绍了使用 cmake & make 命令可以生成动态库。

使用 whispe.cpp 的 demo

example 目录下有各种使用的 demo ,通过在 whisper.cpp 目录下执行 make xxx (xxx 为 example 中演示 demo 的名字),即可生成该 demo 的可执行文件。

通过分析各种 demo 文件可以发现,主要使用了 whisper 中的如下几个函数:

whisper_lang_id() 语言支持检测——这里要使用小写,如 chinese 而不能用 Chinese whisper_context_default_params() 设置默认的上下文参数 whisper_init_from_file_with_params() 初始化上下文 whisper_is_multilingual() 检查上下文是否支持多国语言 whisper_full() 语音识别过程的函数 whisper_full_n_segments() 获取一共产生了多少段文字 whisper_full_get_segments_text() 获取识别到的一段文字 whisper_full_n_token() 一段识别中有多少个 tokern whisper_full_get_token_id() 获取对应 id 的 token

以下是参考 example/main/main.cpp 改写的简单 cpp 文件。

#include "common.h"

#include "whisper.h"
#include "grammar-parser.h"

#include <cmath>
#include <fstream>
#include <cstdio>
#include <regex>
#include <string>
#include <thread>
#include <vector>
#include <cstring>

bool wav_read(std::string fname, std::vector<float>& pcmf32)
{
    std::vector<std::vector<float>> pcmf32s;

    if (!::read_wav(fname, pcmf32, pcmf32s, false)) {
        fprintf(stderr, "error: failed to read WAV file '%s'\n", fname.c_str());
        return false;
    }

    return true;
}

int whisper_init(struct whisper_context * *ctx, whisper_full_params& wparams)
{
    if (whisper_lang_id("chinese") == -1) {
        fprintf(stderr, "error: unknown language '%s'\n", "Chinese");
        exit(0);
    }

    struct whisper_context_params cparams = whisper_context_default_params();
    cparams.use_gpu = false;

   *ctx = whisper_init_from_file_with_params("models/ggml-small.bin", cparams);
    if (*ctx == nullptr) {
        fprintf(stderr, "error: failed to initialize whisper context\n");
        return 3;
    }


    wparams = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
    wparams.language         = "chinese";

    return 0;
}

int whisper_exit(struct whisper_context ** ctx)
{
    whisper_free(*ctx);
    return 0;
}

int whisper_identify(struct whisper_context **ctx, whisper_full_params& wparams, std::vector<float> pcmf32, std::string& result)
{
    if(whisper_full(*ctx, wparams, pcmf32.data(), pcmf32.size()) != 0){
        return -1;
    }

    const int n_segments = whisper_full_n_segments(*ctx);
    for (int i = 0; i < n_segments; ++i) {
        const char * text = whisper_full_get_segment_text(*ctx, i);

        result += text;
    }

    return 0;
}

int main(int argc, char ** argv) {
    std::vector<float> pcmf32;
    struct whisper_context *ctx = nullptr;
    whisper_full_params wparams;
    std::string text;

    if(!wav_read("output.wav", pcmf32)){
        fprintf(stderr, "wave read failed !\n");
        return -1;
    }

    if(whisper_init(&ctx, wparams)){
        fprintf(stderr, "whisper init error !\n");
        return -1;
    }

    if(whisper_identify(&ctx, wparams, pcmf32, text)){
        fprintf(stderr, "identify error !\n");
        return 0;
    }

    whisper_exit(&ctx);

   fprintf(stdout, "text is : %s\n", text.c_str());

    return 0;
}

该文件简化为 wav 文件读、whisper 初始化、whisper 识别以及 whisper 退出这几个函数,结构简单更容易理解。将其替换 example/main/main.cpp 重新编译即可以执行。

注:
在 whisper 的 api 函数中涉及到的语言全部用小写,不能使用大写,否则会提示不支持。

参考文献

whisper.cpp

更新时间 2024-07-07