diff --git a/.gitignore b/.gitignore index 15201ac..e675f7a 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,11 @@ cython_debug/ # PyPI configuration file .pypirc + +# Custom: Ignore notebook execution outputs +workspace-nim-with-rag/jupyter_notebook/embed/ +workspace-nim-with-rag/jupyter_notebook/nim_llm_values.yaml +workspace-nim-with-rag/jupyter_notebook/nim-llm-1.7.0.tgz + +# Mac +*.DS_Store diff --git a/Deployment_Guide-jp.md b/Deployment_Guide-jp.md new file mode 100644 index 0000000..b3d1ec3 --- /dev/null +++ b/Deployment_Guide-jp.md @@ -0,0 +1,127 @@ +# NIM ブートキャンプ + +このブートキャンプは、開発者が実際のGenAIアプリケーションを構築することで、NVIDIA® NIM™を使い始めることができるように設計されています。 +ラボでは、参加者がNIM Dockerコンテナをセットアップし、推論リクエストを提供するためにREST API Endpointを利用することをガイドします。さらに、参加者は、LLaMA-3 8Bモデルのアダプターをファインチューニングする実習を体験しながら、LoRAのようなPEFT(Parameter Efficient Fine-Tuning)テクニックを使ったモデルのファインチューニングを探求します。 + +## ラボのデプロイ + +### 前提条件 + +このチュートリアルを実行するには、Ampereまたはそれ以降の世代の80GB GPUを最低1つ搭載したラップトップ/ワークステーション/DGXマシンが必要です。 + +- 最新の[Docker](https://docs.docker.com/engine/install/)をインストールし、GPUアクセスを可能にするNVIDIA Container Toolkitがインストールに含まれていることを確認してください。 +- ファインチューニングのラボでは、モデルの重みをダウンロードするためにHuggingfaceセキュリティトークンが必要です。手順は[こちらのリンク]( https://huggingface.co/docs/hub/en/security-tokens )にあります。 + +### テスト環境 + +Ampere A100 GPUを搭載したDGXマシンですべてのラボをテスト・実行しました。 + +#### 1. 仮想環境のセットアップ + +まず、このリポジトリをクローンし、プロジェクト・ディレクトリに移動します: +```bash +git clone https://github.com/openhackathons-org/NIM-Bootcamp/tree/main +cd NIM-Bootcamp +``` + +新しい仮想環境を作成し、アクティブにします: +```bash +# Create virtual environment +python -m venv nim-bootcamp-env + +# Activate virtual environment +source nim-bootcamp-env/bin/activate +``` + +#### 2. 必要なパッケージのインストール + +仮想環境を起動した状態で、必要なパッケージをインストールします: +```bash +# Upgrade pip (recommended) +pip install --upgrade pip + +# Install requirements +pip install -r https://github.com/openhackathons-org/NIM-Bootcamp/blob/main/bootcamp_requirements.txt +``` + +#### 3. GPU アクセスの検証 + +お使いの環境が GPU リソースにアクセスできることを確認するには、以下の Python コマンドを実行します: +```python +import torch + +# Check if CUDA is available +print(f"CUDA available: {torch.cuda.is_available()}") + +# If CUDA is available, show device information +if torch.cuda.is_available(): + print(f"Current device: {torch.cuda.get_device_name(0)}") + print(f"Device count: {torch.cuda.device_count()}") +``` + +これらのコマンドは、Pythonターミナルで実行することも、簡単なスクリプトを作成することもできます。 + +#### 4. JupyterLab の起動 + +ポート8888でJupyterLabを起動します: +```bash +#Choose the desired workspace: +cd workspace-nim-with-rag +# Basic start +jupyter lab --port 8888 + +# If you want to make it accessible from other machines on your network +jupyter lab --port 8888 --ip 0.0.0.0 + +# If you want to specify a particular browser +jupyter lab --port 8888 --browser="chrome" +``` + +コマンドを実行すると、次のような出力が表示されるはずです: +``` +[I 2025-01-29 10:00:00.000 LabApp] JupyterLab extension loaded from /path/to/extension +[I 2025-01-29 10:00:00.000 LabApp] JupyterLab application directory is /path/to/app +[I 2025-01-29 10:00:00.000 ServerApp] Serving notebooks from local directory: /path/to/your/project +[I 2025-01-29 10:00:00.000 ServerApp] Jupyter Server 1.x is running at: +[I 2025-01-29 10:00:00.000 ServerApp] http://localhost:8888/lab +``` + +出力されたURLをコピーし、ブラウザに貼り付けます。トークンの入力を求められたら、ターミナルの出力でそれを見つけることができます。 + +#### トラブルシューティング + +何らかの問題が発生した場合: + +1. **仮想環境の問題** + - 仮想環境を作成する際に、正しいディレクトリにいることを確認してください。 + - 仮想環境が有効になっていることを確認します。(ターミナルプロンプトに `(nim-bootcamp-env)` と表示されるはずです) + +2. **Package Installation Issues** + - Try updating pip before installing requirements: `pip install --upgrade pip` + - If a package fails to install, try installing it separately + +3. **GPU アクセスの問題** + - NVIDIAドライバが正しくインストールされているか確認します。 + - CUDAツールキットがインストールされていて、PyTorchのバージョンと一致しているか確認します。 + - ターミナルで `nvidia-smi` を実行して GPU が認識されていることを確認します。 + +4. **JupyterLab アクセスの問題** + - ポート8888が他のアプリケーションによって使用されていないか確認してください。 + - 他のマシンからアクセスする場合、ファイアウォール設定が接続を許可していることを確認します。 + - ポート8888が使用できない場合は、別のポートを試してください。 + +追加のヘルプが必要な場合は、GitHubリポジトリでissueをオープンして下さい。 + +`http://localhost:8888`でブラウザを開き、`Start_Here.ipynb`をクリックします。 +残りのラボの作業が終わり次第、`File > Shut Down`を選択してjupyterラボをシャットダウンし、ターミナルウィンドウで`exit`とタイプするか、`ctrl+d`を押してコンテナをシャットダウンします。 + +これでNIM Bootcampのビルドとデプロイは完了です! + + + + + + + + + diff --git a/README-jp.md b/README-jp.md new file mode 100644 index 0000000..b09e410 --- /dev/null +++ b/README-jp.md @@ -0,0 +1,46 @@ +# NVIDIA NIM ブートキャンプ +NVIDIA® NIM™ ブートキャンプへようこそ!このハンズオン学習プログラムでは、NVIDIA® NIM™ を使用した本番環境対応の生成AIアプリケーション構築スキルを習得できます。 +参加者は「検索拡張生成(Retrieval Augmented Generation: RAG)」のクラウドベースとローカル展開の両シナリオを実践を通じて学びます。包括的なラボでは以下を指導します: + +* NIM Dockerコンテナのセットアップと運用 +* 推論用REST APIエンドポイントの実装と利用 +* エンドツーエンドRAGアプリケーションの構築 +* PEFT(Parameter Efficient Fine-Tuning)手法の探索 +* カスタムLoRA(Low-Rank Adaptation)モデルのトレーニングとデプロイ +* Llama-3 8Bなど最新モデルのアダプターのファインチューニング + +## ブートキャンプ内容 +3つの主要ラボとオプショナルのLoRAファインチューニングNotebookから構成されます: + +- Lab 1: NVIDIA NIM APIを利用したRAG構築 +- Lab 2: ローカル環境でのNVIDIA NIM活用 +- Lab 3: LoRAアダプターを用いたNIM運用 +- [オプション] カスタムデータセットでのアダプタートレーニング +- NIM Blueprintsの応用(近日公開) + +## 使用ツールとフレームワーク + +ブートキャンプの教材で使用するツールとフレームワークは以下の通りです: + +- [NVIDIA NIM™](https://developer.nvidia.com/nim) + + +## 所要時間 + +合計約3時間30分 + + +## ブートキャンプのマテリアルのデプロイ方法 + +ラボの展開手順は[デプロイガイド](https://github.com/openhackathons-org/NIM-Bootcamp/blob/main/Deployment_Guide.md) +を参照して下さい。 + +## 出典 + +この教材はOpenHackathons GitHubリポジトリが起源です。追加マテリアルは [こちらから](https://github.com/openhackathons-org)ご確認下さい。 + +[Open Hackathons Resources](https://www.openhackathons.org/s/technical-resources) の確認や、あなたの経験のシェアやコミュニティからより多くの助けを得るために[OpenACC and Hackathons Slack Channel](https://www.openacc.org/community#slack)への参加もお忘れなく! + +## Licensing + +Copyright © 2025 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials may include references to hardware and software developed by other entities; all applicable licensing and copyrights apply. diff --git a/bootcamp_requirements.txt b/bootcamp_requirements.txt index c29a684..bf19158 100644 --- a/bootcamp_requirements.txt +++ b/bootcamp_requirements.txt @@ -120,6 +120,7 @@ pycryptodomex==3.20.0 pydantic==2.8.2 pydantic_core==2.20.1 Pygments==2.18.0 +pypdf==5.7.0 python-dateutil==2.9.0.post0 python-json-logger==2.0.7 pytz==2024.1 diff --git a/workspace-nim-with-rag-jp/Start-NIM-RAG.ipynb b/workspace-nim-with-rag-jp/Start-NIM-RAG.ipynb new file mode 100644 index 0000000..8f642f7 --- /dev/null +++ b/workspace-nim-with-rag-jp/Start-NIM-RAG.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "aad2fbb6-2499-4d0e-8869-41dd8ff57c11", + "metadata": {}, + "source": [ + "# RAG(検索拡張生成)によるNIMアプリケーション \n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "2abac7d8-8d46-4a88-83f7-e543b8529369", + "metadata": {}, + "source": [ + "## NVIDIA推論マイクロサービス NVIDIA NIM 入門" + ] + }, + { + "cell_type": "markdown", + "id": "8587527a-eb01-4dff-9f38-bbea97270dc2", + "metadata": {}, + "source": [ + "This contents was last updated on Jul 17, 2025. \n", + "\n", + "NVIDIA AI Enterpriseの一部であるNVIDIA NIM™は、生成AIモデルのデプロイを加速するために設計された、使いやすいマイクロサービスのセットです。これは、クラウド、データセンター、ワークステーションを含むさまざまな環境にわたって、事前学習済みAIモデルとカスタマイズされたAIモデルの両方に対してGPUによって高速化された推論マイクロサービスをセルフホストするためのコンテナを提供します。モデルは、業界標準のAPIから呼び出すだけで利用できます。これらのマイクロサービスは、モデル・ファミリーやモデルごとに分類され、さまざまなAIデプロイのニーズに対して柔軟性と拡張性を提供します。NIMは、実行エンジンやランタイム操作などのモデル推論内部を抽象化します。\n", + "\n", + "
\n", + "\n", + "
\n", + "\n", + "**主なメリット:**\n", + "1. **市場投入期間の短縮**: NVIDIA NIMは使いやすくスケーラブルなモデル推論ソリューションとして設計されているため、開発者はインフラストラクチャの構築やスケールアウトにサイクルを費やすことなく、アプリケーションロジックの開発に集中することができます。\n", + "2. **適応可能なスケーラビリティ**: このシステムは、少数のユーザーから数百万人規模のユーザーまで、容易に拡張できる高性能なデプロイメントを提供し、一貫したパフォーマンスを保証します。\n", + "3. **包括的な言語モデルのサポート**: 最先端の大規模言語モデル(LLM)アーキテクチャに最適化されたエンジンを提供し、高度な言語処理能力を実現します。\n", + "4. **シームレスな統合**: このマイクロサービスは、既存のシステムやアプリケーションに簡単に統合できるように設計されています。開発者には、OpenAI API互換のプログラミングインターフェースと、機能強化のためのNVIDIA独自の拡張機能が提供されます。\n", + "5. **強固なセキュリティ対策**: このプラットフォームは、セーフセンサーの実装、テクノロジースタック内の共通脆弱性・暴露(CVE)の継続的な監視と対処、定期的な内部セキュリティ評価の実施により、エンタープライズレベルのセキュリティを優先しています。\n", + "
\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "f612c9b4-3408-4867-a35f-35c4a39415b3", + "metadata": {}, + "source": [ + "以下の目次を通じて、RAGとNIMのアプローチについて説明し、含まれるチャレンジではコンセプトの理解を確認しましょう。" + ] + }, + { + "cell_type": "markdown", + "id": "5dd69f64-5e2a-43f5-9afd-2cd7bb11f81e", + "metadata": {}, + "source": [ + "### 目次\n", + "\n", + "1. [NVIDIA NIM APIを利用したRAG構築](jupyter_notebook/rag_nim_endpoints.ipynb)\n", + "1. [ローカル環境でのNVIDIA NIM活用](jupyter_notebook/locally_deployed_nim.ipynb)\n", + "1. [LoRAアダプターを用いたNIM運用](jupyter_notebook/nim_lora_adapter.ipynb) \n", + "1. [エージェントとしてのNVIDIA NIM](jupyter_notebook/nim_agent_main.ipynb)" + ] + }, + { + "cell_type": "markdown", + "id": "5e276b10-01c2-47a4-adf9-22f81724681a", + "metadata": {}, + "source": [ + "### チュートリアル時間\n", + "\n", + "合計3時間のセッション\n", + "\n", + "### 内容レベル\n", + "初級~上級\n", + "\n", + "### 対象者および前提条件\n", + "このラボの対象者は、GPUを使用した生成AIタスクを解決するためのRAG(Retrieval Augmented Generation)とNVIDIA NIMアプローチに興味のある研究者、開発者、大学院生です。受講者は、Pythonプログラミングと自然言語処理の知識を持ち、NVIDIA NGCとAPIキーを所有していることが期待されます。" + ] + }, + { + "cell_type": "markdown", + "id": "c2cab51b-02ab-49ff-9c2c-84a5a08a0f06", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Licensing\n", + "\n", + "Copyright © 2024 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/gke_handson_locally_deployed_nim.ipynb b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/gke_handson_locally_deployed_nim.ipynb new file mode 100644 index 0000000..01ee289 --- /dev/null +++ b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/gke_handson_locally_deployed_nim.ipynb @@ -0,0 +1,856 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3135f42d-057d-4fc3-a11c-744eb9ad5b3f", + "metadata": {}, + "source": [ + "# NVIDIA NIM と Google Kubernetes Engine でつくる高性能 AI 推論基盤\n", + "---\n", + "\n", + "\n", + "このNotebookでは、ローカライズされたNVIDIA NIM(NVIDIA Inference Microservice)とGKE(Google Kubernetes Engine)を使用してRAG (検索拡張生成)パイプラインを構築するデモンストレーションを行います。まず初めに、RAGの概要およびNVIDIA API Keyの設定、NIMイメージの取得とデプロイ、ローカルにデプロイされたNIMを使用するRAGアプリケーションの構築について説明します。\n", + "\n", + "- GKE(Google Kubernetes Engine)の概要およびNVIDIA NIMのGKE経由でのデプロイの方法\n", + "- NIMモデルにアクセスするためのNVIDIA APIキーの設定方法\n", + "- RAGのコンセプトの理解\n", + "- ウェブリンクからコンテンツを取得し、コンテンツをテキストチャンクに分割し、エンベッディングを作成し、ベクターストアでローカルに保存する方法\n", + "- ベクトルストアから埋め込みモデルをロードし、NVIDIA Endpointsを使用してRAGを構築する概念" + ] + }, + { + "cell_type": "markdown", + "id": "e17864e7-86e7-4502-a378-089ee9099cb5", + "metadata": {}, + "source": [ + "## 前提条件\n", + "今回のハンズオンは [こちらの手順](https://github.com/GoogleCloudPlatform/gcp-getting-started-lab-jp/blob/master/gke-next25/tutorial.md) を実施し、Google Kubernetes Engineを使用して、LLMモデルおよび埋め込みモデルのNIMがデプロイされている事が前提となっております。\n", + "今回のハンズオンでは、LLMモデルのNIMと埋め込みモデルのNIMがそれぞれNVIDIA L4を1枚使う想定で作成されています。" + ] + }, + { + "cell_type": "markdown", + "id": "1c67dc66-a58a-466e-862d-686853807693", + "metadata": { + "tags": [] + }, + "source": [ + "### 事前準備\n", + "デプロイした LLM のサービスの外部 IP アドレスを環境変数に設定しておきます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "969159f2-b726-49eb-b2d2-5ac8d66f85a6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import getpass\n", + "\n", + "os.environ[\"LLM_IP_ADDR\"] = #Swallow の サービス外部 IP を設定します\n", + "os.environ[\"EMB_IP_ADDRESS\"] = #Embedding の サービス外部 IP を設定します\n" + ] + }, + { + "cell_type": "markdown", + "id": "ab4d8c71-18c5-4cff-91e6-f9e7c822b036", + "metadata": {}, + "source": [ + "以下のセルを実行し、今回のハンズオンに必要なPythonライブラリのインストールを行います。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7fc92d99-6d71-4c36-b2cc-02f6c96d084a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!pip install -r gke_handson_requirements.txt" + ] + }, + { + "cell_type": "markdown", + "id": "b7943f73-a23a-49b2-b00b-67fd6cf62ffe", + "metadata": { + "tags": [] + }, + "source": [ + "## NVIDIA NIMとは?\n", + "\n", + "NVIDIA NIMは、生成AIのデプロイを加速する為の推論向けマイクロサービスです。クラウド、データセンター、ワークステーションなど、あらゆる環境にデプロイできるように設計されています。業界標準(OpenAI APIなど)のAPI互換のAPIを提供しており、既存のコードのエンドポイントをNVIDIA NIMで起動したエンドポイントに変更するだけで簡単にお使いいただく事が可能です。\n", + "今回のハンズオンでは、Google GKEを用い、LLMモデルのNIMと埋め込みモデルのNIMをデプロイし、RAGアプリケーションの構築を行います。\n", + "\n", + "### NVIDIA API KEYの取得\n", + "NVIDIA NIMは、[NVIDIA API Catalog](https://build.nvidia.com/explore/discover)で利用可能な、使いやすいオープンAPIを介して素早くアクセスできます。これは、オンラインで幅広いマイクロサービスにアクセスするためのプラットフォームです。NVIDIA NIMを使い始めるには、登録が必要な`NVIDIA API Key`が必要です。以下のスクリーンショットに示すように、`ログインボタンをクリックしてメールアドレスを入力し`、プロセスに従って残りの情報登録するか、[NVIDIA NGC](https://ngc.nvidia.com/signin)の登録(*アカウント名をクリック -> セットアップ -> パーソナルキーの生成*)からAPIキーを生成してください。プロセスが完了したら、API Keyを将来の使用のためにアクセスできる場所に保存してください。APIキーのサンプルは、`nvapi-`で始まり、アンダースコア `_` を含む64文字です。\n", + "\n", + "すでにアカウントをお持ちの方は、この手順に従って`NVIDIA API KEY`を取得してください:\n", + "\n", + "- [ここ](https://build.nvidia.com/explore/discover)からあなたのアカウントでログイン.\n", + "- ご希望のモデルをクリックしてください。\n", + "- [Input]で[Python]タブを選択し、[Get API Key]をクリックし、[Generate Key]をクリックします。\n", + "- 生成されたキーをコピーし、NVIDIA_API_KEYとして保存します。そこからエンドポイントにアクセスできるはずです。\n", + "\n", + "
\n", + " \n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "578dba70-1db4-4ab7-837b-724c1e0ba7f2", + "metadata": {}, + "source": [ + "### クイックテストの開始" + ] + }, + { + "cell_type": "markdown", + "id": "ba7a091e-a04e-414f-85b7-afe360291fe6", + "metadata": { + "tags": [] + }, + "source": [ + "### Llama 3.1 Swallow NIM の動作確認\n", + "NIMが稼働していることを2つの方法で素早くテストできます:\n", + "- LangChain NVIDIA Endpoints\n", + "- 単純なOpenAI完了リクエスト\n", + "\n", + "**パラメータの説明:**\n", + "- **base_url**: NVIDIA NIM の docker イメージがデプロイされているURL\n", + "- **model**: デプロイされた NVIDIA NIM モデルの名前\n", + "- **temperature**: サンプリングのランダム性を調整する。温度を下げると、高い確率で単語が選択される可能性が高くなります。\n", + "- **top_p**: モデルがどの程度決定論的かを制御します。正確で事実に基づいた回答を求める場合は、この値を低く保ちます。より多様な回答を求める場合は、値を大きくします\n", + "- **max_tokens**: 生成される出力トークンの最大数\n", + "\n", + "\n", + "以下のセルを実行しLLMのNIMのエンドポイントの動作確認を行いましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3acc2d7c-c7a7-4d75-8342-29c36ce9745d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!curl -s -X POST \"http://${LLM_IP_ADDR}:8000/v1/chat/completions\" \\\n", + " -H \"accept: application/json\" \\\n", + " -H \"Content-Type: application/json\" \\\n", + " -d '{ \\\n", + " \"model\": \"llama-3.1-swallow-8b\", \\\n", + " \"messages\": [ \\\n", + " { \\\n", + " \"role\": \"user\", \\\n", + " \"content\": \"日本の首都はどこですか?\" \\\n", + " } \\\n", + " ], \\\n", + " \"temperature\": 0.7, \\\n", + " \"max_tokens\": 50 \\\n", + " }' \\\n", + " | jq" + ] + }, + { + "cell_type": "markdown", + "id": "c49788a1-8a54-413c-81e9-232ac1227559", + "metadata": {}, + "source": [ + "エラーが出力された場合は、しばらく待ってから上記のセルを再実行してください。エラーは、NIMコンテナが完全に立ち上がっていないことが原因かもしれません。" + ] + }, + { + "cell_type": "markdown", + "id": "9ba40068-d612-460a-9f0a-b419ae9825d4", + "metadata": {}, + "source": [ + "### llama-3.2-nv-embedqa LLM の動作確認\n", + "次に埋め込みモデルのNIMについても、以下のセルを実行してテストしましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e8280dd-3cc9-499f-9d53-9ce568d42b49", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!curl -s -X POST \"http://${EMB_IP_ADDRESS}:8000/v1/embeddings\" \\\n", + " -H \"accept: application/json\" \\\n", + " -H \"Content-Type: application/json\" \\\n", + " -d '{ \\\n", + " \"model\": \"/model\", \\\n", + " \"input\": [ \"NVIDIAの新しく出たGPUには何がありますか?名前を教えてください。\" ], \\\n", + " \"input_type\": \"query\" \\\n", + " }' \\\n", + " | jq" + ] + }, + { + "cell_type": "markdown", + "id": "128458f8-0aef-4b58-b451-1d0f263c0b41", + "metadata": {}, + "source": [ + "## RAG(検索拡張生成)\n", + "\n", + "### RAGとは?\n", + "\n", + "RAG(検索拡張生成)は、外部ソースから取得した事実を用いて生成AIモデルの精度と信頼性を向上させる技術です。\n", + "事前に学習された大規模な言語モデル(LLM)は、そのパラメータに事実知識を格納することが示されており、ダウンストリームの自然言語処理タスクでファインチューニングを行うと、最先端の結果を達成します。しかし、知識にアクセスして正確に操作する能力はまだ限定的であるため、知識集約的なタスクでは、タスクに特化したアーキテクチャに比べて性能が劣ります。さらに、その決定に実証性を与え、世界知識を更新することは、依然として未解決の研究課題です。詳しくは[こちら](https://arxiv.org/pdf/2005.11401)をご確認下さい。 \n", + " \n", + "\n", + "#### RAGはどのように役立つのか?\n", + "RAGは、大規模言語モデル(LLM)の強力なセマンティック機能と、メタデータ、テキスト、画像、ビデオ、表、グラフなどを含むデータセットとの接続を支援するアーキテクチャです。\n", + "\n", + "
\n", + " \"RAG
\n", + " RAG Architecture\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "2ce6f9f6-8942-490c-ba33-66a9534b0e91", + "metadata": {}, + "source": [ + "### RAG コンポーネント\n", + "\n", + "RAGアプリケーションは`Retriever` と ` Generator`の2つの主要コンポーネントから構成されています: . `Retriever` (RET) コンポーネントは、ユーザからの問い合わせに応答して、保存されているデータセットから適切な情報を抽出します。続いて、` Generator`(GEN)コンポーネントは、ユーザーからの問い合わせと検索された情報を利用して応答を生成します。\n", + "\n", + "RAGシステムのアーキテクチャは、主に以下の要素で構成されています:\n", + "- **RET**\n", + " - 埋め込みモデル: 入力データを密なベクトル表現に変換するバイエンコーダーネットワーク\n", + " - ベクトルストア: 高次元ベクトル埋め込みを格納し、クエリするために最適化されたデータベース.\n", + " - *リ*-ランキング (オプショナル): クエリとの関連性に基づいて検索された情報に優先順位を付けるクロスエンコーダ\n", + "\n", + "- **GEN**\n", + " - 大規模言語モデル (LLM): 膨大なテキストデータで訓練されたニューラルネットワークで、人間のような応答を生成することができます。\n", + " - ガードレール (オプショナル): 生成された出力が事前に定義された制約や品質基準に準拠していることを保証するために実装されたメカニズムです。\n", + "\n", + "堅牢なRAGアプリケーションには、以下に関して優れている必要があります:\n", + "- `Retriever`パイプライン\n", + "- ` Generator`モデル\n", + "- 両者のリンク\n", + "\n", + "ラボでは、これらのコンポーネントを1つずつインスタンス化し、それらをまとめます。" + ] + }, + { + "cell_type": "markdown", + "id": "30faffb2-31f7-4488-834a-baf0dc1922eb", + "metadata": {}, + "source": [ + "## LangChainの概要\n", + "\n", + "[LangChain](https://python.langchain.com/v0.2/docs/introduction/)は、大規模言語モデル(LLM)を使用するアプリケーションの開発を簡素化するために設計された強力なフレームワークです。\n", + "RAGアプリケーションのために、LangChainは以下を提供します:\n", + "\n", + "- 様々なファイルタイプのドキュメントローダー\n", + "- ドキュメントを分割するテキスト分割機能\n", + "- テキストをベクトル表現に変換する埋め込み\n", + "- 効率的な類似検索のためのベクトルストア\n", + "- 関連情報を取得するための`Retriever`メソッド\n", + "- クエリと応答を構造化するプロンプトテンプレート" + ] + }, + { + "cell_type": "markdown", + "id": "f5bbbc0d-08d0-422c-9b10-ce5cb3ee4da9", + "metadata": {}, + "source": [ + "### LLM AI Endpoints\n", + "\n", + "`langchain-nvidia-ai-endpoints`パッケージには、NVIDIA NIM推論マイクロサービス上のモデルでアプリケーションを構築するLangChain統合が含まれています。[ChatNVIDIA](https://python.langchain.com/v0.2/docs/integrations/chat/nvidia_ai_endpoints/)クラスは `langchain_nvidia_ai_endpoints` の一部で、チャットアプリケーションのためのNVIDIA NIMへのアクセスや、ホストまたはローカルにデプロイされたマイクロサービスへの接続を可能にします。\n", + "以下では、`meta/llama3-8b-instruct`を使った例を示します。各モデルはカタログにあるモデル名キーで一意に識別されます(例:meta/llama3-8b-instruct、meta/llama-3.1-70b-instruct)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed8c923a-d633-46e9-bdc5-ae8d32a03f5b", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "\n", + "llm = ChatNVIDIA(base_url=\"http://{}:8000/v1\".format(os.environ['LLM_IP_ADDR']), model=\"llama-3.1-swallow-8b\", temperature=0.1, max_tokens=1000, top_p=1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10a7c6ba-bbdb-4877-aad0-2ba791de1e13", + "metadata": {}, + "outputs": [], + "source": [ + "result = llm.invoke(\"RAGとは何ですか?\")\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "12cb3393-0b68-4864-bf61-02818f3eb620", + "metadata": {}, + "source": [ + "**予想される出力**:\n", + "```\n", + "AIMessage(content='RAGとは、Risk Assessment and Governance(リスク評価とガバナンス)の略称であり、一般的に、組織におけるリスク管理とガバナンスのプロセスを指します。組織は、RAGを通じて、ビジネス、財務、運用などに関するリスクを特定、評価、優先順位付けし、適切な対策を講じることで、リスクを管理します。', additional_kwargs={}, response_metadata={'role': 'assistant', 'content': 'RAGとは、Risk Assessment and Governance(リスク評価とガバナンス)の略称であり、一般的に、組織におけるリスク管理とガバナンスのプロセスを指します。組織は、RAGを通じて、ビジネス、財務、運用などに関するリスクを特定、評価、優先順位付けし、適切な対策を講じることで、リスクを管理します。', 'token_usage': {'prompt_tokens': 16, 'total_tokens': 115, 'completion_tokens': 99}, 'finish_reason': 'stop', 'model_name': 'institute-of-science-tokyo/llama-3.1-swallow-8b-instruct-v0.1'}, id='run--28eba379-a97b-4cba-9b09-6e9905ab79f8-0', usage_metadata={'input_tokens': 16, 'output_tokens': 99, 'total_tokens': 115}, role='assistant')\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "id": "174a7f72-b19e-4e78-be2e-93b4d3f0fc3e", + "metadata": {}, + "source": [ + "以下のセルを実行すると、利用可能なモデルのリストにアクセスできます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18982c31-a1d7-4c0c-95d1-70eeee7f8890", + "metadata": {}, + "outputs": [], + "source": [ + "ChatNVIDIA.get_available_models()" + ] + }, + { + "cell_type": "markdown", + "id": "c597f355-457a-4313-b502-9ad1f1688e4b", + "metadata": {}, + "source": [ + "#### 回答内容の分析方法 \n", + "\n", + "前のセルからの出力成分を解読してみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a88e020a-2776-41f6-819b-37ee84ffc6ac", + "metadata": {}, + "outputs": [], + "source": [ + "import pprint\n", + "from IPython.display import display, Markdown, Latex" + ] + }, + { + "cell_type": "markdown", + "id": "7f72b4c4-be9c-4a51-b1e9-6640d8c9ffd0", + "metadata": {}, + "source": [ + "主な応答内容を表示します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93fbea62-a29d-48d5-baa8-d554e4f64634", + "metadata": {}, + "outputs": [], + "source": [ + "display(Markdown(result.content))" + ] + }, + { + "cell_type": "markdown", + "id": "713029b2-382c-4602-924a-fdbe36eeff5b", + "metadata": {}, + "source": [ + "`response_metadata` はその他の重要な詳細を示します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef05ea03-e2ce-43da-a473-5e7f0c7ce77e", + "metadata": {}, + "outputs": [], + "source": [ + "result.response_metadata" + ] + }, + { + "cell_type": "markdown", + "id": "32c03c9c-5910-4a1a-9723-ddd954219580", + "metadata": {}, + "source": [ + "#### LLM 応答の Dictionary Keys\n", + "\n", + "- `role`: レスポンダを識別する (例: 'assistant')\n", + "- `content`: AI からの実際のテキストレスポンス\n", + "- `token_usage`:\n", + " - `prompt_tokens`: 入力に含まれるトークンの数\n", + " - `total_tokens`: インタラクションで使われたトークンの総数\n", + " - `completion_tokens`: AI のレスポンスに含まれるトークン数\n", + "- `finish_reason`: AIがテキスト生成を停止した理由\n", + "- `model_name`: 使用するAIモデルを指定" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02255da4-4f74-4e31-a0b0-a2845fa2da03", + "metadata": {}, + "outputs": [], + "source": [ + "# Let's try asking a followup question\n", + "result = llm.invoke(\"それはどう便利なのですか?\")\n", + "display(Markdown(result.content))" + ] + }, + { + "cell_type": "markdown", + "id": "b0f09430-87d4-4f2b-ba49-ad0a115bb06a", + "metadata": {}, + "source": [ + "**注意:** LLMのステートレスな振る舞いを確認してみましょう。したがって、関連するすべてのコンテキスト、背景、クエリーを単一のプロンプトで渡すことが重要です。" + ] + }, + { + "cell_type": "markdown", + "id": "156c5b17-5d00-43fd-bbc4-c8d6c7893fcd", + "metadata": {}, + "source": [ + "## RAG アプリケーション\n", + "\n", + "このセクションでは、NIMを使って簡単なRAGアプリケーションを作成します。このアプリケーションは、ウェブリンクからデータソースを受け取り、コンテンツを文書として読み込み、文書を分割し、埋め込みをインスタンス化し、ベクトルデータベースに格納します。さらに、要約用とチャット用の2つのLLMを使って[会話検索チェーン](https://python.langchain.com/v0.1/docs/modules/chains/#conversationalretrievalchain-with-streaming-to-stdout)を作成する。組み合わせたLLMの重要性は、複雑なシナリオにおいて全体的な結果を向上させることを支援することです。最後に、関連するクエリープロンプトを生成するための質問ジェネレーターを追加します。以下にその手順を示します:\n", + "\n", + "- 必要なライブラリをすべてインポート\n", + "- ウェブリンクのデータソースを作成 \n", + "- ウェブリンクからHTMLファイルをロードする関数を作成\n", + "- embeddingsとdocument text splitterの作成\n", + "- LangChainからNVIDIA AI Endpointを使って埋め込みを生成し、オフラインのvector storeに保存\n", + "- vector storeから埋め込みをロードし、NVIDIA Endpointを使用してRAGを構築\n", + "- 2つのLLMを使って会話型検索チェーンを作る\n", + "- 質問ジェネレーターを追加して、関連するクエリー・プロンプトを生成する" + ] + }, + { + "cell_type": "markdown", + "id": "661dd09c-bdef-4907-8b78-ae22095814b9", + "metadata": {}, + "source": [ + "#### ライブラリのインポート" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "921e0b4e-5477-4875-8687-8ea4e88bd0f2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from langchain.chains import ConversationalRetrievalChain, LLMChain\n", + "from langchain.chains.conversational_retrieval.prompts import CONDENSE_QUESTION_PROMPT, QA_PROMPT\n", + "from langchain.chains.question_answering import load_qa_chain\n", + "from langchain.memory import ConversationBufferMemory\n", + "from langchain.vectorstores import FAISS\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings" + ] + }, + { + "cell_type": "markdown", + "id": "b0d3c408-5735-4446-83ba-ee2c154d3fab", + "metadata": {}, + "source": [ + "#### ウェブリンク・データソースの作成\n", + "\n", + "お好みのウェブリンクを差し替えたり、追加したりすることができます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e827c50-9f64-4b29-9995-405ebd0b7f5b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "urls = [\n", + "\"https://www.fsa.go.jp/news/r5/20230829/230829_main.pdf\",\n", + "\"https://www.tioj.or.jp/activity/pdf/190619_06.pdf\",\n", + "\"https://www.pmda.go.jp/files/000251332.pdf\",\n", + "\"https://www.jili.or.jp/files/research/zenkokujittai/pdf/r3/i-xvii.pdf\",\n", + "\"https://www.zenginkyo.or.jp/fileadmin/res/news/news350331_1.pdf\"\n", + "]\n" + ] + }, + { + "cell_type": "markdown", + "id": "337a41cc-8765-40e3-a1ea-0b536ac25bcd", + "metadata": {}, + "source": [ + "## 実装" + ] + }, + { + "cell_type": "markdown", + "id": "6d9634d7-ecfb-43b1-bb15-6a5b42261cc4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### PDF ファイルを読み込む関数を作る\n", + "\n", + "以下は、PDFファイルを読み込むためのヘルパー関数です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8b7796f-ad07-45eb-8d08-5c977c897da3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from pypdf import PdfReader\n", + "from io import BytesIO\n", + "import requests\n", + "import re\n", + "\n", + "def clean_japanese_pdf_text(text: str) -> str:\n", + " # 日本語文中の改行を削除\n", + " text = re.sub(r'(?<=[ぁ-んァ-ヶ一-龥])\\s*\\n\\s*(?=[ぁ-んァ-ヶ一-龥])', '', text)\n", + " # 連続するスペースや全角スペースを1つにまとめる\n", + " text = re.sub(r'[  ]{2,}', ' ', text)\n", + " # 各行の先頭空白を削除\n", + " text = re.sub(r'^\\s+', '', text, flags=re.MULTILINE)\n", + " # 「..... 4」形式の目次行のページ番号を除去\n", + " text = re.sub(r'\\.{3,}\\s*\\d{1,3}', '', text)\n", + " text = text.replace(\"\\n\", \"\")\n", + " return text\n", + "\n", + "def pdf_document_loader(url: str) -> str:\n", + " \"\"\"\n", + " Loads and extracts cleaned text from a PDF at the given URL using `pypdf`.\n", + "\n", + " Args:\n", + " url: The URL of the PDF.\n", + "\n", + " Returns:\n", + " Cleaned text content of the PDF.\n", + " \"\"\"\n", + " try:\n", + " response = requests.get(url)\n", + " response.raise_for_status()\n", + " reader = PdfReader(BytesIO(response.content))\n", + " text = \"\"\n", + " for page in reader.pages:\n", + " extracted = page.extract_text()\n", + " if extracted:\n", + " text += extracted\n", + " return clean_japanese_pdf_text(text.strip())\n", + " except Exception as e:\n", + " print(f\"Failed to load {url} due to: {e}\")\n", + " return \"\"\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "87ae33e1-5588-4ed3-9a5b-72e26770b134", + "metadata": {}, + "source": [ + "#### 埋め込みとDocument Text Splitterの作成\n", + "\n", + "埋め込みを保存するパスを初期化し、`pdf_document_loader`関数を実行し、ドキュメントをテキストの塊に分割する関数を作ってみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91d3616d-fb9e-4e9c-b645-054348b10dca", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from tqdm import tqdm\n", + "\n", + "def create_embeddings(embeddings_model,embedding_path: str = \"./embed\"):\n", + "\n", + " embedding_path = \"./embed\"\n", + " print(f\"Storing embeddings to {embedding_path}\")\n", + "\n", + " documents = []\n", + " for url in tqdm(urls):\n", + " print(url)\n", + " document = pdf_document_loader(url)\n", + " #document = html_document_loader(url)\n", + " documents.append(document)\n", + "\n", + "\n", + " text_splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=500,\n", + " chunk_overlap=0,\n", + " length_function=len,\n", + " )\n", + " print(\"Total documents:\",len(documents))\n", + " texts = text_splitter.create_documents(documents)\n", + " print(\"Total texts:\",len(texts))\n", + " index_docs(embeddings_model,url, text_splitter, texts, embedding_path,)\n", + " print(\"Generated embedding successfully\")" + ] + }, + { + "cell_type": "markdown", + "id": "a8b2b731-2763-4ca9-8d51-4b99157526d2", + "metadata": {}, + "source": [ + "#### LangChainからNVIDIA AI Endpointsを使って埋め込みを生成する\n", + "\n", + "このセクションでは、LangChain用のNVIDIA AI Endpointsを使って埋め込みを生成し、将来の再利用のためにエンベッディングを`/embed`ディレクトリのオフラインベクタストアに保存する方法をデモします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d15ffae-6a9b-4ca1-9293-bb41c6b575e1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "embeddings_model = NVIDIAEmbeddings(base_url=\"http://{}:8000/v1\".format(os.environ['EMB_IP_ADDRESS']), model=\"/model\") # or use nvidia/nv-embedqa-e5-v5" + ] + }, + { + "cell_type": "markdown", + "id": "11067f62-6896-4f37-ad9f-b25fbf26bf75", + "metadata": {}, + "source": [ + "以下では、ドキュメントページのコンテンツをループしてテキストとメタデータを拡張し、[FAISS](https://faiss.ai/index.html)を適用する `index_docs` 関数を作成します。埋め込みはローカルに保存されます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49ff7545-bb65-4864-9a77-f3536ce36178", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from typing import List, Union\n", + "\n", + "\n", + "def index_docs(embeddings_model, url: Union[str, bytes], splitter, documents: List[str], dest_embed_dir: str) -> None:\n", + " \"\"\"\n", + " Split the documents into chunks and create embeddings for them.\n", + " \n", + " Args:\n", + " embeddings_model: Model used for creating embeddings.\n", + " url: Source url for the documents.\n", + " splitter: Splitter used to split the documents.\n", + " documents: List of documents whose embeddings need to be created.\n", + " dest_embed_dir: Destination directory for embeddings.\n", + " \"\"\"\n", + " texts = []\n", + " metadatas = []\n", + "\n", + " for document in documents:\n", + " chunk_texts = splitter.split_text(document.page_content)\n", + " texts.extend(chunk_texts)\n", + " metadatas.extend([document.metadata] * len(chunk_texts))\n", + "\n", + " if os.path.exists(dest_embed_dir):\n", + " docsearch = FAISS.load_local(\n", + " folder_path=dest_embed_dir, \n", + " embeddings=embeddings_model, \n", + " allow_dangerous_deserialization=True\n", + " )\n", + " docsearch.add_texts(texts, metadatas=metadatas)\n", + " else:\n", + " docsearch = FAISS.from_texts(texts, embedding=embeddings_model, metadatas=metadatas)\n", + "\n", + " docsearch.save_local(folder_path=dest_embed_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "431a4d44-800a-4083-a63e-af74ce2b04bf", + "metadata": {}, + "source": [ + "#### ベクターストアからの埋め込みのロードとNVIDIA Endpointを使用したRAGの構築\n", + "\n", + "次に、関数 `create_embeddings` を呼び出し、FAISSを使って[vector store](https://developer.nvidia.com/blog/accelerating-vector-search-fine-tuning-gpu-index-algorithms/)から文書を読み込む。ベクトルストアはembeddingsと呼ばれる高次元空間に関連情報を格納しています。\n", + "\n", + "以下の2つのセルを実行してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0bc1104-86af-4896-894a-7de2f022fd2a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "create_embeddings(embeddings_model=embeddings_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7920b73d-e78e-4a85-beef-5b2d22648f96", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# load Embed documents\n", + "embedding_path = \"./embed/\"\n", + "docsearch = FAISS.load_local(folder_path=embedding_path, embeddings=embeddings_model, allow_dangerous_deserialization=True)" + ] + }, + { + "cell_type": "markdown", + "id": "96bbec25-fa14-4176-88c7-b97c6565bfb9", + "metadata": {}, + "source": [ + "### llama-3.1-swallow-8b-instructで会話型検索チェーンを作る\n", + "\n", + "デプロイされたNIMは`http://0.0.0.0:8000`で稼働しているので、NIMの基本モデル`llama-3.1-swallow-8b-instruct`に基づいて[会話型検索チェーン](https://python.langchain.com/v0.1/docs/modules/chains/#conversationalretrievalchain-with-streaming-to-stdout)を作成する。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84565130-b12d-443d-8e5e-81928278dab5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# llm = ChatNVIDIA(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']),\n", + "# model=\"tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\", temperature=0.0, max_tokens=1000, top_p=1.0)\n", + "\n", + "memory = ConversationBufferMemory(memory_key=\"chat_history\", return_messages=True)\n", + "\n", + "qa_prompt=QA_PROMPT\n", + "\n", + "doc_chain = load_qa_chain(llm, chain_type=\"stuff\", prompt=QA_PROMPT)\n", + "\n", + "qa = ConversationalRetrievalChain.from_llm(\n", + " llm=llm,\n", + " retriever=docsearch.as_retriever(),\n", + " chain_type=\"stuff\",\n", + " memory=memory,\n", + " combine_docs_chain_kwargs={'prompt': qa_prompt},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "62929ee2-61ee-4943-8a8f-65dc93de7a6f", + "metadata": {}, + "source": [ + "### クエリによるテスト" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24fa8222-85f3-4c0b-b17e-665a9ce5610b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "query = \"世帯主が不意の事故により入院が必要になる場合の必要資金について、60~64歳及び65歳以上の夫婦が公的年金以 \\\n", + " 外に必要とする月間生活資金と比較してください。\"\n", + "result = qa({\"question\": query})\n", + "print(result.get(\"answer\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d145d79b-a3c3-4132-b4e2-09c3c2cfbb40", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "query = \"製造たばこの個包装及び中間包装に求められる識別マークの表示方法の条件について日本語で説明してください。\"\n", + "result = qa({\"question\": query})\n", + "print(result.get(\"answer\"))" + ] + }, + { + "cell_type": "markdown", + "id": "3aa15b93-4154-4637-b8c6-e3a51c5b7df9", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## References\n", + "\n", + "- https://developer.nvidia.com/blog/tips-for-building-a-rag-pipeline-with-nvidia-ai-langchain-ai-endpoints/\n", + "- https://nvidia.github.io/GenerativeAIExamples/latest/notebooks/05_RAG_for_HTML_docs_with_Langchain_NVIDIA_AI_Endpoints.html\n", + "\n", + "## Licensing\n", + "\n", + "Copyright © 2024 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + } + ], + "metadata": { + "environment": { + "kernel": "conda-base-py", + "name": "workbench-notebooks.m131", + "type": "gcloud", + "uri": "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/workbench-notebooks:m131" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/gke_handson_requirements.txt b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/gke_handson_requirements.txt new file mode 100644 index 0000000..9edd2b2 --- /dev/null +++ b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/gke_handson_requirements.txt @@ -0,0 +1,174 @@ +absl-py==2.1.0 +accelerate==0.34.0 +aiohappyeyeballs==2.4.0 +aiohttp==3.10.5 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.4.0 +argon2-cffi==23.1.0 +argon2-cffi-bindings==21.2.0 +arrow==1.3.0 +asttokens==2.4.1 +async-lru==2.0.4 +attrs==24.2.0 +babel==2.16.0 +beautifulsoup4==4.12.3 +bitsandbytes==0.43.3 +bleach==6.1.0 +blobfile==3.0.0 +certifi==2024.8.30 +cffi==1.17.0 +charset-normalizer==3.3.2 +comm==0.2.2 +dataclasses-json==0.6.7 +datasets==2.21.0 +debugpy==1.8.5 +decorator==5.1.1 +defusedxml==0.7.1 +dill==0.3.8 +docstring_parser==0.16 +executing==2.1.0 +faiss-cpu==1.11.0 +fastjsonschema==2.20.0 +filelock==3.15.4 +fqdn==1.5.1 +frozenlist==1.4.1 +fsspec==2024.6.1 +greenlet==3.0.3 +grpcio==1.66.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.2 +huggingface-hub==0.24.6 +idna==3.8 +ipykernel==6.29.5 +ipython==8.27.0 +isoduration==20.11.0 +jedi==0.19.1 +Jinja2==3.1.4 +json5==0.9.25 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +jupyter-events==0.10.0 +jupyter-lsp==2.2.5 +jupyter_client==8.6.2 +jupyter_core==5.7.2 +jupyter_server==2.14.2 +jupyter_server_terminals==0.5.3 +jupyterlab==4.2.5 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.3 +langchain==0.2.16 +langchain-community==0.2.16 +langchain-core==0.2.38 +langchain-nvidia-ai-endpoints==0.2.2 +langchain-text-splitters==0.2.4 +langdetect==1.0.9 +langsmith==0.1.108 +lxml==5.3.0 +Markdown==3.7 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +marshmallow==3.22.0 +matplotlib-inline==0.1.7 +mdurl==0.1.2 +mistune==3.0.2 +mpmath==1.3.0 +multidict==6.0.5 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nbclient==0.10.0 +nbconvert==7.16.4 +nbformat==5.10.4 +nest-asyncio==1.6.0 +networkx==3.3 +notebook_shim==0.2.4 +numpy==1.26.4 +nvidia-cublas-cu12==12.1.3.1 +nvidia-cuda-cupti-cu12==12.1.105 +nvidia-cuda-nvrtc-cu12==12.1.105 +nvidia-cuda-runtime-cu12==12.1.105 +nvidia-cudnn-cu12==9.1.0.70 +nvidia-cufft-cu12==11.0.2.54 +nvidia-curand-cu12==10.3.2.106 +nvidia-cusolver-cu12==11.4.5.107 +nvidia-cusparse-cu12==12.1.0.106 +nvidia-nccl-cu12==2.20.5 +nvidia-nvjitlink-cu12==12.6.68 +nvidia-nvtx-cu12==12.1.105 +orjson==3.10.7 +overrides==7.7.0 +packaging==24.1 +pandas==2.2.2 +pandocfilters==1.5.1 +parso==0.8.4 +peft==0.12.0 +pexpect==4.9.0 +pillow==10.4.0 +platformdirs==4.2.2 +prometheus_client==0.20.0 +prompt_toolkit==3.0.47 +protobuf==5.28.0 +psutil==6.0.0 +ptyprocess==0.7.0 +pure_eval==0.2.3 +pyarrow==17.0.0 +pycparser==2.22 +pycryptodomex==3.20.0 +pydantic==2.8.2 +pydantic_core==2.20.1 +Pygments==2.18.0 +pypdf==5.7.0 +python-dateutil==2.9.0.post0 +python-json-logger==2.0.7 +pytz==2024.1 +PyYAML==6.0.2 +pyzmq==26.2.0 +referencing==0.35.1 +regex==2024.7.24 +requests==2.32.3 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rich==13.8.0 +rpds-py==0.20.0 +safetensors==0.4.4 +Send2Trash==1.8.3 +setuptools==72.1.0 +shtab==1.7.1 +six==1.16.0 +sniffio==1.3.1 +soupsieve==2.6 +SQLAlchemy==2.0.33 +stack-data==0.6.3 +sympy==1.13.2 +tenacity==8.5.0 +tensorboard==2.17.1 +tensorboard-data-server==0.7.2 +terminado==0.18.1 +tiktoken==0.7.0 +tinycss2==1.3.0 +tokenizers==0.19.1 +torch==2.4.0 +tornado==6.4.1 +tqdm==4.66.5 +traitlets==5.14.3 +transformers==4.44.2 +triton==3.0.0 +trl==0.10.1 +types-python-dateutil==2.9.0.20240821 +typing-inspect==0.9.0 +typing_extensions==4.12.2 +tyro==0.8.10 +tzdata==2024.1 +uri-template==1.3.0 +urllib3==2.2.2 +wcwidth==0.2.13 +webcolors==24.8.0 +webencodings==0.5.1 +websocket-client==1.8.0 +Werkzeug==3.0.4 +wheel==0.43.0 +xxhash==3.5.0 +yarl==1.9.7 diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/architecture-NeMo-Retriever.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/architecture-NeMo-Retriever.png new file mode 100644 index 0000000..294b3f3 Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/architecture-NeMo-Retriever.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/builder_catalog.jpg b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/builder_catalog.jpg new file mode 100644 index 0000000..afc57a4 Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/builder_catalog.jpg differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/catalog1.jpg b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/catalog1.jpg new file mode 100644 index 0000000..5692bd0 Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/catalog1.jpg differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/catalog2.jpg b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/catalog2.jpg new file mode 100644 index 0000000..4404b1c Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/catalog2.jpg differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/embeddings_flow.svg b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/embeddings_flow.svg new file mode 100644 index 0000000..1d33c4c --- /dev/null +++ b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/embeddings_flow.svg @@ -0,0 +1,4 @@ + + + +
Webpage 
URLS
HTML Parser fn
TextSplitter
TEXT's
Embedding Model
VectorDB
\ No newline at end of file diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/lora-arch.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/lora-arch.png new file mode 100644 index 0000000..3a9be8e Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/lora-arch.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nim-benefits.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nim-benefits.png new file mode 100644 index 0000000..449c4c3 Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nim-benefits.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nim-catalog.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nim-catalog.png new file mode 100644 index 0000000..06a21fb Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nim-catalog.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nvidia-nim-dynamic-lora-architecture.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nvidia-nim-dynamic-lora-architecture.png new file mode 100644 index 0000000..d91b666 Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/nvidia-nim-dynamic-lora-architecture.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/practitioner-nim.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/practitioner-nim.png new file mode 100644 index 0000000..56468df Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/practitioner-nim.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/quantization.png b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/quantization.png new file mode 100644 index 0000000..754074f Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/quantization.png differ diff --git a/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/sample_picture.jpg b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/sample_picture.jpg new file mode 100644 index 0000000..bf5ba88 Binary files /dev/null and b/workspace-nim-with-rag-jp/gke_handson/jupyter_notebook/imgs/sample_picture.jpg differ diff --git a/workspace-nim-with-rag-jp/gke_handson/k8s-manifest/embed.yaml b/workspace-nim-with-rag-jp/gke_handson/k8s-manifest/embed.yaml new file mode 100644 index 0000000..0957b0e --- /dev/null +++ b/workspace-nim-with-rag-jp/gke_handson/k8s-manifest/embed.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nim-embedding-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: nim-embedding + template: + metadata: + labels: + app: nim-embedding + spec: + imagePullSecrets: + - name: ngc-docker-registry-secret + nodeSelector: + cloud.google.com/compute-class: "Accelerator" + cloud.google.com/gke-accelerator: "nvidia-l4" + serviceAccountName: default + containers: + - name: nim-container + image: nvcr.io/nim/nvidia/llama-3.2-nv-embedqa-1b-v2:1.3.0 + command: ["nemo-retriever-embedding-start-nim"] + ports: + - name: http-nim + containerPort: 8000 + env: + - name: NGC_API_KEY + valueFrom: + secretKeyRef: + name: ngc-api-key-secret + key: NGC_API_KEY + - name: NIM_MODEL_NAME + value: "/model" + - name: NIM_SERVED_MODEL_NAME + value: "llama-3.2-nv-embedqa-1b-v2" + - name: NIM_MODEL_PROFILE + value: "6ba678701e59a5ee61a7898dee0cdcb283cd064b96b03bf60044d2f6a3f95496" + resources: + requests: + cpu: "2" + memory: "30Gi" + nvidia.com/gpu: "1" + limits: + nvidia.com/gpu: "1" + startupProbe: + httpGet: + path: /v1/health/ready + port: http-nim + initialDelaySeconds: 300 + periodSeconds: 10 + failureThreshold: 18 + volumeMounts: + - name: cache-storage + mountPath: /opt/nim/.cache + volumes: + - name: cache-storage + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: nim-embedding-service +spec: + type: LoadBalancer + selector: + app: nim-embedding + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 diff --git a/workspace-nim-with-rag-jp/gke_handson/k8s-manifest/swallow.yaml b/workspace-nim-with-rag-jp/gke_handson/k8s-manifest/swallow.yaml new file mode 100644 index 0000000..91120a6 --- /dev/null +++ b/workspace-nim-with-rag-jp/gke_handson/k8s-manifest/swallow.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nim-swallow-autopilot-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: nim-swallow-autopilot + template: + metadata: + labels: + app: nim-swallow-autopilot + spec: + initContainers: + - name: model-downloader + image: alpine/git:latest + env: + - name: GIT_LFS_SKIP_SMUDGE + value: "1" + command: + - /bin/sh + - -c + - | + apk add git-lfs && \ + git lfs install && \ + git clone https://huggingface.co/tokyotech-llm/Llama-3.1-Swallow-8B-Instruct-v0.1 /models && \ + cd /models && \ + git lfs pull + volumeMounts: + - name: model-storage + mountPath: /models + + containers: + - name: nim-container + image: nvcr.io/nim/meta/llama-3.1-8b-instruct:1.8 + env: + - name: NIM_MODEL_NAME + value: "/models" + - name: NIM_SERVED_MODEL_NAME + value: "llama-3.1-swallow-8b" + - name: NIM_MODEL_PROFILE + value: "vllm" + - name: NIM_ENABLE_KV_CACHE_REUSE + value: "1" + ports: + - containerPort: 8000 + resources: + limits: + nvidia.com/gpu: 1 + volumeMounts: + - name: model-storage + mountPath: /models + - name: cache-storage + mountPath: /opt/nim/.cache + + nodeSelector: + cloud.google.com/gke-accelerator: nvidia-l4 + tolerations: + - key: "nvidia.com/gpu" + operator: "Exists" + effect: "NoSchedule" + + imagePullSecrets: + - name: ngc-docker-registry-secret + + volumes: + - name: model-storage + emptyDir: {} + - name: cache-storage + emptyDir: {} + +--- +apiVersion: v1 +kind: Service +metadata: + name: nim-swallow-autopilot-service +spec: + type: LoadBalancer + selector: + app: nim-swallow-autopilot + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/custom_lora.ipynb b/workspace-nim-with-rag-jp/jupyter_notebook/custom_lora.ipynb new file mode 100644 index 0000000..d576be5 --- /dev/null +++ b/workspace-nim-with-rag-jp/jupyter_notebook/custom_lora.ipynb @@ -0,0 +1,931 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c9805966-0f48-4c67-8905-233f499d05e1", + "metadata": {}, + "source": [ + "# カスタムLoRA: LLaMA3-8B-Instructのファインチューニング\n", + "---\n", + "\n", + "**概要:** \n", + "\n", + "このNotebookでは、Low-Rank Adaptation (LoRA)を使ってLLaMA 3モデルをファインチューニングするプロセスを探ります。扱うトピックは以下の通りです:\n", + "\n", + "1. **データ前処理**: モデルのトレーニングに最適な入力を確保するためのデータの準備とクリーニング\n", + "2. **学習のセットアップ**:\n", + " - **Tokenizerのロード**: テキストデータを処理するために、LLaMA 3に適したトークナイザーを初期化\n", + " - **ハイパーパラメータの設定**: 効果的なモデルトレーニングのための主要なハイパーパラメータを定義\n", + " - **PEFT (Parameter-Efficient Fine-Tuning)**: 少ない計算ソースでモデルをファインチューニングする技術\n", + " - **量子化**: パフォーマンスを維持したままモデルサイズを縮小し、より高速な推論とメモリ使用量の削減が可能\n", + " - **事前学習済みモデルのロード**: 事前学習されたLLaMA 3モデルを持ち込み、カスタムデータでさらにファインチューニング\n", + " - **Trainerのハイパーパラメータの設定**: 最適なパフォーマンスを得るために、トレーニングループに特有のパラメータを設定\n", + " - **推論の実行**: ファインチューニングされたモデルを新しいデータで評価し、その性能と精度をテストを実施\n", + "\n", + "このNotebookは、LlaMA 3モデルを特定のユースケースに合わせて効果的にファインチューニングし、最適化するための手順を説明します。" + ] + }, + { + "cell_type": "markdown", + "id": "aceef47a", + "metadata": {}, + "source": [ + "## データ前処理\n", + "トレーニングにはLLM-jpの[magpie-sft-v1.0](https://huggingface.co/datasets/llm-jp/magpie-sft-v1.0)データセット(Apache 2.0ライセンス)を使用します。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3d637021", + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "from datasets import load_dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcd48b2c-70ab-473d-9e5d-b62f135ad096", + "metadata": {}, + "outputs": [], + "source": [ + "# 日本語の指示データセット\n", + "dataset_name = \"llm-jp/magpie-sft-v1.0\"\n", + "ds = load_dataset(dataset_name) # ダウンロードには15秒程度かかります \n", + "ds" + ] + }, + { + "cell_type": "markdown", + "id": "db47e3b8-f287-454e-b24d-20ab646c8f00", + "metadata": {}, + "source": [ + "データセットから5つのサンプルを見てみましょう。`conversations`フィールドに`user`と`assistant`の会話データが含まれています。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76f71948-6334-4201-99da-13df1f1aaa6d", + "metadata": {}, + "outputs": [], + "source": [ + "for sample in ds['train'].select(range(5)):\n", + " print(f\"\\n {'*' * 64}\\n{sample}\\n{'*' * 64}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3d0ae13f", + "metadata": {}, + "source": [ + "Llama3モデルは,学習データセットが[こちら](https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3/)のような特定の形式である必要があります(=`<|begin_of_text|>`や`<|eot_id|>`などの特殊トークンを使用)。これは、トークナイザの`apply_chat_template`メソッドを使用して、データセットの`conversations`フィールドを変換することで実現できます。サンプルを1つ見てみましょう。\n", + "\n", + "Llama-3ファミリーのモデルはオープンソースですが、アクセスリクエストの承認が必要です。ブートキャンプ環境では、ウエイトはすでにhuggingface互換フォーマットに変換され、参加者が素早くアクセスできるように共有の場所に保存されています。\n", + "\n", + "ご自身の環境でこの教材を実行する場合は、[こちら](https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct)からLlamaモデルへのアクセスをリクエストし、こちらの[リンク](https://huggingface.co/settings/tokens)からHuggingFaceユーザアクセストークンを生成してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7faee5b4", + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import AutoTokenizer\n", + "\n", + "# 独自の環境で実行する場合は、以下で`token`を指定してください。\n", + "# token='hf_..'\n", + "# base_model = \"meta-llama/Meta-Llama-3-8B-Instruct\"\n", + "\n", + "# 共有モデルの重みの場所\n", + "base_model = \"/mnt/lustre/docker-strg/llama3_8b_weights\" \n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(base_model) # Tokenizerをロード\n", + "\n", + "# conversationsフィールドをLlama3のフォーマットに変換\n", + "preprocessed_text = tokenizer.apply_chat_template(\n", + " conversation=ds['train'][0]['conversations'],\n", + " tokenize=False,\n", + " # token=token, # 独自の環境で実行する場合は、こちらを指定してください。\n", + ")\n", + "print(preprocessed_text)" + ] + }, + { + "cell_type": "markdown", + "id": "935b3d47", + "metadata": {}, + "source": [ + "データセット内の全てのサンプルに対して同じ操作を行います。これはデータセットの`map`メソッドを使用して実現できます。変換されたテキストデータを`preprocessed_conversations`フィールドに保存します。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "42af064d", + "metadata": {}, + "outputs": [], + "source": [ + "ds['train'] = ds['train'].map(lambda x: {'preprocessed_conversations': tokenizer.apply_chat_template(\n", + " conversation=x['conversations'],\n", + " tokenize=False, \n", + ")})" + ] + }, + { + "cell_type": "markdown", + "id": "5a313b21", + "metadata": {}, + "source": [ + "`preprocessed_conversations`フィールドの最初のサンプルを見てみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2a8adbe", + "metadata": {}, + "outputs": [], + "source": [ + "print(ds['train']['preprocessed_conversations'][0])" + ] + }, + { + "cell_type": "markdown", + "id": "8759d3e7", + "metadata": {}, + "source": [ + "このデータセットを保存する前に、データセットをトレーニングセットとテストセットに分割します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80893089", + "metadata": {}, + "outputs": [], + "source": [ + "ds = ds['train'].train_test_split(test_size=0.1, seed=42)\n", + "ds" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06aa43ce", + "metadata": {}, + "outputs": [], + "source": [ + "!mkdir -p data/ds_preprocess\n", + "ds.save_to_disk('data/ds_preprocess/')" + ] + }, + { + "cell_type": "markdown", + "id": "ca1ed783-501c-40a7-892d-75c7c560aaf1", + "metadata": {}, + "source": [ + "これで変換されたデータセットを保存できました。これで、カーネルが故障した場合にデータセットを直接ロードするのに役立ちます。" + ] + }, + { + "cell_type": "markdown", + "id": "202a46e2", + "metadata": {}, + "source": [ + "## 学習セットアップ\n", + "\n", + "ファインチューニングの設定には、以下のライブラリーが役に立ちます。" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b0fe928a", + "metadata": {}, + "outputs": [], + "source": [ + "# メモリが不足している場合は、`os.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"max_split_size_mb:64\"`のコメントを解除してください。\n", + "\n", + "import torch\n", + "\n", + "from peft import LoraConfig, PeftModel\n", + "from trl import SFTTrainer\n", + "from datasets import load_from_disk\n", + "from transformers import (\n", + " AutoModelForCausalLM,\n", + " AutoTokenizer,\n", + " BitsAndBytesConfig,\n", + " TrainingArguments,\n", + ")\n", + "\n", + "# import os\n", + "# os.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"max_split_size_mb:64\"" + ] + }, + { + "cell_type": "markdown", + "id": "a13d9f4e-7581-4a91-a679-6fca569e741c", + "metadata": {}, + "source": [ + "重要な成果物のロードと保存のための重要なパスを設定します。" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "fd3eaf64-a348-4b8b-9bd5-32f26f857d5a", + "metadata": {}, + "outputs": [], + "source": [ + "# LoRAのベースモデル\n", + "# base_model = \"meta-llama/Meta-Llama-3-8B-Instruct\" # 独自の環境で実行する場合はこちらを使用してください。\n", + "base_model = \"/mnt/lustre/docker-strg/llama3_8b_weights\" # 共有モデルの重みの場所\n", + "\n", + "# 学習データセットのパス\n", + "data_path = \"data/ds_preprocess/train\"\n", + "# テストデータセットのパス\n", + "eval_path = \"data/ds_preprocess/test\"\n", + "\n", + "# 変換されたデータセットのロード\n", + "dataset = load_from_disk(data_path)\n", + "eval_dataset = load_from_disk(eval_path)" + ] + }, + { + "cell_type": "markdown", + "id": "31136fd4", + "metadata": {}, + "source": [ + "先ほど保存したデータセットが正確にロードされていることを確認します。(上記で確認したサンプルとは異なるサンプルが表示されている可能性がありますが、問題ありません。)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6221eabd", + "metadata": {}, + "outputs": [], + "source": [ + "print(dataset['preprocessed_conversations'][0])" + ] + }, + { + "cell_type": "markdown", + "id": "0d1c3de7-27d3-48b2-9ae6-e12a26c432aa", + "metadata": {}, + "source": [ + "### Tokenizerのロード" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bdee1d25", + "metadata": {}, + "outputs": [], + "source": [ + "# Llama-3-8B-Instructのトークナイザーをダウンロードします。\n", + "tokenizer = AutoTokenizer.from_pretrained(base_model,\n", + " # token=token,\n", + " # trust_remote_code=True\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5522772f", + "metadata": {}, + "outputs": [], + "source": [ + "tokenizer.pad_token = tokenizer.eos_token\n", + "tokenizer.padding_side = \"right\"" + ] + }, + { + "cell_type": "markdown", + "id": "d17b9699-7e15-47a4-86f8-0ff73ae69bb8", + "metadata": {}, + "source": [ + "結果を保存するディレクトリを作成します。" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b5d624d7-8727-47c7-87c5-646161fd4510", + "metadata": {}, + "outputs": [], + "source": [ + "! mkdir -p model" + ] + }, + { + "cell_type": "markdown", + "id": "ba8f953f-4e85-4bc3-a78c-6884a97af10f", + "metadata": {}, + "source": [ + "### 学習のハイパーパラメータの設定" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2da5298", + "metadata": {}, + "outputs": [], + "source": [ + "# ハイパーパラメータの設定\n", + "training_params = TrainingArguments(\n", + " output_dir=\"model/results\", # モデルの結果を保存するディレクトリ\n", + " num_train_epochs=2, # トレーニングエポック数\n", + " per_device_train_batch_size=2, # 学習中のデバイスあたりのバッチサイズ\n", + " gradient_accumulation_steps=10, # 勾配を蓄積するステップ数\n", + " group_by_length=True, # 同様の長さの文をグループ化\n", + " save_steps=100, # 100ステップごとにモデルのチェックポイントを保存\n", + " logging_steps=25, # 25ステップごとにメトリクスをログに記録\n", + " learning_rate=2e-4, # 初期学習率\n", + " weight_decay=0.001, # 適用する重み減衰(L2正則化)\n", + " evaluation_strategy=\"steps\", # 一定のステップ数で評価を実施\n", + " eval_steps=25, # 25ステップごとに評価\n", + " fp16=False, # FP16\n", + " bf16=False, # BF16\n", + " max_grad_norm=0.3, # 最大勾配ノルム(勾配クリッピング用)\n", + " max_steps=-1, # 学習ステップの総数(-1は制限なしを意味します)\n", + " warmup_ratio=0.03, # 学習率のウォームアップを実行するステップの割合\n", + " optim=\"paged_adamw_32bit\", # 使用するオプティマイザ(ページングメモリ付き32ビットAdamW)\n", + " lr_scheduler_type=\"constant\", # 学習率スケジューラタイプ(定数)\n", + " report_to=\"tensorboard\" # レポートツール(この場合はTensorBoard)\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "8d25da6c-f7f0-4a46-ace7-66d02ee6f300", + "metadata": {}, + "source": [ + "### PEFT\n", + "LoRA テクニックは `LoraConfig` によって適用され、ベースモデルへの適用方法を制御する PEFT パラメータを提供します。以下のセルで使用されているパラメータの説明は以下の通りです:\n", + "\n", + "- **lora_alpha**: LoRA スケーリング係数\n", + "- **lora_dropout**: LoRAレイヤーのドロップアウト確率。\n", + "- **r**: 更新行列のランク、int で表される。ランクが低いほど更新行列が小さくなり、学習可能なパラメータが少なくなります。\n", + "- **bias**: バイアスパラメータを学習するかどうかを指定する。'none'、'all'、'lora_only'のいずれかを指定します。\n", + "- **task_type**: `CAUSAL_LM`, `FEATURE_EXTRACTION`, `QUESTION_ANS`, `SEQ_2_SEQ_LM`, `SEQ_CLS and TOKEN_CLS` などがあります。\n", + "- **target_modules**: LoRAの適用対象のモジュールを指定します。`all-linear`はすべての線形層にLoRAを適用します。 \n", + "\n", + "実行したいタスクはテキスト生成なので、task_type をテキスト生成タスクでよく使われる Causal language model `(CAUSAL_LM)` に設定しました。以下のセルを実行して、LoRAの設定を行ってください。" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "743a2ce3", + "metadata": {}, + "outputs": [], + "source": [ + "peft_params = LoraConfig(\n", + " lora_alpha=16, # LoRAスケーリング係数\n", + " lora_dropout=0.1, # LoRAレイヤーのドロップアウト確率\n", + " r=32, # LoRA行列のランク\n", + " bias=\"none\", # 適用するバイアスの種類(この場合は \"none\")\n", + " task_type=\"CAUSAL_LM\", # タスクの種類(この場合はCausal Language Modeling)\n", + " target_modules=\"all-linear\", # LoRAを適用するモジュール(全線形層)\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "f0cf32ac-6bb0-4763-b59e-c1c0b65e6138", + "metadata": {}, + "source": [ + "### 量子化\n", + "**4-bit 量子化コンフィグレーション**\n", + "\n", + "モデルの量子化は、一般的なディープラーニングの最適化手法であり、モデルデータ(ネットワークパラメータと活性化)を浮動小数点から低精度表現(通常は8ビット整数)に変換します。量子化はより少ないビット数でデータを表現するため、特に大規模な言語モデル(LLM)において、メモリ使用量を削減し、推論を高速化するのに有効な手法です。PEFTメソッドと組み合わせることで、推論のためのLLMの学習とロードを容易にすることができます。\n", + "\n", + "
\n", + "
source: Using Quantization Aware Training with NVIDIA TensorRT
\n", + "\n", + "モデルを量子化するいくつかの方法とアルゴリズムは[こちら](https://huggingface.co/docs/peft/main/en/developer_guides/quantization)にあります。量子化を簡単に実装し、Transformersと統合するためのライブラリに `bitsandbytes` ライブラリがあります。このライブラリは、`BitsAndBytesConfig`クラスを使ってモデルを8ビットまたは4ビットに量子化するための設定パラメータを提供します。以下のセルで使用されている4ビットのパラメータは以下のように記述されています:\n", + "\n", + "- **load_in_4bit**: モデルをロードする際に4ビットに量子化するには `True` を設定。\n", + "- **bnb_4bit_quant_type**: `\"nf4\"` に設定すると、正規分布から初期化された重みに特別な4ビットのデータ型を使用。\n", + "- **bnb_4bit_use_double_quant**: `True` に設定すると、既に量子化された重みをネストした量子化スキームで量子化。\n", + "- **bnb_4bit_compute_dtype**: `torch.float16` または `torch.bfloat16` に設定。\n", + "\n", + "以下のセルを実行して、モデルの4ビット量子化を設定。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "abc15fde", + "metadata": {}, + "outputs": [], + "source": [ + "quant_config = BitsAndBytesConfig(\n", + " load_in_4bit=True,\n", + " bnb_4bit_quant_type=\"nf4\",\n", + " bnb_4bit_compute_dtype=torch.bfloat16,\n", + " bnb_4bit_use_double_quant=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e04e9e79-dcaa-4459-970d-a89389037c4b", + "metadata": {}, + "source": [ + "### 事前学習モデルのロード" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b355ece", + "metadata": {}, + "outputs": [], + "source": [ + "model = AutoModelForCausalLM.from_pretrained(\n", + " base_model,\n", + " quantization_config=quant_config,\n", + " device_map={\"\": 0},\n", + " # token=token\n", + ")\n", + "model.config.use_cache = False\n", + "model.config.pretraining_tp = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f31ee3c8", + "metadata": {}, + "outputs": [], + "source": [ + "!nvidia-smi" + ] + }, + { + "cell_type": "markdown", + "id": "70856c8c-0ede-4a08-ac8f-45e71a3826c3", + "metadata": {}, + "source": [ + "モデルがGPUにロードされていることが分かります。" + ] + }, + { + "cell_type": "markdown", + "id": "523b4407-717c-436a-a773-e4e4de8b15be", + "metadata": {}, + "source": [ + "### Trainerのハイパーパラメータのセット\n", + "\n", + "モデルトレーナを開始するために、 [Supervised fine-tuning (SFT)](https://huggingface.co/docs/trl/en/sft_trainer)からトレーナオブジェクトを作成します。SFTは強化学習を使って変換言語モデルを学習するための統合変換ツール [Reinforcement Learning (TRL)](https://huggingface.co/docs/trl/en/index)の一部です。その他に、[Reward Modeling step (RM)](https://huggingface.co/docs/trl/en/reward_trainer) や [Proximal Policy Optimization (PPO)](https://arxiv.org/abs/1707.06347)があります。SFTトレーナーオブジェクトでは、モデル、学習データセット、PEFT設定オブジェクト、モデル・トークナイザー、学習引数パラメータを設定します。また、データセット内で使用するフィールド(`text`)も指定します。\n", + "**注意:** *単一のDGX A100 GPUで実行する場合は、`max_seq_length`の値を1024に変更するか、デフォルトのnoneに設定します。*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4e7d8e3-ed7e-414a-8cb3-c1c0df0a5811", + "metadata": {}, + "outputs": [], + "source": [ + "len(dataset)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ffcc51f", + "metadata": {}, + "outputs": [], + "source": [ + "eval_dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4fec6c81", + "metadata": {}, + "outputs": [], + "source": [ + "# 時間の節約のため、1000のランダムサンプルを使用して学習します。評価は100サンプルで行います。\n", + "trainer = SFTTrainer(\n", + " model=model,\n", + " tokenizer=tokenizer,\n", + " train_dataset=dataset.shuffle(seed=42).select(range(1000)),\n", + " eval_dataset = eval_dataset.select(range(100)),\n", + " dataset_text_field=\"preprocessed_conversations\",\n", + " peft_config=peft_params,\n", + " args=training_params,\n", + " max_seq_length=1024,\n", + " packing=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bdbb4801", + "metadata": {}, + "source": [ + "以下のセルを実行して、以下のSFT trainerオブジェクトの学習を行います。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7c65d31-eaa3-44f2-b360-75f07ba38943", + "metadata": {}, + "outputs": [], + "source": [ + "import jinja2\n", + "print(jinja2.__version__)\n" + ] + }, + { + "cell_type": "markdown", + "id": "0c6a6106", + "metadata": {}, + "source": [ + "以下のセルを実行して学習を開始します。学習はおよそ30分程度かかります。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08b6b9d7", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "trainer.train()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa0ec40c", + "metadata": {}, + "outputs": [], + "source": [ + "# LoRAアダプターのsafetensorsファイルおよびトークナイザーを保存\n", + "new_model_name = \"model/Llama-3-8b-instruct-hf-finetune\"\n", + "\n", + "trainer.model.save_pretrained(new_model_name)\n", + "trainer.tokenizer.save_pretrained(new_model_name)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "c58e39c8-181c-4dc2-8b9d-de8033c4a29f", + "metadata": {}, + "outputs": [], + "source": [ + "# LoRAアダプターのバイナリファイルを保存\n", + "trainer.model.save_pretrained(new_model_name,safe_serialization=False)" + ] + }, + { + "cell_type": "markdown", + "id": "7148c67c-b6ea-40aa-90e0-40a141e6a792", + "metadata": {}, + "source": [ + "### 推論の実行" + ] + }, + { + "cell_type": "markdown", + "id": "90433b56", + "metadata": {}, + "source": [ + "まずは**学習前のベースのモデル**の生成結果を確認します。 \n", + "\n", + "Llama-3-8B-Instructの例だと、日本語で入力しているのにもかかわらず、生成結果が英語になっています。\n", + "**CUDA Out of Memoryが出る場合は、一度Jupyterカーネルを再起動して以下のセルから実行してください。**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c947bec", + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import pipeline, AutoTokenizer\n", + "\n", + "# 独自の環境で実行する場合はこちらを使用してください。\n", + "# model_name = \"meta-llama/Meta-Llama-3-8B-Instruct\"\n", + "\n", + "# 共有モデルの重みの場所\n", + "model_name = \"/mnt/lustre/docker-strg/llama3_8b_weights\" \n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(model_name)\n", + "\n", + "pp_base = pipeline(\n", + " model=model_name, \n", + " tokenizer=tokenizer, \n", + " max_new_tokens=512, \n", + " task=\"text-generation\", \n", + " temperature=.7, \n", + " top_p=0.4,\n", + " device=\"cuda:0\"\n", + ")\n", + "response = pp_base(tokenizer.apply_chat_template(\n", + " conversation=[\n", + " {\"role\": \"user\", \"content\": \"最近朝起きるのが辛いのですが、その場合はどうすればいいですか?\"}\n", + " ],\n", + " tokenize=False,\n", + "))[0]['generated_text']\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "id": "84a9fa56", + "metadata": {}, + "source": [ + "**想定される出力**\n", + "```\n", + "<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n", + "\n", + "最近朝起きるのが辛いのですが、その場合はどうすればいいですか?<|eot_id|>assistant\n", + "\n", + "The struggle is real! 😴 Waking up early can be tough, but don't worry, I've got some tips to help you overcome the morning blues:\n", + "\n", + "1. **Set a consistent wake-up time**: Set your alarm for the same time every day, including weekends. Consistency will help regulate your body's internal clock.\n", + "2. **Gradually adjust your wake-up time**: If you're not a morning person, don't try to wake up at 5 am immediately. Start by setting your alarm 15-30 minutes earlier each day and gradually work your way up to your desired wake-up time.\n", + "3. **Create a bedtime routine**: Wind down before bed with a relaxing activity, such as reading, meditation, or a warm bath. This will help signal to your body that it's time to sleep.\n", + "4. **Get enough sleep**: Aim for 7-9 hours of sleep each night to ensure you're well-rested and energized in the morning.\n", + "5. **Get some morning sunlight**: Exposure to natural light in the morning helps regulate your circadian rhythms, making it easier to wake up. Open your curtains or take a short walk outside in the morning.\n", + "6. **Avoid screens before bed**: The blue light emitted by smartphones, tablets, and computers can suppress melatonin production, making it harder to fall asleep and wake up in the morning.\n", + "7. **Use a wake-up light**: A wake-up light is a device that simulates a sunrise by gradually increasing the light in your room, helping to wake you up naturally.\n", + "8. **Make a plan for the morning**: Give yourself a reason to get out of bed by setting a goal or scheduling something enjoyable for the morning, such as exercise, breakfast with a friend, or a hobby.\n", + "9. **Use a motivational alarm**: Choose an alarm sound that motivates you to get out of bed, such as a favorite song or a motivational quote.\n", + "10. **Reward yourself**: Give yourself a small reward for waking up on time, such as a favorite breakfast treat or a short break from work.\n", + "\n", + "Remember, it may take some time for your body to adjust to a new wake-up time. Be patient, and don't be too hard on yourself if you don't see immediate results. Good luck! 💪\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "108066d8", + "metadata": {}, + "source": [ + "続いて先ほど学習したLoRAモデルの生成結果を確認します。まずは、学習したモデルのLoRAアダプターをロードします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d40cc8b", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "from transformers import AutoModelForCausalLM, BitsAndBytesConfig\n", + "from peft import PeftModel, PeftConfig\n", + "\n", + "peft_model_path = \"model/Llama-3-8b-instruct-hf-finetune\"\n", + "peft_config = PeftConfig.from_pretrained(peft_model_path)\n", + "quant_config = BitsAndBytesConfig(\n", + " load_in_4bit=True,\n", + " bnb_4bit_quant_type=\"nf4\",\n", + " bnb_4bit_compute_dtype=torch.bfloat16,\n", + " bnb_4bit_use_double_quant=False,\n", + ")\n", + "\n", + "model = AutoModelForCausalLM.from_pretrained(\n", + " peft_config.base_model_name_or_path,\n", + " quantization_config=quant_config,\n", + " device_map={\"\": 0},\n", + ")\n", + "peft_model = PeftModel.from_pretrained(\n", + " model, # modified inplace\n", + " peft_model_path,\n", + ")\n", + "\n", + "peft_model" + ] + }, + { + "cell_type": "markdown", + "id": "b35869ad", + "metadata": {}, + "source": [ + "学習したLoRAモデルの生成結果を確認します。まずはパイプラインを定義します。セルを実行した際に以下のWarningが出ますが、無視してください。\n", + "\n", + "`The model 'PeftModelForCausalLM' is not supported for text-generation. Supported models are ['BartForCausalLM', 'BertLMHeadModel', 'BertGenerationDecoder', 'BigBirdForCausalLM', 'BigBirdPegasusForCausalLM', 'BioGptForCausalLM', 'BlenderbotForCausalLM', 'BlenderbotSmallForCausalLM', 'BloomForCausalLM', 'CamembertForCausalLM', 'LlamaForCausalLM', 'CodeGenForCausalLM', 'CohereForCausalLM', 'CpmAntForCausalLM', 'CTRLLMHeadModel', 'Data2VecTextForCausalLM', 'DbrxForCausalLM', 'ElectraForCausalLM', 'ErnieForCausalLM', 'FalconForCausalLM', 'FuyuForCausalLM', 'GemmaForCausalLM', 'Gemma2ForCausalLM', 'GitForCausalLM', 'GPT2LMHeadModel', 'GPT2LMHeadModel', 'GPTBigCodeForCausalLM', 'GPTNeoForCausalLM', 'GPTNeoXForCausalLM', 'GPTNeoXJapaneseForCausalLM', 'GPTJForCausalLM', 'JambaForCausalLM', 'JetMoeForCausalLM', 'LlamaForCausalLM', 'MambaForCausalLM', 'Mamba2ForCausalLM', 'MarianForCausalLM', 'MBartForCausalLM', 'MegaForCausalLM', 'MegatronBertForCausalLM', 'MistralForCausalLM', 'MixtralForCausalLM', 'MptForCausalLM', 'MusicgenForCausalLM', 'MusicgenMelodyForCausalLM', 'MvpForCausalLM', 'NemotronForCausalLM', 'OlmoForCausalLM', 'OpenLlamaForCausalLM', 'OpenAIGPTLMHeadModel', 'OPTForCausalLM', 'PegasusForCausalLM', 'PersimmonForCausalLM', 'PhiForCausalLM', 'Phi3ForCausalLM', 'PLBartForCausalLM', 'ProphetNetForCausalLM', 'QDQBertLMHeadModel', 'Qwen2ForCausalLM', 'Qwen2MoeForCausalLM', 'RecurrentGemmaForCausalLM', 'ReformerModelWithLMHead', 'RemBertForCausalLM', 'RobertaForCausalLM', 'RobertaPreLayerNormForCausalLM', 'RoCBertForCausalLM', 'RoFormerForCausalLM', 'RwkvForCausalLM', 'Speech2Text2ForCausalLM', 'StableLmForCausalLM', 'Starcoder2ForCausalLM', 'TransfoXLLMHeadModel', 'TrOCRForCausalLM', 'WhisperForCausalLM', 'XGLMForCausalLM', 'XLMWithLMHeadModel', 'XLMProphetNetForCausalLM', 'XLMRobertaForCausalLM', 'XLMRobertaXLForCausalLM', 'XLNetLMHeadModel', 'XmodForCausalLM'].`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53faecdc", + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import pipeline, AutoTokenizer\n", + "tokenizer = AutoTokenizer.from_pretrained(peft_config.base_model_name_or_path)\n", + "\n", + "# パイプラインの定義\n", + "pp_peft = pipeline(\n", + " model=peft_model, \n", + " tokenizer=tokenizer,\n", + " max_new_tokens=512, \n", + " task=\"text-generation\", \n", + " temperature=.7, \n", + " top_p=0.4,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3944d6e9", + "metadata": {}, + "source": [ + "続いて生成結果です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdae8fe4", + "metadata": {}, + "outputs": [], + "source": [ + "response = pp_peft(tokenizer.apply_chat_template(\n", + " conversation=[\n", + " {\"role\": \"user\", \"content\": \"最近朝起きるのが辛いのですが、その場合はどうすればいいですか?\"}\n", + " ],\n", + " tokenize=False,\n", + "))[0]['generated_text']\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "id": "238184aa", + "metadata": {}, + "source": [ + "**想定される出力**\n", + "```\n", + "<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n", + "\n", + "最近朝起きるのが辛いのですが、その場合はどうすればいいですか?<|eot_id|>assistant\n", + "\n", + "朝起きるのが辛いのは、体が自然と眠りにつく時間帯に起きる必要があるため、体調が整っていない状態で起きることによるものです。以下にいくつかの対策を提案します:\n", + "\n", + "1. **リラックスした睡眠**: 7時間以上の睡眠を取ることで、朝起きるのが楽になることがあります。ただし、過度に過眠は避けてください。\n", + "\n", + "2. **定時的な起床**: 一定の時間に起きるよう、闇時間を設定することで、体に慣れさせることができます。\n", + "\n", + "3. **朝のリズムを作る**: 朝起きてすぐに何かを始めることは難しいかもしれませんが、まずはゆっくりとした動きを始めると良いでしょう。例えば、ゆっくりと体を動かす、または一日の計画を立てるなど、ゆっくりとした活動を始めることができます。\n", + "\n", + "4. **朝食**: 朝食は体を温め、エネルギーを補給するのに役立ちます。健康的な朝食を摂ることで、朝起きるのが楽になるかもしれません。\n", + "\n", + "5. **モチベーションを高める**: 朝起きるのが辛いと感じる場合、目標を設定することでモチベーションを高めることができます。例えば、特定のタスクを達成したい、または特定の時間に到着したいなど、具体的な目標を設定することで、朝起きるのが楽になるかもしれません。\n", + "\n", + "これらの方法を試してみてください。何か他に質問があれば教えてください。何かお手伝いできることがあれば、喜んでお手伝いします。何か他に質問があれば教えてください。何かお手伝いできることがあれば、喜んでお手伝いします。何か他に質問があれば教えてください。何かお手伝いできることがあれば、喜んでお手伝いします。何か他に質問があれば教えてください。何かお手伝いできることがあれば、喜んでお手伝いします。何か他に質問があれば教えてください。何かお手伝いできることがあ\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c5c93bb7", + "metadata": {}, + "source": [ + "生成文が日本語になっており、LoRAチューニングによってモデルを日本語対応できたことが確認できます。さらに、こちらのモデルは重み部分は4ビットに量子化されているため、メモリの使用量がベースのモデルよりもおよそ1/4少なくなっている点も注目です。 \n", + "\n", + "他にもいくつか生成してみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e90f48a", + "metadata": {}, + "outputs": [], + "source": [ + "response = pp_peft(tokenizer.apply_chat_template(\n", + " conversation=[\n", + " {\"role\": \"user\", \"content\": \"NLPの分野ではどのような研究が行われていますか?\"}\n", + " ],\n", + " tokenize=False,\n", + "))[0]['generated_text']\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97c5312f", + "metadata": {}, + "outputs": [], + "source": [ + "response = pp_peft(tokenizer.apply_chat_template(\n", + " conversation=[\n", + " {\"role\": \"user\", \"content\": \"かわいいだけじゃだめですか?\"}\n", + " ],\n", + " tokenize=False,\n", + "))[0]['generated_text']\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a81841d-42e1-4649-acb9-258f4ca1513f", + "metadata": {}, + "outputs": [], + "source": [ + "# モデルとLoRAアダプターをマージしたい場合は以下のセルを実行してください。\n", + "model = PeftModel.from_pretrained(model, peft_model_path)\n", + "merged_model = model.merge_and_unload()\n", + "merged_model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c41fa15e-5d63-4298-9aeb-1f7a3394b07f", + "metadata": {}, + "outputs": [], + "source": [ + "!nvidia-smi" + ] + }, + { + "cell_type": "markdown", + "id": "ba574f9b-554b-4619-a213-abea0c6a7fa9", + "metadata": {}, + "source": [ + "以下のコマンドは、現在のカーネルを終了させ、NIMを実行するためにGPUを解放します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d31e68ff-b9d3-497a-bd74-8ba12f2419a1", + "metadata": {}, + "outputs": [], + "source": [ + "!kill -9 $(nvidia-smi --query-compute-apps=pid --format=csv,noheader | awk 'NR==1')\n" + ] + }, + { + "cell_type": "markdown", + "id": "62dc639d-9f12-4bbb-9c6f-a37a8dfee007", + "metadata": {}, + "source": [ + "カスタムLoRAアダプターを使用するには、 nim_lora_adapter ノートブックをご参照ください。" + ] + }, + { + "cell_type": "markdown", + "id": "5ed86fc2-ba43-4b8a-84d5-64c91d49f6d1", + "metadata": {}, + "source": [ + "---\n", + "## Licensing\n", + "\n", + "Copyright © 2024 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/agent-components.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/agent-components.png new file mode 100644 index 0000000..b5dacb8 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/agent-components.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/architecture-NeMo-Retriever.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/architecture-NeMo-Retriever.png new file mode 100644 index 0000000..294b3f3 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/architecture-NeMo-Retriever.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/builder_catalog.jpg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/builder_catalog.jpg new file mode 100644 index 0000000..afc57a4 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/builder_catalog.jpg differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/catalog1.jpg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/catalog1.jpg new file mode 100644 index 0000000..5692bd0 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/catalog1.jpg differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/catalog2.jpg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/catalog2.jpg new file mode 100644 index 0000000..4404b1c Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/catalog2.jpg differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/embeddings_flow.svg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/embeddings_flow.svg new file mode 100644 index 0000000..1d33c4c --- /dev/null +++ b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/embeddings_flow.svg @@ -0,0 +1,4 @@ + + + +
Webpage 
URLS
HTML Parser fn
TextSplitter
TEXT's
Embedding Model
VectorDB
\ No newline at end of file diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/lora-arch.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/lora-arch.png new file mode 100644 index 0000000..3a9be8e Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/lora-arch.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nim-benefits.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nim-benefits.png new file mode 100644 index 0000000..449c4c3 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nim-benefits.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nim-catalog.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nim-catalog.png new file mode 100644 index 0000000..06a21fb Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nim-catalog.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nvidia-nim-dynamic-lora-architecture.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nvidia-nim-dynamic-lora-architecture.png new file mode 100644 index 0000000..d91b666 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/nvidia-nim-dynamic-lora-architecture.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/place_pic.jpg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/place_pic.jpg new file mode 100644 index 0000000..d958eb0 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/place_pic.jpg differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/place_pic1.jpg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/place_pic1.jpg new file mode 100644 index 0000000..875cef2 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/place_pic1.jpg differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/practitioner-nim.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/practitioner-nim.png new file mode 100644 index 0000000..56468df Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/practitioner-nim.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/quantization.png b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/quantization.png new file mode 100644 index 0000000..754074f Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/quantization.png differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/imgs/sample_picture.jpg b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/sample_picture.jpg new file mode 100644 index 0000000..bf5ba88 Binary files /dev/null and b/workspace-nim-with-rag-jp/jupyter_notebook/imgs/sample_picture.jpg differ diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/locally_deployed_nim.ipynb b/workspace-nim-with-rag-jp/jupyter_notebook/locally_deployed_nim.ipynb new file mode 100644 index 0000000..d9d2ad6 --- /dev/null +++ b/workspace-nim-with-rag-jp/jupyter_notebook/locally_deployed_nim.ipynb @@ -0,0 +1,1466 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d79eca0c-b6f2-4272-b647-9eb53d899bf2", + "metadata": {}, + "source": [ + "

Home Page

\n", + "\n", + "
\n", + " Previous Notebook\n", + " \n", + " 1\n", + " 2\n", + " 3\n", + " \n", + " \n", + " Next Notebook\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "3135f42d-057d-4fc3-a11c-744eb9ad5b3f", + "metadata": {}, + "source": [ + "# ローカライズされたNVIDIA NIMによるRAGの構築\n", + "---\n", + "\n", + "\n", + "このNotebookでは、ローカライズされたNVIDIA NIM(NVIDIA Inference Microservice)を使用してRAG (検索拡張生成)パイプラインを構築するデモンストレーションを行います。まず初めに、NVIDIA API Keyの設定、NIMイメージの取得とデプロイ、ローカルにデプロイされたNIMを使用するRAGアプリケーションの構築について説明します。" + ] + }, + { + "cell_type": "markdown", + "id": "43c1ee23-a007-4bdc-ac6b-d1c0fb6e24c6", + "metadata": {}, + "source": [ + "### NVIDIA API キーの設定" + ] + }, + { + "cell_type": "markdown", + "id": "d90a4b22-fb12-4f9f-8c53-50a8dfd8a5de", + "metadata": {}, + "source": [ + "前回のNotebookでは、生成したNVIDIA API KEYの設定方法を学びました。このNotebookの要件として、NVIDIA NIM の docker イメージを取得する為に、環境変数 `NVIDIA_API_KEY` としてキーを設定する必要があります。まだキーを取得していない場合は、NVIDIA NIM API [ホームページ](https://build.nvidia.com/explore/discover) にアクセスしてAPIキーを生成してください。以下のセルを実行し、表示されるテキストボックスにNVIDIA API KEYを入力し、キーボードのEnterキーを押してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "969159f2-b726-49eb-b2d2-5ac8d66f85a6", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import getpass\n", + "\n", + "if not os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " nvapi_key = getpass.getpass(\"Enter your NVIDIA API key: \")\n", + " assert nvapi_key.startswith(\"nvapi-\"), f\"{nvapi_key[:5]}... is not a valid key\"\n", + " os.environ[\"NVIDIA_API_KEY\"] = nvapi_key\n", + " os.environ[\"NGC_API_KEY\"] = nvapi_key\n" + ] + }, + { + "cell_type": "markdown", + "id": "b7943f73-a23a-49b2-b00b-67fd6cf62ffe", + "metadata": {}, + "source": [ + "### セルフホスト型NIM" + ] + }, + { + "cell_type": "markdown", + "id": "ed2290fb-fbf4-4414-849d-664025a7e880", + "metadata": {}, + "source": [ + "以下のセルを実行して、dockerデーモンが起動していることを確認してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a7035d2-e3a1-41c9-be4f-64310df647bb", + "metadata": {}, + "outputs": [], + "source": [ + "! docker ps" + ] + }, + { + "cell_type": "markdown", + "id": "4f252ef8-c121-440b-96f4-bed4521bf14a", + "metadata": {}, + "source": [ + "**期待される出力 (実行中のコンテナが存在しない場合):**\n", + "\n", + "```python\n", + "\n", + "CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "6501f84f-5e20-4410-ad7f-56ca90284d21", + "metadata": {}, + "source": [ + "### NVCR (NVIDIA Container Registry)へのログイン\n", + "\n", + "NVIDIA NIM の docker イメージにアクセスするには、`docker login nvcr.io`でログインする必要があります。このプロセスでは、`--username $oauthtoken`としてのデフォルトのユーザ名と、`$NGC_API_KEY`の値を受け付ける `--password-stdin` が必要です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20d58181-7190-4a26-950a-aa20ce60d43b", + "metadata": {}, + "outputs": [], + "source": [ + "! echo -e \"$NGC_API_KEY\" | docker login nvcr.io --username '$oauthtoken' --password-stdin" + ] + }, + { + "cell_type": "markdown", + "id": "254d2657-8abf-4097-978f-e3991bcd6f41", + "metadata": {}, + "source": [ + "**想定される出力**:\n", + "```\n", + "WARNING! Your password will be stored unencrypted in /home/yagupta/.docker/config.json.\n", + "Configure a credential helper to remove this warning. See\n", + "https://docs.docker.com/engine/reference/commandline/login/#credentials-store\n", + "\n", + "Login Succeeded\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "bbce5dd3-b0a5-4d7f-bed9-52a59e7aa504", + "metadata": {}, + "source": [ + "### NVIDIA NIMの選択" + ] + }, + { + "cell_type": "markdown", + "id": "ed076e88-c9e1-46e1-accb-7ebb691586f1", + "metadata": {}, + "source": [ + "[NIMs Catalog](https://build.nvidia.com/explore/reasoning)には、さまざまなドメインにおける複数の最先端モデルが掲載されています。下のスクリーンショットのように、`RUN ANYWHERE`タグの付いたものを探してください。これらのNIMイメージはダウンロード可能で、モデルや必要な最適化ランタイムを含んでおり、すぐに使い始めるのに役立ちます。\n", + "\n", + "\n", + "\n", + "お好みのNIMモデルを選択し、dockerタブをクリックし、以下のスクリーンショットのように赤枠内のイメージ名をコピーします。 \n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "bfb24f5d-95f9-4ee0-9e37-abc9c4cbbee4", + "metadata": {}, + "source": [ + "### ImageのPull\n", + "\n", + "次のステップはDockerイメージをPullすることです。ここでは、`llama3-3.1-swallow-8b-instruct-v0.1`をプルすることでこのステップを実演します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "924eed1e-4420-488e-9fd7-80d4e5b91723", + "metadata": {}, + "outputs": [], + "source": [ + "! docker pull nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest" + ] + }, + { + "cell_type": "markdown", + "id": "13147b53-61da-40b1-86e8-9f8f82d702c7", + "metadata": {}, + "source": [ + "**想定される出力:** (すでにImageを取得している場合)\n", + "```python\n", + "\n", + "latest: Pulling from nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\n", + "Digest: sha256:00dbe414f31d19cb63b09795d40e74bb309456b9cb32f580110a20e4473849d6\n", + "Status: Image is up to date for nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest\n", + "nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "63df9235-a28f-474d-b3c4-a597a8b84283", + "metadata": {}, + "source": [ + "利用可能なイメージをリストアップして、モデルイメージをチェックしてみましょう。*`IMAGE ID`は、以下の期待される出力の下に表示されるものと異なる可能性があることに注意してください*。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08697e63-f700-42fd-a916-af43d6365b80", + "metadata": {}, + "outputs": [], + "source": [ + "! docker image ls" + ] + }, + { + "cell_type": "markdown", + "id": "2f62c8fc-b3bd-49fe-b0f0-903cc2ca8f4c", + "metadata": {}, + "source": [ + "**想定される出力**:\n", + "\n", + "```python\n", + "REPOSITORY TAG IMAGE ID CREATED SIZE\n", + "nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1 latest 3882ef11dbcc 5 months ago 18.9GB\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "id": "81f7ac7f-101d-4a35-bbcc-19f550440b5c", + "metadata": {}, + "source": [ + "#### モデル アーティファクトの為のキャッシュの設定\n", + "\n", + "NVIDIA NIMは、ハードウェア上で最大のパフォーマンスを達成するために最適なプロファイルが選択されるように、多くのファイルをダウンロードします。モデル成果物をキャッシュする場所を `LOCAL_NIM_CACHE` として設定し、その変数をエクスポートします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83b228f8-8733-452a-b096-a205286e68b2", + "metadata": {}, + "outputs": [], + "source": [ + "from os.path import expanduser\n", + "home = expanduser(\"~\")\n", + "os.environ['LOCAL_NIM_CACHE']=f\"{home}/.cache/nim\"\n", + "!echo $LOCAL_NIM_CACHE" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c3efb68-4a4c-4ae5-a716-efad00a2ee5d", + "metadata": {}, + "outputs": [], + "source": [ + "!mkdir -p \"$LOCAL_NIM_CACHE\"" + ] + }, + { + "cell_type": "markdown", + "id": "83f912e2-a304-4cce-b9d0-9dfe5eded959", + "metadata": {}, + "source": [ + "### NIM LLM マイクロサービスの起動\n", + "\n", + "以下のセルを実行し docker run コマンドを実行して NIM LLM マイクロサービスを起動します。\n", + "\n", + "```python\n", + "docker run -it --rm -d --gpus all --name=llm_nim --shm-size=16GB -e NGC_API_KEY -v '$LOCAL_NIM_CACHE':/opt/nim/.cache -u $(id -u) -p 8000:8000 nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\n", + "```\n", + "\n", + "このDockerコマンドは、以下のフラグを使ってNIM LLMマイクロサービスを起動します:\n", + "\n", + "- `-it`: 擬似TTYを割り当て、対話プロセス用にSTDINをオープンしておきます\n", + "- `--rm`: 終了時にコンテナを自動的に削除します\n", + "- `-d`: コンテナをデタッチモード(バックグラウンド)で実行します\n", + "- `--gpus all`: コンテナが利用可能なすべての GPU にアクセスできるようにします\n", + "- `--name=llm_nim`: コンテナに\"llm_nim\"という名前をつけます\n", + "- `--shm-size=16GB`: `/dev/shmの`サイズを 16GB に設定します\n", + "- `-e NGC_API_KEY`: 環境変数 NGC_API_KEY をコンテナに渡します\n", + "- `-v $LOCAL_NIM_CACHE:/opt/nim/.cache`: ローカル NIM キャッシュディレクトリをコンテナの /opt/nim/.cache にマウントします\n", + "- `-u $(id -u)`: 現在のユーザーの UID でコンテナを実行します\n", + "- `-p 8000:8000`: ホストのポート 8000 をコンテナのポート 8000 にマップします\n", + "- `nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1`: 使用するDockerイメージを指定します" + ] + }, + { + "cell_type": "markdown", + "id": "3186633f-f091-49eb-96fb-9dc215b16c30", + "metadata": {}, + "source": [ + "システムは複数のプロセスを実行することができるので、実行中のアプリケーションでポートを占有しないようにしなければなりません。以下のコードでは、一意の空きポートを見つけて割り当てています:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5e9f5d7-4ed6-43a1-a264-3f6126990164", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import socket\n", + "\n", + "def find_available_port(start=11000, end=11999):\n", + " while True:\n", + " # Randomly select a port between start and end range\n", + " port = random.randint(start, end)\n", + " \n", + " # Try to create a socket and bind to the port\n", + " with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:\n", + " try:\n", + " sock.bind((\"localhost\", port))\n", + " # If binding is successful, the port is free\n", + " return port\n", + " except OSError:\n", + " # If binding fails, the port is in use, continue to the next iteration\n", + " continue\n", + "\n", + "# Find and print an available port\n", + "os.environ['CONTAINER_PORT'] = str(find_available_port())\n", + "print(f\"Your have been alloted the available port: {os.environ['CONTAINER_PORT']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "351e316d-021d-49dd-bf05-3a5371f7bd80", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it -d --rm \\\n", + "--gpus '\"device=0\"' \\\n", + "--name=llm_nim \\\n", + "--shm-size=16GB \\\n", + "-e NGC_API_KEY=$NGC_API_KEY \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-u $(id -u) \\\n", + "-p $CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest\n", + "\n", + "# In order to ensure, the local NIM container is completely loaded and doesn't remain in pending stage, we instantiate a wait interval\n", + "! sleep 60" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d54dcdd6-2816-4b37-89c5-33cb15c527d5", + "metadata": {}, + "outputs": [], + "source": [ + "! docker logs --tail 100 llm_nim" + ] + }, + { + "cell_type": "markdown", + "id": "afed9719-42bf-48fb-ae7a-e2cc7c05ba69", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "```\n", + "0.0.0.0:8000/v1/metrics\n", + " 0.0.0.0:8000/v1/health/ready\n", + " 0.0.0.0:8000/v1/health/live\n", + " 0.0.0.0:8000/v1/models\n", + " 0.0.0.0:8000/v1/license\n", + " 0.0.0.0:8000/v1/metadata\n", + " 0.0.0.0:8000/v1/manifest\n", + " 0.0.0.0:8000/v1/version\n", + " 0.0.0.0:8000/v1/chat/completions\n", + " 0.0.0.0:8000/v1/completions\n", + " 0.0.0.0:8000/experimental/ls/inference/chat_completion\n", + " 0.0.0.0:8000/experimental/ls/inference/completion\n", + "INFO 2025-05-26 07:20:10.123 api_server.py:663] An example cURL request:\n", + "curl -X 'POST' \\\n", + " 'http://0.0.0.0:8000/v1/chat/completions' \\\n", + " -H 'accept: application/json' \\\n", + " -H 'Content-Type: application/json' \\\n", + " -d '{\n", + " \"model\": \"tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\",\n", + " \"messages\": [\n", + " {\n", + " \"role\":\"user\",\n", + " \"content\":\"Hello! How are you?\"\n", + " },\n", + " {\n", + "...\n", + "INFO 2025-05-26 07:20:10.151 server.py:82] Started server process [66]\n", + "INFO 2025-05-26 07:20:10.152 on.py:48] Waiting for application startup.\n", + "INFO 2025-05-26 07:20:10.154 on.py:62] Application startup complete.\n", + "INFO 2025-05-26 07:20:10.155 server.py:214] Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c9332387-d80b-4fb3-8b97-98194801964a", + "metadata": {}, + "source": [ + "NIM LLM マイクロサービスの起動後、埋め込みモデルのNIMサーバも起動します。\n", + "以下のセルを実行して、埋め込みモデルNIM用のポートを取得とキャッシュディレクトリの設定を行います。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0cf12348-0b90-4915-83ba-e5e1da1c6476", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['EMB_CONTAINER_PORT'] = str(find_available_port())\n", + "print(f\"Your have been alloted the available port: {os.environ['EMB_CONTAINER_PORT']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "4f20aade-755e-45eb-9c8a-cd1bf23bfc86", + "metadata": {}, + "source": [ + "以下のセルを実行して埋め込みモデルのNIMを起動します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0ebc5f8-4691-4841-8e48-23eb270ce292", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it -d --rm \\\n", + "--name=embedding_nim \\\n", + "--gpus '\"device=1\"' \\\n", + "--shm-size=16GB \\\n", + "-e NGC_API_KEY=$NGC_API_KEY \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-u $(id -u) \\\n", + "-p $EMB_CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/nvidia/llama-3.2-nv-embedqa-1b-v2:latest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70c6e5e3-30f9-428a-908b-7d40e7344bcb", + "metadata": {}, + "outputs": [], + "source": [ + "! docker logs --tail 45 embedding_nim" + ] + }, + { + "cell_type": "markdown", + "id": "a124d5a6-fcb8-4c6c-8446-35492d45bb0f", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "```\n", + "+---------------------------------------------+---------+--------+\n", + "| Model | Version | Status |\n", + "+---------------------------------------------+---------+--------+\n", + "| nvidia_llama_3_2_nv_embedqa_1b_v2 | 1 | READY |\n", + "| nvidia_llama_3_2_nv_embedqa_1b_v2_model | 1 | READY |\n", + "| nvidia_llama_3_2_nv_embedqa_1b_v2_tokenizer | 1 | READY |\n", + "+---------------------------------------------+---------+--------+\n", + "\n", + "I0705 14:55:03.956665 932 metrics.cc:890] \"Collecting metrics for GPU 0: NVIDIA A100-SXM4-80GB\"\n", + "I0705 14:55:03.963810 932 metrics.cc:783] \"Collecting CPU metrics\"\n", + "I0705 14:55:03.963979 932 tritonserver.cc:2598] \n", + "+----------------------------------+------------------------------------------+\n", + "| Option | Value |\n", + "+----------------------------------+------------------------------------------+\n", + "| server_id | triton |\n", + "| server_version | 2.54.0 |\n", + "| server_extensions | classification sequence model_repository |\n", + "| | model_repository(unload_dependents) sch |\n", + "| | edule_policy model_configuration system_ |\n", + "| | shared_memory cuda_shared_memory binary_ |\n", + "| | tensor_data parameters statistics trace |\n", + "| | logging |\n", + "| model_repository_path[0] | /opt/nim/tmp/run/triton-model-repository |\n", + "| model_control_mode | MODE_NONE |\n", + "| strict_model_config | 0 |\n", + "| model_config_name | |\n", + "| rate_limit | OFF |\n", + "| pinned_memory_pool_byte_size | 268435456 |\n", + "| cuda_memory_pool_byte_size{0} | 67108864 |\n", + "| min_supported_compute_capability | 6.0 |\n", + "| strict_readiness | 1 |\n", + "| exit_timeout | 30 |\n", + "| cache_enabled | 0 |\n", + "+----------------------------------+------------------------------------------+\n", + "\n", + "I0705 14:55:03.968801 932 grpc_server.cc:2558] \"Started GRPCInferenceService at 0.0.0.0:8001\"\n", + "I0705 14:55:03.969015 932 http_server.cc:4725] \"Started HTTPService at 0.0.0.0:8080\"\n", + "I0705 14:55:04.010495 932 http_server.cc:358] \"Started Metrics Service at 0.0.0.0:8002\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "ba7a091e-a04e-414f-85b7-afe360291fe6", + "metadata": {}, + "source": [ + "### クイックテストの開始\n", + "NIMが稼働していることを2つの方法で素早くテストできます:\n", + "- LangChain NVIDIA Endpoints\n", + "- 単純なOpenAI完了リクエスト\n", + "\n", + "**パラメータの説明:**\n", + "- **base_url**: NVIDIA NIM の docker イメージがデプロイされているURL\n", + "- **model**: デプロイされた NVIDIA NIM モデルの名前\n", + "- **temperature**: サンプリングのランダム性を調整する。温度を下げると、高い確率で単語が選択される可能性が高くなります。\n", + "- **top_p**: モデルがどの程度決定論的かを制御します。正確で事実に基づいた回答を求める場合は、この値を低く保ちます。より多様な回答を求める場合は、値を大きくします\n", + "- **max_tokens**: 生成される出力トークンの最大数\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3acc2d7c-c7a7-4d75-8342-29c36ce9745d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "\n", + "llm = ChatNVIDIA(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']), \\\n", + " model=\"tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\", \\\n", + " temperature=0.1, max_tokens=1000, top_p=1.0)\n", + "\n", + "result = llm.invoke(\"フランスの首都はどこですか?\")\n", + "print(result.content)" + ] + }, + { + "cell_type": "markdown", + "id": "c49788a1-8a54-413c-81e9-232ac1227559", + "metadata": {}, + "source": [ + "エラーが出力された場合は、しばらく待ってから上記のセルを再実行してください。エラーは、NIMコンテナが完全に立ち上がっていないことが原因かもしれません。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78a649ef-a4a2-4296-8487-c7d9b3ca22a2", + "metadata": {}, + "outputs": [], + "source": [ + "!curl -X 'POST' \\\n", + " \"http://0.0.0.0:${CONTAINER_PORT}/v1/completions\" \\\n", + " -H \"accept: application/json\" \\\n", + " -H \"Content-Type: application/json\" \\\n", + " -d '{\"model\": \"tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\", \"prompt\": \"フランスの首都はどこですか?\", \"temperature\": 0.1, \"top_p\": 1.0, \"max_tokens\": 16}'" + ] + }, + { + "cell_type": "markdown", + "id": "757e3104-6488-4bd5-90c6-103479acb299", + "metadata": {}, + "source": [ + "次に埋め込みモデルのNIMについても、以下のセルを実行してテストしましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8656fe39-c6a7-4063-9f6e-f140c68ab952", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "!curl -X \"POST\" \\\n", + " \"http://localhost:${EMB_CONTAINER_PORT}/v1/embeddings\" \\\n", + " -H 'accept: application/json' \\\n", + " -H 'Content-Type: application/json' \\\n", + " -d '{\"input\": [\"NVIDIAの新しく出たGPUには何がありますか?名前を教えてください。\"],\"model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\"input_type\": \"query\"}'" + ] + }, + { + "cell_type": "markdown", + "id": "156c5b17-5d00-43fd-bbc4-c8d6c7893fcd", + "metadata": {}, + "source": [ + "### RAG アプリケーション\n", + "\n", + "このセクションでは、ローカルに配置されたNVIDIA NIMをベースとしたRAGアプリケーションを構築します。このデモでは、1つのLLM `llama-3.1-swallow--8b-instruct` と1つのembedding model `llama-3.2-nv-embedqa-1b-v2` を使って会話型検索チェーンを作ります。\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "661dd09c-bdef-4907-8b78-ae22095814b9", + "metadata": {}, + "source": [ + "#### ライブラリのインポート" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "921e0b4e-5477-4875-8687-8ea4e88bd0f2", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import ConversationalRetrievalChain, LLMChain\n", + "from langchain.chains.conversational_retrieval.prompts import CONDENSE_QUESTION_PROMPT, QA_PROMPT\n", + "from langchain.chains.question_answering import load_qa_chain\n", + "from langchain.memory import ConversationBufferMemory\n", + "from langchain.vectorstores import FAISS\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings" + ] + }, + { + "cell_type": "markdown", + "id": "b0d3c408-5735-4446-83ba-ee2c154d3fab", + "metadata": {}, + "source": [ + "#### ウェブリンク・データソースの作成\n", + "\n", + "お好みのウェブリンクを差し替えたり、追加したりすることができます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e827c50-9f64-4b29-9995-405ebd0b7f5b", + "metadata": {}, + "outputs": [], + "source": [ + "urls = [\n", + "\"https://www.fsa.go.jp/news/r5/20230829/230829_main.pdf\",\n", + "\"https://www.tioj.or.jp/activity/pdf/190619_06.pdf\",\n", + "\"https://www.pmda.go.jp/files/000251332.pdf\",\n", + "\"https://www.jili.or.jp/files/research/zenkokujittai/pdf/r3/i-xvii.pdf\",\n", + "\"https://www.zenginkyo.or.jp/fileadmin/res/news/news350331_1.pdf\"\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "337a41cc-8765-40e3-a1ea-0b536ac25bcd", + "metadata": {}, + "source": [ + "## 実装" + ] + }, + { + "cell_type": "markdown", + "id": "6d9634d7-ecfb-43b1-bb15-6a5b42261cc4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### PDF ファイルを読み込む関数を作る\n", + "\n", + "以下は、PDFファイルを読み込むためのヘルパー関数です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8b7796f-ad07-45eb-8d08-5c977c897da3", + "metadata": {}, + "outputs": [], + "source": [ + "from pypdf import PdfReader\n", + "from io import BytesIO\n", + "import requests\n", + "import re\n", + "\n", + "def clean_japanese_pdf_text(text: str) -> str:\n", + " # 日本語文中の改行を削除\n", + " text = re.sub(r'(?<=[ぁ-んァ-ヶ一-龥])\\s*\\n\\s*(?=[ぁ-んァ-ヶ一-龥])', '', text)\n", + " # 連続するスペースや全角スペースを1つにまとめる\n", + " text = re.sub(r'[  ]{2,}', ' ', text)\n", + " # 各行の先頭空白を削除\n", + " text = re.sub(r'^\\s+', '', text, flags=re.MULTILINE)\n", + " # 「..... 4」形式の目次行のページ番号を除去\n", + " text = re.sub(r'\\.{3,}\\s*\\d{1,3}', '', text)\n", + " text = text.replace(\"\\n\", \"\")\n", + " return text\n", + "\n", + "def pdf_document_loader(url: str) -> str:\n", + " \"\"\"\n", + " Loads and extracts cleaned text from a PDF at the given URL using `pypdf`.\n", + "\n", + " Args:\n", + " url: The URL of the PDF.\n", + "\n", + " Returns:\n", + " Cleaned text content of the PDF.\n", + " \"\"\"\n", + " try:\n", + " response = requests.get(url)\n", + " response.raise_for_status()\n", + " reader = PdfReader(BytesIO(response.content))\n", + " text = \"\"\n", + " for page in reader.pages:\n", + " extracted = page.extract_text()\n", + " if extracted:\n", + " text += extracted\n", + " return clean_japanese_pdf_text(text.strip())\n", + " except Exception as e:\n", + " print(f\"Failed to load {url} due to: {e}\")\n", + " return \"\"\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "87ae33e1-5588-4ed3-9a5b-72e26770b134", + "metadata": {}, + "source": [ + "#### 埋め込みとDocument Text Splitterの作成\n", + "\n", + "埋め込みを保存するパスを初期化し、`pdf_document_loader`関数を実行し、ドキュメントをテキストの塊に分割する関数を作ってみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91d3616d-fb9e-4e9c-b645-054348b10dca", + "metadata": {}, + "outputs": [], + "source": [ + "from tqdm import tqdm\n", + "\n", + "def create_embeddings(embeddings_model,embedding_path: str = \"./embed\"):\n", + "\n", + " embedding_path = \"./embed\"\n", + " print(f\"Storing embeddings to {embedding_path}\")\n", + "\n", + " documents = []\n", + " for url in tqdm(urls):\n", + " print(url)\n", + " document = pdf_document_loader(url)\n", + " #document = html_document_loader(url)\n", + " documents.append(document)\n", + "\n", + "\n", + " text_splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=500,\n", + " chunk_overlap=0,\n", + " length_function=len,\n", + " )\n", + " print(\"Total documents:\",len(documents))\n", + " texts = text_splitter.create_documents(documents)\n", + " print(\"Total texts:\",len(texts))\n", + " index_docs(embeddings_model,url, text_splitter, texts, embedding_path,)\n", + " print(\"Generated embedding successfully\")" + ] + }, + { + "cell_type": "markdown", + "id": "a8b2b731-2763-4ca9-8d51-4b99157526d2", + "metadata": {}, + "source": [ + "#### LangChainからNVIDIA AI Endpointsを使って埋め込みを生成する\n", + "\n", + "このセクションでは、LangChain用のNVIDIA AI Endpointsを使って埋め込みを生成し、将来の再利用のためにエンベッディングを`/embed`ディレクトリのオフラインベクタストアに保存する方法をデモします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d15ffae-6a9b-4ca1-9293-bb41c6b575e1", + "metadata": {}, + "outputs": [], + "source": [ + "#embeddings_model = NVIDIAEmbeddings(model=\"NV-Embed-QA\") # or use nvidia/nv-embedqa-e5-v5\n", + "embeddings_model = NVIDIAEmbeddings(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['EMB_CONTAINER_PORT']), \\\n", + " model=\"nvidia/llama-3.2-nv-embedqa-1b-v2\",truncate=\"NONE\")" + ] + }, + { + "cell_type": "markdown", + "id": "11067f62-6896-4f37-ad9f-b25fbf26bf75", + "metadata": {}, + "source": [ + "以下では、ドキュメントページのコンテンツをループしてテキストとメタデータを拡張し、[FAISS](https://faiss.ai/index.html)を適用する `index_docs` 関数を作成します。埋め込みはローカルに保存されます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49ff7545-bb65-4864-9a77-f3536ce36178", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Union\n", + "\n", + "\n", + "def index_docs(embeddings_model, url: Union[str, bytes], splitter, documents: List[str], dest_embed_dir: str) -> None:\n", + " \"\"\"\n", + " Split the documents into chunks and create embeddings for them.\n", + " \n", + " Args:\n", + " embeddings_model: Model used for creating embeddings.\n", + " url: Source url for the documents.\n", + " splitter: Splitter used to split the documents.\n", + " documents: List of documents whose embeddings need to be created.\n", + " dest_embed_dir: Destination directory for embeddings.\n", + " \"\"\"\n", + " texts = []\n", + " metadatas = []\n", + "\n", + " for document in documents:\n", + " chunk_texts = splitter.split_text(document.page_content)\n", + " texts.extend(chunk_texts)\n", + " metadatas.extend([document.metadata] * len(chunk_texts))\n", + "\n", + " if os.path.exists(dest_embed_dir):\n", + " docsearch = FAISS.load_local(\n", + " folder_path=dest_embed_dir, \n", + " embeddings=embeddings_model, \n", + " allow_dangerous_deserialization=True\n", + " )\n", + " docsearch.add_texts(texts, metadatas=metadatas)\n", + " else:\n", + " docsearch = FAISS.from_texts(texts, embedding=embeddings_model, metadatas=metadatas)\n", + "\n", + " docsearch.save_local(folder_path=dest_embed_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "431a4d44-800a-4083-a63e-af74ce2b04bf", + "metadata": {}, + "source": [ + "#### ベクターストアからの埋め込みのロードとNVIDIA Endpointを使用したRAGの構築\n", + "\n", + "次に、関数 `create_embeddings` を呼び出し、FAISSを使って[vector store](https://developer.nvidia.com/blog/accelerating-vector-search-fine-tuning-gpu-index-algorithms/)から文書を読み込む。ベクトルストアはembeddingsと呼ばれる高次元空間に関連情報を格納しています。\n", + "\n", + "以下の2つのセルを実行してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0bc1104-86af-4896-894a-7de2f022fd2a", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "create_embeddings(embeddings_model=embeddings_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7920b73d-e78e-4a85-beef-5b2d22648f96", + "metadata": {}, + "outputs": [], + "source": [ + "# load Embed documents\n", + "embedding_path = \"./embed/\"\n", + "docsearch = FAISS.load_local(folder_path=embedding_path, embeddings=embeddings_model, allow_dangerous_deserialization=True)" + ] + }, + { + "cell_type": "markdown", + "id": "96bbec25-fa14-4176-88c7-b97c6565bfb9", + "metadata": {}, + "source": [ + "### llama-3.1-swallow-8b-instructで会話型検索チェーンを作る\n", + "\n", + "デプロイされたNIMは`http://0.0.0.0:{CONTAINER_PORT}`で稼働しているので、NIMの基本モデル`llama-3.1-swallow-8b-instruct`に基づいて[会話型検索チェーン](https://python.langchain.com/v0.1/docs/modules/chains/#conversationalretrievalchain-with-streaming-to-stdout)を作成する。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84565130-b12d-443d-8e5e-81928278dab5", + "metadata": {}, + "outputs": [], + "source": [ + "llm = ChatNVIDIA(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']),\n", + " model=\"tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\", temperature=0.0, max_tokens=1000, top_p=1.0)\n", + "\n", + "memory = ConversationBufferMemory(memory_key=\"chat_history\", return_messages=True)\n", + "\n", + "qa_prompt=QA_PROMPT\n", + "\n", + "qa = ConversationalRetrievalChain.from_llm(\n", + " llm=llm,\n", + " retriever=docsearch.as_retriever(),\n", + " chain_type=\"stuff\",\n", + " memory=memory,\n", + " combine_docs_chain_kwargs={'prompt': qa_prompt},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "62929ee2-61ee-4943-8a8f-65dc93de7a6f", + "metadata": {}, + "source": [ + "### クエリによるテスト" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24fa8222-85f3-4c0b-b17e-665a9ce5610b", + "metadata": {}, + "outputs": [], + "source": [ + "query = \"世帯主が不意の事故により入院が必要になる場合の必要資金について、60~64歳及び65歳以上の夫婦が公的年金以 \\\n", + " 外に必要とする月間生活資金と比較してください。\"\n", + "result = qa({\"question\": query})\n", + "print(result.get(\"answer\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d145d79b-a3c3-4132-b4e2-09c3c2cfbb40", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "query = \"製造たばこの個包装及び中間包装に求められる識別マークの表示方法の条件について説明してください。\"\n", + "result = qa({\"question\": query})\n", + "print(result.get(\"answer\"))" + ] + }, + { + "cell_type": "markdown", + "id": "e596623c-4540-4930-bd90-cd707b378ebd", + "metadata": {}, + "source": [ + "先に進む前に、dockerコンテナを停止してGPUのVRAMを解放しましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fec87de1-0f2f-406d-992c-ac0bfe4b50f4", + "metadata": {}, + "outputs": [], + "source": [ + "! docker container stop llm_nim" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b216603-ce8d-4acf-9916-8676c420fadf", + "metadata": {}, + "outputs": [], + "source": [ + "! docker container stop embedding_nim" + ] + }, + { + "cell_type": "markdown", + "id": "5bf6c74a-8437-4c27-a72a-4be6d61c8f3f", + "metadata": {}, + "source": [ + "次のノートブックでは、LoRAのようなPEFTの機能をNIMに追加する方法を説明します。" + ] + }, + { + "cell_type": "markdown", + "id": "346655f8-3e08-4cb0-9c6a-84b2fd12f7bc", + "metadata": {}, + "source": [ + "このノートブックではNVIDIA NIMとLangChainを用いてRAGアプリケーションの作成を行いました。\n", + "以下のオプショナル演習は、NVIDIA NIMの機能に関する追加演習です。\n", + "\n", + "### オプション演習1 NVIDIA NIMの環境変数について学ぶ\n", + "NVIDIA NIMはあらかじめビルド済のプロファイル、 NIMコンテナをpullしたサーバ上でのjust-in-timeコンパイルのオプションなど、各NIM毎にいくつかのプロファイルが用意されています。\n", + "vLLMエンジンで明示的に起動したい。Tensor Parallel数を明示的に指定したい等、推論サーバの設定をカスタムしたい場合の為に、以下でNVIDIA NIMのプロファイルについて学びます。\n", + "\n", + "以下のセルを実行しllama-3.1-swallow-8b-instruct-v0.1のプロファイル一覧を取得します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "125e2df9-3c2e-4564-9f5b-777332df926e", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it --rm \\\n", + "--gpus '\"device=0\"' \\\n", + "--name=llm_nim \\\n", + "--shm-size=16GB \\\n", + "-e NGC_API_KEY=$NGC_API_KEY \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-u $(id -u) \\\n", + "-p $CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest list-model-profiles" + ] + }, + { + "cell_type": "markdown", + "id": "66603530-502a-4478-b619-619d0eb83bff", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "```\n", + "SYSTEM INFO\n", + "- Free GPUs:\n", + " - [20b2:10de] (0) NVIDIA A100-SXM4-80GB (A100 80GB) [current utilization: 0%]\n", + "Profile 375dc0ff86133c2a423fbe9ef46d8fdf12d6403b3caa3b8e70d7851a89fc90dd is not fully defined with checksums\n", + "Profile 4f904d571fe60ff24695b5ee2aa42da58cb460787a968f1e8a09f5a7e862728d is not fully defined with checksums\n", + "Profile 54946b08b79ecf9e7f2d5c000234bf2cce19c8fee21b243c1a084b03897e8c95 is not fully defined with checksums\n", + "Profile 7fa4a5a68c0338f16aef61de94977acfdacb7cabd848d38c49c48d2f639f04b3 is not fully defined with checksums\n", + "Profile ac34857f8dcbd174ad524974248f2faf271bd2a0355643b2cf1490d0fe7787c2 is not fully defined with checksums\n", + "Profile c84b2a068e56a551906563035ed77f88c88cbe1a63c6768fb2d4a9e0af1e67ba is not fully defined with checksums\n", + "MODEL PROFILES\n", + "- Compatible with system and runnable:\n", + " - 4f904d571fe60ff24695b5ee2aa42da58cb460787a968f1e8a09f5a7e862728d (vllm-bf16-tp1-pp1)\n", + " - With LoRA support:\n", + "- Compilable to TRT-LLM using just-in-time compilation of HF models to TRTLLM engines:\n", + " - ac34857f8dcbd174ad524974248f2faf271bd2a0355643b2cf1490d0fe7787c2 (tensorrt_llm-trtllm_buildable-bf16-tp1-pp1)\n", + " - With LoRA support:\n", + "- Incompatible with system:\n", + " - c84b2a068e56a551906563035ed77f88c88cbe1a63c6768fb2d4a9e0af1e67ba (vllm-bf16-tp4-pp1)\n", + " - 7fa4a5a68c0338f16aef61de94977acfdacb7cabd848d38c49c48d2f639f04b3 (vllm-bf16-tp2-pp1)\n", + " - 54946b08b79ecf9e7f2d5c000234bf2cce19c8fee21b243c1a084b03897e8c95 (tensorrt_llm-trtllm_buildable-bf16-tp4-pp1)\n", + " - 375dc0ff86133c2a423fbe9ef46d8fdf12d6403b3caa3b8e70d7851a89fc90dd (tensorrt_llm-trtllm_buildable-bf16-tp2-pp1)\n", + "```\n", + "\n", + "\n", + "出力の中に`Compatible with system and runnable`、`Compilable to TRT-LLM using just-in-time compilation`、`Incompatible with system`がある事を確認して下さい。\n", + "`Compatible with system and runnable`で表示されているのが、NVIDIA NIMのあらかじめビルド済のプロファイルの中で実行環境上で実行可能なプロファイル一覧、`Compilable to TRT-LLM using just-in-time compilation`が実行環境上でjust-in-timeコンパイルを行い推論エンジンを作成するプロファイルになります。\n", + "`NIM_MODEL_PROFILE`環境変数を用いる事で、プロファイルを明示的に指定したNVIDIA NIMの起動が可能です。\n", + "以下は`vllm-bf16-tp1-pp1`というビルド済のプロファイルを明示的に指定した起動の例です。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a10d854e-0799-4341-8149-5708b438764f", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it --rm \\\n", + "--gpus '\"device=0\"' \\\n", + "--name=llm_nim \\\n", + "--shm-size=16GB \\\n", + "-e NGC_API_KEY=$NGC_API_KEY \\\n", + "-e NIM_MODEL_PROFILE=vllm-bf16-tp1-pp1 \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-u $(id -u) \\\n", + "-p $CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest" + ] + }, + { + "cell_type": "markdown", + "id": "5eeab18e-b308-4eb9-94d6-366b4663bfb1", + "metadata": {}, + "source": [ + "NVIDIA NIMの環境変数に関する情報は[こちら](https://docs.nvidia.com/nim/large-language-models/latest/configuration.html#environment-variables)にあります。今回紹介した`NIM_MODEL_PROFILE`以外にも細かな制御を行う為の様々なオプションが用意でされています。様々なフラグを有効化した起動をぜひお試しください" + ] + }, + { + "cell_type": "markdown", + "id": "f24bf9e4-0d56-4be8-8708-3731ea7408ff", + "metadata": {}, + "source": [ + "### オプション演習2 NVIDIA NIMのファインチューニングモデルサポート機能を使用して最新のLlama3.1-Swallowモデルのデプロイを行う\n", + "バージョン1.3以降のNVIDIA NIMでは[ファインチューニングモデルサポート機能](https://docs.nvidia.com/nim/large-language-models/latest/ft-support.html)が追加されました。NVIDIA NIMのファインチューニングサポート機能を使用すると、モデルの重みをファインチューニング後の重みに変更して推論サーバーを起動する事が可能です。\n", + "ファインチューニングモデルサポート機能については[エヌビディア技術ブログ](https://developer.nvidia.com/ja-jp/blog/deploying-fine-tuned-ai-models-with-nvidia-nim/)にも記事が公開されています。併せてご確認下さい。\n", + "例えば、[Llama-3.1-Swallow](https://swallow-llm.github.io/llama3.1-swallow.ja.html)は[Meta社のLlama-3.1](https://huggingface.co/collections/meta-llama/llama-31-669fc079a0c406a149a5738f)に対してファインチューニングを実施し作成されたモデルです。つまり、Meta Llama-3.1-8BのNIMに対してファインチューニングモデルサポート機能を使用する事で、Llama-3.1-Swallow-8Bの重みに変更して推論サーバーを起動する事が可能です。\n", + "\n", + "[NVIDIA APIカタログ](https://build.nvidia.com/)に公開されているNIM化されたモデルはHuggingFaceに公開されているモデル情報より古い可能性があります。例えば[NIM化済のLlama-3.1-Swallow-8B-Instruct](https://catalog.ngc.nvidia.com/orgs/nim/teams/tokyotech-llm/containers/llama-3.1-swallow-8b-instruct-v0.1)は、[こちらの重み](https://huggingface.co/tokyotech-llm/Llama-3.1-Swallow-8B-Instruct-v0.1)を使ってNIM化されています。\n", + "しかしLlama-3.1-Swallow-8B-Instructは定期的に更新されており、重みが更新された[Llama-3.1-swallow-8B-Instruct-v0.5](https://huggingface.co/tokyotech-llm/Llama-3.1-Swallow-8B-Instruct-v0.5)が最新です。\n", + "\n", + "以下を実行し、NIMのファインチューニングサポート機能を使用したLlama-3.1-8B-Instruct-v0.5のNIMの起動を試してみましょう。\n", + "\n", + "まずはMeta Llama-3.1-8B-InstructのNIMコンテナをpullしましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78ae38de-9f4e-40ec-bc0b-002643b7617c", + "metadata": {}, + "outputs": [], + "source": [ + "!docker pull nvcr.io/nim/meta/llama-3.1-8b-instruct:1.8.4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adf3cbb7-52ea-4b23-a7ce-03f2f517034c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from huggingface_hub import snapshot_download\n", + "\n", + "# In this workshop we do not download checkpoint as we have already have the one on shared storage.\n", + "# If you want to manually download this, please uncomment below.\n", + "\n", + "\n", + "#MODEL_DIR = \"./models\"\n", + "#os.makedirs(MODEL_DIR, exist_ok=True)\n", + "# \n", + "#snapshot_download(\n", + "# repo_id=\"tokyotech-llm/Llama-3.1-Swallow-8B-Instruct-v0.5\",\n", + "# local_dir=f\"{MODEL_DIR}/Llama-3.1-Swallow-8B-Instruct-v0.5\",\n", + "# local_dir_use_symlinks=False\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fd3dfb2-3ecb-4f87-b179-0bde47c66263", + "metadata": {}, + "outputs": [], + "source": [ + "current_path = os.getcwd()\n", + "os.environ['NIM_SERVED_MODEL_NAME']=\"Llama-3.1-Swallow-8B-v0.5\"\n", + "# os.environ['TRTLLM_MODEL_PATH']=current_path+\"/models/Llama-3.1-Swallow-8B-Instruct-v0.5\" # for manually download\n", + "os.environ['TRTLLM_MODEL_PATH']=\"/mnt/lustre/docker-containers/E2E-LLM-NIMS/models/Llama-3.1-Swallow-8B-Instruct-v0.5\" # on this workshop\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51210b17-2136-41c5-a9b4-300b2bb4af87", + "metadata": {}, + "outputs": [], + "source": [ + "!docker run -it -d --rm \\\n", + "--name=swallow \\\n", + "--gpus '\"device=0\"' \\\n", + "--shm-size=16GB \\\n", + "-e NIM_MODEL_NAME=/model \\\n", + "-e NIM_SERVED_MODEL_NAME=${NIM_SERVED_MODEL_NAME} \\\n", + "-e NIM_MODEL_PROFILE=tensorrt_llm \\\n", + "-e NIM_ENABLE_KV_CACHE_REUSE=1 \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-v $TRTLLM_MODEL_PATH:/model \\\n", + "-u $(id -u) \\\n", + "-p $CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/meta/llama-3.1-8b-instruct:1.8.4" + ] + }, + { + "cell_type": "markdown", + "id": "af364911-eb77-4794-8eb4-5741629be0ab", + "metadata": {}, + "source": [ + "NIMをデプロイしたサーバ上でjust-in-timeコンパイルが実行され、TensorRT-LLMエンジンがビルドされます。ビルド後に推論サーバーが起動します。\n", + "ビルドには暫く時間がかかります。\n", + "数分したら、以下のセルを実行しサーバが起動済か確認してみましょう。以下のような出力が出れば推論サーバーの起動は完了です。\n", + "\n", + "**想定される出力:**\n", + "```\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:214] Available routes are:\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /openapi.json, Methods: HEAD, GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /docs, Methods: HEAD, GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /docs/oauth2-redirect, Methods: HEAD, GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/chat/completions, Methods: POST\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/completions, Methods: POST\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /experimental/ls/inference/chat_completion, Methods: POST\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /experimental/ls/inference/completion, Methods: POST\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/health/ready, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/health/live, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/models, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/metadata, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/version, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/health/live, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/health/ready, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/metrics, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/license, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/metadata, Methods: GET\n", + "INFO 2025-07-02 12:36:42.773 api_server.py:222] Route: /v1/manifest, Methods: GET\n", + "INFO 2025-07-02 12:36:42.825 server.py:82] Started server process [61]\n", + "INFO 2025-07-02 12:36:42.825 on.py:48] Waiting for application startup.\n", + "INFO 2025-07-02 12:36:42.826 on.py:62] Application startup complete.\n", + "INFO 2025-07-02 12:36:42.827 server.py:214] Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c10576d-6778-40ac-87b8-00a038b25ed1", + "metadata": {}, + "outputs": [], + "source": [ + "! docker logs --tail 100 swallow" + ] + }, + { + "cell_type": "markdown", + "id": "d12e11fd-0a45-4420-ade6-807594c43728", + "metadata": {}, + "source": [ + "以下のセルを実行してクエリーを推論サーバに投げ、応答が返ってくるかテストしてみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fc919fc-a65d-41fd-93d4-368dd375e11f", + "metadata": {}, + "outputs": [], + "source": [ + "llm_swallow = ChatNVIDIA(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']), model=\"Llama-3.1-Swallow-8B-v0.5\", temperature=0.6, max_tokens=512, top_p=0.9)\n", + "\n", + "result = llm_swallow.invoke(\"東京の紅葉した公園で、東京タワーと高層ビルを背景に、空を舞うツバメと草地に佇むラマが出会う温かな物語を書いてください。\")\n", + "print(result.content)" + ] + }, + { + "cell_type": "markdown", + "id": "c02a6d0f-a2a3-4931-89fb-832c53624226", + "metadata": {}, + "source": [ + "以下のセルを実行しNIMコンテナを停止します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6bbf7232-bf92-4665-a416-b39f2456bdfa", + "metadata": {}, + "outputs": [], + "source": [ + "! docker container stop swallow" + ] + }, + { + "cell_type": "markdown", + "id": "5f0e66f1-dbaf-4078-acfc-ab79da3467e6", + "metadata": {}, + "source": [ + "### オプション演習3 Model-less LLM NIMの機能を試す\n", + "最近リリースされたバージョン1.11以降のNVIDIA NIMコンテナは、機能アップデートにより更に柔軟になりました。`TensorRT-LLM`および`vLLM`のサポートに加えて`SGLang`のサポートも追加されました。また既存のファインチューニングモデルサポート機能では、同一アーキテクチャのNIM化されたモデルコンテナを用意する必要がありましたが、バージョン1.11で追加されたModel-less LLM NIMの機能により、その制約がなくなりました。\n", + "つまり、[NVIDIA APIカタログ](https://build.nvidia.com/)に公開されていないモデルであっても、HuggingFace等にモデル情報が公開されていれば、just-in-timeコンパイルで起動時に推論エンジンをビルドし、NIM化できるようになりました。\n", + "\n", + "例えば[phi-4](https://huggingface.co/microsoft/phi-4)のNIM化モデルはまだ[NVIDIA APIカタログ](https://build.nvidia.com/)に公開されていません。Model-less LLM NIMの機能を使用して、phi-4の推論サーバーを起動してみましょう。\n", + "\n", + "以下のセルを実行し、Model-less LLM NIM機能の追加された新しいNIVIDIA NIMコンテナをpullします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e13d2c9-603d-4feb-a12f-cccbe08fff5e", + "metadata": {}, + "outputs": [], + "source": [ + "!docker pull nvcr.io/nim/nvidia/llm-nim:latest" + ] + }, + { + "cell_type": "markdown", + "id": "1c7d404f-ecea-44e3-8c27-59ed8859f037", + "metadata": {}, + "source": [ + "次にphi-4をHuggingFaceからダウンロードします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "939a2da2-0d7a-424c-a6f8-2054fd44606b", + "metadata": {}, + "outputs": [], + "source": [ + "# In this workshop we do not download checkpoint as we have already have the one on shared storage.\n", + "# If you want to manually download this, please uncomment below.\n", + "\n", + "#snapshot_download(\n", + "# repo_id=\"microsoft/Phi-4-reasoning\",\n", + "# local_dir=f\"{MODEL_DIR}/Phi-4-reasoning\",\n", + "# local_dir_use_symlinks=False\n", + "# )" + ] + }, + { + "cell_type": "markdown", + "id": "b7adaf60-c5f2-4feb-ada4-eec7c20e1a4f", + "metadata": {}, + "source": [ + "以下のセルを実行し、phi-4のNIM推論サーバを起動します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b83e1b3-1644-44e5-bb86-b16ad40a694b", + "metadata": {}, + "outputs": [], + "source": [ + "current_path = os.getcwd()\n", + "os.environ['NIM_SERVED_MODEL_NAME']=\"Phi-4-reasoning\"\n", + "#os.environ['TRTLLM_MODEL_PATH']=current_path+\"/models/Phi-4-reasoning\" # for manually download ckpt\n", + "os.environ['TRTLLM_MODEL_PATH']=\"/mnt/lustre/docker-containers/E2E-LLM-NIMS/models/Phi-4-reasoning\" # for this workshop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "424a1471-80a3-4295-b9c8-ce2d67a3db7d", + "metadata": {}, + "outputs": [], + "source": [ + "!docker run -it -d --rm \\\n", + "--name=phi4 \\\n", + "--gpus '\"device=0\"' \\\n", + "--shm-size=16GB \\\n", + "-e NIM_MODEL_NAME=/model \\\n", + "-e NIM_SERVED_MODEL_NAME=${NIM_SERVED_MODEL_NAME} \\\n", + "-e NIM_MODEL_PROFILE=vllm \\\n", + "-e NIM_ENABLE_KV_CACHE_REUSE=1 \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-v $TRTLLM_MODEL_PATH:/model \\\n", + "-u $(id -u) \\\n", + "-p $CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/nvidia/llm-nim:latest" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87b8db67-14df-4680-bcb5-5e5d2a5c5bb6", + "metadata": {}, + "outputs": [], + "source": [ + "! docker logs --tail 100 phi4" + ] + }, + { + "cell_type": "markdown", + "id": "626d4596-80a9-4275-9b29-0fad5f290313", + "metadata": {}, + "source": [ + "Phi4の推論サーバーが起動したら、以下のセルを実行してみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fd13e95-39cc-40c0-942e-76f8688baba3", + "metadata": {}, + "outputs": [], + "source": [ + "llm_phi = ChatNVIDIA(\n", + " base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']),\n", + " model=\"Phi-4-reasoning\",\n", + " temperature=0.8,\n", + " top_p=0.95,\n", + " top_k=50, \n", + " max_tokens=256\n", + ")\n", + "for chunk in llm_phi.stream([{\"role\": \"system\", \"content\": \"あなたはPhiです。Microsoftがユーザーを支援するためにトレーニングした言語モデルです。アシスタントとしてのあなたの役割は、体系的な思考プロセスを通して質問を徹底的に探求し、最終的な正確で的確な解決策を提供することです。そのためには、分析、要約、探索、再評価、反省、バックトレース、そして反復という包括的なサイクルに取り組むことで、熟考された思考プロセスを開発する必要があります。回答は、指定された形式( {Thought section} {Solution section})を使用して、「思考」と「解決策」の2つの主要なセクションに分けて構成してください。回答はすべて日本語で行って下さい。\"},{\"role\": \"user\", \"content\": \"12と8の積はいくらですか?\"}]): \n", + " print(chunk.content, end=\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "004e0499-5d4c-4e7e-abc5-d00738981819", + "metadata": {}, + "source": [ + "最後にNIMコンテナを停止します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe9f8152-054f-4948-9504-82499f9a7a7b", + "metadata": {}, + "outputs": [], + "source": [ + "! docker container stop phi4" + ] + }, + { + "cell_type": "markdown", + "id": "3aa15b93-4154-4637-b8c6-e3a51c5b7df9", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## References\n", + "\n", + "- https://developer.nvidia.com/blog/tips-for-building-a-rag-pipeline-with-nvidia-ai-langchain-ai-endpoints/\n", + "- https://nvidia.github.io/GenerativeAIExamples/latest/notebooks/05_RAG_for_HTML_docs_with_Langchain_NVIDIA_AI_Endpoints.html\n", + "\n", + "## Licensing\n", + "\n", + "Copyright © 2024 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + }, + { + "cell_type": "markdown", + "id": "a9cb39be-ea23-43ad-b934-2c0c188d7c01", + "metadata": {}, + "source": [ + "
\n", + "
\n", + " Previous Notebook\n", + " \n", + " 1\n", + " 2\n", + " 3\n", + " \n", + " \n", + " Next Notebook\n", + "
\n", + "\n", + "
\n", + "

Home Page

" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6bfe5f29-418b-48f7-ad13-ef5205d30a18", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/nim_agent_main.ipynb b/workspace-nim-with-rag-jp/jupyter_notebook/nim_agent_main.ipynb new file mode 100644 index 0000000..90063e8 --- /dev/null +++ b/workspace-nim-with-rag-jp/jupyter_notebook/nim_agent_main.ipynb @@ -0,0 +1,780 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3825a2ba-9bc6-4dc8-98ee-4c7fc7ff1921", + "metadata": {}, + "source": [ + "# エージェントとしてのNVIDIA NIM" + ] + }, + { + "cell_type": "markdown", + "id": "f41ca1ca-8f59-40fc-a0f7-03434a706ca7", + "metadata": {}, + "source": [ + "このノートブックは、LangGraphを用いて固定されたワークフローに沿ってツールを呼び出し、実行するエージェントについて説明します。マルチエージェントを含む自律的なエージェントを効率的に開発するツールキットとしてはNeMo Agent Toolkitがあります。NeMo Agent Toolkitについてはこのノートブックでは触れませんが、興味がある方は[こちら](https://developer.nvidia.com/nemo-agent-toolkit)をご覧ください\n", + "\n", + "ツールとは、入力を受け取り、アクションを実行し、定義済みスキーマに従った構造化出力で結果を返すインターフェースです。多くの場合、外部API呼び出しを含み、LLM単体ではできないタスクを実行できます。ただし、必ずしも外部API呼び出しである必要はありません。例えば、サンディエゴの天気を取得するには天気ツール、49ersの試合結果を取得するにはウェブ検索ツールやESPNツールなどが使えます。\n", + "AIエージェントの詳細は [こちら](https://www.nvidia.com/en-us/glossary/ai-agents/)をご覧ください。\n", + "\n", + "
\n", + " \"Agent
\n", + "
\n", + "
\n", + "\n", + "このノートブックでは、以下のエージェント的な構成でNIMを利用します:\n", + "### LLM\n", + "* ローカルでホストされたLlama 3.1 8Bモデルのエンドポイント\n", + "\n", + "### ツール\n", + "* 画像キャプション用のローカルNIM(Llama 3.2 vision instructモデル使用)\n", + "* リアルタイム情報取得用APIベースのツール(Tavily Search)" + ] + }, + { + "cell_type": "markdown", + "id": "94bb738e-4f20-4a34-982e-91b3f0903a4b", + "metadata": {}, + "source": [ + "## 環境設定" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8707d434-70d7-43a3-ab93-7fddca59a833", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "# del os.environ['NVIDIA_API_KEY'] ## delete key and reset\n", + "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " print(\"Valid NVIDIA_API_KEY already in environment. Delete to reset\")\n", + "else:\n", + " nvapi_key = getpass.getpass(\"NVAPI Key (starts with nvapi-): \")\n", + " assert nvapi_key.startswith(\"nvapi-\"), f\"{nvapi_key[:5]}... is not a valid key\"\n", + " os.environ[\"NVIDIA_API_KEY\"] = nvapi_key\n", + " os.environ[\"NGC_API_KEY\"] = nvapi_key\n", + "\n", + "global nvapi_key" + ] + }, + { + "cell_type": "markdown", + "id": "546a89d3-52bb-497b-a767-cb255f4f4cbc", + "metadata": {}, + "source": [ + "### ローカルVLMのデプロイ\n", + "\n", + "ローカルにデプロイされたVLM NIMは、エージェントが呼び出す最初のツールとなります。VLM NIMは画像にキャプションを付けるために使用されます。" + ] + }, + { + "cell_type": "markdown", + "id": "1bc81855-b1dd-48a2-9b1b-d506902aa888", + "metadata": {}, + "source": [ + "利用可能なポートを検索" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "573f5a8e-e38d-477d-8860-d8cc76308e61", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import socket\n", + "import os \n", + "\n", + "def find_available_port(start=11000, end=11999):\n", + " while True:\n", + " # Randomly select a port between start and end range\n", + " port = random.randint(start, end)\n", + " \n", + " # Try to create a socket and bind to the port\n", + " with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:\n", + " try:\n", + " sock.bind((\"localhost\", port))\n", + " # If binding is successful, the port is free\n", + " return port\n", + " except OSError:\n", + " # If binding fails, the port is in use, continue to the next iteration\n", + " continue\n", + "\n", + "# Find and print an available port\n", + "os.environ['CONTAINER_PORT'] = str(find_available_port())\n", + "print(f\"Your have been alloted the available port: {os.environ['CONTAINER_PORT']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c3067bb-d17f-4e84-ac50-539f3dca253f", + "metadata": {}, + "outputs": [], + "source": [ + "from os.path import expanduser\n", + "home = expanduser(\"~\")\n", + "os.environ['LOCAL_NIM_CACHE']=f\"{home}/.cache/nim\"\n", + "!echo $LOCAL_NIM_CACHE" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27c0f513-b0c7-4428-bc17-4dcdee825bc3", + "metadata": {}, + "outputs": [], + "source": [ + "!mkdir -p \"$LOCAL_NIM_CACHE\"\n", + "!chmod 777 \"$LOCAL_NIM_CACHE\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6501437-c968-42b9-8cf3-b83eb0d86ada", + "metadata": {}, + "outputs": [], + "source": [ + "! echo -e \"$NGC_API_KEY\" | docker login nvcr.io --username '$oauthtoken' --password-stdin" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "268a8fdf-e1be-4a50-b5d1-d55b0a742c73", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it --rm -d \\\n", + "--gpus '\"device=0\"' \\\n", + "--name=vlm_nim \\\n", + "--shm-size=16GB \\\n", + "-e NGC_API_KEY \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-u $(id -u) \\\n", + "-p $CONTAINER_PORT:8000 \\\n", + "nvcr.io/nim/meta/llama-3.2-11b-vision-instruct:1.1.1\n", + "\n", + "# -e NIM_MODEL_PROFILE=tensorrt_llm-a100-bf16-tp1-throughput \\\n", + "# In order to ensure, the local NIM container is completely loaded and doesn't remain in pending stage, we instantiate a wait interval\n", + "! sleep 60" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f9e4692-145e-46b2-885f-11787c203620", + "metadata": {}, + "outputs": [], + "source": [ + "! docker ps -a | awk 'NR==1 || /llama/'" + ] + }, + { + "cell_type": "markdown", + "id": "b89719a5-9774-4569-a621-6aa5285320f7", + "metadata": {}, + "source": [ + "### ローカルLLMのデプロイ\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "6fff4b49-287f-4bd1-ab7d-60640bc1ecbc", + "metadata": {}, + "source": [ + "今回使用するLLMは `tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1` です。\n", + "\n", + "モデルは1台のA100/H100 80GB GPUで実行できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5dbe23c-3ef8-469d-8468-b98649763b89", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import socket\n", + "import os \n", + "\n", + "def find_available_port(start=11000, end=11999):\n", + " while True:\n", + " # Randomly select a port between start and end range\n", + " port = random.randint(start, end)\n", + " \n", + " # Try to create a socket and bind to the port\n", + " with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:\n", + " try:\n", + " sock.bind((\"localhost\", port))\n", + " # If binding is successful, the port is free\n", + " return port\n", + " except OSError:\n", + " # If binding fails, the port is in use, continue to the next iteration\n", + " continue\n", + "\n", + "# Find and print an available port\n", + "os.environ['LLM_CONTAINER_PORT'] = str(find_available_port())\n", + "print(f\"Your have been alloted the available port: {os.environ['LLM_CONTAINER_PORT']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "136922c6-fb3e-4a4a-af82-5be13df8fc74", + "metadata": {}, + "outputs": [], + "source": [ + "!export LOCAL_NIM_CACHE=~/.cache/nim\n", + "!mkdir -p \"$LOCAL_NIM_CACHE\"\n", + "\n", + "!docker run -it -d --rm \\\n", + " --gpus '\"device=1\"' \\\n", + " --name=llm_nim \\\n", + " --shm-size=16GB \\\n", + " -e NGC_API_KEY \\\n", + " -v \"$LOCAL_NIM_CACHE:/opt/nim/.cache\" \\\n", + " -u $(id -u) \\\n", + " -p $LLM_CONTAINER_PORT:8000 \\\n", + " nvcr.io/nim/tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1:latest\n", + "\n", + "!sleep 60" + ] + }, + { + "cell_type": "markdown", + "id": "b78be98e-068d-4c3c-96dc-66aa7974def6", + "metadata": {}, + "source": [ + "## ツール呼び出しの設定" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "889d0e4a-a75f-4d3a-a30c-5ddb40e822fa", + "metadata": {}, + "outputs": [], + "source": [ + "# === Imports ===\n", + "import base64, io\n", + "from PIL import Image\n", + "from typing import Annotated, TypedDict\n", + "from pydantic import BaseModel, Field\n", + "\n", + "from langchain_core.tools import tool\n", + "from langchain_core.runnables import RunnableLambda\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "from langgraph.graph import END, StateGraph\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", + "# === Image-to-base64 ===\n", + "def img2base64_string(img_path):\n", + " print(img_path)\n", + " image = Image.open(img_path)\n", + " if image.width > 800 or image.height > 800:\n", + " image.thumbnail((800, 800))\n", + " buffered = io.BytesIO()\n", + " image.convert(\"RGB\").save(buffered, format=\"JPEG\", quality=85)\n", + " return base64.b64encode(buffered.getvalue()).decode()" + ] + }, + { + "cell_type": "markdown", + "id": "57220dc0-fed2-49f3-968d-804d87e00935", + "metadata": {}, + "source": [ + "### === ツール: 画像キャプション生成 ===" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0e1ba361-69a0-4724-8424-c65e3d1445e9", + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def ImageCaptionTool(img_path: Annotated[str, \"入力画像のパス\"]) -> str:\n", + " \"\"\"vision LLMを使って画像の状況を記述して.英語で出力する\"\"\"\n", + " image_b64 = img2base64_string(img_path)\n", + " prompt = f'What is this image? '\n", + " \n", + " chat_model = ChatNVIDIA(\n", + " base_url=f\"http://0.0.0.0:{os.environ['CONTAINER_PORT']}/v1\",\n", + " model=\"meta/llama-3.2-11b-vision-instruct\",\n", + " max_tokens=512,\n", + " temperature=0.00,\n", + " top_p=1.00\n", + " )\n", + " \n", + " response = chat_model.invoke(prompt)\n", + " return response.content" + ] + }, + { + "cell_type": "markdown", + "id": "a10bdbfd-218f-45e2-8552-ef50157767c6", + "metadata": {}, + "source": [ + "画像キャプションツールが正しく実行されているか確認します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b57ae70-57f7-4237-94aa-8996440a4584", + "metadata": {}, + "outputs": [], + "source": [ + "img_path=\"./imgs/place_pic.jpg\"\n", + "\n", + "out=ImageCaptionTool(img_path)\n", + "out" + ] + }, + { + "cell_type": "markdown", + "id": "f8d2f246-53ce-4392-8c89-df044326b8ac", + "metadata": {}, + "source": [ + "### 期待される出力\n", + "\n", + "```\n", + "./imgs/place_pic.jpg\n", + "'This image depicts the Taj Mahal, a mausoleum located in Agra, India, built by Mughal Emperor Shah Jahan in memory of his wife, Mumtaz Mahal, who died during the birth of their 14th child. The Taj Mahal is an example of Mughal architecture, which combines elements of Indian, Persian, and Islamic styles. It is considered one of the most beautiful buildings in the world and is a UNESCO World Heritage Site.\\n\\nThe Taj Mahal is made of white marble and features intricate carvings, calligraphy, and inlays of precious stones such as jasper, jade, and turquoise. The main structure is surrounded by gardens and a reflecting pool, which creates a sense of symmetry and balance. The Taj Mahal is also decorated with flowers, birds, and other motifs, reflecting the Islamic tradition of using geometric patterns and natural forms in architecture.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59b3235c-e833-4d6d-b771-819db1e05cac", + "metadata": {}, + "outputs": [], + "source": [ + "img_path=\"imgs/place_pic1.jpg\"\n", + "\n", + "out=ImageCaptionTool(img_path)\n", + "out" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0e5b5f1-dff3-4121-9420-07b453ab2654", + "metadata": {}, + "outputs": [], + "source": [ + "# === Location extractors ===\n", + "class ImgPathExtractor(BaseModel):\n", + " img_path: str = Field(description=\"入力画像へのパス\")\n", + "\n", + "class LocationExtractor(BaseModel):\n", + " location: str = Field(description=\"記述もしくは言及された場所の名前\")\n", + "\n", + "# Using the 405B model for excellent tool calling capabilities\n", + "#llm = ChatNVIDIA(model=\"meta/llama-3.1-405b-instruct\", max_tokens=1024)\n", + "\n", + "# OR USE the locally deployed variant.\n", + "llm = ChatNVIDIA(model=\"tokyotech-llm/llama-3.1-swallow-8b-instruct-v0.1\", \\\n", + " max_tokens=1024,\\\n", + " base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['LLM_CONTAINER_PORT'])\n", + " )\n", + "\n", + "\n", + "# === Parser functions ===\n", + "format_chain = (\n", + " ChatPromptTemplate.from_template(\"inputから画像パスを抽出: {input}\")\n", + " | llm.with_structured_output(ImgPathExtractor)\n", + ")\n", + "\n", + "def parse_image_path(state) -> dict:\n", + " structured = format_chain.invoke({\"input\": state[\"input\"]})\n", + " return {\"img_path\": structured.img_path}\n", + "\n", + "def extract_caption(state) -> dict:\n", + " result = ImageCaptionTool.invoke({\"img_path\": state[\"img_path\"]})\n", + " return {\"image_caption\": result}\n", + "\n", + "caption_to_location = (\n", + " ChatPromptTemplate.from_template(\"caption中の画像の記述から場所を抽出: {caption}\")\n", + " | llm.with_structured_output(LocationExtractor)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "671214a8-feba-48f6-b208-cf9106c1e353", + "metadata": {}, + "outputs": [], + "source": [ + "def extract_location_from_caption(state) -> dict:\n", + " print(state)\n", + " result = caption_to_location.invoke({\"caption\": state[\"image_caption\"]})\n", + " return {\"location\": result.location}\n", + "\n", + "text_location_chain = (\n", + " ChatPromptTemplate.from_template(\"このユーザークエリから場所を抽出: {input}\")\n", + " | llm.with_structured_output(LocationExtractor)\n", + ")\n", + "\n", + "def extract_location_from_text(state) -> dict:\n", + " result = text_location_chain.invoke({\"input\": state[\"input\"]})\n", + " # Add default values for image-related fields so summarizer doesn't fail\n", + " return {\n", + " \"location\": result.location,\n", + " \"img_path\": \"N/A\", # Default value\n", + " \"image_caption\": \"No image provided\" # Default value\n", + " }" + ] + }, + { + "cell_type": "markdown", + "id": "59e84499-4ff3-48a2-96b2-fa01e5b51225", + "metadata": {}, + "source": [ + "### === ツール: Web検索 ===" + ] + }, + { + "cell_type": "markdown", + "id": "650a9fd1-0e8b-44bc-9e92-aa99af924009", + "metadata": {}, + "source": [ + "[Tavily](https://docs.tavily.com/documentation/about)は、効率的で迅速かつ永続的な検索結果を目指したLLM向けに最適化された検索エンジンです。SerpやGoogleなどの他の検索APIとは異なり、TavilyはAI開発者と自律型AIエージェント向けの検索の最適化に重点を置いています。オンラインソースからの検索、スクレイピング、フィルタリング、最も関連性の高い情報の抽出など、すべての負担を1回のAPI呼び出しで処理します!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41f49f6a-8560-4b34-947d-752e158143ca", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_community.tools.tavily_search import TavilySearchResults" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6666922d-8a88-41ea-b0c4-24bd96fb70ea", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "if \"TAVILY_API_KEY\" not in os.environ:\n", + " os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Enter your Tavily API key\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec8fe690-00fd-480b-8b2f-9c8dbfe7db89", + "metadata": {}, + "outputs": [], + "source": [ + "weather_search_tool = TavilySearchResults(max_results=2)\n", + "\n", + "def search_weather(state) -> dict:\n", + " query = f\"current weather in {state['location']}\"\n", + " result = weather_search_tool.invoke(query)\n", + " return {\"search_results\": result}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df569e1e-f350-4dfb-8a75-497ef35bf90d", + "metadata": {}, + "outputs": [], + "source": [ + "summary_prompt = ChatPromptTemplate.from_template(\n", + " \"\"\"あなたは、視覚情報とウェブデータをもとに要約するアシスタントです。\n", + "\n", + "ウェブ検索結果: {search_results}\n", + "\n", + "これらの情報を使って、その場所の現在の天気を2~3行でわかりやすく要約してください。また、その天気に合ったおすすめのアクティビティも提案してください。\n", + "\"\"\"\n", + ")\n", + "\n", + "summarizer_chain = summary_prompt | llm | StrOutputParser()\n", + "\n", + "def summarize_result(state) -> dict:\n", + " summary = summarizer_chain.invoke(state)\n", + " return {\"final_response\": summary}\n", + "\n", + "# === Routing node ===\n", + "def route_input(state) -> dict:\n", + " if any(ext in state[\"input\"] for ext in [\".png\", \".jpg\", \".jpeg\"]):\n", + " return {\"has_image\": True}\n", + " else:\n", + " return {\"has_image\": False}" + ] + }, + { + "cell_type": "markdown", + "id": "0a618c9b-49eb-4cc3-9499-963f8cabc4de", + "metadata": {}, + "source": [ + "## LangGraph セットアップ\n", + "\n", + "[LangGraph](https://langchain-ai.github.io/langgraph/)は、制御可能なエージェントを構築するための低レベルのオーケストレーションフレームワークです。Langchainが LLMアプリケーション開発を効率化するための統合機能とコンポーネントを提供する一方、LangGraphライブラリはエージェントのオーケストレーションを可能にし、カスタマイズ可能なアーキテクチャ、長期メモリ、および人間参加型のワークフローを提供して、複雑なタスクを確実に処理します。" + ] + }, + { + "cell_type": "markdown", + "id": "3900f2c9-c1ad-4865-819b-a4f04933c92b", + "metadata": {}, + "source": [ + "### ステートのセットアップ\n", + "\n", + "グラフを定義する際の最初のステップは、グラフのステートを定義することです。\n", + "ステートは、グラフのスキーマと、ステートの更新方法を指定するリデューサー関数で構成されています。ステートのスキーマは、グラフ内のすべてのノードとエッジへの入力スキーマとなり、TypedDictまたはPydanticモデルのいずれかを使用できます。すべてのノードはステートへの更新を発行し、それらは指定されたリデューサー関数を使用して適用されます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71d5b347-a01d-4caa-9756-c2a082fd2830", + "metadata": {}, + "outputs": [], + "source": [ + "# === State type ===\n", + "class ImageWeatherState(TypedDict):\n", + " input: str\n", + " img_path: str\n", + " image_caption: str\n", + " location: str\n", + " search_results: str\n", + " final_response: str\n", + " has_image: bool\n", + "\n", + "# === Build LangGraph ===\n", + "builder = StateGraph(ImageWeatherState)\n", + "\n", + "builder.add_node(\"route_input\", RunnableLambda(route_input))\n", + "builder.add_node(\"parse_image_path\", RunnableLambda(parse_image_path))\n", + "builder.add_node(\"caption_image\", RunnableLambda(extract_caption))\n", + "builder.add_node(\"extract_location_from_caption\", RunnableLambda(extract_location_from_caption))\n", + "builder.add_node(\"extract_location_from_text\", RunnableLambda(extract_location_from_text))\n", + "builder.add_node(\"search_weather\", RunnableLambda(search_weather))\n", + "builder.add_node(\"summarize\", RunnableLambda(summarize_result))\n", + "\n", + "builder.set_entry_point(\"route_input\")\n", + "\n", + "builder.add_conditional_edges(\n", + " source=\"route_input\",\n", + " path=lambda state: \"has_image\" if state[\"has_image\"] else \"text_only\",\n", + " path_map={\n", + " \"has_image\": \"parse_image_path\",\n", + " \"text_only\": \"extract_location_from_text\"\n", + " }\n", + ")\n", + "\n", + "builder.add_edge(\"parse_image_path\", \"caption_image\")\n", + "builder.add_edge(\"caption_image\", \"extract_location_from_caption\")\n", + "builder.add_edge(\"extract_location_from_caption\", \"search_weather\")\n", + "builder.add_edge(\"extract_location_from_text\", \"search_weather\")\n", + "\n", + "builder.add_edge(\"search_weather\", \"summarize\")\n", + "builder.add_edge(\"summarize\", END)\n", + "\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "beb34847-c96c-455f-8ccb-cf4865c677ca", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Image as img, display\n", + "\n", + "try:\n", + " display(img(graph.get_graph().draw_mermaid_png()))\n", + "except Exception as e:\n", + " # This requires some extra dependencies and is optional\n", + " print(e)\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "3cd83284-a4e8-41bf-867f-08cba0370384", + "metadata": {}, + "source": [ + "## エージェントの実行" + ] + }, + { + "cell_type": "markdown", + "id": "462ab1f0-2a4b-492d-99c2-d016e0262df3", + "metadata": {}, + "source": [ + "#### 画像を使用するユースケース" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d6e5bf8-5c7d-4da3-8a61-e3ec7a53e2ef", + "metadata": {}, + "outputs": [], + "source": [ + "# With image\n", + "result = graph.invoke({\"input\": \"この写真の場所の天気は? ./imgs/place_pic.jpg\"})\n", + "\n", + "# Print all steps\n", + "for k, v in result.items():\n", + " print(f\"{k.upper()}:\\n{v}\\n{'-'*50}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cc062a02-8664-4f30-a54f-6235eaeddf9b", + "metadata": {}, + "source": [ + "#### 画像を使用しないユースケース" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1c3c0f3-21c1-467a-8a48-b39fdd530690", + "metadata": {}, + "outputs": [], + "source": [ + "# Without image\n", + "result = graph.invoke({\"input\": \"今日の東京の天気を調べて\"})\n", + "\n", + "# Print all steps\n", + "for k, v in result.items():\n", + " print(f\"{k.upper()}:\\n{v}\\n{'-'*50}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d933e9db-930e-4d43-bb48-ab23f64ef621", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.runnables import RunnableConfig\n", + "\n", + "from pprint import pprint\n", + "\n", + "inputs = {\n", + " \"input\": \"この写真の場所の天気は? ./imgs/place_pic1.jpg\"\n", + "}\n", + "\n", + "from langchain_core.callbacks.base import BaseCallbackHandler\n", + "\n", + "class PrintStepCallback(BaseCallbackHandler):\n", + " def on_tool_start(self, serialized, input_str, **kwargs):\n", + " print(f\"\\n🔧 Tool Call: {serialized['name']} with input → {input_str}\")\n", + "\n", + " def on_tool_end(self, output, **kwargs):\n", + " print(f\"✅ Tool Output: {output}\")\n", + "\n", + " def on_chain_end(self, outputs, **kwargs):\n", + " print(f\"\\n🔚 Final Chain Output: {outputs}\")\n", + "\n", + "config = RunnableConfig(callbacks=[PrintStepCallback()])\n", + "state = graph.invoke(inputs, config=config)\n", + "\n", + "print(\"\\n✅ Final Result:\")\n", + "print(state['final_response'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd048943-821d-41cc-a41a-81307d63e671", + "metadata": {}, + "outputs": [], + "source": [ + "! docker stop llm_nim" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35aaec9c-d7d2-446a-9536-b8742835c53c", + "metadata": {}, + "outputs": [], + "source": [ + "! docker stop vlm_nim" + ] + }, + { + "cell_type": "markdown", + "id": "69e53f9e-916e-42c4-9f57-bd08906e2f75", + "metadata": {}, + "source": [ + "## 演習\n", + "\n", + "エージェントフローにカスタム操作を実行する独自のツールを作成して追加してください。\n", + "例えば:\n", + "* 計算ツールを作成する (LLMは数学的計算が苦手なため、長い数式の評価に使用できます)\n", + "* 場所を検索して地図上に配置できるジオロケーターツールを作成する\n", + "* Stable Diffusionなどのtext2imgモデルを使用して、その場所の有名なランドマークのアニメーションレプリカを作成するツールを作成する" + ] + }, + { + "cell_type": "markdown", + "id": "f2947caa-7422-4994-b255-776837747394", + "metadata": {}, + "source": [ + "このノートブックでは、NVIDIA NIMが複数の異なるツールや他のNVIDIA NIMと連携して、エージェントワークフローを作成できることを示しました。" + ] + }, + { + "cell_type": "markdown", + "id": "29f5a023-c49d-4de1-8a05-087bc4cff031", + "metadata": {}, + "source": [ + "---\n", + "## Licensing\n", + "\n", + "Copyright © 2025 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/nim_lora_adapter.ipynb b/workspace-nim-with-rag-jp/jupyter_notebook/nim_lora_adapter.ipynb new file mode 100644 index 0000000..755ba30 --- /dev/null +++ b/workspace-nim-with-rag-jp/jupyter_notebook/nim_lora_adapter.ipynb @@ -0,0 +1,1015 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5bfdf4ac-e367-4bf6-afd3-9012128e0128", + "metadata": {}, + "source": [ + "

Home Page

\n", + "\n", + "
\n", + " Previous Notebook\n", + " \n", + " 1\n", + " 2\n", + " 3\n", + " \n", + " \n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "767809c7-c3c0-40fe-9783-2d41a072d4a8", + "metadata": {}, + "source": [ + "# LoRAアダプタによるNVIDIA NIMの実行\n", + "---\n", + "\n", + "**概要:** このラボの目的は、LoRAアダプターを中心に、PEFT(Parameter Efficient Fine Tuning)の概念を紹介することです。\n" + ] + }, + { + "cell_type": "markdown", + "id": "bb586235-1ca1-406f-b865-686bf96dff2b", + "metadata": {}, + "source": [ + "## Low Rank Adapters(LoRA)" + ] + }, + { + "cell_type": "markdown", + "id": "97278d93-4d12-4867-89bf-83419ef0f576", + "metadata": {}, + "source": [ + "大規模なLLMの完全なフルファインチューニング(つまりモデルの全パラメータを更新すること)は、モデル全体の学習に必要な計算インフラ量のために困難な場合があります。 インフラストラクチャーのコストは、ユーザーが複数の大きなモデルをメモリにホストするか、モデル全体がスワップイン・アウトされることによる待ち時間の増加を許容する必要があり、デプロイ時にも増加します。したがって、最小限の変更回数で目的を達成し、重みの読み取りと書き込みを行う必要があります。\n", + "\n", + "一般的な方法は、最先端の[Parameter-Efficient Finetuning (PEFT)](https://github.com/huggingface/peft/tree/main) アプローチを使用することです。[PEFT](https://arxiv.org/abs/2305.16742)は、モデルの全パラメータではなく、少数の(余分な)モデルパラメータをファインチューニングすることを可能にし、計算コストとストレージコストを大幅に削減します。PEFTを実装する方法の1つは、Low-Rank Adaptation (LoRA) テクニックを採用することです。LoRA手法では、ダウンストリームのタスクで学習可能なパラメータの数を大幅に減らすことで、ファインチューニングをより効率的に行うことが出来ます。これは、事前に訓練されたモデルの重みを凍結し、Transformerアーキテクチャの各層に訓練可能なランク分解行列を注入することで実現されます。[LoRAの作者](https://arxiv.org/abs/2106.09685)によると、学習可能なパラメータの数を10k倍削減するほか、GPUの消費量を3倍削減し、推論レイテンシなしで高いスループットを実現します。LoRAの簡単な背景については、こちらの[リンク](https://huggingface.co/docs/peft/main/en/conceptual_guides/lora)を参照してください。\n", + "\n", + "
\n", + "
LoRA reparametrization and Weight merging. View source
" + ] + }, + { + "cell_type": "markdown", + "id": "2778513c-58ab-44f6-90e5-c9ed1532ad32", + "metadata": {}, + "source": [ + "## NVIDIA NIMのLoRAサポート\n", + "\n", + "LoRAの多面的なメリットを活かすため、NIMではベースモデルの上位に複数のLoRAインスタンスを配置することができます。これにより、下図の例のように、同じバックボーンを使いながら、複数のジャンルのユーザーアプリケーションにカスタムメイドで対応することができます:\n", + "\n", + "
\n", + "
LoRA reparametrization and Weight merging. View source
\n" + ] + }, + { + "cell_type": "markdown", + "id": "64efc225-42f0-4f6e-a386-218bddfa85ea", + "metadata": {}, + "source": [ + "## モデルのプロファイル\n", + "\n", + "NVIDIA NIMは、計算リソースから引き出せる最高のパフォーマンスを提供するように設計されています。これは、利用可能なハードウェア(すなわち、アーキテクチャやGPUの数)、要件などに基づいて特別に調整された最適化されたモデルプロファイルによって行われます。完全なリストは[こちら](https://docs.nvidia.com/nim/large-language-models/latest/support-matrix.html#supported-lora-formats)にあります。\n", + "\n", + "先にダウンロードした`Llama3-8b` NIMで利用可能なモデルプロファイルをリストアップしてみましょう。その前に、ポートを設定する必要があります。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "008cc4ce-707a-4b5f-940f-f4f583cc2fe4", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import socket\n", + "import os\n", + "def find_available_port(start=11000, end=11999):\n", + " while True:\n", + " # ポートをランダムに選択\n", + " port = random.randint(start, end)\n", + " \n", + " # ソケットを作成し、ポートにバインド\n", + " with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:\n", + " try:\n", + " sock.bind((\"localhost\", port))\n", + " # バインドが成功した場合、ポートは空きの状態であるため、ポートを返す\n", + " return port\n", + " except OSError:\n", + " # バインドに失敗した場合、ポートは使用中であるため、次のイテレーションに継続\n", + " continue\n", + "\n", + "# 利用可能なポートを見つけて出力\n", + "os.environ['CONTAINER_PORT'] = str(find_available_port())\n", + "print(f\"Your have been alloted the available port: {os.environ['CONTAINER_PORT']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb5ff8ff-bfa9-4ccf-9834-07de38c4f3d3", + "metadata": {}, + "outputs": [], + "source": [ + "!docker run -it --rm --gpus '\"device=0\"' --shm-size=16GB -u $(id -u) -p $CONTAINER_PORT:8000 nvcr.io/nim/meta/llama3-8b-instruct:1.0.0 list-model-profiles" + ] + }, + { + "cell_type": "markdown", + "id": "a2227c81-367f-4331-bb2f-5ad00416d45c", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "\n", + "```text\n", + "\n", + "===========================================\n", + "== NVIDIA Inference Microservice LLM NIM ==\n", + "===========================================\n", + "\n", + "NVIDIA Inference Microservice LLM NIM Version 1.0.3\n", + "Model: meta/llama3-8b-instruct\n", + "\n", + "Container image Copyright (c) 2016-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "This NIM container is governed by the NVIDIA AI Product Agreement here:\n", + "https://www.nvidia.com/en-us/data-center/products/nvidia-ai-enterprise/eula/.\n", + "A copy of this license can be found under /opt/nim/LICENSE.\n", + "\n", + "The use of this model is governed by the AI Foundation Models Community License\n", + "here: https://docs.nvidia.com/ai-foundation-models-community-license.pdf.\n", + "\n", + "ADDITIONAL INFORMATION: Meta Llama 3 Community License, Built with Meta Llama 3.\n", + "A copy of the Llama 3 license can be found under /opt/nim/MODEL_LICENSE.\n", + "\n", + "SYSTEM INFO\n", + "- Free GPUs:\n", + " - [20b2:10de] (0) NVIDIA A100-SXM4-80GB (A100 80GB) [current utilization: 1%]\n", + "MODEL PROFILES\n", + "- Compatible with system and runnable:\n", + " - 8835c31752fbc67ef658b20a9f78e056914fdef0660206d82f252d62fd96064d (vllm-fp16-tp1)\n", + " - With LoRA support:\n", + " - 8d3824f766182a754159e88ad5a0bd465b1b4cf69ecf80bd6d6833753e945740 (vllm-fp16-tp1-lora)\n", + "- Incompatible with system:\n", + " - dcd85d5e877e954f26c4a7248cd3b98c489fbde5f1cf68b4af11d665fa55778e (tensorrt_llm-h100-fp8-tp2-latency)\n", + " - f59d52b0715ee1ecf01e6759dea23655b93ed26b12e57126d9ec43b397ea2b87 (tensorrt_llm-l40s-fp8-tp2-latency)\n", + " - 30b562864b5b1e3b236f7b6d6a0998efbed491e4917323d04590f715aa9897dc (tensorrt_llm-h100-fp8-tp1-throughput)\n", + " - 09e2f8e68f78ce94bf79d15b40a21333cea5d09dbe01ede63f6c957f4fcfab7b (tensorrt_llm-l40s-fp8-tp1-throughput)\n", + " - a93a1a6b72643f2b2ee5e80ef25904f4d3f942a87f8d32da9e617eeccfaae04c (tensorrt_llm-a100-fp16-tp2-latency)\n", + " - e0f4a47844733eb57f9f9c3566432acb8d20482a1d06ec1c0d71ece448e21086 (tensorrt_llm-a10g-fp16-tp2-latency)\n", + " - 879b05541189ce8f6323656b25b7dff1930faca2abe552431848e62b7e767080 (tensorrt_llm-h100-fp16-tp2-latency)\n", + " - 24199f79a562b187c52e644489177b6a4eae0c9fdad6f7d0a8cb3677f5b1bc89 (tensorrt_llm-l40s-fp16-tp2-latency)\n", + "...\n", + "```\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "aa927d64-8974-40d9-9bdb-cb15c97920e8", + "metadata": {}, + "source": [ + "サンプルのプロファイルを取り上げてみましょう: `cce57ae50c3af15625c1668d5ac4ccbe82f40fa2e8379cc7b842cc6c976fd334 (tensorrt_llm-a100-fp16-tp1-throughput-lora)`\n", + "\n", + "この説明文はNIMsモデルプロファイルの詳細を示しています:\n", + "- `tensorrt_llm` 推論エンジン\n", + "- Nvidia `A100` GPU用\n", + "- `fp16`精度\n", + "- 単一の `テンソル並列` (つまり単一デバイス)\n", + "- `throughput` 重視で\n", + "- `lora`をサポート" + ] + }, + { + "cell_type": "markdown", + "id": "46adcc7e-a62c-4cc2-84a0-e0b36f3fb15a", + "metadata": {}, + "source": [ + "### モデルプロファイルと共にNVIDIA NIMを動かす" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4ad5aaeb-e289-477c-9672-ba7d0f1ba61c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import getpass\n", + "\n", + "if not os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " nvapi_key = getpass.getpass(\"Enter your NVIDIA API key: \")\n", + " assert nvapi_key.startswith(\"nvapi-\"), f\"{nvapi_key[:5]}... is not a valid key\"\n", + " os.environ[\"NVIDIA_API_KEY\"] = nvapi_key\n", + " os.environ[\"NGC_API_KEY\"] = nvapi_key" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cc7eac2c-72d7-45be-9017-e43775eef914", + "metadata": {}, + "outputs": [], + "source": [ + "from os.path import expanduser\n", + "home = expanduser(\"~\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "edaa25d7-005c-4d26-a387-f752c799deac", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "try:\n", + " os.mkdir(f\"{home}/.cache/nim/\")\n", + " print(\"Cache Created\")\n", + "except:\n", + " print(\"Cache Exists..\")\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4cb90e31-eff0-4ad7-96a3-eb00dadd2fff", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['LOCAL_NIM_CACHE'] = f\"{home}/.cache/nim/\"" + ] + }, + { + "cell_type": "markdown", + "id": "4d8edd72-88f8-4b71-8655-3d10063d91ae", + "metadata": {}, + "source": [ + "リストアップされたプロファイルでNVIDIA NIMを走らせてみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70667fea-3e58-4c50-ae9c-a132fbed8bbb", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it --rm --gpus '\"device=0\"' --shm-size=16GB \\\n", + "-e NIM_MODEL_PROFILE=8d3824f766182a754159e88ad5a0bd465b1b4cf69ecf80bd6d6833753e945740 \\\n", + "-e NGC_API_KEY \\\n", + "-v $LOCAL_NIM_CACHE:/opt/nim/.cache \\\n", + "-u $(id -u) -p $CONTAINER_PORT:8000 nvcr.io/nim/meta/llama3-8b-instruct:1.0.0" + ] + }, + { + "cell_type": "markdown", + "id": "83ecdecd-41bb-41e4-8145-e93136047427", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "\n", + "```\n", + "\n", + "===========================================\n", + "== NVIDIA Inference Microservice LLM NIM ==\n", + "===========================================\n", + "\n", + "NVIDIA Inference Microservice LLM NIM Version 1.0.3\n", + "Model: nim/meta/llama3-8b-instruct\n", + "\n", + "Container image Copyright (c) 2016-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "\n", + "This NIM container is governed by the NVIDIA AI Product Agreement here:\n", + "https://www.nvidia.com/en-us/data-center/products/nvidia-ai-enterprise/eula/.\n", + "A copy of this license can be found under /opt/nim/LICENSE.\n", + "\n", + "The use of this model is governed by the AI Foundation Models Community License\n", + "here: https://docs.nvidia.com/ai-foundation-models-community-license.pdf.\n", + "\n", + "ADDITIONAL INFORMATION: Meta Llama 3 Community License, Built with Meta Llama 3. \n", + "A copy of the Llama 3 license can be found under /opt/nim/MODEL_LICENSE.\n", + "\n", + "2024-08-14 07:27:01,272 [INFO] PyTorch version 2.2.2 available.\n", + "2024-08-14 07:27:02,170 [WARNING] [TRT-LLM] [W] Logger level already set from environment. Discard new verbosity: error\n", + "2024-08-14 07:27:02,171 [INFO] [TRT-LLM] [I] Starting TensorRT-LLM init.\n", + "2024-08-14 07:27:02,214 [INFO] [TRT-LLM] [I] TensorRT-LLM inited.\n", + "[TensorRT-LLM] TensorRT-LLM version: 0.10.1.dev2024053000\n", + "INFO 08-14 07:27:03.173 api_server.py:489] NIM LLM API version 1.0.0\n", + "INFO 08-14 07:27:03.175 ngc_profile.py:217] Running NIM without LoRA. Only looking for compatible profiles that do not support LoRA.\n", + "INFO 08-14 07:27:03.175 ngc_profile.py:219] Detected 0 compatible profile(s).\n", + "INFO 08-14 07:27:03.175 ngc_profile.py:221] Detected additional 2 compatible profile(s) that are currently not runnable due to low free GPU memory.\n", + "ERROR 08-14 07:27:03.176 utils.py:21] You are running NIM without LoRA, but the selected profile '8d3824f766182a754159e88ad5a0bd465b1b4cf69ecf80bd6d6833753e945740' has LoRA enabled. Please select a profile that does not have LoRA enabled, or alternatively, run NIM with LoRA enabled.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c0e7e60e-a02e-47cc-abe2-1772171c6752", + "metadata": {}, + "source": [ + "**NIMのログには、LoRAが有効なモデル・プロファイルを選択したが、LoRAアダプタの定義を渡さなかったことが明確に示されています。**" + ] + }, + { + "cell_type": "markdown", + "id": "b63b1972-1f99-4aff-b7cb-e397a2478630", + "metadata": {}, + "source": [ + "### LoRA セットアップ概要\n", + "\n", + "環境変数で定義されたコンフィギュレーションを使って、NIMを拡張してLoRAモデルを提供することができます。ただし、条件があります: 使用する基礎となるNVIDIA NIMは、LoRAのベースモデルと互換性がなければなりません。したがって、コンフィギュレーションが必要です。詳細は後述します。\n", + "\n", + "#### LoRA アダプタ\n", + "\n", + "- LoRAアダプタを `NGC` または `Hugging Face` からダウンロードするか、`カスタムLoRAアダプタ` を使用します。\n", + "- LoRAアダプタは別々のディレクトリに格納する必要があり、`LOCAL_PEFT_DIRECTORY`ディレクトリの中に1つ以上のLoRAディレクトリが必要です。\n", + "- ロードされた LoRA アダプタの名前は、アダプタのディレクトリ名と一致していなければなりません。 \n", + "\n", + "NIM for LLMはNeMoとHugging Face Transformersの互換フォーマットをサポートしています。\n", + "\n", + "- NeMoフォーマットのLoRAディレクトリには、拡張子が`.nemo`のファイルが1つ含まれていなければなりません。`.nemo`ファイルの名前は親ディレクトリの名前と一致する必要はありません。サポートされるターゲットモジュールは`[\"gate_proj\", \"o_proj\", \"up_proj\", \"down_proj\", \"k_proj\", \"q_proj\", \"v_proj\", \"attention_qkv\"]`です。\n", + "\n", + "- Hugging Face Transformersで学習されたLoRAアダプタがサポートされます。LoRAは、adapter_config.jsonファイルと{adapter_model.safetensors, adapter_model.bin}ファイルのいずれかを含む必要があります。NVIDIA NIMでサポートされるターゲットモジュールは`[\"gate_proj\", \"o_proj\", \"up_proj\", \"down_proj\", \"k_proj\", \"q_proj\", \"v_proj\"]`です。" + ] + }, + { + "cell_type": "markdown", + "id": "969fc84d-8c44-4d3a-b19b-e936d4632db8", + "metadata": {}, + "source": [ + "#### LoRA モデルのディレクトリ構造\n", + "\n", + "例えば、1つ以上のLoRA(`LOCAL_PEFT_DIRECTORY`)を格納するディレクトリは以下のように構成します: \n", + "- `lolas`には、dockerコンテナに渡すディレクトリ名を `LOCAL_PEFT_DIRECTORY` の値として指定します。\n", + "- そして、ロードされる LoRA は `llama3-8b-math`、`llama3-8b-math-hf`、`llama3-8b-squad`、`llama3-8b-squad-hf` という名前になります。\n", + "\n", + "\n", + "```text\n", + "\n", + "loras\n", + "├── llama3-8b-math\n", + "│ └── llama3_8b_math.nemo\n", + "├── llama3-8b-math-hf\n", + "│ ├── adapter_config.json\n", + "│ └── adapter_model.bin\n", + "├── llama3-8b-squad\n", + "│ └── squad.nemo\n", + "└── llama3-8b-squad-hf\n", + " ├── adapter_config.json\n", + " └── adapter_model.safetensors\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "54516fd2-c764-44b4-9d7d-30cab84a22e2", + "metadata": {}, + "source": [ + "### LoRAアダプタのダウンロード\n", + "\n", + "前述したように、NVIDIAモデルレジストリNGCまたはHuggingFaceから、事前に訓練されたアダプタをダウンロードすることができます。 LoRAモデルの重みは、特定のベースモデルに結びついていることに注意してください。NVIDIA NIMによって提供されるものと同じモデルでチューニングされたLoRAモデルのみをデプロイする必要があります。HuggingFaceからアダプターをダウンロードする方法の例を以下に示します。 \n", + "\n", + "*huggingface-cli ログインが必須であることに注意してください。*\n", + "\n", + "```text\n", + "#export and make directory\n", + "export LOCAL_PEFT_DIRECTORY=~/loras\n", + "mkdir $LOCAL_PEFT_DIRECTORY\n", + "\n", + "#download a LoRA from Hugging Face Hub\n", + "mkdir $LOCAL_PEFT_DIRECTORY/llama3-lora\n", + "huggingface-cli download adapter_config.json adapter_model.safetensors --local-dir $LOCAL_PEFT_DIRECTORY/llama3-lora\n", + "\n", + "#create permissions\n", + "chmod -R 777 $LOCAL_PEFT_DIRECTORY\n", + "\n", + "```\n", + "\n", + "\n", + "次に、NGCから`llama3-8b-instruct`用のLoRAアダプターをダウンロードする方法を示します。" + ] + }, + { + "cell_type": "markdown", + "id": "6da8f70a-57b6-42cf-8965-30c0423b2c85", + "metadata": {}, + "source": [ + "### NVCR (NVIDIA Container Registry)へのログイン\n", + "\n", + "NIM の docker イメージにアクセスするには、`docker login nvcr.io`でログインする必要があります。このプロセスには、デフォルトのユーザー名 `--username $oauthtoken` と `--password-stdin` が必要で、`$NGC_API_KEY` の値を受け付けます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2941df99-daff-4035-a758-858ca24bdd11", + "metadata": {}, + "outputs": [], + "source": [ + "! echo -e \"$NGC_API_KEY\" | docker login nvcr.io --username '$oauthtoken' --password-stdin" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d8132374-af76-4e96-8c41-dad5d6e3fea4", + "metadata": {}, + "outputs": [], + "source": [ + "# LoRA用のディレクトリを作成\n", + "!mkdir -p loras" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a1eec87-28c0-4c9a-b057-b7b0ff599e26", + "metadata": {}, + "outputs": [], + "source": [ + "# vLLM-formatのLoRAをダウンロード\n", + "# NOTE: LoRAのダウンロードはNGCで行う場合、configのセットアップが必要です。BootcampではLoRAが事前にダウンロードされています。\n", + "# !ngc registry model download-version \"nim/meta/llama3-8b-instruct-lora:hf-math-v1\" --dest \"./loras/\" " + ] + }, + { + "cell_type": "markdown", + "id": "19c9cc73-059b-4fdd-8673-0a55fb0e9d7d", + "metadata": {}, + "source": [ + "**想定される出力:** (NGCからダウンロードする場合)\n", + "\n", + "```text\n", + "Downloaded 37.14 MB in 2s, Download speed: 18.57 MB/s \n", + "--------------------------------------------------------------------------------\n", + " Transfer id: llama3-8b-instruct-lora_vhf-math-v1\n", + " Download status: Completed\n", + " Downloaded local path: /home//NIMs_Bootcamp/loras/llama3-8b-instruct-lora_vhf-math-v1-1\n", + " Total files downloaded: 4\n", + " Total downloaded size: 37.14 MB\n", + " Started at: 2024-08-15 12:37:20.924505\n", + " Completed at: 2024-08-15 12:37:22.928174\n", + " Duration taken: 2s\n", + " \n", + "...\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "94d2f756-3a67-45de-8fa2-f0e475be8bdf", + "metadata": {}, + "source": [ + "### NIMへのLoRAアダプタの追加\n", + "\n", + "docker からアクセスできるように、LoRA ファイルにすべての表示権限を付与します。" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ea1e6dbb-2361-4eb8-a8dc-03fe8cc2b552", + "metadata": {}, + "outputs": [], + "source": [ + "! chmod -R 777 loras/\n", + "os.environ['NIM_PEFT_SOURCE'] = f'{os.getcwd()}/loras'\n", + "os.environ['LOCAL_PEFT_DIRECTORY'] = f'{os.getcwd()}/loras'" + ] + }, + { + "cell_type": "markdown", + "id": "d50b6ec2-891c-4837-906d-47522b8e245e", + "metadata": {}, + "source": [ + "上述したように、loraのコンフィギュレーションはファイルとフォルダーの構造に敏感なので、正しいデプロイメントを確実にするために、.ipynbチェックポイントに入り込んでいる可能性があるものを取り除きます。" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c458b2b8-174c-40ef-b34f-daa7f657ddbe", + "metadata": {}, + "outputs": [], + "source": [ + "! find . -type d -name \".ipynb_checkpoints\" -exec rm -rf {} +\n" + ] + }, + { + "cell_type": "markdown", + "id": "984b323e-4cb6-4ab8-8223-8d36d01fa85a", + "metadata": {}, + "source": [ + "NVIDIA NIM dockerコンテナを実行します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee02f36c-da74-4e51-9dda-e1a86cd59961", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['LORA_NIM_DOCKER']=\"nim_llm_with_lora\"\n", + "! docker run -it -d --rm --gpus '\"device=0\"' --shm-size=16GB --name=$LORA_NIM_DOCKER -e NGC_API_KEY -e NIM_PEFT_SOURCE -v $LOCAL_PEFT_DIRECTORY:$NIM_PEFT_SOURCE -v $LOCAL_NIM_CACHE:/opt/nim/.cache -u $(id -u) -p $CONTAINER_PORT:8000 nvcr.io/nim/meta/llama3-8b-instruct:1.0.0\n", + "\n", + "# ローカルのNIMコンテナが完全にロードされ、保留状態にならないように、一定時間待ちます。(5分以上かかるケースもあり)\n", + "! sleep 60" + ] + }, + { + "cell_type": "markdown", + "id": "9a6a6774-51e1-4912-8e02-e6a1a688e09f", + "metadata": {}, + "source": [ + "dockerログでNVIDIA NIMが動作していることを確認します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "235bd541-f995-4359-9af9-ea390f289e33", + "metadata": {}, + "outputs": [], + "source": [ + "! docker logs --tail 45 $LORA_NIM_DOCKER" + ] + }, + { + "cell_type": "markdown", + "id": "cfb262fe-aa00-42a7-95cd-7709677f405f", + "metadata": {}, + "source": [ + "**想定される出力**: \n", + "\n", + "```\n", + "INFO 09-10 12:17:48.149 api_server.py:456] Serving endpoints:\n", + " 0.0.0.0:8000/openapi.json\n", + " 0.0.0.0:8000/docs\n", + " 0.0.0.0:8000/docs/oauth2-redirect\n", + " 0.0.0.0:8000/metrics\n", + " 0.0.0.0:8000/v1/health/ready\n", + " 0.0.0.0:8000/v1/health/live\n", + " 0.0.0.0:8000/v1/models\n", + " 0.0.0.0:8000/v1/version\n", + " 0.0.0.0:8000/v1/chat/completions\n", + " 0.0.0.0:8000/v1/completions\n", + "INFO 09-10 12:17:48.149 api_server.py:460] An example cURL request:\n", + "curl -X 'POST' \\\n", + " 'http://0.0.0.0:8000/v1/chat/completions' \\\n", + " -H 'accept: application/json' \\\n", + " -H 'Content-Type: application/json' \\\n", + " -d '{\n", + " \"model\": \"meta/llama3-8b-instruct\",\n", + " \"messages\": [\n", + " {\n", + " \"role\":\"user\",\n", + " \"content\":\"Hello! How are you?\"\n", + " },\n", + " {\n", + " \"role\":\"assistant\",\n", + " \"content\":\"Hi! I am quite well, how can I help you today?\"\n", + " },\n", + " {\n", + " \"role\":\"user\",\n", + " \"content\":\"Can you write me a song?\"\n", + " }\n", + " ],\n", + " \"top_p\": 1,\n", + " \"n\": 1,\n", + " \"max_tokens\": 15,\n", + " \"stream\": true,\n", + " \"frequency_penalty\": 1.0,\n", + " \"stop\": [\"hello\"]\n", + " }'\n", + "\n", + "INFO 09-10 12:17:48.196 server.py:82] Started server process [33]\n", + "INFO 09-10 12:17:48.197 on.py:48] Waiting for application startup.\n", + "INFO 09-10 12:17:48.224 on.py:62] Application startup complete.\n", + "INFO 09-10 12:17:48.225 server.py:214] Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "466f28a6-f6e6-48c8-9b41-ac3d19949be7", + "metadata": {}, + "source": [ + "### 推論の実行\n", + "\n", + "NIMsのtriton推論サーバーで指定された`/models`エンドポイントから、利用可能なモデルエンドポイントとホストされたLoRAを素早くチェックすることができます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0fbaf807-8f28-4ebd-a9ab-3644f7bd5017", + "metadata": {}, + "outputs": [], + "source": [ + "! curl -s http://0.0.0.0:${CONTAINER_PORT}/v1/models | jq" + ] + }, + { + "cell_type": "markdown", + "id": "01ded3cc-fbca-4a27-8f5a-e4f28c35702f", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "\n", + "```json\n", + "\n", + "{\n", + " \"object\": \"list\",\n", + " \"data\": [\n", + " {\n", + " \"id\": \"meta/llama3-8b-instruct\",\n", + " \"object\": \"model\",\n", + " \"created\": 1723751020,\n", + " \"owned_by\": \"system\",\n", + " \"root\": \"meta/llama3-8b-instruct\",\n", + " \"parent\": null,\n", + " \"permission\": [\n", + " {\n", + " \"id\": \"modelperm-0f38baa00db14455846b31342c8b4367\",\n", + " \"object\": \"model_permission\",\n", + " \"created\": 1723751020,\n", + " \"allow_create_engine\": false,\n", + " \"allow_sampling\": true,\n", + " \"allow_logprobs\": true,\n", + " \"allow_search_indices\": false,\n", + " \"allow_view\": true,\n", + " \"allow_fine_tuning\": false,\n", + " \"organization\": \"*\",\n", + " \"group\": null,\n", + " \"is_blocking\": false\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \"id\": \"llama3-8b-instruct-lora_vhf-math-v1\",\n", + " \"object\": \"model\",\n", + " \"created\": 1723751020,\n", + " \"owned_by\": \"system\",\n", + " \"root\": \"meta/llama3-8b-instruct\",\n", + " \"parent\": null,\n", + " \"permission\": [ \n", + " ... \n", + " ]\n", + "}\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "9d485906-8ced-4306-9b79-d743a4031eaa", + "metadata": {}, + "source": [ + "簡単な推論リクエストを実行します。" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "6ac9f7d6-c184-440a-9967-cd051665e7fc", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "\n", + "model = \"llama3-8b-instruct-lora_vhf-math-v1\"\n", + "llm = ChatNVIDIA(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']),model=model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c765fe2b-4a97-4ef1-9622-77658e73a6ef", + "metadata": {}, + "outputs": [], + "source": [ + "out = llm.invoke(\"7+9は?\")\n", + "\n", + "print(out)" + ] + }, + { + "cell_type": "markdown", + "id": "feab09bf-af73-43cc-9e61-4a78d89396ea", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "```python\n", + "content='7+9 = 16' response_metadata={'role': 'assistant', 'content': '7+9 = 16', 'token_usage': {'prompt_tokens': 15, 'total_tokens': 22, 'completion_tokens': 7}, 'finish_reason': 'stop', 'model_name': 'llama3-8b-instruct-lora_vhf-math-v1'} id='run-cab739a6-3055-4dec-ab56-61ec32f62a15-0' role='assistant'\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7b7dda3-f72a-43bb-8f5d-9d7ab70e8caf", + "metadata": {}, + "outputs": [], + "source": [ + "out = llm.invoke(\"$(3,7)$と$(5,1)$の中点の座標を求めよ。\")\n", + "\n", + "print(out.content)" + ] + }, + { + "cell_type": "markdown", + "id": "82005e6c-17cb-4878-b8fb-97d162360a3e", + "metadata": {}, + "source": [ + "実行中のdockerコンテナを停止します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bea97ef-ba81-4383-a8c4-8fa6a43dac8a", + "metadata": {}, + "outputs": [], + "source": [ + "!docker container stop $LORA_NIM_DOCKER " + ] + }, + { + "cell_type": "markdown", + "id": "12afb511-ca0f-4bba-96de-fd653f8a5bb0", + "metadata": {}, + "source": [ + "### カスタムLoRAアダプタの作成\n", + "\n", + "カスタムLoRAアダプターを使用するには、 custom_lora notebook を使用して構築する必要があります。プロセスが完了したら、以下のセルを実行して、カスタムLoRAアダプタをNIMでデプロイしてください。 最初のステップは、アダプタを `loras` ディレクトリにコピーすることです。" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "393fa191-940a-4df6-b260-0658107e03da", + "metadata": {}, + "outputs": [], + "source": [ + "!cp -r model/Llama-3-8b-instruct-hf-finetune loras " + ] + }, + { + "cell_type": "markdown", + "id": "1a581e7c-e2ef-4215-8e0a-1665c21e5bf0", + "metadata": {}, + "source": [ + "パーミッションの作成" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "3d496f44-5038-45dc-8770-98395e6f73a7", + "metadata": {}, + "outputs": [], + "source": [ + "!chmod -R 777 loras/" + ] + }, + { + "cell_type": "markdown", + "id": "998fcd43-ed4e-40cf-98b5-1d1123e41cbd", + "metadata": {}, + "source": [ + "NIM dockerコンテナを実行します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db469796-2e21-4a5c-888b-15484d0bf218", + "metadata": {}, + "outputs": [], + "source": [ + "! docker run -it --rm -d --gpus '\"device=0\"' --shm-size=16GB --name=$LORA_NIM_DOCKER -e NGC_API_KEY -e NIM_PEFT_SOURCE -v $LOCAL_PEFT_DIRECTORY:$NIM_PEFT_SOURCE -v $LOCAL_NIM_CACHE:/opt/nim/.cache -u $(id -u) -p $CONTAINER_PORT:8000 nvcr.io/nim/meta/llama3-8b-instruct:1.0.0\n", + "\n", + "! sleep 60 # ローカルのNIMコンテナが完全にロードされ、保留状態にならないように、一定時間待ちます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f32dbfb2-adea-480a-8a9a-063fd647653d", + "metadata": {}, + "outputs": [], + "source": [ + "! docker logs --tail 50 $(docker ps -q -l) # view the last logs from the container" + ] + }, + { + "cell_type": "markdown", + "id": "5b7936a5-f76e-4aa3-88d5-28da25930c13", + "metadata": {}, + "source": [ + "### カスタムLoRAアダプタによる推論の実行\n", + "\n", + "NIMsのtriton推論サーバ経由で指定された`/models`エンドポイントから、利用可能なモデルエンドポイントとホストされたLoRAを素早くチェックすることができます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18d04883-cdd5-4cc9-875e-6a5e18564104", + "metadata": {}, + "outputs": [], + "source": [ + "!curl -s http://0.0.0.0:${CONTAINER_PORT}/v1/models | jq" + ] + }, + { + "cell_type": "markdown", + "id": "bf8e9bd6-a665-4332-b65a-1e73670a88d1", + "metadata": {}, + "source": [ + "モデルが到達可能で、稼働していることを確認するテストを行います。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2bf7fc0-e094-4833-86fe-bd0ca2987af3", + "metadata": {}, + "outputs": [], + "source": [ + "!curl -X 'POST' \\\n", + " \"http://0.0.0.0:${CONTAINER_PORT}/v1/completions\" \\\n", + " -H 'accept: application/json' \\\n", + " -H 'Content-Type: application/json' \\\n", + " -d '{\"model\": \"Llama-3-8b-instruct-hf-finetune\", \\\n", + " \"prompt\": \"昔々あるところに\",\\\n", + " \"max_tokens\": 64 }'\n" + ] + }, + { + "cell_type": "markdown", + "id": "0df63395", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "```json\n", + "{\"id\":\"cmpl-6656aabe1b734521bbef5b2428978661\",\"object\":\"text_completion\",\"created\":1748858890,\"model\":\"Llama-3-8b-instruct-hf-finetune\",\"choices\":[{\"index\":0,\"text\":\"、ある百姓の家があったという話があります。彼の家には、妻と子供だけで、自分は町で働いていました。ある日、彼が帰宅すると、それまでの家が全く変わっており、彼の妻が新しいフローリングを作り、家の中を\",\"logprobs\":null,\"finish_reason\":\"length\",\"stop_reason\":null}],\"usage\":{\"prompt_tokens\":7,\"total_tokens\":71,\"completion_tokens\":64}}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c8bd4e4f-c190-477d-8752-b286e986e0cf", + "metadata": {}, + "source": [ + "簡単な推論リクエストを実行します。" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "67b118b8-4310-4b85-b80a-18554f45be56", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "\n", + "model = \"Llama-3-8b-instruct-hf-finetune\" # カスタムLoRAアダプタを使用\n", + "\n", + "llm = ChatNVIDIA(base_url=\"http://0.0.0.0:{}/v1\".format(os.environ['CONTAINER_PORT']),model=model, temperature=0.6, max_new_tokens=128, top_p=0.4, frequency_penalty=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41c62e56", + "metadata": {}, + "outputs": [], + "source": [ + "for tok in llm.stream(\"仙台を舞台にした短い物語を書いて\"):\n", + " print(tok.content, end=\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "e8529976", + "metadata": {}, + "source": [ + "**想定される出力:**\n", + "```\n", + "もちろんです。以下に、仙台を舞台にした短い物語を書きます。\n", + "\n", + "**「雪の夜の散歩」**\n", + "\n", + "冬の夜、仙台の中心部を歩く。雪が降り、街並みは静かに輝きます。私は、友人と約束した場所に向かう途中です。彼は、仙台城址の近くにある喫茶店で待っています。\n", + "\n", + "歩くにつれて、雪の音が増し、心地よいリズムを刻むようになります。雪が降る様子を眺める人々も、街の雰囲気をさらに豊かにしています。\n", + "\n", + "ようやく喫茶店に到着し、友人と合流します。暖かいコーヒーを飲みながら、雪の夜の仙台の美しさを語り合います。雪が降る音と、喫茶店の内側の暖かさの対比が、心を温めます。\n", + "\n", + "その後、雪を踏みながら、仙台城址を訪れます。雪が降る中、城の石垣は白く染まっています。雪の夜の仙台城は、歴史と自然の調和が美しく表現されています。\n", + "\n", + "夜が深くなり、雪が止むと、友人と共に仙台の夜景を楽しむことになります。雪が降る中、仙台の夜景はさらに魅力的なものになります。\n", + "\n", + "この雪の夜の散歩は、仙台の美しさと、友情の深さを感じさせる素晴らしい経験となりました。雪の夜の仙台は、心を温め、記憶に残るものです。\n", + "\n", + "\n", + "以上が、仙台を舞台にした短い物語です。希望の通りでしたか?何か変更や追加が必要であれば、教えてください。ご活用いただければと思います。\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "id": "326fca6c", + "metadata": {}, + "source": [ + "実行中のdockerコンテナを停止します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dae53369", + "metadata": {}, + "outputs": [], + "source": [ + "!docker container stop $LORA_NIM_DOCKER " + ] + }, + { + "cell_type": "markdown", + "id": "06457663-b5cc-4c9a-af46-7dd7e4fd765f", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 参照\n", + "\n", + "- https://docs.nvidia.com/nim/large-language-models/latest/peft.html\n", + "\n", + "## Licensing\n", + "\n", + "Copyright © 2024 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + }, + { + "cell_type": "markdown", + "id": "58d20729-200c-4e2a-88f9-4c700c57f43f", + "metadata": {}, + "source": [ + "
\n", + "
\n", + " Previous Notebook\n", + " \n", + " 1\n", + " 2\n", + " 3\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "
\n", + "

Home Page

" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00be7543-f603-44ca-ad41-2a067e413313", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workspace-nim-with-rag-jp/jupyter_notebook/rag_nim_endpoints.ipynb b/workspace-nim-with-rag-jp/jupyter_notebook/rag_nim_endpoints.ipynb new file mode 100644 index 0000000..d6f218f --- /dev/null +++ b/workspace-nim-with-rag-jp/jupyter_notebook/rag_nim_endpoints.ipynb @@ -0,0 +1,947 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4cf5bac5-4f3c-4973-95e9-11900a25b6c6", + "metadata": {}, + "source": [ + "

Home Page

\n", + "\n", + "
\n", + " \n", + " 1\n", + " 2\n", + " 3\n", + " \n", + " \n", + " Next Notebook\n", + "
\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "6e03e664-771b-47f8-ae4b-5de29f5831d5", + "metadata": {}, + "source": [ + "# NVIDIA NIM APIを利用したRAG構築\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "674e3116-4ecc-473d-a567-545ad2836a36", + "metadata": {}, + "source": [ + "このノートブックでは、NVIDIA API Keyの設定方法を説明し、NVIDIA Inference Microservices(NIM)モデルへの簡単なリクエストのリモート送信方法を実演します。次に、RAG(検索拡張生成)アプローチとLangChainフレームワークについて簡単に説明し、最後にNVIDIA Endpointsを使った簡単なRAGアプリケーションのデモを行います。 このノートブックの最後まで学習することで以下の事を学ぶことが可能です:\n", + "- NIMモデルにアクセスするためのNVIDIA APIキーの設定方法\n", + "- RAGのコンセプトの理解\n", + "- ウェブリンクからコンテンツを取得し、コンテンツをテキストチャンクに分割し、エンベッディングを作成し、ベクターストアでローカルに保存する方法\n", + "- ベクトルストアから埋め込みモデルをロードし、NVIDIA Endpointsを使用してRAGを構築する概念\n" + ] + }, + { + "cell_type": "markdown", + "id": "82455b45-ff67-4fa8-8f99-082df438d434", + "metadata": {}, + "source": [ + "## さあ始めましょう\n", + "\n", + "NVIDIA NIMは、[NVIDIA API Catalog](https://build.nvidia.com/explore/discover)で利用可能な、使いやすいオープンAPIを介して素早くアクセスできます。これは、オンラインで幅広いマイクロサービスにアクセスするためのプラットフォームです。NVIDIA NIMを使い始めるには、登録が必要な`NVIDIA API Key`が必要です。以下のスクリーンショットに示すように、`ログインボタンをクリックしてメールアドレスを入力し`、プロセスに従って残りの情報登録するか、[NVIDIA NGC](https://ngc.nvidia.com/signin)の登録(*アカウント名をクリック -> セットアップ -> パーソナルキーの生成*)からAPIキーを生成してください。プロセスが完了したら、API Keyを将来の使用のためにアクセスできる場所に保存してください。APIキーのサンプルは、`nvapi-`で始まり、アンダースコア `_` を含む64文字です。\n", + "\n", + "すでにアカウントをお持ちの方は、この手順に従って`NVIDIA API KEY`を取得してください:\n", + "\n", + "- [ここ](https://build.nvidia.com/explore/discover)からあなたのアカウントでログイン.\n", + "- ご希望のモデルをクリックしてください。\n", + "- [Input]で[Python]タブを選択し、[Get API Key]をクリックし、[Generate Key]をクリックします。\n", + "- 生成されたキーをコピーし、NVIDIA_API_KEYとして保存します。そこからエンドポイントにアクセスできるはずです。\n", + "\n", + "
\n", + " \n", + " \n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "id": "3c03482e-a17c-44f9-8903-5da47d5a0928", + "metadata": {}, + "source": [ + "## NVIDIA API キーのセットアップ\n", + "\n", + "NGC環境の外でNVIDIA NIMにアクセスしたいので、API Keyを環境変数として設定し、それを使ってNIMモデルにリクエストを送信してテストすることが重要です。以下の2つのセルを実行してテストしてください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ae82dd9-8dec-477b-a59d-bfa774e1f972", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import getpass\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "if not os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " nvapi_key = getpass.getpass(\"Enter your NVIDIA API key: \")\n", + " assert nvapi_key.startswith(\"nvapi-\"), f\"{nvapi_key[:5]}... is not a valid key\"\n", + " os.environ[\"NVIDIA_API_KEY\"] = nvapi_key\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f807facb-483d-4761-b1bd-2d21967f2db1", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import json\n", + "\n", + "url = \"https://integrate.api.nvidia.com/v1/chat/completions\"\n", + "headers = {\n", + " \"Content-Type\": \"application/json\",\n", + " \"Authorization\": \"Bearer {}\".format(os.environ[\"NVIDIA_API_KEY\"])\n", + "}\n", + "data = {\n", + " \"model\": \"mistralai/mistral-7b-instruct-v0.3\",\n", + " \"messages\": [{\"role\": \"user\", \"content\": \"NVIDIA はいつ設立され、CEO は誰ですか?\"}],\n", + " \"temperature\": 0.5,\n", + " \"top_p\": 0.7,\n", + " \"max_tokens\": 1024,\n", + " \"stream\": False\n", + "}\n", + "\n", + "response = requests.post(url, headers=headers, data=json.dumps(data))\n", + "\n", + "print(response.json()['choices'][0]['message']['content'])" + ] + }, + { + "cell_type": "markdown", + "id": "2e676e37-ba43-460d-9ed3-f2e12648dd68", + "metadata": {}, + "source": [ + "## RAG(検索拡張生成)\n", + "\n", + "### RAGとは?\n", + "\n", + "RAG(検索拡張生成)は、外部ソースから取得した事実を用いて生成AIモデルの精度と信頼性を向上させる技術です。\n", + "事前に学習された大規模な言語モデル(LLM)は、そのパラメータに事実知識を格納することが示されており、ダウンストリームの自然言語処理タスクでファインチューニングを行うと、最先端の結果を達成します。しかし、知識にアクセスして正確に操作する能力はまだ限定的であるため、知識集約的なタスクでは、タスクに特化したアーキテクチャに比べて性能が劣ります。さらに、その決定に実証性を与え、世界知識を更新することは、依然として未解決の研究課題です。詳しくは[こちら](https://arxiv.org/pdf/2005.11401)をご確認下さい。 \n", + " \n", + "\n", + "#### RAGはどのように役立つのか?\n", + "RAGは、大規模言語モデル(LLM)の強力なセマンティック機能と、メタデータ、テキスト、画像、ビデオ、表、グラフなどを含むデータセットとの接続を支援するアーキテクチャです。\n", + "\n", + "
\n", + " \"RAG
\n", + " RAG Architecture\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "02b96b49-f98a-4b7d-a5fd-9ee7e06f78eb", + "metadata": {}, + "source": [ + "### RAG コンポーネント\n", + "\n", + "RAGアプリケーションは`Retriever` と ` Generator`の2つの主要コンポーネントから構成されています: . `Retriever` (RET) コンポーネントは、ユーザからの問い合わせに応答して、保存されているデータセットから適切な情報を抽出します。続いて、` Generator`(GEN)コンポーネントは、ユーザーからの問い合わせと検索された情報を利用して応答を生成します。\n", + "\n", + "RAGシステムのアーキテクチャは、主に以下の要素で構成されています:\n", + "- **RET**\n", + " - 埋め込みモデル: 入力データを密なベクトル表現に変換するバイエンコーダーネットワーク\n", + " - ベクトルストア: 高次元ベクトル埋め込みを格納し、クエリするために最適化されたデータベース.\n", + " - *リ*-ランキング (オプショナル): クエリとの関連性に基づいて検索された情報に優先順位を付けるクロスエンコーダ\n", + "\n", + "- **GEN**\n", + " - 大規模言語モデル (LLM): 膨大なテキストデータで訓練されたニューラルネットワークで、人間のような応答を生成することができます。\n", + " - ガードレール (オプショナル): 生成された出力が事前に定義された制約や品質基準に準拠していることを保証するために実装されたメカニズムです。\n", + "\n", + "堅牢なRAGアプリケーションには、以下に関して優れている必要があります:\n", + "- `Retriever`パイプライン\n", + "- `Generator`モデル\n", + "- 両者のリンク\n", + "\n", + "ラボでは、これらのコンポーネントを1つずつインスタンス化し、それらをまとめます。" + ] + }, + { + "cell_type": "markdown", + "id": "a4b81b10-a536-4df7-9f38-8637478b2385", + "metadata": {}, + "source": [ + "## LangChainの概要\n", + "\n", + "[LangChain](https://python.langchain.com/v0.2/docs/introduction/)は、大規模言語モデル(LLM)を使用するアプリケーションの開発を簡素化するために設計された強力なフレームワークです。\n", + "RAGアプリケーションのために、LangChainは以下を提供します:\n", + "\n", + "- 様々なファイルタイプのドキュメントローダー\n", + "- ドキュメントを分割するテキスト分割機能\n", + "- テキストをベクトル表現に変換する埋め込み\n", + "- 効率的な類似検索のためのベクトルストア\n", + "- 関連情報を取得するための`Retriever`メソッド\n", + "- クエリと応答を構造化するプロンプトテンプレート" + ] + }, + { + "cell_type": "markdown", + "id": "6294cd89-cd31-40a9-8a28-af54052ecbaa", + "metadata": {}, + "source": [ + "### LLM AI Endpoints\n", + "\n", + "`langchain-nvidia-ai-endpoints`パッケージには、NVIDIA NIM推論マイクロサービス上のモデルでアプリケーションを構築するLangChain統合が含まれています。[ChatNVIDIA](https://python.langchain.com/v0.2/docs/integrations/chat/nvidia_ai_endpoints/)クラスは `langchain_nvidia_ai_endpoints` の一部で、チャットアプリケーションのためのNVIDIA NIMへのアクセスや、ホストまたはローカルにデプロイされたマイクロサービスへの接続を可能にします。\n", + "以下では、`institute-of-science-tokyo/llama-3.1-swallow-8b-instruct`を使った例を示します。各モデルはカタログにあるモデル名キーで一意に識別されます(例:meta/llama3-8b-instruct、meta/llama-3.1-70b-instruct)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1db88f4d-1830-490f-9942-090261ed6dc6", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "\n", + "model = \"institute-of-science-tokyo/llama-3.1-swallow-8b-instruct-v0.1\"\n", + "llm = ChatNVIDIA(model=model, max_tokens=1024)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bcc4e4c2-2663-4d43-8c0e-b2e78c6a5581", + "metadata": {}, + "outputs": [], + "source": [ + "result = llm.invoke(\"RAGとは何ですか?\")\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "c0bafe2a-ce4f-4ae5-b5d9-7f4d624ea928", + "metadata": { + "tags": [] + }, + "source": [ + "**予想される出力**:\n", + "```\n", + "AIMessage(content='RAGとは、Risk Assessment and Governance(リスク評価とガバナンス)の略称であり、一般的に、組織におけるリスク管理とガバナンスのプロセスを指します。組織は、RAGを通じて、ビジネス、財務、運用などに関するリスクを特定、評価、優先順位付けし、適切な対策を講じることで、リスクを管理します。', additional_kwargs={}, response_metadata={'role': 'assistant', 'content': 'RAGとは、Risk Assessment and Governance(リスク評価とガバナンス)の略称であり、一般的に、組織におけるリスク管理とガバナンスのプロセスを指します。組織は、RAGを通じて、ビジネス、財務、運用などに関するリスクを特定、評価、優先順位付けし、適切な対策を講じることで、リスクを管理します。', 'token_usage': {'prompt_tokens': 16, 'total_tokens': 115, 'completion_tokens': 99}, 'finish_reason': 'stop', 'model_name': 'institute-of-science-tokyo/llama-3.1-swallow-8b-instruct-v0.1'}, id='run--28eba379-a97b-4cba-9b09-6e9905ab79f8-0', usage_metadata={'input_tokens': 16, 'output_tokens': 99, 'total_tokens': 115}, role='assistant')\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "id": "24535db0-1ee6-47f3-b21d-bdfab72afcc7", + "metadata": {}, + "source": [ + "以下のセルを実行すると、利用可能なモデルのリストにアクセスできます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9a8ba5f-5df7-49fe-abc9-99dd357c42e5", + "metadata": {}, + "outputs": [], + "source": [ + "ChatNVIDIA.get_available_models()" + ] + }, + { + "cell_type": "markdown", + "id": "2b105ab2-4b2f-42e9-994c-cf5ad2c4aae8", + "metadata": {}, + "source": [ + "#### 回答内容の分析方法 \n", + "\n", + "\n", + "前のセルからの出力成分を解読してみましょう。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33ccce95-fade-4dc8-86b6-6386e1b771eb", + "metadata": {}, + "outputs": [], + "source": [ + "import pprint\n", + "from IPython.display import display, Markdown, Latex" + ] + }, + { + "cell_type": "markdown", + "id": "6e001a79-6d39-4d56-9cde-502f5cdbc0f8", + "metadata": {}, + "source": [ + "主な応答内容を表示します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40ffa29d-cef5-4b12-84b7-033d9212242f", + "metadata": {}, + "outputs": [], + "source": [ + "display(Markdown(result.content))" + ] + }, + { + "cell_type": "markdown", + "id": "226804f9-0949-45ab-b0d7-06f9f78672a5", + "metadata": {}, + "source": [ + "`response_metadata` はその他の重要な詳細を示します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cfb57f0a-ae1f-453d-9ac1-2ca3347f5cfe", + "metadata": {}, + "outputs": [], + "source": [ + "result.response_metadata" + ] + }, + { + "cell_type": "markdown", + "id": "0f49116f-050a-4d18-b4f9-1c0d84c5ac89", + "metadata": {}, + "source": [ + "#### LLM 応答の Dictionary Keys\n", + "\n", + "- `role`: レスポンダを識別する (例: 'assistant')\n", + "- `content`: AI からの実際のテキストレスポンス\n", + "- `token_usage`:\n", + " - `prompt_tokens`: 入力に含まれるトークンの数\n", + " - `total_tokens`: インタラクションで使われたトークンの総数\n", + " - `completion_tokens`: AI のレスポンスに含まれるトークン数\n", + "- `finish_reason`: AIがテキスト生成を停止した理由\n", + "- `model_name`: 使用するAIモデルを指定" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64b29776-1f80-47aa-b42d-3251c7805289", + "metadata": {}, + "outputs": [], + "source": [ + "# Let's try asking a followup question\n", + "result = llm.invoke(\"それはどう便利なのですか?\")\n", + "display(Markdown(result.content))" + ] + }, + { + "cell_type": "markdown", + "id": "ff43bdc6-1b12-43a6-a471-47f69050a3f6", + "metadata": {}, + "source": [ + "**注意:** LLMのステートレスな振る舞いを確認してみましょう。したがって、関連するすべてのコンテキスト、背景、クエリーを単一のプロンプトで渡すことが重要です。\n" + ] + }, + { + "cell_type": "markdown", + "id": "c6169d39-3904-4fcb-8367-6c9aca8248cb", + "metadata": {}, + "source": [ + "## RAG アプリケーション\n", + "\n", + "このセクションでは、NIMを使って簡単なRAGアプリケーションを作成します。このアプリケーションは、ウェブリンクからデータソースを受け取り、コンテンツを文書として読み込み、文書を分割し、埋め込みをインスタンス化し、ベクトルデータベースに格納します。さらに、要約用とチャット用の2つのLLMを使って[会話検索チェーン](https://python.langchain.com/v0.1/docs/modules/chains/#conversationalretrievalchain-with-streaming-to-stdout)を作成する。組み合わせたLLMの重要性は、複雑なシナリオにおいて全体的な結果を向上させることを支援することです。最後に、関連するクエリープロンプトを生成するための質問ジェネレーターを追加します。以下にその手順を示します:\n", + "\n", + "- 必要なライブラリをすべてインポート\n", + "- ウェブリンクのデータソースを作成 \n", + "- ウェブリンクからHTMLファイルをロードする関数を作成\n", + "- embeddingsとdocument text splitterの作成\n", + "- LangChainからNVIDIA AI Endpointを使って埋め込みを生成し、オフラインのvector storeに保存\n", + "- vector storeから埋め込みをロードし、NVIDIA Endpointを使用してRAGを構築\n", + "- 2つのLLMを使って会話型検索チェーンを作る\n", + "- 質問ジェネレーターを追加して、関連するクエリー・プロンプトを生成する\n" + ] + }, + { + "cell_type": "markdown", + "id": "7f1ad9c5-d54e-4612-9901-8a7ea6570232", + "metadata": {}, + "source": [ + "#### ライブラリのインポート" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2aa534fc-6b56-4d62-8a3a-16ddf3368054", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import ConversationalRetrievalChain, LLMChain\n", + "from langchain.chains.conversational_retrieval.prompts import CONDENSE_QUESTION_PROMPT, QA_PROMPT\n", + "from langchain.chains.question_answering import load_qa_chain\n", + "from langchain.vectorstores import FAISS\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n", + "from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings" + ] + }, + { + "cell_type": "markdown", + "id": "8e3b87b3-7777-48df-87be-65a174ea81f8", + "metadata": {}, + "source": [ + "#### ウェブリンク・データソースの作成\n", + "\n", + "お好きなウェブリンクを差し替えたり、追加したりできます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ed60ac5-3fb1-4766-891b-9bf22c445ee8", + "metadata": {}, + "outputs": [], + "source": [ + "urls = [\n", + " \"https://www.nvidia.com/ja-jp/glossary/retrieval-augmented-generation/\",\n", + " \"https://ja.wikipedia.org/wiki/%E5%A4%A7%E8%A6%8F%E6%A8%A1%E8%A8%80%E8%AA%9E%E3%83%A2%E3%83%87%E3%83%AB\",\n", + " \"https://docs.nvidia.com/cuda/\",\n", + " \"https://github.com/NVIDIA/cuda-samples\"\n", + " ]" + ] + }, + { + "cell_type": "markdown", + "id": "147e0ade-860a-486d-88ad-4ed0a696a38c", + "metadata": {}, + "source": [ + "#### HTMLファイルを読み込む関数を作成する\n", + "\n", + "\n", + "以下は、埋め込みデータを生成するために使用する、HTMLファイルを読み込むためのヘルパー関数です。この関数は、HTMLを解析するための[Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)オブジェクトを作成することで、URLデータソースから関連するHTMLドキュメントを読み込みます。Beautiful SoupはHTMLやXMLファイルからデータを取り出すためのPythonライブラリです。オブジェクトを通してHTMLスクリプトとスタイルタグを削除し、HTMLドキュメントからプレーンテキストを取得することができます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96e8db4f", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "import requests\n", + "from bs4 import BeautifulSoup\n", + "from typing import List, Union\n", + "\n", + "def html_document_loader(url: Union[str, bytes]) -> str:\n", + " \"\"\"\n", + " Loads the HTML content of a document from a given URL and return it's content.\n", + "\n", + " Args:\n", + " url: The URL of the document.\n", + "\n", + " Returns:\n", + " The content of the document.\n", + "\n", + " Raises:\n", + " Exception: If there is an error while making the HTTP request.\n", + "\n", + " \"\"\"\n", + " try:\n", + " response = requests.get(url)\n", + " html_content = response.text\n", + " except Exception as e:\n", + " print(f\"Failed to load {url} due to exception {e}\")\n", + " return \"\"\n", + "\n", + " try:\n", + " # Create a Beautiful Soup object to parse html\n", + " soup = BeautifulSoup(html_content, \"html.parser\")\n", + "\n", + " # Remove script and style tags\n", + " for script in soup([\"script\", \"style\"]):\n", + " script.extract()\n", + "\n", + " # Get the plain text from the HTML document\n", + " text = soup.get_text()\n", + "\n", + " # Remove excess whitespace and newlines\n", + " text = re.sub(\"\\s+\", \" \", text).strip()\n", + "\n", + " return text\n", + " except Exception as e:\n", + " print(f\"Exception {e} while loading document\")\n", + " return \"\"" + ] + }, + { + "cell_type": "markdown", + "id": "88060038-3fd9-4a6e-aa91-38e1211022a2", + "metadata": {}, + "source": [ + "#### 埋め込みとDocument Text Splitterの作成\n", + "\n", + "埋め込みを保存するパスを初期化し、`html_document_loader`関数を実行し、ドキュメントをテキストの塊に分割する関数を作ってみましょう。しかし、関数の中で考慮しなければならないことがあります。\n", + "\n", + "- **主な検討事項** \n", + " - [TextSplitter](https://python.langchain.com/v0.1/docs/modules/data_connection/document_transformers/recursive_text_splitter/)のchunk_sizeパラメータに注意してください。 \n", + " - RAGの成功例の多くは、検索ステップが生成のための正しいコンテキストを見つけることに基づいている為です。\n", + " - プロンプト全体(検索されたチャンク+ユーザークエリー)は、 [LLMのコンテキストウィンドウ](https://www.appen.com/blog/understanding-large-language-models-context-windows)内に収まらなければなりません。したがって、大きすぎるチャンクサイズを指定せず、推定クエリーサイズとのバランスをとる必要があります。 \n", + " - 例えば、OpenAIのLLMのコンテキストウィンドウは8k-32kトークンですが、Llama3は8kトークンに制限されています。 \n", + " - LLMによって異なりますが、典型的な値は100-600です。\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a86faeb-3c4d-4219-8f7d-001cdb370c89", + "metadata": {}, + "outputs": [], + "source": [ + "def create_embeddings(embeddings_model,embedding_path: str = \"./embed\"):\n", + "\n", + " print(f\"Storing embeddings to {embedding_path}\")\n", + "\n", + " documents = []\n", + " for url in urls:\n", + " document = html_document_loader(url)\n", + " documents.append(document)\n", + "\n", + "\n", + " text_splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=500,\n", + " chunk_overlap=0,\n", + " length_function=len,\n", + " )\n", + " print(\"Total documents:\",len(documents))\n", + " texts = text_splitter.create_documents(documents)\n", + " print(\"Total texts:\",len(texts))\n", + " index_docs(embeddings_model,url, text_splitter, texts, embedding_path,)\n", + " print(\"Generated embedding successfully\")" + ] + }, + { + "cell_type": "markdown", + "id": "b53b72c8-f4cb-4e66-aa98-3e63109426b0", + "metadata": {}, + "source": [ + "#### LangChainからNVIDIA AI Endpointを使用して埋め込みを生成する\n", + "\n", + "このセクションでは、LangChain用のNVIDIA AI Endpointsを使ってエンベッディングを生成し、将来の再利用のためにエンベッディングを`/api_embed`ディレクトリのオフラインベクタストアに保存する方法をデモします。NVIDIA Retrieval QA Embedding エンドポイントを使用して埋め込みモデルを作成します。このモデルは、単語、フレーズ、またはその他のエンティティを数値のベクトルとして表現し、単語とフレーズ間の関係を理解します。以下は、Nvidiaカタログ上の主要な埋め込みモデルです:\n", + "\n", + "* `NV-EmbedQA-E5-v5`: テキストの質問応答検索に最適化された埋め込みモデル\n", + "* `NV-EmbedQA-Mistral7B-v2`: テキストの埋め込みと正確な質問応答用に微調整された多言語モデル\n", + "* `Snowflake-Arctic-Embed-L`: テキスト埋め込みに最適化されたモデル\n", + "\n", + "
\n", + " \"RAG
\n", + " Embedding's architecture\n", + "
\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75410126-b4b2-4cec-8427-c28b6794a58d", + "metadata": {}, + "outputs": [], + "source": [ + "embeddings_model = NVIDIAEmbeddings(model=\"nvidia/nv-embedqa-e5-v5\") # or use NV-Embed-QA" + ] + }, + { + "cell_type": "markdown", + "id": "f454defa-d78a-4f0e-acd3-8e5cde36f26c", + "metadata": {}, + "source": [ + "以下のフローチャートは、埋め込みモデル作成後の次のステップを説明しています:\n", + "
\n", + " \"RAG
\n", + " Embedding's architecture\n", + "
\n", + "
\n", + "\n", + "以下では、文書ページのコンテンツをループしてテキストとメタデータを拡張し、[FAISS](https://faiss.ai/index.html)を適用するindex_docs関数を作成する。埋め込みはローカルに保存されます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc0ca1c2-d7aa-45f1-be88-8b396eb67056", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Union\n", + "import os\n", + "from langchain.vectorstores import FAISS\n", + "\n", + "def index_docs(embeddings_model, url: Union[str, bytes], splitter, documents: List[str], dest_embed_dir: str) -> None:\n", + " \"\"\"\n", + " Split the documents into chunks and create embeddings for them.\n", + " \n", + " Args:\n", + " embeddings_model: Model used for creating embeddings.\n", + " url: Source url for the documents.\n", + " splitter: Splitter used to split the documents.\n", + " documents: List of documents whose embeddings need to be created.\n", + " dest_embed_dir: Destination directory for embeddings.\n", + " \"\"\"\n", + " texts = []\n", + " metadatas = []\n", + "\n", + " for document in documents:\n", + " chunk_texts = splitter.split_text(document.page_content)\n", + " texts.extend(chunk_texts)\n", + " metadatas.extend([document.metadata] * len(chunk_texts))\n", + "\n", + " if os.path.exists(dest_embed_dir):\n", + " docsearch = FAISS.load_local(\n", + " folder_path=dest_embed_dir, \n", + " embeddings=embeddings_model, \n", + " allow_dangerous_deserialization=True\n", + " )\n", + " docsearch.add_texts(texts, metadatas=metadatas)\n", + " else:\n", + " docsearch = FAISS.from_texts(texts, embedding=embeddings_model, metadatas=metadatas)\n", + "\n", + " docsearch.save_local(folder_path=dest_embed_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "2268c4cf-a970-40c1-a57e-0d30ae06aa1d", + "metadata": {}, + "source": [ + "#### ベクターストアからの埋め込みのロードとNVIDIA Endpointを使用したRAGの構築\n", + "\n", + "次に、関数 `create_embeddings` を呼び出し、FAISSを使って[vector store](https://developer.nvidia.com/blog/accelerating-vector-search-fine-tuning-gpu-index-algorithms/)から文書を読み込みます。ベクトルストアは埋め込みと呼ばれる高次元空間に関連情報を格納します。また、推論時にクエリに最も関連する文書を検索するための接続点としても機能します。情報検索には複数のアルゴリズムアプローチがあり、[こちら](https://developer.nvidia.com/blog/accelerating-vector-search-fine-tuning-gpu-index-algorithms/)を参照できます。\n", + "\n", + "以下の2つのセルを実行してください。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a3c81fb-e08d-4683-a0b2-b343865c2c24", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "create_embeddings(embeddings_model=embeddings_model, embedding_path=\"./api_embed\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d0fdeef-c8c1-4dca-b9a4-b5b9ce1d6043", + "metadata": {}, + "outputs": [], + "source": [ + "# load Embed documents\n", + "embedding_path = \"./api_embed/\"\n", + "docsearch = FAISS.load_local(folder_path=embedding_path, embeddings=embeddings_model, allow_dangerous_deserialization=True)" + ] + }, + { + "cell_type": "markdown", + "id": "e0dd4c2e-5626-4add-877f-648e7fe1abcb", + "metadata": {}, + "source": [ + "#### 2つのLLMを使った会話型検索チェーンの作成\n", + "\n", + "NIMはスタンドアロンサービスとして展開されるので、複数のNIMを異なるコンポーネントに利用することができます。ここでは、2つの異なるLLMを使ったチェーンを紹介します: \n", + "- `llama-3-swallow-70b-instruct-v0.1` は現在のメッセージの前の会話を要約します。\n", + "- `llama-3.1-swallow-8b-Instruct-v0.1`はチャットメッセージに応答するためのものです。\n", + "\n", + "それぞれのモデルには強みがあるので、個々のNIMをその分野に割り当てることができます。例えば、大きなコンテキストウィンドウを持つモデルは会話の履歴と要約に、関数呼び出し機能を持つ別のモデルはタスクの実行に使うことができます。\n", + "\n", + "以下のセルを実行してチェーンを実行してください。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9692f40c-5472-4aec-be07-d6eb35996dfc", + "metadata": {}, + "outputs": [], + "source": [ + "# load our models\n", + "summary_llm = ChatNVIDIA(model=\"tokyotech-llm/llama-3-swallow-70b-instruct-v0.1\")\n", + "chat_llm = ChatNVIDIA(model=\"institute-of-science-tokyo/llama-3.1-swallow-8b-instruct-v0.1\",\n", + " temperature=0.1,\n", + " max_tokens=1000,\n", + " top_p=1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3be27774-27ce-4772-b629-ec88bcc6dc16", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chains import (\n", + "create_history_aware_retriever,\n", + "create_retrieval_chain)\n", + "\n", + "from langchain.chains.combine_documents import create_stuff_documents_chain\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.chat_history import BaseChatMessageHistory\n", + "from langchain_core.runnables.history import RunnableWithMessageHistory\n", + "from langchain_community.chat_message_histories import ChatMessageHistory\n" + ] + }, + { + "cell_type": "markdown", + "id": "64aac5b8-492c-4b82-be78-8b0fcf7428c7", + "metadata": {}, + "source": [ + "\n", + "カンバセーショナル・チェーンでは、レスポンスを、セッションの過去のクエリに関連した実用的なものにする必要があります。そのために、過去の会話状況の補完をLLMに認識させるプロンプト・テンプレートを定義します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "453ff919-49f9-455a-bbf5-2b2f44d09efc", + "metadata": {}, + "outputs": [], + "source": [ + "#Setup Message History format and retriever\n", + "contextualize_q_system_prompt = \"\"\"チャット履歴と、履歴の文脈を参照している可能性のある最新のユーザーの質問が与えられたとき、 \\\n", + "その履歴がなくても意味が通じるように質問を単独の形で書き直してください。質問に答えてはいけません。もし書き直す必要がなければ、そのままの形で返してください。\"\"\"\n", + "contextualize_q_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", contextualize_q_system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")\n", + "\n", + "#Setup Agent Behaviour\n", + "qa_system_prompt = \"\"\"あなたは質問応答タスク用のアシスタントです。\\\n", + "取得された以下の文脈情報を使って質問に答えてください。\\\n", + "回答は最大3文以内で簡潔にしてください。\\\n", + "\n", + "{context}\"\"\"\n", + "qa_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", qa_system_prompt),\n", + " MessagesPlaceholder(\"chat_history\"),\n", + " (\"human\", \"{input}\"),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "da64fd63-a7f8-4c10-a741-7ff372b7bb39", + "metadata": {}, + "source": [ + "Retrieverを作成し、チェーンを確立します。\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf1e2500-2132-4ec3-8c73-e8ee3c4e21b2", + "metadata": {}, + "outputs": [], + "source": [ + "history_aware_retriever = create_history_aware_retriever(\n", + " summary_llm, \n", + " docsearch.as_retriever(), # the vectorstore serves as the junction to retrieve documents with highest similarity to the query.\n", + " contextualize_q_prompt\n", + ")\n", + "question_answer_chain = create_stuff_documents_chain(chat_llm, qa_prompt)\n", + "rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae921a8c-3f4a-4b1a-a2ea-b7c5abaa8c80", + "metadata": {}, + "outputs": [], + "source": [ + "# sample session creation\n", + "store = {}\n", + "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n", + " if session_id not in store:\n", + " store[session_id] = ChatMessageHistory()\n", + " return store[session_id]\n", + "\n", + "\n", + "conversational_rag_chain = RunnableWithMessageHistory(\n", + " rag_chain,\n", + " get_session_history,\n", + " input_messages_key=\"input\",\n", + " history_messages_key=\"chat_history\",\n", + " output_messages_key=\"answer\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ed552114-cdaf-4d42-b6d7-0b22c48ecfbf", + "metadata": {}, + "source": [ + "### クエリを使ったテスト" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2331f97f-9dfb-4b59-be5e-4816f0136547", + "metadata": {}, + "outputs": [], + "source": [ + "conversational_rag_chain.invoke(\n", + " {\"input\": \"RAGとは何ですか?\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"abc123\"}\n", + " }\n", + ")[\"answer\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e0b8691-129b-43b6-9846-ec620750e12c", + "metadata": {}, + "outputs": [], + "source": [ + "out = conversational_rag_chain.invoke(\n", + " {\"input\": \"それはどう便利なのですか?\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"abc123\"}\n", + " }\n", + ")[\"answer\"]\n", + "print(out)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3642bcb7-55a2-44d4-b902-db045c832a59", + "metadata": {}, + "outputs": [], + "source": [ + "out = conversational_rag_chain.invoke(\n", + " {\"input\": \"retrievalの意味は何ですか?\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"abc123\"}\n", + " }\n", + ")[\"answer\"]\n", + "print(out)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82f8a7e0-1035-4548-8dd7-f440a6f54742", + "metadata": {}, + "outputs": [], + "source": [ + "out = conversational_rag_chain.invoke(\n", + " {\"input\": \"CUDAとは何ですか?\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"xyz456\"}\n", + " }\n", + ")[\"answer\"]\n", + "\n", + "print(out)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e11cded9-34d3-41d2-be84-96d2c1324aa9", + "metadata": {}, + "outputs": [], + "source": [ + "out = conversational_rag_chain.invoke(\n", + " {\"input\": \"2つの配列の要素を加算し、その結果を3つ目の配列に格納するカーネルを書いてもらえますか??\"},\n", + " config={\n", + " \"configurable\": {\"session_id\": \"xyz456\"}\n", + " }\n", + ")[\"answer\"]\n", + "\n", + "print(out)" + ] + }, + { + "cell_type": "markdown", + "id": "4d190508-3893-4155-b184-2fd76fbfbb8d", + "metadata": {}, + "source": [ + "次のノートブックに進み、ローカルにデプロイされたNVIDIA NIMで同様のアプリケーションを作成してみましょう。" + ] + }, + { + "cell_type": "markdown", + "id": "d5fa2d87-0a0d-413b-be21-1f0bedaf4c78", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## References\n", + "\n", + "- https://developer.nvidia.com/blog/tips-for-building-a-rag-pipeline-with-nvidia-ai-langchain-ai-endpoints/\n", + "- https://nvidia.github.io/GenerativeAIExamples/latest/notebooks/05_RAG_for_HTML_docs_with_Langchain_NVIDIA_AI_Endpoints.html\n", + "\n", + "## Licensing\n", + "\n", + "Copyright © 2024 OpenACC-Standard.org. This material is released by OpenACC-Standard.org, in collaboration with NVIDIA Corporation, under the Creative Commons Attribution 4.0 International (CC BY 4.0). These materials include references to hardware and software developed by other entities; all applicable licensing and copyrights apply." + ] + }, + { + "cell_type": "markdown", + "id": "8a76f62d-646c-40bf-8f4c-07e95267fcaf", + "metadata": {}, + "source": [ + "\n", + "
\n", + "
\n", + " \n", + " 1\n", + " 2\n", + " 3\n", + " \n", + " \n", + " Next Notebook\n", + "
\n", + "\n", + "
\n", + "

Home Page

\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}