الصورة الرمزية الخاصة بـ ديفيد كورفويسير


على مدى الأشهر القليلة الماضية، شهدنا ظهورًا في استخدام العمود الفقري للنشر القائم على المحولات لإنشاء تحويل النص إلى صورة عالي الدقة (T2I). تستخدم هذه النماذج بنية المحولات باعتبارها حجر الأساس لعملية الانتشار، بدلاً من بنية UNet التي كانت سائدة في العديد من نماذج الانتشار الأولية. بفضل طبيعة المحولات، تُظهر هذه العناصر الأساسية قابلية توسع جيدة، مع نماذج تتراوح من 0.6B إلى 8B من المعلمات.

كلما أصبحت النماذج أكبر، زادت متطلبات الذاكرة. وتتفاقم المشكلة لأن خط أنابيب الانتشار يتكون عادة من عدة مكونات: أداة تشفير النص، والعمود الفقري للنشر، ووحدة فك ترميز الصور. علاوة على ذلك، تستخدم خطوط التوزيع الحديثة برامج تشفير نصية متعددة – على سبيل المثال، هناك ثلاثة في حالة Stable Diffusion 3. ويستغرق الأمر 18.765 جيجابايت من ذاكرة وحدة معالجة الرسومات لتشغيل استدلال SD3 باستخدام دقة FP16.

يمكن أن تجعل متطلبات الذاكرة العالية هذه من الصعب استخدام هذه النماذج مع وحدات معالجة الرسومات الاستهلاكية، مما يؤدي إلى إبطاء اعتمادها وجعل التجريب أكثر صعوبة. في هذا المنشور، نعرض كيفية تحسين كفاءة الذاكرة لخطوط أنابيب الانتشار القائمة على المحولات من خلال الاستفادة من أدوات القياس الكمي الخاصة بـ Quanto من مكتبة Diffusers.

جدول المحتويات

التصفيات

للحصول على مقدمة تفصيلية عن Quanto، يرجى الرجوع إلى هذا المنشور. باختصار، Quanto عبارة عن مجموعة أدوات للتكميم مبنية على PyTorch. إنها جزء من Hugging Face Optimum، وهي مجموعة من الأدوات لتحسين الأجهزة.

يعد تقدير النماذج أداة شائعة بين ممارسي LLM، ولكن ليس كثيرًا مع نماذج الانتشار. يمكن أن يساعد Quanto في سد هذه الفجوة وتوفير الذاكرة مع تدهور بسيط في الجودة أو عدمه.

لأغراض قياس الأداء، نستخدم وحدة معالجة الرسومات H100 مع البيئة التالية:

ما لم ينص على خلاف ذلك، فإننا نقوم افتراضيًا بإجراء العمليات الحسابية في FP16. لقد اخترنا عدم تحديد حجم VAE لمنع مشكلات عدم الاستقرار العددي. يمكن العثور على رمز القياس الخاص بنا هنا.

في وقت كتابة هذه السطور، لدينا خطوط أنابيب النشر التالية المستندة إلى المحولات لإنشاء النص إلى الصورة في الناشرين:

لدينا أيضًا Latte، وهو خط أنابيب لتحويل النص إلى فيديو يعتمد على المحولات.

للإيجاز، نبقي دراستنا مقتصرة على الثلاثة التالية: PixArt-Sigma، وStable Diffusion 3، وAura Flow. يوضح الجدول أدناه عدد المعلمات الخاصة بالعمود الفقري للانتشار:

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

التكميم أ DiffusionPipeline مع كوانتو

يعد تحديد حجم النموذج باستخدام Quanto أمرًا بسيطًا.

from optimum.quanto import freeze, qfloat8, quantize
from diffusers import PixArtSigmaPipeline
import torch

pipeline = PixArtSigmaPipeline.from_pretrained(
    "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", torch_dtype=torch.float16
).to("cuda")

quantize(pipeline.transformer, weights=qfloat8)
freeze(pipeline.transformer)

نحن نتصل quantize() على الوحدة المراد تكميمها، مع تحديد ما نريد تكميمه. في الحالة المذكورة أعلاه، نقوم فقط بتقدير المعلمات، وترك عمليات التنشيط كما هي. نحن نقوم بالتكميم لنوع البيانات FP8. ندعو أخيرا freeze() لاستبدال المعلمات الأصلية بالمعلمات الكمية.

يمكننا بعد ذلك أن نسمي هذا pipeline عادة:

image = pipeline("ghibli style, a fantasy landscape with castles").images[0]
FP16 محول الانتشار في FP8
صورة FP16. FP8 الصورة الكمية.

نلاحظ التوفير التالي في الذاكرة عند استخدام FP8، مع زمن وصول أعلى قليلاً وعدم تدهور الجودة تقريبًا:

حجم الدفعة التكميم الذاكرة (جيجابايت) الكمون (ثواني)
1 لا أحد 12.086 1.200
1 FP8 11.547 1.540
4 لا أحد 12.087 4.482
4 FP8 11.548 5.109

يمكننا تكميم برنامج تشفير النص بنفس الطريقة:

quantize(pipeline.text_encoder, weights=qfloat8)
freeze(pipeline.text_encoder)

يعد برنامج تشفير النص أيضًا نموذجًا للمحولات، ويمكننا قياسه كميًا أيضًا. يؤدي تكميم كل من أداة تشفير النص والعمود الفقري للانتشار إلى تحسينات أكبر بكثير في الذاكرة:

حجم الدفعة التكميم تكميم TE الذاكرة (جيجابايت) الكمون (ثواني)
1 FP8 خطأ شنيع 11.547 1.540
1 FP8 حقيقي 5.363 1.601
4 FP8 خطأ شنيع 11.548 5.109
4 FP8 حقيقي 5.364 5.141

يؤدي تحديد حجم أداة تشفير النص إلى نتائج مشابهة جدًا للحالة السابقة:

ckpt@pixart-bs@1-dtype@fp16-qtype@fp8-qte@1.png

عمومية الملاحظات

إن تكميم أداة تشفير النص مع العمود الفقري للانتشار يعمل عمومًا مع النماذج التي جربناها. يعد Stable Diffusion 3 حالة خاصة، لأنه يستخدم ثلاثة برامج تشفير نصية مختلفة. لقد وجدنا أن تحديد الكمية ثانية برنامج ترميز النص لا يعمل بشكل جيد، لذا نوصي بالبدائل التالية:

يعطي الجدول أدناه فكرة عن التوفير المتوقع في الذاكرة لمجموعات تكميم تشفير النص المختلفة (يتم تكميم محول الانتشار في جميع الحالات):

حجم الدفعة التكميم تكميم TE 1 تكميم TE 2 تكميم TE 3 الذاكرة (جيجابايت) الكمون (ثواني)
1 FP8 1 1 1 8.200 2.858
1 ✅ FP8 0 0 1 8.294 2.781
1 FP8 1 1 0 14.384 2.833
1 FP8 0 1 0 14.475 2.818
1 ✅ FP8 1 0 0 14.384 2.730
1 FP8 0 1 1 8.325 2.875
1 ✅ FP8 1 0 1 8.204 2.789
1 لا أحد 16.403 2.118
تشفير النص الكمي: 1 تشفير النص الكمي: 3 تشفير النص الكمي: 1 و 3
صورة مع تشفير النص الكمي 1. صورة مع تشفير النص الكمي 3. صورة مع ترميز النص الكمي 1 و 3.

نتائج متنوعة


bfloat16 عادة ما يكون أفضل على H100

استخدام bfloat16 يمكن أن يكون أسرع بالنسبة لبنيات GPU المدعومة، مثل H100 أو 4090. يعرض الجدول أدناه بعض أرقام PixArt التي تم قياسها على أجهزتنا المرجعية H100:

حجم الدفعة دقة التكميم الذاكرة (جيجابايت) الكمون (ثواني) تكميم TE
1 FP16 إنت8 5.363 1.538 حقيقي
1 BF16 إنت8 5.364 1.454 حقيقي
1 FP16 FP8 5.363 1.601 حقيقي
1 BF16 FP8 5.363 1.495 حقيقي

الوعد qint8

لقد وجدنا التكميم مع qint8 (بدلاً من qfloat8) أفضل بشكل عام من حيث زمن الوصول للاستدلال. يصبح هذا التأثير أكثر وضوحًا عندما نقوم بدمج إسقاطات QKV للانتباه أفقيًا (استدعاء fuse_qkv_projections() في الناشرين)، وبالتالي سماكة أبعاد حبات int8 لتسريع الحساب. نقدم بعض الأدلة أدناه لـ PixArt:

حجم الدفعة التكميم الذاكرة (جيجابايت) الكمون (ثواني) تكميم TE إسقاط QKV
1 إنت8 5.363 1.538 حقيقي خطأ شنيع
1 إنت8 5.536 1.504 حقيقي حقيقي
4 إنت8 5.365 5.129 حقيقي خطأ شنيع
4 إنت8 5.538 4.989 حقيقي حقيقي

ماذا عن INT4؟

لقد جربنا أيضًا qint4 عند الاستخدام bfloat16. وهذا لا ينطبق إلا على bfloat16 على H100 لأن التكوينات الأخرى غير مدعومة حتى الآن. مع qint4يمكننا أن نتوقع رؤية المزيد من التحسينات في استهلاك الذاكرة على حساب زيادة زمن الوصول للاستدلال. من المتوقع زيادة زمن الوصول، نظرًا لعدم وجود دعم أجهزة أصلي لحساب int4 – يتم نقل الأوزان باستخدام 4 بتات، ولكن لا يزال الحساب يتم في bfloat16. يوضح الجدول أدناه نتائجنا لـ PixArt-Sigma:

حجم الدفعة تكميم TE الذاكرة (جيجابايت) الكمون (ثواني)
1 لا 9.380 7.431
1 نعم 3.058 7.604

ومع ذلك، لاحظ أنه نظرًا للتمييز القوي لـ INT4، يمكن أن تتعرض النتائج النهائية لضربة قوية. ولهذا السبب، بالنسبة للنماذج المعتمدة على المحولات بشكل عام، فإننا عادةً ما نترك طبقة الإسقاط النهائية خارج نطاق التكميم. في Quanto، نقوم بذلك عن طريق:

quantize(pipeline.transformer, weights=qint4, exclude="proj_out")
freeze(pipeline.transformer)

"proj_out" يتوافق مع الطبقة النهائية في pipeline.transformer. يعرض الجدول أدناه نتائج لإعدادات مختلفة:

تكميم TE: لا، استبعاد الطبقة: لا شيء تحديد حجم TE: لا، استبعاد الطبقة: “proj_out” تكميم TE: نعم، استبعاد الطبقة: لا شيء تحديد حجم TE: نعم، استبعاد الطبقة: “proj_out”
الصورة 1 بدون تكميم تشفير النص. الصورة 2 بدون تكميم أداة تشفير النص ولكن مع استبعاد proj_out في تكميم محول الانتشار. الصورة 3 مع تكميم تشفير النص. الصورة 3 مع تكميم تشفير النص ولكن مع استبعاد proj_out في تكميم محول الانتشار.

لاستعادة جودة الصورة المفقودة، من الممارسات الشائعة إجراء تدريب مراعٍ للتكميم، وهو مدعوم أيضًا في Quanto. هذه التقنية خارج نطاق هذا المنشور، فلا تتردد في الاتصال بنا إذا كنت مهتمًا!

يمكن العثور على جميع نتائج تجاربنا لهذا المنشور هنا.

المكافأة – حفظ وتحميل نماذج الناشرين في Quanto

يمكن حفظ نماذج الناشرات الكمية وتحميلها:

from diffusers import PixArtTransformer2DModel
from optimum.quanto import QuantizedPixArtTransformer2DModel, qfloat8

model = PixArtTransformer2DModel.from_pretrained("PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", subfolder="transformer")
qmodel = QuantizedPixArtTransformer2DModel.quantize(model, weights=qfloat8)
qmodel.save_pretrained("pixart-sigma-fp8")

نقطة التفتيش الناتجة هي 587 ميجابايت في الحجم بدلاً من 2.44 جيجا بايت الأصلية. يمكننا بعد ذلك تحميله:

from optimum.quanto import QuantizedPixArtTransformer2DModel
import torch

transformer = QuantizedPixArtTransformer2DModel.from_pretrained("pixart-sigma-fp8") 
transformer.to(device="cuda", dtype=torch.float16)

واستخدامها في أ DiffusionPipeline:

from diffusers import DiffusionPipeline
import torch

pipe = DiffusionPipeline.from_pretrained(
    "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", 
    transformer=None,
    torch_dtype=torch.float16,
).to("cuda")
pipe.transformer = transformer

prompt = "A small cactus with a happy face in the Sahara desert."
image = pipe(prompt).images[0]

في المستقبل، يمكننا أن نتوقع تمرير transformer مباشرة عند تهيئة خط الأنابيب حتى يعمل هذا:

pipe = PixArtSigmaPipeline.from_pretrained(
    "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", 
-    transformer=None,
+    transformer=transformer,
    torch_dtype=torch.float16,
).to("cuda")

QuantizedPixArtTransformer2DModel التنفيذ متاح هنا كمرجع. إذا كنت تريد المزيد من النماذج من Diffusers المدعومة في Quanto للحفظ والتحميل، يرجى فتح مشكلة هنا والإشارة إليها @sayakpaul.

نصائح

  • بناءً على متطلباتك، قد ترغب في تطبيق أنواع مختلفة من القياس الكمي على وحدات خطوط الأنابيب المختلفة. على سبيل المثال، يمكنك استخدام FP8 لمشفر النص ولكن INT8 لمحول الانتشار. بفضل مرونة الموزعات وQuanto، يمكن القيام بذلك بسلاسة.
  • لتحسين حالات الاستخدام الخاصة بك، يمكنك أيضًا الجمع بين التكميم وتقنيات تحسين الذاكرة الأخرى في Diffusers، مثل enable_model_cpu_offload().

خاتمة

في هذا المنشور، أظهرنا كيفية تحديد كمية نماذج المحولات من Diffusers وتحسين استهلاكها للذاكرة. تصبح تأثيرات التكميم أكثر وضوحًا عندما نقوم أيضًا بتكميم مشفرات النص المشاركة في المزيج. نأمل أن تطبقوا بعض سير العمل على مشاريعكم وتستفيدوا منها 🤗.

شكرًا لبيدرو كوينكا على مراجعاته الشاملة لهذا المنشور.

شاركها.
اترك تعليقاً