Nvidia Driver، LLaMA و ChatGPT

Home PDF

LLaMA (نموذج اللغة الكبيرة Meta AI) هي عائلة من نماذج اللغة الكبيرة (LLMs)، تم إصدارها بواسطة Meta AI بدءًا من فبراير 2023.

لقد قمت مؤخرًا ببناء جهاز الكمبيوتر الخاص بي مع وحدة معالجة الرسومات من Nvidia. يمكنك التحقق من ذلك هنا، كيفية بناء جهاز كمبيوتر، https://lzwjava.github.io/computer.

بعد ذلك، بدأت في تشغيل مشروع LLaMA. عنوان GitHub لمشروع LLaMA هو 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

يظهر الخطأ التالي: “RuntimeError: الحزمة الموزعة لا تحتوي على NCCL مدمجًا”. دعونا نتعرف على NCCL.

مكتبة الاتصال الجماعي من NVIDIA (NCCL) تنفذ بدائيات الاتصال متعددة وحدات معالجة الرسوميات (GPUs) ومتعددة العقد (multi-node) المُحسنة لوحدات معالجة الرسوميات من NVIDIA والشبكات. أنا أشير إلى المواقع التالية لتثبيت برامج تشغيل NVIDIA.

عندما نقوم بتثبيت برنامج تشغيل NVIDIA بنجاح لبطاقة الرسومات الخاصة بنا، ثم نستخدم الأمر nvidia-smi لعرض تفاصيلها، يمكن أن تظهر المعلومات التالية.

(base) lzw@lzw-MS-7E01:~$ nvidia-smi
الخميس 17 أغسطس 2023 04:15:43       
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.86.10              إصدار السائق: 535.86.10    إصدار CUDA: 12.2         |
|-----------------------------------------+----------------------+----------------------+
| GPU  الاسم                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| المروحة  الحرارة   الأداء          الطاقة: الاستخدام/السعة |         استخدام الذاكرة | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|   0  NVIDIA GeForce RTX 4070        تشغيل  | 00000000:01:00.0  تشغيل |                  N/A |
|  0%   34C    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، https://github.com/kmcminn/thinkpad/tree/main/extreme3g.

تعلم LLaMA

LLaMA (Large Language Model Meta AI) هي مجموعة من نماذج اللغة الكبيرة التي طورتها Meta (المعروفة سابقًا باسم Facebook). هذه النماذج مصممة لتكون فعالة من حيث الحجم وقادرة على أداء مهام معالجة اللغة الطبيعية (NLP) بشكل جيد. إذا كنت ترغب في تعلم المزيد عن LLaMA وكيفية استخدامها، إليك بعض الخطوات التي يمكنك اتباعها:

1. فهم أساسيات LLaMA

2. إعداد البيئة

3. تنزيل النموذج

4. التدريب والضبط

5. التقييم والاختبار

6. التطبيقات العملية

7. الموارد الإضافية

باتباع هذه الخطوات، ستتمكن من تعلم LLaMA واستخدامها بشكل فعال في مشاريعك الخاصة.

بعد تنزيل النماذج، ومحاولة تشغيل الأمر، سنواجه الخطأ التالي،

torch.cuda.OutOfMemoryError: نفاد ذاكرة CUDA. تمت محاولة تخصيص 86.00 ميجابايت (GPU 0؛ سعة إجمالية 11.69 جيجابايت؛ تم تخصيص 9.70 جيجابايت بالفعل؛ 64.81 ميجابايت متاحة؛ 9.70 جيجابايت محجوزة إجمالًا بواسطة PyTorch). إذا كانت الذاكرة المحجوزة أكبر بكثير من الذاكرة المخصصة، حاول ضبط max_split_size_mb لتجنب التجزئة.

نظرًا لأن ذاكرة بطاقة الرسوميات لدينا هي 12 جيجابايت فقط، وحجم نموذج llama-2-7b يبلغ حوالي 13 جيجابايت، فإننا لا نستطيع تشغيله باستخدام بطاقة الرسوميات الخاصة بنا.

نحاول استخدام المشروع الآخر، open-llama-3b، https://huggingface.co/openlm-research/open_llama_3b.

نواجه الخطأ التالي:

RuntimeError: توقع أن تكون جميع الموترات على نفس الجهاز، ولكن تم العثور على جهازين على الأقل، cuda:0 و cpu! (عند التحقق من الوسيط index في الدالة wrapper_CUDA__index_select)

ونحن نسأل ChatGPT عن هذا.

يوفر لنا ChatGPT إصلاحًا جميلًا جدًا. نحتاج إلى إضافة الكود التالي.

input_ids = input_ids.to(model.device)

تم تحويل input_ids إلى الجهاز الذي يعمل عليه النموذج (model.device).

أخيرًا، يمكننا تشغيله.

(llama) lzw@lzw-MS-7E01:~/Projects/open_llama_3b$ python run.py 
س: ما هو أكبر حيوان؟   
ج: الحوت الأزرق.   
س: ما هو أكبر حيوان؟    
ج: الحوت الأزرق. إنه أكبر حيوان على وجه الأرض. وهو أيضًا  

لقد جربنا أيضًا المشروع llama2.c، https://github.com/karpathy/llama2.c.

(base) lzw@lzw-MS-7E01:~/Projects/llama2.c$ ./run stories15M.bin 
في يوم من الأيام، كان هناك أسد كبير وأرنب صغير في الغابة. قال الأسد الكبير: "لنتسابق إلى النهر ونشرب بعض الماء." وافق الأرنب الصغير، وبدأوا في السباق.
كان الأسد سريعًا، ولكنه كان حريصًا على ألا يؤذي الأرنب الصغير. كان الأرنب يقترب من النهر، لكن الأسد الكبير لم يره. عندما عبر الأسد النهر، انزلق وسقط في النهر.
رأى الأرنب الصغير الأسد في الماء وفكر: "يجب أن أخبر الأسد ألا يشرب." قال: "يا أسد، لا تشرب من النهر. إنه ليس آمنًا." لكن الأسد لم يستمع وشرب من النهر.
حاول الأرنب الصغير مساعدة الأسد، لكنه لم يستطع. شرب الأسد كثيرًا وبدأت معدته تؤلمه. كان الأسد الكبير حزينًا جدًا وقال: "لقد أخبرتك ألا تشرب من النهر." ذهب الأرنب الصغير إلى المنزل وهو يبكي، وما زال الأسد الكبير غير قادر على المساعدة.
تحقيق 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)

تم ترجمة الكود أعلاه إلى:

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]
            # قص إلى رمز نهاية الجملة إذا وجد
            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)

بالطبع، إليك شرحًا للكود المقدم:

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 = self.model.params
bsz = len(prompt_tokens)
assert bsz <= params.max_batch_size, (bsz, params.max_batch_size)

ملاحظة: الكود لم يتم ترجمته لأنه يحتوي على أسماء متغيرات ودوال بالإنجليزية، والتي عادةً ما تبقى كما هي في البرمجة.

هنا، تقوم الدالة بالحصول على معلمات النموذج وحساب حجم الدُفعة (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)  # الطول الكلي المقبول

هذه الأسطر تحسب الحد الأدنى والحد الأقصى لأطوال النصوص المقدمة (prompts) وتضمن ألا يتجاوز الحد الأقصى لطول النص المقدم الحد الأقصى لطول التسلسل المحدد في معلمات النموذج. يتم حساب الطول الإجمالي بناءً على الطول الأقصى الذي يمكن إنشاؤه والطول الأقصى للنص المقدم.

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

يقوم هذا القسم بتهيئة موتر tokens برموز الحشو (padding tokens) ويملؤه بتسلسلات الرموز (token sequences) المستخرجة من النصوص المقدمة (prompts).

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 = 0
eos_reached = torch.tensor([False] * bsz, device="cuda")
input_text_mask = tokens != pad_id

ملاحظة: الكود لم يتم ترجمته لأنه يحتوي على أسماء متغيرات ودوال بلغة Python، والتي لا يتم ترجمتها عادةً.

يتم تهيئة المتغيرات 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)
    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

تمت ترجمة الكود أعلاه إلى:

    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

ملاحظة: الكود لم يتم ترجمته لأنه يحتوي على أسماء ودوال محددة في لغة البرمجة Python والتي لا يتم ترجمتها عادةً.

داخل هذه الحلقة، يقوم النموذج بإنشاء توزيع احتمالي logits للرمز التالي. إذا كان logprobs صحيحًا، فإنه يحسب اللوغاريتمات الاحتمالية باستخدام الانتروبيا المتقاطعة. اعتمادًا على temperature، يستخدم softmax أو argmax لتحديد الرمز التالي. ثم يقوم بتحديث موتر tokens ويفحص رموز نهاية التسلسل.

if logprobs:
    token_logprobs = token_logprobs.tolist()

إذا كانت logprobs تساوي True، يتم تحويل موتر احتمالات اللوغاريتمات إلى قائمة Python.

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

في هذه الحلقة، لكل عينة إدخال، يتم توليد تسلسل الرموز الناتج والاحتمالات اللوغاريتمية المرتبطة بها.

return (out_tokens, out_logprobs if logprobs else None)

أخيرًا، تُعيد الدالة تسلسلات الرموز المُنشأة والاحتمالات اللوغاريتمية المقابلة لها (إذا تم طلب ذلك).


Back 2025.01.18 Donate