این سند ادامه پیشرفتهای WebAssembly و WebGPU برای هوش مصنوعی وب سریعتر، بخش 1 است. توصیه می کنیم قبل از ادامه این پست را بخوانید یا سخنرانی را در IO 24 تماشا کنید .
WebGPU
WebGPU به برنامه های کاربردی وب امکان دسترسی به سخت افزار GPU مشتری را برای انجام محاسبات کارآمد و بسیار موازی می دهد. از زمان راهاندازی WebGPU در کروم ، شاهد نمایشهای باورنکردنی هوش مصنوعی (AI) و یادگیری ماشینی (ML) در وب هستیم.
برای مثال، Web Stable Diffusion نشان داد که میتوان از هوش مصنوعی برای تولید تصاویر از متن، مستقیماً در مرورگر استفاده کرد. در اوایل سال جاری، تیم Mediapipe خود گوگل ، پشتیبانی آزمایشی را برای استنتاج مدل زبان بزرگ منتشر کرد.
انیمیشن زیر Gemma ، مدل زبان بزرگ منبع باز Google (LLM) را نشان می دهد که به طور کامل روی دستگاه در کروم اجرا می شود، در زمان واقعی.
نسخه نمایشی زیر از Hugging Face از Meta's Segment Anything Model ماسک های شی با کیفیت بالا را به طور کامل بر روی مشتری تولید می کند.
اینها تنها چند پروژه شگفت انگیز است که قدرت WebGPU را برای هوش مصنوعی و ML به نمایش می گذارد. WebGPU به این مدل ها و سایر مدل ها اجازه می دهد تا به طور قابل توجهی سریعتر از آنچه می توانند روی CPU اجرا شوند.
معیار WebGPU Hugging Face برای جاسازی متن، در مقایسه با یک CPU از همان مدل، سرعتهای فوقالعادهای را نشان میدهد. در لپتاپ Apple M1 Max، WebGPU بیش از 30 برابر سریعتر بود. برخی دیگر گزارش کرده اند که WebGPU این معیار را بیش از 120 برابر تسریع می کند.
بهبود ویژگی های WebGPU برای هوش مصنوعی و ML
WebGPU برای مدلهای هوش مصنوعی و ML که به لطف پشتیبانی از شیدرهای محاسباتی میتوانند میلیاردها پارامتر داشته باشند، عالی است. شیدرهای محاسباتی روی GPU اجرا می شوند و به اجرای عملیات آرایه موازی روی حجم زیادی از داده کمک می کنند.
در میان پیشرفتهای متعدد WebGPU در سال گذشته، ما به افزودن قابلیتهای بیشتری برای بهبود عملکرد ML و AI در وب ادامه دادهایم. اخیراً دو ویژگی جدید راه اندازی کردیم: ممیز شناور 16 بیتی و محصولات نقطه صحیح بسته بندی شده.
ممیز شناور 16 بیتی
به یاد داشته باشید، بارهای کاری ML نیازی به دقت ندارند . shader-f16
قابلیتی است که امکان استفاده از نوع f16 را در زبان سایه زنی WebGPU فراهم می کند. این نوع ممیز شناور به جای 32 بیت معمول، 16 بیت می گیرد. f16 برد کمتری دارد و دقت کمتری دارد، اما برای بسیاری از مدل های ML این کافی است.
این ویژگی به چند روش کارایی را افزایش می دهد:
کاهش حافظه : تانسورهای دارای عناصر f16 نیمی از فضا را اشغال میکنند که استفاده از حافظه را به نصف کاهش میدهد. محاسبات GPU اغلب در پهنای باند حافظه با تنگنا مواجه می شوند، بنابراین نیمی از حافظه اغلب به این معنی است که شیدرها دو برابر سریعتر اجرا می شوند. از نظر فنی، برای صرفه جویی در پهنای باند حافظه به f16 نیاز ندارید. این امکان وجود دارد که داده ها را در قالبی با دقت کم ذخیره کنید و سپس برای محاسبه آن را به f32 کامل در سایه زن گسترش دهید. اما، GPU قدرت محاسباتی بیشتری را برای بسته بندی و باز کردن داده ها صرف می کند.
کاهش تبدیل داده : f16 از محاسبات کمتری با به حداقل رساندن تبدیل داده استفاده می کند. داده های با دقت پایین را می توان ذخیره کرد و سپس مستقیماً بدون تبدیل استفاده کرد.
افزایش موازی کاری : GPUهای مدرن قادرند مقادیر بیشتری را به طور همزمان در واحدهای اجرایی GPU جای دهند و به آن اجازه می دهد تعداد بیشتری از محاسبات موازی را انجام دهد. به عنوان مثال، یک GPU که تا 5 تریلیون عملیات ممیز شناور f32 در ثانیه را پشتیبانی می کند، ممکن است از 10 تریلیون عملیات ممیز شناور f16 در هر ثانیه پشتیبانی کند.
WebLLM پروژه ای است که می تواند چندین مدل زبان بزرگ را اجرا کند. از Apache TVM ، یک چارچوب کامپایلر یادگیری ماشین منبع باز استفاده می کند.
از WebLLM خواستم تا با استفاده از مدل پارامتر هشت میلیاردی Llama 3 برای سفر به پاریس برنامه ریزی کند. نتایج نشان می دهد که در مرحله پیش پر کردن مدل، f16 2.1 برابر سریعتر از f32 است. در مرحله رمزگشایی، بیش از 1.3 برابر سریعتر است.
برنامهها ابتدا باید تأیید کنند که آداپتور GPU از f16 پشتیبانی میکند و اگر در دسترس است، هنگام درخواست دستگاه GPU، آن را به صراحت فعال کنید. اگر 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);
}
سپس، در سایه زن های WebGPU خود، باید به صراحت f16 را در بالا فعال کنید. پس از آن، شما آزاد هستید که از آن در سایه زن مانند هر نوع داده شناور دیگری استفاده کنید.
// 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) به خوبی کار میکنند. این در بین LLM ها و مدل های تصویر برای تقسیم بندی و تشخیص اشیا محبوب است. همانطور که گفته شد، کیفیت خروجی برای مدلها با دقت کمتری کاهش مییابد، بنابراین کوانتیزاسیون 8 بیتی برای هر کاربرد مناسب نیست.
تعداد نسبتا کمی از GPU ها به طور بومی از مقادیر 8 بیتی پشتیبانی می کنند. اینجاست که محصولات نقطه صحیح بسته بندی شده وارد می شوند. ما DP4a را در Chrome 123 ارسال کردیم.
پردازندههای گرافیکی مدرن دستورالعملهای خاصی برای گرفتن دو عدد صحیح 32 بیتی دارند، هر کدام را به عنوان 4 عدد صحیح 8 بیتی متوالی تفسیر میکنند و محصول نقطهای را بین اجزای آنها محاسبه میکنند.
این به ویژه برای هوش مصنوعی و یادگیری ماشین مفید است زیرا هسته های ضرب ماتریس از محصولات نقطه بسیار بسیار زیادی تشکیل شده اند.
برای مثال، بیایید یک ماتریس 4 در 8 را با یک بردار 8 x 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
بررسی کنید. اگر GPU به طور بومی از محصولات packed dot پشتیبانی نمیکند، مرورگر پیادهسازی خود را به صورت polyfill میکند.
// main.js
if (navigator.gpu.wgslLanguageFeatures.has('packed_4x8_integer_dot_product')) {
// Use dot4U8Packed, dot4I8Packed builtin
// functions in the shaders.
}
تفاوت قطعه کد زیر (تفاوت) تغییرات مورد نیاز برای استفاده از محصولات عدد صحیح بسته بندی شده در یک سایه زن WebGPU را برجسته می کند.
قبل - یک سایه زن WebGPU که محصولات نقطه جزئی را در متغیر "sum" جمع می کند. در انتهای حلقه، «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 بیتی و هم محصولات نقطه صحیح بسته بندی شده، ویژگی های ارسال شده در کروم هستند که هوش مصنوعی و ML را تسریع می کنند. ممیز شناور 16 بیتی زمانی در دسترس است که سختافزار از آن پشتیبانی کند، و Chrome محصولات نقطهصحیح بستهبندی شده را در همه دستگاهها پیادهسازی میکند.
امروز میتوانید از این ویژگیها در Chrome Stable برای دستیابی به عملکرد بهتر استفاده کنید.
ویژگی های پیشنهادی
به آینده، ما دو ویژگی دیگر را بررسی می کنیم: زیرگروه ها و ضرب ماتریس تعاونی.
ویژگی زیرگروهها موازیسازی سطح SIMD را برای برقراری ارتباط یا انجام عملیات ریاضی جمعی، مانند مجموع بیش از 16 عدد، قادر میسازد. این امکان به اشتراک گذاری داده های بین رشته ای کارآمد را فراهم می کند. زیرگروهها در APIهای GPUهای مدرن، با نامهای متفاوت و در اشکال کمی متفاوت پشتیبانی میشوند.
ما مجموعه مشترک را در یک پیشنهاد تقطیر کرده ایم که به گروه استانداردسازی WebGPU برده ایم. و، ما زیرگروههایی را در کروم در پشت پرچم آزمایشی نمونهسازی کردهایم و نتایج اولیه خود را وارد بحث کردهایم. مسئله اصلی نحوه اطمینان از رفتار قابل حمل است.
ضرب ماتریس تعاونی جدیدتر به GPU اضافه شده است. یک ضرب ماتریس بزرگ را می توان به چندین ضرب ماتریس کوچکتر تقسیم کرد. ضرب ماتریس تعاونی ضربات را روی این بلوک های کوچکتر با اندازه ثابت در یک مرحله منطقی انجام می دهد. در این مرحله، گروهی از نخ ها به طور موثر برای محاسبه نتیجه همکاری می کنند.
ما پشتیبانی در APIهای GPU اساسی را بررسی کردیم و قصد داریم پیشنهادی را به گروه استانداردسازی WebGPU ارائه کنیم. همانطور که در مورد زیر گروه ها، ما انتظار داریم که بیشتر بحث در مورد قابلیت حمل متمرکز شود.
برای ارزیابی عملکرد عملیات زیرگروه، در یک برنامه واقعی، ما پشتیبانی آزمایشی برای زیرگروه ها را در MediaPipe ادغام کردیم و آن را با نمونه اولیه Chrome برای عملیات زیرگروه آزمایش کردیم.
ما از زیرگروهها در هستههای GPU فاز پیشپرکردن مدل زبان بزرگ استفاده کردیم، بنابراین من فقط افزایش سرعت را برای مرحله پیشپر گزارش میدهم. در پردازندههای گرافیکی اینتل، میبینیم که زیرگروهها دو و نیم برابر سریعتر از پایه کار میکنند. با این حال، این پیشرفت ها در GPU های مختلف سازگار نیستند.
نمودار بعدی نتایج استفاده از زیرگروهها را برای بهینهسازی یک میکروبنچمارک ضرب ماتریسی در چندین پردازنده گرافیکی مصرفکننده نشان میدهد. ضرب ماتریس یکی از عملیات های سنگین تر در مدل های زبان بزرگ است. داده ها نشان می دهد که در بسیاری از پردازنده های گرافیکی، زیرگروه ها سرعت را دو، پنج و حتی سیزده برابر سطح پایه افزایش می دهند. با این حال، توجه داشته باشید که در اولین GPU، زیرگروه ها اصلاً بهتر نیستند.
بهینه سازی GPU مشکل است
در نهایت، بهترین راه برای بهینه سازی GPU بستگی به GPU ارائه شده توسط مشتری دارد. استفاده از ویژگیهای جدید GPU همیشه آنطور که انتظار دارید نتیجه نمیدهد، زیرا میتواند عوامل پیچیده زیادی در آن دخیل باشند. بهترین استراتژی بهینه سازی در یک GPU ممکن است بهترین استراتژی در GPU دیگر نباشد.
شما می خواهید پهنای باند حافظه را به حداقل برسانید، در حالی که به طور کامل از رشته های محاسباتی GPU استفاده می کنید.
الگوهای دسترسی به حافظه نیز می توانند بسیار مهم باشند. هنگامی که رشته های محاسباتی با الگوی بهینه برای سخت افزار به حافظه دسترسی پیدا می کنند، پردازنده های گرافیکی بسیار بهتر عمل می کنند. مهم: باید انتظار ویژگی های عملکرد متفاوتی را در سخت افزارهای مختلف GPU داشته باشید. ممکن است لازم باشد بسته به پردازنده گرافیکی، بهینه سازی های مختلفی را اجرا کنید.
در نمودار زیر، الگوریتم ضرب ماتریس یکسانی را انتخاب کردهایم، اما بعد دیگری را برای نشان دادن بیشتر تأثیر استراتژیهای بهینهسازی مختلف، و پیچیدگی و واریانس در بین GPUهای مختلف، اضافه کردهایم. ما در اینجا یک تکنیک جدید را معرفی کرده ایم که آن را "Swizzle" می نامیم. Swizzle الگوهای دسترسی به حافظه را بهینه می کند تا برای سخت افزار بهینه تر باشد.
می توانید ببینید که چرخش حافظه تأثیر قابل توجهی دارد. گاهی اوقات حتی از زیرگروه ها تاثیرگذارتر است. در GPU 6، swizzle سرعت 12 برابر را ارائه می دهد، در حالی که زیرگروه ها سرعت 13 برابر را ارائه می دهند. در مجموع، سرعت فوق العاده 26 برابری دارند. برای سایر پردازندههای گرافیکی، گاهی اوقات swizzle و زیرگروهها با هم بهتر از هر یک به تنهایی عمل میکنند. و در سایر پردازندههای گرافیکی، استفاده انحصاری از Swizzle بهترین عملکرد را دارد.
تنظیم و بهینه سازی الگوریتم های GPU برای کارکرد خوب روی هر قطعه سخت افزاری، می تواند به تخصص زیادی نیاز داشته باشد. اما خوشبختانه تعداد زیادی کار با استعداد در چارچوبهای کتابخانههای سطح بالاتر، مانند Mediapipe ، Transformers.js ، Apache TVM ، ONNX Runtime Web و موارد دیگر وجود دارد.
کتابخانهها و فریمورکها برای مدیریت پیچیدگیهای مدیریت معماریهای مختلف GPU و تولید کدهای مخصوص پلتفرم که به خوبی روی کلاینت اجرا میشوند، موقعیت خوبی دارند.
غذای آماده
تیم Chrome همچنان به توسعه استانداردهای WebAssembly و WebGPU برای بهبود پلت فرم وب برای بارهای کاری یادگیری ماشین کمک می کند. ما روی محاسبات اولیه سریعتر، تعامل بهتر در استانداردهای وب سرمایهگذاری میکنیم و مطمئن میشویم که مدلهای بزرگ و کوچک قادر به اجرای کارآمد در دستگاهها هستند.
هدف ما به حداکثر رساندن قابلیتهای پلتفرم در عین حفظ بهترین وب است: دسترسی، قابلیت استفاده و قابلیت حمل. و ما این کار را به تنهایی انجام نمی دهیم. ما در حال همکاری با سایر فروشندگان مرورگر در W3C و بسیاری از شرکای توسعه هستیم.
امیدواریم هنگام کار با WebAssembly و WebGPU موارد زیر را به خاطر بسپارید:
- استنتاج هوش مصنوعی اکنون در وب، در همه دستگاهها در دسترس است. این مزیت اجرا بر روی دستگاه های سرویس گیرنده، مانند کاهش هزینه سرور، تاخیر کم و افزایش حریم خصوصی را به همراه دارد.
- در حالی که بسیاری از ویژگیهای مورد بحث عمدتاً به نویسندگان فریمورک مربوط میشوند، برنامههای شما میتوانند بدون هزینه زیاد سود ببرند.
- استانداردهای وب سیال و در حال تکامل هستند و ما همیشه به دنبال بازخورد هستیم. شما را برای WebAssembly و WebGPU به اشتراک بگذارید.
قدردانی
مایلیم از تیم گرافیک وب اینتل تشکر کنیم که در هدایت WebGPU f16 و ویژگیهای محصول اعداد صحیح مفید بودند. مایلیم از سایر اعضای گروه های کاری WebAssembly و WebGPU در W3C، از جمله سایر فروشندگان مرورگر، تشکر کنیم.
از تیمهای هوش مصنوعی و ML هم در Google و هم در جامعه منبع باز برای شرکای باورنکردنی تشکر میکنیم. و البته همه هم تیمی های ما که همه اینها را ممکن می کنند.