x=x+1がわからないのは逐次実行がわかっていないからで、記号を変えても解決にならない

x = x + 1がわからないという話が流行ってました。
で、「=ではなく:=を使えば」とか「イミュータブルにすれば」とかいう話が出たりしてますが、問題をあとまわしにしてるように見えます。
結局のところ、逐次実行がわかっていないことが表面化している場合がほとんどではないかと。
https://speakerdeck.com/kishida/lets-code-a-process?slide=8

プログラム以外の文章は、基本的にひとつの状態を示していて状態が遷移するということがありません。 推理小説なども、最終的な状態が徐々に開示されるだけともいえます。途中から読むと犯人が変わるということはないですね。もちろんそのように途中から読むと犯人が変わるような叙述トリックは可能ですが、通常はそうではないからトリックになるわけで。

つまり、x = x + 1という式は、x + 1という演算を行ってからx = その結果を行うという逐次実行があり、そしてxの状態が遷移するコードになっていて、演算・逐次実行・状態遷移の3要素が詰まっています。 ここで、逐次実行という概念を理解していないためにx = x + 1がわからないということになるわけです。
そのためx := x + 1x + 1 -> xのように表記を変えても、問題が緩和されるかもしれないけど解決にはならないと思います。

x = x + 1はイディオムとして覚えてしまうこともあるので、x = (x = x + 1) + xが読めなかったりしますね。

そしてこれはプログラムを教える人と教わる人の断絶をあらわしています。プログラムがわかるようになると逐次実行は空気のように当たり前で「わかる / わからない」というものではなくなります。一方でプログラムを初めて見る人には、逐次実行こそが異様なものです。
そのため「x = x + 1がわからないのはセンスがないからプログラムをやめたほうがいい」のような、問題から完全に目をそむけたような発言をする人があらわれたりもします。
もちろんセンスの問題ではなく、知識やトレーニングでカバーできるものです。

また、「変数は箱か場所か」のような論争も起きたりしますが、結局のところ学習者がそこでつまずくことはあまりなく、「変数がわからない」の実態は、逐次実行がわからないことからの、変数で表されている状態遷移がわかっていないことが多いように思います。
ループがわからないのも、逐次実行がわからないことが表面化しているだけです。

あと、「Haskellで解決」のようなこともみかけますが、そうすると状態遷移が必要なときに複雑な構文が必要になり、問題をあとまわしにして難しくすることにもなります。

REPLやデバッガなどで「プログラムが順に動く」ということを確認するのがいいと思います。 ということで「プロになるJava」ではREPLから始めてループではデバッガを見せ、そして段階的に難しくなるループを例に逐次実行と状態遷移を勉強するという構成にしています。

味噌汁

雑に味噌汁を食いたいときがあるわけですね。
でも、味噌を常備して鍋に湯を沸かして煮るとかめんどくさい。

と思ったら、なんか出汁入り味噌をお椀にとってお湯をいれるだけでいいという話をききつつ、そうするとチューブのやつがあるねってなりつつ、探すと粉のあった。
具もなんかある。

なので、粉を大さじ1

具も大さじ1

そうすると味噌汁になる。

この写真をとったとき、かきまぜが足りていなかったのだけど、ちゃんと混ぜるともっと味噌汁になった。そしておいしい。
冷蔵庫にいれておく必要もない。
なんなりかソリューションな気がする。

rinnaのYouri 7Bで8GB GPUでもLLM翻訳ができるかも

rinnaからLlama 2 7Bに日本語を追加学習させたYouri 7Bが公開されました。
rinna、Llama 2の日本語継続事前学習モデル「Youri 7B」を公開|rinna株式会社

このうち、Youri 7B Instructionが指示に応じた返答をするモデルになっています。このモデルを使って「この英文を日本語に翻訳して」などというと、和訳ができるわけです。
さらに、AutoGPTQを使った4bit量子化モデルも用意されているので、これを使うと8GBメモリくらいで日英翻訳ができそうです。

ということで、gradioでUIを作ってみました。

ソースはこちら。
https://gist.github.com/kishida/66b4e456e9e14362eb3d339f19815477

メモリは9GBを超えているけど、起動前後で4GBくらいを消費しているので、6GBくらいの余裕があれば動きそう。返答も10秒弱でした。

CNNのこちらの記事の冒頭を和訳するとこんな感じ。

7Bモデルをそのまま動かすと、GPUメモリ15.5GBつかっていて、生成に2分近くかかりました。

メモリを見ると終了時に12GBくらい解放されているので、2割くらいCPUで動いているんじゃないかと思います。

20GBあればいけるのかな。

load_in_8bitにすれば12GBメモリに載って、25秒くらいで返ってきました。

たまにハズレで間違った翻訳が出てくることがあるので、翻訳完全自動化には使えないですが、なかなかいい感じです。翻訳専用ではなく、他にも用途があることを考えると、このメモリ容量とスピードでちゃんと動くのはいいですね。

LINE LLMをMacで動かす

先日、LINE(現LINEヤフー)のNLP Foundation Devチームから日本語言語モデルが公開されました。(以降LINE LLMと表記します)
36億パラメータの日本語言語モデルを公開しました
Instruction Tuningにより対話性能を向上させた3.6B日本語言語モデルを公開します

でも「NVIDIAGPUがないと動かないんでしょ」と、試していない人も多いんではないでしょうか。
そこでこのブログでは、MacでLINE LLMを動かす手順をまとめたいと思います。
Windowsや他のLLMでもほぼ同じ手順で動かせるはずです。

次のような手順で進めます。

  • pythonインストール
  • ライブラリインストール
  • 1.7Bのサンプルコードを動かす
  • チャットインタフェースで1.7Bを動かす
  • CTranslateによる3.6B
  • llama.cppによる3.6B

Pythonインストール

LLMはPythonで組まれていることが多く、LINE LLMも例外ではないため、Pythonが必要です。すでにPythonをインストール済みであっても、バージョンが3.8以上が推奨されているので、古いものがインストールされている場合はアップデートしておきましょう。
Windowsの場合は3.11を直接インストールするのが無難です。最新は3.12ですが、対応していないライブラリがあるようです。
https://www.python.org/downloads/

ここでMacでは、pyenvを使ってPythonをインストールする手順を紹介します。またpyenvはHomebrewを使ってインストールします。
そこで、Homebrew、pyenv、Pythonの順にインストールしていきますが、すでにインストール済みであれば適宜飛ばしてください。

Homebrewインストール

Macで標準的に使われているソフトウェア管理ツールであるHomebrewを使います。

https://brew.sh/index_ja.html

インストールは次のように。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

環境設定を次のように行います。

(echo; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"') >> /Users/my_name/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

確認のためにバージョンを表示してみます。なにか表示されたら成功。

% brew -v
Homebrew 4.1.5

pyenv

Homebrewから直接Pythonをインストールすることもできますが、バージョン管理も行えたほうがいいため、pyenvを使います。

brew install pyenv

インストールがうまく行われたか確認するために、バージョンを見てみましょう。

% pyenv -v
pyenv 2.3.24

環境変数の設定などを行います。 zshでは次のコマンドを実行します。

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc

bashなどzsh以外のシェルを使っている場合は、pyenvのReadmeを確認してください。
https://github.com/pyenv/pyenv#set-up-your-shell-environment-for-pyenv

Windowsでpyenvを使いたい場合はpyenv-winを使うといいようです。
https://github.com/pyenv-win/pyenv-win

Python

さて、ようやくPythonのインストールです。ここでは3.11をインストールします。最新は3.12ですが、ライブラリが対応してなかったりするようです。

pyenv install 3.11

環境変数の設定

pyenv global 3.11

バージョン確認

% python -V
Python 3.11.4

ライブラリの準備

まずはpipの更新

pip install --upgrade pip

LLM用のフォルダを作っておく

いろいろ動かすためにLLM用のフォルダを作っておきましょう。

mkdir llm
cd llm

venvを使って、LLM用の環境も作っておきます。

python -m venv .venv
source .venv/bin/activate

環境を切り替えると、プロンプトがこんな感じの表示になります。

(.venv) llm % 

venvの環境を抜ける場合にはdeactivateコマンドを実行します。

% ./deactivate

PyTorch

機械学習ライブラリ、PyTorchをインストールします。Macの場合はpipで普通にインストールすることになりますが、念のため公式サイトを確認しておくのがいいでしょう。
https://pytorch.org/get-started/locally/

環境を選ぶとインストール用のコマンドが表示されます。

それを実行

% pip3 install torch torchvision torchaudio

結局pipでインストールできるのだけど、インストール時に公式を確認しておくほうがよさそう。

Transformersをインストール

ほとんどのLLMはHuggingFaceのTransformersライブラリを使います。LINE LLMもTransformersを使っているのでインストールします。

pip install transformers sentencepiece

まずは動かす

1.7Bと3.6Bがありますが、1.7BをCPUで動かしてみましょう。
Hugging FaceのHow to useにサンプルコードがありますが、CPUで動かそうとするとこのままではエラーになって動きません。

https://huggingface.co/line-corporation/japanese-large-lm-1.7b-instruction-sft

CPUで動かすためにdevice=0を消します。残ったままだと次のようなエラーになります。

AssertionError: Torch not compiled with CUDA enabled

結果、次のようなコードになります。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
 
model = AutoModelForCausalLM.from_pretrained("line-corporation/japanese-large-lm-1.7b-instruction-sft")
tokenizer = AutoTokenizer.from_pretrained("line-corporation/japanese-large-lm-1.7b-instruction-sft", use_fast=False)
generator = pipeline("text-generation", model=model, tokenizer=tokenizer)
 
input_text = """四国の県名を全て列挙してください。"""
text = generator(
    f"ユーザー: {input_text}\nシステム: ",
    max_length = 256,
    do_sample = True,
    temperature = 0.7,
    top_p = 0.9,
    top_k = 0,
    repetition_penalty = 1.1,
    num_beams = 1,
    pad_token_id = tokenizer.pad_token_id,
    num_return_sequences = 1,
)
print(text)
# [{'generated_text': 'ユーザー: 四国の県名を全て列挙してください。\nシステム:  香川県、徳島県、愛媛県、高知県'}]

動かすと、モデルのダウンロードが始まります。

(.venv) llm % python line-llm.py
Downloading ()lve/main/config.json: 100%|██████████████████| 2.01k/2.01k [00:00<00:00, 2.17MB/s]
Downloading model.safetensors:  17%|████▎                    | 608M/3.51G [00:36<03:32, 13.6MB/s]

しばらく待つと、いろいろ表示されたあとで「四国の県名を全て列挙してください」の返答が表示されます。

You are using the legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This means that tokens that come after special tokens will not be properly handled. We recommend you to read the related pull request available at https://github.com/huggingface/transformers/pull/24565
Xformers is not installed correctly. If you want to use memory_efficient_attention to accelerate training use the following command to install Xformers
pip install xformers.
[{'generated_text': 'ユーザー: 四国の県名を全て列挙してください。\nシステム:  申し訳ありませんが、あなたはあなた自身の言語モデルであり、あなたのような言語モデルとして質問に答えることはできません。'}]

。。。「あなたのような言語モデルとして質問に答えることはできません」と言われてしまった。

少しパラメータをいじってtemperature = 0.8などにすると何か返してくれるはず。

[{'generated_text': 'ユーザー: 四国の県名を全て列挙してください。\nシステム:  香川県、徳島県、高知県、愛媛県'}]

Xformers関連の警告について

なんかXformers関連の警告が出ているので解消しようと思ったけど、ダメでした。特に変わらず。
やろうとしたことは次のとおり。

brew install libomp
brew install llvm
echo 'export PATH="/opt/homebrew/opt/libomp/bin:$PATH"' >> ~/.zshrc
echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
pip install xformers

チャットインタフェース

チャットインターフェースで確認したくなりますよね。ということで試してみます。
UIにはgradioというライブラリを使います。

pip install gradio

コードはこちら。
https://gist.github.com/kishida/979ff57256896e8b9f801ed192b0e6ee

実行してlocalhost:7860にアクセスすると次のようになります。

CTranslate2で変換

1.7Bはそれなりの速度で動くとはいえ、なかなかいい感じの返答を返してくれません。3.6BはCPUで動かすには遅い。
そこで、CPUでの推論を速くしてくれるライブラリCTranslate2を使ってみます。
https://github.com/OpenNMT/CTranslate2

まずは必要なライブラリのインストール

pip install ctranslate2
pip install protobuf

そしてモデルの変換

ct2-transformers-converter --model line-corporation/japanese-large-lm-3.6b-instruction-sft --quantization int8 --output_dir line-3.6b-sft-ct2

次のコードで動かせます。

import ctranslate2
import transformers

model_name = "line-corporation/japanese-large-lm-3.6b-instruction-sft"
ct2_model = "line-3.6b-sft-ct2"

generator = ctranslate2.Generator(ct2_model)
tokenizer = transformers.AutoTokenizer.from_pretrained(model_name, use_fast=False)
prompt = "ユーザー: 日本の首都はどこ?\nシステム: "

tokens = tokenizer.convert_ids_to_tokens(tokenizer.encode(prompt, add_special_tokens=False))

results = generator.generate_batch(
    [tokens],
    max_length=256,
    sampling_topk=20,
    sampling_temperature=0.7,
)

text = tokenizer.decode(results[0].sequences_ids[0])
print(text)

チャットインタフェースは次のようになります。
https://gist.github.com/kishida/46e7d540c64b7b7a9eda3be987bbbc6b

llama.cpp

CPUを使った高速推論のフレームワークとしてllama.cppがあります。

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

ただ、llama.cppはGPTNeoXに対応していないので、mmngaさんがパッチをあてたものを使います。

git clone --branch mmnga-dev https://github.com/mmnga/llama.cpp.git

本家と区別するために名前を変えておきます。

mv llama.cpp llama.cpp.mmnga

そしてビルド

cd llama.cpp.mmnga
make -j gptneox

モデルの変換が必要ですが、すでに変換済みのモデルがあるので、こちらを使います。

https://huggingface.co/mmnga/line-corp-japanese-large-lm-3.6b-instruction-sft-gguf

次のファイルをダウンロードして、modelsフォルダに保存します。
line-corp-japanese-large-lm-3.6b-instruction-sft-q4_0.gguf

次のコマンドで推論できます。

./gptneox -m 'models/line-corp-japanese-large-lm-3.6b-instruction-sft-q4_0.gguf' -n 128 -p 'ユーザー: 吾輩って猫ですか? システム: '

llama.cppでは画像対応モデルも動くので、試してみてください。
画像対応モデルのLLaVAをMacで動かす - きしだのHatena

ファインチューニング

返答の形式を指定するなど、ベースモデルから出力の調整ことをファインチューニングといいます。
LINE LLMでも対話用にファインチューニングしたモデルが出ていますね。
ただ、Macでファインチューニングするのは厳しそうなので、Google Colabを使ってファインチューニングを試します。

語尾が「ござる」になるようにファインチューニングを行っていきます。 ChatGPTのような大きいLLMであれば「語尾をござるにしてください」で対応できますが、小さいLLM(小さい大規模言語モデル?)ではプロンプトでの出力指定は難しいので、ファインチューニングが必要です。

ここでは「ござるデータセット」を使ってファインチューニングを行います。
また、パラメータをすべて更新するチューニングはVRAMが足りないので、一部のパラメータだけを更新するLoRAというテクニックを使います。といってもPEFT(Parameter-efficient Fine-Tuning)というライブラリを使うだけです。

こちらのノートブックを読ませてひとつずつ実行するとチューニングが行えます。
40分ほどかかります。
https://gist.github.com/kishida/4fa70a66a7bc258a153ffbf178c04198

画像対応モデルのLLaVAをMacで動かす

画像認識対応モデルのLLaVAで、おうちでも設計画像からコードが生成できるようになりました。
LLaVAを使っておうちのパソコンでも画像からコード生成 - きしだのHatena

llama.cppが対応したことでMacでも動かしやすくなりました。
https://github.com/ggerganov/llama.cpp/tree/master/examples/llava

llama.cppは生成AIをc/cppで動かそうというプロジェクトですね。
https://github.com/ggerganov/llama.cpp

Macで動かす手順も簡単。
cloneして、

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

makeするだけ

make

動かすには、モデルを自分でコンバートするかコンバート済みのものをダウンロードする必要があります。
https://huggingface.co/mys/ggml_llava-v1.5-7b
https://huggingface.co/mys/ggml_llava-v1.5-13b

filesにあるファイルをダウンロードしてどこかに置いておきます。必要なのはggmo-model-....ggufのうちひとつとmmprojj-....ggufです。

今回は13Bのggml-model-q4-k.ggufとmmproj-model-f16.ggufをmodels/llava1.5-13Bというフォルダにダウンロードしました。

あとはllavaという実行ファイルができてるので呼び出すだけ。-m--mmprojにダウンロードしたモデルファイルを、--imageに画像を、-pにプロンプトを指定します。

./llava -m models/llava1.5-13B/ggml-model-q4_k.gguf
  --mmproj models/llava1.5-13B/mmproj-model-f16.gguf
  --image images/network.png
  -p 'write docker compose file'

そうするとこんな感じで動きます。なんかPyTorch版よりも思い込みが強い感じですね。

LLaVAを使っておうちのパソコンでも画像からコード生成

ChatGPTが画像対応して、画像からいろいろなコードが生成できて楽しいことになっていましたが、同じようなことをおうちで動かせるLLaVAが出ていたので試してみました。
GPUはVRAM 12GBあれば十分、8GBはギリギリという感じ。

LLaVA-1.5

先週、LLaVAのバージョンアップ版、LLaVA-1.5が出てました。

LLaVAはマイクロソフトウィスコンシン大学が開発しているマルチモーダルモデルです。
LLaVA: Large Language and Vision Assistant - Microsoft Research

デモはここ。
https://llava.hliu.cc/

このデモをおうちのWindowsで動かそうというのが今回のブログの趣旨

セットアップ

基本的にはGitHubの手順に従います。
https://github.com/haotian-liu/LLaVA

まずはClone

git clone https://github.com/haotian-liu/LLaVA.git
cd LLaVA

で、venvやらcondaの環境をお好みで。

python -m venv .venv
.venv\Scripts\activate.bat

そしてpip install -e .するのだけど、deepspeedはWindowsに対応していなくてインストール時にエラーが出るので、pyproject.tomlから消しておきましょう。
https://github.com/haotian-liu/LLaVA/blob/main/pyproject.toml#L20

あと、PyTorchがGPU非対応のものが入ってしまうので、自力でインストールします。
PyTorchのサイトを確認。
https://pytorch.org/

CUDA 11.8が入ってるとして、次のようにします。

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

bitsandbytesもWindows用バイナリが足りないものが入ってしまうので、これも自力で。
bitsandbytes-windows-webuiのバイナリを使います。
https://github.com/jllllll/bitsandbytes-windows-webui

pyproject.tomlでは0.41.0という指定になっているので、次のように。

python -m pip install bitsandbytes==0.41.0 --prefer-binary --extra-index-url=https://jllllll.github.io/bitsandbytes-windows-webui

これでようやくpip installができます。

pip install --upgrade pip
pip install -e .

コマンドラインで試す

Web UIも用意されてるけど手順がめんどいので、まずはコマンドラインで試します。改行してるけど、一行で。

python -m llava.serve.cli
  --model-path liuhaotian/llava-v1.5-7b 
  --image-file llava/serve/examples/extreme_ironing.jpg
  --load-4bit

7Bモデルを4bitで読み込んでいます。画像として指定しているのはタクシーの後ろでアイロンかける人ですね。
結果はこんな感じに。

Google翻訳するとこうなりました。

この画像の珍しい点は、男性が走行中の黄色いタクシーの後ろに立って、衣服にアイロンをかけていることです。 衣類のアイロンがけは通常、家や洗濯室などの室内で行われるため、これは典型的な場面ではありません。 走行中の車両の中で衣服にアイロンをかけるという男性の行為は異例であり、事故や怪我につながる可能性があり、潜在的に危険です。

メモリは、3.6GB使っていた状態から読み込み時点では8.5GBに、そして上記の結果を出力するときに10GBになっていたので、6.4GB使った感じですね。なので8GB VRAMでいけそう。

RTX 4060 Ti 16GBでのスピードはこんな感じです。

すごく雑には、こんな感じでメモリ使ってました。13B 8bitは16GBだと少しはみでて遅くなっている感じ。

モデル 7B 13B
normal(16bit) 15GB 26GB?
load-8bit 11GB 18GB?
load-4bit 7GB 11GB

Web UIで試す

さて、Web UI。3つ起動が必要でめんどい。
まずコントローラー

python -m llava.serve.controller

21001番ポートで起動します。

次にモデル管理のためのmodel_worker。
ただ、Windowsの日本語環境で実行すると、ログでエラーがでてそのエラーのログでエラーが出て、となって正常に動かないので、llava/utils.pyを修正します。
50行目にencoding='UTF-8'を追加します。プルリク出しているので、ここを参考に。
https://github.com/haotian-liu/LLaVA/pull/556/files

そしたら、こんな感じで動かします。ここでは13Bを4bitで読み込ませてます。実際には1行で。

python -m llava.serve.model_worker
  --model-path liuhaotian/llava-v1.5-13b --load-4bit

21002番ポートで起動します。

最後にWeb UI

python -m llava.serve.gradio_web_server --model-list-mode reload

7860番ポートで起動します。

この画像を投げて「write docker compose」と指定してみます。

なんかdocker composeファイルを書いてくれました!

動きとしてはこんな感じ。上記と別のタイミングの動画なので、内容はちょっと違います。

これでローカルでもいろいろできそうです!