على مدى الأشهر القليلة الماضية، شهدنا ظهورًا في استخدام العمود الفقري للنشر القائم على المحولات لإنشاء تحويل النص إلى صورة عالي الدقة (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 |
|---|---|
![]() |
![]() |
نلاحظ التوفير التالي في الذاكرة عند استخدام 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 |
يؤدي تحديد حجم أداة تشفير النص إلى نتائج مشابهة جدًا للحالة السابقة:

عمومية الملاحظات
إن تكميم أداة تشفير النص مع العمود الفقري للانتشار يعمل عمومًا مع النماذج التي جربناها. يعد 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 |
|---|---|---|
![]() |
![]() |
![]() |
نتائج متنوعة
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” |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
لاستعادة جودة الصورة المفقودة، من الممارسات الشائعة إجراء تدريب مراعٍ للتكميم، وهو مدعوم أيضًا في 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 وتحسين استهلاكها للذاكرة. تصبح تأثيرات التكميم أكثر وضوحًا عندما نقوم أيضًا بتكميم مشفرات النص المشاركة في المزيج. نأمل أن تطبقوا بعض سير العمل على مشاريعكم وتستفيدوا منها 🤗.
شكرًا لبيدرو كوينكا على مراجعاته الشاملة لهذا المنشور.








