انواع حافظه عامل هوش مصنوعی و هزینههای ذخیرهسازی
تفاوت حافظه معنایی، رویدادی و رویهای در عاملهای هوش مصنوعی را بررسی کنید. در این راهنما هزینههای واقعی ذخیرهسازی و بازسازی هر نوع حافظه را روی سرور VPS محاسبه کردهایم.
سه نوع حافظه عامل چیست
انواع حافظه عامل به سه دسته تقسیم میشوند و هر کدام تأثیر متفاوتی بر سختافزاری که برای آن هزینه میکنید دارند: حافظه معنایی (semantic) حقایق را نگه میدارد، حافظه رویدادی (episodic) وقایع رخداده را ذخیره میکند و حافظه رویهای (procedural) نحوه انجام یک کار را در بر میگیرد. جدول زیر هر یک را با یک مثال سروری تعریف میکند. هر آنچه پس از آن میآید، بخشی است که معمولاً نادیده گرفته میشود؛ یعنی هزینه ذخیرهسازی و هزینه بازسازی هر نوع حافظه.
The data behind this chart
[
{
"label": "Semantic",
"what_it_holds": "Facts the agent should treat as currently true",
"server_example": "The database listens on 10.8.0.4:5432 and the nightly dump runs at 03:15 UTC"
},
{
"label": "Episodic",
"what_it_holds": "A record of one past event or session",
"server_example": "On 2026-08-11 the deploy failed because the disk was full, and rotating logs fixed it"
},
{
"label": "Procedural",
"what_it_holds": "How to carry out a task, as steps the agent can run",
"server_example": "The restore runbook: stop the service, load the dump, run migrations, start the service"
}
]این نامها از روانشناسی انسانی وام گرفته شدهاند و این اقتباس چندان دقیق نیست. این تقسیمبندی به یک دلیل کاربردی اهمیت دارد: این سه نوع حافظه دارای اندازههای متفاوت و مسیرهای بازیابی متفاوتی هستند، بنابراین قرار دادن همه آنها در یک vector store، عملکرد هر کدام را تضعیف میکند.
حافظه معنایی محدود است و شما باید آن را بهصورت دستی ویرایش کنید
چند صد واقعیت درباره سرورهای شخصی شما، تنها چند ده کیلوبایت متن است. در اینجا مشکل، فضای ذخیرهسازی نیست، بلکه اصلاح دادههاست. یک واقعیت نادرست در حافظه معنایی، در تمام پاسخهای بعدی عامل (agent) تکرار میشود؛ بنابراین سیستم ذخیرهسازی باید به شما اجازه دهد یک واقعیت را با نام آن پیدا کنید، تغییر دهید و مطمئن شوید که مقدار قدیمی کاملاً حذف شده است.
این موضوع به استفاده از یک ذخیرهساز کلید-محور اشاره دارد: یک جدول در Postgres با کلید اصلی (primary key) یا مجموعهای از فایلهای کوچک markdown در git. هر دو روش به شما اجازه میدهند یک پرسوجو (query) اجرا کنید، مقدار را ببینید و آن را در همان محل ویرایش کنید. جستجوی شباهت (similarity search) چنین قابلیتی به شما نمیدهد، زیرا بازیابی اطلاعات در آن بر اساس شباهت است، نه کلید. در آن صورت، «تغییر پورت دیتابیس» به «یافتن تمام بخشهایی که به پورت دیتابیس اشاره دارند» تبدیل میشود و نمیتوانید اثبات کنید که همه آنها را پیدا کردهاید. واقعیتها را بهصورت کلید-محور نگه دارید. اگر به بازیابی آزاد (loose recall) نیز نیاز دارید، آنها را بردار (embed) کنید، اما نسخه کلید-محور را به عنوان منبع حقیقت در نظر بگیرید.
حقایق قدیمی و منقضیشده خودشان را اعلام نمیکنند. پورت تغییر میکند اما سطر قدیمی باقی میماند، بنابراین عامل همچنان عددی را پاسخ میدهد که در ماه ژوئن درست بوده است. سیاست منقضیسازی و پاکسازی برای حافظه عامل نیمه دیگر این صفحه است و طراحی آن زمانی که جدول هنوز کوچک است، بسیار کمهزینهتر خواهد بود.
چرا حافظه اپیزودیک بدون محدودیت رشد میکند
حافظه اپیزودیک یک لاگ است و لاگها رشد میکنند. هر نشست، هر فراخوانی ابزار و هر دستور ناموفق، یک اپیزود بالقوه محسوب میشود. عاملی (agent) که در هر نوبت یک ردیف مینویسد، در طول یک ماه بسیار بیشتر از آنچه هر انسانی قادر به خواندنش باشد، داده تولید میکند. هزینه فقط محدود به فضای دیسک نیست: هر اپیزودِ برداریشده (embedded) به ایندکسی اضافه میشود که جستجو باید آن را پیمایش کند.
قوانین نگهداری (retention) را همان روزی که جدول را میسازید تعیین کنید، زمانی که حذف دادهها هنوز هزینهای ندارد. دو پرسش تکلیف اکثر موارد را روشن میکند. نخست، چه چیزی ارزش ثبت دارد: خلاصه یک نشست معمولاً ارزش ثبت دارد، اما خروجی کامل یک ls -la معمولاً خیر. دوم، هر کلاس از اپیزودها چقدر باید باقی بماند: مثلاً اپیزودهای خام برای 30 روز و خلاصههای نشست برای یک سال.
به هر ردیف اپیزود یک timestamp در created_at و یک ستون source اختصاص دهید. بدون created_at نمیتوانید دادهها را بر اساس سن حذف کنید. بدون source نمیتوانید تمام دادههای دریافتی از یک منبع مخرب را پاک کنید؛ دقیقاً همان کاری که در روزی که مشخص میشود یک صفحه وب یا یک تیکت، دستوراتی را به حافظه تزریق کرده است، به آن نیاز دارید.
DELETE FROM episodes WHERE created_at < now() - interval '30 days';این دستور را از طریق یک systemd timer اجرا کنید و سپس تغییر در تعداد ردیفها و اندازه جدول را مشاهده کنید. سیاستی برای نگهداری دادهها که توسط هیچکس اجرا نشود، عملاً فقط یک توضیح (comment) است.
psql -d agentmem -c "SELECT count(*) FROM episodes;"
psql -d agentmem -c "SELECT pg_size_pretty(pg_total_relation_size('episodes'));"حافظه رویهای باید در یک مخزن (Repository) نگهداری شود
حافظه رویهای نشاندهنده نحوه انجام یک کار توسط عامل است: یک اسکریپت shell، یک فایل مهارت (skill file)، یا یک کتابچه راهنمای عملیاتی (runbook) با مراحل شمارهگذاری شده. این موارد در واقع کد هستند و کد باید در جایی باشد که کدها در آن زندگی میکنند؛ یعنی در یک مخزن git که امکان بازبینی، نسخهبندی و مشاهده تفاوتها (diff) در آن وجود دارد.
اگر یک کتابچه راهنما را به صورت تکههای تعبیهشده (embedded chunks) ذخیره کنید، تنها یک کپی تقریبی از آن را دریافت خواهید کرد. بازیابی اطلاعات، تکههایی را برمیگرداند که بالاترین امتیاز را کسب کردهاند؛ بنابراین ممکن است عامل بر اساس مرحله 2 و مرحله 5 عمل کند، در حالی که مرحله 3 هرگز ظاهر نشده است و هیچ رکوردی وجود ندارد که نشان دهد کدام نسخه از رویه اجرا شده است. در git، git log به هر دو پرسش پاسخ میدهد. هزینه ذخیرهسازی در این روش نزدیک به صفر است، که این خود دلیل دیگری است برای اینکه برای چنین دادههایی هزینههای ذخیرهسازی برداری (vector prices) را نپردازید.
هزینه واقعی ذخیرهسازی embedding روی دیسک
The data behind this chart
[
{
"label": "bge-small-en-v1.5 (384 dims)",
"bytes_per_vector": 1544,
"mib_per_100k_rows": 147.2
},
{
"label": "bge-base-en-v1.5 (768 dims)",
"bytes_per_vector": 3080,
"mib_per_100k_rows": 293.7
},
{
"label": "bge-large-en-v1.5 (1024 dims)",
"bytes_per_vector": 4104,
"mib_per_100k_rows": 391.4
},
{
"label": "text-embedding-3-small (1536 dims)",
"bytes_per_vector": 6152,
"mib_per_100k_rows": 586.7
},
{
"label": "text-embedding-3-large (3072 dims)",
"bytes_per_vector": 12296,
"mib_per_100k_rows": 1172.6
}
]افزونه pgvector هر vector را به ازای هر بُعد 4 بایت به همراه 8 بایت هدر ذخیره میکند؛ بنابراین این محاسبات ثابت است و میتوانید پیش از بارگذاری هر دادهای، برای آن برنامهریزی کنید. یک بردار 384 بُعدی معادل 1544 بایت است که باعث میشود 100,000 قطعه داده (chunk) به 147.2 مبیبایت (MiB) فضا برای بردارها نیاز داشته باشد. همین مجموعه داده با 3,072 بُعد، به 1172.6 مبیبایت فضا با 12296 بایت به ازای هر ردیف نیاز دارد. متن یکسان است، اما حجم ذخیرهسازی نزدیک به 8 برابر میشود.
این فقط مربوط به ستون بردار است. متن قطعهبندیشده، کلید اصلی (primary key)، سربار ردیف و ایندکس همگی به این مقدار اضافه میشوند و ایندکس همان بخشی است که معمولاً فراموش میشود. الگوریتم HNSW (گراف سلسلهمراتبی کوچکجهان که pgvector میسازد) کپی مخصوص خود از بردارهایی که به آنها لینک میدهد را نگه میدارد؛ بنابراین یک مخزن ایندکسشده بهراحتی بیش از 2 برابر ارقام بالا فضا اشغال میکند. بهجای حدس زدن، فضای مصرفی خود را اندازهگیری کنید.
SELECT pg_size_pretty(pg_total_relation_size('memories')) AS total,
pg_size_pretty(pg_relation_size('memories_embedding_idx')) AS idx;رم تعیین میکند که آیا جستجو سریع حس میشود یا خیر، زیرا گراف تنها زمانی سریع پیمایش میشود که در حافظه باشد. هنگامی که حجم ایندکس از حافظهای که Postgres میتواند در اختیار بگیرد بیشتر شود، جستجوها شروع به خواندن از دیسک میکنند و تأخیر (latency) افزایش مییابد. فرآیند ساخت ایندکس محدودیت خاص خود یعنی maintenance_work_mem را دارد و زمانی که گراف از این حد فراتر رود، فرآیند ساخت این موضوع را اعلام کرده و کند میشود:
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 61440 tuples
HINT: Increase maintenance_work_mem to speed up builds.دو اهرم برای کوچک کردن حجم یک مجموعه داده وجود دارد. یک مدل کوچکتر انتخاب کنید؛ چرا که 384 بُعد، یکچهارمِ 1,536 بُعد هزینه دارد و برای پیدا کردن یادداشتهای شخصی، تفاوت دقت اغلب آنقدر ناچیز است که قابل چشمپوشی باشد. یا از دقت نیمممیز (half precision) استفاده کنید: نوع داده halfvec به ازای هر بُعد 2 بایت به همراه همان 8 بایت هدر مصرف میکند که تقریباً حجم ستون و ایندکس آن را با هم نصف میکند.
پیش از انتخاب مدل، دانستن یک محدودیت ضروری است. تا اوت 2026، یک ستون vector میتواند تا 2,000 بُعد ایندکس شود؛ بنابراین یک embedding با 3,072 بُعد توسط ستون پذیرفته میشود اما ایندکس آن را رد میکند:
ERROR: column cannot have more than 2000 dimensions for hnsw indexhalfvec از ایندکسگذاری تا 4,000 بُعد پشتیبانی میکند، بنابراین راه حل معمول، ایندکسگذاری روی cast است:
CREATE INDEX ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);چه زمانی pgvector از یک سرویس حافظه مجزا بهتر عمل میکند
اگر سرور شما در حال حاضر Postgres را اجرا میکند، استفاده از بردارها تنها به یک بسته و یک دستور نیاز دارد.
psql -V
sudo apt install postgresql-16-pgvectorCREATE EXTENSION vector;عدد موجود در نام بسته، نسخه اصلی Postgres شماست که در Ubuntu 24.04 برابر با 16 است، بنابراین پیش از تایپ دستور، psql -V را مطالعه کنید.
نگهداری حافظه در همان پایگاه داده، مزایایی به همراه دارد: یک نسخه پشتیبان که همزمان از حافظه و دادههای برنامه تهیه میشود، یک pool اتصال واحد، و تراکنشها؛ به این معنی که یک واقعیت و ردیفی که آن را توصیف میکند، با هم commit میشوند یا با هم شکست میخورند. یک سرویس مجزا نمیتواند چنین تضمینی ارائه دهد.
زمانی به یک سرویس حافظه اختصاصی مهاجرت کنید که یکی از شرایط زیر برقرار باشد: بار کاری جستجو با برنامه شما رقابت میکند و به ماشین مستقل نیاز دارد. چندین عامل (agent) روی چندین میزبان از یک حافظه مشترک استفاده میکنند. یا اینکه به منطق استخراج و حذف دادههای تکراری (deduplication) نیاز دارید که یک محصول نهایی ارائه میدهد؛ این همان استدلالی است که برای یک سرور حافظه Mem0 خودمیزبان مطرح میشود. برای یک عامل و مجموعهای از دادهها در حد چند میلیون قطعه (chunk)، استفاده از pgvector روی سروری که هماکنون دارید، سربار عملیاتی کمتر و احتمال خرابی پایینتری دارد. انتخاب موتور و میزان RAM مورد نیاز برای هر موتور، در اجرای یک پایگاه داده برداری روی VPS بررسی شده است.
هزینههای جایگذاری مجدد (re-embedding) هنگام تغییر مدل
بردارهای حاصل از دو مدل متفاوت با یکدیگر قابل مقایسه نیستند؛ بنابراین نمیتوانید حافظههای جدید را با مدل جدید جایگذاری (embed) کنید و ردیفهای قدیمی را به حال خود رها کنید. یک جدول ترکیبی نتایج بیمعنی بازمیگرداند، زیرا فاصلهای که بین دو سیستم مختصات متفاوت محاسبه شده باشد، عددی فاقد معناست. تغییر مدل به معنای جایگذاری مجدد کل پیکرهٔ دادهها (corpus) است.
این هزینه چهار بخش دارد: توکنها (هزینه API، یا زمان CPU و GPU در سرور شخصی شما)، زمان واقعی (wall clock time) اجرای عملیات، فضای دیسک مورد نیاز برای نگهداری همزمان هر دو ستون در طول فرایند backfill، و در نهایت بازسازی ایندکس در پایان کار. ترتیب ایمن برای این کار عبارت است از: افزودن یک ستون جدید، پر کردن آن به صورت دستهای (batch)، تغییر کوئری به ستون جدید، و سپس حذف ستون قدیمی و ایندکس آن.
نرخ عملیات را روی سختافزار خودتان اندازهگیری کنید و به ارقام منتشرشده اعتماد نکنید، زیرا جایگذاری فقط با CPU در یک VPS کوچک، بسیار کندتر از اجرای همان مدل روی GPU است. یک بخش نمونه را زمانبندی کنید و سپس آن را در اندازه کل پیکره ضرب کنید.
ollama pull nomic-embed-text
time curl -s http://localhost:11434/api/embed \
-d '{"model": "nomic-embed-text", "input": "one representative chunk of your corpus"}' > /dev/nullیک مدل جایگذاری محلی، وزنهای خود را نیز روی همان دیسکی ذخیره میکند که حافظه اصلی در آن قرار دارد، و بخش محل ذخیرهسازی مدلهای دانلود شده توسط Ollama توضیح میدهد که این فضا کجا مصرف میشود.
یک الزام، انجام تمام این مراحل را ممکن میسازد: متن منبع را در کنار هر بردار نگه دارید. فروشگاهی که فقط حاوی بردارها باشد، به هیچ وجه قابل جایگذاری مجدد نیست، زیرا چیزی برای خوراندن به مدل جدید باقی نمانده است. اگر نمیتوانید به این پرسش پاسخ دهید که «چه متنی این ردیف را تولید کرده است»، مسیر مهاجرت شما بازسازی کامل از همان منبع اولیهای است که متن از آنجا آمده بود.
نکاتی که باید پس از فعالسازی حافظه زیر نظر داشت
با پر شدن حافظه، تنها هزینه نیست که تغییر میکند. حقایق قدیمی منسوخ میشوند و رویدادهای کهنه، نتایج مفید را کنار میزنند؛ این همان مسئله هرس کردن (pruning) است. حافظه همچنین یک ورودی قابلنوشتن برای رفتار آتی عامل (agent) محسوب میشود، بنابراین هر چیزی که اجازه نوشتن در آن را داشته باشد، میتواند رفتار عامل را در آینده هدایت کند. اگر متن صفحات وب یا تیکتها به حافظه راه پیدا میکنند، پیش از آنکه دامنه دسترسیهای نوشتاری را گسترش دهید، نحوه مسمومسازی حافظه عامل را مطالعه کنید. حجم بازیابی اطلاعات (retrieval size) نیز بر مصرف توکن در تکتک درخواستها تأثیر مستقیم دارد و این همان نقطهای است که پیشبینیپذیر کردن هزینههای جاری عامل از آنجا آغاز میشود.
FAQ
آیا برای حافظه عامل (agent) به دیتابیس برداری نیاز دارم؟
برای حقایق، خیر. حافظه معنایی (semantic memory) کوچک است و شما باید بتوانید آن را بر اساس نام اصلاح کنید؛ بنابراین یک جدول کلید-مقدار یا مجموعهای از فایلهای markdown در git برای این کار مناسبتر است، زیرا میتوانید یک مقدار را مشاهده و ویرایش کنید. استفاده از embedding زمانی توجیه اقتصادی دارد که نیاز به بازیابی بر اساس مفهوم در مجموعهای از دادهها داشته باشید که برای فهرستکردن بیش از حد بزرگ است؛ این معمولاً شامل حافظه اپیزودیک و اسناد میشود. اگر در حال حاضر از Postgres استفاده میکنید، CREATE EXTENSION vector این نیاز را بدون افزودن سرویس جدید برای مدیریت، برطرف میکند.
حافظه عامل چه مقدار فضای دیسک اشغال میکند؟
حجم بردارها قابل پیشبینی است؛ در pgvector به ازای هر بعد 4 بایت به اضافه 8 بایت هدر در نظر گرفته میشود. در 768 بعد، این مقدار برابر با 293.7 مگابایت به ازای هر 100,000 ردیف و در 384 بعد برابر با 147.2 مگابایت است. سپس متن قطعهبندیشده (chunk text)، سربار ردیف و یک ایندکس HNSW که کپی مخصوص خود از بردارها را نگه میدارد را اضافه کنید؛ بنابراین حداقل دو برابر مقدار بردارها را بودجهبندی کنید و عدد واقعی را با pg_total_relation_size اندازهگیری کنید.
حافظه رویهای (procedural memory) باید کجا ذخیره شود؟
در یک مخزن git، به صورت اسکریپت یا فایلهای مهارتی که عامل مستقیماً اجرا میکند. یک رویه نیاز به فراخوانی دقیق و تاریخچه نسخهها دارد و جستجوی شباهت (similarity search) هیچکدام را فراهم نمیکند. یک کتابچه راهنمای قطعهبندیشده (chunked runbook) با قطعاتی که بالاترین امتیاز را دارند بازمیگردد، که ممکن است باعث شود مرحله 2 و 5 بازیابی شوند در حالی که مرحله 3 مفقود است، و هیچ رکوردی از اینکه کدام نسخه اجرا شده است وجود نخواهد داشت.
تغییر مدل embedding چه هزینهای دارد؟
هزینه آن شامل بازنویسی کامل (re-embedding) کل مجموعه داده است، زیرا بردارهای حاصل از مدلهای مختلف قابل مقایسه با یکدیگر نیستند. هزینه توکن یا زمان GPU، فضای دیسک برای ستونهای قدیمی و جدید به صورت همزمان، و بازسازی ایندکس را در بودجه خود لحاظ کنید. ستون جدید را اضافه کنید، دادهها را به صورت دستهای (batch) پر کنید، کوئری را تغییر دهید و سپس ستون قدیمی را حذف کنید. تمام این مراحل وابسته به این است که متن منبع را در کنار هر بردار حفظ کرده باشید.