はじめに
自宅でAI関連のコードを実行できる環境を準備して遊んでいるのですが、基本的に週末しか使わないので時間が経ってみたらこの前まで実行できてたコードが実行できなくなっていました。
調べてみると参考にしていた記事が公開されてからgradioのバージョンがv3からv5まで更新されているなど、システム側の変更点が大きいようでした。
2024年に作成された記事を検索してもgradio v3の環境を利用している例もあるようですが、ダイナミックに変化していく分野なのでできるだけ最新版を使おうと作業した時のメモを残しておきます。
参考資料
最初に環境を構築する時、参考にしていた資料です。
ローカル環境で、何かを学習(ファインチューニング)させた特定用途向けのChatBotを作ってみたいと思って、その準備として動作させています。
この他には公式サイトの情報などをみています。
Gradio 3から5に変更されているので、中間のGradio 4の変更点も必要に応じて参照しています。
環境
元々動作させていた環境からはアップグレードして次のような環境になっています。
- Ubuntu 24.04.1 amd64
- Python 3.12.3 with venv (Ubuntu 24.04の標準パッケージ)
nvidia-smi
コマンドの出力は次のとおりです。
Sun Oct 27 17:32:37 2024
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA RTX A4000 Off | 00000000:08:00.0 On | 0 |
| 41% 48C P8 20W / 140W | 1621MiB / 15352MiB | 31% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
Ubuntuのアップグレードなどで古いCUDA Toolkitも導入されていた形跡(/usr/local/cuda-11.7)はあるのですが、動作していなかったので最新のToolkit(12.5)を導入しています。
LLAMAモデルの変更
GGUF形式だったら何でも良いのかと思ったら次のようなエラーが表示されてしまいました。
gguf_init_from_file: GGUFv1 is no longer supported. please use a more up-to-date version
llama_model_load: error loading model: llama_model_loader: failed to load model from ggml-model-q4_m.gguf
元々利用していたのはなんとなく選択したggml-model-q4_m.gguf
でしたが、GGUFにもフォーマット違いがあるとこの時に知りました。
このタイミングでELYZAが配布している最新のLlama-3-ELYZA-JP-8B-q4_k_m.gguf
をダウンロードしました。
ollamaに認識させるために、Modelfileを準備する際に次の記事も参考にしています。
準備作業
ライブラリなどをvenvの環境に導入していきます。
$ python3 -m venv venv/llama
$ . venv/llama/bin/activate
最終的に次のようなrequirements.txtを利用しています。
## for RAG
langchain_community
langchain-core
## for http client
requests
## for HTML Splitter
lxml
## for RAG embedding
uuid
langchain-chroma
langchain-ollama
ライブラリをインストールします。
$ . venv/llama/bin/activate
(llama) $ pip install -f requirements.txt
コードの変更
実行しながらエラーに対応していきます。
1. retry_btn などの仕様変更
参考にしていたコードをそのまま実行すると次のようなメッセージが表示されます。
(llama) $ python3 chat.py
...
TypeError: ChatInterface.__init__() got an unexpected keyword argument 'retry_btn'
Gradio ChatInterface Docsをみると、そもそも ここで利用していないsubmit_btn
とstop_btn
を除き、retry_btn
を含めた*_btn
に関する変数自体が存在しないようです。
Migrating to Gradio 5では、ボタンのUIはgr.Chatbot
に統合されたとのことなので、これらの部分はとりあえず削除して実行していきます。
2. concurrencty_countが存在しない
TypeError: Blocks.queue() got an unexpected keyword argument 'concurrency_count'
これもMigrating to Gradio 5を確認すると、Gradio 4で既にdeprecated扱いだったので削除したという内容が確認できるので、Breaking Changes in Gradio 4.0を確認します。
コード上はqueue()のパラメータとしてconcurrency_count=1が指定されているのですが、デフォルト値のようです。concurrency_limit
に変更されてもデフォルトはそのままだということなので、queue()の引数から削除しています。
3. 初期メッセージをクリックするとerrorになる
一応ここまでの変更でGradioが起動して、対話的に動作するようになるのですが、examplesに指定している日本の四国にある県名を挙げてください。
というプロンプトをクリックするとエラーになります。
Inputダイアログに入力すれば正しく回答が表示されますが、クリックしてエラーになるというのも気持ちが悪いので修正しておきます。
ValueError: A function (example_clicked) didn't return enough output values (needed: 2, returned: 1).
Output components:
[chatbot, state]
Output values returned:
[('日本の四国にある県名を挙げてください。', '以下は日本の四国地方にある県名です。\n\n1. 愛媛県\n2. 高知県\n3. 香川県\n4. 徳島県\n\n以上、四国の4県を挙げました。')]
この問題は簡単に解決できるのかと思ったのですが、結構面倒でした。
デフォルトのtype="tuples"はdeprecated
まず実行時に次のようなメッセージが表示されます。
.../venv/llama/lib/python3.12/site-packages/gradio/components/chatbot.py:229: UserWarning: The 'tuples' format for chatbot messages is deprecated and will be removed in a future version of Gradio. Please set type='messages' instead, which uses openai-style 'role' and 'content' keys.
このためtype="messages"をChatInterface()の引数に指定して最終的には解決を目指します。
cache_examples=Trueがエラーを引き起す
とりあえずtype="tuples"のまま調査すると、cache_examples=Trueが次のようなエラーメッセージを引き起します。
FileNotFoundError: [Errno 2] No such file or directory: '.gradio/cached_examples/10/log.csv'
cache_examples=False
を指定すると、問題なく動作するようになりました。
ひとまずはここまでで満足することにしました。
type="messages" で動作させる
さて、最終的には type="messages" を指定して動作させたいので、動作するように変更していきます。
主な変更点は、generate_text()
で参照しているhistory
リストの中身が、{"role": "...", "content": "..."}
のような辞書型(dict)データ形式になるのでfor interaction in history:
で参照しているinteractionオブジェクトの参照がinput_prompt = input_prompt + "\nUSER: " + str(interaction["role"]) + "\nASSISTANT: " + str(interaction["content"])
のように変更になります。
また関数の最後で実行しているhistoryリストへの代入が、history.append({"role": "user", "content": input_prompt})
のような形になります。
cache_examples=False
であれば、ここまでで動作するようになります。
cache_examples=Trueにすると動作しない
ここからcache_examples=True
にすると動作しなくなります。
...
File ".../venv/llama/lib/python3.12/site-packages/gradio/chat_interface.py", line 678, in example_clicked
return self.examples_handler.load_from_cache(x.index)[0].root
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
Input should be a valid dictionary or instance of Message [type=model_type, input_value=['日本の四国にあ...方にあ
ます。'], input_type=list]
For further information visit https://errors.pydantic.dev/2.9/v/model_type
cacheから読み込まれるデータ形式が正しくなさそうなので、内容を確認します。
Chatbot,timestamp
"[[""\u65e5\u672c\u306e\u56db\u56fd\u306b\u3042\u308b\u770c\u540d\u3092\u6319\u3052\u3066\u304f\u3060\u3055\u3044\u3002"", ""\u4ee5\u4e0b\u306f\u56db\u56fd\u5730\u65b9\u306e\u770c\u540d\u3067\u3059\u3002\n\n\u611b\u5a9b\u770c\u3001\u9999\u5ddd\u770c\u3001\u9ad8\u77e5\u770c\u3001\u5fb3\u5cf6\u770c\n\n\u4ee5\u4e0a4\u770c\u304c\u56db\u56fd\u5730\u65b9\u306b\u3042\u308a\u307e\u3059\u3002""]]",2024-10-28 06:58:59.567660
内容は質問のリスト形式になっているので、type="messages"
で期待する{"role": "...", "content": "..."}
形式にはなっていないようです。
cacheディレクトリ(.gradio/cached_examples/10)を削除すると、エラーメッセージが少し変化します。
File "/home/yasu/project/llama_chatbot/venv/llama/lib/python3.12/site-packages/gradio/components/chatbot.py", line 323, in _check_format
raise Error(
gradio.exceptions.Error: "Data incompatible with messages format. Each message should be a dictionary with 'role' and 'content' keys or a ChatMessage object."
該当するコードを確認すると、次のような判定ロジックが正しく動作していないようでした。
if type == "messages":
all_valid = all(
isinstance(message, dict)
and "role" in message
and "content" in message
or isinstance(message, ChatMessage | Message)
for message in messages
)
if not all_valid:
raise Error(
"Data incompatible with messages format. Each message should be a dictionary with 'role' and 'content' keys or a ChatMessage object."
)
for message in messages
の内容が正しいフォーマットになっていないようです。
もう少し調べると、messages
はリスト形式のはずなのに要素のはずのdict形式の生データが入ってしまっているので、message
の中身は分割された('role', 'user')
のような2要素のタプルになることでall()
の条件判定に失敗していることが原因でした。
``