هذا المستند هو ملحق لتحسينات WebAssembly وWebGPU لتحسين الذكاء الاصطناعي على الويب بشكل أسرع، الجزء 1. ننصحك بقراءة هذه المشاركة أو مشاهدة المحادثة في مؤتمر IO 24 قبل المتابعة.
WebGPU
تتيح WebGPU لتطبيقات الويب الوصول إلى أجهزة وحدة معالجة الرسومات (GPU) للعميل لإجراء عمليات حسابية فعّالة ومتوازية للغاية. منذ إطلاق WebGPU في Chrome، شهدنا عروضًا توضيحية رائعة للذكاء الاصطناعي (AI) وتعلُّم الآلة (ML) على الويب.
على سبيل المثال، أثبتت ميزة نشرة ثابتة على الويب أنّه من الممكن استخدام الذكاء الاصطناعي (AI) لإنشاء صور من النصوص، وذلك في المتصفّح مباشرةً. في وقت سابق من هذا العام، نشر فريق Mediapi الخاص بشركة Google دعمًا تجريبيًا للاستنتاج اللغوي الكبير.
تعرض الصورة المتحركة التالية نموذج Gemma، وهو نموذج لغوي كبير مفتوح المصدر من Google يتم تشغيله بالكامل في متصفّح Chrome على الجهاز فقط في الوقت الفعلي.
ينتج عن العرض التوضيحي التالي لـ Hugging Face لـ Meta's Segment Anything model أقنعة عناصر عالية الجودة تمامًا على العميل.
هذان مشروعان رائعان يُبرزان قوة WebGPU للذكاء الاصطناعي وتعلُّم الآلة. وتسمح أداة WebGPU لهذه النماذج وغيرها بالعمل بشكل أسرع بكثير من وحدة المعالجة المركزية (CPU).
تُظهِر مقياس أداء WebGPU لنظام Hugging Face لتضمين النص سرعات هائلة مقارنةً بتطبيق وحدة المعالجة المركزية (CPU) للنموذج نفسه. على جهاز كمبيوتر محمول Apple M1 Max، كانت قيمة WebGPU أسرع بمقدار 30 مرة. وأفاد آخرون أنّ WebGPU تعمل على تسريع مقياس الأداء بمقدار أكثر من 120 ضعفًا.
تحسين ميزات WebGPU للذكاء الاصطناعي وتعلُّم الآلة
تُعدّ أداة WebGPU مناسبة تمامًا لنماذج الذكاء الاصطناعي (AI) وتعلُّم الآلة التي يمكن أن تحتوي على مليارات المَعلمات، وذلك بفضل دعم أدوات تمييز الحوسبة. يتم تشغيل أدوات التظليل على وحدة معالجة الرسومات والمساعدة في تشغيل عمليات الصفائف الموازية على كميات كبيرة من البيانات.
من بين التحسينات العديدة التي تم إجراؤها على WebGPU في العام الماضي، واصلنا إضافة المزيد من الإمكانات لتحسين أداء تعلُّم الآلة والذكاء الاصطناعي على الويب. أطلقنا مؤخرًا ميزتين جديدتين: منتجات النقطة العائمة بتنسيق 16 بت ومنتجات النقاط العددية الصحيحة.
نقطة عائمة 16 بت
تذكَّر أنّ أعباء العمل في تعلُّم الآلة لا تتطلب دقة. shader-f16
هي ميزة تتيح استخدام النوع f16 في لغة تظليل WebGPU. ويستغرق هذا النوع من النقاط العائمة 16 بت بدلاً من 32 بت المعتادة. أمّا الإصدار f16، فهو يتضمّن نطاقًا أصغر وأقل دقة، ولكنّ هذا يكفي بالنسبة إلى العديد من نماذج تعلُّم الآلة.
تزيد هذه الميزة من الكفاءة بعدة طرق:
تقليل الذاكرة: تشغل العشرات التي تحتوي على عناصر f16 نصف المساحة، ما يؤدي إلى تقليل استخدام الذاكرة إلى النصف. غالبًا ما تؤثر عمليات احتساب وحدة معالجة الرسومات في معدّل نقل بيانات الذاكرة، لذا غالبًا ما يعني نصف الذاكرة أنّ أدوات التظليل تعمل بسرعة أكبر بمعدّل مرّتين. من الناحية الفنية، لا تحتاج إلى مفتاح f16 لتوفير معدّل نقل بيانات الذاكرة. ومن الممكن تخزين البيانات بتنسيق منخفض الدقة، ثم توسيعها إلى الإصدار f32 الكامل في أداة التظليل لإجراء العمليات الحسابية. ولكن، تنفق وحدة معالجة الرسومات قوة حوسبة إضافية لتجميع البيانات وفك ضغطها.
تقليل الإحالات الناجحة للبيانات: تستخدِم f16 عملية حوسبة أقل عن طريق تقليل عمليات تحويل البيانات. ويمكن تخزين البيانات ذات الدقة المنخفضة ثم استخدامها مباشرة دون تحويل.
زيادة التوازي: وحدات معالجة الرسومات الحديثة قادرة على استيعاب المزيد من القيم في الوقت نفسه في وحدات تنفيذ وحدة معالجة الرسومات، ما يتيح لها إجراء عدد أكبر من العمليات الحسابية المتوازية. على سبيل المثال، قد تتوافق وحدة معالجة الرسومات التي تتيح ما يصل إلى 5 تريليونات من عمليات النقطة العائمة f32 في الثانية مع 10 تريليونات من عمليات النقطة العائمة f16 في الثانية.
WebLLM هو مشروع يمكنه تشغيل نماذج لغوية كبيرة متعدّدة. وتستخدم Apache TVM، وهو إطار عمل مفتوح المصدر لتجميع تعلُّم الآلة.
لقد طلبتُ من WebLLM التخطيط لرحلة إلى باريس باستخدام نموذج معلَمة Llama 3 المكوّنة من ثمانية مليارات. توضح النتائج أنه خلال مرحلة الملء المسبق للنموذج، تكون f16 أسرع بمقدار f32 بمقدار 2.1 مرة. وخلال مرحلة فك الترميز، يتم النقل بسرعة أكبر بمقدار 1.3 مرة.
يجب أن تتأكّد التطبيقات أولاً من توافق محوّل وحدة معالجة الرسومات مع الإصدار f16، وفي حال توفّره، يجب تفعيله بشكل صريح عند طلب جهاز وحدة معالجة رسومات. إذا لم يكن f16 متاحًا، لا يمكنك طلبه في مصفوفة requiredFeatures
.
// main.js
const adapter = await navigator.gpu.requestAdapter();
const supportsF16 = adapter.features.has('shader-f16');
if (supportsF16) {
// Use f16.
const device = await adapter.requestDevice({
requiredFeatures: ['shader-f16'],
});
initApp(device);
}
بعد ذلك، يجب تفعيل f16 في أعلى الشاشة في أدوات تظليل WebGPU. بعد ذلك، يمكنك استخدامها داخل الظل مثل أي نوع بيانات عائم آخر.
// my-shader.wgsl
enable f16;
struct Data {
values : array<vec4<f16>>
}
@group(0) @binding(0) var<storage, read> data : Data;
@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) gid : vec3u) {
let value : vec4<f16> = data.values[gid.x];
...
}
منتجات نقطية بأرقام صحيحة
لا تزال العديد من النماذج تعمل بشكل جيد مع 8 بت فقط من الدقة (نصف f16). وهو شائع بين النماذج اللغوية الكبيرة ونماذج الصور للتقسيم والتعرّف على العناصر. مع ذلك، تنخفض جودة النتائج للنماذج بدقة أقل، وبالتالي لا يتناسب استخدام تقدير 8 بت لكل تطبيق.
عدد قليل نسبيًا من وحدات معالجة الرسومات التي تتوافق في الأصل مع قيم 8 بت. في هذه النقطة، تأتي المنتجات النقطية المعبّأة ذات الأعداد الصحيحة. تم شحن DP4a في Chrome 123.
لدى وحدات معالجة الرسومات الحديثة تعليمات خاصة بشأن احتوائها على عددين صحيحَين 32 بت، وتفسير كل منها على أنّه 4 أعداد صحيحة 8 بت معبأة بشكل متتالٍ، وحساب ناتج الضرب النقطي بين مكوّناتها.
يفيد ذلك بشكل خاص في الذكاء الاصطناعي وتعلُّم الآلة، لأنّ نواة ضرب المصفوفات تتألّف من العديد من منتجات النقاط.
على سبيل المثال، لنضرب مصفوفة 4 × 8 في متجه 8 × 1. تشمل عملية حساب ذلك ضرب 4 ضربات نقطية لحساب كل قيمة من القيم في الخط المتجه الناتج؛ "أ" و"ب" و"ج" و"د".
عملية حساب كل من هذه المخرجات هي نفسها؛ سوف نلقي نظرة على الخطوات اللازمة لاحتساب إحداها. قبل إجراء أي عملية حسابية، سنحتاج أولاً إلى تحويل بيانات الأعداد الصحيحة المكونة من 8 بت إلى نوع يمكننا استخدامه في العمليات الحسابية، مثل f16. ثم نشغل عملية الضرب على مستوى العناصر وأخيرًا، نجمع جميع المنتجات معًا. في المجمل، بالنسبة إلى عملية ضرب متجه المصفوفة بالكامل، نُجري 40 عددًا صحيحًا لعدد الإحالات الناجحة العائمة لفك ضغط البيانات، و32 عملية ضرب عائمة، و28 إضافة عائمة.
بالنسبة إلى المصفوفات الأكبر حجمًا التي يتم إجراء المزيد من العمليات بها، يمكن أن تساعد منتجات النقاط الصحيحة في تقليل مقدار العمل.
بالنسبة إلى كل ناتج من الناتج في متّجه النتيجة، نُجري عمليتَي حزم نقطيتَين باستخدام لغة WebGPU Shading Language المضمَّنة في dot4U8Packed
، ثم نجمع النتائج معًا. في المجمل، لا نجري أي تحويل للبيانات في عملية ضرب متجه المصفوفة بالكامل. ننفّذ 8 منتجات نقطية معبأة و4 إضافات على شكل أعداد صحيحة.
لقد اختبرنا منتجات نقطية معبأة بأرقام صحيحة مع بيانات من 8 بت على مجموعة متنوعة من وحدات معالجة الرسومات الاستهلاكية. بالمقارنة مع النقطة العائمة 16 بت، يمكننا أن نرى أن 8 بت أسرع بمقدار 1.6 إلى 2.8 مرة. وعندما نستخدم أيضًا منتجات نقطية معبّأة، يكون الأداء أفضل. إنها أسرع بمقدار 1.7 إلى 2.9 مرة.
تأكَّد من توافق المتصفّح مع السمة wgslLanguageFeatures
. إذا كانت وحدة معالجة الرسومات لا تتوافق في الأصل مع منتجات النقاط المعبأة، يملأ المتصفّح تطبيقه الخاص.
// main.js
if (navigator.gpu.wgslLanguageFeatures.has('packed_4x8_integer_dot_product')) {
// Use dot4U8Packed, dot4I8Packed builtin
// functions in the shaders.
}
الفرق (الفرق) التالي في مقتطف الرمز الذي يبرز التغييرات اللازمة لاستخدام منتجات الأعداد الصحيحة المعبأة في أداة تظليل WebGPU.
قبل: أداة تظليل WebGPU تعمل على تجميع منتجات نقطية بشكل جزئي في المتغيّر "sum". في نهاية التكرار الحلقي، يحتوي `المجموع` على حاصل الضرب النقطي الكامل بين متجه وصف واحد من مصفوفة الإدخال.
// my-dot-product.wgsl @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) gid : vec3u) { var sum : f16; let start = gid.x * uniforms.dim; for (var i = 0u; i < uniforms.dim; i++) { let v1 : vec4<f16> = vector.values[i]; let v2 : vec4<f16> = matrix.values[start + i]; sum += dot(v1, v2); } }
بعد: أداة تظليل WebGPU، والتي تمت كتابتها لاستخدام منتجات نقطية معبأة بأعداد صحيحة. الفرق الرئيسي هو أنه بدلاً من تحميل 4 قيم عائمة من المتجه والمصفوفة، تقوم أداة التظليل هذه بتحميل عدد صحيح واحد 32 بت. يحمل هذا العدد الصحيح 32 بت بيانات من أربع قيم لأعداد صحيحة 8 بت. بعد ذلك، نستدعي dot4U8Packed
لحساب ناتج الضرب النقطي للقيمتين.
// my-dot-product.wgsl
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid : vec3u) {
var sum : f32;
let start = gid.x * uniforms.dim;
for (var i = 0u; i < uniforms.dim; i++) {
let v1 : u32 = vector.values[i];
let v2 : u32 = matrix.values[start + i];
sum += dot4U8Packed(v1, v2);
}
}
إنّ كل من منتجات النقطة العائمة 16 بت والمنتجات النقطية مع عدد صحيح معبأ هما الميزات التي يتم شحنها في Chrome لتسريع عمل الذكاء الاصطناعي وتعلُّم الآلة. تتوفّر النقطة العائمة 16 بت عندما يدعمها الجهاز، وينفّذ Chrome منتجات نقطية معبأة بعدد صحيح على جميع الأجهزة.
يمكنك استخدام هذه الميزات في إصدار Chrome الثابت اليوم لتحقيق أداء أفضل.
الميزات المقترَحة
وبالنظر إلى المستقبل، فإننا نبحث في ميزتين أخريين: ضرب المجموعات الفرعية والمصفوفة التعاونية.
تمكّن ميزة المجموعات الفرعية التوازي على مستوى SIMD من الاتصال أو إجراء عمليات رياضية جماعية، مثل المجموع لأكثر من 16 رقمًا. يتيح ذلك المشاركة الفعّالة للبيانات بين سلاسل المحادثات. يتم دعم المجموعات الفرعية على واجهات برمجة التطبيقات الحديثة لوحدات معالجة الرسومات، وقد تحمل أسماء مختلفة وبأشكال مختلفة قليلاً.
لقد شرخنا المجموعة المشتركة في اقتراح أرسلناه إلى مجموعة توحيد وحدة معالجة الرسومات WebGPU. ولقد أنشأنا نماذج أولية لمجموعات فرعية في Chrome وراء علامة تجريبية، وعرضنا نتائجنا الأولية في المناقشة. وتتمثل المشكلة الرئيسية في كيفية ضمان سلوك الجهاز المحمول.
ضرب المصفوفة التعاونية هو إضافة أحدث إلى وحدات معالجة الرسومات. يمكن تقسيم ضرب المصفوفة الكبيرة إلى عمليات ضرب مصفوفات أصغر متعددة. ينفذ ضرب المصفوفة التعاونية عمليات ضرب في هذه الكتل البرمجية الأصغر حجمًا ذات الحجم الثابت في خطوة منطقية واحدة. وفي هذه الخطوة، تتعاون مجموعة من سلاسل المحادثات بفعالية لحساب النتيجة.
لقد أجرينا استبيانًا حول الدعم في واجهات برمجة تطبيقات وحدة معالجة الرسومات الأساسية، ونخطط لتقديم اقتراح إلى مجموعة توحيد وحدة معالجة الرسومات. وكما هو الحال مع المجموعات الفرعية، نتوقع أن يتمحور الكثير من المناقشة حول إمكانية النقل.
وفي تقييم أداء عمليات المجموعات الفرعية، دمجنا الدعم التجريبي للمجموعات الفرعية مع MediaPipe في تطبيق حقيقي، واختبرنا هذا الدعم باستخدام نموذج Chrome الأولي لعمليات المجموعات الفرعية.
استخدمنا مجموعات فرعية في نواة وحدة معالجة الرسومات لمرحلة الملء المسبق للنموذج اللغوي الكبير، لذا أُعدّ تقارير عن التسريع لمرحلة الملء المسبق فقط. في وحدة معالجة الرسومات من Intel، نرى أن أداء المجموعات الفرعية أسرع بمقدار مرتين ونصف من المتوقع. ومع ذلك، لا تتطابق هذه التحسينات في وحدات معالجة الرسومات المختلفة.
يعرض الرسم البياني التالي نتائج تطبيق المجموعات الفرعية لتحسين ضرب مقياس الأداء المصغَّر في المصفوفة على مستوى وحدات معالجة الرسومات المتعددة الخاصة بالمستهلكين. يعتبر ضرب المصفوفة من العمليات الأكثر شدة في النماذج اللغوية الكبيرة. تظهر البيانات أنه في العديد من وحدات معالجة الرسومات، تزيد المجموعات الفرعية من السرعة اثنين وخمس، وحتى ثلاثة عشر ضعفًا للسرعة الأساسية. ومع ذلك، لاحظ أنه في وحدة معالجة الرسومات الأولى، لا تكون المجموعات الفرعية أفضل على الإطلاق.
عملية تحسين وحدة معالجة الرسومات صعبة
في النهاية، تعتمد أفضل طريقة لتحسين وحدة معالجة الرسومات على وحدة معالجة الرسومات التي يوفّرها العميل. لا يؤدي استخدام ميزات وحدة معالجة الرسومات الجديدة والرائعة دائمًا إلى النتائج المرجوة على النحو الذي تتوقعه، نظرًا لاحتمالية توفّر الكثير من العوامل المعقدة. قد لا تكون أفضل استراتيجية للتحسين على وحدة معالجة رسومات واحدة هي أفضل استراتيجية على وحدة معالجة رسومات أخرى.
لنفترض أنك تريد تقليل معدل نقل البيانات للذاكرة، مع استخدام سلاسل الحوسبة في وحدة معالجة الرسومات بشكل كامل.
وقد تكون أنماط الوصول إلى الذاكرة مهمة أيضًا. عادةً ما تحقق وحدات معالجة الرسومات أداءً أفضل عندما تصل سلاسل الحوسبة إلى الذاكرة بنمط مناسب للأجهزة. ملاحظة مُهمّة: من المفترض أن تتوقّع خصائص أداء مختلفة على أجهزة وحدة معالجة الرسومات المختلفة. قد تحتاج إلى إجراء تحسينات مختلفة حسب وحدة GPU.
في الرسم البياني التالي، استخدمنا خوارزمية ضرب المصفوفة نفسها، ولكننا أضفنا بُعدًا آخر لتوضيح تأثير استراتيجيات التحسين المختلفة ومدى التعقيد والتباين على مستوى وحدات معالجة الرسومات المختلفة. لقد قدمنا هنا تقنية جديدة، سنسميها "Swizzle". يحسّن Swizzle أنماط الوصول إلى الذاكرة لتكون أكثر مثالية للأجهزة.
وتلاحظ أنّ تأثير اهتزاز الذاكرة له تأثير كبير، يكون في بعض الأحيان أكثر تأثيرًا من المجموعات الفرعية. على وحدة معالجة الرسومات 6، يوفر swizzle السرعة البالغة 12 مرّة، في حين توفّر المجموعات الفرعية تسريعًا بمقدار 13 مرّة. وتمكّنا معًا من تحقيق سرعة مذهلة بلغت 26 ضعفًا. بالنسبة إلى وحدات معالجة الرسومات الأخرى، أحيانًا يكون أداء الدوران والمجموعات الفرعية معًا أفضل من أداء أيّ منهما على حدة. وعلى وحدات معالجة الرسومات الأخرى، يؤدي استخدام swizzle حصريًا إلى تقديم أفضل أداء.
يتطلب ضبط خوارزميات وحدة معالجة الرسومات وتحسينها للعمل بشكل جيد على كل جهاز من الأجهزة الكثير من الخبرة. ولكن لحسن الحظ، هناك مقدار هائل من العمل الموهوب الذي يتم إدخاله في أُطر عمل المكتبات ذات المستوى الأعلى، مثل Mediapi وTransformers.js وApache TVM وONNX Runtime Web وغير ذلك.
إنّ المكتبات وأُطر العمل لديها وضع جيد للتعامل مع تعقيدات إدارة البُنى الأساسية المتنوّعة لوحدات معالجة الرسومات وإنشاء رموز خاصة بالنظام الأساسي تعمل بشكل جيد على العميل.
الخلاصات
يواصل فريق Chrome المساعدة في تطوير معايير WebAssembly وWebGPU لتحسين النظام الأساسي للويب من أجل تنفيذ مهام العمل المتعلّقة بتعلُّم الآلة. نحن نستثمر في مبادئ الحوسبة الأساسية الأسرع، وإمكانية التشغيل التفاعلي بشكل أفضل على مستوى معايير الويب، ونحرص على أن تعمل النماذج الكبيرة والصغيرة معًا بكفاءة على جميع الأجهزة.
وهدفنا هو تعظيم إمكانات النظام الأساسي مع الاحتفاظ بأفضل ما على الويب: وذلك من حيث مدى الوصول وسهولة الاستخدام وقابلية النقل. ونحن لا نفعل ذلك بمفردنا. نحن نعمل بالتعاون مع موردي المتصفحات الآخرين في W3C والعديد من شركاء التطوير.
عند استخدام WebAssembly وWebGPU، نأمل أن تتذكر ما يلي:
- يتوفّر الآن الاستنتاج المستنِد إلى الذكاء الاصطناعي على الويب وعلى جميع الأجهزة. وهذا يوفر ميزة التشغيل على أجهزة العميل، مثل تقليل تكلفة الخادم ووقت الاستجابة البطيء وزيادة الخصوصية.
- في حين أن العديد من الميزات التي تمت مناقشتها ذات صلة في المقام الأول بمؤلفي إطار العمل، يمكن أن تستفيد تطبيقاتك من دون الكثير من النفقات العامة.
- معايير الويب سلسة ومتطورة، ونتطلع دائمًا إلى الحصول على الملاحظات. شارك موقعك مع WebAssembly وWebGPU.
شكر وتقدير
نود أن نشكر فريق رسومات الويب من Intel الذي كان له دور فعال في تشغيل WebGPU f16 والعديد من ميزات منتج نقطية من الأعداد الصحيحة. نودّ أن نشكر أعضاء فريقَي العمل WebAssembly وWebGPU في W3C، بما في ذلك مورِّدي المتصفِّحات الآخرين.
نتوجّه بالشكر إلى فِرق الذكاء الاصطناعي وتعلُّم الآلة في Google ومجتمع البرامج المفتوحة المصدر على تعاونهم الرائع. وبالطبع، جميع زملائنا في الفريق الذين يجعلون كل هذا ممكنًا.