اگر صاحب یک وبسایت استاتیک هستید و میخواهید کاربرانتان بدون نیاز به سرور، نتایج دقیقی بر اساس «معنا» و نه فقط «کلمات» پیدا کنند، بازی تغییر کرده است. تصور کنید تمام قدرت درک معنایی یک مدل زبانی را در فایلی به اندازه چند عکس باکیفیت جای دهید و مستقیم در مرورگر کاربر اجرا کنید. در حالی که شبکههای عصبی عظیم معمولاً برای عملکرد صحیح به کلاسترهای قدرتمند GPU و رمهای گرانقیمت نیاز دارند، اکنون یک جدول جستوجوی ۴ مگابایتی میتواند جستوجوی معنایی را به هر وبسایت استاتیکی بدون نیاز به سرور بیاورد.
بر اساس گزارش فنی مفصلی که بارت دگو (Bart De Goe) در ۱۱ ژوئیه ۲۰۲۶ منتشر کرد، او نشان میدهد که این رویکرد سبکوزن برای درک قصد (Intent) پشت پرسوجوی کاربر کاملاً کفایت میکند. تا پیش از این، سالها بود که مالکان سایتهای استاتیک به ابزارهای جستوجوی کلمات کلیدی مانند Lunr.js (که یک ایندکس معکوس را در زمان ساخت سایت ایجاد کرده و آن را به صورت JSON ارسال میکند) یا Fuse.js متکی بودند. این ابزارها سریع اما «کور» هستند؛ آنها یک پست را تنها در صورتی پیدا میکنند که کلمه دقیق در متن وجود داشته باشد. برای مثال، اگر کاربر به جای عبارت «سیل آبجو لندن»، عبارت «فاجعه نوشیدنی الکلی در انگلستان» را جستوجو کند، یک موتور کلمات کلیدی هیچ نتیجهای برنمیگرداند. این مشکل در واقع همان چیزی است که متخصصان به آن «گسست معنایی» میگویند و سالها تلاش کردهاند تا با استفاده از ریاضیات و فضای بردارهای متنی، فاصله میان کلمه و مفهوم را پر کنند.
جایگزین این ابزارها تاکنون جستوجوی معنایی سمت سرور بود که نیازمند ماشینی با کتابخانههای sentence-transformers و چند صد مگابایت PyTorch است. این موضوع اجرای چنین سیستمی را در یک تب مرورگر غیرممکن میکرد، زیرا به سختافزار قدرمند و رم گرانقیمت نیاز داشت. همانطور که در تحلیلهای پیشین ما دربارهی بهینهسازی مدلهای کوچک اشاره کردیم، چالش اصلی همیشه توازن میان دقت و حجم بوده است.
معماری معنایی فشرده
این تحول از خانوادهای از مدلها به نام model2vec (بهویژه سری "potion") بهره میبرد. برخلاف مدلهای ترنسفورمر استاندارد، این مدلها از یک مدل sentence-transformer واقعی به یک مدل بردار معنایی (Embedding) استاتیک «تقطیر» (Distill) شدهاند. در اینجا به جای یک شبکه عصبی پیچیده با لایههای توجه (Attention layers)، مدل در واقع یک جدول جستوجوی بزرگ است.
طبق مستندات منتشر شده در bart.degoe.de، مسیر پیشرو (Forward Pass) این مدل به چهار گام اساسی کاهش یافته است:
- تبدیل متن ورودی به شناسهها:
ids = tokenize(text) - یافتن بردار مربوط به هر توکن در جدول بردارها:
rows = embedding[ids] - محاسبه میانگین آن بردارها:
vector = rows.mean(axis=0) - نرمالسازی نتیجه برای رسیدن به طول واحد:
vector = vector / norm(vector)
این فرآیند نیاز به استنتاج (Inference) را بهطور کامل از بین میبرد. هیچ لایهای وجود ندارد و هیچ مکانیزم توجهی اجرا نمیشود. اجرای مدل صرفاً شامل چند جستوجو در آرایه و یک میانگینگیری ریاضی است. برای مدل potion-base-8M، این جدول شامل ۲۹,۵۲۸ توکن در ۲۵۶ بُعد از نوع float32 است. این مدل خاص میتواند به ۸۱٪ کیفیت بازیابی مدل MiniLM برسد، در حالی که از نظر تعریف، چیزی جز یک لغتنامه نیست.
حل بحران حجم داده و کوانتیزاسیون
یک نسخه خام float32 از این جدول حدود ۳۰ مگابایت حجم داشت که برای دانلود توسط یک کاربر موبایل تنها برای استفاده از یک جعبه جستوجو، بسیار سنگین است (تقریباً معادل دوازده عکس با رزولوشن بالا). برای رفع این مشکل، پروژه از کوانتیزاسیون int8 استفاده میکند.
نکته جالب توجه نویسنده این بود که «لیست کلمات توقف» (Stopword list) مدل در واقع در هندسهی آن پنهان شده است. با مرتبسازی هر توکن در potion-base-8M بر اساس طول بردارش، یک الگوی واضح ظاهر میشود:
- کوتاهترین بردارها (نزدیک به صفر): واژگانی مثل "a"، "."، ""، "-"، ")"، "the"، "to"، "and"، "of" و "in".
- بلندترین بردارها: واژگانی مثل "turkmenistan"، "seychelles"، "guantanamo"، "hemingway" و "vanuatu".
این یک نویز نیست، بلکه یک مکانیزم یادگرفته شده است. وقتی بردارهای توکن میانگین گرفته میشوند، کلماتی با بردارهای بسیار کوچک (stopwords) تقریباً تأثیری در نتیجه نمیگذارند، در حالی که کلماتی با بردارهای بزرگ بر نتیجه غلبه میکنند. مدل بدون نیاز به لیست رسمی کلمات توقف یا کدنویسی خاص، یاد گرفته است که کلمهای مانند "the" باید کمترین سهم و کلمهای مانند "guantanamo" باید بیشترین سهم را در معنای نهایی داشته باشد.
نویسنده برای حفظ این بزرگیها در حین کوانتیزاسیون، ضربکنندههای مقیاس را برای هر ردیف آزمایش کرد، اما مشخص شد که یک مقیاس جهانی (Global scale) کفایت میکند. مقیاس جهانی تنها دو ردیف ("." و "a") را صفر میکند که پیش از این هم تقریباً هیچ تأثیری نداشتند. شباهت کسینوسی مجموعی نسبت به مدل اصلی در سطح ۰.۹۹۹۸ باقی ماند. جدول نهایی int8، شامل مقیاسهای هر ردیف (که تنها ۱۱۸ کیلوبایت هزینه دارد)، مدل float32 اصلی را با شباهت کسینوسی ۰.۹۹۹۹۵۸ بازتولید میکند.
ساخت ایندکس: تکهبندی و کشینگ
بردارها در زمان ساخت سایت از طریق یک اسکریپت پایتون که با Hugo ادغام شده تولید میشوند. این فرآیند شامل حذف متادادههای ابتدای پست (Front matter) و بلوکهای کد است، پیش از آنکه متن به تکههای همپوشان (Overlapping chunks) تقریباً ۶۰۰ کاراکتری تقسیم شود. این بردارها در فایلی نوشته میشوند که مرورگر تنها زمانی دانلود میکند که کاربر روی جعبه جستوجو کلیک کند.
چرا تکهبندی (Chunking) حیاتی است؟
- مشکل میانگینگیری: چون یک بردار استاتیک، میانگین بردارهای توکنهای آن است، میانگینگیری از یک پست ۱۵,۰۰۰ کاراکتری، یک بردار کلی و بیمعنی مثل «نثری انگلیسی درباره نرمافزار» ایجاد میکند.
- حفظ سیگنال: خرد کردن پستها به تکههای کوچکتر اجازه میدهد تا عبارات نادر و متمایز (مانند
pydubیاmmh3) توسط صدها کلمه معمولی غرق نشوند. - سیگنالهای تیز (Sharp Signals): تکههای کوچکتر باعث تند و تیز ماندن سیگنالها میشوند، که نویسنده آن را دلیل اصلی برتری برخی مدلها در کیفیت بازیابی میداند.
تلهی مهندسی بیشازحد (Overengineering)
نویسنده همچنین یک حافظه پنهان (Cache) برای بردارها پیاده کرد که بر اساس هش (Hash) هر تکه متن کار میکرد تا از تبدیل مجدد محتویات تغییرنیافته جلوگیری کند. با این حال، مشخص شد این کار بیهوده است. تبدیل هر تکه از یک وبلاگ ۱۴ پستی تنها حدود ۱۰ میلیثانیه زمان میبرد (چند صد جستوجو و یک میانگین). در حالی که چنین کشی برای مدل MiniLM که نیاز به یک مسیر پیشرو در شبکه عصبی واقعی دارد ضروری است، برای رویکرد جدول جستوجو اضافی بود. این مفهوم مدیریت حافظه و کشینگ معنایی در ابعادی بسیار گستردهتر در پایگاهدادههای سازمانی مانند Oracle 26ai نیز برای بهینهسازی کوئریهای سنگین به کار گرفته میشود. این بخش در کد باقی مانده تا یادآوری کند که مهندسی بیشازحد اغلب اتلاف وقت و توکن است.
جزئیات پیادهسازی و چالشهای فنی
برای اجرای این سیستم در مرورگر، توکنساز BERT WordPiece باید در حدود ۸۰ خط کد جاوااسکریپت بازنویسی میشد. نویسنده به سه «تله» (Gotcha) حیاتی اشاره کرد که میتوانستند بیصدا بردار پرسوجو را مسموم کنند:
- توکنهای خاص: توکنسازهای BERT معمولاً متن را در نشانگرهای
[CLS]و[SEP]قرار میدهند. اما model2vec توکنساز را با تنظیمadd_special_tokens=Falseفراخوانی میکند. افزودن این نشانگرها، دو بردار اضافی به میانگین اضافه میکند که نباید آنجا باشند. - توکنهای ناشناخته: اگر کلمهای در لغتنامه نباشد، model2vec آن را بهطور کامل از توالی حذف میکند، بهجای اینکه یک بردار
[UNK]جایگزین کند. این یعنی پرسوجوهای بیمعنی منجر به یک لیست توکن خالی و بردار صفر میشوند که باید برای جلوگیری از خطای تقسیم بر صفر مدیریت شوند. در عمل این مورد نادر است؛ چون هر کاراکتر در لغتنامه ۲۹,۵۲۸ توکنی وجود دارد و فقط کلمات طولانیتر از ۱۰۰ کاراکتر معمولاً این اتفاق را میاندازند. - حذف اکسنتها (Sstripping Accents): در کتابخانه HuggingFace، اگر
strip_accentsمقدار null داشته باشد، از تنظیم lowercase (که فعال است) ارثبری میکند. در نتیجه "café" به "cafe" تبدیل میشود. اگر این تنظیمات عیناً کپی شوند و در مرورگر رعایت نشوند، پرسوجوهای دارای اکسنت دچار انحراف میشوند.
بنچمارکهای عملکرد
جستوجو در واقع شامل چند صد ضرب داخلی (Dot product) است. از آنج که تنها چند صد بردار تکه وجود دارد، مرورگر دهها هزار عملیات ضرب-جمع را در کسری از میلیثانیه انجام میدهد. در یک مکبوک ایر M1، کل خط لوله — از توکنسازی و تبدیل به بردار تا امتیازدهی به هر تکه و رتبهبندی — حدود ۰.۴ میلیثانیه زمان میبرد.
یک پیچیدگی فنی در مورد نحوه مدیریت ضرب int8 در جاوااسکریپت کشف شد. از آنج که ضرب int8 x int8 در JS بهطور بیصدا سرریز (Overflow) میکند (مثلاً مقداری در حدود ۳ میلیون ممکن است بدون هشدار به ۶۴- تبدیل شود)، نویسنده توصیه میکند که ضرب داخلی در یک float معمولی جمع شود. با این حال، چون شباهت کسینوسی نسبت به مقیاس مثبت تغییرناپذیر است، مرورگر میتواند یک پرسوجوی float32 را مستقیماً در برابر بایتهای خام int8 ضرب کند تا بدون نیاز به خروج از کوانتیزاسیون، رتبهبندی صحیح را به دست آورد.
مقایسه سه مدل بنچمارک شده:
- model2vec (جدول جستوجو): حجم ۴.۲ مگابایت، زمان تبدیل به بردار ۰.۳ میلیثانیه. رسیدن به ۸۱٪ کیفیت بازیابی MiniLM.
- Ternlight (مدل Ternary): مدلی به سبک BitNet که با WebAssembly کامپایل شده و سرعت و حجم متوسطی دارد.
- MiniLM (از طریق Transformers.js): حجم ۲۳.۵ مگابایت، زمان تبدیل به بردار ۱۸ میلیثانیه. از یک بیلد ONNX و WebAssembly استفاده میکند و حدود دو ثانیه زمان برای بارگذاری نیاز دارد.
اندازهگیری و رفع نقاط کور جستوجو
پیش از استقرار، نویسنده از Claude برای ایجاد ۳۰ پرسوجوی تست استفاده کرد که به سه دسته تقسیم شدند: توکنهای دقیق (مانند pydub, lunr, mmh3, papermod)، پارافرازها (مثلاً «سندها را بر اساس معنای آنها پیدا کن نه کلماتی که دارند») و قصدهای ناوبری (مثلاً «چگونه جستوجو را به سایت استاتیک اضافه کنم»). این کار یک باگ قدیمی در جستوجوی کلمات کلیدی موجود را فاش کرد: تنظیم location در Fuse.js به پنجره فاصله ۱,۰۰۰ کاراکتری محدود شده بود.
به دلیل اینکه کلمه pydub اولین بار در حدود ۳,۷۰۰ کاراکتر بعد از ابتدای پست ظاهر میشود، جستوجوی کلمات کلیدی در یافتن آن شکست میخورد. همین اتفاق برای mmh3 و هر کلمه متمایزی که در اعماق یک پست طولانی بود رخ میداد. این دو موتور به روشهای متقابلی کور هستند:
- جستوجوی کلمات کلیدی: روی توکنهای دقیق (پس از اصلاح باگ) عالی است اما برای تمام ۱۰ پارافراز نتیجه صفر میدهد.
- جستوجوی معنایی: پارافرازها را بهخوبی شناسایی میکند اما در مواجهه با اسامی خاص مثل
pydubدچار مشکل میشود، زیرا این کلمات در لغتنامه نیستند و به تکههای زیر-کلمه (subword) بیمعنی خرد میشوند.
راهکار ترکیبی: ادغام رتبه متقابل (RRF)
برای ترکیب این دو، نویسنده روش Reciprocal Rank Fusion را پیاده کرد. از آنجایی که فواصل تطبیق فازی کلمات کلیدی و شباهتهای کسینوسی در مقیاسهای متفاوت و غیرقابل مقایسهای هستند، RRF امتیازها را نادیده گرفته و فقط از «رتبهها» استفاده میکند.
امتیاز ادغام شده به صورت مجموع 1 / (k + rank) محاسبه میشود که در آن k معمولاً ۶۰ است. این یعنی سندی که توسط هر دو موتور در رتبه ۳ قرار گرفته باشد (1/63 + 1/63)، امتیاز بیشتری نسبت به سندی میگیرد که تنها توسط یک موتور رتبه ۱ شده است (1/61). این تضمین میکند که عبارت «هر دو موتور این را پسندیدند» بر «یک موتور عاشق این بود» غلبه کند؛ موضوعی که وقتی یک موتور نسبت به پارافرازها و دیگری نسبت به اسامی خاص کور است، حیاتی است.
خطر معیارهای ضعیف
در جریان ارزیابی، نویسنده ابتدا از معیار recall@3 (آیا پست مربوطه در سه نتیجه اول است) استفاده کرد. با داشتن تنها ۱۳ پست، این سطح خیلی پایین بود و چندین پیکربندی با امتیاز ۰.۹۷۸ مساوی شدند. این منجر به «مهملات مطمئن» شد؛ جایی که مدل روی توکنهای دقیق امتیاز کامل ۱.۰۰۰ میگرفت، حتی وقتی پست صحیح در رتبه ۳ بود و دو نتیجه بیربط (مانند «SSL رایگان در گیتهاب پیجز») بالاتر از پست pydub قرار داشتند.
تغییر به معیار recall@1 («آیا نتیجه اول درست است؟») دقت لازم برای تفکیک مدلها را فراهم کرد و نشان داد که معیار قبلی، مدل را برای قرار دادن پاسخها در یک دستهی بیش از حد وسیع پاداش میداد.
این معماری، جستوجوی برداری را از یک نیاز پیچیده به پایگاهداده (مانند ایندکسهای HNSW برای نزدیکترین همسایه تقریبی) به یک حلقه for ساده در مرورگر کلاینت تبدیل میکند. سیستم نهایی از یک جدول ۴ مگابایتی و حدود ۳۰۰ خط جاوااسکریپت بدون وابستگی (Dependency-free) استفاده میکند که تنها هنگام فوکوس روی باکس جستوجو بهصورت Lazy-load بارگذاری میشود.
تحلیل برای توسعهدهندگان کاربردی
این تغییر، این فرض بنیادین را که جستوجوی معنایی نیازمند یک بکاند است، تغییر میدهد. برای توسعهدهندگان سایتهای مستندات، وبلاگهای شخصی یا فروشگاههای کوچک الکترونیکی، «هزینه استنتاج» عملاً به صفر رسیده است.
با انتقال محاسبات به کلاینت و «هوش» مدل به یک جدول جستوجوی کوانتیده، شاهد حرکتی به سمت «هوش مصنوعی لبهی افراطی» (Extreme Edge AI) هستیم. اثر ثانویه این رویکرد، بهبود قابل توجه در حریم خصوصی و تأخیر (Latency) است، زیرا پرسوجوهای کاربر هرگز دستگاه آنها را ترک نمیکند. این ثابت میکند که برای کارهای خاصی مانند بازیابی اطلاعات، یک جدول لغت به خوبی تقطیر شده، بهینهتر از یک شبکه عصبی زنده است.
برای کسانی که میخواهند این سیستم را پیاده کنند، نویسنده پکیج پایتونی به نام static-site-search-eval را منتشر کرده است. توسعهدهندگان میتوانند خط لوله ساخت را با دستور زیر اجرا کنند:pip install static-site-search-eval sss-eval build --corpus content/post --outdir static/search --model minishlab/potion-base-8M --dims 128 --chunk-size 600
گام بعدی شما
- اگر وبلاگ یا مستندات فنی دارید، پکیج
static-site-search-evalرا برای تست کیفیت بازیابی مدلهای کوچک بررسی کنید. - برای پیادهسازی، مدل
minishlab/potion-base-8Mرا با ابعاد ۱۲۸ و تکهبندی ۶۰۰ کاراکتری امتحان کنید. - استراتژی RRF را برای ترکیب جستوجوی معنایی و کلمات کلیدی در کلاینت به کار ببرید تا نقاط کور هر دو روش پوشانده شود.
اما تأثیر این رویکرد بر حریم خصوصی کاربران حتی عمیقتر است؛ در تحلیل ما دربارهی رایانش لبه (Edge Computing) بخوانید که چگونه دادهها هرگز سایت را ترک نمیکنند.




گفتگو