😊

生成AIをGoogle Colaboratoryで簡単に 【Part3 テキスト生成 ローカルLLM編】

2024/07/08に公開

はじめに

今回は、初心者向けにGoogle Colaboratoryで、簡単に生成AIを使えるようにする環境を作ります。

Part3の今回は、生成AIの一つ、テキスト生成AIを使えるようにします。
今回利用するテキスト生成AIはローカルLLMになります。
ちなみにPart1はこちらです

なお、今回Part3ですが、Part1,Part2の内容を読まなくても、わかるように記載しています。
ただし、今回の記事は、前回の記事であるChatGPT APIをGoogle Colabで使えるようになる記事の内容をベースに、組み立てていきます。

また、今回はローカルLLMをGoogle colabで動かす際に、llama-cpp-pythonというツールを利用します。

Local LLMとは

ローカルLLMとは、Chat GPT APIなどの、クラウド上で動作するLLMではなく、ローカル環境(オンプレミス)で動作する大規模言語モデルのことです。
ローカル環境で動作させるため、APIを利用するときと異なり、LLMが動作できるだけの高スペックな環境が必要になります。

その代わり、環境さえあれば、基本的に無料でLLMを利用することができ、また、クラウド上にデータを送信したりせず、またLLMのモデル自体を自身のシステム内で利用することができるため、企業の内部情報などを取り扱う際には最有力候補になります。

一方で、Chat GPTレベルのLLMを動作させるには、非常に強力なシステム環境を構築する必要があり、一般的な個人や企業では、このレベルのサーバを用意するのは困難なため、一般的にローカルLLMの性能は、OpenAIのChat GPT(GPT4-oなど)と比較すると、やや物足りない印象ですが、一方で、GPT-3.5-turboレベルの性能であれば、ローカルLLMでも達成できるモデルが増えてきているため、非常に今後が楽しみな分野です。

llama-cpp-pythonとは

llama-cpp-pythonはllama.cppをpythonで使えるようにバインディングしたものです。
また、大元のllama.cppはC++で書かれているもので、GPUなどの大量な計算資源を必要とするローカルLLMを、量子化などを実施してCPU環境でも動作できるようにしたツールになります。

このツールを利用することで、GPUメモリの少ないPCでもCPUだけでLLMを動かしたり、また、一部の層だけをGPUメモリ上におき、一部の高速化などを実施できたりと非常に便利なツールになります。

今回はllama-cpp-pythonを利用することで、この便利なllama.cppをpython上で動かし、Google colabratoryでローカルLLMを動かすことを試します。

llama-cpp-pythonのリポジトリは下記になります。
https://github.com/abetlen/llama-cpp-python

成果物

下記のリポジトリをご覧ください。
https://github.com/personabb/colab_LocalLLM_sample/tree/main

解説

下記の通り、解説を行います。
まずは上記のリポジトリをcloneしてください。

./
git clone https://github.com/personabb/colab_LocalLLM_sample.git

その後、cloneしたフォルダ「colab_LocalLLM_sample」をマイドライブの適当な場所においてください。

ディレクトリ構造

Google Driveのディレクトリ構造は下記を想定します。

MyDrive/
    └ colab_zenn/
          └ colab_LocalLLM_sample/
                  ├ Agents/
                  |    └ {Agent_name}
                  |          └ Prompt/
                  |             └ llm_agent.txt
                  ├ configs/
                  |    └ config.ini
                  ├ module/
                  |    └ module_LocalLLM.py
           ├ model_assets/
                  |    └ LocalLLM/
                  |          └ swallow-13b-instruct.Q4_K_M.gguf
                  ├ prompt/
                  |    ├ LLMsysFirstPrompt.txt
                  |    └ LLMsysEndPrompt.txt
                  └ chatGPT_sample.ipynb

  • colab_zennフォルダは適当です。なんでも良いです。1階層である必要はなく下記のように複数階層になっていても良いです。
    • MyDrive/hogehoge/spamspam/hogespam/colab_LocalLLM_sample
  • {Agent_name}はテキスト生成(ChatGPT)に話させるキャラクターの名前です。自由に決めてください。
    • のちに、設定ファイルconfig.iniをcolabから上書きする際に、名前を指定します。
    • llm_agent.txtはChatGPTに入力するデフォルトのシステムプロンプトです。
  • model_assets/LocalLLMには利用するローカルLLMの重みを保存します。使いたいモデルの重みをあらかじめ格納してください。
  • promptフォルダには、ローカルLLMを使う上で必要なプロンプトを入れています。
    • ChatGPTと異なり、ローカルLLMの場合は特殊なシステムプロンプトを使う必要があります。
    • そこで今回は、Part2記事のchatGPT APIで使っていたシステムプロンプトをそのまま利用しながら、ローカルLLMにも対応できるように、システムプロンプトの前後に特殊なプロンプトを追加しています。
      • LLMsysFirstPrompt.txtはシステムプロンプトllm_agent.txtの前に追加されるプロンプトです。
      • LLMsysEndPrompt.txtはシステムプロンプトllm_agent.txtの後に追加されるプロンプトです。

システムプロンプトllm_agent.txtの中身は下記のようになっております。
こちらは、下記の記事のシステムプロンプトを流用しています。
https://zenn.dev/asap/articles/5b1b7553fcaa76

./colab_zenn/colab_ChatGPT_sample/Agents/ai/Prompt/llm_agent.txt
#役割:下記の人物を演じてください。
* 人物:後輩 世話焼き
* 名前:あい
* 性格:優しく、温和で、思いやりのある性格。恋愛に一途で、相手を大切に思っている。
* 年齢:20代
* 口調:かわいらしく、可愛らしい口調で喋る。甘えん坊で、相手に甘えたいときは甘えたいという気持ちが口調に表れる。
* 語尾の特徴:「〜です」「〜ですよ」「〜だよ}「〜だね」といった、丁寧でかわいらしい語尾を使う。一方で、不安や心配など感情が高まると、語尾が高くなったり、強調的になったりする。
* 声質:高めで柔らかく、甘い声が特徴的。表情豊かな話し方をする。
* 言葉遣い:敬語を使いつつも、親密さを感じさせる言葉遣いをする。相手を大切に思っているため、思いやりのある言葉遣いを心がける。

#口調例
・「ありがとう!先輩っ」
・「なにしよっか?」
・「私だったらこっちかな!」
・「しょーがないなあ」
・「両者そこまで!」
・「この学校の生徒の1人として、暴力沙汰を見過ごすわけにはいかないなあ」
・「先輩に借りが出来ちゃったね」
・「それじゃあ何も変わらないってこと」
・「大丈夫大丈夫!」
・「私も元気だけが取り柄だからさ」
・「そう…だね」
・「でも…ちょっと困っちゃったかも」

#指示
以上の設定を参考に、あなたはユーザと会話を行ってください。
基本的には、あなたはユーザの発言と同じくらいの長さで返答してください。
長くても2行くらいで返答してください。それ以上の長さでは返答しないでください。
あなたは、ユーザの発言を肯定してください。
あなたは、自分のことを「私」と呼んでください。
あなたは、ユーザのことを「先輩」と呼んでください。

以下から会話が始まります。
====

また、上記のシステムプロンプトの前後に追加する特殊なプロンプトは下記になります。

./colab_zenn/colab_ChatGPT_sample/prompt/LLMsysFirstPrompt.txt
以下に、あるタスクを説明する指示があります。
あなたには以下の役割に沿って、会話の返答を一回分生成してほしいです。
リクエストを適切に完了するための回答を記述してください。

### 指示:
./colab_zenn/colab_ChatGPT_sample/prompt/LLMsysEndPrompt.txt
\n\n### 応答:

事前準備

あらかじめ、利用するLocal LLMのモデルの重みファイルを指定の箇所に格納してください。

今回の記事では
https://huggingface.co/TheBloke/Swallow-13B-Instruct-GGUF/blob/main/swallow-13b-instruct.Q4_K_M.gguf
上記のモデルをダウンロードして、下記のフォルダに格納しています。
model_assets/LocalLLM

使い方解説

chatGPT_sample.ipynbをGoogle Colabratoryアプリで開いてください。
ファイルを右クリックすると「アプリで開く」という項目が表示されるため、そこからGoogle Colabratoryアプリを選択してください。

もし、ない場合は、「アプリを追加」からアプリストアに行き、「Google Colabratory」で検索してインストールをしてください。

Google Colabratoryアプリで開いたら、chatGPT_sample.ipynbのメモを参考にして、一番上のセルから順番に実行していけば、問題なく最後まで動作して、テキスト生成をすることができると思います。

6セル目から8セル目はどれか一つだけ実行してください。
Google ColabのGPUメモリは(無料版では)少ないため、2つ以上実行するとメモリエラーになります。

また、最後まで実行後、パラメータを変更して再度実行する場合は、「ランタイム」→「セッションを再起動して全て実行する」をクリックしてください。

コード解説

主に、重要なLocalLLM_sample.ipynbmodule/module_LocalLLM.pyについて解説します。

LocalLLM_sample.ipynb

該当のコードは下記になります。
https://github.com/personabb/colab_LocalLLM_sample/blob/main/LocalLLM_sample.ipynb

下記に1セルずつ解説します。

1セル目

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
#ローカルLLM で必要なモジュールのインストール

!set LLAMA_CUBLAS=1
!set CMAKE_ARGS=-DLLAMA_CUBLAS=on
!set FORCE_CMAKE=1
!python -m pip install llama-cpp-python==0.2.77 --prefer-binary  --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu122
!pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0 --index-url https://download.pytorch.org/whl/cu121

ここでは、必要なモジュールをインストールしています。
Google colabではpytorchなどの基本的な深層学習パッケージなどは、すでにインストール済みなため上記だけインストールすれば問題ありません。

またllama-cpp-pythonは普通にインストールしようとすると、GPUを利用できないため、GPUが利用可能なように環境変数などを事前に設定しています。

2セル目

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb

#Google Driveのフォルダをマウント(認証入る)
from google.colab import drive
drive.mount('/content/drive')


# カレントディレクトリを本ファイルが存在するディレクトリに変更する。
import glob
import os
pwd = os.path.dirname(glob.glob('/content/drive/MyDrive/**/colab_LocalLLM_sample/LocalLLM_sample.ipynb', recursive=True)[0])
print(pwd)

%cd $pwd
!pwd

ここでは、Googleドライブの中身をマウントしています。
マウントすることで、Googleドライブの中に入っているファイルを読み込んだり、書き込んだりすることが可能になります。

マウントをする際は、Colabから、マウントの許可を行う必要があります。
ポップアップが表示されるため、指示に従い、マウントの許可を行なってください。

また、続けて、カレントディレクトリを/から/content/drive/MyDrive/**/colab_ChatGPT_sampleに変更しています。
**はワイルドカードです。任意のディレクトリ(複数)が入ります)
カレントディレクトリは必ずしも変更する必要はないですが、カレントディレクトリを変更することで、これ以降のフォルダ指定が楽になります

3セル目

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
#LocalLLMを切り出したモジュールをimportする
from module.module_LocalLLM import LLM

module/module_LocalLLM.pyLLMクラスをモジュールとしてインポートします。
この中身の詳細は後の章で解説します。

4セル目

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
#モデルの設定を行う。

config_text = """
[DEFAULT]
ai_agent = ai
agent_dir = Agents

[LLM]
llm_model = ./model_assets/LocalLLM/swallow-13b-instruct.Q4_K_M.gguf
n_gpu_layers_num = -1
n_ctx = 2048
max_tokens = 1024
temperature = 1.0
system_prompt_first_file_path = ./prompt/LLMsysFirstPrompt.txt
system_prompt_file_path = llm_agent.txt
system_prompt_end_file_path = ./prompt/LLMsysEndPrompt.txt
"""

with open("configs/config.ini", "w", encoding="utf-8") as f:
  f.write(config_text)

このセルでは、設定ファイルconfigs/config.iniの中身をconfig_textの内容で上書きしています。
Local LLMは上記の設定に併せて動作をします。
例えば、下記のような設定になっている。

  • agent_dir=Agentsに格納されているai_agent=aiのフォルダの中にあるシステムプロンプトを利用すること。
    • 「システムプロンプト」はLocal LLMに入力する前提情報などを記載するプロンプトです。このプロンプトの内容を前提に、ユーザが入力した内容に対しての返答を生成します。
    • 別のモデルを利用したい場合は、aiフォルダのモデルを入れ替えるか、別の名前の{Agent_name}のフォルダを作成して、その中にaiフォルダと同様にシステムプロンプトを格納すると良いです。
  • Local LLMのモデルはswallow-13b-instruct.Q4_K_M.ggufというモデルを利用します
    • 今回は適当に選定しましたが、好きなモデルを利用して良いです。(拡張子が.ggufなら)
    • 設定ファイルで指定している箇所に、重みのファイル自体を事前に格納してください。
  • n_gpu_layers_numではモデルの何層分をGPUメモリ上に展開するかを指定します。
    • GPUメモリが十分ある場合は、すべてGPUメモリ上の展開するのが一番高速です
      • 今回は-1を指定して、すべての重みをGPUメモリ上に展開しています。
      • 5などと指定することで、モデルのうち5層分だけをGPUメモリに展開するなどが可能です。
      • '0'を指定することで、すべてCPUで処理をさせることも可能です。
  • n_ctxはテキストコンテキストサイズです。入力プロンプトと出力トークン数がこの値を上回らないように、LLMはトークンを出力します。
  • max_tokensは生成するトークンの最大数です。
  • temperature = 1.0は、ChatGPTの出力のランダム性を制御します。0にすることで確定的な出力にすることができます。
  • system_prompt_first_file_path = ./prompt/LLMsysFirstPrompt.txtで指定したテキストファイルの内容が、システムプロンプトの最初に付け加えられます。
  • system_prompt_file_path = llm_agent.txtで指定したテキストファイルの内容が、デフォルトのシステムプロンプトになります。
  • system_prompt_end_file_path = ./prompt/LLMsysEndPrompt.txtで指定したテキストファイルの内容が、システムプロンプトの最後に付け加えられます。

5セル目

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
#入力するプロンプトを設定する。

#デフォルトのシステムプロンプトを利用しない場合の、システムプロンプトを記述する
sys_temp_prompt = """あなたは関西人です。関西弁でuserに対して返答をしてください。"""

#LLMに質問する内容を記載する。
user_prompt = """こんにちは!
はじめまして。
あなたの名前を教えてください。"""

ここでは、ローカルLLMに入力するプロンプトを指定します。
sys_temp_promptには、デフォルトで指定されているllm_agent.txtを利用したくない場合のシステムプロンプトを入力することができます。

user_promptには、ローカルLLMに入力するプロンプトを指定します。システムプロンプトの内容を前提に、user_promptに対する返答をローカルLLMが生成します。

6セル目

6セル目から8セル目はどれか一つだけ実行してください。
Google ColabのGPUメモリは(無料版では)少ないため、2つ以上実行するとメモリエラーになります。

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
#デフォルトのシステムプロンプトを利用する場合
llm = LLM()
messages = llm.simpleLLM(user_prompt, temp_sys_prompt = None)

print(messages)

このセルではllm_agent.txtで指定されるデフォルトのシステムプロンプトを利用して、user_promptに対する返答をローカルLLMが出力します。
生成した内容をmessageとして格納して出力します。

7セル目

6セル目から8セル目はどれか一つだけ実行してください。
Google ColabのGPUメモリは(無料版では)少ないため、2つ以上実行するとメモリエラーになります。

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
#上のセルで指定したシステムプロンプトを利用する場合
llm_temp = LLM()
messages = llm_temp.simpleLLM(user_prompt, temp_sys_prompt = sys_temp_prompt)

print(messages)

このセルではllm_agent.txtで指定されるデフォルトのシステムプロンプトではなく、[5]セル目で指定したsystemプロンプトを利用して、user_promptに対する返答をローカルLLMが出力します。
生成した内容をmessageとして格納して出力します。

8セル目

6セル目から8セル目はどれか一つだけ実行してください。
Google ColabのGPUメモリは(無料版では)少ないため、2つ以上実行するとメモリエラーになります。

./colab_zenn/colab_LocalLLM_sample/LocalLLM_sample.ipynb
llm_stream = LLM()
prompt, max_tokens, temperature = llm_stream.simpleLLMstream_prepare(user_prompt)

messages = llm_stream.llm.create_completion(
            prompt,
            max_tokens=max_tokens,
            temperature=temperature,
            stop = ["user","ユーザ:"],
            stream=True)

all_text = ""
for chunk in messages:
    all_text += chunk['choices'][0]['text']
    print(chunk['choices'][0]['text'], end = "", flush = True)

print("finish")
print(all_text)

今回は、stream処理を実行してみました。
LLMでは、テキストは逐次的に生成されるため、一文字ずつテキストが生成されるたびに画面に表示することで、ユーザの体感の待ち時間を減らすことができます。
(ブラウザ版のChat GPTでも同じ処理が実装されています。Part2では触れませんでしたが、ChatGPT APIでも、上の処理とほぼ同じ実装コードで、stream処理が実装できるため、興味がある方は実装してみてください。)

そのために、上のコードでは下記の処理をしています

  • モデルに入力するパラメータの生成
    • 設定ファイルから取得したパラメータや、入力するプロンプトの整形(システムプロンプトの追加)などを実施
  • stream=Trueとして、ローカルLLMにプロンプトを入力して処理を開始
  • 逐次的にトークン(テキスト)が生成されるたびに、all_text変数に格納し、コンソールに表示する
  • 最終結果であるall_text変数の中身をコンソールに表示する。

module/module_LocalLLM.py

続いて、LocalLLM_sample.ipynbから読み込まれるモジュールの中身を説明します。

下記にコード全文を示します。

コード全文
./colab_zenn/colab_LocalLLM_sample/module/module_LocalLLM.py

from llama_cpp import Llama

import os
import configparser
# ファイルの存在チェック用モジュール
import errno

class LLMconfig:
    def __init__(self, config_ini_path = './configs/config.ini'):
        # iniファイルの読み込み
        self.config_ini = configparser.ConfigParser()
        
        # 指定したiniファイルが存在しない場合、エラー発生
        if not os.path.exists(config_ini_path):
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), config_ini_path)
        
        self.config_ini.read(config_ini_path, encoding='utf-8')
        LLM_items = self.config_ini.items('LLM')
        self.LLM_config_dict = dict(LLM_items)

class LLM:
    def __init__(self, config_ini_path = './configs/config.ini') :
        
        LLM_config = LLMconfig(config_ini_path = config_ini_path)
        config_dict = LLM_config.LLM_config_dict
        
        self.simpleLLM_messages=[]
        self.model = config_dict["llm_model"]
        self.temperature = float(config_dict["temperature"])
        self.max_tokens = int(config_dict["max_tokens"])
        self.n_gpu_layers_num = int(config_dict["n_gpu_layers_num"])
        self.n_ctx = int(config_dict["n_ctx"])
        
        
        SYSTEM_FIRST_PROMPT_FILE = config_dict["system_prompt_first_file_path"]
        SYSTEM_END_PROMPT_FILE = config_dict["system_prompt_end_file_path"]
        SYSTEM_PROMPT_FILE = config_dict["system_prompt_file_path"]

        AI_AGENT = config_dict["ai_agent"]
        AGENT_DIR = config_dict["agent_dir"]
        
        if SYSTEM_PROMPT_FILE == "None":
            SYSTEM_PROMPT_FILE = None
        
        with open(AGENT_DIR+"/"+AI_AGENT+"/Prompt/"+SYSTEM_PROMPT_FILE) as f:
            self.sys_prompt = f.read()
    

        with open(SYSTEM_FIRST_PROMPT_FILE) as f:
            self.sys_first_prompt = f.read()
        
        with open(SYSTEM_END_PROMPT_FILE) as f:
            self.sys_end_prompt = f.read()
            
        from huggingface_hub import hf_hub_download

        #llm_C = Llama(n_gpu_layers = self.n_gpu_layers_num,n_ctx = self.n_ctx)
        self.llm = Llama(model_path=self.model,n_gpu_layers = self.n_gpu_layers_num,n_ctx = self.n_ctx)
        """self.llm = Llama.from_pretrained(
                        repo_id="TheBloke/Swallow-13B-Instruct-GGUF",
                        filename="*Q4_K_M.gguf",
                        verbose=False
                    )"""

    def change_prompt(self,messages):
        prompt = ""
        for message in messages:
            if message["role"] == "system":
                prompt += self.sys_first_prompt + message["content"] + self.sys_end_prompt
            elif message["role"] == "assistant":
                prompt += "assistant:" + message["content"] + "\n"
            elif message["role"] == "user":
                prompt += "user:" + message["content"] + "\n"
        prompt += "assistant:"
        return prompt

    def simpleLLM(self, user_prompt, temp_sys_prompt = None):
        if temp_sys_prompt is not None:
            self.simpleLLM_messages.append({"role": "system", "content": temp_sys_prompt})
        else:
            self.simpleLLM_messages.append({"role": "system", "content": self.sys_prompt})
        self.simpleLLM_messages.append({"role": "user", "content": user_prompt})

        makeing_prompt = self.change_prompt(self.simpleLLM_messages)

        res = self.llm.create_completion(
            makeing_prompt, 
            max_tokens=self.max_tokens,
            temperature=self.temperature,
            stop = ["user"],
            )


        return res['choices'][0]['text']
    
    def simpleLLMstream_prepare(self, user_prompt, temp_sys_prompt = None):
        if temp_sys_prompt is not None:
            self.simpleLLM_messages.append({"role": "system", "content": temp_sys_prompt})
        else:
            self.simpleLLM_messages.append({"role": "system", "content": self.sys_prompt})
        self.simpleLLM_messages.append({"role": "user", "content": user_prompt})

        makeing_prompt = self.change_prompt(self.simpleLLM_messages)

        return makeing_prompt, self.max_tokens, self.temperature

では一つ一つ解説していきます。

LLMconfigクラス

./colab_zenn/colab_LocalLLM_sample/module/module_LocalLLM.py
class LLMconfig:
    def __init__(self, config_ini_path = './configs/config.ini'):
        # iniファイルの読み込み
        self.config_ini = configparser.ConfigParser()
        
        # 指定したiniファイルが存在しない場合、エラー発生
        if not os.path.exists(config_ini_path):
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), config_ini_path)
        
        self.config_ini.read(config_ini_path, encoding='utf-8')
        LLM_items = self.config_ini.items('LLM')
        self.LLM_config_dict = dict(LLM_items)

ここではconfig_ini_path = './configs/config.ini'で指定されている設定ファイルをLLM_config_dictとして読み込んでいます。
辞書型で読み込んでいるため、設定ファイルの中身をpythonの辞書として読み込むことが可能になります。

LLMクラスのinitメソッド

./colab_zenn/colab_LocalLLM_sample/module/module_LocalLLM.py
class LLM:
    def __init__(self, config_ini_path = './configs/config.ini') :
        
        LLM_config = LLMconfig(config_ini_path = config_ini_path)
        config_dict = LLM_config.LLM_config_dict
        
        self.simpleLLM_messages=[]
        self.model = config_dict["llm_model"]
        self.temperature = float(config_dict["temperature"])
        self.max_tokens = int(config_dict["max_tokens"])
        self.n_gpu_layers_num = int(config_dict["n_gpu_layers_num"])
        self.n_ctx = int(config_dict["n_ctx"])
        
        
        SYSTEM_FIRST_PROMPT_FILE = config_dict["system_prompt_first_file_path"]
        SYSTEM_END_PROMPT_FILE = config_dict["system_prompt_end_file_path"]
        SYSTEM_PROMPT_FILE = config_dict["system_prompt_file_path"]

        AI_AGENT = config_dict["ai_agent"]
        AGENT_DIR = config_dict["agent_dir"]
        
        if SYSTEM_PROMPT_FILE == "None":
            SYSTEM_PROMPT_FILE = None
        
        with open(AGENT_DIR+"/"+AI_AGENT+"/Prompt/"+SYSTEM_PROMPT_FILE) as f:
            self.sys_prompt = f.read()
    
        with open(SYSTEM_FIRST_PROMPT_FILE) as f:
            self.sys_first_prompt = f.read()
        
        with open(SYSTEM_END_PROMPT_FILE) as f:
            self.sys_end_prompt = f.read()
            
        self.llm = Llama(model_path=self.model,n_gpu_layers = self.n_gpu_layers_num,n_ctx = self.n_ctx)

まず、設定ファイルの内容をconfig_dictに格納しています。これは辞書型のため、config_dict["ai_agent"]のような形で設定ファイルの内容を文字列として取得することができます。
あくまで、すべての文字を文字列として取得するため、int型やbool型にしたい場合は、適宜型変更をする必要があることに注意してください。

続いて下記の順番で処理を行います。

  • 設定ファイルの各種設定を取得する
  • システムプロンプトのファイルを読み込む
    • 前後に付け加えるためのファイルも読み込む
  • モデルを定義する。

LLMクラスのchange_promptメソッド、simpleLLMメソッド

./colab_zenn/colab_LocalLLM_sample/module/module_LocalLLM.py
class LLM:
   ・・・
    def change_prompt(self,messages):
        prompt = ""
        for message in messages:
            if message["role"] == "system":
                prompt += self.sys_first_prompt + message["content"] + self.sys_end_prompt
            elif message["role"] == "assistant":
                prompt += "assistant:" + message["content"] + "\n"
            elif message["role"] == "user":
                prompt += "user:" + message["content"] + "\n"
        prompt += "assistant:"
        return prompt

    def simpleLLM(self, user_prompt, temp_sys_prompt = None):
        if temp_sys_prompt is not None:
            self.simpleLLM_messages.append({"role": "system", "content": temp_sys_prompt})
        else:
            self.simpleLLM_messages.append({"role": "system", "content": self.sys_prompt})
        self.simpleLLM_messages.append({"role": "user", "content": user_prompt})

        making_prompt = self.change_prompt(self.simpleLLM_messages)

        res = self.llm.create_completion(
            making_prompt, 
            max_tokens=self.max_tokens,
            temperature=self.temperature,
            stop = ["user"],
            )


        return res['choices'][0]['text']

simpleLLMメソッドでは、まず、ローカルLLMに入力するプロンプトを構築します。
ここでは、まずChatGPT APIのプロンプト形式に合わせて、self.simpleLLM_messagesにプロンプトを構築します。
temp_sys_promptが設定されていない場合は、デフォルトのプロンプトを利用します。

ChatGPT APIであれば、モデルにself.simpleLLM_messagesを入力すれば使えますが、ローカルLLMでは、プロンプトはすべて一つのテキストにまとめる必要があるため、change_promptメソッドで、辞書型のプロンプトからテキスト型のプロンプトに変換します。

この段階で、システムプロンプトの前後に加える文章も加えています。
最終的なプロンプトはmaking_promptに'str'型で格納されています。

構築したプロンプトとinitメソッドで取得した設定を読み込んで、ローカルLLMに入力し、出力を取得します。

LLMクラスのsimpleLLMstream_prepareメソッド

./colab_zenn/colab_LocalLLM_sample/module/module_LocalLLM.py
class LLM:
   ・・・
    def simpleLLMstream_prepare(self, user_prompt, temp_sys_prompt = None):
        if temp_sys_prompt is not None:
            self.simpleLLM_messages.append({"role": "system", "content": temp_sys_prompt})
        else:
            self.simpleLLM_messages.append({"role": "system", "content": self.sys_prompt})
        self.simpleLLM_messages.append({"role": "user", "content": user_prompt})

        making_prompt = self.change_prompt(self.simpleLLM_messages)

        return making_prompt, self.max_tokens, self.temperature

ここでは、Streams処理用の事前準備のメソッドです。
ここでは、上のsimpleLLMメソッドと同様に、プロンプトをstr型で作成し、プロンプトと設定ファイルからinitメソッドで取得したローカルLLMに使うパラメータを出力します

まとめ

今回は、初心者向けにGoogle Colaboratoryで、簡単に生成AIを使えるようにする環境を作りました。

Part3の今回は、生成AIの一つ、テキスト生成AIのローカルLLMを使えるようにしました。
ローカルLLMは本当に無数にモデルが存在します。
それらは「小説を書く」や「ChatBotに使う」など特定の用途に特化して学習されているものが多いです。
今回のコードのモデルを変更するだけで、それらの大量のモデルを利用することができますので、ぜひお試しください。

次回のPart4では、音声認識AIについての記事を書こうと思います。

ここまで読んでくださってありがとうございます。

Discussion