هذا المقال يتطلب مستوى: متوسط. بيفترض إنك تعرف تشغّل نموذج لغوي محليًا بـ Python وسمعت قبل كده عن ذاكرة الـ GPU، لكنه ما بيفترضش إنك تعرف رياضيات التكميم.
نموذج بـ 70 مليار باراميتر يحتاج نحو 140 جيجابايت ذاكرة GPU علشان يشتغل بدقة FP16. التكميم بينزّل الرقم ده لـ 35 جيجابايت تقريبًا، من غير ما تعيد تدريب أي حاجة. هنا هتفهم إزاي بيحصل، وتشغّله بنفسك في 12 سطر كود.
التكميم: تشغيل نماذج أكبر بذاكرة أقل
المشكلة باختصار
كل باراميتر في النموذج هو رقم عشري. بدقة FP16 الرقم بياخد 2 بايت. يعني نموذج 8 مليار باراميتر = 16 جيجابايت أوزان لوحدها، من غير حساب الـ activations والـ KV cache. الكارت الاستهلاكي بتاعك (مثلًا 8 جيجابايت) مش هيقدر يحمّله أصلًا.
الطريقة اللي بيلجأ لها ناس كتير — يشتروا كارت أغلى أو يأجروا A100 بسعر عالي بالساعة — بتحل المشكلة بفلوس مش بهندسة. التكميم بيحل نفس المشكلة بتقليل عدد البتات لكل رقم بدل زيادة العتاد.
المفهوم بمثال بسيط قبل الرياضيات
تخيّل إنك بتسجّل أسعار في دفتر، بس مساحة كل خانة صغيرة جدًا. بدل ما تكتب سعر بكل الكسور، اتفقنا إننا هنقرّب كل سعر لأقرب رُبع جنيه: 7.00، 7.25، 7.50 وهكذا. السعر 7.213 بيتحوّل لـ 7.25. خسرت دقة صغيرة، بس وفّرت مساحة كبيرة، والمعنى العام للسعر فضل زي ما هو.
التكميم بيعمل نفس الحاجة بالظبط مع أوزان النموذج. علميًا: بناخد المدى المتصل لقيم الأوزان ونقسمه لعدد محدود من المستويات المنفصلة. INT4 معناه 4 بت لكل رقم، يعني 16 مستوى ممكن بس. كل وزن حقيقي بيتخزّن كأقرب مستوى من الستّاشر، ومعاه معامل قياس بنضرب فيه وقت الحساب علشان نرجّع الرقم لمداه الأصلي.
ده معناه إن كل باراميتر بينزل من 2 بايت لنص بايت. النتيجة: تصغير 4 مرات في حجم الأوزان. من 140 جيجابايت لـ 35 جيجابايت في نموذج الـ 70B.
إزاي تعمله عمليًا في 12 سطر
مكتبة bitsandbytes مع transformers بتخلّيك تحمّل أي نموذج مكمّم بسطر إعداد واحد. الكود ده بيحمّل Llama 3 بـ 8 مليار باراميتر بدقة 4 بت:
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
tok = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto",
)
print(round(model.get_memory_footprint() / 1e9, 2), "GB")
النتيجة المتوقّعة من السطر الأخير: حوالي 5.5 إلى 6 جيجابايت بدل 16 جيجابايت. يعني النموذج اللي مكانش بيركب على كارت 8 جيجابايت، بقى يركب ويفضل مكان للسياق.