LLM Everywhere:ローカルおよびハグフェイスホスティング用のDocker

この記事は、Docker Captain Harsh Manvar と共同で執筆しています。

Hugging Face は、機械学習(ML)の分野で強力な存在となっています。 事前学習済みモデルの大規模なコレクションとユーザーフレンドリーなインターフェースは、AI/MLの展開とスペースへのアプローチ方法を完全に変えました。 Docker モデルと Hugging Face モデルの統合について詳しく知りたい場合は、「Hugging Face の Docker Spaces を使用した機械学習アプリの構築」という記事に包括的なガイドがあります。

言語生成の驚異である大規模言語モデル(LLM)は、驚異的な発明です。 この記事では、Hugging Face がホストする Llama モデルを Docker のコンテキストで使用する方法を見て、自然言語処理 (NLP) の愛好家や研究者に新たな機会を提供します。

長方形 LLM どこでも Docker ローカルおよび抱きしめ顔ホスティング 1

顔のハグとLLMの紹介

Hugging Face(HF)は、 MLモデルのトレーニング、微調整、デプロイのための包括的なプラットフォームを提供します。 また、LLMは、テキストの生成、補完、分類などのタスクを実行できる最先端のモデルを提供します。

Docker を ML に活用する

堅牢な Docker コンテナ化技術により、プログラムのパッケージ化、配布、運用が容易になります。 MLモデルをDockerコンテナで囲むことで、さまざまなコンテキストで一貫して動作することを保証します。 再現性が保証され、長年の「私のマシンで動作する」という問題が解決されます。

フォーマットの種類

Hugging Faceのほとんどのモデルでは、2つのオプションが利用可能です。 

  • GPTQ (通常は 4 ビットまたは 8 ビット、GPU のみ)
  • GGML (通常は 4、5、8 ビット、CPU/GPU ハイブリッド)

AIモデルの量子化で使用される量子化手法の例としては、GGMLモデルやGPTQモデルなどがあります。 これは、トレーニング中またはトレーニング後の量子化を意味します。 GGML モデルと GPTQ モデル (よく知られた 2 つの量子化モデル) は、モデルの重みを低い精度に減らすことで、モデルのサイズと計算の必要性を最小限に抑えます。 

HF モデルは GPU に負荷がかかり、GPU は CPU よりも大幅に高速に推論を実行します。 一般的に、モデルは巨大であり、多くのVRAMも必要です。 この記事では、CPUでうまく動作し、GPUが優れていない場合はおそらく高速であるGGMLモデルを利用します。

このデモではトランスフォーマーとトランスフォーマーも使用するので、まずそれらを理解しましょう。 

  • トランスフォーマー: トランスフォーマーの API とツールのおかげで、最新の事前トレーニング済みモデルを簡単にダウンロードしてトレーニングできます。 事前トレーニング済みモデルを使用することで、モデルをゼロからトレーニングするために必要な時間とリソース、および計算コストと二酸化炭素排出量を削減できます。
  • ctransformers: GGML ライブラリを使用して C/C++ で開発されたトランスフォーマー モデルの Python バインディング。

Llama モデルへのアクセスを要求する

Meta Llamaモデルを利用し、サインアップし、アクセスをリクエストします。

Hugging Faceトークンの作成

今後使用するアクセストークンを作成するには、Hugging Faceプロファイル設定に移動し、左側のサイドバーから [アクセストークン ]を選択します(図1)。 作成したアクセストークンの値を保存します。

Hugging Faceのプロフィール設定のスクリーンショット。[アクセス トークン] を選択します。
図 1. アクセストークンの生成。

Docker環境のセットアップ

LLMの領域を探る前に、まずDocker環境を構成する必要があります。 オペレーティングシステムに基づいて、 Dockerの公式Webサイトの 指示に従って、最初にDockerをインストールします。 インストール後、次のコマンドを実行してセットアップを確認します。

1
docker --version

クイックデモ

次のコマンドは、Hugging Face harsh-manvar-llama-2-7b-chat-test:latest イメージを使用してコンテナーを実行し、コンテナーからホスト コンピューターにポート 7860 を公開します。 また、環境変数 HUGGING_FACE_HUB_TOKEN を指定した値に設定します。

1
2
3
4
docker run -it -p 7860:7860 --platform=linux/amd64 \
    -e HUGGING_FACE_HUB_TOKEN="YOUR_VALUE_HERE"
\
    registry.hf.space/harsh-manvar-llama-2-7b-chat-test:latest python app.py
  • このフラグは -it 、コンテナをインタラクティブモードで実行し、ターミナルをアタッチするようにDockerに指示します。 これにより、コンテナとそのプロセスを操作できるようになります。
  • このフラグは -p 、コンテナからホストマシンにポート7860を公開するようにDockerに指示します。 これは、ポート 7860 のホスト マシンからコンテナーの Web サーバーにアクセスできることを意味します。
  • このフラグは --platform=linux/amd64 、AMD64アーキテクチャのLinuxマシンでコンテナを実行するようにDockerに指示します。
  • このフラグは -e HUGGING_FACE_HUB_TOKEN="YOUR_VALUE_HERE" 、環境変数 HUGGING_FACE_HUB_TOKEN を指定した値に設定するように Docker に指示します。 これは、コンテナからHugging Faceモデルにアクセスするために必要です。

スクリプトは app.py 、コンテナーで実行する Python スクリプトです。 これにより、コンテナが起動し、ターミナルが開きます。 その後、ターミナルでコンテナとそのプロセスを操作できます。 コンテナーを終了するには、 Ctrl+C キーを押します。

ランディングページへのアクセス

コンテナの Web サーバにアクセスするには、Web ブラウザ http://localhost:7860を開き、 に移動します。 Hugging Faceモデルのランディングページが表示されます(図2)。

ブラウザを開き、http://localhost:7860 に移動します

ローカル Docker LLM のランディング ページのスクリーンショットと、「こんにちは、よろしくお願いします。 何かお手伝いできることはありますか、それともおしゃべりしたいですか?」
図 2. ローカルの Docker LLM へのアクセス。

はじめ

プロジェクトのクローン作成

開始するには、Hugging Faceの既存の スペース/リポジトリを複製またはダウンロードします。

ファイル: 必要条件.txt

requirements.txtファイルは、プロジェクトを実行するために必要な Python パッケージとモジュールを一覧表示するテキスト ファイルです。これは、プロジェクトの依存関係を管理し、プロジェクトで作業するすべての開発者が必要なパッケージの同じバージョンを使用していることを確認するために使用されます。

Hugging Face llama-2-13b-chat モデルを実行するには、次のPythonパッケージが必要です。 このモデルはサイズが大きいため、ダウンロードとインストールに時間がかかる場合があります。 また、モデルを実行するために Python プロセスに割り当てられるメモリを増やす必要がある場合もあります。

1
2
3
4
5
6
7
gradio==3.37.0
protobuf==3.20.3
scipy==1.11.1
torch==2.0.1
sentencepiece==0.1.99
transformers==4.31.0
ctransformers==0.2.27

ファイル: ドッカーファイル

1
2
3
4
5
6
7
8
FROM python:3.9
RUN useradd -m -u 1000 user
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --upgrade pip
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
USER user
COPY --link --chown=1000 ./ /code

次のセクションでは、Dockerfile の詳細について説明します。 最初の行は、公式の Python 3.9 イメージをイメージのベース イメージとして使用するように Docker に指示します。

1
FROM python:3.9

次の行は、ユーザー ID が 1000 の user という名前の新しいユーザーを作成します。 このフラグは -m 、ユーザーのホームディレクトリを作成するようにDockerに指示します。

1
RUN useradd -m -u 1000 user

次に、この行はコンテナの作業ディレクトリを /codeに設定します。

1
WORKDIR /code

要件ファイルを現在のディレクトリ /code からコンテナーにコピーします。 また、この行は、コンテナー内の pip パッケージ マネージャーをアップグレードします。

1
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

この行は、コンテナーの既定のユーザーを user に設定します。

1
USER user

次の行は、現在のディレクトリ /code の内容をコンテナ内にコピーします。 このフラグは --link 、ファイルをコピーする代わりにハードリンクを作成するようにDockerに指示するため、パフォーマンスが向上し、イメージのサイズが小さくなります。 このフラグは --chown=1000 、コピーされたファイルの所有権をユーザーユーザーに変更するようにDockerに指示します。

1
COPY --link --chown=1000 ./ /code

Dockerイメージをビルドしたら、コマンドを使用して実行できます docker run 。 これにより、Python 3.9 イメージを実行する新しいコンテナーが root 以外のユーザーで起動します。 その後、ターミナルを使用してコンテナーを操作できます。

ファイル: app.py

Python コードは、Gradio を使用して、トランスフォーマーを使用してトレーニングされたテキスト生成モデルのデモを作成する方法を示しています。 このコードを使用すると、ユーザーはテキストプロンプトを入力し、テキストの続きを生成できます。

Gradioは、インタラクティブな機械学習デモを簡単に作成して共有できるPythonライブラリです。 デモを作成およびデプロイするためのシンプルで直感的なインターフェイスを提供し、トランスフォーマーを含む幅広い機械学習フレームワークとライブラリをサポートします。

このPythonスクリプトは、テキストチャットボットのGradioデモです。 事前トレーニング済みのテキスト生成モデルを使用して、ユーザー入力に対する応答を生成します。 ファイルを分解し、各セクションを見ていきます。

次の行は、 Iterator モジュールから型をインポートします typing 。 この型は、反復処理できる値のシーケンスを表すために使用されます。 次の行では、 gradio ライブラリもインポートされます。

1
2
from typing import Iterator
import gradio as gr

次の行は logging 、自然言語処理用の一般的な機械学習ライブラリであるライブラリから transformers モジュールをインポートします。

1
2
from transformers.utils import logging
from model import get_input_token_length, run

次に、この行は get_input_token_length() model モジュールから and run() 関数をインポートします。 これらの関数は、テキストの入力トークンの長さを計算し、事前トレーニング済みのテキスト生成モデルを使用してテキストを生成するためにそれぞれ使用されます。 次の 2 行では、情報レベルのメッセージを出力し、transformers ロガーを使用するように logging モジュールを設定します。

1
2
3
4
from model import get_input_token_length, run
 
logging.set_verbosity_info()
logger = logging.get_logger("transformers")

次の行は、コード全体で使用されるいくつかの定数を定義しています。 また、行は Gradio デモで表示されるテキストを定義します。

1
2
3
4
5
6
7
8
DEFAULT_SYSTEM_PROMPT = """"""
MAX_MAX_NEW_TOKENS = 2048
DEFAULT_MAX_NEW_TOKENS = 1024
MAX_INPUT_TOKEN_LENGTH = 4000
 
DESCRIPTION = """"""
 
LICENSE = """"""

この行は、コードが開始中であることを示す情報レベルのメッセージをログに記録します。 この関数は、テキストボックスをクリアし、入力メッセージを状態変数に saved_input 保存します。

1
2
3
logger.info("Starting")
def clear_and_save_textbox(message: str) -> tuple[str, str]:
    return '', message

以下の関数は、入力したメッセージをチャットボットに表示し、チャット履歴にメッセージを追加します。

1
2
3
4
5
def display_input(message: str,
                   history: list[tuple[str, str]]) -> list[tuple[str, str]]:
  history.append((message, ''))
  logger.info("display_input=%s",message)            
  return history

この関数は、チャット履歴から以前の応答を削除し、更新されたチャット履歴と以前の応答を返します。

1
2
3
4
5
6
7
def delete_prev_fn(
    history: list[tuple[str, str]]) -> tuple[list[tuple[str, str]], str]:
  try:
    message, _ = history.pop()
  except IndexError:
    message = ''
  return history, message or ''

次の関数は、事前トレーニング済みのテキスト生成モデルと指定されたパラメーターを使用してテキストを生成します。 これは、各タプルが入力メッセージと生成された応答を含むタプルのリストを生成するイテレータを返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def generate(
    message: str,
    history_with_input: list[tuple[str, str]],
    system_prompt: str,
    max_new_tokens: int,
    temperature: float,
    top_p: float,
    top_k: int,
) -> Iterator[list[tuple[str, str]]]:
  #logger.info("message=%s",message)
  if max_new_tokens > MAX_MAX_NEW_TOKENS:
    raise ValueError
 
  history = history_with_input[:-1]
  generator = run(message, history, system_prompt, max_new_tokens, temperature, top_p, top_k)
  try:
    first_response = next(generator)
    yield history + [(message, first_response)]
  except StopIteration:
    yield history + [(message, '')]
  for response in generator:
    yield history + [(message, response)]

次の関数は、指定されたメッセージに対する応答を生成し、空の文字列と生成された応答を返します。

1
2
3
4
5
def process_example(message: str) -> tuple[str, list[tuple[str, str]]]:
  generator = generate(message, [], DEFAULT_SYSTEM_PROMPT, 1024, 1, 0.95, 50)
  for x in generator:
    pass
  return '', x

完全なPythonコードは次のとおりです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
from typing import Iterator
import gradio as gr
 
from transformers.utils import logging
from model import get_input_token_length, run
 
logging.set_verbosity_info()
logger = logging.get_logger("transformers")
 
DEFAULT_SYSTEM_PROMPT = """"""
MAX_MAX_NEW_TOKENS = 2048
DEFAULT_MAX_NEW_TOKENS = 1024
MAX_INPUT_TOKEN_LENGTH = 4000
 
DESCRIPTION = """"""
 
LICENSE = """"""
 
logger.info("Starting")
def clear_and_save_textbox(message: str) -> tuple[str, str]:
    return '', message
 
 
def display_input(message: str,
                  history: list[tuple[str, str]]) -> list[tuple[str, str]]:
    history.append((message, ''))
    logger.info("display_input=%s",message)            
    return history
 
 
def delete_prev_fn(
        history: list[tuple[str, str]]) -> tuple[list[tuple[str, str]], str]:
    try:
        message, _ = history.pop()
    except IndexError:
        message = ''
    return history, message or ''
 
 
def generate(
    message: str,
    history_with_input: list[tuple[str, str]],
    system_prompt: str,
    max_new_tokens: int,
    temperature: float,
    top_p: float,
    top_k: int,
) -> Iterator[list[tuple[str, str]]]:
    #logger.info("message=%s",message)
    if max_new_tokens > MAX_MAX_NEW_TOKENS:
        raise ValueError
 
    history = history_with_input[:-1]
    generator = run(message, history, system_prompt, max_new_tokens, temperature, top_p, top_k)
    try:
        first_response = next(generator)
        yield history + [(message, first_response)]
    except StopIteration:
        yield history + [(message, '')]
    for response in generator:
        yield history + [(message, response)]
 
 
def process_example(message: str) -> tuple[str, list[tuple[str, str]]]:
    generator = generate(message, [], DEFAULT_SYSTEM_PROMPT, 1024, 1, 0.95, 50)
    for x in generator:
        pass
    return '', x
 
 
def check_input_token_length(message: str, chat_history: list[tuple[str, str]], system_prompt: str) -> None:
    #logger.info("check_input_token_length=%s",message)
    input_token_length = get_input_token_length(message, chat_history, system_prompt)
    #logger.info("input_token_length",input_token_length)
    #logger.info("MAX_INPUT_TOKEN_LENGTH",MAX_INPUT_TOKEN_LENGTH)
    if input_token_length > MAX_INPUT_TOKEN_LENGTH:
        logger.info("Inside IF condition")
        raise gr.Error(f'The accumulated input is too long ({input_token_length} > {MAX_INPUT_TOKEN_LENGTH}). Clear your chat history and try again.')
    #logger.info("End of check_input_token_length function")
 
 
with gr.Blocks(css='style.css') as demo:
    gr.Markdown(DESCRIPTION)
    gr.DuplicateButton(value='Duplicate Space for private use',
                       elem_id='duplicate-button')
 
    with gr.Group():
        chatbot = gr.Chatbot(label='Chatbot')
        with gr.Row():
            textbox = gr.Textbox(
                container=False,
                show_label=False,
                placeholder='Type a message...',
                scale=10,
            )
            submit_button = gr.Button('Submit',
                                      variant='primary',
                                      scale=1,
                                      min_width=0)
    with gr.Row():
        retry_button = gr.Button('Retry', variant='secondary')
        undo_button = gr.Button('Undo', variant='secondary')
        clear_button = gr.Button('Clear', variant='secondary')
 
    saved_input = gr.State()
 
    with gr.Accordion(label='Advanced options', open=False):
        system_prompt = gr.Textbox(label='System prompt',
                                   value=DEFAULT_SYSTEM_PROMPT,
                                   lines=6)
        max_new_tokens = gr.Slider(
            label='Max new tokens',
            minimum=1,
            maximum=MAX_MAX_NEW_TOKENS,
            step=1,
            value=DEFAULT_MAX_NEW_TOKENS,
        )
        temperature = gr.Slider(
            label='Temperature',
            minimum=0.1,
            maximum=4.0,
            step=0.1,
            value=1.0,
        )
        top_p = gr.Slider(
            label='Top-p (nucleus sampling)',
            minimum=0.05,
            maximum=1.0,
            step=0.05,
            value=0.95,
        )
        top_k = gr.Slider(
            label='Top-k',
            minimum=1,
            maximum=1000,
            step=1,
            value=50,
        )
 
    gr.Markdown(LICENSE)
 
    textbox.submit(
        fn=clear_and_save_textbox,
        inputs=textbox,
        outputs=[textbox, saved_input],
        api_name=False,
        queue=False,
    ).then(
        fn=display_input,
        inputs=[saved_input, chatbot],
        outputs=chatbot,
        api_name=False,
        queue=False,
    ).then(
        fn=check_input_token_length,
        inputs=[saved_input, chatbot, system_prompt],
        api_name=False,
        queue=False,
    ).success(
        fn=generate,
        inputs=[
            saved_input,
            chatbot,
            system_prompt,
            max_new_tokens,
            temperature,
            top_p,
            top_k,
        ],
        outputs=chatbot,
        api_name=False,
    )
 
    button_event_preprocess = submit_button.click(
        fn=clear_and_save_textbox,
        inputs=textbox,
        outputs=[textbox, saved_input],
        api_name=False,
        queue=False,
    ).then(
        fn=display_input,
        inputs=[saved_input, chatbot],
        outputs=chatbot,
        api_name=False,
        queue=False,
    ).then(
        fn=check_input_token_length,
        inputs=[saved_input, chatbot, system_prompt],
        api_name=False,
        queue=False,
    ).success(
        fn=generate,
        inputs=[
            saved_input,
            chatbot,
            system_prompt,
            max_new_tokens,
            temperature,
            top_p,
            top_k,
        ],
        outputs=chatbot,
        api_name=False,
    )
 
    retry_button.click(
        fn=delete_prev_fn,
        inputs=chatbot,
        outputs=[chatbot, saved_input],
        api_name=False,
        queue=False,
    ).then(
        fn=display_input,
        inputs=[saved_input, chatbot],
        outputs=chatbot,
        api_name=False,
        queue=False,
    ).then(
        fn=generate,
        inputs=[
            saved_input,
            chatbot,
            system_prompt,
            max_new_tokens,
            temperature,
            top_p,
            top_k,
        ],
        outputs=chatbot,
        api_name=False,
    )
 
    undo_button.click(
        fn=delete_prev_fn,
        inputs=chatbot,
        outputs=[chatbot, saved_input],
        api_name=False,
        queue=False,
    ).then(
        fn=lambda x: x,
        inputs=[saved_input],
        outputs=textbox,
        api_name=False,
        queue=False,
    )
 
    clear_button.click(
        fn=lambda: ([], ''),
        outputs=[chatbot, saved_input],
        queue=False,
        api_name=False,
    )
 
demo.queue(max_size=20).launch(share=False, server_name="0.0.0.0")

generate関数はcheck_input_token_length、コードの主要部分を構成します。この generate 関数は、メッセージ、以前のメッセージの履歴、および次のようなさまざまな生成パラメーターを指定して応答を生成する役割を担います。

  • max_new_tokens: これは、応答生成モデルが生成できるトークンの最大数を示す整数です。
  • temperature: この浮動小数点値は、生成される出力のランダム性を調整します。 結果は、高い値 (1.0 など) ではよりランダムになり、低いレベル (0.2 など) では予測しやすくなります。
  • top_p: 核サンプリングは、0 から 1 の範囲のこの浮動小数点値によって決定されます。 これにより、トークンの累積確率のカットオフポイントが確立されます。
  • top_k: 次に考慮されるトークンの数は、この整数で表されます。 数値が大きいほど、出力が集中します。

UI コンポーネントと API サーバーの実行は、 によって app.py処理されます。 基本的には、 app.py アプリケーションやその他の構成を初期化する場所です。

ファイル: Model.py

Python スクリプトは、LLM を使用してユーザー入力に対する応答を生成するチャット ボットです。 このスクリプトでは、次の手順を使用して応答を生成します。

  • ユーザ入力、チャット履歴、およびシステム プロンプトを組み合わせて、LLM のプロンプトを作成します。
  • プロンプトの入力トークンの長さを計算します。
  • LLM と次のパラメーターを使用して応答を生成します。
    • max_new_tokens: 生成する新しいトークンの最大数。
    • temperature: 応答を生成するときに使用する温度。 温度が高いほど、より創造的で多様な応答が得られますが、一貫性の低い応答になる可能性もあります
    • top_p: このパラメータは、応答の生成に使用される核サンプリングアルゴリズムを制御します。 値が大きい top_p ほど、より焦点を絞った有益な回答が得られ、値が小さいほど、より創造的で多様な回答が得られます。
    • top_k: このパラメーターは、応答を生成するときに考慮する最も確率の高いトークンの数を制御します。 値が大きい top_k ほど、より予測可能で一貫性のある応答が得られ、値が小さいほど、より創造的で多様な応答が得られます。

この TextIteratorStreamer クラスの主な機能は、印刷可能なテキストをキューに格納することです。 このキューは、ダウンストリーム アプリケーションで反復子として使用して、生成されたテキストに非ブロッキングな方法でアクセスできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from threading import Thread
from typing import Iterator
 
#import torch
from transformers.utils import logging
from ctransformers import AutoModelForCausalLM
from transformers import TextIteratorStreamer, AutoTokenizer
 
logging.set_verbosity_info()
logger = logging.get_logger("transformers")
 
config = {"max_new_tokens": 256, "repetition_penalty": 1.1,
          "temperature": 0.1, "stream": True}
model_id = "TheBloke/Llama-2-7B-Chat-GGML"
device = "cpu"
 
 
model = AutoModelForCausalLM.from_pretrained(model_id, model_type="llama", lib="avx2", hf=True)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf")
 
def get_prompt(message: str, chat_history: list[tuple[str, str]],
               system_prompt: str) -> str:
    #logger.info("get_prompt chat_history=%s",chat_history)
    #logger.info("get_prompt system_prompt=%s",system_prompt)
    texts = [f'<s>[INST] <<SYS>>\n{system_prompt}\n<</SYS>>\n\n']
    #logger.info("texts=%s",texts)
    do_strip = False
    for user_input, response in chat_history:
        user_input = user_input.strip() if do_strip else user_input
        do_strip = True
        texts.append(f'{user_input} [/INST] {response.strip()} </s><s>[INST] ')
    message = message.strip() if do_strip else message
    #logger.info("get_prompt message=%s",message)
    texts.append(f'{message} [/INST]')
    #logger.info("get_prompt final texts=%s",texts)
    return ''.join(texts) def get_input_token_length(message: str, chat_history: list[tuple[str, str]], system_prompt: str) -> int:
    #logger.info("get_input_token_length=%s",message)
    prompt = get_prompt(message, chat_history, system_prompt)
    #logger.info("prompt=%s",prompt)
    input_ids = tokenizer([prompt], return_tensors='np', add_special_tokens=False)['input_ids']
    #logger.info("input_ids=%s",input_ids)
    return input_ids.shape[-1]
 
def run(message: str,
        chat_history: list[tuple[str, str]],
        system_prompt: str,
        max_new_tokens: int = 1024,
        temperature: float = 0.8,
        top_p: float = 0.95,
        top_k: int = 50) -> Iterator[str]:
    prompt = get_prompt(message, chat_history, system_prompt)
    inputs = tokenizer([prompt], return_tensors='pt', add_special_tokens=False).to(device)
 
    streamer = TextIteratorStreamer(tokenizer,
                                    timeout=15.,
                                    skip_prompt=True,
                                    skip_special_tokens=True)
    generate_kwargs = dict(
        inputs,
        streamer=streamer,
        max_new_tokens=max_new_tokens,
        do_sample=True,
        top_p=top_p,
        top_k=top_k,
        temperature=temperature,
        num_beams=1,
    )
    t = Thread(target=model.generate, kwargs=generate_kwargs)
    t.start()
 
    outputs = []
    for text in streamer:
        outputs.append(text)
        yield "".join(outputs)

トランスフォーマーを使用したテキスト生成に必要なモジュールとライブラリをインポートするには、次のコードを使用できます。

1
from transformers import AutoTokenizer, AutoModelForCausalLM

これにより、トランスフォーマーを使用したテキストのトークン化と生成に必要なモジュールがインポートされます。

インポートするモデルを定義するには、以下を使用できます。

1
model_id = "TheBloke/Llama-2-7B-Chat-GGML"

この手順では、モデル ID を Meta 7B チャット LLama モデルの縮小バージョンとして TheBloke/Llama-2-7B-Chat-GGML定義します。

必要なモジュールとライブラリをインポートし、インポートするモデルを定義したら、次のコードを使用してトークナイザーとモデルを読み込むことができます。

1
2
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

これにより、Hugging Face Hub からトークナイザーとモデルが読み込まれます。トークナイザーの仕事は、モデルの入力を準備することです。 各モデルのトークナイザーは、ライブラリで入手できます。 インポートするモデルを定義します。ここでも、 TheBloke/Llama-2-7B-Chat-GGML.

max_new_tokensおよびstreamconfigで変数temperaturerepetition_penaltyと値を設定する必要があります。

  • max_new_tokens: プロンプトで指定されたトークン数を無視して、可能なほとんどのトークン。
  • temperature: 後続のトークンの確率を変更するために使用された金額。
  • repetition_penalty: 繰り返しペナルティパラメータ。 1.0 は罰がないことを示します。 
  • : 応答テキストをストリーミング方式で生成するか、1 つのバッチで生成するか。 

また、スペースを作成し、そのスペースにファイルをコミットして、Hugging Faceでアプリケーションをホストし、直接テストすることもできます。

イメージのビルド

次のコマンドは、プラットフォーム上にモデルの Docker イメージ llama-2-13b-chat をビルドします linux/amd64 。 画像には という名前 local-llm:v1が付けられます。

1
docker buildx  build --platform=linux/amd64  -t local-llm:v1 .

コンテナの実行

次のコマンドは、Dockerイメージを実行する local-llm:v1 新しいコンテナを起動し、ホストマシン上のポート 7860 を公開します。 環境変数は -e HUGGING_FACE_HUB_TOKEN="YOUR_VALUE_HERE" 、Hugging Face Hubからモデルをダウンロードする llama-2-13b-chat ために必要なHugging Face Hubトークンを設定します。

1
docker run -it -p 7860:7860 --platform=linux/amd64 -e HUGGING_FACE_HUB_TOKEN="YOUR_VALUE_HERE" local-llm:v1 python app.py

次に、ブラウザを開いて http://localhost:7860 に移動し、ローカルのLLM Dockerコンテナの出力を確認します(図3)。

LLM Docker コンテナの出力のスクリーンショットで、チャットボットが「こんにちは! 私はただのAIなので、人間のように感情や感情を持っていません。 しかし、私は助けるためにここにいます..."
図 3. ローカル LLM Docker コンテナーの出力。

また、Docker Desktop からコンテナーを表示することもできます (図 4)。

Docker デスクトップのコンテナのビューを示すスクリーンショット。
図4. Docker Desktop を使用したコンテナーの監視。

結論

Docker を使用して LLM GGML モデルをローカルにデプロイすることは、自然言語処理を使用するための便利で効果的な方法です。 モデルをドッキングすると、異なる環境間での移動が容易になり、一貫性のある実行が保証されます。 ブラウザーでモデルをテストすると、ユーザー フレンドリーなインターフェイスが提供され、そのパフォーマンスをすばやく評価できます。

このセットアップにより、インフラストラクチャとデータをより詳細に制御でき、さまざまなアプリケーション用の高度な言語モデルを簡単にデプロイできます。 これは、大規模言語モデルの展開における重要な一歩です。

さらに詳しく