Nvidiaドライバー、LLaMA、そしてChatGPT

Home PDF

LLaMA(Large Language Model Meta AI)は、2023年2月からMeta AIによってリリースされた大規模言語モデル(LLM)のファミリーです。

最近、Nvidia GPUを搭載したコンピュータを組み立てました。詳細はこちらをご覧ください、コンピュータの組み立て方、https://lzwjava.github.io/computer

その後、LLaMAプロジェクトを実行し始めました。LLaMAプロジェクトのGitHub URLはhttps://github.com/facebookresearch/llamaです。

Nvidiaドライバのインストール

コマンドを実行すると、

torchrun --nproc_per_node 1 example_text_completion.py \
    --ckpt_dir llama-2-7b/ \
    --tokenizer_path tokenizer.model \
    --max_seq_len 128 --max_batch_size 4

このコードは、torchrunを使用して1つのプロセスでexample_text_completion.pyスクリプトを実行するコマンドです。以下のオプションが指定されています:

エラーが表示されます:「RuntimeError: Distributed package doesn’t have NCCL built in」。NCCLについて学びましょう。

NVIDIA Collective Communication Library (NCCL) は、NVIDIA GPUとネットワーキング向けに最適化されたマルチGPUおよびマルチノード通信プリミティブを実装しています。 以下のウェブサイトを参照して、NVIDIAドライバーをインストールしました。

NVIDIAドライバーをグラフィックカードに正常にインストールした後、nvidia-smiコマンドを使用してその詳細を表示すると、以下の情報が表示されます。

(base) lzw@lzw-MS-7E01:~$ nvidia-smi
2023年8月17日 04:15:43       
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.86.10              ドライバーバージョン: 535.86.10    CUDAバージョン: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| GPU  名前                 持続性-M | バス-ID        ディスプレイ.A | 揮発性未修正ECC |
| ファン  温度   パフォーマンス         電力:使用量/上限 |         メモリ使用量 | GPU使用率  計算モード |
|                                         |                      |              MIGモード |
|=========================================+======================+======================|
|   0  NVIDIA GeForce RTX 4070        オン | 00000000:01:00.0 オン |                  N/A |
|  0%   34℃    P8               9W / 215W |    666MiB / 12282MiB |     15%      デフォルト |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
                                                                                         
+---------------------------------------------------------------------------------------+
| プロセス:                                                                             |
|  GPU   GI   CI        PID   タイプ   プロセス名                            GPUメモリ |
|        ID   ID                                                             使用量      |
|=======================================================================================|
|    0   N/A  N/A      1926      G   /usr/lib/xorg/Xorg                          381MiB |
|    0   N/A  N/A      2065      G   /usr/bin/gnome-shell                        120MiB |
|    0   N/A  N/A      3482      G   gnome-control-center                          2MiB |
|    0   N/A  N/A      3803      G   ...irefox/2987/usr/lib/firefox/firefox      149MiB |
+---------------------------------------------------------------------------------------+

実際、この段階に到達するのは難しいです。こちらのリンク、Ubuntu 22.04 Notes、https://github.com/kmcminn/thinkpad/tree/main/extreme3g を注意深く参照してください。

LLaMAを学ぶ

モデルをダウンロードし、コマンドを実行しようとすると、以下のエラーが発生します。

torch.cuda.OutOfMemoryError: CUDAのメモリ不足です。86.00 MiBの割り当てを試みました(GPU 0; 合計容量 11.69 GiB; 9.70 GiBが既に割り当て済み; 64.81 MiBが空き; PyTorchによって合計9.70 GiBが予約されています)。予約メモリが割り当てメモリを大幅に上回る場合、フラグメンテーションを防ぐためにmax_split_size_mbを設定してみてください。

私たちのグラフィックカードのメモリは12GBしかなく、llama-2-7bモデルのサイズは約13GBであるため、このグラフィックカードでは実行できません。

私たちは、別のプロジェクトであるopen-llama-3b、https://huggingface.co/openlm-research/open_llama_3bを使用してみます。

以下のエラーが発生しました。

RuntimeError: すべてのテンソルが同じデバイス上にあることが期待されましたが、少なくとも2つのデバイス、cuda:0とcpuが見つかりました!(メソッドwrapper_CUDA__index_selectの引数indexをチェックしている際に発生)

そして、これについてChatGPTに尋ねます。

ChatGPTは非常に美しい修正を提供してくれました。以下のコードを追加する必要があります。

input_ids = input_ids.to(model.device)

最後に、それを実行することができます。

(llama) lzw@lzw-MS-7E01:~/Projects/open_llama_3b$ python run.py 
Q: 最も大きな動物は何ですか?   
A: シロナガスクジラです。   
Q: 最も大きな動物は何ですか?    
A: シロナガスクジラです。地球上で最も大きな動物です。また、  

また、プロジェクト「llama2.c」も試してみました。https://github.com/karpathy/llama2.c

(base) lzw@lzw-MS-7E01:~/Projects/llama2.c$ ./run stories15M.bin 
ある日、大きなライオンと小さなウサギが森にいました。大きなライオンは言いました。「川まで競争して、水を飲もうよ。」小さなウサギは同意し、彼らは競争を始めました。
ライオンは速かったですが、小さなウサギを傷つけないように気をつけていました。ウサギは川に近づいていましたが、大きなライオンは彼を見ていませんでした。ライオンが川を渡ったとき、滑って川に落ちてしまいました。
小さなウサギは水の中のライオンを見て、「ライオンに飲まないように言うべきだ」と思いました。彼は言いました。「ライオン、川の水を飲んではいけない。危ないよ。」しかし、ライオンは聞かずに川の水を飲みました。
小さなウサギはライオンを助けようとしましたが、できませんでした。ライオンは飲みすぎてお腹が痛くなりました。大きなライオンはとても悲しんで言いました。「川の水を飲むなと言ったのに。」小さなウサギは泣きながら家に帰り、大きなライオンはまだ助けられませんでした。
achieved tok/s: 185.527329

ChatGPTでコードを理解する

Zhiwei: 以下のコードを説明していただけますか?

   def generate(
        self,
        prompt_tokens: List[List[int]],
        max_gen_len: int,
        temperature: float = 0.6,
        top_p: float = 0.9,
        logprobs: bool = False,
        echo: bool = False,
    ) -> Tuple[List[List[int]], Optional[List[List[float]]]]:
        params = self.model.params
        bsz = len(prompt_tokens)
        assert bsz <= params.max_batch_size, (bsz, params.max_batch_size)
min_prompt_len = min(len(t) for t in prompt_tokens)
max_prompt_len = max(len(t) for t in prompt_tokens)
assert max_prompt_len <= params.max_seq_len
total_len = min(params.max_seq_len, max_gen_len + max_prompt_len)
pad_id = self.tokenizer.pad_id
tokens = torch.full((bsz, total_len), pad_id, dtype=torch.long, device="cuda")
for k, t in enumerate(prompt_tokens):
    tokens[k, : len(t)] = torch.tensor(t, dtype=torch.long, device="cuda")
if logprobs:
    token_logprobs = torch.zeros_like(tokens, dtype=torch.float)
prev_pos = 0
eos_reached = torch.tensor([False] * bsz, device="cuda")
input_text_mask = tokens != pad_id
for cur_pos in range(min_prompt_len, total_len):
    logits = self.model.forward(tokens[:, prev_pos:cur_pos], prev_pos)
    if logprobs:
        token_logprobs[:, prev_pos + 1 : cur_pos + 1] = -F.cross_entropy(
            input=logits.transpose(1, 2),
            target=tokens[:, prev_pos + 1 : cur_pos + 1],
            reduction="none",
            ignore_index=pad_id,
        )
    if temperature > 0:
        probs = torch.softmax(logits[:, -1] / temperature, dim=-1)
        next_token = sample_top_p(probs, top_p)
    else:
        next_token = torch.argmax(logits[:, -1], dim=-1)
            next_token = next_token.reshape(-1)
            # プロンプトがすでに生成されている場合のみトークンを置換
            next_token = torch.where(
                input_text_mask[:, cur_pos], tokens[:, cur_pos], next_token
            )
            tokens[:, cur_pos] = next_token
            eos_reached |= (~input_text_mask[:, cur_pos]) & (
                next_token == self.tokenizer.eos_id
            )
            prev_pos = cur_pos
            if all(eos_reached):
                break
        if logprobs:
            token_logprobs = token_logprobs.tolist()
        out_tokens, out_logprobs = [], []
        for i, toks in enumerate(tokens.tolist()):
            # 最大生成長に切り詰める
            start = 0 if echo else len(prompt_tokens[i])
            toks = toks[start : len(prompt_tokens[i]) + max_gen_len]
            probs = None
            if logprobs:
                probs = token_logprobs[i][start : len(prompt_tokens[i]) + max_gen_len]
            # もしeosトークンがあればそこで切り詰める
            if self.tokenizer.eos_id in toks:
                eos_idx = toks.index(self.tokenizer.eos_id)
                toks = toks[:eos_idx]
                probs = probs[:eos_idx] if logprobs else None
            out_tokens.append(toks)
            out_logprobs.append(probs)
        return (out_tokens, out_logprobs if logprobs else None)

ChatGPT: もちろん、以下に提供されたコードの説明をします:

def generate(
    self,
    prompt_tokens: List[List[int]],
    max_gen_len: int,
    temperature: float = 0.6,
    top_p: float = 0.9,
    logprobs: bool = False,
    echo: bool = False,
) -> Tuple[List[List[int]], Optional[List[List[float]]]]:

この関数は、指定されたプロンプトトークンに基づいてテキストを生成するためのものです。以下に各パラメータの説明を示します:

この関数は、生成されたトークンのリストと、必要に応じて対数確率のリストを返します。

この関数はgenerateという名前で、いくつかのパラメータを受け取ります:

params = self.model.params
bsz = len(prompt_tokens)
assert bsz <= params.max_batch_size, (bsz, params.max_batch_size)

このコードスニペットでは、paramsにモデルのパラメータを代入し、bszにプロンプトトークンの数を代入しています。その後、assert文を使用して、バッチサイズ(bsz)がモデルの最大バッチサイズ(params.max_batch_size)を超えていないことを確認しています。もし超えていた場合、エラーが発生します。

ここで、関数はモデルのパラメータを取得し、プロンプトのバッチサイズを計算します。この関数は、バッチサイズがモデルパラメータで指定された最大バッチサイズを超えないようにします。

min_prompt_len = min(len(t) for t in prompt_tokens)
max_prompt_len = max(len(t) for t in prompt_tokens)
assert max_prompt_len <= params.max_seq_len
total_len = min(params.max_seq_len, max_gen_len + max_prompt_len)

このコードスニペットでは、以下の処理を行っています:

  1. min_prompt_lenprompt_tokens 内の最小のトークン長を代入します。
  2. max_prompt_lenprompt_tokens 内の最大のトークン長を代入します。
  3. max_prompt_lenparams.max_seq_len 以下であることを確認します(アサーション)。
  4. total_lenparams.max_seq_lenmax_gen_len + max_prompt_len のうち小さい方を代入します。

このコードは、プロンプトのトークン長を確認し、生成するシーケンスの長さを制限するために使用されます。

これらの行は、プロンプトの最小および最大長を計算し、プロンプトの最大長がモデルパラメータで指定された最大シーケンス長を超えないようにします。総長は、生成される最大長とプロンプトの最大長に基づいて計算されます。

pad_id = self.tokenizer.pad_id
tokens = torch.full((bsz, total_len), pad_id, dtype=torch.long, device="cuda")
for k, t in enumerate(prompt_tokens):
    tokens[k, : len(t)] = torch.tensor(t, dtype=torch.long, device="cuda")

このコードは、PyTorchを使用して、与えられたプロンプトトークンをパディングされたテンソルに変換する処理を行っています。以下に各ステップの説明を日本語で示します。

  1. pad_id = self.tokenizer.pad_id:
    • pad_id は、トークナイザーが定義するパディングトークンのIDを取得します。パディングトークンは、シーケンスの長さを揃えるために使用されます。
  2. tokens = torch.full((bsz, total_len), pad_id, dtype=torch.long, device="cuda"):
    • tokens は、バッチサイズ bsz とシーケンスの総長 total_len を持つテンソルを作成します。このテンソルは、すべての要素が pad_id で初期化され、データ型は torch.long、デバイスは cuda(GPU)に設定されます。
  3. for k, t in enumerate(prompt_tokens)::
    • prompt_tokens は、各プロンプトのトークンリストを含むリストです。このループでは、各プロンプトトークンリスト t とそのインデックス k を取得します。
  4. tokens[k, : len(t)] = torch.tensor(t, dtype=torch.long, device="cuda"):
    • 各プロンプトトークンリスト ttorch.tensor に変換し、tokens テンソルの対応する行にコピーします。このとき、t の長さに応じて、tokens テンソルの該当部分が更新されます。

このコードは、バッチ内の各プロンプトトークンをパディングされたテンソルに変換し、GPU上で処理できるようにするためのものです。

このセクションでは、tokensテンソルをパディングトークンで初期化し、プロンプトからのトークンシーケンスで埋めます。

if logprobs:
    token_logprobs = torch.zeros_like(tokens, dtype=torch.float)

logprobsがTrueの場合、対数確率を格納するためのテンソルtoken_logprobsが作成されます。

prev_pos = 0
eos_reached = torch.tensor([False] * bsz, device="cuda")
input_text_mask = tokens != pad_id

このコードスニペットでは、以下の変数が定義されています:

変数 prev_pos(前の位置)、eos_reached(シーケンス終端トークンが検出されたかどうか)、および input_text_mask(パディングが埋められていない位置)が初期化されます。

次のループは、最小のプロンプト長から始めて、トータルの長さに達するまでトークンを生成します。

for cur_pos in range(min_prompt_len, total_len):
    logits = self.model.forward(tokens[:, prev_pos:cur_pos], prev_pos)
    if logprobs:
        token_logprobs[:, prev_pos + 1 : cur_pos + 1] = -F.cross_entropy(
            input=logits.transpose(1, 2),
            target=tokens[:, prev_pos + 1 : cur_pos + 1],
            reduction="none",
            ignore_index=pad_id,
        )
    if temperature > 0:
        probs = torch.softmax(logits[:, -1] / temperature, dim=-1)
        next_token = sample_top_p(probs, top_p)
    else:
        next_token = torch.argmax(logits[:, -1], dim=-1)

このコードは、指定された範囲内でトークンを生成するループです。logitsはモデルの出力で、logprobsが有効な場合、トークンの対数確率を計算します。temperatureが0より大きい場合、確率分布をソフトマックス関数で計算し、sample_top_p関数を使って次のトークンをサンプリングします。temperatureが0以下の場合、最も確率の高いトークンを選択します。

    next_token = next_token.reshape(-1)
    next_token = torch.where(
        input_text_mask[:, cur_pos], tokens[:, cur_pos], next_token
    )
    tokens[:, cur_pos] = next_token
    eos_reached |= (~input_text_mask[:, cur_pos]) & (
        next_token == self.tokenizer.eos_id
    )
    prev_pos = cur_pos
    if all(eos_reached):
        break

このループ内で、モデルは次のトークンに対する確率分布 logits を生成します。logprobs が True の場合、クロスエントロピーを使用して対数確率を計算します。temperature に応じて、次のトークンを選択するためにソフトマックスまたはアーグマックスを使用します。その後、tokens テンソルを更新し、シーケンス終了トークンをチェックします。

if logprobs:
    token_logprobs = token_logprobs.tolist()

このコードは、logprobsが真(True)の場合に、token_logprobsをリスト形式に変換しています。tolist()メソッドは、NumPy配列やPyTorchテンソルなどをPythonのネイティブなリストに変換するために使用されます。

logprobsがTrueの場合、対数確率テンソルはPythonのリストに変換されます。

out_tokens, out_logprobs = [], []
for i, toks in enumerate(tokens.tolist()):
    ...

このループでは、各入力サンプルに対して、出力トークンシーケンスとそれに関連する対数確率を生成します。

return (out_tokens, out_logprobs if logprobs else None)

このコードは、out_tokensと、logprobsがTrueの場合にout_logprobsを返し、そうでない場合はNoneを返すことを示しています。コード自体は変更せず、そのままにしておきます。

最後に、この関数は生成されたトークンシーケンスとそれに対応する対数確率(要求された場合)を返します。


Back 2025.01.18 Donate