Nvidia-Treiber, LLaMA und ChatGPT

Home PDF

LLaMA (Large Language Model Meta AI) ist eine Familie von großen Sprachmodellen (LLMs), die ab Februar 2023 von Meta AI veröffentlicht wurde.

Ich habe kürzlich meinen Computer mit einer Nvidia-GPU zusammengebaut. Sie können hier nachlesen, wie man einen Computer baut: https://lzwjava.github.io/computer.

Danach begann ich, das LLaMA-Projekt auszuführen. Die GitHub-URL des LLaMA-Projekts ist https://github.com/facebookresearch/llama.

Nvidia-Treiber installieren

Wenn Sie den Befehl ausführen,

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

Hinweis: Der Code wurde nicht übersetzt, da es sich um eine technische Anweisung handelt, die in der Regel in der Originalsprache belassen wird.

Es zeigt den Fehler: “RuntimeError: Distributed package doesn’t have NCCL built in”. Lassen Sie uns etwas über NCCL lernen.

Die NVIDIA Collective Communication Library (NCCL) implementiert Multi-GPU- und Multi-Node-Kommunikationsprimitive, die für NVIDIA-GPUs und Netzwerke optimiert sind. Ich verweise auf die folgenden Websites, um NVIDIA-Treiber zu installieren.

Wenn wir den NVIDIA-Treiber für unsere Grafikkarte erfolgreich installiert haben und dann den Befehl nvidia-smi verwenden, um die Details anzuzeigen, kann er die folgenden Informationen anzeigen.

(base) lzw@lzw-MS-7E01:~$ nvidia-smi
Do Aug 17 04:15:43 2023       
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.86.10              Treiber-Version: 535.86.10    CUDA-Version: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Speicherverbrauch | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|   0  NVIDIA GeForce RTX 4070        On  | 00000000:01:00.0  On |                  N/A |
|  0%   34C    P8               9W / 215W |    666MiB / 12282MiB |     15%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
                                                                                         
+---------------------------------------------------------------------------------------+
| Prozesse:                                                                             |
|  GPU   GI   CI        PID   Typ   Prozessname                            GPU-Speicher |
|        ID   ID                                                             Verbrauch   |
|=======================================================================================|
|    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 |
+---------------------------------------------------------------------------------------+

Tatsächlich ist es schwierig, diese Phase zu erreichen. Bitte beziehen Sie sich sorgfältig auf den Link hier, Ubuntu 22.04 Notes, https://github.com/kmcminn/thinkpad/tree/main/extreme3g.

Lerne LLaMA

Nachdem wir die Modelle heruntergeladen haben und versuchen, den Befehl auszuführen, werden wir auf den folgenden Fehler stoßen:

torch.cuda.OutOfMemoryError: CUDA-Speicher erschöpft. Es wurde versucht, 86.00 MiB zuzuweisen (GPU 0; 11.69 GiB Gesamtkapazität; 9.70 GiB bereits zugewiesen; 64.81 MiB frei; 9.70 GiB insgesamt von PyTorch reserviert). Wenn der reservierte Speicher deutlich größer ist als der zugewiesene Speicher, versuchen Sie, max_split_size_mb zu setzen, um Fragmentierung zu vermeiden.

Da der Speicher unserer Grafikkarte nur 12 GB beträgt und die Größe des llama-2-7b-Modells etwa 13 GB umfasst, können wir es nicht mit unserer Grafikkarte ausführen.

Wir versuchen, das andere Projekt, open-llama-3b, zu verwenden, https://huggingface.co/openlm-research/open_llama_3b.

Wir stoßen auf den folgenden Fehler.

RuntimeError: Es wurde erwartet, dass alle Tensoren auf demselben Gerät liegen, aber es wurden mindestens zwei Geräte gefunden, cuda:0 und cpu! (beim Überprüfen des Arguments für das Argument index in der Methode wrapper_CUDA__index_select)

Und wir fragen ChatGPT danach.

ChatGPT bietet uns eine sehr elegante Lösung. Wir müssen den folgenden Code hinzufügen:

input_ids = input_ids.to(model.device)

Schließlich können wir es hochfahren.

(llama) lzw@lzw-MS-7E01:~/Projects/open_llama_3b$ python run.py 
F: Was ist das größte Tier?   
A: Der Blauwal.   
F: Was ist das größte Tier?    
A: Der Blauwal. Es ist das größte Tier auf der Erde. Es ist auch das  

Wir haben auch das Projekt llama2.c ausprobiert, https://github.com/karpathy/llama2.c.

(base) lzw@lzw-MS-7E01:~/Projects/llama2.c$ ./run stories15M.bin 
Eines Tages waren ein großer Löwe und ein kleiner Hase im Wald. Der große Löwe sagte: "Lass uns zum Fluss rennen und etwas Wasser trinken." Der kleine Hase stimmte zu, und sie begannen zu rennen.
Der Löwe war schnell, aber er war vorsichtig, den kleinen Hasen nicht zu verletzen. Der Hase kam dem Fluss immer näher, aber der große Löwe sah ihn nicht. Als der Löwe den Fluss überquerte, rutschte er aus und fiel in den Fluss.
Der kleine Hase sah den Löwen im Wasser und dachte: "Ich sollte dem Löwen sagen, dass er nicht trinken soll." Er sagte: "Löwe, trink nicht aus dem Fluss. Es ist nicht sicher." Aber der Löwe hörte nicht zu und trank aus dem Fluss.
Der kleine Hase versuchte, dem Löwen zu helfen, aber er konnte es nicht. Der Löwe trank zu viel und sein Bauch begann zu schmerzen. Der große Löwe war sehr traurig und sagte: "Ich habe dir gesagt, du sollst nicht aus dem Fluss trinken." Der kleine Hase ging weinend nach Hause, und der große Löwe konnte immer noch nicht helfen.
erreichte tok/s: 185.527329

Code verstehen mit ChatGPT

Zhiwei: Könnten Sie den folgenden Code erklären?

   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)
        # Token nur ersetzen, wenn der Prompt bereits generiert wurde
        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()):
            # auf maximale Generierungslänge kürzen
            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]
            # auf EOS-Token kürzen, falls vorhanden
            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: Sicher, hier ist eine Erklärung des bereitgestellten Codes:

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]]]]:

Diese Funktion heißt generate und akzeptiert mehrere Parameter:

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

(Der Code bleibt unverändert, da es sich um eine technische Implementierung handelt, die nicht übersetzt werden muss.)

Hier erhält die Funktion die Modellparameter und berechnet die Batch-Größe der Eingabeaufforderungen. Sie stellt sicher, dass die Batch-Größe nicht größer ist als die im Modell angegebene maximale Batch-Größe.

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)

Diese Zeilen berechnen die minimalen und maximalen Längen der Prompts und stellen sicher, dass die maximale Prompt-Länge die im Modell angegebene maximale Sequenzlänge nicht überschreitet. Die Gesamtlänge wird basierend auf der maximalen generierten Länge und der maximalen Prompt-Länge berechnet.

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")

Hinweis: Der Code wurde nicht übersetzt, da es sich um eine Programmiersprache handelt, die in der Regel nicht übersetzt wird.

Dieser Abschnitt initialisiert den tokens-Tensor mit Padding-Tokens und füllt ihn mit den Token-Sequenzen aus den Prompts.

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

Wenn logprobs True ist, wird ein Tensor token_logprobs erstellt, um die Log-Wahrscheinlichkeiten zu speichern.

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

Hinweis: Der Code wurde nicht übersetzt, da es sich um eine Programmiersprache handelt, die in der Regel nicht übersetzt wird.

Die Variablen prev_pos (vorherige Position), eos_reached (ob End-of-Sequence-Token erkannt wurden) und input_text_mask (Positionen, die nicht mit Padding gefüllt sind) werden initialisiert.

Die folgende Schleife erzeugt Token, beginnend bei der minimalen Eingabelänge bis zur Gesamtlänge.

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

Innerhalb dieser Schleife generiert das Modell eine Wahrscheinlichkeitsverteilung logits für das nächste Token. Wenn logprobs auf True gesetzt ist, werden die logarithmierten Wahrscheinlichkeiten mithilfe der Kreuzentropie berechnet. Abhängig von der temperature wird entweder Softmax oder Argmax verwendet, um das nächste Token auszuwählen. Anschließend wird der tokens-Tensor aktualisiert und auf End-of-Sequence-Tokens überprüft.

if logprobs:
    token_logprobs = token_logprobs.tolist()

Hinweis: Der Code bleibt unverändert, da es sich um eine Programmiersprache handelt und keine Übersetzung erforderlich ist.

Wenn logprobs True ist, wird der Log-Wahrscheinlichkeiten-Tensor in eine Python-Liste umgewandelt.

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

In dieser Schleife wird für jede Eingabeprobe die Ausgabe-Token-Sequenz und die zugehörigen Log-Wahrscheinlichkeiten generiert.

return (out_tokens, out_logprobs if logprobs else None)

Schließlich gibt die Funktion die generierten Token-Sequenzen und ihre entsprechenden Log-Wahrscheinlichkeiten zurück (falls angefordert).


Back 2025.01.18 Donate