توصیف محاسبات Tensor
بسیار ساده است، اما زمان و نحوه انجام این محاسبه به این بستگی دارد که کدام backend برای Tensor
s استفاده می شود و چه زمانی نتایج در CPU میزبان مورد نیاز است.
در پشت صحنه، عملیات روی Tensor
به شتابدهندههایی مانند GPU یا TPU ارسال میشود، یا زمانی که هیچ شتابدهندهای در دسترس نیست، روی CPU اجرا میشود. این به طور خودکار برای شما اتفاق می افتد و انجام محاسبات موازی پیچیده را با استفاده از یک رابط سطح بالا آسان می کند. با این حال، درک اینکه چگونه این ارسال رخ میدهد و بتوانید آن را برای عملکرد بهینه شخصیسازی کنید، میتواند مفید باشد.
Swift for TensorFlow دو پشتیبان برای انجام محاسبات تسریع شده دارد: حالت مشتاق TensorFlow و X10. باطن پیشفرض حالت مشتاق TensorFlow است، اما میتوان آن را نادیده گرفت. یک آموزش تعاملی در دسترس است که شما را در استفاده از این باطن های مختلف راهنمایی می کند.
حالت مشتاق TensorFlow
باطن حالت مشتاق TensorFlow از TensorFlow C API استفاده می کند تا هر عملیات Tensor
را در صورت مواجه شدن با آن به یک GPU یا CPU ارسال کند. سپس نتیجه آن عملیات بازیابی شده و به عملیات بعدی منتقل می شود.
درک این ارسال عملیات به عملیات ساده است و نیازی به پیکربندی صریح در کد شما ندارد. با این حال، در بسیاری از موارد به دلیل سربار ناشی از ارسال بسیاری از عملیات کوچک، همراه با عدم ادغام عملیات و بهینهسازی که میتواند در صورت وجود نمودارهای عملیات رخ دهد، منجر به عملکرد بهینه نمیشود. در نهایت، حالت مشتاق TensorFlow با TPU ها ناسازگار است و فقط با CPU و GPU قابل استفاده است.
X10 (ردیابی مبتنی بر XLA)
X10 نام باطن Swift برای TensorFlow است که از ردیابی تانسور تنبل و کامپایلر بهینهسازی XLA استفاده میکند تا در بسیاری از موارد عملکرد را نسبت به ارسال عملیات به عملیات به طور قابل توجهی بهبود بخشد. علاوه بر این، سازگاری را برای TPU ها اضافه می کند، شتاب دهنده هایی که به طور خاص برای انواع محاسبات موجود در مدل های یادگیری ماشین بهینه شده اند.
استفاده از X10 برای محاسبات Tensor
پیشفرض نیست، بنابراین باید در این backend شرکت کنید. این کار با مشخص کردن اینکه یک Tensor
روی یک دستگاه XLA قرار می گیرد انجام می شود:
let tensor1 = Tensor<Float>([0.0, 1.0, 2.0], on: Device.defaultXLA)
let tensor2 = Tensor<Float>([1.5, 2.5, 3.5], on: Device.defaultXLA)
پس از آن نقطه، توصیف یک محاسبه دقیقاً مشابه حالت مشتاق TensorFlow است:
let tensor3 = tensor1 + tensor2
جزئیات بیشتری را می توان هنگام ایجاد یک Tensor
ارائه کرد، مانند نوع شتاب دهنده ای که باید استفاده کرد و حتی کدام یک، در صورت وجود چندین مورد. به عنوان مثال، یک Tensor
را می توان در دستگاه TPU دوم ایجاد کرد (با فرض اینکه برای میزبانی که برنامه روی آن اجرا می شود قابل مشاهده باشد) با استفاده از موارد زیر:
let tpuTensor = Tensor<Float>([0.0, 1.0, 2.0], on: Device(kind: .TPU, ordinal: 1, backend: .XLA))
هیچ حرکت ضمنی Tensor
s بین دستگاه ها انجام نمی شود، بنابراین اگر دو Tensor
در دستگاه های مختلف در یک عملیات با هم استفاده شوند، خطای زمان اجرا رخ می دهد. برای کپی دستی محتویات یک Tensor
در یک دستگاه جدید، می توانید از مقداردهی اولیه Tensor(copying:to:)
استفاده کنید. برخی از سازههای مقیاس بزرگتر که حاوی Tensor
در داخل خود هستند، مانند مدلها و بهینهسازها، عملکردهای کمکی برای انتقال تمام Tensor
داخلی خود به یک دستگاه جدید در یک مرحله دارند.
برخلاف حالت اشتیاق TensorFlow، عملیات با استفاده از باطن X10 به صورت جداگانه انجام نمی شود. در عوض، ارسال به یک شتاب دهنده تنها با خواندن مقادیر محاسبه شده به میزبان یا با قرار دادن یک مانع صریح آغاز می شود. روش کار به این صورت است که زمان اجرا از مقدار خوانده شده به هاست (یا آخرین محاسبه قبل از یک مانع دستی) شروع می شود و نمودار محاسباتی را که منجر به آن مقدار می شود ردیابی می کند.
سپس این نمودار ردیابی شده به نمایش میانی XLA HLO تبدیل می شود و به کامپایلر XLA ارسال می شود تا برای اجرا در شتاب دهنده بهینه سازی و کامپایل شود. از آنجا کل محاسبات به شتاب دهنده ارسال می شود و نتیجه نهایی به دست می آید.
محاسبه فرآیندی زمانبر است، بنابراین X10 بهتر است با محاسبات موازی که از طریق یک نمودار بیان میشوند و بارها انجام میشوند استفاده شود. از مقادیر هش و کش استفاده می شود به طوری که نمودارهای یکسان برای هر پیکربندی منحصر به فرد فقط یک بار کامپایل می شوند.
برای مدلهای یادگیری ماشین، فرآیند آموزش اغلب شامل یک حلقه است که در آن مدل بارها و بارها تحت یک سری محاسبات قرار میگیرد. شما میخواهید که هر یک از این پاسها بهعنوان تکرار همان ردیابی دیده شود، نه یک نمودار طولانی با واحدهای مکرر درون آن. این کار با درج دستی یک فراخوانی به تابع LazyTensorBarrier()
در مکانهایی از کد شما که میخواهید یک ردیابی پایان یابد، فعال میشود.
پشتیبانی با دقت ترکیبی در X10
آموزش با دقت ترکیبی از طریق X10 پشتیبانی میشود و هر دو API سطح پایین و سطح بالا برای کنترل آن ارائه شدهاند. API سطح پایین دو ویژگی محاسبه شده را ارائه می دهد: toReducedPrecision
و toFullPrecision
که بین دقت کامل و کاهش یافته تبدیل می شوند، همراه با isReducedPrecision
برای پرس و جو از دقت. علاوه بر Tensor
s، مدل ها و بهینه سازها را می توان با استفاده از این API بین دقت کامل و کاهش یافته تبدیل کرد.
توجه داشته باشید که تبدیل به دقت کاهش یافته، نوع منطقی یک Tensor
را تغییر نمی دهد. اگر t
یک Tensor<Float>
باشد، t.toReducedPrecision
نیز یک Tensor<Float>
با نمایش زیربنایی با دقت کاهش یافته است.
مانند دستگاه ها، عملیات بین تانسورهای با دقت متفاوت مجاز نیست. این امر از ارتقای بیصدا و ناخواسته به شناورهای 32 بیتی جلوگیری میکند، که تشخیص آن توسط کاربر دشوار است.