Breaking News

لماذا تنفجر فاتورة LLM الخاصة بك – وكيف يمكن للتخزين المؤقت الدلالي أن يخفضها بنسبة 73٪



كانت فاتورة LLM API الخاصة بنا تنمو بنسبة 30% شهريًا. كانت حركة المرور تتزايد، ولكن ليس بهذه السرعة. عندما قمت بتحليل سجلات الاستعلام لدينا، وجدت المشكلة الحقيقية: يسأل المستخدمون نفس الأسئلة بطرق مختلفة.

“ما هي سياسة الإرجاع الخاصة بك؟”، و”كيف يمكنني إرجاع شيء ما؟”، و”هل يمكنني استرداد أموالي؟” كانت جميعها تصل إلى برنامج LLM الخاص بنا بشكل منفصل، مما أدى إلى توليد استجابات متطابقة تقريبًا، وكل منها يتكبد تكاليف واجهة برمجة التطبيقات (API) كاملة.

التخزين المؤقت للمطابقة التامة، وهو الحل الأول الواضح، استحوذ على 18% فقط من هذه المكالمات الزائدة عن الحاجة. نفس السؤال الدلالي، بصيغة مختلفة، تجاوز ذاكرة التخزين المؤقت بالكامل.

لذلك، قمت بتنفيذ التخزين المؤقت الدلالي بناءً على معنى الاستعلامات، وليس كيفية صياغتها. بعد تنفيذه، ارتفع معدل ضربات ذاكرة التخزين المؤقت لدينا إلى 67%، مما أدى إلى تقليل تكاليف LLM API بنسبة 73%. لكن الوصول إلى هناك يتطلب حل المشكلات التي تفشل فيها التطبيقات الساذجة.

لماذا يفشل التخزين المؤقت للمطابقة التامة؟

يستخدم التخزين المؤقت التقليدي نص الاستعلام كمفتاح ذاكرة التخزين المؤقت. يعمل هذا عندما تكون الاستعلامات متطابقة:

# التخزين المؤقت للمطابقة التامة

Cache_key = التجزئة (query_text)

إذا كان Cache_key في ذاكرة التخزين المؤقت:

إرجاع ذاكرة التخزين المؤقت[cache_key]

لكن المستخدمين لا يقومون بصياغة الأسئلة بشكل متطابق. وجد تحليلي لـ 100000 استعلام عن الإنتاج ما يلي:

  • 18% فقط كانت نسخًا مكررة تمامًا من الاستعلامات السابقة

  • 47% كانت مشابهة لغويًا للاستعلامات السابقة (نفس القصد، وصياغة مختلفة)

  • 35% كانت استفسارات جديدة حقًا

وتمثل نسبة الـ 47% تلك توفيرًا هائلاً في التكاليف كنا نفتقده. أدى كل استعلام مشابه لغويًا إلى استدعاء LLM كاملاً، مما أدى إلى إنشاء استجابة مماثلة تقريبًا للاستجابة التي قمنا بحسابها بالفعل.

بنية التخزين المؤقت الدلالي

يستبدل التخزين المؤقت الدلالي المفاتيح المستندة إلى النص بالبحث عن التشابه القائم على التضمين:

فئة SemanticCache:

def __init__(self, Embedding_model,تشابه_العتبة=0.92):

self.embedding_model = embedding_model

self.threshold = تشابه_العتبة

self.vector_store = VectorStore() # فايس، كوز الصنوبر، الخ.

self.response_store = ResponseStore() # ريديس، دينامو دي بي، إلخ.

def get(self, query: str) -> اختياري[str]:

“””إرجاع الاستجابة المخزنة مؤقتًا في حالة وجود استعلام مشابه لغويًا.”””

query_embedding = self.embedding_model.encode(query)

# ابحث عن معظم الاستعلامات المخزنة مؤقتًا المشابهة

التطابقات = self.vector_store.search(query_embedding, top_k=1)

إذا المباريات والمباريات[0].التشابه >= العتبة الذاتية:

Cache_id = match[0].بطاقة تعريف

إرجاع self.response_store.get(cache_id)

عودة لا شيء

مجموعة def (self، query: str، Response: str):

“””زوج استعلام واستجابة ذاكرة التخزين المؤقت.”””

query_embedding = self.embedding_model.encode(query)

Cache_id = create_id()

self.vector_store.add(cache_id, query_embedding)

self.response_store.set(cache_id, {

“استعلام”: استعلام،

“رد”: رد،

“الطابع الزمني”: datetime.utcnow()

})

الفكرة الأساسية: بدلاً من تجزئة نص الاستعلام، أقوم بتضمين الاستعلامات في مساحة المتجهات والعثور على الاستعلامات المخزنة مؤقتًا ضمن حد التشابه.

مشكلة العتبة

عتبة التشابه هي المعلمة الحرجة. قم بضبطه على مستوى مرتفع جدًا، وستفقد نتائج ذاكرة التخزين المؤقت الصالحة. قم بضبطه على مستوى منخفض جدًا، وستعود بإجابات خاطئة.

لقد بدت العتبة الأولية التي حددناها عند 0.85 معقولة؛ 85% من التشابه يجب أن يكون “نفس السؤال”، أليس كذلك؟

خطأ. عند 0.85، حصلنا على نتائج ذاكرة التخزين المؤقت مثل:

هذه أسئلة مختلفة بإجابات مختلفة. سيكون إرجاع الاستجابة المخزنة مؤقتًا غير صحيح.

لقد اكتشفت أن الحدود المثلى تختلف حسب نوع الاستعلام:

نوع الاستعلام

العتبة المثلى

الأساس المنطقي

الأسئلة الشائعة على غرار الأسئلة

0.94

الدقة العالية المطلوبة؛ الإجابات الخاطئة تدمر الثقة

عمليات البحث عن المنتجات

0.88

المزيد من التسامح مع المباريات القريبة

استعلامات الدعم

0.92

التوازن بين التغطية والدقة

استعلامات المعاملات

0.97

التسامح منخفض للغاية مع الأخطاء

لقد قمت بتطبيق عتبات خاصة بنوع الاستعلام:

فئة AdaptiveSemanticCache:

تعريف __init__(الذات):

العتبات الذاتية = {

“الأسئلة الشائعة”: 0.94،

“البحث”: 0.88،

“الدعم”: 0.92،

“المعاملات”: 0.97،

“الافتراضي”: 0.92

}

self.query_classifier = QueryClassifier()

def get_threshold(self, query: str) -> float:

query_type = self.query_classifier.classify(query)

إرجاع self.thresholds.get(query_type, self.thresholds[‘default’])

def get(self, query: str) -> اختياري[str]:

العتبة = self.get_threshold(الاستعلام)

query_embedding = self.embedding_model.encode(query)

التطابقات = self.vector_store.search(query_embedding, top_k=1)

إذا المباريات والمباريات[0].التشابه >= العتبة:

إرجاع self.response_store.get(matches[0].بطاقة تعريف)

عودة لا شيء

منهجية ضبط العتبة

لم أستطع ضبط العتبات بشكل أعمى. كنت بحاجة إلى الحقيقة الأساسية بشأن أزواج الاستعلام التي كانت في الواقع “متماثلة”.

منهجيتنا:

الخطوة 1: أزواج الاستعلام النموذجية. قمت بأخذ عينات من 5000 زوج استعلام بمستويات تشابه مختلفة (0.80-0.99).

الخطوة 2: وضع العلامات البشرية. وصف المفسرون كل زوج بأنه “نفس النية” أو “نية مختلفة.” لقد استخدمت ثلاثة تعليقات توضيحية لكل زوج وحصلت على أغلبية الأصوات.

الخطوة 3: حساب منحنيات الدقة/الاستدعاء. لكل عتبة، قمنا بحساب:

  • الدقة: من بين عدد مرات الوصول إلى ذاكرة التخزين المؤقت، ما هو الجزء الذي كان له نفس النية؟

  • تذكر: من بين الأزواج ذات النية نفسها، ما هو الكسر الذي قمنا بضربه في ذاكرة التخزين المؤقت؟

تعريف compute_precision_recall (أزواج، تسميات، عتبة):

“””حساب الدقة والاستدعاء عند عتبة التشابه المحددة.”””

توقعات = [1 if pair.similarity >= threshold else 0 for pair in pairs]

true_positives = مجموع (1 لـ p، l في الرمز البريدي (تنبؤات، تسميات) إذا كان p == 1 وl == 1)

false_positives = مجموع (1 لـ p، l في الرمز البريدي (تنبؤات، تسميات) إذا كان p == 1 وl == 0)

false_negatives = sum(1 لـ p، l في الرمز البريدي (تنبؤات، تسميات) إذا كان p == 0 وl == 1)

الدقة = true_positives / (true_positives + false_positives) إذا (true_positives + false_positives) > 0 وإلا 0

استدعاء = true_positives / (true_positives + false_negatives) إذا (true_positives + false_negatives)> 0 آخر 0

دقة العودة، أذكر

الخطوة 4: حدد العتبة بناءً على تكلفة الأخطاء. بالنسبة إلى استعلامات الأسئلة الشائعة حيث تؤدي الإجابات الخاطئة إلى الإضرار بالثقة، فقد قمت بتحسين الدقة (أعطت عتبة 0.94 دقة بنسبة 98٪). بالنسبة لاستعلامات البحث التي يؤدي فقدان ذاكرة التخزين المؤقت فيها إلى تكاليف مالية فقط، فقد قمت بتحسين عملية الاستدعاء (عتبة 0.88).

الكمون النفقات العامة

يضيف التخزين المؤقت الدلالي زمن الوصول: يجب عليك تضمين الاستعلام والبحث في مخزن المتجهات قبل معرفة ما إذا كنت تريد استدعاء LLM أم لا.

قياساتنا:

عملية

الكمون (ص50)

الكمون (ص 99)

تضمين الاستعلام

12 مللي ثانية

28 مللي ثانية

بحث المتجهات

8 مللي ثانية

19 مللي ثانية

إجمالي البحث عن ذاكرة التخزين المؤقت

20 مللي ثانية

47 مللي ثانية

استدعاء LLM API

850 مللي ثانية

2400 مللي ثانية

إن الحمل البالغ 20 مللي ثانية لا يكاد يذكر مقارنة باستدعاء LLM الذي يبلغ 850 مللي ثانية والذي نتجنبه عند الوصول إلى ذاكرة التخزين المؤقت. حتى عند p99، فإن الحمل البالغ 47 مللي ثانية مقبول.

ومع ذلك، فإن فقدان ذاكرة التخزين المؤقت يستغرق الآن 20 مللي ثانية أطول من ذي قبل (التضمين + البحث + استدعاء LLM). عند معدل الإصابة الذي يبلغ 67%، فإن الحسابات تسير بشكل إيجابي:

تحسين صافي زمن الوصول بنسبة 65% إلى جانب خفض التكلفة.

إبطال ذاكرة التخزين المؤقت

أصبحت الاستجابات المخزنة مؤقتًا قديمة. تتغير معلومات المنتج، ويتم تحديث السياسات، وتصبح الإجابة الصحيحة بالأمس هي الإجابة الخاطئة اليوم.

لقد قمت بتنفيذ ثلاث استراتيجيات إبطال:

  1. TTL على أساس الوقت

انتهاء الصلاحية البسيط بناءً على نوع المحتوى:

TTL_BY_CONTENT_TYPE = {

‘التسعير’: timedelta(hours=4), # التغييرات بشكل متكرر

“السياسة”: timedelta(days=7), # نادرا ما تتغير

‘معلومات_المنتج’: timedelta(days=1), # التحديث اليومي

‘general_faq’: timedelta(days=14), #مستقر جدًا

}

  1. الإبطال على أساس الحدث

عندما تتغير البيانات الأساسية، قم بإبطال إدخالات ذاكرة التخزين المؤقت ذات الصلة:

فئة CacheInvalidator:

تعريف on_content_update(self, content_id: str, content_type: str):

“””إبطال إدخالات ذاكرة التخزين المؤقت المرتبطة بالمحتوى المحدث.”””

# ابحث عن الاستعلامات المخزنة مؤقتًا التي تشير إلى هذا المحتوى

تتأثر_الاستعلامات = self.find_queries_referencing(content_id)

بالنسبة إلى query_id في الاستعلامات المتأثرة:

self.cache.invalidate(query_id)

self.log_invalidation(content_id, len(affected_queries))

  1. كشف الجمود

بالنسبة إلى الاستجابات التي قد تصبح قديمة بدون أحداث واضحة، قمت بإجراء فحوصات دورية للحداثة:

def check_freshness(self, cached_response: dict) -> منطقي:

“””التحقق من أن الاستجابة المخزنة مؤقتًا لا تزال صالحة.”””

# أعد تشغيل الاستعلام مقابل البيانات الحالية

Fresh_response = self.generate_response(cached_response[‘query’])

# قارن التشابه الدلالي للاستجابات

cached_embedding = self.embed(cached_response[‘response’])

Fresh_embedding = self.embed(fresh_response)

التشابه = جيب التمام_التشابه (التضمين المخبأ، التضمين الطازج)

# إذا تباينت الإجابات بشكل كبير، يتم إبطالها

إذا كان التشابه <0.90:

self.cache.invalidate(cached_response[‘id’])

العودة كاذبة

عودة صحيح

نقوم بإجراء فحوصات الحداثة على عينة من الإدخالات المخزنة مؤقتًا يوميًا، لاكتشاف الحداثة التي تفوتها TTL والإبطال المستند إلى الحدث.

نتائج الإنتاج

بعد ثلاثة أشهر من الإنتاج:

متري

قبل

بعد

يتغير

معدل ضرب ذاكرة التخزين المؤقت

18%

67%

+272%

تكاليف LLM API

47 ألف دولار شهرياً

12.7 ألف دولار شهريًا

-73%

متوسط ​​الكمون

850 مللي ثانية

300 مللي ثانية

-65%

معدل إيجابي كاذب

لا يوجد

0.8%

شكاوى العملاء (إجابات خاطئة)

خط الأساس

+0.3%

الحد الأدنى من الزيادة

كان المعدل الإيجابي الخاطئ بنسبة 0.8% (الاستعلامات حيث قمنا بإرجاع استجابة مخبأة غير صحيحة لغويًا) ضمن الحدود المقبولة. حدثت هذه الحالات في المقام الأول عند حدود العتبة الخاصة بنا، حيث كان التشابه أعلى بقليل من الحد الفاصل ولكن النية اختلفت قليلاً.

المزالق لتجنب

لا تستخدم عتبة عالمية واحدة. أنواع الاستعلام المختلفة لها تسامح مختلف مع الأخطاء. ضبط العتبات لكل فئة.

لا تتخطى خطوة التضمين في نتائج ذاكرة التخزين المؤقت. قد تميل إلى تخطي عملية التضمين عند إرجاع الاستجابات المخزنة مؤقتًا، ولكنك تحتاج إلى التضمين لإنشاء مفتاح ذاكرة التخزين المؤقت. النفقات العامة لا مفر منها.

لا تنسى الإبطال. يؤدي التخزين المؤقت الدلالي بدون استراتيجية الإبطال إلى استجابات قديمة تؤدي إلى تآكل ثقة المستخدم. بناء إبطال من اليوم الأول.

لا تقم بتخزين كل شيء. لا ينبغي تخزين بعض الاستعلامات مؤقتًا: الاستجابات الشخصية والمعلومات الحساسة للوقت وتأكيدات المعاملات. بناء قواعد الاستبعاد.

def must_cache(self, query: str, Response: str) -> bool:

“””تحديد ما إذا كان يجب تخزين الاستجابة مؤقتًا.””

# لا تقم بتخزين الاستجابات الشخصية مؤقتًا

إذا كان self.contains_personal_info (الاستجابة):

العودة كاذبة

# لا تقم بتخزين المعلومات الحساسة للوقت

إذا self.is_time_sensitive (استعلام):

العودة كاذبة

# لا تقم بتخزين تأكيدات المعاملات مؤقتًا

إذا self.is_transactional (الاستعلام):

العودة كاذبة

عودة صحيح

الوجبات السريعة الرئيسية

يعد التخزين المؤقت الدلالي نمطًا عمليًا للتحكم في تكلفة LLM والذي يلتقط أخطاء التخزين المؤقت المطابقة تمامًا للتكرار. وتتمثل التحديات الرئيسية في ضبط العتبة (استخدم عتبات خاصة بنوع الاستعلام استنادًا إلى تحليل الدقة/الاستدعاء) وإبطال ذاكرة التخزين المؤقت (الجمع بين TTL والكشف المستند إلى الحدث والجاذبية).

مع خفض التكلفة بنسبة 73%، كان هذا هو أعلى تحسين لدينا في عائد الاستثمار لأنظمة LLM للإنتاج. تعقيد التنفيذ معتدل، ولكن ضبط العتبة يتطلب اهتماما دقيقا لتجنب تدهور الجودة.

سرينفاسا ريدي هوليبيدو ريدي هو مهندس برمجيات رئيسي.

مرحبًا بك في مجتمع VentureBeat!

برنامج نشر الضيوف الخاص بنا هو المكان الذي يتبادل فيه الخبراء الفنيون الأفكار ويقدمون تعمقًا محايدًا وغير مكتسب حول الذكاء الاصطناعي والبنية التحتية للبيانات والأمن السيبراني وغيرها من التقنيات المتطورة التي تشكل مستقبل المؤسسة.

اقرأ المزيد من برنامج نشر الضيف الخاص بنا – وتحقق من موقعنا المبادئ التوجيهية إذا كنت مهتمًا بالمساهمة بمقالة خاصة بك!