背景
生成AIを普段業務の中でチャットBOTとして利用していますが、それだけでは昨今の流れに置いていかれると思い、色々実験を試みてみたいと思います。普段、生成AI開発をバリバリしているぜ!という方よりは、生成AIって何ですか、ローカルLLMって何ですか、という状態の方にとってご参考になるかもしれません。
生成AIとは?
ChatGPTのように、様々なコンテンツを生成できるAIのことを生成AIと総称しています。文章を生成し、自然な会話を実現することだけでなく、画像や音声処理も可能です。
ローカルLLM?
LLMとは大規模言語モデル(Large Language Models)の略で、大量のデータを学習したモデルのことです。昨今の生成AIを支える技術です。この生成AIを利用する方法としては、ChatGPTサービスのように外部の環境(クラウドなど)を利用する方法と、自身のパソコンにLLMを用意する方法があります。後者の方法をローカルLLMと呼びます。今回は、このローカルLLMを試してみたいと思います。
ローカルLLMのメリット
ローカルLLMは読んで字のごとく自身のパソコン内で稼働します。データを外部に送信しないため、セキュリティ的な面で安心できます。
ChatGPTやGeminiなど、アカウントを登録して利用するタイプのサービスの場合、生成AIへ入力した情報を外部へアップロードしてしまいます。
そのため、業務で機密情報を扱う場合、ローカルLLMの方がよりセキュアと言えます。
ローカルLLM利用時の注意点
セキュリティ的な面で安心と書きましたが1点注意したい点があります。それは、ローカルLLMが新たに学習した内容を回答に利用する可能性です。とある顧客情報の内容を学習したとして、その情報を回答に利用してしまう恐れがあります。そのため、できれば学習機能はOFFにしておきたいものです。
もしくは、プロジェクト毎にローカルLLMを用意し、以下のような管理を行う方法が考えられます。
- アクセス制限(プロジェクトメンバのみアクセスできるように管理)
- 他のメンバのチャットを閲覧できないようにする
- チャット履歴管理(管理者がメンバとBOTとのやりとりを定期的に監査する)
LLM実行環境(Jan)をダウンロード
LLMを実行できる環境をダウンロードします。今回は実行環境の1つである”Jan”を選択しました。以下サイトへアクセスします。
「Download for Windows」を選択します。
すると、「jan-win-x64-0.5.2.exe」というファイルがダウンロードされます。
LLM実行環境(Jan)をインストール
「jan-win-x64-0.5.2.exe」をダブルクリックし、インストールします。
Janの画面が立ち上がりました。
ローカルLLMを入手、チャットしてみる
Janの画面で「Explore The Hub」という部分をクリックします。
すると以下のような画面になります。
様々なローカルLLMをダウンロードできる画面でして、現在Janを動かしている環境に適したローカルLLMを選択できます。
RecommendedされているローカルLLMを選択してみます。
「Use」と「Download」でステータスが異なっています。「Use」のものはダウンロードせず利用できるということなのでしょうか。「OpenAI GPT 4 Turbo」を選択してみます。
おや、最初のページに戻されました。もしかしてJanを立ち上げた時点でローカルLLMがいくつか含まれているのかと思いチャットしてみます。
おや、APIキーが違うと怒られました。これはクラウド側のLLMっぽいですね。
Janの右上に「Model」というタブがあるのでクリックしてみます。
「OpenAI GPT 4 Turbo」が選択されています。右側の矢印をクリックしてみます。
Allにチェックが入っていますが、On-deviceにチェックを入れるとローカルLLMが表示されそうです。
Recommendedがリストアップされていません。。。気を取り直してLLMのダウンロード画面に戻ります。
Recommendedで「Download」となっている「TinyLlama Chat 1.1B Q4」を選択しダウンロードしてみます。
説明文には「TinyLlama is a tiny model with only 1.1B. It’s a good model for less powerful computers.」とあり、パワーのないパソコンでも動かせますよ、ということのようです。ちなみに、LLMの名称にはルールがあり、どのような性能をもったLLMかが読み取れます。
1.1B=11億パラメータ(Bはbillionの略)
q4=量子化のビット数は4ビット(qはquantizationの略)
つまり、「TinyLlama Chat 1.1B Q4」とは、4ビットで量子化された11億パラメータのTinyLlama Chatですよ、という意味になります。詳しい説明は多くのWebサイトにありますので割愛しますが、ざっくり以下イメージです。
- パラメータ数が多いとLLMの性能が高いが、高性能な環境が必要
- 量子化のビット数が少ないほどLLMが元のサイズから圧縮されており、性能が低い環境に適している
「TinyLlama Chat 1.1B Q4」のステータスが「Download」から「Use」に変わりました。
チャットしてみます。
日本語で質問をすると、英語で返ってきました。
生成スピードに関しては、上記で10秒くらいでした。
チャットをクリアしてもう一度同じ質問をしてみます。
今度は日本語で返ってきました。何故ラズパイで敵を落とすのか。。。
日本語は苦手なようなので、英語で質問してみます。スターウォーズの良いところを聞いてみました。
日本語訳です。
スター・ウォーズ」は、世界中の観客を魅了し続ける時代を超えた傑作です。
その魅力的なストーリーテリング、緻密な世界構築、そしてアイコニックなキャラクターたちは、文化的現象としての地位を確立しています。
初めて観る時も、お気に入りのエピソードを再び観る時も、スター・ウォーズは時代を超えた喜びを提供し、観客に 長期間にわたって感情的、文化的、または知的な影響 を与えます。
生成AIらしい、何とも言えない回答が来ました。さらに質問を続けます。
日本語訳です。ここからは吹き出しで。
Why does Darth Vader wear a mask?
ダースベイダーは何故マスクをしているのですか?
Darth Vader, also known as Darth Vader, is the sinister villain of the Star Wars franchise. He wears a mask on his face, which is a significant design element of his character. In the original Star Wars trilogy, Vader is depicted as a masked villain with a menacing presence. The mask serves as a visual representation of Vader’s character, as well as his power and danger. It is often suggested that Vader’s mask is a symbol of his inner turmoil and his fear of losing his power. In the newest Star Wars film, The Rise of Skywalker, Vader’s mask is revealed to be a symbol of his power and his willingness to use it for evil. The mask serves as a visual representation of the character’s evil nature and his willingness to do whatever it takes to protect the Emperor. Overall, Darth Vader wearing a mask is a significant design element in the Star Wars franchise, representing the character’s power, fear, and evil nature.
ダース・ベイダー(Darth Vader)は、スター・ウォーズシリーズの邪悪な悪役で、彼の顔にはマスクを着用しています。このマスクはキャラクターの重要なデザイン要素です。オリジナルのスター・ウォーズ三部作では、ベイダーはマスクをつけた恐ろしい存在として描かれています。このマスクはベイダーのキャラクターを視覚的に表現するとともに、彼の力や危険を示しています。また、ベイダーのマスクは彼の内面の葛藤や権力を失うことへの恐怖の象徴ともされています。最新作『スター・ウォーズ/スカイウォーカーの夜明け』では、ベイダーのマスクが彼の力とそれを悪用する意志を象徴するものであることが明らかにされます。このマスクはキャラクターの邪悪な本性と皇帝を守るために何でもする覚悟を視覚的に表しています。全体として、ダース・ベイダーのマスクはスター・ウォーズシリーズにおける重要なデザイン要素であり、キャラクターの力、恐怖、そして邪悪な本性を表しています。
What order do you recommend for watching the works?
どの順番で作品を見るのがお勧めですか?
Watching Star Wars in order is a personal preference, but here are some general recommendations based on the chronological order of the film series:
- Star Wars: The Phantom Menace (1999)
- Star Wars: Attack of the Clones (2002)
- Star Wars: Revenge of the Sith (2005)
- Star Wars: The Force Awakens (2015)
- Star Wars: The Rise of Skywalker (2019)
Each movie in the series is connected thematically and narratively, so it is recommended to watch them in the order they appear in the story. However, it is also possible to watch them in any order you choose, as the films are designed to be watched in a specific sequence.
スター・ウォーズを順番に観るかどうかは個人の好みによりますが、以下は映画シリーズの時系列に基づく一般的なおすすめの順序です:
- スター・ウォーズ エピソード1/ファントム・メナス (1999)
- スター・ウォーズ エピソード2/クローンの攻撃 (2002)
- スター・ウォーズ エピソード3/シスの復讐 (2005)
- スター・ウォーズ エピソード7/フォースの覚醒 (2015)
- スター・ウォーズ エピソード9/スカイウォーカーの夜明け (2019)
シリーズの各映画はテーマやストーリーでつながっているため、物語の順番で観ることが推奨されます。ただし、映画は特定の順序で観るように設計されているため、自分の好きな順番で観ることも可能です。
最後の質問への回答内容から、文脈を踏まえた回答ができていることがわかります。回答速度も気にならないレベルです。
「ダースベイダーが何故マスクをしているのか」への回答内容はネタバレを考慮してくれているのか、もしくは知らないのかは分かりかねますが、知っている人からすると「違うよ」という内容でした。
また、スターウォーズを観る順番については、エピソード4~6および8が抜けているのが気にはなりますが、実験としてはまぁまぁですね・・・(個人的にはエピソード4→5→6→1→2→3→7→8→9の順に、って脱線してますね)
スターウォーズまだ観たことない!という方は人生のほとんどを損しているといっても過言ではない名作ですので是非!(Amazonへのリンク貼っておきます)。
CPU、メモリ使用状況をモニターしてみる
JanにはCPU、メモリ使用状況をモニターする機能があります。「System Monitor」をクリックしてみます。
CPUとメモリの使用状況が表示されました。質問を投げたらどうなるか試してみましょう。
メモリ使用状況はほぼ変化ありませんが、CPU使用率が跳ね上がりましたね。
ちなみに質問内容はこちらです(英語で質問しています。日本語訳はChatGPTにお願いしました)。
Please describe the appeal of Star Wars: The Rise of Skywalker in 2000 characters.
スター・ウォーズ エピソード9/スカイウォーカーの夜明け (2019)の良い点を2000文字で教えてください。
Star Wars: The Risse of Skywalker is the final film in the Star Wars prequel trilogy. The film was released in 2019 and is the conclusion of the Star Wars saga. The film centers on Rey, Finn, and Poe Dameron, who are on a mission to find the last Jedi, Luke Skywalker. The film also introduces new characters like Ben Solo, aka Kylo Ren, and the First Order. The film is known for its epic action sequences, including the battle between Rey and Kylo Ren, as well as its themes of family, loyalty, and the conflict between light and darkness. Some of the film’s most memorable moments include the scene where Rey learns to control her powers, the scene where Finn and Poe Dameron are ambushed, and the final battle between Rey and Kylo Ren. The film has been widely praised for its action sequences, its themes of family, loyalty, and conflict between light and darkness, and for its innovative use of Star Wars mythology. Overall, while Star Wars: The Risse of Skywalker is not a “must-watch,” it is a significant conclusion to the Star Wars prequel trilogy, known for its epic action sequences, its themes of family, loyalty, and conflict between light and darkness.
『スター・ウォーズ: スカイウォーカーの夜明け』は、スター・ウォーズの前日譚三部作の最終作です。この映画は2019年に公開され、スター・ウォーズサーガの結末を迎えます。物語は、レイ、フィン、ポー・ダメロンが最後のジェダイ、ルーク・スカイウォーカーを探す任務に挑む様子を中心に展開します。また、ベン・ソロ(別名カイロ・レン)やファースト・オーダーといった新しいキャラクターも登場します。この映画は、レイとカイロ・レンの戦いを含む壮大なアクションシーンや、家族、忠誠心、光と闇の対立といったテーマで知られています。特に記憶に残るシーンとしては、レイが自分の力を制御する方法を学ぶ場面、フィンとポー・ダメロンが待ち伏せされる場面、そしてレイとカイロ・レンの最終決戦があります。映画は、そのアクションシーンの壮大さ、家族や忠誠心、光と闇の対立というテーマ、そしてスター・ウォーズ神話の革新的な利用が高く評価されています。全体として、『スター・ウォーズ: スカイウォーカーの夜明け』は「必見」とまでは言えないものの、スター・ウォーズの前日譚三部作の重要な結末であり、その壮大なアクションシーンとテーマが評価されています。
前日譚三部作?あまり聞きなれない言葉ですが、ちょっと違うような・・・全体的にはそれらしい回答です。(ファースト・オーダーはキャラクターではない、って細かい突っ込みしたくなりますがまた脱線・・・)
日本語対応のローカルLLMを試してみる
今後活用していこうとすると、どうしても日本語対応のローカルLLMが欲しくなります。そこで日本語に対応したローカルLLMがないか調べていると、「Llama-3-ELYZA-JP-8B-GGUF」というモデルが対応しているようです。早速試してみます。
ローカルLLMをHugging Faceからダウンロード
お目当てのモデルはJanでは提供していないようでしたので、Hugging Faceというサイトからダウンロードします。
https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B-AWQ/tree/main
「Clone repository」をクリックすると。。。
上記画面が現れました。まず、Gitをインストールする必要があります。
GItのダウンロード、インストール
以下サイトからGitをダウンロードします。
「Git-2.46.0-64-bit.exe」というファイルを実行します。画面指示に従いインストールします。
Gitの設定およびGit BASHを実行
Git BASHを実行するとコマンドプロンプトが現れますので、以下コマンドを打ち込みます。
$ git lfs install
Git LFS initialized.
$ git clone https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B-AWQ
Cloning into 'Llama-3-ELYZA-JP-8B-AWQ'...
remote: Enumerating objects: 48, done.
remote: Counting objects: 100% (44/44), done.
remote: Compressing objects: 100% (43/43), done.
remote: Total 48 (delta 17), reused 0 (delta 0), pack-reused 4 (from 1)
Unpacking objects: 100% (48/48), 2.65 MiB | 2.23 MiB/s, done.
Filtering content: 100% (2/2), 1.33 GiB | 1.11 MiB/s, done.
通信環境のせいなのか30分ほどかかりました。。。
pythonモジュールをインストール
Llama-3-ELYZA-JP-8B-GGUFを動かすために必要なパッケージをインストールします。
$pip install llama-cpp-python
Building wheel for llama-cpp-python (pyproject.toml) ... error
error: subprocess-exited-with-error
× Building wheel for llama-cpp-python (pyproject.toml) did not run successfully.
│ exit code: 1
╰─> [20 lines of output]
*** scikit-build-core 0.10.5 using CMake 3.30.2 (wheel)
*** Configuring CMake...
2024-08-28 16:41:10,601 - scikit_build_core - WARNING - Can't find a Python library, got libdir=None, ldlibrary=None, multiarch=None, masd=None
loading initial cache file C:\Users\xxx\AppData\Local\Temp\tmpx0w5k3o8\build\CMakeInit.txt
-- Building for: NMake Makefiles
CMake Error at CMakeLists.txt:3 (project):
Running
'nmake' '-?'
failed with:
no such file or directory
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
CMakeに関連したエラーのようです。
CMakeをダウンロードします。
cmake-3.30.2.zipというフォルダをC直下で解凍します。
解凍が終わったらパスを通します。Winキーを押し、設定をクリックします。
「設定の検索」から、「環境変数」を検索します。
「詳細設定」タブから「環境変数(N)」をクリックします。
Pathを選択した状態で編集をクリックします。
新規をクリックします。
パスに以下を追加します。
C:\cmake-3.30.2-windows-x86_64\bin
次に、visual studio build toolsをダウンロードします。
https://visualstudio.microsoft.com/ja/downloads
「VisualStudioSetup.exe」というファイルをクリックするとインストール開始します。
「C++によるデスクトップ開発」にチェックを入れてインストール続行します。
visual studio build toolsインストール後、再びpythonモジュールのインストールに挑戦します。
$pip install llama-cpp-python
Successfully built llama-cpp-python
Installing collected packages: MarkupSafe, diskcache, jinja2, llama-cpp-python
Successfully installed MarkupSafe-2.1.5 diskcache-5.6.3 jinja2-3.1.4 llama-cpp-python-0.2.89
成功したようです。
pythonでローカルLLMを実行
早速pythonで実行してみます。ソースコードは以下です。
from llama_cpp import Llama
import chainlit as cl
llm = Llama(
model_path="models/Llama-3-ELYZA-JP-8B-q4_k_m.gguf",
chat_format="llama-3",
n_ctx=1024,
)
async def process_message(user_message):
response = llm.create_chat_completion(
messages=[
{"role": "user", "content": user_message}
],
max_tokens=1024,
)
# デバッグ用にレスポンスを出力
print("Raw response:", response)
print("Response keys:", response.keys())
response_text = ""
if "choices" in response and len(response["choices"]) > 0:
choice = response["choices"][0]
print("Choice details:", choice)
if "message" in choice and "content" in choice["message"]:
response_text = choice["message"]["content"]
print("response_text:", response_text)
else:
response_text = "No content found in message."
else:
response_text = "No choices found in response."
return response_text
@cl.on_message
async def on_message(message: cl.Message):
user_message = message.content
print(f"Received user message: {user_message}")
response_text = await process_message(user_message)
print(f"Response text: {response_text}")
if response_text:
print("debug1")
await cl.Message(content=response_text).send()
print("debug2")
else:
await cl.Message(content="Error: No response text found.").send()
if __name__ == "__main__":
cl.start()
Chainlitの扱いに関してはGemini API利用時のコードをベースに修正しています。
気になった方はこちらをご覧ください。
以下コマンドでChainlitを起動します。
$chainlit run local-llm2.py
Chainlitが立ち上がりました。
犬と猫飼うならばどちらがよいか質問してみます。
50秒くらいすると”Could not reach the server”と表示されました。さらにLLMからの応答を待ちます。
ちなみに、実行中のCPUやメモリ使用量はこんな感じです。
実行環境は以下です。
- プロセッサ:Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz 2.11 GHz
- 実装RAM:8.00 GB (7.79 GB 使用可能)
- OS:Windows11(64bit)
3分ほど経過後、LLMからの応答がありましたが、GUI上は表示がありません。
<LLMからの応答(デバッグログより)>
犬と猫はどちらも人気のペットですが、性格や特徴が異なります。以下は、犬と猫の長所と短所をまとめたものです。
犬の長所と短所:
長所:
- 社会性が高く、人とコミュニケーションをとることができる。
- 鳴き声や体を使って表現することができる。
- 運動能力が高く、散歩や遊びに連れて行くことができる。
- 専門の訓練を受けることで、様々な仕事や競技に参加することができる。
短所:
- 鳴き声がうるさい。
- 専門の訓練やしつけが必要。
- 社会性が高いため、人と接することが多く、ストレスを感じることがある。
- 運動能力が高いため、散歩や遊びに時間と体力が必要。
猫の長所と短所:
長所:
- 独立心が高く、人と接することが少ない。
- 鳴き声が小さく、うるさくない。
- 専門の訓練やしつけが必要ない。
- 運動能力が低いため、散歩や遊びに時間と体力が必要ない。
短所:
- 社会性が低く、人とコミュニケーションをとることが少ない。
- 表現方法が限定的で、感情を表すことが少ない。
- 専門の訓練やしつけが必要ないため、問題行動をとることがある。
- 運動能力が低いため、散歩や遊びに連れて行くことが少ない。
以上は、犬と猫の長所と短所をまとめたものです。どちらがよいかは、個人の好みや生活スタイルによって異なります。
右下のマークに焦点を当てると”Stop Task”と表示されているため、タスクが終了していないように見えます。
pythonコード内で、ユーザーへ応答する非同期関数である”await cl.Message(content=response_text).send()”が実行されていないのかと思いデバッグログを仕込んでみました。
if response_text:
print("debug1")
await cl.Message(content=response_text).send()
print("debug2")
“debug1”,”debug2″両方ともログに残っているため、実行自体はされていそうです。
調べていると同じような問題を抱えている方々がいることを知りました。
https://github.com/Chainlit/chainlit/issues/274
彼らが言うには、「同期だからダメなんでは?非同期にすればいいよ!」とありますが、一応全て非同期のつもりなのです。
そもそも低パフォーマンス環境なので仕方ないっちゃ仕方ないということでGUI表示は断念。。。しようとして最後のあがきをしました。
あ、できた。。。以下のサイトに救われました!
https://qiita.com/taka_yayoi/items/3e233ab877030e7f05c0
process_messageという関数を非同期で定義していたので、非同期なはずと思っていましたが、これが機能していないのかもと思い、非同期の定義を外した上でmake_asyncしてみると上手くいきました(なんで?)。
修正後のpythonコードです。
from llama_cpp import Llama
import chainlit as cl
from chainlit import make_async
llm = Llama(
model_path="models/Llama-3-ELYZA-JP-8B-q4_k_m.gguf",
chat_format="llama-3",
n_ctx=1024,
)
def process_message(user_message):
response = llm.create_chat_completion(
messages=[
{"role": "user", "content": user_message}
],
max_tokens=1024,
)
# デバッグ用にレスポンスを出力
print("Raw response:", response)
print("Response keys:", response.keys())
response_text = ""
if "choices" in response and len(response["choices"]) > 0:
choice = response["choices"][0]
print("Choice details:", choice)
if "message" in choice and "content" in choice["message"]:
response_text = choice["message"]["content"]
print("response_text:", response_text)
else:
response_text = "No content found in message."
else:
response_text = "No choices found in response."
return response_text
async_process_message = make_async(process_message)
@cl.on_message
async def on_message(message: cl.Message):
user_message = message.content
print(f"Received user message: {user_message}")
response_text = await async_process_message(user_message)
print(f"Response text: {response_text}")
if response_text:
print("debug1")
await cl.Message(content=response_text).send()
print("debug2")
else:
await cl.Message(content="Error: No response text found.").send()
if __name__ == "__main__":
cl.start()
動かすだけで大分時間を使ってしまった。。。(私は犬より猫が好きです)
参考にさせていただいた記事
以下のサイトを参考にさせていただきました。ありがとうございます。
https://qiita.com/kenta1984/items/7233f8ec9d256f4fa4f7
https://qiita.com/T-H9703EnAc/items/4fbe6593d42f9a844b1c
https://qiita.com/suzukiyukizi/items/0f2b9c5eea212ed9266c
https://github.com/Chainlit/chainlit/issues/274
https://qiita.com/taka_yayoi/items/3e233ab877030e7f05c0
[…] Windows環境でローカルLLM(Llama)のチャットBOTを作成してみた PlantUMLとChatGPTを利用してシステムアクティビティ図を自動生成してみた Gemini APIを利用してWeb UIのチャットBOTを作成してみる Googleが提供する生成AI”Gemini”を利用してみた~通常利用、API使用まで~ ChatGPTでブログ記事を書いてみた […]