LLM Caching
אחת הדרכים הפשוטות לחסוך הרבה כסף ולהאיץ תגובה: לא לשלם פעמיים על אותו חישוב. שלושה סוגי caching ומתי כל אחד.
שלושה דברים שנקראים "מטמון"
המילה אחת, והתופעות שונות לגמרי — בעיקר בסיכון. ההפרדה הזאת היא כל העמוד:
- מטמון פרומפט. הספק שומר את החלק הקבוע של הבקשה ולא מחשב אותו מחדש. חוסך הרבה, אפס סיכון — התשובה עדיין נוצרת מחדש בכל פעם.
- מטמון התאמה מדויקת. אותה שאלה בדיוק מחזירה את התשובה השמורה. חוסך הכל, סיכון נמוך, ורלוונטי לעיתים רחוקות — כי אנשים לא שואלים אותו דבר פעמיים.
- מטמון סמנטי. שאלה דומה מחזירה תשובה שמורה. חוסך הכי הרבה, וזה היחיד שיכול להחזיר תשובה שגויה.
הסדר הזה הוא גם סדר היישום המומלץ: הראשון כמעט תמיד, השני כשיש דפוס, והשלישי רק אחרי שהבנת בדיוק מה אתה מסכן.
ולפני הכל — מטמון הוא אופטימיזציה, לא תיקון. אם המערכת איטית כי היא שולפת יותר מדי קטעים או שולחת היסטוריה שלמה בכל תור, המטמון יסתיר את זה ולא יפתור. שווה לבדוק מה באמת צורך לפני שמוסיפים שכבה.
שניים מהשלושה בטוחים ולא מעניינים. השלישי מפתה, והוא זה שדורש שתדע מה אתה עושה.
מטמון פרומפט: הזול והבטוח
ברוב המערכות, רוב הטוקנים בבקשה הם קבועים: הנחיית המערכת, הסכימה, הדוגמאות, לפעמים מסמך שלם שנשלח בכל פעם. רק הסוף משתנה — השאלה של המשתמש.
מטמון פרומפט מנצל בדיוק את זה: הספק שומר את החלק הקבוע במצב מעובד, ובקריאה הבאה לא מחשב אותו מחדש. התוצאה היא הוזלה משמעותית של הקלט, וגם תגובה מהירה יותר.
והתנאי היחיד הוא גם מה שכולם שוברים: הקידומת חייבת להיות זהה בבתים. שינוי של תו אחד בתחילת הבקשה מבטל את ההתאמה לכל מה שאחריו.
הכלל שנגזר: הקבוע קודם, המשתנה אחרון
# ✗ הורס את המטמון בכל בקשה
messages = [
{"role": "system", "content": f"היום {today}. " + INSTRUCTIONS},
{"role": "system", "content": SCHEMA},
{"role": "user", "content": question},
]
# ✓ כל מה שקבוע בהתחלה, כל מה שזז בסוף
messages = [
{"role": "system", "content": INSTRUCTIONS}, # לא משתנה
{"role": "system", "content": SCHEMA}, # לא משתנה
{"role": "system", "content": f"היום {today}."},
{"role": "user", "content": question},
]
התאריך בדוגמה הראשונה נראה תמים והוא מבטל את המטמון פעם ביום לפחות — ואם הוא כולל שעה, בכל בקשה. אותו דבר נכון למזהה משתמש, חותמת זמן, מזהה בקשה, וכל דבר שמישהו הוסיף "לצורכי דיבאג" בתחילת ההנחיה.
שני פרטים נוספים שכדאי להכיר: לשמירה יש תפוגה — היא נמחקת אחרי זמן חוסר שימוש, ולכן היא עוזרת בתעבורה רציפה ופחות בבקשה בודדת ביום; ויש סף גודל — קידומת קצרה מדי לא נשמרת בכלל, כך שהטכניקה משתלמת בעיקר כשהחלק הקבוע גדול.
ובעברית יש תוספת: טקסט עברי צורך יותר טוקנים מאותו טקסט באנגלית, ולכן גם החלק הקבוע וגם החיסכון גדולים יותר. אם ההנחיות שלך בעברית, הטכניקה הזאת שווה לך יותר ממה שמדווח במדריכים באנגלית.
מתי זה בכלל משתלם
חישוב פשוט שמכריע: כמה מהבקשה קבוע וכמה משתנה.
מערכת A — עוזר על מסמכי החברה
קבוע: הנחיות + סכימה ≈ 2,000 טוקן
משתנה: שאלה + קטעים ≈ 3,000 טוקן
→ 40% מהקלט נשמר. שווה מאוד.
מערכת B — סיווג הודעה קצרה
קבוע: הנחיה קצרה ≈ 150 טוקן
משתנה: ההודעה ≈ 200 טוקן
→ כמעט אין מה לשמור. דלג.
הכלל: ככל שההקשר הקבוע גדול יותר, כך הטכניקה שווה יותר. במערכת שמזריקה מדריך שלם או עשרות דוגמאות לכל בקשה — זה אחד השיפורים הגדולים ביותר שאפשר לעשות, ובלי שום סיכון.
התאמה מדויקת: פשוט, ומוגבל
לשמור את התשובה לפי השאלה, ולהחזיר אותה כשאותה שאלה חוזרת. בטוח כמעט לחלוטין — אותו קלט, אותו פלט.
הבעיה היא שזה כמעט אף פעם לא קורה בטקסט חופשי. אנשים מנסחים אחרת בכל פעם, ולכן שיעור הפגיעות נמוך מאוד — אלא אם אתה מנרמל.
import hashlib, re
def key(q, model, prompt_version):
q = q.strip().lower()
q = re.sub(r"[^\w\s\u0590-\u05FF]", "", q) # פיסוק
q = re.sub(r"\s+", " ", q) # רווחים
raw = "%s|%s|%s" % (prompt_version, model, q)
return hashlib.sha256(raw.encode()).hexdigest()
ובעברית, הנרמול הוא מה שהופך את זה מחסר תועלת לשימושי, כי אותה שאלה נכתבת בהרבה יותר צורות: עם ובלי ה"א הידיעה ("מה המחיר" / "מה מחיר"), אותיות סופיות, כתיב מלא וחסר. נרמול פשוט מכפיל את שיעור הפגיעות.
ואיפה זה באמת עובד: לא בשאלות של משתמשים אלא בקריאות פנימיות. סיווג של אותם ערכים חוזרים, תרגום של מחרוזות ממשק, עיבוד של פריטי קטלוג. שם הקלט חוזר על עצמו באמת, ושיעור הפגיעות גבוה.
מטמון סמנטי: איפה זה נשבר
הרעיון מפתה: להטמיע את השאלה, למצוא שאלה קודמת דומה, ולהחזיר את התשובה שלה. שיעור פגיעות גבוה בהרבה, וחיסכון אמיתי.
וזה גם היחיד מהשלושה שיכול להחזיר תשובה נכונה לשאלה אחרת. לא שגיאה — תשובה. שתי שאלות יכולות להיות דומות מאוד במרחב ההטמעות ושונות לחלוטין במשמעות:
"מה מדיניות ההחזרות למוצרי חשמל?"
"מה מדיניות ההחזרות למוצרי הלבשה?"
→ דמיון גבוה מאוד. משמעות שונה.
"כמה עולה המסלול הבסיסי?"
"כמה עולה המסלול העסקי?"
→ אותו דבר. ותשובה שגויה כאן עולה כסף.
ההבדל בשני המקרים הוא מילה אחת שנושאת את כל המשמעות, ובדיוק זה מה שהטמעה מטשטשת. ככל שהשאלות במערכת שלך דומות זו לזו במבנה, כך הסיכון גבוה יותר — וזה מתאר את רוב מערכות התמיכה.
איך להשתמש בזה בלי להישרף
- סף גבוה, ולא לפי תחושה. תבדוק על שאלות אמיתיות מה הדמיון בין צמדים שאסור להתבלבל ביניהם, ותקבע את הסף מעליהם. סף שנבחר מהאוויר הוא ניחוש.
- לעולם לא על שאלות עם פרמטר. מספר הזמנה, שם מוצר, סכום, תאריך. אלה בדיוק המקרים שבהם המילה שמבדילה נבלעת.
- לא כשהתשובה תלויה במשתמש. אם התשובה מושפעת מהרשאות, מסלול או היסטוריה — מטמון סמנטי ידלוף בין משתמשים.
- להתחיל בצל. להריץ שבוע בלי להגיש: לתעד מתי היה פגיעה ומה הייתה התשובה האמיתית, ולהשוות. זה הופך את הסף מהימור למדידה.
ובעברית — חובה מודל הטמעה רב-לשוני, ורצוי לאמת אותו בעצמך. מודל שאומן לאנגלית יחזיר ציוני דמיון על עברית בלי שום שגיאה, והם פשוט לא אמינים. אותו כלל שמפורט בReranking ובEmbeddings.
מפתח המטמון: מה חייב להיכנס
זו הטעות שגורמת לתקלות הכי מוזרות, והיא נפוצה. מפתח שמורכב רק מהשאלה הוא מפתח שיחזיר תשובות מהעבר.
מה שחייב להיות בפנים:
- גרסת הפרומפט. שינית הנחיה? התשובות הישנות נוצרו לפי ההנחיה הקודמת. בלי זה, שינוי פרומפט לא ישפיע על שום דבר ששמור.
- מזהה המודל. החלפת מודל ולא שינית מפתח — אתה מגיש את התשובות של הקודם.
- היקף המשתמש או הארגון. הפריט הקריטי. בלעדיו, תשובה שנוצרה עבור לקוח אחד תוגש לאחר — וזו לא תקלת ביצועים אלא דליפה.
- גרסת המסמכים. אם התשובה נגזרה משליפה, עדכון המסמך צריך לפסול את מה שנשמר.
במערכת רב-לקוחית, מפתח בלי מזהה ארגון הוא באג אבטחה שנראה כמו שיפור ביצועים. תבנה אותו פנימה מההתחלה — להוסיף אותו אחר כך פירושו לזרוק את כל מה שנשמר.
מה אסור לשמור
- תשובה שתלויה בהרשאות. מה שמשתמש רואה תלוי במי הוא.
- מידע שמשתנה בזמן. מלאי, יתרה, סטטוס הזמנה, זמינות. תשובה נכונה מלפני שעה היא תשובה שגויה עכשיו.
- כל דבר עם נתונים אישיים בפלט. גם אם המפתח תקין — המטמון עצמו הוא עכשיו מאגר שצריך לנהל, למחוק ולאבטח.
- פעולות. מטמון על קריאה שמבצעת משהו — שולחת, מחייבת, יוצרת — הוא לא מטמון אלא באג. שמור רק על קריאות.
- תשובות בתחומים רגישים. רפואי, משפטי, פיננסי. ההקשר משנה יותר משנדמה, ושתי שאלות דומות הן מצבים שונים.
פסילה: מתי לזרוק
שתי גישות, ואחת מהן נכונה כמעט תמיד.
לפי זמן. תפוגה קבועה. פשוט, צפוי, ולא דורש שום תשתית. הכיול הוא לפי כמה מהר המידע מתיישן: שעות לתוכן שמשתנה, ימים לתיעוד יציב.
לפי אירוע. מסמך עודכן — פוסלים מה שנגזר ממנו. מדויק יותר, ודורש לדעת מה נגזר ממה. בפועל, זה אומר לשמור לצד כל תשובה את מזהי המסמכים ששימשו אותה.
ההמלצה: התחל בזמן. תפוגה של שעות מכסה את רוב הסיכון בעשירית מהעבודה, ואפשר לעבור לפסילה לפי אירוע כשמתברר שהיא חסרה. מה שכן שווה כבר עכשיו הוא כפתור לרוקן הכל — ביום שתגלה תשובה שגויה שמורה, תרצה אותו.
וטכניקה שכדאי להכיר: גרסה במפתח במקום מחיקה. להעלות מונה גלובלי פוסל את כל מה שקודם בלי לגעת באחסון — זול, מיידי, ובלי סיכון של מחיקה חלקית.
בסוכנים זה מתנהג אחרת
כשהמערכת לא עונה פעם אחת אלא מריצה שרשרת של צעדים, המטמון משנה אופי — ולטובה, כי הרבה מהחזרתיות שם אמיתית.
מה ששווה לשמור בסוכן:
- תוצאות קריאות קריאה בלבד. שליפת פרטי לקוח, בדיקת מחיר, קריאת מסמך. אותה קריאה באותה שיחה חוזרת לעיתים קרובות, ואין סיבה לשלם עליה פעמיים. אף פעם לא על קריאה שמבצעת משהו.
- הקידומת הקבועה. בסוכן ההנחיות והגדרות הכלים גדולות ונשלחות בכל צעד — וזה בדיוק המקרה שבו מטמון פרומפט מחזיר הכי הרבה. שרשרת של חמישה צעדים שולחת אותן חמש פעמים.
ומה שלא: החלטות. לשמור "מה הצעד הבא" לפי מצב דומה נשמע חכם ומייצר סוכן שחוזר על טעות בעקביות. ההחלטה תלויה בהיסטוריה המלאה, ושתי שיחות שנראות דומות יכולות להיות במצבים שונים.
ופרט שקל לפספס: מטמון קריאות כלים חייב היקף של שיחה או של משתמש. פרטי לקוח שנשמרו בשיחה אחת ומוגשים באחרת הם אותה דליפה שתוארה למעלה, רק שהפעם היא קורית בתוך זרימה אוטומטית שאף אחד לא קורא.
לשמור את השליפה, לא רק את התשובה
נקודה שמוזנחת ולעיתים קרובות עדיפה: ב-RAG, החלק היקר הוא לא תמיד המודל.
לפני שהמודל נקרא בכלל, קורים דברים שאפשר לשמור בבטחה מוחלטת:
- הטמעת השאילתה. אותה שאלה מייצרת אותו וקטור. שמירה לפי טקסט מנורמל חוסכת קריאה, ואין בה שום סיכון.
- תוצאת השליפה. אותה שאילתה על אותו אינדקס מחזירה את אותם קטעים. שווה במיוחד כשיש גם שלב דירוג מחדש, שהוא היקר בשרשרת.
- הטמעות של המסמכים. מחושבות פעם אחת. זה נשמע מובן מאליו, ובכל זאת יש מערכות שמחשבות מחדש בכל אינדוקס.
היתרון הגדול: התשובה עדיין נוצרת מחדש, ולכן היא מותאמת למשתמש ולהקשר — ואתה חוסך את הצד שאין בו שיקול דעת. בהרבה מערכות זה חוסך יותר ממטמון סמנטי ובלי אף אחד מהסיכונים שלו.
סדר היישום: מה לעשות בשבוע הראשון
אחרי כל ההבחנות, זה מסתכם בסדר פעולות. וההמלצה היא דווקא לא להתחיל מהמטמון.
- תמדוד לאן הכסף הולך. עלות לבקשה, בהתפלגות. לרוב מגלים שהעשירון העליון צורך פי כמה — ושם יושבת בעיה אחרת לגמרי: היסטוריה שלא נחתכת, יותר מדי קטעים שנשלפים, או לולאה. אף מטמון לא יתקן את אלה, הוא רק יסתיר אותם.
- תסדר את סדר ההודעות. כל מה שקבוע בהתחלה, כל מה שמשתנה בסוף. עשר דקות עבודה, ולרוב זה ההישג הגדול ביותר בכל הרשימה.
- תפעיל מטמון פרומפט אם הספק שלך תומך. אין החלטה לקבל — זה בטוח.
- תשמור את השליפה — הטמעות ותוצאות. גם כאן אין סיכון.
- ורק עכשיו תשקול סמנטי, ובצל, ואחרי שמדדת ספים על שאלות אמיתיות.
הצעדים 2–4 מכסים את רוב החיסכון בלי אף אחת מהשאלות הקשות. מי שמתחיל מהחמישי בונה את החלק המסוכן לפני שהוא מיצה את הבטוח — וזו, ברוב המקרים שראיתי, הסיבה שמטמון סמנטי מקבל שם רע.
ושתי אפשרויות שאינן מטמון ולעיתים עדיפות
לפני שמוסיפים שכבה, שווה לדעת ששני דברים אחרים חוסכים לעיתים קרובות יותר ובלי שום סיכון.
מודל קטן יותר למשימה קטנה יותר. סיווג, חילוץ שדה, החלטת ניתוב — כל אלה לא דורשים את המודל החזק ביותר. ניתוב של המשימות הפשוטות למודל זול הוא לרוב הוזלה גדולה יותר מכל מטמון, והוא גם מהיר יותר. הדרך לוודא שזה לא פוגע היא ערכת ההערכה — להריץ את שני המודלים ולהשוות, כפי שמפורט בLLMOps.
לשלוח פחות. לחתוך היסטוריה, לצמצם את מספר הקטעים שנשלפים, למחוק דוגמאות שכבר לא נחוצות. כל טוקן שלא נשלח הוא חיסכון ודאי שלא דורש תשתית, ובמערכות רבות זה הסעיף הגדול ביותר. מטמון על בקשה מנופחת עדיין משלם על הניפוח בכל פספוס.
איפה לשים את זה בפועל
שאלה מעשית שנשאלת אחרי שההחלטה התקבלה: איפה המטמון חי.
- בזיכרון של התהליך. מילון פשוט. מהיר מאוד, אפס תשתית, ונעלם בכל פריסה מחדש — וגם לא משותף בין מכונות. מתאים לפרוטוטייפ ולכלים פנימיים.
- במאגר מפתח-ערך משותף. מה שרוב המערכות צריכות. משותף בין מכונות, שורד פריסה, ותומך בתפוגה מובנית.
- בטבלה רגילה. אם כבר יש לך בסיס נתונים ואין עומס גבוה, זה עובד מצוין ולא מוסיף שירות לתחזק. לא לזלזל באפשרות הזאת.
- אצל הספק. מטמון הפרומפט אינו אצלך בכלל — אין מה לתחזק, ולכן גם אין החלטה לקבל.
ושתי נקודות שמפתיעות מי שמוסיף שכבה כזאת בפעם הראשונה. הראשונה: מטמון הוא נקודת כשל. כשהמאגר לא זמין, המערכת צריכה להמשיך לעבוד — כלומר כישלון בקריאה ממנו הוא אירוע שמתועד וממשיכים, לא שגיאה שמופיעה למשתמש.
def cached_answer(k, compute):
try:
hit = store.get(k)
if hit:
log("cache_hit", key=k)
return hit, True
except StoreError as e:
log("cache_unavailable", err=e) # ממשיכים
out = compute()
try:
store.set(k, out, ttl=3600)
except StoreError:
pass # לא נכשלים על כתיבה
return out, False
והשנייה: גודל. תשובות מצטברות, ובלי תפוגה המאגר גדל עד שמשהו נשבר. תפוגה היא לא רק נכונות — היא גם מה שמונע מהאחסון להיות בעיה בעוד חצי שנה.
למדוד, ולא רק את מה שנוח
המדד המתבקש הוא שיעור פגיעות, והוא מטעה לבדו.
מה שכדאי לעקוב אחריו:
- שיעור פגיעות לפי שכבה. בנפרד. מטמון פרומפט ומטמון סמנטי מתנהגים אחרת לגמרי, וממוצע ביניהם לא אומר כלום.
- עלות לבקשה, לפני ואחרי. המספר שבגללו עשית את זה.
- חביון באחוזון 95. מטמון משפר את החציון דרמטית ולפעמים לא נוגע בזנב — וזה מה שמשתמשים מרגישים.
- ובמטמון סמנטי — דיוק הפגיעות. דגימה ידנית של חמישים פגיעות בשבוע: האם התשובה שהוגשה באמת עונה על השאלה שנשאלה. שיעור פגיעות גבוה יכול להיות סימן טוב או סימן שאתה מגיש תשובות שגויות, ואין דרך לדעת בלי להסתכל.
ופרט מעשי: סמן בלוג כל תשובה שהוגשה ממטמון. בלי זה, תלונה על תשובה שגויה תוביל אותך לחקור פרומפט שכלל לא רץ.
ושווה לשמור לצד כל רשומה גם מתי היא נוצרה. כשמגיעה תלונה, ההבדל בין תשובה שנשמרה לפני שעה לבין אחת מלפני שבועיים הוא לרוב כל האבחון — והוא גם מה שאומר לך אם התפוגה שהגדרת מתאימה למציאות או שנבחרה מהאוויר.
מטמון והזרמה לא מסתדרים לבד
פרט טכני שמפיל מימושים ושכמעט לא כתוב עליו: אם הממשק שלך מזרים תשובה מילה-מילה, תשובה שמורה מגיעה בבת אחת. המשתמש רואה התנהגות שונה לגמרי בין פגיעה לפספוס — לפעמים זורם, לפעמים קופץ — וזה נקרא כתקלה.
שתי דרכים לטפל:
- לדמות הזרמה מהמטמון. לפרק את התשובה השמורה לחתיכות ולשלוח בקצב. נשמע מלאכותי והוא הפתרון הנכון — החוויה אחידה, וההבדל היחיד הוא שהתשובה מגיעה מהר יותר.
- לא להזרים בכלל במסכים שבהם התשובות קצרות ממילא.
ויש כאן גם צד שכדאי לדעת עליו במטמון פרומפט: הוא משפר את הזמן עד התו הראשון, לא את קצב הכתיבה. כלומר התשובה מתחילה להופיע מהר יותר, ואורך הייצור עצמו לא משתנה. זה בדיוק החלק שמשתמשים מרגישים, וזו סיבה נוספת שהטכניקה הזאת שווה גם כשהחיסכון הכספי קטן.
מתי לא
- כשהנפח קטן. מטמון מוסיף מורכבות ונקודת כשל. במאות בקשות בחודש, החיסכון לא מצדיק.
- כשכל שאלה ייחודית באמת. שיעור פגיעות אפסי הוא תשתית שמתחזקים בשביל כלום.
- כשהתשובות מותאמות אישית. אין מה לשתף בין משתמשים, ומטמון לכל משתמש כמעט אף פעם לא נפגע.
- לפני שמדדת מה יקר. לעיתים קרובות הבזבוז הוא היסטוריה שלא נחתכת או יותר מדי קטעים שנשלפים — ושניהם נפתרים בלי מטמון בכלל.
טעויות שחוזרות
- תאריך או מזהה בתחילת ההנחיה. מבטל את מטמון הפרומפט בלי שאף אחד שם לב.
- מפתח בלי גרסת פרומפט. ואז שינוי בהנחיה לא משפיע על כלום.
- מפתח בלי היקף משתמש. דליפה שנראית כמו שיפור ביצועים.
- סף סמנטי שנבחר בתחושה. תמדוד על צמדים שאסור להתבלבל ביניהם.
- מטמון סמנטי על שאלות עם פרמטר. המילה שמבדילה היא בדיוק זו שנבלעת.
- לשמור תשובות על מידע שמשתנה. נכון אתמול, שגוי היום.
- בלי נרמול בעברית. שיעור פגיעות אפסי בלי סיבה.
- לא לסמן בלוג מה הוגש ממטמון. ואז מנפים פרומפט שלא רץ.
הצעד הבא
Caching הוא חלק מבקרת עלות רחבה. שלב אותו עם ניתוב מודלים ותמחור חכם.