למה עברית יקרה פי 2-3 מאנגלית במודלי שפה
הסבר מלא על tokenization
כשאתם כותבים למודל בעברית אתם משלמים פי 2-3 יותר מאנגלים על אותה שיחה. זה לא באג ב-API, זה תוצאה ישירה של איך tokenization עובד. מבייטים, דרך BPE, ועד ה-context window שנשחק הרבה יותר מהר בעברית.
- #tokenization
- #BPE
- #LLM
- #context-window
- #hebrew
תוכן עניינים
כשאתם כותבים למודל בעברית, אתם משלמים פי 2-3 יותר מאנגלים על אותה שיחה.
לא כי ה-API יקר יותר לישראלים, וגם לא כי המודל “אוהב” אנגלית. הסיבה נמצאת בשלב שקורה עוד לפני שהמודל מתחיל לחשוב בכלל, ברגע שבו הטקסט שלכם מתפרק לחתיכות שנקראות טוקנים. עברית פשוט מתפרקת לחתיכות גרועות יותר.
זה לא באג, אלא תוצאה ישירה של איך tokenization עובד ואיך נתוני האימון נראים. ואותו מנגנון בדיוק הוא שקובע כמה מקום הטקסט שלכם תופס, כמה הוא עולה, וכמה ממנו בכלל נכנס לחלון ההקשר של המודל.
אז בואו נלך אחרי הטקסט שלכם, צעד אחר צעד, מהרגע שלחצתם Enter ועד שהמודל רואה אותו. נתחיל מהמקום הכי בסיסי שיש, עוד לפני מילים ואפילו לפני אותיות: בייטים.
מאיפה הכל מתחיל? בייטים
מחשבים לא מבינים טקסט, הם מבינים מספרים. כל תו שאתם מקלידים, עוד לפני שהוא מגיע למודל, מתורגם לרצף של מספרים דרך תקן שנקרא UTF-8.
וכבר כאן מתחיל הסיפור. באנגלית, כל אות לטינית מקודדת לבייט בודד. יש 256 ערכים אפשריים לבייט, וכל האלפבית האנגלי, הספרות והסימנים הנפוצים נכנסים בנוחות בתוך הטווח הזה. עברית, לעומת זאת, יושבת מחוץ לטווח, אז כל תו דורש 2 בייטים. סינית ויפנית צריכות אפילו 3.
שימו לב מה קרה כאן: עוד לא דיברנו על טוקנים בכלל, וכבר עברית פותחת בנקודת פתיחה כפולה באורכה. הקלידו טקסט משלכם ותראו את זה קורה בזמן אמת:
הקלידו טקסט בכל שפה. כל תו צבוע לפי כמה בייטים הוא תופס: ירוק=1, כחול=2, אדום=3.
למה לא פשוט להאכיל את המודל בבייטים
אם בסוף הכל מספרים, אפשר היה לכאורה לזרוק את הבייטים האלה ישר למודל ולסיים. אז למה לא עושים את זה?
כי זה בזבזני. “שלום עולם” הקצר היה הופך לעשרות בייטים, וכל אחד מהם תופס מקום בזיכרון הפעיל של המודל. הזיכרון הזה, ה-context window, הוא משאב יקר ומוגבל, וכל בייט שמבוזבז עליו הוא בייט שלא נשאר לתוכן אמיתי.
מה שצריך זה דרך לדחוס, לקחת רצפים ארוכים של בייטים ולקפל אותם ליחידות בודדות וצפופות. הדרך הזו נקראת BPE, וזה בדיוק המקום שבו טוקנים נולדים.
BPE: האלגוריתם שמחליט מה זה טוקן
BPE, ראשי תיבות של Byte Pair Encoding, הוא במקור אלגוריתם דחיסה פשוט שהותאם לעולם השפה. כל הרעיון שלו הוא לזהות מה חוזר על עצמו הרבה, ולתת לזה שם קצר.
זה עובד כך:
- מתחילים מכל הבייטים הבודדים, 256 סמלים בסיסיים
- סורקים המון טקסט ומחפשים את זוג הסמלים הסמוכים שמופיע הכי הרבה פעמים
- ממזגים את הזוג הזה לסמל אחד חדש, ומוסיפים אותו ל-vocabulary
- חוזרים שוב ושוב, אלפי פעמים, עד שמגיעים למספר הטוקנים הרצוי
ככל שצירוף מסוים נפוץ יותר בטקסט, כך מהר יותר הוא מקבל טוקן משלו. בסוף התהליך, GPT-4 עצר על 100,277 טוקנים. מספר לא עגול, כי גם התהליך שמייצר אותו לא עגול.
וכאן נקודה שלא כדאי לפספס: כל זה, הסריקה, ספירת התדירויות והמיזוגים, קורה פעם אחת בלבד, מראש, על כמויות עתק של טקסט. התוצאה היא אוצר מילים קבוע. כשאתם כותבים משפט בפועל הטוקנייזר כבר לא סופר כלום, הוא פשוט מפעיל את המיזוגים שכבר נלמדו ומחלק את הטקסט לפי המילון המוכן.
הכי קל להרגיש את זה כשמריצים את זה לבד. כל לחיצה היא מיזוג אחד, בדיוק כמו בצעד 2 ו-3 למעלה:
בכל צעד BPE מאחד את הזוג הסמוך הנפוץ ביותר לסמל חדש. לחצו מיזוג וראו איך אוצר המילים גדל. להמחשה, מתחילים מהתווים של הטקסט שלכם ולא מ-256 בייטים, ומתאמנים רק עליו.
אז איך טוקן נראה בפועל
עכשיו, אחרי שראינו איך הם נוצרים, אפשר להבין למה טוקנים כל כך לא אינטואיטיביים. הם לא חיתוכים נקיים לפי מילים או הברות, אלא פשוט מה שיצא מתהליך הדחיסה. ולפעמים זה נראה ממש מוזר:
"hello""strawberry""Hello""␣world"שימו לב לשתי השורות האחרונות. אות גדולה בתחילת מילה, או רווח שמודבק מלפנים, יוצרים טוקן אחר לגמרי. ה-tokenizer לא חושב במונחים של “מילה”, הוא חושב במונחים של תדירות סטטיסטית, ולכן ההיגיון שלו לא תמיד מסתדר עם שלנו.
ומשהו אחרון שקל לשכוח: למודל, אפילו הטקסט של הטוקן הוא רק בשבילנו. כל טוקן הוא בעיניו מספר בודד, המיקום שלו באוצר המילים, ושורת המספרים הזו (ה-token IDs) היא מה שבאמת נכנס פנימה. ומה הוא עושה עם המספרים האלה, איך הוא מחליט לאיזו מילה לתת משקל, זה כבר הסיפור של מנגנון הattention.
הטקסט שלכם מחולק לטוקנים אמיתיים של GPT-4 (cl100k). כל צבע הוא טוקן נפרד, עם ה-ID שלו.
ועכשיו, למה עברית כל כך יקרה
אם שיחקתם עם הווידג’ט למעלה והקלדתם משפט בעברית, כנראה כבר ראיתם את זה: מספר הטוקנים מזנק. שתי סיבות עומדות מאחורי הקפיצה הזו, והן עובדות יחד.
הסיבה הראשונה טכנית, ואותה כבר פגשנו. עברית מקודדת ל-2 בייטים לתו לעומת בייט אחד באנגלית, ו-BPE מתחיל את כל התהליך מהבייטים. כלומר עברית פותחת את המרוץ עם פיגור מובנה.
הסיבה השנייה סטטיסטית, וכאן הפער באמת נפתח. BPE ממזג זוגות לפי כמה הם נפוצים בנתוני האימון. באנגלית, צירופים כמו “th”, “er” ו-”ing” חוזרים כל כך הרבה שהם הופכים לטוקן בודד כמעט מיד. עברית, שמיוצגת הרבה פחות באינטרנט, כמעט לא מגיעה לשלב המיזוג. הצירופים שלה פשוט לא הופיעו מספיק פעמים, אז כל תו נשאר לבד.
חברו את שתי הסיבות ותקבלו את התוצאה: כ-4 תווים לטוקן באנגלית, לעומת קרוב לתו אחד לטוקן בעברית.
ההשלכות יכולות להיות מאוד מורגשות, ה-context window נשחק מהר יותר, עלות ה-API על אותו תוכן גבוהה יותר, והמודל בפועל “זוכר” פחות ממה שכתבתם.
היחס המדויק משתנה ממשפט למשפט, לפעמים פי 2 ולפעמים יותר מ-3, תלוי כמה המילים נפוצות.
שלום, מה שלומך היום?
Hello, how are you today?
* מספר הטוקנים לפי הטוקנייזר של GPT-4 (cl100k), ותמחור input טיפוסי של $2.5 ל-1M טוקנים. להמחשה בלבד.
אותו משפט בשתי שפות, מטוקנן עם הטוקנייזר האמיתי של GPT-4. בחרו משפט והשוו מספר טוקנים ועלות.
Context Window
ה-context window הוא כמות הטוקנים שהמודל מחזיק מול העיניים בבת אחת: כל מה שהוא “רואה” ברגע נתון, מהשיחה, מההוראות ומהמסמכים שנתתם לו. במודלים של היום מדובר במאות אלפי טוקנים, ולפעמים אפילו מיליונים, מספרים שנשמעים כמעט בלתי מוגבלים.
אבל החלון נמדד בטוקנים, לא בתווים. ואם עברית תופסת יותר טוקנים, אז אותו חלון מחזיק פחות טקסט עברי מאשר אנגלי. גם חלון של מיליון טוקנים מתמלא בעברית הרבה יותר מהר, וזה משנה כל החלטה של מי שבונה מערכות מעל מודלים, כמה היסטוריית שיחה לשמור, כמה מסמכים לאחזר ב-RAG.
שלום, מה שלומך היום?
Hello, how are you today?
רוחב הפס = אותו חלון הקשר לשתי השפות (להמחשה). האפור = מה שנשאר פנוי.
מה הולך להשתנות
נסיים בנימה אופטימית, tokenization הוא צוואר בקבוק מוכר, וכבר עובדים על דרכים לעקוף אותו.
כבר היום יש ניסיונות למודלים שמוותרים על טוקנים לגמרי ומעבדים בייטים גולמיים ישירות. OpenAI עצמם שיפרו משמעותית את היחס לשפות לא-לטיניות בין GPT-3 ל-GPT-4, ועדיין יש פער, אבל הוא קטן יותר משהיה. ויש כיוון מבטיח של dynamic tokenization, שמתאים את הטוקנים לפי הקונטקסט במקום להסתמך על vocabulary קבוע מראש.
מה זה dynamic tokenization?
במקום אוצר מילים קבוע שנקבע פעם אחת באימון, הרעיון הוא לתת למודל להחליט על גבולות הטוקנים תוך כדי, לפי הקונטקסט. כך טקסט בשפה תת-מיוצגת לא נתקע עם החלוקה הגרועה שנקבעה מראש. זה עדיין תחום מחקר, לא משהו שרץ ברוב המודלים בפועל.
אבל כל זה עדיין במחקר. נכון להיום, אם אתם כותבים בעברית, אתם עדיין שורפים כנראה יותר טוקנים.
אז מה לקחנו מכאן
עברנו את כל המסע: מתווים, לבייטים, לטוקנים, ולמספרים שהמודל מקבל.
אותו טקסט, ארבעה שלבים: מהתווים, לבייטים, לטוקנים, ולמספרים שהמודל באמת מקבל. לחצו הפעלה.
ובעצם, כל המאמר הזה התחיל בשאלה על עברית אבל הנושא האמיתי הוא טוקניזציה. זו שכבה כמעט בלתי-נראית שיושבת בין מה שאתם כותבים לבין מה שהמודל מבין, ובשקט קובעת כמה הכול עולה, כמה נכנס לזיכרון, ואיך כל שפה, מספר או סימן בכלל נראים. המחיר של עברית הוא פשוט הסימפטום שהכי קל לנו, דוברי העברית, להרגיש אבל אותו מנגנון עצמו פועל מאחורי הקלעים על כל טקסט, בכל שפה.
המודל לא קורא מילים, הוא קורא טוקנים. וברגע שזה כך, עברית מתפרקת ליותר טוקנים מאנגלית ולכן עולה יותר ונכנס ממנה פחות לאותו חלון הקשר.
שלוש נקודות לקחת הביתה:
- המודל לא קורא מילים, הוא קורא טוקנים, וטוקניזציה היא מה שקובע כמה כל טקסט “שוקל”
- עברית עולה פי 2-3 (ולפעמים יותר) מאנגלית, משתי סיבות: UTF-8 ותת-ייצוג בנתוני האימון
- אותו פער שוחק את ה-context window בפועל, אז כשאורך השיחה או העלות קריטיים, ניסוח באנגלית חוסך הרבה
ואם בא לכם לשחק עם זה עוד קצת, tiktoken playground של OpenAI נותן ממשק נוח לראות בדיוק איך כל טקסט מתפצל.