در مدلهای چندوجهی تصویر و زبان، یکی از مهمترین چالشها اتصال معنایی کلمات به نواحی متناظر در تصویر است. اتصال سادهی بردارهای ویژگی معمولاً برای وظایف دقیقی مثل تولید کپشن پاسخگو نیست؛ چرا که هر کلمه در حین تولید باید بتواند بهصورت پویا به بخشهای متفاوتی از تصویر نگاه کند. این دقیقاً همان نقشی است که مکانیزم کراساتنشن (Cross-Attention) بر عهده دارد.
در این درس معماری ادغام دیرهنگام (Late fusion) را بررسی میکنیم؛ راهکاری که مدلهای شاخصی مثل Flamingo و BLIP-2 استفاده میکنند تا ضمن حفظ توانایی پردازش متن خالص، تصویر را یک بار پردازش کرده و در تمام مراحل تولید کلمات با کمترین هزینهی محاسباتی فراخوانی کنند. تنظیم دقیق ابعاد ماتریسها و نحوهی اعمال ماسک علّی (Causal mask) روی متن بدون تداخل با ویژگیهای تصویر، از مباحث عملی این پیادهسازی است.
لایهی تصویر کردن (Projection layer)، یک بردار تصویر را با یک بردار کپشن همتراز میکند؛ اما یک دیکودر تصویر-زبان واقعی به این نیاز دارد که تکتک توکنهای متن به تمام توکنهای تکه (Patch) تصویر اتنشن داشته باشند تا مدل بتواند هر کلمه را به ناحیهای مشخص ارجاع دهد (Grounding). این ارجاعدهی دقیقاً با کراساتنشن (Cross-Attention) اتفاق میافتد: متن کوئری میزند و کلیدها و مقادیر تصویر پاسخ میدهند. در این درس، بلوک کراساتنشن، خوداتنشن علّی متن و ابعاد ماسکهایی را که برای کارکرد درست هر دو لازم است، میسازیم.
اهداف یادگیری
- پیادهسازی کراساتنشن چندسر بهطوری که جریان کوئری، متن و جریان کلید/مقدار، تصویر باشد.
- ساخت یک بلوک دیکودر: خوداتنشن علّی + کراساتنشن + شبکهی پیشخور (Feed-forward).
- تنظیم درست ابعاد ماسکها: ماسک علّی (Causal mask) برای خوداتنشن و بدون ماسک برای کراساتنشن.
- اجرای یک گذر رو به جلو (Forward pass) با دستهای از توکنهای متنی و مجموعهای ثابت از توکنهای تصویر.
صورت مسئله
پیوستن توکنهای تصویر و توکنهای متن در قالب یک توالی واحد، یکی از گزینههای ادغام است (ادغام زودهنگام یا Early fusion؛ همان مسیری که Chameleon و Emu3 رفتهاند). کراساتنشن گزینهی دیگر است (ادغام دیرهنگام یا Late fusion؛ راهکاری که Flamingo معرفی کرد و از آن زمان تمام دیکودرهای با معماری مشابه Flamingo از آن الگو گرفتهاند). در ادغام دیرهنگام، دیکودر متن فقط روی توکنهای متنی اجرا میشود و در هر لایه از طریق کراساتنشن به سراغ جریان تصویر میرود.
ادغام دیرهنگام دو مزیت دارد: نخست، جریان متن دستنخورده باقی میماند و مدل قابلیتهای پردازش متنی صرف خود را حفظ میکند. دوم، جریان تصویر برای هر تصویر تنها یک بار محاسبه شده و برای تمام مراحل رمزگشایی (Decode) دوباره استفاده میشود؛ بنابراین تولید متن حتی برای کپشنهای طولانی هم ارزان است. بهای این مزایا، یک زیرلایهی اتنشن اضافه در هر بلوک است.
مفاهیم پایه
ابعاد ماسکها
دو بخش اتنشن داخل یک بلوک دیکودر به ماسکهای متفاوتی نیاز دارند:
| اتنشن | طول کوئری | طول کلید | ماسک | دلیل |
|---|---|---|---|---|
| خوداتنشن | Nt (متن) |
Nt (متن) |
علّی: مثلثی پایینی (Nt, Nt) |
توکنهای متن هنگام تولید خودبازگشتی نباید به جلو نگاه کنند |
| کراساتنشن | Nt (متن) |
Nv (تصویر) |
بدون ماسک | تمام تصویر برای هر موقعیت متنی قابل مشاهده است |
در این درس تابعی برای اعتبارسنجی ابعاد تعبیه شده تا اشتباه در جابهجا گرفتن آنها، بهجای خراب کردن خاموش روند تابع زیان (Loss)، بلافاصله در قالب یک خطای ValueError خودش را نشان دهد.
چرا کراساتنشن به ماسک نیاز ندارد؟
پیش از تولید هر کلمهای از متن، تصویر بهطور کامل مشاهده میشود. توکن t از کپشن میتواند به هر تکهای از تصویر اتنشن داشته باشد؛ چرا که ترتیبی زمانی بین تکههای تصویر وجود ندارد. برخی نسخههای Flamingo هنگام ترکیب متناوب چند تصویر و بخشهای متنی، الگوی ماسکگذاری اختصاصی برای هر نمونه اضافه میکنند؛ اما برای حالت تکتصویر به همراه یک کپشن، کراساتنشن همهچیز را کامل میبیند.
کش کلید/مقدار
کلیدها و مقادیر تصویر در شروع مرحلهی رمزگشایی یک بار محاسبه و در یک کش نگهداری میشوند. هر توکن جدید متن، بدون محاسبهی مجدد، از همان دادههای کش استفاده میکند. همین ویژگی سرعت بالای تولید کپشن در زمان استنتاج را تضمین میکند: مدل سنگین ViT یک بار اجرا میشود و کراساتنشن در هر گام کلیدها و مقادیر آن را دوباره به کار میگیرد. این درس ساختار کش را پیاده کرده و مسیر مبتنی بر استفاده از کش (Cache-hit) را آزمایش میکند.
ساختار بلوک دیکودر
ترتیب اجرای یک بلوک دیکودر به این صورت است: پیشنرمالسازی (Pre-LN) -> خوداتنشن -> اتصال باقیمانده (Residual) -> پیشنرمالسازی -> کراساتنشن -> اتصال باقیمانده -> پیشنرمالسازی -> شبکهی پیشخور -> اتصال باقیمانده؛ یعنی سه زیرلایه که هر کدام لایهی LayerNorm خود را دارند. مقالهی Flamingo یک گیت تانژانت هذلولوی (tanh) یادگیرنده با مقدار اولیهی صفر به کراساتنشن اضافه کرد تا مدل زبانی منجمد کارش را بدون تغییر آغاز کند و پایداری آموزش حفظ شود؛ ساختار استاندارد و پایهای که اینجا استفاده میشود (Baseline)، فاقد گیت است.
class DecoderBlock:
def forward(self, text_tokens, image_tokens, text_mask, cross_mask):
text_tokens = text_tokens + self.self_attn(self.ln1(text_tokens),
mask=text_mask)
text_tokens = text_tokens + self.cross_attn(self.ln2(text_tokens),
image_tokens,
mask=cross_mask)
text_tokens = text_tokens + self.ffn(self.ln3(text_tokens))
return text_tokens
پیادهسازی
فایل code/main.py این بخشها را پیادهسازی میکند:
CrossAttention(hidden, heads): کراساتنشن چندسر با تصویر کردنهای مجزا برایqوkv.CausalSelfAttention(hidden, heads): خوداتنشن با ماسک علّی برگرفته از یک دیکودر استاندارد.DecoderBlock: ترکیب سه زیرلایه با اتصالات باقیمانده و پیشنرمالسازی.VisionLanguageDecoder: دیکودر چهارلایهای که خروجی فرضی یک انکودر تصویر و جدول کوچک امبدینگ متن به آن داده میشود.causal_mask(length): تابعی که یک تنسور بولی مثلثی پایینی با ابعاد(length, length)برمیگرداند.- یک نسخهی نمایشی که دستهای شامل دو توالی متنی به طول 10 را همراه با حافظهی تصویری به طول 197 دریافت کرده و ابعاد خروجی، ابعاد ماسک خوداتنشن و نُرم خروجی کراساتنشن را در هر موقعیت چاپ میکند.
اجرای آن:
python3 code/main.py
خروجی: دیکودر تنسور لاجیتهایی با ابعاد (2, 10, text_vocab) تولید میکند. ابعاد ماسک (10, 10) است. بررسی استفادهی مجدد از کش KV نیز یکسان بودن لاجیتها را میان مسیر کششده و مسیر بدون کش تأیید میکند.
کاربرد در عمل
کراساتنشن در دو خانوادهی مطرح در محیط عملیاتی (پروداکشن) حضور دارد:
- Flamingo و IDEFICS: اضافه کردن یک زیرلایهی کراساتنشن در میان هر K بلوک از یک مدل زبانیِ منجمد. تطبیقدهندهی تصویر-زبان (Adapter) در اینجا، همان بلوک کراساتنشن به همراه گیت آن است.
- BLIP-2: بخش Q-Former از کراساتنشن برای ارتباط میان مجموعهای ثابت از 32 توکن کوئری با ویژگیهای تصویر استفاده میکند و سپس این کوئریها را به فضای امبدینگ مدل زبانی تصویر میکند.
ساختار بلوک این درس مستقیماً روی هر دو معماری پیاده میشود و منطق اعمال ماسک (ماسک علّی برای خوداتنشن، بدون ماسک برای کراساتنشن) در آنها کاملاً یکسان است.
تستها
فایل code/test_main.py موارد زیر را پوشش میدهد:
- ماسک علّی مثلثی پایینی است و با ابعاد بولی مورد انتظار همخوانی دارد.
- خروجی کراساتنشن صرفنظر از طول کلید، همیشه ابعاد
(B, Nt, hidden)دارد. - خروجی مسیر مبتنی بر کش KV تا میزان تلورانس ممیز شناور با مسیر بدون کش منطبق است.
- عدم تطابق ابعاد بین جریانهای متن و تصویر، خطای واضح
ValueErrorرا صادر میکند. - یک گذر کامل رو به جلوی دیکودر، خروجی با ابعاد درست دستهای و توالی تولید میکند.
اجرای تستها:
python3 -m unittest code/test_main.py
تمرینها
-
یک گیت یادگیرندهی tanh به اتصال باقیماندهی کراساتنشن اضافه کنید (ترفند Flamingo) و بررسی کنید که آموزش چگونه از گیت با مقدار اولیهی نزدیک به صفر همگرا میشود. گیت از 0 شروع میشود؛ مدل پیش از ترکیب اطلاعات جریان تصویر، رفتار متنی خالص خود را بازیابی میکند.
-
اتنشن متناوب را پیادهسازی کنید؛ حالتی که همان دیکودر، همزمان چند تصویر به همراه چند قطعهی متنی را دریافت میکند. ماسک کراساتنشن اختصاصیِ نمونه را بسازید تا مانع از اتنشن داشتن قطعهی متنی 2 به تصویر 1 شود.
-
بازدهی و زمان اجرای لایهی کراساتنشن را در مقایسه با خوداتنشن در ابعاد
Nt=64, Nv=576(مشبک 24x24 با رزولوشن بالاتر) ارزیابی کنید. هزینهی کراساتنشنNt * Nvاست و در رزولوشنهای بالای تصویر، بخش عمدهی محاسبات را به خود اختصاص میدهد. -
روی نگاشت اتنشن در سمت کوئریهای کراساتنشن دراپاوت بگذارید و تنوع کپشنهای تولیدی را در نسخهی نمایشی اندازه بگیرید (با دراپاوت در نگاشت کراس، واریانس نمونههای کپشن بالا میرود).
-
لایهی کراساتنشن را با یک بلوک اتنشن مشابه Q-Former جایگزین کنید؛ حالتی که در آن مجموعهای ثابت از 32 توکن کوئری در هر لایه یک بار به ویژگیهای تصویر اتنشن میکنند.
اصطلاحات کلیدی
| اصطلاح | مفهوم |
|---|---|
| ادغام دیرهنگام (Late fusion) | جریانهای متن و تصویر در مسیرهای جداگانه میمانند؛ کراساتنشن در هر بلوک نقش رابط (Bridge) را بین آنها ایفا میکند |
| کراساتنشن (Cross-attention) | حالتی که کوئری (Q) از یک جریان و کلیدها (K) و مقادیر (V) از جریانی دیگر میآیند |
| ماسک علّی (Causal mask) | ماسک بولی مثلثی پایینی که مانع دیدن آینده در فرآیند خودبازگشتی میشود |
| کش کلید-مقدار (KV cache) | ذخیرهی یکبارهی کلیدها و مقادیر تصویر و استفادهی مجدد از آنها در تمام گامهای رمزگشایی |
| توکنهای حافظه (Memory tokens) | توکنهای منجمد تصویر که دیکودر برای دریافت اطلاعات به سراغشان میرود |
برای مطالعهی بیشتر
- مقالهی Flamingo (2022) برای دیدن طراحی استاندارد ادغام دیرهنگام با کراساتنشن گیتدار.
- مقالهی BLIP-2 (2023) برای آشنایی با Q-Former؛ بلوک کراساتنشنی که در قالب مجموعهای از کوئریهای یادگیرنده پیاده شده است.
- مقالهی IDEFICS (2023) برای مشاهدهی بازتولید متنباز روش Flamingo.
