פרק 5 מתוך 5 | Integration Capstone

להשיק פרויקט $0: pipeline מקצה לקצה מ-prototype ל-deployment

בפרק הזה אתם מחברים את כל מה שבניתם בקורס למערכת אחת: אימון קצר ב-Kaggle, ייצוא adapter ל-Hugging Face Hub, ממשק Gradio שרץ על HF Spaces CPU, קריאות inference דרך ספקים חינמיים, וניטור שמונע מהמערכת להתרסק כשמגיע 429. זה לא פרק על עוד כלי. זה פרק על שיגור אחראי של מערכת קטנה שחיה בתוך מגבלות חינמיות בלי להעמיד פנים שהן לא קיימות.

מה יהיה ביד שלכם בסוף הפרק

מטרות למידה

  • תוכלו לתכנן אימון LoRA על מודל 7B ב-Kaggle T4 עם QLoRA ולשמור אותו בתוך תקציב של פחות מ-10 GPU-hours.
  • תוכלו לייצא adapter ל-Hugging Face Hub כ-private model ולהבין למה זה גבול נקי בין training ל-serving.
  • תוכלו לחבר Gradio UI על HF Spaces CPU tier חינמי לספק inference חינמי בלי להכניס secrets לקוד.
  • תוכלו לבנות מנגנון usage monitoring, provider rotation ו-graceful degradation שמחזיק מערכת קטנה ב-$0 גם כשאחד הספקים מגביל אתכם.

לפני שמתחילים

הפרויקט שלך

בפרקים 1-4 בניתם מפת compute, קריאה לספקי inference, notebook שלא מאבד עבודה, ו-fallback מקומי עם Ollama. בפרק הזה אתם מוסיפים שכבת שיגור: artifact, UI, monitoring ו-runbook שמחברים את כל החלקים למערכת אחת. זה פרק הסיום; אחריו אין עוד פרק שמתקן בשבילכם את הארכיטקטורה, ולכן המטרה היא להשאיר מערכת שאפשר לתחזק חודש שלם בלי לשלם ובלי לנחש.

מונחים שתפגשו בפרק

Termתרגוםהגדרה קצרה
QLoRAאימון LoRA על מודל מכווץשיטת fine-tuning שבה מודל בסיס נטען ב-4-bit ורק adapter קטן מאומן.
LoRA adapterמתאם LoRAקבצים קטנים שמייצגים התאמה למשימה בלי לשמור מחדש את כל משקולות המודל.
push_to_hubדחיפה ל-Hubפעולת upload מתוך ספריות Hugging Face שמעלה adapter או מודל ל-repository.
HF Spaces CPU Basicשכבת CPU חינמית ב-Spacesסביבת deployment חינמית ל-UI ולוגיקה קלה, לא להרצת מודל 7B בעצמה.
429יותר מדי בקשותקוד HTTP שמסמן rate limit; צריך backoff, rotation או תשובת fallback.
retry-afterכמה זמן לחכותHeader שמופיע לעיתים אחרי 429 ומציע מתי לנסות שוב.
usage ledgerיומן שימושרישום של ספק, בקשה, tokens, headers, כשלון ופעולת fallback.
בינוני12 דקותחינםאסטרטגיה

הארכיטקטורה: מה בדיוק נשלח ולמה זה נשאר $0

הטעות הכי נפוצה בפרויקטי AI חינמיים היא לדבר עליהם כאילו הם מוצר production מלא. הם לא. הם מערכת קטנה שמנצלת כמה שכבות חינמיות, וכל שכבה נותנת משהו אחר: Kaggle נותן GPU זמני לאימון, Hugging Face Hub נותן מקום יציב לשמור artifacts, HF Spaces CPU נותן ממשק משתמש פשוט, וספק inference כמו Groq או Cerebras נותן את ההרצה בפועל בלי GPU משלכם. ברגע שמערבבים את התפקידים האלה, מתחילות הבעיות. מישהו מנסה לטעון מודל 7B לתוך Space חינמי, מישהו אחר מאמן ב-Kaggle בלי checkpoint, ומישהו שלישי שם API key בקוד כי זה עבד בדמו מקומי.

לכן הארכיטקטורה שלנו מתחילה לא מהקוד אלא מגבולות האחריות. Kaggle הוא מקום עבודה זמני, לא מקום אחסון אמין ולא שרת production. Hugging Face Hub הוא ארון artifacts: adapter, model card, README, אולי dataset קטן אם הוא מותר לפרסום. HF Spaces CPU Basic הוא UI/router: הוא מקבל קלט מהמשתמש, קורא לספק inference, מציג תשובה, ושומר כמה מדדי שימוש בסיסיים. Provider API הוא המנוע בזמן אמת: Groq כשצריך latency, Cerebras כשצריך batch כבד tokens, OpenRouter כשצריך מודל חלופי, ואולי Ollama כשצריך פרטיות או עבודה מקומית.

3 דקות

עשו עכשיו: ציירו את גבולות האחריות

ציירו ארבע קופסאות: Kaggle, Hub, HF Space, Provider API. ליד כל אחת כתבו משפט אחד: מה היא עושה, ומה היא לא עושה. אם יש קופסה שאחראית על שני דברים כבדים, פירקתם לא נכון.

השם “$0 pipeline” קצת מפתה, אז נדייק אותו. $0 לא אומר בלתי מוגבל. הוא אומר שאתם לא שולפים כרטיס אשראי, אבל אתם כן מנהלים תקציב: GPU-hours ב-Kaggle, בקשות ליום אצל Groq, tokens ליום אצל Cerebras, שינה של Space חינמי כשאין שימוש, ומגבלות privacy על מה מותר להעלות ל-Hub. מערכת טובה לא מסתירה את המגבלות האלה. היא הופכת אותן לחלק מההתנהגות. כשהמכסה מתקרבת לסוף, המערכת עוברת למודל קטן יותר, לספק אחר, או לתשובה שמסבירה למשתמש שהשירות חוזר מאוחר יותר. זה פחות נוצץ מדמו שעובד פעם אחת, אבל זה ההבדל בין “הצלחתי להריץ” לבין “אפשר לתת למישהו אחר להשתמש בזה”.

ארכיטקטורת pipeline חינמי Kaggle אימון זמני checkpoints HF Hub adapter פרטי model card HF Space UI ו-router CPU חינמי Provider API Groq / Cerebras / OpenRouter יומן שימוש ו-fallback סוגרים את הלולאה

מסגרת החלטה: איפה מריצים כל רכיב ב-stack?

אם הרכיב צריך...שימו אותו ב...אל תשימו אותו ב...
GPU זמני לאימון או ניסויKaggle או ColabHF Space CPU
אחסון יציב של adapter או metadataHugging Face Hubדיסק ephemeral של notebook
UI קל, טופס, תצוגה, routingHF Spaces CPU Basicnotebook פתוח בדפדפן
תשובת מודל בזמן אמתGroq, Cerebras, OpenRouter או Ollamaטעינת 7B לתוך CPU Space

שימו לב שהחלקים לא חייבים להיות מושלמים כדי להיות שימושיים. Capstone טוב לא מוכיח שבניתם SaaS. הוא מוכיח שאתם יודעים להעביר רעיון ממחברת ניסוי למשהו שאדם אחר יכול לפתוח בדפדפן, לשלוח בקשה, לקבל תשובה, ולהבין מה קורה כשהחינם נגמר. זו יכולת ששווה הרבה יותר מעוד screenshot של notebook עם output ירוק.

כדי להפוך את הארכיטקטורה לישימה, כתבו ליד כל חץ גם את סוג הנתונים שעובר בו. מ-Kaggle ל-Hub עובר adapter, לא raw dataset. מה-Hub ל-Space עוברת הפניה ל-repo או configuration, לא token. מה-Space ל-provider עוברת בקשת inference, לא קבצי אימון. מה-provider חזרה ל-Space עוברת תשובה, headers וסטטוס. ההפרדה הזאת מונעת בלבול מסוכן: אם raw dataset עובר ל-Hub במקרה, ייתכן שחשפתם מידע; אם token עובר לקוד, ייתכן שחשפתם חשבון; אם adapter לא מתועד, ייתכן שאי אפשר לטעון אותו שוב.

בפרויקטים קטנים כדאי להוסיף גם “גבול כשל” לכל רכיב. גבול כשל הוא תשובה לשאלה: מה הדבר הכי צפוי שישבור את הרכיב הזה? ב-Kaggle זה session שנגמר או GPU שלא זמין. ב-Hub זה הרשאות או קובץ חסר. ב-Space זה secret חסר, sleep, או dependency שלא נבנה. אצל provider זה 429, מודל שהוחלף, או שינוי ב-quota. כשאתם יודעים מראש מה ישבור כל חלק, אתם יכולים לכתוב בדיקות קטנות במקום לחכות לכשל מלא. זה בדיוק המעבר מפרויקט “עובד אצלי” לפרויקט שאפשר לתחזק.

עוד כלל שימושי: כל שכבה צריכה להיות ניתנת לבדיקה בנפרד. Kaggle run טוב מסתיים גם בלי Space. Hub repo טוב ניתן לפתיחה ולקריאה גם בלי provider. Space טוב יכול להציג הודעת secret חסר בלי לקרוס. Provider wrapper טוב יכול לקבל prompt מזויף ולהחזיר fallback בלי UI. אם אתם צריכים להפעיל את כל המערכת כדי לבדוק שינוי קטן, הפכתם את ה-capstone לקשה מדי. פצלו את הבדיקות עכשיו, כשהפרויקט עדיין קטן.

בינוני14 דקותחינםניתוח

בחירת הפרויקט: מתי צריך LoRA ומתי prompt מספיק

לפני שנוגעים ב-QLoRA, צריך לשאול שאלה לא נוחה: האם בכלל צריך fine-tune? הרבה פרויקטים של vibe coders מתחילים ב”נאמן מודל על הדאטה שלי” אבל בפועל הם צריכים prompt טוב, few-shot examples, או RAG קטן. LoRA טוב כשאתם רוצים ללמד את המודל סגנון קבוע, פורמט פלט, מיפוי קטגוריות, או התנהגות שחוזרת על עצמה בתוך דומיין צר. הוא פחות טוב כשאתם רוצים ידע עדכני, פתרון reasoning חדש, או זיכרון של מסמכים משתנים. אם אתם מאמנים adapter כדי לעקוף כתיבת prompt מסודר, אתם מוסיפים מורכבות בלי לקבל יציבות.

הפרויקט המייצג שלנו בפרק הוא “מסווג פניות לקוחות בעברית”. הוא מקבל טקסט חופשי, מחזיר קטגוריה, דחיפות, וטון תשובה ראשוני. זה מתאים ל-LoRA כי אנחנו לא מלמדים את המודל עובדות חדשות על העולם. אנחנו מלמדים אותו התנהגות עסקית קטנה: איך נראית פנייה דחופה, איך מחזירים JSON קבוע, ואיך מדברים בעברית שירותית בלי להיות רובוטיים. אפשר לעשות את זה גם עם prompt, ולכן המדד שלנו פשוט: אם prompt עם 20 דוגמאות נותן תוצאה מספיק יציבה, לא נאמן. אם הפורמט נשבר שוב ושוב, או שהטון לא מתייצב, LoRA נהיה מועמד.

4 דקות

עשו עכשיו: כתבו הצדקת LoRA במשפט אחד

השלימו: “הפרויקט שלי צריך LoRA כי ________ חוזר בכל בקשה ולא יציב מספיק עם prompt בלבד.” אם אין לכם המשך טוב למשפט, התחילו בלי fine-tune.

Dataset טוב לפרויקט כזה לא חייב להיות גדול. לפרק הזה מספיק לתכנן 80-200 דוגמאות איכותיות, גם אם לא תריצו אימון מלא בזמן הקריאה. כל דוגמה צריכה לכלול input ו-output צפוי. אל תאספו מסמכים גולמיים ותקוו שהאימון יבין לבד. כתבו את ההתנהגות שאתם רוצים לראות. לדוגמה, input הוא הודעת לקוח, output הוא JSON עם category, urgency, recommended_action ו-draft_reply. אם אתם לא יכולים לכתוב עשר דוגמאות ידנית, אתם עדיין לא יודעים מה אתם מבקשים מהמודל ללמוד.

מסגרת החלטה: LoRA או prompt?

מצבבחירהלמה
צריך פורמט פלט קבוע שנשבר גם אחרי prompt טובLoRA קטןההתנהגות חוזרת וניתנת לדוגמאות.
צריך ידע עדכני על מוצר, מחירים או נהליםRAG או prompt עם contextfine-tune לא מתעדכן לבד.
צריך reasoning כללי, קוד או פתרון בעיות פתוחותמודל חזק דרך APILoRA קטן עלול להחליש generality.
הדאטה כולל לקוחות אמיתיים או מידע רגישprivate adapter או localלא מעלים dataset ציבורי ולא מכניסים secrets לקוד.

בישראל יש עוד שיקול קטן אך חשוב: עברית עסקית. מודלים חזקים יודעים עברית, אבל לעיתים הם מחליפים בין לשון זכר/נקבה, נשמעים מתורגמים, או מחזירים ניסוח אמריקאי מדי לשירות לקוחות מקומי. אם המוצר שלכם מיועד לעסק קטן בארץ, דוגמאות מקומיות עוזרות: “חשבונית מס”, “זיכוי”, “משלוח בדואר ישראל”, “איסוף עצמי”, “מע״מ”, “שירות לקוחות בוואטסאפ”. אל תכניסו מספרי תעודת זהות, כתובות, טלפונים או הודעות לקוח אמיתיות בלי ניקוי. Capstone טוב נשאר חינמי גם מבחינת סיכון, לא רק מבחינת כסף.

טעות נפוצה: לאמן כי זה נשמע מקצועי

הפיתוי ברור: fine-tune נשמע כמו הדבר האמיתי, prompt נשמע כמו קיצור דרך. בפועל, fine-tune מוסיף קבצים, GPU-hours, גרסאות, uploads ו-debug. אם הבעיה נפתרת עם prompt ו-20 דוגמאות, בחרו prompt. שמרו LoRA למקום שבו יש התנהגות חוזרת, מדידה, וקשה לייצב אותה בדרך פשוטה יותר.

אם החלטתם ש-LoRA מוצדק, השלב הבא הוא לנסח “חוזה פלט”. חוזה פלט הוא תיאור קצר של מה המודל חייב להחזיר בכל פעם. לדוגמה: JSON תקין עם ארבעה שדות, קטגוריה מתוך רשימה סגורה, urgency מתוך low/medium/high, ותשובה בעברית באורך עד 80 מילים. בלי חוזה כזה, אי אפשר לדעת אם האימון עזר. המודל יכול להישמע נחמד יותר ועדיין להיות פחות שימושי כי הוא שובר schema. בפרויקט capstone, schema יציב חשוב יותר מטון מושלם.

הדאטה צריך להיבנות מתוך החוזה הזה. אל תתחילו מאוסף טקסטים ואז תנסו להבין מה ללמוד מהם. התחילו מהפלט שאתם רוצים, ואז כתבו דוגמאות שמכסות את המצבים. אם יש שלוש קטגוריות, ודאו שיש דוגמאות לכל קטגוריה. אם יש דחיפות, ודאו שיש דוגמאות גבול: לקוח כועס שלא באמת דחוף, הודעה קצרה מאוד שכן דחופה, בקשה טכנית שנשמעת מפחידה אבל אפשר לטפל בה מחר. דוגמאות גבול הן המקום שבו adapter לומד את ההבדל בין “טקסט דומה” לבין “החלטה נכונה”.

כדאי גם להחזיק eval set קטן שלא נכנס לאימון. חמישה עשר עד שלושים פריטים מספיקים לפרויקט לימודי. כתבו אותם ידנית או נקו אותם בזהירות, ושמרו אותם בקובץ נפרד. אחרי כל ריצה, בדקו שלושה דברים: האם ה-JSON תקין, האם הקטגוריה נכונה, והאם התשובה לא מכניסה מידע שלא היה בקלט. אם האימון משפר רק את הטון אבל פוגע בקטגוריה, הוא לא שיפור. אם הוא מחזיר category נכון אבל כותב תשובה ארוכה מדי לשימוש בוואטסאפ, גם זה כשל מוצרי.

כדי לא להיתקע בבחירת פרויקט, השתמשו בכלל הזה: הפרויקט צריך להיות קטן מספיק כדי למדוד אותו בעיניים, אבל אמיתי מספיק כדי לחשוף תקלות. “עוזר אישי לכל העסק” גדול מדי. “מסווג פניות לקוחות לשלוש קטגוריות ומחזיר draft reply” מתאים. “מודל שמחליף מוקד שירות” גדול מדי. “בודק אם פנייה דורשת טיפול מיידי או יכולה לחכות עד מחר” מתאים. המטרה היא pipeline, לא מחקר מודלים. אתם רוצים להוכיח שזרימת training-storage-serving-monitoring עובדת.

מתקדם18 דקותחינםהקמה

הכנת Kaggle run: dataset, secrets ו-checkpoints לפני אימון

האימון עצמו הוא החלק הכי פחות סלחני ב-pipeline. API call שנכשל אפשר לנסות שוב. Space שנרדם אפשר להעיר. אבל notebook שאיבד שמונה שעות אימון בלי checkpoint משאיר אתכם עם כלום. לכן אנחנו מתכננים את Kaggle run כמו פעולה מבצעית קצרה: מה נכנס, איפה נשמר, כל כמה זמן שומרים, איך חוזרים אחרי restart, ומה נחשב הצלחה. אל תפתחו notebook ותתחילו להדביק תאים לפני שיש לכם runbook.

ה-runbook צריך לכלול חמישה פרטים: שם המודל הבסיסי, גודל הדאטה, יעד GPU-hours, נתיב checkpoints, ופקודת resume. אם אתם משתמשים ב-Kaggle T4, זכרו שהמכסה השבועית וה-session cap הם משאבים מתכלים. גם אם המחקר הקורסי מציין 30 GPU-hours לשבוע ו-session סביב 9 שעות, המספר שמעניין אתכם לפני ריצה אמיתית הוא מה שמופיע כרגע בחשבון שלכם. כתבו “בדקתי quota בתאריך ___” במסמך. זה נשמע בירוקרטי, אבל זה מונע ריצה שתמות באמצע כי נשארו לכם רק שעתיים.

5 דקות

עשו עכשיו: פתחו training-runbook.md

כתבו בו חמש שורות: base_model, dataset_rows, target_gpu_hours, checkpoint_every, resume_from. אל תמשיכו לפני שיש לכל שורה ערך ראשוני.

Secrets הם חלק מההכנה, לא תיקון מאוחר. אם notebook צריך להעלות adapter ל-Hugging Face Hub, הוא צריך HF_TOKEN. אל תכתבו את הטוקן בתוך תא. השתמשו ב-Kaggle Secrets או בתהליך התחברות שמבקש את הערך בזמן הריצה. אם אתם משתפים notebook, תא שמכיל token הוא דליפה. גם אם הטוקן הוא “רק לחשבון חינמי”, הוא יכול למחוק repo, להעלות קבצים או לחשוף private adapter. בפרק הזה אנחנו מתייחסים לכל token כאילו הוא production secret.

טעות נפוצה: להריץ אימון בלי checkpoint אמיתי

קל להוסיף בסוף הקוד שורה ששומרת adapter. זה לא checkpointing. checkpointing אומר שאתם יכולים להרוג את ה-session באמצע, לפתוח אותו מחדש, ולהמשיך מנקודה קרובה. אם לא בדקתם resume על ריצה קטנה, אין לכם checkpointing אלא תקווה.

Dataset capstone צריך להיות גם קטן וגם נקי. השתמשו ב-JSONL או CSV עם עמודות ברורות. לדוגמה: input_text, category, urgency, draft_reply. אם אתם משתמשים בדוגמאות סינתטיות, סמנו אותן כך. אם אתם משתמשים בדוגמאות אמיתיות, הסירו שמות, טלפונים, כתובות, מזהים, פרטי הזמנה וכל דבר שמאפשר לזהות אדם. אם אתם לא בטוחים אם דוגמה רגישה, אל תעלו אותה. קורס על free compute לא שווה דליפת מידע.

project/
  data/
    train.jsonl
    eval.jsonl
  notebooks/
    train_qlora_kaggle.ipynb
  outputs/
    checkpoints/
    final_adapter/
  training-runbook.md

הקפידו להפריד בין checkpoint לבין final_adapter. checkpoint הוא מצב ביניים שמאפשר resume. final_adapter הוא artifact נקי לשיגור. בתחילת הדרך שניהם יכולים להיות באותה תיקיית outputs, אבל תנו להם שמות שונים. כשיש תקלה, השם חשוב. “output-final-v2-real-last” הוא לא אסטרטגיה. “checkpoint-step-300” ו-“adapter-v0.1” הם שמות שאפשר לעבוד איתם.

תרגיל 1: Capstone training runbook

זמן: 35 דקות. תוצר: training-runbook.md שמאפשר לכם או למישהו אחר להריץ את האימון בלי לנחש.

  1. בחרו פרויקט קטן אחד: למשל מסווג פניות, מחולל תגובות שירות, או normalizer לטקסט עברי.
  2. כתבו את מטרת האימון במשפט אחד ואת מדד ההצלחה: דיוק על 20 דוגמאות eval, שמירה על JSON תקין, או טון תגובה עקבי.
  3. הגדירו dataset schema עם 3-5 שדות בלבד. כתבו שלוש דוגמאות מלאות, גם אם הן סינתטיות.
  4. כתבו target_gpu_hours. לפרויקט לימודי בחרו יעד שמרני: עד 2 שעות לניסוי קטן, עד 10 שעות רק לריצה מורחבת.
  5. כתבו checkpoint_every: למשל כל 100 steps או כל 20 דקות. בחרו גם save_total_limit כדי לא למלא storage.
  6. כתבו resume procedure: מאיפה נטען checkpoint, מה עושים אם הקובץ חסר, ואיך יודעים שהריצה באמת המשיכה.
  7. הוסיפו privacy note: האם הדאטה סינתטי, מנוקה או רגיש. אם הוא רגיש, adapter נשאר private.

פלט צפוי: קובץ markdown עם כותרות ברורות: Objective, Dataset, Budget, Checkpoints, Resume, Privacy, Done Criteria. אם מישהו אחר יכול לקרוא אותו ולהבין מה להריץ, הצלחתם.

אחרי התרגיל, הוסיפו ל-runbook סעיף בשם “Stop conditions”. סעיף זה קובע מתי עוצרים ולא ממשיכים לשרוף GPU-hours. דוגמאות טובות: loss לא זז אחרי מספר צעדים קטן, eval JSON נשבר ביותר מ-20% מהמקרים, memory usage קרוב מדי לתקרה, או checkpoint לא נטען מחדש. Stop condition הוא מנגנון הגנה מפני sunk cost. כשאתם עובדים בחינם, קל להגיד “ננסה עוד קצת”. אבל quota חינמי הוא עדיין משאב. אם הריצה לא עומדת בתנאי בסיס, עצרו, תקנו את הדאטה או הקונפיג, ורק אז חזרו.

סעיף נוסף שכדאי להוסיף הוא “Recovery owner”. גם אם אתם עובדים לבד, כתבו מי אחראי להחזיר את המערכת אחרי כשל: אתם, חבר צוות, או “אף אחד כי זה demo”. ההחלטה הזאת משנה את רמת הפירוט. אם רק אתם מפעילים את הפרויקט, מספיקים סימנים קצרים. אם אדם אחר אמור להשתמש בו, ה-runbook צריך לכלול פקודות, נתיבים, צילומי מסך והודעות שגיאה צפויות. אל תגלו בזמן תקלה שהמסמך כתוב רק למי שכבר מכיר את כל הסיפור.

לבסוף, כתבו “cost boundary”: מה יגרום לכם להפסיק להתעקש על $0. למשל, אם הפרויקט מקבל יותר מ-200 בקשות ביום, אם זמני fallback פוגעים בעבודה, או אם fine-tune חוזר דורש יותר מדי התעסקות. גבול כזה לא אומר שנכשלתם. הוא אומר שאתם יודעים לזהות מתי פרויקט עבר משלב למידה לשלב שימוש אמיתי. לפעמים תשלום של כמה דולרים בחודש הוא ההחלטה הכי זולה בזמן.

מתקדם25 דקותחינםתרגול

אימון QLoRA קצר: base ב-4-bit, adapter קטן, תוצאה שמורה

QLoRA הוא הטריק שמאפשר לאמן התאמה קטנה על מודל גדול יחסית בלי להחזיק את כל המשקולות בדיוק מלא. המודל הבסיסי נטען ב-4-bit, בדרך כלל עם bitsandbytes ו-NF4, ואז מאמנים רק LoRA adapter. זה לא קסם: אתם עדיין צריכים GPU, זיכרון, גרסאות תואמות ונתונים נקיים. אבל זה הופך ריצה לימודית על T4 למעשית. לפי תיעוד PEFT, הדפוס המקובל כולל BitsAndBytesConfig עם load_in_4bit, הכנה דרך prepare_model_for_kbit_training, ואז LoraConfig. בפרק הזה אנחנו לא מתחייבים לגרסת ספרייה ספציפית, כי versions משתנות. אנחנו כן מתחייבים למבנה: load, prepare, attach adapter, train, save, push.

4 דקות

עשו עכשיו: בדקו את ארבעת המרכיבים בקונפיג

בשלד שלכם חייבים להופיע: load_in_4bit, nf4 או quant type שקול, LoraConfig, ונתיב save_pretrained ל-adapter. אם אחד חסר, השלד עוד לא מוכן.

הקוד הבא הוא skeleton, לא הבטחה שירוץ בכל סביבת Kaggle בלי התאמות. המטרה היא להראות איפה כל אחריות יושבת. ב-notebook אמיתי תצטרכו להתאים model_id, tokenizer, dataset formatting, גרסאות transformers/peft/accelerate/bitsandbytes, ופרמטרים כמו batch size. שימו לב לשתי החלטות: ראשית, אנחנו שומרים adapter בלבד. שנית, אנחנו מפרידים output_dir של checkpoints מ-final_adapter. זה מאפשר resume בזמן אימון ו-export נקי אחריו.

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
import torch

model_id = "mistralai/Mistral-7B-v0.1"  # החליפו למודל שמותר לכם להשתמש בו
output_dir = "/kaggle/working/checkpoints/service-router-v01"
adapter_dir = "/kaggle/working/final_adapter/service-router-v01"

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
    bnb_4bit_compute_dtype=torch.bfloat16,
)

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",
)
model = prepare_model_for_kbit_training(model)

lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules="all-linear",
    task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)

training_args = TrainingArguments(
    output_dir=output_dir,
    save_steps=100,
    save_total_limit=3,
    logging_steps=20,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    num_train_epochs=1,
    learning_rate=2e-4,
    fp16=True,
    report_to="none",
)

# trainer = SFTTrainer(...)
# trainer.train(resume_from_checkpoint=True if checkpoint_exists else None)
# model.save_pretrained(adapter_dir)
# tokenizer.save_pretrained(adapter_dir)

אל תעתיקו skeleton כזה בלי להבין מה חסר. חסר בו trainer, formatting function, dataset loader ו-evaluation loop. בכוונה. בפרק capstone אנחנו מתמקדים במבנה pipeline, לא בללמד מחדש את כל עולם fine-tuning. אם כבר יש לכם notebook מוכן, התאימו אליו את הגבולות. אם אין לכם, התחילו בריצה זעירה: 20 דוגמאות train, 5 eval, epoch אחד, max_steps נמוך. המטרה הראשונה היא לא איכות. המטרה הראשונה היא להוכיח שהמערכת יודעת להתחיל, לשמור, להמשיך, ולייצא.

מחזור אימון QLoRA בטוח Dataset JSONL נקי 4-bit base NF4 + prepare LoRA train adapter בלבד Checkpoint resume נבדק Final adapter artifact לשיגור

אחרי הריצה הקטנה, בדקו שלושה דברים לפני שאתם מרחיבים: האם loss יורד בצורה סבירה, האם eval examples שומרים פורמט, והאם adapter נטען מחדש בלי notebook state. הדבר השלישי חשוב במיוחד. הרבה ריצות נראות מצליחות כי האובייקט עדיין בזיכרון. ואז מעלים files ל-Hub, טוענים בסביבה חדשה, ומגלים שחסר tokenizer, config או base model reference. בדיקת טעינה מחדש היא חלק מהאימון, לא שלב אחרי deployment.

טעות נפוצה: לייצא adapter בלי לדעת איך טוענים אותו

adapter הוא לא מודל מלא. אם אתם מעלים רק adapter, סביבת inference צריכה לדעת על איזה base model הוא יושב ואיך מחברים אותו. כתבו את base_model ב-model card, בשם הקבצים, וב-runbook. אחרת אדם אחר, כולל אתם בעוד שבועיים, לא יידע איך להשתמש בתוצר.

כדי לשמור על $0, אל תריצו sweep גדול. אל תנסו חמישה מודלים, ארבעה learning rates ושלוש גרסאות dataset באותו שבוע אם אין לכם מכסה. בפרויקט אמיתי בתשלום זה אולי סביר. במסלול חינמי זה מתכון לשרוף quota בלי תוצר. התחילו מגרסה אחת, מדד אחד, וקו בסיס prompt-only. אם ה-LoRA לא משפר משהו מדיד לעומת prompt-only, אל תמשיכו רק כי כבר התחלתם.

מדד baseline פשוט יכול להיות טבלה עם 20 דוגמאות eval. עמודה אחת היא תשובת prompt-only, עמודה שנייה היא תשובת adapter, ועמודה שלישית היא החלטה ידנית: prompt ניצח, adapter ניצח, תיקו, או שניהם נכשלו. אל תסתפקו בתחושה שה-adapter “נשמע יותר טוב”. בפרויקטים קטנים, מדידה ידנית מהירה יותר מכל framework. אם adapter מנצח ב-12 מתוך 20 דוגמאות, לא ברור שיש לכם שיפור. אם הוא מנצח ב-17 מתוך 20 ושומר JSON תקין, יש בסיס להמשיך.

שימו לב במיוחד ל-overfitting סגנוני. Adapter קטן על dataset קטן יכול ללמוד ביטויים שחוזרים יותר מדי. אם כל תשובה מתחילה באותו משפט, או אם המודל הופך בטוח מדי במקרים גבוליים, זה סימן שהדאטה צר מדי. הוסיפו דוגמאות של “לא בטוח”, “צריך להעביר לאדם”, ו-“אין מספיק מידע”. בפרויקט שירות לקוחות, יכולת להודות בחוסר ודאות חשובה יותר מתשובה חלקה. מערכת $0 שאומרת בביטחון דבר שגוי תעלה לכם ביוקר גם אם השרת לא עולה כסף.

עוד בדיקה קטנה: טענו את adapter בסביבה חדשה ככל האפשר. אם יש לכם רק Kaggle, פתחו session חדש. אם יש Colab זמין, טענו שם. אם אין GPU, לפחות בדקו שהקבצים קיימים וש-config קריא. מטרת הבדיקה אינה להריץ inference מלא בכל מקום, אלא לוודא שאין dependency נסתרת מה-notebook המקורי. פרויקטים רבים נשברים כי הם מסתמכים על משתנה בזיכרון, קובץ זמני או package שהותקן ידנית ולא נרשם בשום מקום.

בינוני12 דקותחינםהקמה

ייצוא ל-Hugging Face Hub: adapter פרטי כגבול בין training ל-serving

אחרי שיש adapter, אתם צריכים להוציא אותו מה-notebook. זו נקודת מעבר קריטית. כל עוד הקובץ יושב ב-/kaggle/working, הוא חלק מניסוי. כשהוא נמצא ב-Hugging Face Hub, עם שם repo, הרשאות, model card וגרסה, הוא artifact. השינוי הזה קטן טכנית אבל גדול מחשבתית: serving לא תלוי יותר ב-notebook פתוח, והאימון הבא לא מוחק את התוצר הקודם.

3 דקות

עשו עכשיו: בחרו שם repo בטוח

בחרו שם כמו service-router-hebrew-lora-v01. אל תכניסו שם לקוח, שם חברה שלא אמור להתפרסם, או רמז לדאטה רגיש. כתבו לידו: private או public.

ברירת המחדל שלי בפרויקטי למידה היא private. לא כי כל adapter הוא סודי, אלא כי קל מדי לטעות. אם הדאטה סינתטי לגמרי ומיועד לשיתוף, אפשר להפוך ציבורי. אם יש אפילו ספק שהדוגמאות משחזרות ניסוח של לקוח, פנייה אמיתית, או תהליך פנימי, שמרו private. Hugging Face מאפשר ליצור repo פרטי, והעלות במסלול הבסיסי תלויה במדיניות החשבון שלכם ובסוג השימוש; בפרק הזה אנחנו לא בונים מוצר מסחרי על assumption של privacy חינמית לנצח. אנחנו בונים הרגל: קודם פרטיות, אחר כך פתיחה אם יש סיבה.

from huggingface_hub import login

# התחברות בטוחה: עדיף להשתמש ב-secret שמגיע מסביבת הריצה
# login(token=os.environ["HF_TOKEN"])

adapter_repo = "your-user/service-router-hebrew-lora-v01"
model.push_to_hub(adapter_repo, private=True)
tokenizer.push_to_hub(adapter_repo, private=True)

model card מינימלי צריך לענות על ארבע שאלות: מה base model, מה המשימה, מה הדאטה ברמת תיאור, ומה מגבלות השימוש. אל תכתבו “trained on customer support data” אם זה חושף יותר מדי. כתבו “trained on synthetic Hebrew customer-support style examples for classification and draft response formatting”. אם השתמשתם בדאטה אמיתי ומנוקה, כתבו שהוא מנוקה ושאין לכלול בו PII. כן, זה נשמע כמו עבודה צדדית. אבל model card הוא מה שמונע מה-adapter להפוך לקובץ מסתורי שאף אחד לא נוגע בו.

מסגרת החלטה: private או public?

אם...בחרו...הערה
הדאטה כולל לקוחות, צוות, עסק אמיתי או מידע פנימיPrivateגם אחרי ניקוי, ברירת המחדל היא זהירות.
הדאטה סינתטי ומיועד ללימודPublic אפשרירק אם model card מסביר זאת.
אתם רוצים שאחרים ישכפלו את ה-SpacePublic או template נפרדלעולם לא עם secrets.
לא בטוחיםPrivateאפשר לפתוח אחר כך; קשה להחזיר מידע שכבר פורסם.

כאן גם נכנס versioning פשוט. אל תדרסו adapter קיים בלי סיבה. שמרו v0.1, v0.2, או tag לפי תאריך. אם Space עובד מול adapter מסוים, רשמו את ה-revision. כשמשהו נשבר, תוכלו לשאול: האם נשבר הקוד ב-Space, הספק החינמי, או adapter חדש? בלי גרסאות, כל כשל נראה אותו דבר.

model card טוב לפרויקט כזה לא צריך להיות ארוך, אבל הוא כן צריך להיות מדויק. כתבו “Base model”, “Training data”, “Intended use”, “Not intended for”, “Known limitations”, ו-“Serving notes”. תחת Training data אל תכתבו דוגמאות רגישות. תארו את הסוג: “synthetic Hebrew customer-support messages for routing and draft replies”. תחת Not intended for כתבו מה המערכת לא עושה: לא מחליפה נציג אנושי, לא נותנת ייעוץ משפטי, לא מבטיחה טיפול בתלונות מורכבות. ההגבלות האלה לא מחלישות את הפרויקט; הן הופכות אותו בטוח יותר.

אם אתם מעלים דרך hf CLI במקום push_to_hub, תעדו את הפקודה בדיוק. לדוגמה, “hf upload your-user/service-router-hebrew-lora-v01 ./final_adapter --repo-type model”. אם אתם משתמשים ב-git, ציינו האם צריך git-xet לקבצים גדולים. אל תבנו runbook שמניח שכולם יודעים איך Hugging Face Hub עובד. בפרויקט capstone, אפילו פרטים קטנים כמו repo type ו-private flag משפיעים על האם אדם אחר יוכל לשחזר את העבודה.

לפני שאתם מחברים את ה-Space ל-adapter, עשו “artifact review”. פתחו את repo ב-Hub ובדקו שיש adapter_config, קבצי weights או safetensors, tokenizer files אם נדרשים, README, ושם base model. אם אחד חסר, אל תגידו “נתקן ב-serving”. serving צריך לקבל artifact מוכן. הפרדה נקייה בין training ל-serving היא אחת המיומנויות המרכזיות בפרק הזה.

מתקדם22 דקותחינםהקמה

Serving בלי GPU: Gradio Space שמדבר עם ספק inference חינמי

עכשיו מגיע החלק שבו הרבה פרויקטים חינמיים נשברים: מנסים להפוך את HF Space עצמו לשרת מודל. בשכבת CPU Basic החינמית יש CPU ו-RAM שמספיקים ל-UI, routing, validation וקריאות HTTP. זה לא מקום נכון לטעון מודל 7B ולצפות לתשובות מהירות. לכן ה-Space שלנו הוא thin UI: הוא מקבל input, בודק אותו, קורא ל-provider API, מציג תשובה, ומנהל fallback. המודל הכבד, אם יש כזה, נמצא אצל הספק או אצל fallback מקומי שלכם, לא בתוך ה-Space.

3 דקות

עשו עכשיו: כתבו רשימת secrets בלי ערכים

צרו רשימה בשם space-secrets.md וכתבו רק את השמות: GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, DEFAULT_PROVIDER. אל תכתבו את הערכים בשום קובץ repo.

ב-HF Spaces מוסיפים secrets דרך Settings. לפי תיעוד Spaces, secrets נחשפים לאפליקציית Python כ-environment variables, ולכן ב-app.py קוראים להם עם os.getenv. משתנים לא רגישים, כמו DEFAULT_PROVIDER או APP_MODE, יכולים להיות variables. מפתחות API הם secrets. ההבדל חשוב: variable יכול להיות ציבורי או להשתכפל עם Space. secret לא אמור להיות קריא אחרי ההזנה. אם אתם לא בטוחים, בחרו secret.

import os
import gradio as gr
from openai import OpenAI

PROVIDERS = {
    "groq": {
        "base_url": "https://api.groq.com/openai/v1",
        "api_key_env": "GROQ_API_KEY",
        "model": "llama-3.1-8b-instant",
    },
    "cerebras": {
        "base_url": "https://api.cerebras.ai/v1",
        "api_key_env": "CEREBRAS_API_KEY",
        "model": "gpt-oss-120b",
    },
}

def call_provider(prompt, provider_name="groq"):
    config = PROVIDERS[provider_name]
    api_key = os.getenv(config["api_key_env"])
    if not api_key:
        return "חסר secret לספק הזה. בדקו את Settings של ה-Space."

    client = OpenAI(base_url=config["base_url"], api_key=api_key)
    response = client.chat.completions.create(
        model=config["model"],
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2,
        max_tokens=500,
    )
    return response.choices[0].message.content

def ui(prompt, provider):
    return call_provider(prompt, provider)

with gr.Blocks(title="Zero Dollar Router") as demo:
    gr.Markdown("# Zero Dollar Router")
    prompt = gr.Textbox(label="טקסט לבדיקה", lines=6)
    provider = gr.Dropdown(["groq", "cerebras"], value="groq", label="Provider")
    output = gr.Textbox(label="תשובה")
    gr.Button("שליחה").click(ui, [prompt, provider], output)

demo.launch()

הקוד הזה עדיין תמים: אין בו monitoring, אין rotation, אין retry. אבל הוא מציב גבול נכון. ה-Space לא יודע שום secret בתוך הקוד, לא מנסה לטעון מודל מקומי, ולא מניח שספק אחד תמיד עובד. בשלב הראשון בדקו שה-Space עולה, שהשדה מופיע, שקריאה אחת חוזרת, וששגיאה על secret חסר מוצגת בצורה ברורה. זה smoke test, לא מוצר.

טעות נפוצה: לבנות serving רציף על ZeroGPU

ZeroGPU מצוין לדמוים מסוימים, אבל free quota ותורים הופכים אותו לבחירה לא טובה ל-serving רציף של אפליקציה קטנה. אל תבנו UI שמחכה ל-GPU slot כדי לענות על הודעת טקסט פשוטה. שמרו את ה-Space כ-CPU router וקראו לספק inference חיצוני.

אם האפליקציה שלכם משתמשת ב-adapter שייצאתם, יש שתי אפשרויות. האפשרות הפשוטה למסלול $0 היא להשתמש ב-provider שמריץ base model חזק ולשלב את ההתנהגות דרך prompt, כלומר adapter נשאר artifact לימודי או משמש בהמשך בסביבה אחרת. האפשרות המתקדמת היא serving שממש טוען adapter מעל base model בסביבה שתומכת בכך. לא כל ספק חינמי יתמוך ב-adapter שלכם, ולכן אל תבטיחו לעצמכם “fine-tune and serve” בלי לבדוק את מסלול ה-serving. בפרק הזה ה-capstone בנוי כך שגם אם adapter הוא תוצר אימון, ה-Space עדיין יכול לעבוד עם API חינמי רגיל כגרסת שיגור.

תרגיל 2: הרכיבו תיקיית Space לשיגור

זמן: 45 דקות. תוצר: תיקיית space/ שאפשר להעלות ל-HF Space או לשמור כ-template.

  1. צרו תיקייה בשם space/ עם app.py, requirements.txt ו-README.md.
  2. ב-app.py כתבו Gradio UI מינימלי שמקבל טקסט, provider, ומחזיר תשובה או הודעת שגיאה ידידותית.
  3. ב-requirements.txt הוסיפו gradio, openai, requests או ספריות אחרות שאתם באמת משתמשים בהן.
  4. ב-README כתבו metadata של Space: title, sdk: gradio, app_file: app.py.
  5. כתבו secrets checklist: GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY לפי הספקים שתומכים אצלכם.
  6. הוסיפו בדיקת secret חסר: אם אין API key, האפליקציה מחזירה הודעה ברורה ולא stack trace.
  7. הריצו מקומית או ב-Space ובדקו prompt קצר אחד. שמרו screenshot של הפלט.

פלט צפוי: UI פשוט עם שדה טקסט, בחירת provider ותשובה. אם ספק אחד חסר או מוגבל, המשתמש רואה הסבר ולא שגיאת Python.

אחרי שיש תיקיית Space, הריצו dry run מקומי אם אפשר. גם אם ה-Space עצמו הוא היעד, הרצה מקומית מגלה בעיות dependency מהר יותר. פתחו terminal, התקינו requirements בסביבה נקייה, והריצו python app.py. אם הקוד תלוי ב-secret, בדקו שני מצבים: עם משתנה סביבה ובלי משתנה סביבה. המצב השני חשוב לא פחות מהראשון. משתמשים לא צריכים לראות traceback רק כי שכחתם secret ב-Settings.

ב-README של ה-Space כתבו גם “Operational notes”. זה לא חייב להיות גלוי אם ה-Space פרטי, אבל זה עוזר לכם. כללו שם: איזה provider הוא primary, איזה provider fallback, איזה מודל נקרא, מה מגבלת הקלט המומלצת, ומה הודעת הכשל. אם ה-Space ציבורי, היזהרו לא לפרסם שמות secrets או מבנה פנימי רגיש. אפשר לכתוב “Set GROQ_API_KEY as a Space Secret” בלי לחשוף ערך או חשבון.

לגבי sleep של Spaces חינמיים: התייחסו אליו כחלק מהחוויה. אם Space מתעורר לאט אחרי חוסר שימוש, זה לא בהכרח bug. כתבו הודעה במסך או בלוג שמסבירה cold start. אל תנסו לעקוף sleep בעזרת ping אגרסיבי שמפר תנאי שימוש או שורף משאבים. מערכת $0 טובה מכבדת את הפלטפורמות שמאפשרות לה להתקיים.

אם בעתיד תרצו לחבר את ה-adapter ממש ל-serving, עשו זאת כשלב נפרד. תצטרכו לבדוק האם הספק תומך LoRA adapters, האם אפשר לטעון base model תואם, ומה העלות. אל תערבבו את זה עם שיגור ה-UI הראשון. גרסת v0 יכולה להיות prompt/API עם אותה לוגיקת מוצר, וגרסת v1 יכולה להשתמש ב-adapter אם תמצאו מסלול מתאים. הפרדה כזאת מונעת מהפרויקט להיתקע על החלק הכי קשה לפני שיש משתמש אחד.

מתקדם22 דקותחינםכלי

ניטור, rotation ו-graceful degradation: איך לא ליפול על 429

מערכת $0 שלא קוראת headers היא מערכת עיוורת. ספקים חינמיים לא חייבים לכם uptime, וגם המגבלות שלהם משתנות. Groq מתעד rate limits לפי RPM, RPD, TPM ו-TPD, ומציין headers כמו x-ratelimit-remaining-requests ו-retry-after במקרים רלוונטיים. Cerebras מתעד מדידה לפי requests ו-tokens ומחזיר headers דומים. המספרים המדויקים יכולים להשתנות לפי חשבון, מודל ותוכנית. לכן הקוד שלכם לא צריך רק לדעת “יש 1,000 בקשות ביום”. הוא צריך לקרוא מהשרת מה נשאר עכשיו.

4 דקות

עשו עכשיו: בחרו שלושה מצבי fallback

כתבו מה קורה בכל אחד מהמצבים: provider primary על 429, כל הספקים על 429, ו-secret חסר. אם אין תשובה ברורה לכל מצב, המערכת עוד לא מוכנה לשימוש חיצוני.

הנה דפוס בסיסי: כל provider מקבל wrapper שמחזיר גם תשובה וגם metadata. metadata כוללת סטטוס, headers חשובים, זמן תגובה, ומספר tokens אם זמין. אחרי כל בקשה, אתם מוסיפים שורה ל-usage ledger. אם מגיע 429, בודקים retry-after. אם יש retry-after קצר, מחכים או מחזירים “מנסה שוב עוד רגע”. אם daily cap נגמר, מסובבים לספק הבא. אם אין ספק פנוי, מחזירים תשובה degraded: “המערכת הגיעה למכסת השימוש החינמית להיום. אפשר לנסות שוב מאוחר יותר.” זה לא כישלון. זה UX ישר.

Provider rotation על 429 Groq primary latency Cerebras batch tokens OpenRouter catalog fallback Graceful degradation תשובה ידידותית + log + ניסיון מאוחר
import time
from dataclasses import dataclass

@dataclass
class ProviderResult:
    ok: bool
    text: str
    provider: str
    status_code: int
    headers: dict
    fallback_action: str = "none"


def should_rotate(result):
    if result.status_code == 429:
        return True
    remaining = result.headers.get("x-ratelimit-remaining-requests")
    if remaining is not None and int(remaining) < 5:
        return True
    return False


def call_with_rotation(prompt, providers):
    last_result = None
    for provider in providers:
        result = provider.call(prompt)
        write_usage_ledger(result)
        if result.ok and not should_rotate(result):
            return result.text
        last_result = result
        retry_after = result.headers.get("retry-after")
        if retry_after and int(retry_after) <= 10:
            time.sleep(int(retry_after))
            retry_result = provider.call(prompt)
            write_usage_ledger(retry_result)
            if retry_result.ok:
                return retry_result.text
    return "הגענו למכסת השימוש החינמית כרגע. נסו שוב מאוחר יותר או עברו לספק אחר."

מסגרת החלטה: מה עושים כשיש 429?

הסימןפעולהמה אומרים למשתמש
retry-after קצרהמתנה וניסיון חוזר אחד“מנסה שוב בעוד כמה שניות.”
remaining requests נמוךrotation לספק הבא לפני הכשללא צריך להציג כלום אם התשובה מצליחה.
daily cap נגמרמעבר לספק אחר או מודל קטן יותר“עברנו למסלול חסכוני יותר.”
אין ספק זמיןgraceful degradation“המכסה החינמית נגמרה כרגע. נסו שוב מאוחר יותר.”

ה-usage ledger לא חייב להיות מסד נתונים. לפרויקט capstone מספיק CSV או JSONL שנשמר לצד האפליקציה, ואם ה-Space לא שומר דיסק באופן אמין, אפשר לפחות להדפיס logs או לשלוח אותם לקובץ זמני בזמן הדמו. שורה טובה כוללת: timestamp, provider, model, status_code, latency_ms, remaining_requests, remaining_tokens, fallback_action. אם יש לכם רק דבר אחד למדוד, מדדו כמה פעמים עברתם fallback. זה המדד שמספר אם המערכת באמת חיה בתוך free tier או רק מקווה שלא יגיע עומס.

טעות נפוצה: להתייחס ל-429 כ-bug נדיר

ב-free tier, 429 הוא לא bug. הוא חלק מהממשק. אם הקוד שלכם לא מתייחס אליו כנתיב רגיל, המשתמשים יראו stack trace או silence בדיוק ברגע שבו המערכת צריכה להיות הכי ברורה. כתבו את הודעת fallback לפני הקוד, ואז ודאו שהקוד מגיע אליה.

usage ledger טוב עוזר לכם גם להחליט אם הפרויקט עדיין מתאים ל-$0. אם במשך שבוע יש רק 20 בקשות ביום, אין סיבה לשלם. אם יש 600 בקשות ביום, וכל ערב אתם נוגעים בתקרה, אולי הגיע הזמן לשנות תכנון. אל תנהלו את זה בתחושה. מספרים פשוטים מספיקים: total_requests, fallback_rate, average_latency, failed_requests, ו-cost_estimate אם הייתם עוברים לתשלום. לפעמים תגלו שהחינם עובד מצוין. לפעמים תגלו שאתם מבזבזים שעה ביום כדי לחסוך 10 דולר בחודש.

הוסיפו גם “degradation levels”. רמה 0: primary provider עובד. רמה 1: עוברים למודל קטן יותר אצל אותו ספק. רמה 2: עוברים לספק fallback עם איכות או latency אחרת. רמה 3: מחזירים תשובה חלקית או מבקשים מהמשתמש לנסות מאוחר יותר. רמה 4: מכבים את הפיצ׳ר זמנית ומציגים הודעה קבועה. כשיש רמות כאלה, הכשל לא מפתיע אתכם. הוא פשוט מזיז את המערכת למצב מוכר.

בפרויקט בעברית, graceful degradation צריך להיות כתוב בעברית טבעית. אל תחזירו “Rate limit exceeded” למשתמש שאינו מפתח. כתבו: “המערכת הגיעה למכסת השימוש החינמית כרגע. אפשר לנסות שוב מאוחר יותר, או לעבור למסלול מקומי אם זה דחוף.” אם המשתמש הוא אתם או צוות טכני, אפשר להוסיף קוד קצר כמו “provider_limit_groq_daily”. אבל המסר הראשון צריך להיות אנושי. שגיאות טובות הן חלק מהמוצר.

תרגיל 3: הריצו failure drill ל-429

זמן: 30 דקות. תוצר: quota_monitor.py או פסאודו-קוד שמוכיח rotation ו-degradation.

  1. צרו שני providers מזויפים: אחד שמחזיר 429 עם retry-after, ואחד שמחזיר תשובה תקינה.
  2. כתבו פונקציה שמנסה את provider הראשון, מתעדת את הכשל, ואז עוברת לשני.
  3. הוסיפו מצב של כל הספקים נכשלים, והחזירו הודעה ידידותית למשתמש.
  4. כתבו usage ledger עם לפחות ארבעה שדות: provider, status_code, fallback_action, timestamp.
  5. הריצו את התרגיל פעמיים: פעם עם fallback מצליח, פעם עם כל הספקים נכשלים.
  6. בדקו שהפלט לא חושף API keys, headers רגישים או stack trace.

פלט צפוי: log שמראה provider primary נכשל, provider fallback מצליח, ובתרחיש שני מתקבלת הודעת degradation. זה rehearsal של 2 בלילה, לא קוד תיאורטי.

בינוני15 דקותחינםניתוח

בדיקת שיגור: rehearsal, smoke test ו-night-failure drill

לפני שאתם אומרים “שיגרתי”, עשו rehearsal. לא דמו מול עצמכם כשכל הטאבים פתוחים וכל secrets כבר בזיכרון. rehearsal אמיתי מתחיל מסביבה קרה: repo נקי, Space rebuild, provider secrets מוגדרים, prompt קצר, prompt ארוך, שגיאת secret חסר, וסימולציית 429. אם אחד מהשלבים דורש “אה נכון, צריך גם להריץ תא כזה”, הוא לא חלק מהשיגור. ה-runbook צריך להכיל אותו.

3 דקות

עשו עכשיו: כתבו הודעת כשל למשתמש

לפני הקוד, כתבו משפט אחד שיופיע כשהמכסה החינמית נגמרת: “המכסה החינמית נגמרה כרגע; נסו שוב מאוחר יותר או עברו למסלול מקומי.” אם המשפט מביך, שפרו אותו עכשיו.

Smoke test בסיסי כולל ארבע בקשות. הראשונה קצרה וצפויה: “סווג את הפנייה הזאת”. השנייה ארוכה יותר ובודקת אם המודל לא קורס על input עם רעש. השלישית גורמת לשגיאה מבוקרת: secret חסר או provider לא קיים. הרביעית מדמה 429. לכל בקשה צריך להיות expected output. לא “תשובה טובה”, אלא “JSON עם category, urgency ו-draft_reply”, או “הודעת fallback בלי stack trace”. אם אתם לא מגדירים פלט צפוי, אתם לא בודקים אלא מסתכלים.

כאן חוזרת מפת ההחלטה מהפרק הראשון. אם הפרויקט שלכם צריך privacy, אולי ה-serving האמיתי יהיה Ollama מקומי ולא Groq. אם הוא צריך latency, Groq הוא primary. אם הוא צריך batch token-heavy, Cerebras יכול להיות fallback חזק. אם הוא צריך קטלוג מודלים מתחלף, OpenRouter מתאים. Capstone טוב לא אומר שיש ספק אחד מנצח. הוא אומר שאתם יודעים להסביר למה בחרתם primary, למה בחרתם fallback, ומה יקרה אם שניהם לא זמינים.

דוגמה מייצגת: שיגור קטן לעסק ישראלי

דמיינו חנות אונליין קטנה שמקבלת פניות בוואטסאפ ומייל. המערכת לא מחליפה שירות לקוחות. היא מסווגת פנייה ל-“משלוח”, “החזרה”, “חשבונית”, או “דחוף”, ומציעה טיוטת תגובה בעברית. Training ב-Kaggle מייצר adapter שמתאים את הפורמט והטון. HF Space מציג UI לצוות. Groq נותן תשובות מהירות. אם Groq מוגבל, Cerebras או OpenRouter נכנסים. אם הכל מוגבל, הצוות רואה הודעת fallback ולא מאבד את הפנייה. זה פרויקט קטן, אבל הוא שלם.

תוכנית השיגור לשבוע הראשון צריכה להיות צנועה. ביום הראשון מריצים רק אתם. ביום השני נותנים לאדם אחד נוסף לשלוח 10 פניות. ביום השלישי מוסיפים תרחישי כשל: provider לא זמין, input ארוך מדי, טקסט ריק, טקסט עם פרטים אישיים. ביום הרביעי בודקים logs. ביום החמישי מתקנים הודעות שגיאה. רק אחרי זה אפשר להגיד שהמערכת מוכנה לשימוש קטן. שיגור הדרגתי כזה מתאים במיוחד ל-free tier, כי הוא לא מפתיע את המכסות.

אל תמדדו רק “האם עבד”. מדדו גם “האם המשתמש הבין מה לעשות”. אם fallback אומר לנסות מאוחר יותר אבל לא מסביר למה, המשתמש יחשוב שהמערכת שבורה. אם התשובה מסווגת פנייה אבל לא מראה ביטחון או קטגוריה, קשה להשתמש בה. אם UI מאפשר input ארוך מאוד ואז provider נופל, עדיף לחתוך מראש ולהסביר את המגבלה. UX קטן סביב מגבלות חינמיות הוא מה שהופך את הפרויקט למקצועי.

בסוף השבוע הראשון, כתבו postmortem גם אם לא הייתה תקלה גדולה. שלוש שאלות מספיקות: מה עבד בלי התערבות, מה דרש טיפול ידני, ומה יישבר אם מספר המשתמשים יוכפל. התשובות יגידו לכם אם להישאר במסלול $0, להעביר חלק ל-local, לשלם על provider אחד, או לצמצם את scope. זה גם סוגר את הקורס מעשית: אתם כבר לא בוחרים כלי לפי hype, אלא לפי התנהגות, מגבלות ועלות אמיתית.

כדי לסגור את ה-capstone, כתבו לעצמכם “2am failure list” עם חמש נקודות. הראשונה: training checkpoint לא נטען, ולכן אין adapter חדש. הפתרון: לא מוחקים adapter קודם לפני שהחדש נטען בסביבה נקייה. השנייה: HF Space עולה אבל אין secret. הפתרון: הודעת שגיאה ידידותית ומסמך secrets checklist. השלישית: provider primary מחזיר 429. הפתרון: rotation או retry-after, לא stack trace. הרביעית: המודל מחזיר JSON לא תקין. הפתרון: validation לפני הצגה למשתמש, ותשובת fallback שמבקשת לנסות שוב. החמישית: quota משתנה בלי שהבחנתם. הפתרון: בדיקה שבועית של dashboards וקריאת headers בזמן אמת. אם הרשימה הזאת קיימת, כל כשל צפוי כבר קיבל נתיב טיפול.

רשימה כזאת גם עוזרת להחליט מה לא לבנות. אין צורך לבנות dashboard מפואר אם אתם היחידים שמשתמשים במערכת. אין צורך להוסיף queue מורכב אם יש חמש בקשות ביום. אין צורך לאמן מחדש כל שבוע אם ה-eval set יציב. מערכת $0 טובה היא מערכת עם מעט חלקים, לא מערכת שמחקה תשתית enterprise על חשבון הזמן שלכם. כל רכיב חדש צריך לעבור שאלה פשוטה: האם הוא מפחית כשל אמיתי שכבר ראיתי או סיכון ברור שמופיע ברשימת 2am? אם לא, הוא כנראה קישוט.

הדבר האחרון הוא documentation של החלטות, לא רק קוד. כתבו בקובץ DECISIONS.md שלוש החלטות מרכזיות: למה בחרתם provider primary, למה ה-Space נשאר CPU בלבד, ולמה adapter הוא private או public. לכל החלטה הוסיפו “מתי נשנה אותה”. לדוגמה: נחליף provider אם fallback_rate מעל 15% במשך שבוע; נעבור ל-paid Space אם cold start מפריע למשתמשים אמיתיים; נפתח adapter לציבור רק אם הדאטה סינתטי ומישהו אחר צריך לשכפל את הפרויקט. החלטות עם תנאי שינוי מונעות ויכוחים עתידיים עם עצמכם.

אם אתם עובדים בצוות קטן, תנו לכל אדם תפקיד ברור בזמן כשל. אדם אחד בודק provider dashboards. אדם שני בודק Space logs. אדם שלישי בודק adapter repo וגרסה. אם אתם עובדים לבד, הפכו את התפקידים לשלוש בדיקות רצופות. אל תקפצו ישר לקוד. כשמערכת מורכבת מארבע פלטפורמות חינמיות, הכשל יכול להיות בכל אחת מהן. סדר בדיקה קבוע חוסך זמן: קודם UI, אחר כך secrets, אחר כך provider, אחר כך artifact, ורק בסוף אימון מחדש. אימון מחדש הוא כמעט אף פעם לא התגובה הראשונה לכשל serving.

כדאי גם להחזיק “known good input” ו-“known bad input”. known good הוא prompt קצר שאמור תמיד להחזיר תשובה תקינה. known bad הוא input ריק, ארוך מדי, או כזה שמכריח fallback. בכל smoke test מריצים את שניהם. אם known good נכשל, יש בעיית מערכת. אם known bad לא נכשל בצורה צפויה, יש בעיית validation. בדיקה זו קטנה מאוד, אבל היא מגלה מהר אם שינוי ב-Space, ב-provider או ב-dependency שבר התנהגות בסיסית.

לבסוף, זכרו שהקורס הזה לא נועד לגרום לכם להישאר בחינם בכל מחיר. הוא נועד לתת לכם חופש בחירה. עכשיו אתם יודעים להריץ notebook GPU בלי לאבד עבודה, לקרוא למודלים חזקים בלי GPU, להריץ מקומית כשצריך פרטיות, ולחבר הכל ל-pipeline. אם פרויקט נשאר קטן, המסלול החינמי יכול לשרת אותו היטב. אם הוא גדל, תוכלו לשלם על נקודת החנק המדויקת במקום לקנות “פתרון AI” גדול מדי. זו התוצאה האמיתית: לא רק פרויקט $0, אלא שיקול דעת תשתיתי.

לפני הסיום, עשו גם בדיקת “אדם אחר”. שלחו את ה-runbook, את כתובת ה-Space, ואת הודעת fallback לאדם שמבין טכנולוגיה אבל לא היה איתכם בבנייה. בקשו ממנו לבצע שלוש פעולות: לשלוח prompt תקין, לשלוח input בעייתי, ולמצוא איפה כתוב מה עושים כשיש 429. אם הוא צריך לשאול אתכם איפה secret מוגדר, מה provider primary, או למה Space לא עונה מיד אחרי sleep, התיעוד עדיין לא מספיק. בדיקה כזאת זולה יותר מכל refactor.

החזיקו גם קובץ CHANGELOG קטן. כל שינוי ב-provider, model name, adapter version, requirements או secrets policy מקבל שורה עם תאריך וסיבה. בפרויקטים חינמיים הבעיה אינה רק קוד שנשבר; הבעיה היא שאתם לא זוכרים איזה שינוי שבר אותו. CHANGELOG קצר הופך debug עתידי להרבה פחות רגשי. במקום “זה עבד אתמול”, אתם רואים “אתמול החלפנו מודל”, “אתמול שינינו secret name”, או “אתמול הוספנו fallback”. זו משמעת קטנה שמחזירה שליטה.

ולבסוף, הגדירו מראש מה נחשב הצלחה אחרי חודש: מספר בקשות שטופלו, שיעור fallback נמוך, מספר תיקונים ידניים, ואיכות תשובות על eval set קבוע. אם אחרי חודש המערכת לא עזרה לאף אחד, היא עדיין הייתה תרגיל מצוין, אבל אין חובה לתחזק אותה. אם היא כן עזרה, אתם יודעים בדיוק איזה רכיב ראוי להשקעה הבאה. כך פרויקט $0 הופך לשער ללמידה מעשית, לא להתחייבות אינסופית.

אם אתם מתלבטים האם לפרסם את הפרויקט, חזרו לשאלה אחת: האם משתמש חדש יכול להבין מה המערכת עושה, מה מגבלותיה, ומה יקרה כשאין quota. אם התשובה כן, יש לכם שיגור לימודי בשל. אם התשובה עדיין לא, שפרו את ההסבר לפני שאתם משפרים את המודל עצמו.

אל תשכחו את השגרה אחרי השיגור. Free-tier systems דורשות maintenance קל אבל קבוע: לבדוק quotas, לוודא secrets לא פגו, להריץ smoke test, ולקרוא logs. אם אתם לא מוכנים לתחזק, אל תתנו למערכת למישהו אחר. פרויקט $0 אינו פטור מאחריות. הוא פשוט מחליף כסף בתשומת לב.

שגרת עבודה מאוחדת לכל הקורס

תדירותמה עושיםלמה זה חשוב
יומיבדיקה אחת של ה-Space עם prompt קצר ובדיקת לוג fallback.מגלים secrets חסרים, sleep behavior או provider failure לפני משתמשים.
יומיבדיקת usage ledger: כמה קריאות, כמה 429, איזה provider נבחר.מונע שריפת quota שקטה.
שבועיבדיקת dashboards של Kaggle, Groq, Cerebras, OpenRouter ו-HF Spaces.free-tier limits משתנים; dashboard הוא מקור אמת.
שבועיהרצת smoke test על prompt קצר, prompt ארוך ו-fallback מבוקר.שומר את ה-runbook חי ולא תיאורטי.
שבועיבדיקת adapter repo: permissions, model card, base model reference.מונע artifact שאי אפשר לטעון מחדש.
חודשיסקירת ספקים חינמיים: האם מודלים התחלפו, האם quotas ירדו, האם צריך provider חדש.מעדכן את rotation לפני שהוא נשבר.
חודשיריצה קטנה מחדש או eval על 20 דוגמאות כדי לוודא שהאיכות לא נסחפה.מבדיל בין מערכת זמינה למערכת מועילה.
חודשיהחלטה מפורשת: נשארים $0, עוברים local, או משלמים על שכבה אחת.לא כל פרויקט צריך להישאר חינמי לנצח.
5 דקות

אם אתם עושים רק דבר אחד מהפרק הזה

כתבו runbook קצר שמסביר מה קורה כשה-provider הראשי מחזיר 429: לאיזה ספק עוברים, מה רושמים בלוג, ומה המשתמש רואה. פעולה אחת זו תמנע יותר כשלים מכל שורת אופטימיזציה באימון.

בדקו את עצמכם

  1. למה HF Space CPU מתאים כ-UI/router אבל לא כשרת קבוע למודל 7B? (רמז: גבול אחריות ומשאבי חומרה)
  2. איך checkpointing משנה את הסיכון של Kaggle session cap? (רמז: מה קורה אחרי restart)
  3. מתי LoRA עדיף על prompt engineering, ומתי הוא בזבוז? (רמז: התנהגות חוזרת מול ידע משתנה)
  4. מה ההבדל בין retry-after קצר לבין daily cap depleted? (רמז: המתנה מול rotation)
  5. מה חייב להיות secret ב-HF Spaces ולא variable רגיל? (רמז: כל דבר שיכול לאפשר שימוש בחשבון שלכם)

סיכום הפרק

הפרק הזה הפך את כל הקורס ממפה של אפשרויות למערכת אחת עם גבולות אחריות: Kaggle לאימון, Hub לאחסון artifact, Space ל-UI, וספק inference להרצה בזמן אמת. הרעיון המרכזי הוא ש-$0 אינו תירוץ לחוסר תכנון; להפך, הוא מחייב ניטור, checkpoints, secrets, rotation ו-fallback כי אין לכם SLA שמישהו אחר משלם עליו. אם בניתם את ה-runbook, ה-Space skeleton וה-failure drill, אתם כבר לא רק “מריצים מודלים בחינם” אלא יודעים לשגר מערכת קטנה באחריות. מכאן העבודה עוברת לתחזוקה: לבדוק quotas, לעדכן ספקים, ולבחור בכנות מתי החינם עדיין מתאים ומתי שכבה אחת בתשלום תחסוך יותר זמן ממה שהיא עולה.

Checklist סיום