🦙

llama.cpp で CPU で LLM のメモ(2023/05/15 時点日本語もいけるよ)

2023/05/16に公開

C/C++ ベースで CPU で LLM が動く llama.cpp

https://github.com/ggerganov/llama.cpp

すご... 便利...
pytorch + CUDA なんていらんかったんや...

モデルロードも爆速です.

最近日本語も文字化けせず使える感じになってきました!

開発はかなりアクティブで, 機能がどんどん追加されたり, weight ファイルフォーマットも結構まだまだ変化する感じです.
本記事を 2023/05/15 より 2 週間以上経て読んだ方は, 一度 llama.cpp の git repo を見直しするとよいでしょう.

環境

  • Ryzen9 3950X + 128 GB mem
  • Ubuntu 20.04
  • Python 3.10(conda で構築)

ビルド

CMake ファイルが用意されていますので, それでいけます.
(ファイル数少ないので, 自前ビルドシステムに組み込むのも容易でしょう)

デフォでは普通に cmake たたくだけです.

BLAS 有効ビルド(optional)

CPU でなるべくポータブルに高速化したい場合は BLAS(OpenBlas)を使うことになるでしょう.

OpenBlas は cmake ありますが, in-source で config ファイルを吐き出す感じなので out-of-tree build がやりずらいことに注意しましょう.

OpenBLAS を Android aarch64 + NDK r20 or later でビルドするメモ
https://qiita.com/syoyo/items/6f15ddc8eeb260bda433

(ちょい古いですが, OpenBlas 頑張れば Android でもビルドできます)

Windows, Linux などで動かす場合, Python を conda で整備している場合は conda で入れるのがよいでしょう.
(Linux Ubuntu の場合は apt で入れるでもいいかも)

https://anaconda.org/anaconda/openblas-devel

$ conda install -c anaconda openblas-devel

cuBLAS(optional)

ちょうど 2023/05/15 あたりのリリースで, cuBLAS(CUDA)対応されました.

ビルドには nvcc など CUDA SDK がいります.
この場合も CUDA SDK インストールは conda を使うのがよいでしょう.
cuda-tooklit でインストールできます. 自身の nvidia driver version に合った CUDA version のをインストールしましょう.

https://anaconda.org/nvidia/cuda-toolkit

ハイフンのない cudatoolkit もありますが, こちらは deprecated になったのか古くてアップデートされていないので使わないようにしましょう.

CUDA(cuBLAS)有効でビルドした場合, しかしデフォルトでは GPU で実行してくれません.

https://github.com/ggerganov/llama.cpp/issues/1448

--ngl N で GPU で処理するレイヤー数を指定する必要があります.
また, 生成トークン数が 32 以下の場合も GPU は利用されません(デフォは 512)

参考までに, Vicuna-13B 5bit(40 layers)をすべて GPU で処理する場合 9.5 GB ほどメモリが必要でした.
そして, GPU 実行は現状ではそんなに速くない感じです.
Tesla P100(16GB) で 3950X の 2 倍早いくらいでした.

https://github.com/ggerganov/llama.cpp/issues/1129

いくつか改善策は取られているようなので, そのうちより高速化されるでしょう.

実行

.bin(GGML weight model) のフォーマット(特に Q4 4bit 量子化?)が結構変わりますので注意ください.

https://github.com/ggerganov/llama.cpp/pull/1405

2023/05/15 時点では ggjt v2 が最新です.

とりま Vicuna 7B を動かす.

pytorch model からコンバートするのが理想ですが, とりあえずぺろっと試したい方のために...
(ホントは Vicuna モデル直はライセンス的にだめそうですが...)

https://github.com/ggerganov/llama.cpp/discussions/643#discussioncomment-5589738

https://huggingface.co/eachadea/ggml-vicuna-7b-1.1

の ggml-vicuna-7b-q4_0.bin を使います!

$ ./build/main -m models/models/ggml-vic7b-q4_0.bin

-m でモデルファイル(.bin)指定するだけです...
めちゃ楽...

デフォでは text generation になります.

$ ./build/bin/main -m models/ggml-vic7b-q4_0.bin

main: build = 553 (63d2046)
main: seed  = 1684180935
llama.cpp: loading model from models/ggml-vic7b-q4_0.bin
llama_model_load_internal: format     = ggjt v2 (latest)
llama_model_load_internal: n_vocab    = 32000
llama_model_load_internal: n_ctx      = 512
llama_model_load_internal: n_embd     = 4096
llama_model_load_internal: n_mult     = 256
llama_model_load_internal: n_head     = 32
llama_model_load_internal: n_layer    = 32
llama_model_load_internal: n_rot      = 128
llama_model_load_internal: ftype      = 2 (mostly Q4_0)
llama_model_load_internal: n_ff       = 11008
llama_model_load_internal: n_parts    = 1
llama_model_load_internal: model size = 7B
llama_model_load_internal: ggml ctx size =  72.75 KB
llama_model_load_internal: mem required  = 5809.34 MB (+ 1026.00 MB per state)
llama_init_from_file: kv self size  =  256.00 MB

system_info: n_threads = 16 / 32 | AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | VSX = 0 |
sampling: repeat_last_n = 64, repeat_penalty = 1.100000, presence_penalty = 0.000000, frequency_penalty = 0.000000, top_k = 40, tfs_z = 1.000000, top_p = 0.950000, typical_p = 1.000000, temp = 0.800000, mirostat = 0, mirostat_lr = 0.100000, mirostat_ent = 5.000000
generate: n_ctx = 512, n_batch = 512, n_predict = -1, n_keep = 0


 using System;
using System.IO;
using Newtonsoft.Json;

namespace ZipItUp
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("Provide the path to a zip file as an argument.");
                return;
            }

            string filePath = args[0];
            using (ZipArchive archive = ZipFile.OpenRead(filePath))
            {
                foreach (var entry in archive)
                {
                    Console.WriteLine($"{entry.FullName} - {entry.Compression.Method}");
                }
            }
        }
    }

    public class ZipArchive
    {
        [JsonProperty]
        public string Path { get; set; }

        [JsonProperty]
        public Compression Method { get; set; }
    }

    public enum Compression
    {
        Stored,
        Deflated
    }
}
 [end of text]

llama_print_timings:        load time =  2654.46 ms
llama_print_timings:      sample time =   232.39 ms /   245 runs   (    0.95 ms per token)
llama_print_timings: prompt eval time =   229.21 ms /     2 tokens (  114.61 ms per token)
llama_print_timings:        eval time = 44088.22 ms /   244 runs   (  180.69 ms per token)
llama_print_timings:       total time = 47027.20 ms

Voila!

日本語で text generation!

$ ./build/bin/main -m models/ggml-vic7b-q4_0.bin -p "吾輩は猫である"
 吾輩は猫である。
宅のバルコニアは、その音を受け入れており、毎日に夜間にその音が流れないと念的な感覚を感じます。 [end of text]

Cool! 日本語はちょい変ですが, つながりは自然でしょうか.

llama.cpp 出始めのときは日本語文字化けした記憶がありますが, 最新版(GGJT v2)では治っているっぽ?

Chat mode

https://github.com/ggerganov/llama.cpp#interactive-mode

examples/chat.sh, examples/chat-13B.sh を参照ください.

prompt を日本語化して, Vicuna 13B 5bit 版動かしてみました.

https://huggingface.co/eachadea/ggml-vicuna-13b-1.1

== Running in interactive mode. ==
 - Press Ctrl+C to interject at any time.
 - Press Return to return control to LLaMa.
 - To return control without starting a new line, end your input with '/'.
 - If you want to submit another line, end your input with '\'.

 好奇心旺盛な人間("USER")と人工知能アシスタント("ChatVicuna")との会話です. 人工知能アシスタントは, 人間の質問に詳細できちんとした返答をします.

USER: こんにちは, ChatVicuna.
ChatVicuna: こんにちは. いかがしましたか?
USER: ヨーロッパで最大の都市について教えてください.
ChatVicuna: はい, ヨーロッパで最大の都市はモスクワです. ロシアの首都です.
USER: こんにちは
ChatVicuna: こんにちは。何かお手伝いできることがありますか?
USER: 東京についておしえてください
ChatVicuna: 了解です。東京は日本の首都で、世界中でも有名な都市です。人口は約790万人で、世界第一に位置しています。観光スポットや美味しい食べ物、文化イベントなど多くの要素が集まっていることで知られていま
す。
USER: もっと詳しく東京について教えてください
ChatVicuna: はい、より詳しくは、東京は日本の主要なビジネス・Finance・キャリアヘッドクラウドを担当する都市でもあります。また、東京は近代史上最も重要な城市の1つであり、現在も国内最大の都市であり続けています。そのため、東京は多くの人々が住んでおり、多様な文化や生活風が存在しています。
USER: 

llama_print_timings:        load time = 22770.07 ms
llama_print_timings:      sample time =   656.69 ms /   316 runs   (    2.08 ms per token)
llama_print_timings: prompt eval time = 29039.91 ms /   254 tokens (  114.33 ms per token)
llama_print_timings:        eval time = 76810.34 ms /   316 runs   (  243.07 ms per token)
llama_print_timings:       total time = 187324.21 ms

Super cool!

https://twitter.com/syoyo/status/1658473862420627456?s=20

Ryzen9 3950X で 4 tokens/sec くらいでした.
ちょっと遅いけど耐えられるでしょうか.
7B だと 9 tokens/sec 出るので, 速度求めるかたは 7B 使うとよいでしょう.

LoRA ファインチューン model と組み合わせるとさらにいい感じになりそうです!

tokenizer, vocab ファイルについて

tokenizer は llama が利用している sentencepiece (のアルゴリズム)を自前 C++ 実装(!)しています

https://github.com/ggerganov/llama.cpp/issues/167

vocab ファイル(llama(huggingface transformers) でいえば tokenizer.model)は, convert.py.bin(GGML model)に埋め込まれますので, .bin だけあれば動くことになります.

ほかの LLM model

llama.cpp の github repo 漁れば, いくつかほかの LLM model 対応の情報があります.
あとは GPT4All(ややこしい名前であるが, GPT for All の略であり, ベーシックインカムや Worldcoin みたいな感じで, GPT-4 がみんなに無料で使えるようにするプロジェクトではない. llama.cpp を利用している)のサイトが参考になるでしょう.

https://gpt4all.io/index.html

たとえば MPT-7B は PR も出ているので, あとは merge されれば動きます.

https://github.com/ggerganov/llama.cpp/issues/1333

新しい LLM 出てきたら, 基本は ggml への model weight 変換と, tokenizer の vocab を convert すればいけるでしょう.

LLaMA では tokenizer のアルゴリズムが sentencepiece のため, sentencepiece 以外の tokenizer アルゴリズム(RWKV の新・多言語 tokenizer とか)を使っている場合は追加で実装いるでしょう.

GPT-2 の OpenAI の tokenizer(tiktoken) は, BPE ベースなので, 追加 C++ 実装がいるかもはしれません. ただ, llama.cpp 自体には BPE 実装もあるので, ↑の vocab データ(辞書)変換だけでいけるかも?(要検証)

ちなみに, tiktoken の日本語のトークナイズは効率が悪いです.

https://www.passaglia.jp/gpt-japanese/

ただそれでも GPT-3, GPT-4 の日本語がうまく扱えているのは興味深いところです.
(blog では日本語は改善の余地があるとはしている. またに日本語だけではなく各言語も取り入れて学習することでいい感じになることも指摘している)

ファインチューンいけそう. フルの学習もいけそう?

ggml backward を実装する対応も行われ始めています.

https://github.com/ggerganov/llama.cpp/pull/1360

LoRA finetuning であれば CPU でも十分な速度でファインチューンできそうな予感があります!
(高価なメモリマシマシ GPU いらない)

フルの学習(fp16 or fp32 で量子化なしで学習)も baby-llama として対応中です.

https://github.com/ggerganov/ggml/issues/8

llama.cpp の推論性能を見ると, 以外と CPU でもドメインをきっちり絞れば学習も CPU でも効率的にいけるのではないかと思っております(現状の pytorch CUDA 学習は GPU utilization 低かったりで効率が悪い感があるので)

CPU だと最近(2023 年)はメモリ不況でメモリ安価に調達できますし, 民生品マザーでも 128 GB/192 GB いけるので.

ただ, LLM の学習と推論は, 一定の演算量以上では帯域ネックになるため, A64FX みたいな HBM 系メモリ搭載な CPU でないと難しいカモしれません.

TODO

  • GPT4All なり text-generation-webui https://github.com/oobabooga/text-generation-webui などの GUI で llama.cpp うごかす
  • llama.cpp の Tokenizer の仕組みについてより調べる
    • なんか現状実装だと rare word がうまく扱えないみたいな issue を見た気がするので, きちんと検証してみる.
  • iOS, Android で動かしてみる

Discussion