تصور کنید ساعت ۲:۱۴ صبح است و هشدار سیستم استنتاج شما فعال میشود؛ داشبورد تماماً سبز است، اما سیستم در حال فروپاشی است. در حالی که مصرف CPU تنها ۱۲٪ است، سن صف به ۴۷ ثانیه رسیده، در حالی که مهلت زمانی (Deadline) شما ۳۰ ثانیه بوده است؛ یعنی هدف سطح خدمات (SLO) شما پیش از این مرده است.
این سناریو که در راهنمای فنی منتشر شده در ۱۱ سپتامبر ۲۰۲۶ توسط dev.to بررسی شده، یک نقص حیاتی در نحوه مدیریت شکستهای استنتاج (Inference) — لحظهای که مدل واقعاً جواب تولید میکند، شبیه به خودِ آشپزی و نه دورهی آموزش آشپز — را افشا میکند. بسیاری از سیستمها بهگونهای پیکربندی شدهاند که تا زمانی که «فضای خالی» روی سرور وجود دارد، درخواستهای شکستخورده را تکرار کنند. اما ظرفیت خالی نمیتواند زمان جدید خلق کند؛ یک کپی رایگان از سرور نمیتواند مهلتی را که سپری شده است، بازگرداند. شما ممکن است یک پاسخ را سه بار تکرار کنید، اما ظرفیت خالی فقط حجم کارهای پرداختنشده را زیاد میکند. باید تکرار را پیش از آنکه زمان باقیمانده به صفر برسد، رد کنید.
توهم پنلهای سبز
در بسیاری از محیطهای عملیاتی، اپراتورها مصرف پایین CPU را مجوز بازپخش درخواستها میدانند. این یک شرطبندی خطرناک است زیرا تکرارها، کارهایی را تکثیر میکنند که پیش از این مهلت خود را از دست دادهاند. وقتی کلاینت کورکورانه درخواستی را که دیر شده تکرار میکند، فقط محاسبات را هدر نمیدهد، بلکه کارهای بیفایده را در کل خوشه (Cluster) تکثیر میکند. هرگز به تنهایی به پنل سبز مصرف سختافزار اعتماد نکنید؛ زمان بیکاری با بودجه زمانی یکی نیست.
به نقل از گزارش dev.to، این رویکرد در زمان طوفانهای سیستمی سه هزینه ایجاد میکند:
- اتلاف توکن: ارسال تکراری درخواستها با یک شناسه واحد، بدون ایجاد ارزش، توکن (Token) — تکههای کوچکی از متن، شبیه برشهای یک کیک طولانی که مدل تکهتکه میخورد — مصرف میکند. شمارندههای توکن حتی زمانی که کاربر هیچ پاسخی دریافت نکرده است، جلو میروند. این اتلاف منابع شباهت زیادی به فرآیندهای زامبی در سیستمهای استریمینگ دارد که توکنهای AI را بدون دلیل میسوزانند.
- رشد صف: هر تکرار، سن صف را برای تمام کپیهای منتظر دیگر افزایش میدهد. هر نسخه تکثیر شده همچنان باید پشت همان صف پذیرش منتظر بماند.
- خستگی اپراتور: مهندسان ساعتها وقت صرف ردیابی کارگری میکنند که ظاهرش سالم است اما در واقع در کارهای دیرشده غرق شده است.
میزان مصرف میتواند در تمام این سه هزینه پایین بماند. همین تضاد است که باعث شد هشدار ساعت ۲:۱۴ فعال شود. هستههای بیکار در کنار سن بالای صف یعنی «رد کردن»، نه «تکرار».
پیادهسازی پذیرش مبتنی بر سن
برای حل این مشکل، معماری پیشنهادی منطق تصمیمگیری را به لبهی پذیرش منتقل میکند. سیستم بهجای نگاه به میزان مصرف، باید سیگنالهای مشخصی را ارزیابی کند. پیش از هر درخواست POST بعدی، باید پرسید کدام سیگنال باید سد راه تکرار باشد: سن صف، میزان مصرف یا مهلت زمانی باقیمانده.
دادههای کلیدی برای ثبت
برای ساخت این سد، باید این فیلدها را از یک پنجره استخراج کنید:
queue_age_msدر لبه پذیرشworker_utilدر کپی سرویسدهندهdeadline_slack_msدر درخواست ورودیretry_countدر کلاینت یا Sidecartoken_unitsمصرف شده برای این شناسهhttp_statusآخرین تلاش upstream
اگر سن صف از مهلت باقیمانده بیشتر شد، متوقف شوید. هستههای بیکار را برای کاری که دیر شده هزینه نکنید. یک کپی رایگان، زمان واقعی از دست رفته را باز نمیگرداند. اولویت را به ترتیب قرار دهید: اول سن، دوم مهلت و در آخر میزان مصرف. مصرف تنها یک راهنما برای ظرفیت است، نه مجوزی برای پردازش کارهای دیرشده. کارهای دیرشده با بیکار بودن هستهها، ارزان نمیشوند.
ماتریس تصمیمگیری برای تکرارها
برای کسانی که این منطق را در یک پروکسی یا کنترلکننده پذیرش پیاده میکنند، قوانین زیر حاکم است. این قانون را در پروکسی بنویسید، نه در یک صفحه ویکی، و پیش از ادغام (Merge)، منطق آستانهها را با صدای بلند بیان کنید:
| سن صف در برابر مهلت | مصرف Worker | تعداد تکرار | اقدام |
|---|---|---|---|
| سن کمتر از نصف مهلت | هر مقداری | ۰ | پذیرش یکباره |
| سن کمتر از مهلت | مصرف زیر ۰.۳۰ | ۰ | پذیرش یکباره، بدون تکرار |
| سن برابر یا بیشتر از مهلت | بیکار یا مشغول | هر مقداری | رد کردن، عدم تکرار |
| هر سن دیرشدهای | هر مقداری | ۱ یا بیشتر | رد کردن و تخلیه |
مصرف پایین هرگز اولویتش بیشتر از سن صف نیست. کلاینت مهلت خود را مصرف کرده و هستههای بیکار نمیتوانند مهلت جدیدی برای کلاینت خلق کنند.
اعتبارسنجی محلی و تست
نویسنده برای اثبات این سازوکار بدون سوزاندن توکنهای گرانقیمت GPU (واحد پردازش گرافیکی)، یک تمرین محلی با Worker جعلی را پیشنهاد میکند. شما به یک خوشه GPU پولی نیاز ندارید. توپولوژی ساده است: client -> retry_proxy -> admission -> fake_worker. در این مدل، Worker جعلی میخوابد و سپس توکن برمیگرداند. پروکسی در صورت Timeout یا خطای ۵۰۳ تکرار میکند و بخش پذیرش زمانی که سن صف از مهلت بیشتر شود، درخواست را رد میکند.
شرایط تست اعلامشده
برای اطمینان از تنظیم درست متغیرها در آزمایشگاه، این مقادیر را در نظر بگیرید. اینها پیچهای تنظیم آزمایشگاهی هستند، نه بنچمارکهای محصول یا ادعاهای فروشنده درباره نرخ تراکم:
- پرامپت: یک پرامپت مصنوعی با هدف ۲۵۶ توکن
- مهلت کلاینت: ۳۰۰۰ میلیثانیه
- خواب Worker در حالت عادی: ۲۵۰۰ میلیثانیه
- خواب در حالت تزریق خطا: ۴۰۰۰ میلیثانیه
- تکرارهای کلاینت: بدون سقف (تا ۴ تلاش)
- قانون پذیرش: رد کردن در صورت بیشتر بودن سن نسبت به مهلت
اگر SLO شما از ثانیه استفاده میکند، این برچسبها را تغییر دهید.
خط زمانی تمرین شکست
برای تطبیق لاگها با ساعت، این روند را دنبال کنید:
- T+0 ms: کلاینت شناسه
lab-1را با مهلت ۳۰۰۰ میلیثانیه ارسال میکند. - T+2500 ms: در حالت عادی پاسخ میآمد؛ اما در مسیر خطا، Worker هنوز در خواب است.
- T+3000 ms: مهلت به صفر میرسد؛ مصرف هنوز ۰.۱۲ است.
- T+4000 ms: Worker خطای ۵۰۳ یا یک پاسخ دیرشده برمیگرداند.
- T+4001 ms: کلاینت کورکورانه اولین تکرار را زمانبندی میکند.
- T+4001 ms: بخش پذیرش باید درخواست را رد کند؛ چون سن صف از مهلت پیشی گرفته است.
اگر پروکسی شما در T+4001 درخواست را بپذیرد، تمرین شکست خورده است. پیش از آنکه کسی را بیدار کنید، پرچم تکرار را به حالت قبلی برگردانید.
ابزار محلی قابل بازتولید
فایل زیر را با نام retry_admission_drill.py روی لپتاپ خود ذخیره کنید. آن را فقط در برابر Worker جعلی محلی اجرا کنید و به مسیرهای تولید مشترک متصل نکنید.
#!/usr/bin/env python3
import json
import time
from dataclasses import dataclass
DEADLINE_MS = 3000
WORKER_SLEEP_MS = 2500
FAULT_SLEEP_MS = 4000
MAX_CLIENT_RETRIES = 4
@dataclass
class Probe:
req_id: str
retry_count: int
enqueued_at: float
deadline_ms: int
def queue_age_ms(p: Probe) -> float:
return (time.monotonic() - p.enqueued_at) * 1000.0
def deadline_slack_ms(p: Probe) -> float:
return p.deadline_ms - queue_age_ms(p)
def admit(p: Probe) -> str:
age = queue_age_ms(p)
slack = deadline_slack_ms(p)
if p.retry_count >= 1 and slack <= 0:
return 'reject_retry_late'
if age >= p.deadline_ms:
return 'reject_age_beats_slack'
return 'admit_once'
def fake_worker(fault: bool) -> dict:
sleep_ms = FAULT_SLEEP_MS if fault else WORKER_SLEEP_MS
time.sleep(sleep_ms / 1000.0)
return {'ok': not fault, 'sleep_ms': sleep_ms, 'token_units': 64}
def run_client(fault: bool, cap_retries: bool) -> dict:
p = Probe('lab-1', 0, time.monotonic(), DEADLINE_MS)
events = []
attempts = 1 if cap_retries else MAX_CLIENT_RETRIES
for i in range(attempts):
p.retry_count = i
decision = admit(p)
events.append({
'attempt': i,
'decision': decision,
'queue_age_ms': round(queue_age_ms(p), 1),
'deadline_slack_ms': round(deadline_slack_ms(p), 1),
'worker_util_hint': 0.12,
})
if decision.startswith('reject'):
break
result = fake_worker(fault)
events.append({'worker': result})
if result['ok']:
break
return {'fault': fault, 'cap_retries': cap_retries, 'events': events}
if __name__ == '__main__':
print(json.dumps({
'uncapped_fault': run_client(True, False),
'capped_fault': run_client(True, True),
}, indent=2))
خروجی مورد انتظار باید رد شدن درخواستها را پس از صفر شدن مهلت نشان دهد. خطاهای بدون سقف، همان شناسه Probe را تکثیر میکنند، در حالی که خطاهای سقفدار در اولین نمونه سن دیرشده متوقف میشوند. اگر حالت بدون سقف همچنان در مهلت منفی پذیرش میکند، پیش از دست زدن به هر سرور راه دور، پروکسی را اصلاح کنید.
مدیریت خطاهای HTTP 429 و 503
کدهای خطای استاندارد اغلب اپراتورها را به اشتباه میاندازند تا تکرار کنند. خطای ۴۲۹ (Too Many Requests) یعنی Worker در حال محافظت از خود است و خطای ۵۰۳ (Service Unavailable) یعنی مسیر برای کپیهای بیشتر ناامن است. هیچکدام از این وضعیتها، مهلتی که سپری شده را باز نمیگردانند.
بازپخش کورکورانه بعد از این خطاها، فقط کار را روی یک کپی در حال مرگ یا یک صف پر تکثیر میکند. هر دو باید توسط همان قانون «سن در برابر مهلت» محدود شوند تا از گسترش «طوفان تکرار» جلوگیری شود. وضعیت خطا را در کنار retry_count برای تحلیلهای پس از حادثه (Postmortem) ثبت کنید؛ آنها را در یک سبد تکرار واحد ادغام نکنید، زیرا وضعیت بدون سن، شما را دوباره به دنبال CPUهای بیکار میفرستد.
تحلیل: تغییر ذهنیت عملیاتی
این رویکرد نشاندهنده چرخش از نظارت «ظرفیتمحور» به نظارت «زمانمحور» است. در سرویسهای وب سنتی، یک هسته آزاد یک فرصت است. اما در استنتاج مدلهای زبانی بزرگ، جایی که تولید توکن زمانبر است و مهلتها سختگیرانه هستند، هسته آزاد اگر درخواست دیر شده باشد، بیارزش است. این نگاه به مدیریت بودجه زمانی، مشابه معیاری است که در سنجش توسعهدهندگان عاملها از طریق بودجه حلقهها مورد تأکید قرار گرفته است.
عملیات هزینه باید مجموع توکنها را به ازای هر شناسه حساب کند، نه به ازای هر پاسخ موفق. اگر یک شناسه چهار برابر توکن مصرف کرده، یعنی شما آن را تکثیر کردهاید. مصرف پایین این تکثیر را نشان نمیدهد، اما سن صف و تعداد تکرار، آن را افشا میکنند. برای شما این بدان معناست که «مصرف پایین» در زمان قطعی، دیگر نشانه سلامت نیست، بلکه اغلب نشانه شکست سیستمی در کنترل پذیرش است.
تزریق خطا و بازگشت (Rollback)
خطاها را به این ترتیب تزریق کنید: خواب Worker را بیش از مهلت زمانی افزایش دهید، مصرف را روی ۱۲٪ نگه دارید و تماشای کنید که سن صف از مهلت عبور میکند. تایید کنید که پذیرش هر تکرار بعدی را رد میکند و شناسههایی که سن آنها از مهلت بیشتر شده را تخلیه کنید.
اگر این پروکسی ترافیک واقعی را مدیریت میکند، مسیر بازگشت را اینگونه طی کنید:
۱. مقدار RETRY_MAX=0 را فوراً در Sidecar کلاینت تنظیم کنید.
۲. پذیرش را برای مسیر affected روی reject_all قرار دهید.
۳. شناسههایی که سن آنها از مهلت بیشتر شده را تخلیه کنید.
۴. آخرین نسخه سالم را از کنترل نسخه بازیابی کنید.
۵. تنها زمانی مسیر را باز کنید که سن صف برای دو بازه استخراج، زیر مهلت بماند. برای مدیریت سریعتر اینگونه بحرانها در محیط عملیاتی، میتوان از پروتکل ۶۰ دقیقهای مهار خطاهای کدنویسی AI به عنوان یک چارچوب کمکی استفاده کرد.
مقیاسدهی به Workerهای آزاد برای جذب تکرارها اشتباه است؛ این کار سیگنال سن صف را میپوشاند. تنها پس از توقف حلقه تکثیر، کپیهای جدید اضافه کنید.
محیط استقرار و محدودیتها
برای اثبات این محدودیت در محیطی ارزان، MonkeyCode دسترسی رایگان به مدل و سرور ارائه میدهد (افشا: این مقاله به عنوان بخشی از معرفی محصولات MonkeyCode تهیه شده است). از این محیط فقط برای استقرار اولیه استفاده کنید، نه برای برنامهریزی ظرفیت تولید. ترافیک پولی را پشت همان سقف تکرار نگه دارید و سن صف، تعداد تکرار و مهلت باقیمانده را با هم ثبت کنید.
این قانون پذیرش را برای موارد زیر نادیده بگیرید:
- مسیرهای سخت-زمانی (Hard real-time) که هرگز صف تشکیل نمیدهند.
- کارهای دستهای (Batch) با پنجرههای زمانی شبانه.
- توزیعهای Idempotent که از قبل شناسهها را حذف تکرار میکنند.
- تیمهایی که امکان استخراج
queue_age_msرا ندارند.
اگر نمیتوانید سن را اندازه بگیرید، حدس نزنید. حدس زدن، ظرفیت آزاد را به یک طوفان تکرار خاموش تبدیل میکند. این آزمایش کیفیت واقعی مدل را نمیسنجد و ادعای نرخ تراکم برای هیچ فروشندهای ندارد. خواب Worker جایگزین تأخیر سرویسدهی است و توکنها شمارندههای لاگ هستند، نه صورتحساب. همچنین جدول تصمیمگیری، عدالت بین مستاجران مشترک (Tenants) را نادیده میگیرد؛ اگر یک استخر Worker مشترک دارید، بودجههای هر کلید را اضافه کنید.
گامهای بعدی برای پیادهسازی
مهندسان باید با تجهیز لبههای خود برای استخراج queue_age_ms و deadline_slack_ms شروع کنند. پس از مشاهده تلهمتری، یک بلوک پیکربندی کوچک بنویسید:
admission:
reject_if_queue_age_ms_gte: 3000
reject_if_retry_count_gte: 1
ignore_worker_util_when_slack_ms_lte: 0
سپس بر اساس سن صف هشدار (Page) دریافت کنید، نه بر اساس هستههای بیکار. وقتی مصرف پایین و سن بالا است، رد کنید. زمان بیکاری Workerهای آزاد همچنان شرطبندی غلطی برای هزینه است. اولین آستانه خود را انتخاب کنید — سن صف، تعداد تکرار یا مهلت باقیمانده — و با تمرین محلی، رد شدن درخواست را اثبات کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو