پرش به محتوای اصلی
پرش به محتوای مقاله

«تخریب نامحسوس داده‌ها»؛ پیامد نقص امنیتی در مدیریت حافظه llama.cpp

·۲۴ شهریور ۱۴۰۵۶ دقیقه مطالعه۲ بازدید
llama.cpp برای جاسازی‌های M-RoPE از بافر موقعیت خود فراتر می‌خواند، بدون هیچ گزارشی.
llama.cpp برای جاسازی‌های M-RoPE از بافر موقعیت خود فراتر می‌خواند، بدون هیچ گزارشی.
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

کشف یک سرریز حافظه در llama.cpp که به‌جای توقف برنامه، باعث تخریب نامحسوس خروجی مدل‌های M-RoPE می‌شود؛ این اولین مورد مستند شده است که در آن یک خطای حافظه در سطح Runtime مستقیماً منجر به کاهش کیفیت استدلال مدل بدون کرش کردن سیستم شده است.

اگر از مدل‌های چندوجهی برای استنتاج محلی استفاده می‌کنید، احتمالاً با نتایجی مواجه شده‌اید که گاهی بدون دلیل منطقی، نامفهوم یا متناقض هستند. این «ناپایداری» احتمالاً نتیجه یک خطای مهندسی در مدیریت حافظه است، نه ضعف در منطق مدل.

طبق گزارش‌های فنی، یک نقص بحرانی در لاما‌سی‌پلاس‌پلاس (llama.cpp) شناسایی شده است که اجازه می‌دهد این کتابخانه هنگام پردازش بردار معنایی (Embedding) از نوع M-RoPE، فراتر از بافر موقعیت (position buffer) خود بخواند. این باگ باعث تخریب خاموش داده‌ها می‌شود؛ یعنی مدل بدون اینکه متوقف شود یا کرش کند، بر اساس بایت‌های تصادفی موجود در حافظه (heap memory)، نتایجی متناقض تولید می‌کند.

این مشکل به‌طور خاص مدل‌هایی را تحت تأثیر قرار می‌دهد که از رمزگذاری موقعیت چرخشی چندوجهی (M-RoPE) استفاده می‌کنند، از جمله Qwen2.5-VL و Qwen2.5-Omni. در دنیای استنتاج محلی، تراز حافظه (memory alignment) حیاتی است؛ وقتی یک کتابخانه خارج از مرزهای تخصیص‌شده می‌خواند، همیشه سیستم کرش نمی‌کند، بلکه داده‌های مجاور را به‌عنوان ورودی معتبر می‌پذیرد و همین موضوع باعث رفتارهای پیش‌بینی‌ناپذیر مدل می‌شود. برای بهینه‌سازی این فرآیندها در محیط‌های محلی، تنظیمات دقیق ابزارهایی مانند اولاما نقش مهمی در پایداری و سرعت استنتاج ایفا می‌کنند.

همان‌طور که در تحلیل‌های پیشین ما درباره‌ی بهینه‌سازی حافظه در مدل‌های بازمتن اشاره کردیم، هرگونه ناهماهنگی در لایه‌های پایین‌دستی C++ می‌تواند اثرات فاجعه‌باری بر لایه‌های بالای مدل داشته باشد.

شکاف در مستندات: ریشه خطا

به نقل از تحلیل‌های منتشر شده در dev.to، ریشه این باگ در تضاد موجود در فایل include/llama.h است. این فایل حاوی دو دستورالعمل خاص است که توسعه‌دهندگان را به گمراهی می‌کشاند:

  • خط ۲۴۸ بیان می‌کند: «آرایه‌های ارائه‌شده (مانند توکن، بردار معنایی و موقعیت) باید دارای اندازه n_tokens باشند».
  • خط ۲۵۳ اشاره می‌کند که اگر آرایه موقعیت (pos) برابر NULL باشد، «موقعیت توکن به‌طور خودکار ردیابی می‌شود».

برای مدل‌های استاندارد، این دستورات درست هستند. اما برای مدل‌های M-RoPE، منطق داخلی سیستم به بافری بزرگ‌تر از آنچه در هدر فایل ذکر شده نیاز دارد. این وضعیت یک تله ایجاد می‌کند: چه توسعه‌دهنده طبق مستندات عمل کرده و حافظه را به اندازه n_tokens تخصیص دهد و چه به مسیر خودکار NULL تکیه کند، در هر دو حالت خطای «بیش‌خوانی حافظه» (memory overread) فعال می‌شود.

سازوکار سرریز حافظه

طبق تحلیل‌های فنی، این باگ از تضاد بین مستندات کتابخانه و پیاده‌سازی واقعی آن ناشی می‌شود. همان‌طور که ذکر شد، فایل include/llama.h تصریح می‌کند که آرایه موقعیت باید اندازه n_tokens داشته باشد.

با این حال، در مدل‌های M-RoPE، سیستم برای هر بردار معنایی به چندین موقعیت نیاز دارد. به‌طور مشخص، مقدار n_pos_per_embd برای این خانواده از مدل‌ها برابر ۴ است. حلقه تقسیم دسته‌ها (batch splitter loop) در فایل src/llama-batch.cpp (خطوط ۷۸۰ تا ۷۸۷) برای هر بخش RoPE، یک موقعیت را می‌خواند.

وقتی کد یک دسته از بردارهای معنایی را پردازش می‌کند (جایی که batch.token == NULL است)، حلقه‌ای را اجرا می‌کند که مقادیر batch.pos[0 … 4 * n_tokens − 1] را می‌خواند. اما اگر از مسیر خودکار استفاده شده باشد، خط ۹۱ در فایل src/llama-batch.cpp تنها بردار را تا اندازه batch.n_tokens تغییر سایز داده است. در نتیجه، حلقه بسیار فراتر از انتهای بردار تخصیص‌یافته پیش می‌رود و داده‌های نامرتبط را می‌خواند.

شواهد و شناسایی

این باگ به‌دلیل اینکه در نسخه‌های نهایی (release builds) باعث کرش نمی‌شود، پنهان مانده بود، زیرا خواندن‌های سرریز در heap-buffer معمولاً منجر به توقف فوری برنامه نمی‌شوند. شناسایی آن تنها از طریق داده‌های میدانی و تست‌های سخت‌گیرانه ممکن شد:

  • گزارش میدانی: کاربری که Qwen2.5-Omni را روی Metal اجرا می‌کرد، گزارش داد که برای یک پیش‌پُرکردن (prefill) صوتی با ۷۵۰ توکن، سیستم تنها ۳۰۰۰ بایت تخصیص یافته اما سعی در خواندن ۱۲۰۰۰ بایت داشته است.
  • نشانه: خروجی‌ها به‌صورت متناوب نامفهوم بودند. نرخ خطا بسته به سایر پردازش‌های فعال در سیستم تغییر می‌کرد؛ این یک نشانه کلاسیک از خواندن حافظه heap است، جایی که بایت‌های اضافی به آنچه در حافظه مجاور قرار دارد بستگی دارد.
  • تأیید: پژوهشگران با استفاده از ابزار AddressSanitizer (ASan)، یک خطای heap-buffer-overflow در حالت READ به اندازه ۴ بایت را در خط ۷۸۷ فایل llama-batch.cpp بازتولید کردند.

در یک مورد آزمایشی با دسته دو توکنی (n_tokens = 2)، تخصیص حافظه تنها ۸ بایت بود. ردپای ASan نشان داد که سرریز در pos[2] رخ داده است که دقیقاً شروع دومین بخش M-RoPE است. این ردپا به‌طور مشخص سرریز را به عنوان ۰ بایت بعد از یک ناحیه ۸ بایتی که در خط ۹۱ فایل llama-batch.cpp تخصیص یافته بود، شناسایی کرد.

llama.cpp برای جاسازی‌های M-RoPE از بافر موقعیت خود فراتر می‌خواند، بدون هیچ گزارش خطایی.

اصلاحیه و پیامدهای API

یک درخواست تغییر (Pull Request #28910) برای رفع این مشکل ارسال شده است. راهکار شامل تغییر سایز بردار جایگزین به n_tokens * n_pos_per_embd و پخش موقعیت‌های تولیدشده در بخش‌های مورد نیاز است. این اصلاحیه با یک تست ۲۴ خطی در tests/test-batch-alloc.cpp با استفاده از یک واژگان (vocab) ساختگی با n_pos_per_embd = 4 روی مک مینی (Apple clang 21, master 4c9233c) تأیید شده است.

نکته قابل تأمل این است که این اصلاح به‌طور کامل خودکار نیست. در حالی که مسیر داخلی NULL تعمیر شده، مستندات کتابخانه اکنون صرفاً به توسعه‌دهندگان هشدار می‌دهد که باید به‌صورت دستی n_tokens * n_pos_per_embd موقعیت را برای مدل‌های M-RoPE در بخش‌های متوالی تخصیص دهند. این یعنی مسئولیت تخصیص صحیح حافظه اکنون بر عهده توسعه‌دهنده API است. تابع llama_batch_init() همچنان pos را بر اساس n_tokens تخصیص می‌دهد؛ بنابراین سرریز در سمت فراخوان (caller-side) از طریق به‌روزرسانی مستندات بسته شده است، نه از طریق تغییر منطق تخصیص کتابخانه.

تحلیل فنی

این حادثه خطری تکرارشونده در محیط‌های اجرای AI مبتنی بر C++ را برجسته می‌کند: شکاف بین انتظارات مستند شده در API و محدوده‌های واقعی حلقه‌های داخلی. چون این بیش‌خوانی منجر به خطای Segmentation Fault نشد، باگ از چرخه‌های تست استاندارد عبور کرد. در یک نسخه نهایی، چندین هزار بایت بعدی حافظه heap به‌عنوان موقعیت‌های RoPE در نظر گرفته شده و مدل محاسبات توجه (Attention) را بر اساس آن‌ها انجام می‌دهد.

برای حوزه استنتاج محلی، این موضوع ضرورت ادغام ابزارهایی مانند ASan در خط لوله CI/CD را برای هر کتابخانه‌ای که اشکال پیچیده تنسور (tensor shapes) را مدیریت می‌کند، دوچندان می‌کند. تکیه بر این فرض که «برنامه کرش نمی‌کند»، معیار ناکافی برای صحت عملکرد در معماری‌های چندوجهی است که چیدمان حافظه در آن‌ها غیرخطی است. این نوع خطاهای پنهان، نمونه‌ای از چالش‌های رایج مدل‌های زبانی در محیط عملیاتی هستند که شناسایی آن‌ها نیازمند ابزارهای مانیتورینگ پیشرفته است.

گام بعدی شما

اگر در حال حاضر بردارهای معنایی را از طریق API دسته‌ای به یک مدل M-RoPE ارسال می‌کنید، باید اقدامات زیر را انجام دهید:

  • تخصیص دستی: به‌هیچ‌وجه به مسیر NULL یا اندازه n_tokens تکیه نکنید. بافر موقعیت خود را به‌صورت دستی به اندازه n_tokens * 4 تخصیص دهید و چهار صفحه (plane) را پر کنید، مشابه پیاده‌سازی موجود در tools/mtmd/mtmd-helper-common.h.
  • تأیید: برای بررسی تأثیرپذیری نسخه خود، تکه کد تست PR را اعمال کرده، test-batch-alloc را با فعال کردن -DLLAMA_SANITIZE_ADDRESS=ON بیلد کرده و اجرا کنید. این کار به مدل یا GPU نیاز ندارد و کمتر از یک دقیقه زمان می‌برد.
  • عادت به بازبینی کد: هرگاه یک فایل هدر اندازه خاصی برای یک آرایه ذکر کرد، همیشه حلقه‌ای را که آن آرایه را می‌خواند پیدا کرده و محدوده‌ها را با همان عبارت تطبیق دهید. در این مورد، محدوده‌ها در فایل‌هایی بودند که صدها خط با هم فاصله داشتند و همین موضوع باعث شد تضاد را به‌راحتی نادیده بگیرند.

اما داستان سخت‌افزاری این تحول حتی شگفت‌انگیزتر است — به تحلیل ما درباره‌ی تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

این نقص امنیتی اعتبار استنتاج محلی را به چالش می‌کشد، زیرا خروجی‌های مدل بدون هیچ هشدار سیستمی تخریب می‌شوند. تخصص در مدیریت حافظه در سطح C++ اکنون به اندازه بهینه‌سازی وزن‌های مدل برای پایداری سیستم‌های چندوجهی حیاتی است.

تأثیر برای ایران

این خبر برای توسعه‌دهندگانی که در ایران از مدل‌های Qwen برای ساخت دستیارهای چندوجهی محلی استفاده می‌کنند حیاتی است؛ به‌روزرسانی سریع کتابخانه llama.cpp برای جلوگیری از خروجی‌های نامفهوم ضروری است.

·نگاه ما
تحریریه دات‌هوش

این باگ نشان می‌دهد که در مدل‌های چندوجهی، پیچیدگی‌های لایه حافظه بسیار سریع‌تر از توان تطبیق مستندات رشد می‌کنند. تکیه بر «عدم کرش» در زبان C++ برای سیستم‌های AI یک تله است، زیرا مدل‌ها برخلاف نرم‌افزارهای سنتی، در صورت دریافت داده‌های غلط، به‌جای توقف، شروع به «توهم» یا تولید خروجی‌های تخریب‌شده می‌کنند. این اتفاق ضرورت انتقال به زبان‌های حافظه‌-ایمن مانند Rust را در لایه‌های Runtime مدل‌های زبانی بیش از پیش نمایان می‌کند.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.