Local AI

بهینه‌سازی استنباد ترکیبی CPU-GPU: انتقال هدرهای توجه

اجرای مدل‌های زبانی بزرگ به صورت محلی اغلب با یک مانع بزرگ مواجه می‌شود: محدودیت‌های VRAM. حتی با وجود GPUهای گسسته قدرتمند، مدل‌هایی که از ظرفیت حافظه ویدیویی شما فراتر می‌روند، توسعه‌دهندگان را مجبور می‌کنند بین کوانتیزاسیون (با از دست دادن دقت) یا استنباد کند CPU انتخاب کنند. در Apple Silicon و پلتفرم‌های جدیدتر AMD، معماری حافظه یکپارچه (UMA) یک مسیر سوم را ارائه می‌دهد. با در نظر گرفتن RAM سیستم و حافظه GPU به عنوان یک استخر واحد، می‌توانیم اجزای خاصی، مانند هدرهای توجه، را به صورت استراتژیک منتقل کنیم تا از کل پهنای باند حافظه استفاده کنیم و در عین حال محاسبات حیاتی را روی GPU نگه داریم.

چرا هدرهای توجه کاندیداهای ایده‌آل هستند

همه بخش‌های یک مدل ترنسفورمر یکسان ساخته نشده‌اند. ضرب ماتریس-ماتریس در شبکه‌های فید-فورارد (FFN) محاسبات سنگین است و بیشترین بهره را از شتاب‌دهی GPU می‌برد. با این حال، عملیات توجه (به ویژه پروجکشن‌های Q، K، V و نرمال‌سازی softmax بعدی) می‌توانند بیشتر تحت تأثیر پهنای باند حافظه باشند، به ویژه در طولانی‌تر شدن طول دنباله‌ها.

در یک تنظیمات UMA، «GPU» در واقع یک هم‌پردازنده است که می‌تواند با سرعت بالا به RAM سیستم دسترسی پیدا کند (اغلب بیش از 100GB/s در چیپ‌های M2/M3/M4 Max). بنابراین، انتقال ماتریس‌های وزن هدرهای توجه به RAM سیستم همان جریمه‌ای را تحمیل نمی‌کند که روی یک GPU گسسته متصل به PCIe ایجاد می‌شود. دریافت داده به اندازه کافی سریع است که سود محاسباتی ناشی از نگه داشتن مدل در حافظه با پهنای باند بالا اغلب از تأخیر اندک دسترسی به آن از RAM سیستم «کند» بیشتر است.

استراتژی پیاده‌سازی در Python

با استفاده از کتابخانه‌هایی مانند PyTorch یا موتورهای استنباد تخصصی، می‌توانیم به صورت دستی کنترل جایگاه دستگاه را داشته باشیم. کلید کار این است که شاخص‌های لایه‌ها را شناسایی کرده و تانسورهای پارامتر خاصی را به دستگاه 'cpu' منتقل کنیم، در حالی که بقیه را روی دستگاه 'cuda' یا 'mps' نگه می‌داریم.

import torch

def hybrid_load_model(model, offload_ratio=0.5, layer_type='attention'):
    """
    یک کسری از لایه‌های توجه را به CPU منتقل می‌کند.
    فرض می‌کند ساختار استاندارد ترنسفورمر را دارد.
    """
    layers = list(model.named_parameters())
    total_layers = len([l for l in layers if 'self_attn' in l[0]])
    
    # تعیین لایه‌هایی که باید منتقل شوند
    offload_count = int(total_layers * offload_ratio)
    
    # استراتژی: انتقال زودهنگام‌ترین لایه‌ها (اغلب کمتر حیاتی برای لاگیت‌های نهایی)
    # یا جایگزینی آن‌ها. در اینجا ما N بلوک توجه اول را منتقل می‌کنیم.
    target_layers = [l[0] for l in layers if 'self_attn' in l[0]][:offload_count]

    for name, param in model.named_parameters():
        if name in target_layers:
            param.data = param.data.to('cpu')
            param.data = param.data.float() # حفظ دقت بالاتر در RAM
        else:
            # اطمینان حاصل کنید که لایه‌های حیاتی روی GPU باقی می‌مانند
            if param.device.type != 'cuda' and param.device.type != 'mps':
                 param.data = param.data.to('cuda' if torch.cuda.is_available() else 'mps')

    model.eval()
    return model

# مثال استفاده
# model = load_llama_model(path="llama-7b-gguf")
# hybrid_model = hybrid_load_model(model, offload_ratio=0.3, layer_type='attention')

مدیریت سربار انتقال داده

اگرچه UMA هزینه‌های انتقال را به حداقل می‌رساند، اما آن‌ها را حذف نمی‌کند. هر عبور به جلو نیاز به انتقال وزن‌های توجه به GPU برای محاسبه دارد (یا محاسبه آن‌ها روی CPU اگر بک‌اند از آن پشتیبانی کند). برای به حداقل رساندن این موضوع، در نظر بگیرید لایه‌های منتقل شده را دسته‌بندی (batching) کنید. به جای انتقال وزن‌ها لایه به لایه، چندین هدر توجه را گروه‌بندی کرده و آن‌ها را در یک عملیات دسته‌ای واحد منتقل کنید.

علاوه بر این، از torch.compile با گاردهای شکل پویا استفاده کنید. کامپایلر PyTorch اغلب می‌تواند عملیات‌های انتقال داده را با راه‌اندازی هسته‌ها (kernel launches) ادغام کند و تأخیر را پشت عملیات‌های محدودکننده محاسباتی پنهان کند. در Apple Silicon، اطمینان حاصل کنید که از بک‌اند Metal Performance Shaders (MPS) استفاده می‌کنید که پشتیبانی بومی از تخصیص حافظه یکپارچه دارد.

مثال عملی: اجرای یک مدل 13B

یک مدل با 13 میلیارد پارامتر را در نقطه شناور 16 بیتی در نظر بگیرید. این کار به حدود 26 گیگابایت حافظه نیاز دارد. یک GPU استاندارد 16 گیگابایتی نمی‌تواند آن را جای دهد. با این حال، یک MacBook Pro با 32 گیگابایت حافظه یکپارچه می‌تواند.

  1. جایگذاری‌های پایه (Base We Embeddings): روی GPU نگه دارید (حیاتی برای سرعت).
  2. لایه‌های FFN: روی GPU نگه دارید (محاسبات سنگین).
  3. هدرهای توجه (لایه‌های 1-16): به CPU/RAM منتقل کنید.

با انتقال 50 درصد هدرهای توجه، حدود 6.5 گیگابایت VRAM GPU آزاد می‌شود. این امکان می‌دهد مدل بارگذاری شود. در طول استنباد، GPU محاسبات سنگین FFN را انجام می‌دهد، در حالی که CPU پروجکشن توجه را مدیریت می‌کند. روی یک چیپ M3 Max، این رویکرد ترکیبی می‌تواند 25 تا 35 توکن در ثانیه را به دست آورد، در مقایسه با کمتر از 5 توکن در ثانیه در یک تنظیمات خالص CPU.

نتیجه‌گیری

استنباد ترکیبی CPU-GPU روی معماری‌های حافظه یکپارچه فقط یک راه فرار نیست؛ بلکه یک استراتژی اصلی برای توسعه هوش مصنوعی محلی است. با انتقال هوشمندانه هدرهای توجه، شما قابلیت اجرای مدل‌های بزرگ‌تر را بدون قربانی کردن سرعت تولید توکن آزاد می‌کنید. با ادامه تکامل سخت‌افزار، تسلط بر این استراتژی‌های جایگذاری کلید اصلی برای پیش بردن مرزهای عملکرد LLM محلی خواهد بود. با پروفایل کردن مصرف حافظه مدل خود شروع کنید، اجزای محدودکننده پهنای باند را شناسایی کنید و با انتقال جزئی آزمایش کنید تا نقطه بهینه برای پیکربندی سخت‌افزاری خاص خود را پیدا کنید.

Share: