आमचे LLM API बिल दरमहा 30% ने वाढत होते. रहदारी वाढत होती, पण तितकासा वेग नव्हता. जेव्हा मी आमच्या क्वेरी लॉगचे विश्लेषण केले तेव्हा मला खरी समस्या आढळली: वापरकर्ते समान प्रश्न वेगवेगळ्या प्रकारे विचारतात.
"तुमची रिटर्न पॉलिसी काय आहे?" "मी काहीतरी परत कसे करू?"आणि "मला परतावा मिळेल का?" ते सर्व आमच्या LLM सॉफ्टवेअरमध्ये स्वतंत्रपणे प्रवेश करत होते, जवळजवळ एकसारखे प्रतिसाद निर्माण करत होते, प्रत्येकाला पूर्ण API खर्च येत होता.
अचूक जुळणी कॅशिंग, स्पष्ट पहिले समाधान, या अनावश्यक कॉलपैकी फक्त 18% कॅप्चर केले. समान अर्थपूर्ण प्रश्न, कॅशेला पूर्णपणे बायपास करून, वेगळ्या शब्दांत.
म्हणून, मी प्रश्नांच्या अर्थावर आधारित सिमेंटिक कॅशिंग लागू केले, ते कसे तयार केले जातात यावर नाही. ते लागू केल्यानंतर, आमचा कॅशे हिट रेट 67% पर्यंत वाढला, ज्यामुळे LLM API खर्च 73% कमी झाला. परंतु तेथे पोहोचण्यासाठी समस्यांचे निराकरण करणे आवश्यक आहे जिथे निष्पाप अंमलबजावणी अयशस्वी होते.
अचूक जुळणी कॅशिंग अयशस्वी का होते?
पारंपारिक कॅशिंग कॅशे की म्हणून क्वेरी मजकूर वापरते. जेव्हा क्वेरी एकसारख्या असतात तेव्हा हे कार्य करते:
# अचूक जुळणीसाठी कॅशिंग
कॅशे_की = हॅश(क्वेरी_टेक्स्ट)
कॅशे_की कॅशेमध्ये असल्यास:
कॅशे परत करा(कॅशे_की)
परंतु वापरकर्ते एकसारखे प्रश्न तयार करत नाहीत. माझ्या 100,000 उत्पादन प्रश्नांच्या विश्लेषणात खालील गोष्टी आढळल्या:
-
फक्त १८% ते पूर्वीच्या क्वेरींचे अचूक डुप्लिकेट होते
-
४७% मागील क्वेरींप्रमाणेच शब्दार्थाने समान होते (समान हेतू, भिन्न शब्दरचना)
-
35% ते खरोखर नवीन चौकशी होते
ते 47% मोठ्या खर्च बचतीचे प्रतिनिधित्व करते जे आम्ही गमावत होतो. प्रत्येक शब्दार्थाप्रमाणे समान क्वेरीने संपूर्ण LLM आवाहन ट्रिगर केले, जे आम्ही आधीच मोजले होते त्याप्रमाणेच प्रतिसाद निर्माण करते.
सिमेंटिक कॅशिंग आर्किटेक्चर
सिमेंटिक कॅशिंग एम्बेडिंग-आधारित समानता शोधासह मजकूर-आधारित कीिंगची जागा घेते:
सिमेंटिक कॅशे वर्ग:
def __init__(स्वतः, एम्बेडिंग_मॉडेल, समानता_थ्रेशोल्ड=0.92):
self.embedding_model = एम्बेडिंग_मॉडेल
self.threshold = समानता_थ्रेशोल्ड
self.vector_store = VectorStore() # वेस, पाइनकोन इ.
self.response_store = ResponseStore() # रेडिस, डायनॅमो डीबी इ.
def get(self, query: str) -> optional(str):
"""शब्दार्थाप्रमाणे समान क्वेरी अस्तित्वात असल्यास कॅशे केलेला प्रतिसाद परत करा."""
query_embedding = self.embedding_model.encode(क्वेरी)
# सर्वात समान कॅशे केलेल्या क्वेरी शोधा
जुळण्या = self.vector_store.search(query_embedding, top_k=1)
जुळत असल्यास आणि जुळत असल्यास(0). समानता >= self.threshold:
Cache_id = जुळणी(0).id
self.response_store.get(cache_id) परत करा
काहीही परत करू नका
def array(स्व, क्वेरी: str, प्रतिसाद: str):
"""क्वेरी प्रतिसाद जोडी कॅशेमध्ये आहे."""
query_embedding = self.embedding_model.encode(क्वेरी)
Cache_id = create_id()
self.vector_store.add(cache_id, query_embedding)
self.response_store.set(cache_id, {
“query”: चौकशी,
‘प्रतिसाद’: प्रतिसाद,
“टाइमस्टॅम्प”: datetime.utcnow()
})
मूलभूत कल्पना: क्वेरी मजकूर विभाजित करण्याऐवजी, मी वेक्टर स्पेसमध्ये क्वेरी एम्बेड करतो आणि समानता थ्रेशोल्डमध्ये कॅशे केलेल्या क्वेरी शोधतो.
थ्रेशोल्ड समस्या
समानता थ्रेशोल्ड हा गंभीर पॅरामीटर आहे. ते खूप वर सेट करा आणि तुम्ही वैध कॅशे परिणाम गमावाल. ते खूप कमी करा आणि तुम्ही चुकीच्या उत्तरांसह परत याल.
आमचा 0.85 चा प्रारंभिक थ्रेशोल्ड वाजवी वाटला; ते 85% समान असावे "तोच प्रश्न," बरोबर?
चूक 0.85 वर, आम्हाला कॅशे परिणाम मिळाले जसे:
-
चौकशी: "मी माझी सदस्यता कशी रद्द करू शकतो?"
-
लपलेले: "मी माझी ऑर्डर कशी रद्द करू शकतो?"
-
समानता: 0.87
हे भिन्न प्रश्न आहेत ज्यांची उत्तरे भिन्न आहेत. परत केलेला कॅश केलेला प्रतिसाद अवैध असेल.
मला आढळले आहे की इष्टतम सीमा क्वेरीच्या प्रकारानुसार बदलतात:
|
क्वेरी प्रकार |
इष्टतम थ्रेशोल्ड |
तर्क |
|
FAQ शैलीतील प्रश्न |
०.९४ |
उच्च परिशुद्धता आवश्यक; चुकीच्या उत्तरांमुळे विश्वास नष्ट होतो |
|
उत्पादन शोध |
०.८८ |
जवळच्या सामन्यांसाठी अधिक सहनशीलता |
|
सपोर्ट चौकशी |
०.९२ |
कव्हरेज आणि अचूकता दरम्यान संतुलन |
|
व्यवहार प्रश्न |
०.९७ |
त्रुटींसाठी खूप कमी सहनशीलता |
मी क्वेरी प्रकार विशिष्ट थ्रेशोल्ड लागू केले आहेत:
ॲडॉप्टिव्ह सिमेंटिक कॅशे वर्ग:
__init__(स्वत:) ची व्याख्या:
व्यक्तिनिष्ठ थ्रेशोल्ड = {
‘FAQ’: ०.९४,
‘शोध’: ०.८८,
‘आधार’: ०.९२,
‘व्यवहार’: ०.९७,
‘डिफॉल्ट’: 0.92
}
self.query_classifier = QueryClassifier()
def get_threshold(self, query: str) -> float:
query_type = self.query_classifier.classify(क्वेरी)
self.thresholds.get(query_type, self.thresholds(‘default’)) परत करा
def get(self, query: str) -> optional(str):
थ्रेशोल्ड = self.get_threshold(क्वेरी)
query_embedding = self.embedding_model.encode(क्वेरी)
जुळण्या = self.vector_store.search(query_embedding, top_k=1)
जुळत असल्यास आणि जुळत असल्यास (0). समानता >= थ्रेशोल्ड:
self.response_store.ge(maches(0).id) परत करा
काहीही परत करू नका
थ्रेशोल्ड सेटिंग पद्धत
मी आंधळेपणाने थ्रेशहोल्ड समायोजित करू शकत नाही. कोणत्या क्वेरी जोड्या प्रत्यक्षात अस्तित्वात आहेत याबद्दल मला ग्राउंड सत्य हवे आहे "तीच गोष्ट."
आमची कार्यपद्धती:
पायरी 1: ठराविक क्वेरी जोड्या. मी भिन्न समानता स्तरांसह 5000 क्वेरी जोड्यांचा नमुना (0.80-0.99) घेतला.
पायरी २: मानवी लेबलिंग. समालोचकांनी प्रत्येक जोडीचे असे वर्णन केले: "तोच हेतू" किंवा "वेगळा हेतू." मी प्रत्येक जोडीसाठी तीन भाष्य वापरले आणि बहुसंख्य मते मिळाली.
पायरी 3: अचूकता/रिकॉल वक्रांची गणना करा. प्रत्येक थ्रेशोल्डसाठी, आम्ही गणना केली:
-
रिझोल्यूशन: कॅशे हिटच्या संख्येपैकी, कोणत्या भागाचा हेतू समान होता?
-
लक्षात ठेवा: समान हेतू असलेल्या जोड्यांपैकी, आपण कॅशेमध्ये कोणता अंश गुणाकार केला आहे?
compute_precision_recall ची व्याख्या(जोड्या, लेबले, थ्रेशोल्ड):
"""अचूकतेची गणना करा आणि निर्दिष्ट समानता थ्रेशोल्डवर रिकॉल करा."""
अंदाज = (1 जोडीनुसार समानता असल्यास >= जोडीनुसार इतर थ्रेशोल्ड 0)
true_positives = sum(p == 1 आणि l == 1 असल्यास p साठी 1, झिपमध्ये l (अंदाजे, लेबले)
false_positives = sum(p == 1 आणि l == 0 असल्यास p साठी 1, l zip (प्रेडिक्टर्स, लेबल्स)
false_negatives = sum(p == 0 आणि l == 1 असल्यास p साठी 1, l zip (अंदाजे, लेबले)
अचूकता = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 अन्यथा 0
कॉल = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
अचूकता परत करा, आठवा
पायरी 4: त्रुटींच्या किंमतीवर आधारित थ्रेशोल्ड निश्चित करा. FAQ प्रश्नांसाठी जिथे चुकीच्या उत्तरांमुळे आत्मविश्वास दुखावला जातो, मी अचूकता सुधारली (0.94 च्या थ्रेशोल्डने 98% अचूकता दिली). शोध क्वेरींसाठी जिथे कॅशे चुकते फक्त पैसे खर्च होतात, मी रिकॉल (0.88 चा थ्रेशोल्ड) ऑप्टिमाइझ केला.
ओव्हरहेड विलंब
सिमेंटिक कॅशिंग लेटन्सी जोडते: LLM ला कॉल करायचा की नाही हे जाणून घेण्यापूर्वी तुम्हाला क्वेरी समाविष्ट करावी लागेल आणि व्हेक्टर स्टोअरमध्ये शोधावे लागेल.
आमचे मोजमाप:
|
व्यावहारिक |
जिरे (पृ. ५०) |
जिरे (पृ. ९९) |
|
क्वेरी समाविष्ट करा |
12 ms |
28 ms |
|
वेक्टर शोध |
8 ms |
19 ms |
|
एकूण कॅशे शोध |
20 ms |
४७ मिसे |
|
LLM API वर कॉल करा |
850 ms |
2400 ms |
20 ms चे ओव्हरहेड 850 ms LLM कॉलच्या तुलनेत नगण्य आहे जे आम्ही कॅशेमध्ये प्रवेश करताना टाळतो. p99 वर देखील, 47ms चे ओव्हरहेड स्वीकार्य आहे.
तथापि, कॅशे मिसला आता पूर्वीपेक्षा 20 ms जास्त वेळ लागतो (समावेश + शोध + LLM कॉल). 67% च्या संसर्ग दराने, गणना अनुकूलपणे कार्य करते:
-
पूर्वी: 100% क्वेरी x 850 ms = 850 ms सरासरी
-
नंतर: (33% x 870 ms) + (67% x 20 ms) = 287 ms + 13 ms = 300 ms सरासरी
खर्च कपातीसह नेट लेटन्सीमध्ये 65% सुधारणा.
कॅशे अवैध करा
कॅश्ड प्रतिसाद कालबाह्य आहेत. उत्पादन माहिती बदलते, धोरणे अपडेट केली जातात आणि कालचे बरोबर उत्तर आजचे चुकीचे उत्तर बनते.
मी तीन टाळण्याच्या धोरणांची अंमलबजावणी केली आहे:
-
TTL वेळ आधारित
सामग्री प्रकारावर आधारित साधी कालबाह्यता:
TTL_BY_CONTENT_TYPE = {
‘किंमत’: टाइमडेल्टा(तास=4), # वारंवार बदल
“धोरण”: टाइमडेल्टा(दिवस=7), # ते क्वचितच बदलते
‘उत्पादन_माहिती’: टाइमडेल्टा(दिवस=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(प्रभावित_queries))
-
डेडलॉक शोधा
स्पष्ट कार्यक्रमांशिवाय कालबाह्य होऊ शकणाऱ्या प्रतिसादांसाठी, मी नियतकालिक रीसेंसी तपासणी करतो:
def check_freshness(self, cached_response: dict) -> बुलियन:
"""कॅशे केलेला प्रतिसाद अद्याप वैध असल्याचे सत्यापित करा."""
# वर्तमान डेटाच्या विरूद्ध क्वेरी पुन्हा चालवा
ताजा_प्रतिसाद = स्व. generate_response(cached_response(‘query’))
# प्रतिसादांच्या अर्थपूर्ण समानतेची तुलना करा
cached_embedding = self.embed(cached_response(‘प्रतिसाद’))
Fresh_embedding = self.embed(fresh_response)
समानता = कोसाइन_समानता (कॅश एम्बेडिंग, ताजे एम्बेडिंग)
# जर उत्तरे लक्षणीयरीत्या भिन्न असतील तर ती अवैध केली जातात
जर समानता <0.90:
self.cache.invalidate(cached_response(‘id’))
खोटे परतावे
खरे परत
TTL आणि इव्हेंट-आधारित अवैधतेमुळे सुटलेले ताजेपणा शोधण्यासाठी आम्ही दररोज कॅशे केलेल्या नोंदींच्या नमुन्यावर ताजेपणा तपासतो.
उत्पादन परिणाम
उत्पादनाच्या तीन महिन्यांनंतर:
|
मेट्रिक |
आधी |
नंतर |
तो बदलतो |
|
कॅशे हिट रेट |
१८% |
६७% |
+२७२% |
|
LLM API खर्च |
दरमहा 47 हजार डॉलर्स |
दरमहा $12.7 हजार |
-73% |
|
सरासरी विलंब |
850 ms |
300 ms |
-65% |
|
खोटे सकारात्मक दर |
काहीही नाही |
०.८% |
– |
|
ग्राहकांच्या तक्रारी (चुकीची उत्तरे) |
बेसलाइन |
+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 खर्च नियंत्रण नमुना आहे जो वारंवारतेशी जुळणाऱ्या कॅशिंग त्रुटी कॅप्चर करतो. मुख्य आव्हाने म्हणजे थ्रेशोल्ड ट्यूनिंग (परिशुद्धता/रिकॉल विश्लेषणावर आधारित क्वेरी प्रकार-विशिष्ट थ्रेशोल्ड वापरा) आणि कॅशे अवैधीकरण (टीटीएल, इव्हेंट-आधारित शोध आणि चिकाटीचे संयोजन).
73% किमतीत कपात करून, उत्पादन LLM प्रणालीसाठी ROI मध्ये ही आमची सर्वोच्च सुधारणा होती. अंमलबजावणीची जटिलता मध्यम आहे, परंतु थ्रेशोल्ड समायोजित करण्यासाठी गुणवत्ता ऱ्हास टाळण्यासाठी काळजीपूर्वक लक्ष देणे आवश्यक आहे.
श्रीनिवास रेड्डी होलिबिडू रेड्डी हे प्रमुख सॉफ्टवेअर अभियंता आहेत.
















