見出し画像

ローカル環境でllm-jp-3

こちらの記事ではTanukiモデルを取り上げましたが、最近、日本語対応のオープンソース大規模言語モデル「llm-jp-3」が公開されたので、さっそく試してみました。

llm-jp-3は日本語、英語、そしてソースコードに重点を置いて学習されているとのことです。これまで使用してきたTanukiモデルは日本語特化型のため、英語の翻訳やプログラミングにどの程度対応できるか気になっていましたが、llm-jp-3の登場は朗報です。

なお、実行環境については、以前の記事を参考にしてください。

必要なVRAMは以下の通りです。私の環境では、最も大規模な「llm-jp-3-13b-instruct」は動作しませんでした。

  • llm-jp-3-1.8b-instruct: 3.4816 GB

  • llm-jp-3-3.7b-instruct: 7.0482 GB

  • llm-jp-3-13b-instruct: 25.5730 GB

実際に試してみた印象としては、Tanukiモデルと比べて応答が速く、ストレスなく動作します。しかし、会話の自然さはTanukiモデルのほうが優れていると感じました。今後、両者をしっかりと比較してみたいと思います。

特に、llm-jp-3は英語とプログラミングにも強みがあるため、例えば英語のプロンプト作成やプログラミング支援など、幅広い用途に活用できそうです。

LanguageModelHandler.py

from time import time
from vllm.entrypoints.llm import LLM, SamplingParams

class LanguageModelHandler:
  def __init__(self, system_prompt, initial_response, llm_params, sampling_params):
    # モデルの設定を行う。パラメータはモデル名、並列処理数、データ型、GPU使用率など。
    self.vllm = LLM(model=llm_params["model_name"], tensor_parallel_size=llm_params["tensor_parallel_size"], dtype=llm_params["dtype"], gpu_memory_utilization=llm_params["gpu_memory_utilization"])
    self.tokenizer = self.vllm.get_tokenizer()
    self.sampling_params = SamplingParams(**sampling_params)
    # メッセージ履歴を初期化(システムプロンプトとアシスタントの初期応答を設定)
    self.reset_messages(system_prompt, initial_response)

  def reset_messages(self, system_prompt=None, initial_response=None):
    # システムプロンプトと初期応答が指定されていれば、メッセージ履歴をリセット
    if system_prompt and initial_response:
      self.messages = [
        {"role": "system", "content": system_prompt},  # モデルに与えるシステム指示(コンテキスト設定)
        {"role": "assistant", "content": initial_response}  # 初期応答を設定
      ]
    else:
      self.messages = []

  def update_message_history(self, role, content):
    # ユーザーまたはアシスタントの発言を履歴に追加
    self.messages.append({"role": role, "content": content})

  def prepare_inputs(self):
    # 現在のメッセージ履歴を、LLMが理解できるチャット形式に整形
    return self.tokenizer.apply_chat_template(self.messages, tokenize=False, add_generation_prompt=True)

  def count_tokens_and_characters(self, text):
    # 入力テキストのトークン数と文字数をカウント
    tokens = self.tokenizer.tokenize(text)
    token_count = len(tokens)
    character_count = len(text)  # 日本語の文字数を算出
    return token_count, character_count

  def generate_response(self, user_message, show_elapsed_time=False, show_message_history=False, show_token_usage=False):
    # 新しいユーザー発言を履歴に追加
    self.update_message_history("user", user_message)
    # 現在のメッセージ履歴を整形し、入力テキストを準備
    inputs_text = self.prepare_inputs()

    # 入力トークンと文字数を計測
    input_tokens, input_characters = self.count_tokens_and_characters(inputs_text)
    start_time = time()

    # LLMを用いて応答を生成
    outputs = self.vllm.generate(inputs_text, sampling_params=self.sampling_params, use_tqdm=False)
    elapsed_time = time() - start_time

    # 出力テキストとそのトークン数、文字数を取得
    response_text = outputs[0].outputs[0].text.strip()
    output_tokens, output_characters = self.count_tokens_and_characters(response_text)

    # 応答を履歴に追加
    self.update_message_history("assistant", response_text)

    print(f"*** Assistant's Response ***\n{response_text}\n")

    # 時間計測を表示するオプション
    if show_elapsed_time:
      print(f"*** Elapsed Time ***\n{elapsed_time:.4f} sec.\n")

    # トークン使用量を表示するオプション
    if show_token_usage:
      print(f"*** Token Usage ***")
      print(f"Input Tokens: {input_tokens}")  # 入力テキストのトークン数を表示
      print(f"Input Characters (Japanese): {input_characters} 文字")  # 入力テキストの日本語文字数
      print(f"Output Tokens: {output_tokens}")  # 出力テキストのトークン数を表示
      print(f"Output Characters (Japanese): {output_characters} 文字\n")  # 出力テキストの日本語文字数

    # メッセージ履歴を表示するオプション
    if show_message_history:
      print("*** Message History ***")
      for message in self.messages:
        role = message['role'].capitalize()
        content = message['content']
        print(f"{role}: \n{content}\n")

  def get_message_history(self):
    return self.messages


if __name__ == "__main__":
  model_names = {
    "Tanuki-8B-dpo-v1.0-AWQ": "team-hatakeyama-phase2/Tanuki-8B-dpo-v1.0-AWQ",                   # 4.3785 GB
    "Tanuki-8B-dpo-v1.0-4k-AWQ": "team-hatakeyama-phase2/Tanuki-8B-dpo-v1.0-4k-AWQ",             # 4.3804 GB
    "Tanuki-8B-dpo-v1.0-GPTQ-4bit": "team-hatakeyama-phase2/Tanuki-8B-dpo-v1.0-GPTQ-4bit",       # 4.3804 GB
    "Tanuki-8B-dpo-v1.0-4k-GPTQ-4bit": "team-hatakeyama-phase2/Tanuki-8B-dpo-v1.0-4k-GPTQ-4bit", # 4.3804 GB
    "Tanuki-8B-dpo-v1.0-GPTQ-8bit": "team-hatakeyama-phase2/Tanuki-8B-dpo-v1.0-GPTQ-8bit",       # 7.6558 GB
    "Tanuki-8B-dpo-v1.0-4k-GPTQ-8bit": "team-hatakeyama-phase2/Tanuki-8B-dpo-v1.0-4k-GPTQ-8bit", # 7.6558 GB
    "Tanuki-8x8B-dpo-v1.0-AWQ": "team-hatakeyama-phase2/Tanuki-8x8B-dpo-v1.0-AWQ",               # 23.4735 GB
    "Tanuki-8x8B-dpo-v1.0-GPTQ-4bit": "team-hatakeyama-phase2/Tanuki-8x8B-dpo-v1.0-GPTQ-4bit",   # 23.4735 GB
    "Tanuki-8x8B-dpo-v1.0-GPTQ-8bit": "team-hatakeyama-phase2/Tanuki-8x8B-dpo-v1.0-GPTQ-8bit",   # 45.2674 GB
    "llm-jp-3-1.8b-instruct": "llm-jp/llm-jp-3-1.8b-instruct",                                   # 3.4816 GB
    "llm-jp-3-3.7b-instruct": "llm-jp/llm-jp-3-3.7b-instruct",                                   # 7.0482 GB
    "llm-jp-3-13b-instruct": "llm-jp/llm-jp-3-13b-instruct"                                      # 25.5730 GB
  }

  llm_params = {
    "model_name": model_names["llm-jp-3-1.8b-instruct"],  # 使用するモデルの選択
    "tensor_parallel_size": 1,  # 並列処理数(GPU分割の設定)
    "dtype": "auto",  # データ型を自動設定
    "gpu_memory_utilization": 0.99  # GPU使用率の指定(1.0に近いほどフル活用)
  }

  sampling_params = {
    "temperature": 0.2,  # 応答の多様性を制御。低い値にすると安定した応答を生成
    "max_tokens": 512,  # 最大生成トークン数(長文生成を防止)
    "top_p": 0.8,  # 応答のランダム性を調整(0.8は安全な範囲)
    "top_k": 40,  # 応答候補のトークン数を制限(低い値にすると標準的な応答)
    "repetition_penalty": 1.2,  # 同じ表現の繰り返しを防止
    "presence_penalty": 0.0,  # 特定トークンの生成頻度を調整(0.0は制御なし)
    "frequency_penalty": 0.0,  # トークンの出現頻度を調整(0.0は制御なし)
    "seed": 42  # 再現性のためのシード値(同じ出力を得る場合に有効)Noneにすると再現性がなくランダムな応答を生成
  }

  handler = LanguageModelHandler(
    system_prompt="あなたは医者です。ユーザーに対して問診を行い、親しみやすくかつ簡潔に症状について尋ねてください。回答は医療的知識を持ったアシスタントとして、正確でわかりやすく答えてください。コードや技術用語を使用しないこと。",
    initial_response="こんにちは。どのような症状がありますか?",
    llm_params=llm_params,
    sampling_params=sampling_params
  )

  handler.generate_response("胃が痛いです。", show_elapsed_time=True, show_message_history=True, show_token_usage=True)
  handler.generate_response("刺すように痛いです。", show_elapsed_time=True, show_message_history=True, show_token_usage=True)

  handler.reset_messages(
    system_prompt="あなたは家庭教師です。生徒に勉強のアドバイスをしてください。回答は丁寧で親しみやすく、簡潔に伝えてください。",
    initial_response="こんにちは、勉強に関してどんな質問がありますか?"
  )

  handler.generate_response("数学の勉強方法を教えてください。", show_elapsed_time=True, show_message_history=True, show_token_usage=True)

プログラミングに苦手意識をお持ちの方は、ぜひ「DXパーティー」までお問い合わせください。
有料サービスではありますが、オンラインでマンツーマンのレッスンを受けることで、短期間でPythonを習得し、自在に使いこなせるようになると思います!

お問い合わせ - DXパティー (dxpt.jp)

マンツーマンオンライン講師を募集しています。 特に視聴者数が少ないDX・デジタル領域に取り組んでいるYouTube配信者を積極的に募集しています。 DXパティーのレッスンとYouTubeチャンネルの収益化の両方から収益を得て、安定した収入を目指しましょう。

DXパティーでは講師募集中!

お仕事のご依頼もお待ちしております!