דף זה תורגם על ידי Cloud Translation API.
Switch to English

הפצה אימונים עם TensorFlow

צפה ב TensorFlow.org הפעל ב- Google Colab צפה במקור ב- GitHub הורד מחברת

סקירה כללית

tf.distribute.Strategy הוא ממשק API של TensorFlow להפצת אימונים על מספר GPUs, מספר מכונות או TPUs. באמצעות ממשק API זה, אתה יכול להפיץ את הדגמים הקיימים ואת קוד ההדרכה שלך עם שינויים מינימליים בקוד.

tf.distribute.Strategy תוכנן עם מטרות מפתח אלה בחשבון:

  • קל לשימוש ותומך בפלחי משתמשים מרובים, כולל חוקרים, מהנדסי ML וכו '.
  • ספק ביצועים טובים מחוץ לקופסה.
  • מעבר קל בין אסטרטגיות.

ניתן להשתמש ב tf.distribute.Strategy עם ממשק API ברמה גבוהה כמו Keras , וניתן להשתמש בו גם להפצת לולאות הדרכה בהתאמה אישית (ובכלל, כל חישוב המשתמש ב- TensorFlow).

ב- TensorFlow 2.x, אתה יכול להפעיל את התוכניות שלך בשקיקה, או בתרשים באמצעות tf.function . tf.distribute.Strategy מתכוון לתמוך בשני מצבי הביצוע הללו, אך פועל בצורה הטובה ביותר עם tf.function . מצב להוט מומלץ רק למטרת ניפוי באגים ולא נתמך עבור TPUStrategy . למרות שאנחנו מדברים על אימונים רוב הזמן במדריך זה, API זה יכול לשמש גם להפצת הערכה וחיזוי בפלטפורמות שונות.

אתה יכול להשתמש tf.distribute.Strategy עם מעט מאוד שינויים בקוד שלך, כי שינינו את הרכיבים הבסיסיים של TensorFlow כדי להיות מודעים לאסטרטגיה. זה כולל משתנים, שכבות, מודלים, מיטוב, מדדים, סיכומים ונקודות ביקורת.

במדריך זה אנו מסבירים סוגים שונים של אסטרטגיות וכיצד ניתן להשתמש בהן במצבים שונים.

 # Import TensorFlow
import tensorflow as tf
 

סוגי אסטרטגיות

tf.distribute.Strategy מתכוון לכסות מספר מקרי שימוש לאורך צירים שונים. חלק מהשילובים הללו נתמכים כרגע ואחרים יתווספו בעתיד. חלק מהצירים האלה הם:

  • אימונים סינכרוניים לעומת אסינכרוניים: אלה שתי דרכים נפוצות להפצת אימונים במקבילות נתונים. באימוני סנכרון, כל העובדים מתאמנים על פרוסות שונות של נתוני קלט בסנכרון ומצטברים הדרגות בכל שלב. באימוני אסינכרון, כל העובדים מתאמנים באופן עצמאי על נתוני הקלט ומעדכנים משתנים באופן אסינכרוני. בדרך כלל, אימוני סנכרון נתמכים באמצעות הקטנת כל-אסינכרון באמצעות ארכיטקטורת שרת פרמטרים.
  • פלטפורמת חומרה: ייתכן שתרצה להגדיל את האימונים שלך למספר GPUs במחשב אחד, או מכונות מרובות ברשת (עם 0 GPUs או יותר כל אחד), או על TPUs בענן.

כדי לתמוך במקרי שימוש אלה, קיימות שש אסטרטגיות זמינות. בחלק הבא נסביר אילו מאלה נתמכים באילו תרחישים ב- TF 2.2 כרגע. להלן סקירה מהירה:

API להכשרה MirroredStrategy TPUS אסטרטגיה MultiWorkerMirroredStrategy CentralStorageStrategy ParameterServerStrategy
API של Keras נתמך נתמך תמיכה ניסיונית תמיכה ניסיונית תומך בתפקיד המתוכנן 2.3
לולאת אימונים בהתאמה אישית נתמך נתמך תמיכה ניסיונית תמיכה ניסיונית תומך בתפקיד המתוכנן 2.3
ממשק API תמיכה מוגבלת אינו נתמך תמיכה מוגבלת תמיכה מוגבלת תמיכה מוגבלת

MirroredStrategy

tf.distribute.MirroredStrategy תומך באימונים מבוזרים סינכרוניים במספר GPUs במכונה אחת. זה יוצר העתק אחד לכל מכשיר GPU. כל משתנה במודל משתקף על פני כל העותקים המשוכפלים. יחד, משתנים אלה יוצרים משתנה רעיוני יחיד הנקרא MirroredVariable . משתנים אלה נשמרים מסונכרנים זה עם זה על ידי יישום עדכונים זהים.

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

הנה הדרך הפשוטה ביותר ליצור MirroredStrategy :

 mirrored_strategy = tf.distribute.MirroredStrategy()
 
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0',)

פעולה זו תיצור מופע MirroredStrategy אשר ישתמש בכל ה- GPUs הנראים לעיני TensorFlow, וישתמש ב- NCCL כתקשורת התקנים בין מכשירים.

אם ברצונך להשתמש רק בחלק מה- GPU במחשב שלך, אתה יכול לעשות זאת כך:

 mirrored_strategy = tf.distribute.MirroredStrategy(devices=["/gpu:0", "/gpu:1"])
 
WARNING:tensorflow:Some requested devices in `tf.distribute.Strategy` are not visible to TensorFlow: /job:localhost/replica:0/task:0/device:GPU:0,/job:localhost/replica:0/task:0/device:GPU:1
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0', '/job:localhost/replica:0/task:0/device:GPU:1')

אם ברצונך לעקוף את התקשורת בין מכשירים צולבים, תוכל לעשות זאת באמצעות הארגומנט cross_device_ops ידי ספק מופע של tf.distribute.CrossDeviceOps . נכון לעכשיו, tf.distribute.HierarchicalCopyAllReduce ו- tf.distribute.ReductionToOneDevice הן שתי אפשרויות אחרות מלבד tf.distribute.NcclAllReduce שהיא ברירת המחדל.

 mirrored_strategy = tf.distribute.MirroredStrategy(
    cross_device_ops=tf.distribute.HierarchicalCopyAllReduce())
 
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0',)

TPUS אסטרטגיה

tf.distribute.TPUStrategy מאפשרת לך להפעיל את אימוני ה- TensorFlow שלך על יחידות עיבוד Tensor (TPU). מכשירי TPU הם ASICs המתמחים של גוגל המיועדים להאיץ דרמטית את עומסי העבודה של למידת מכונה. הם זמינים ב- Google Colab, ענן המחקר TensorFlow ו- Cloud TPU .

מבחינת ארכיטקטורת אימונים מבוזרת, TPUStrategy היא אותה MirroredStrategy - היא מיישמת אימונים מבוזרים סינכרוניים. מכשירי TPU מספקים יישום משלהם של פעולות קולקטיביות יעילות להפחתה ואחרות בכל ליבות TPU מרובות, המשמשות בתכנית TPUStrategy .

כך TPUStrategy :

 cluster_resolver = tf.distribute.cluster_resolver.TPUClusterResolver(
    tpu=tpu_address)
tf.config.experimental_connect_to_cluster(cluster_resolver)
tf.tpu.experimental.initialize_tpu_system(cluster_resolver)
tpu_strategy = tf.distribute.TPUStrategy(cluster_resolver)
 

מופע TPUClusterResolver מסייע באיתור ה- TPUs. בקולאב אתה לא צריך לציין כל טיעון אליו.

אם ברצונך להשתמש בזה עבור TPUs בענן:

  • עליך לציין את שם משאב ה- TPU שלך בטיעון ה- tpu .
  • עליך לאתחל את מערכת ה- tpu במפורש עם תחילת התוכנית. זה נדרש לפני שניתן להשתמש בתכניות TPU לחישוב. האתחול של מערכת ה- tpu מוחק גם את זיכרון ה- TPU, ולכן חשוב לבצע את הצעד הראשון תחילה כדי להימנע מאיבוד מצב.

MultiWorkerMirroredStrategy

tf.distribute.experimental.MultiWorkerMirroredStrategy דומה מאוד ל- MirroredStrategy . זה מיישם אימונים מבוזרים סינכרוניים על פני עובדים מרובים, שלכל אחד מהם יש GPUs מרובים. בדומה ל- MirroredStrategy , הוא יוצר עותקים של כל המשתנים במודל בכל מכשיר בכל העובדים.

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

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

הנה הדרך הפשוטה ביותר ליצור MultiWorkerMirroredStrategy :

 multiworker_strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()
 
WARNING:tensorflow:Collective ops is not configured at program startup. Some performance features may not be enabled.
INFO:tensorflow:Using MirroredStrategy with devices ('/device:GPU:0',)
INFO:tensorflow:Single-worker MultiWorkerMirroredStrategy with local_devices = ('/device:GPU:0',), communication = CollectiveCommunication.AUTO

MultiWorkerMirroredStrategy מאפשרת לך לבחור בין שני יישומים שונים של אופציות קולקטיביות. CollectiveCommunication.RING מיישם קולקטיבים מבוססי טבעת המשתמשים ב- gRPC כשכבת התקשורת. CollectiveCommunication.NCCL משתמש ב- NCCL של Nvidia כדי ליישם קולקטיבים. CollectiveCommunication.AUTO מגן על הבחירה בזמן ההפעלה. הבחירה הטובה ביותר של יישום קולקטיבי תלויה במספר GPUs ובסוגם, ובקשרים בין חיבורי הרשת באשכול. אתה יכול לציין אותם בדרך הבאה:

 multiworker_strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy(
    tf.distribute.experimental.CollectiveCommunication.NCCL)
 
WARNING:tensorflow:Collective ops is not configured at program startup. Some performance features may not be enabled.
INFO:tensorflow:Using MirroredStrategy with devices ('/device:GPU:0',)
INFO:tensorflow:Single-worker MultiWorkerMirroredStrategy with local_devices = ('/device:GPU:0',), communication = CollectiveCommunication.NCCL

אחד ההבדלים העיקריים בכדי להשיג הכשרה מרובת עובדים, בהשוואה להכשרה רב GPU, הוא ההתקנה של עובדים רבים. משתנה הסביבה TF_CONFIG הוא הדרך הסטנדרטית ב- TensorFlow להגדיר את תצורת האשכול לכל עובד שהוא חלק מהאשכול. למידע נוסף על הגדרת TF_CONFIG .

CentralStorageStrategy

tf.distribute.experimental.CentralStorageStrategy עושה גם אימונים סינכרוניים. משתנים אינם משתקפים, במקום זאת הם ממוקמים על ה- CPU ופעולות משוכפלות בכל ה- GPU המקומי. אם יש רק GPU אחד, כל המשתנים והפעולות ימוקמו על אותו GPU.

צור מופע של CentralStorageStrategy ידי:

 central_storage_strategy = tf.distribute.experimental.CentralStorageStrategy()
 
INFO:tensorflow:ParameterServerStrategy (CentralStorageStrategy if you are using a single machine) with compute_devices = ['/job:localhost/replica:0/task:0/device:GPU:0'], variable_device = '/job:localhost/replica:0/task:0/device:GPU:0'

פעולה זו תיצור מופע CentralStorageStrategy אשר ישתמש בכל ה- GPU והמעבד הנראים לעין. עדכון למשתנים על גבי העתקים יצובר לפני שיוחל על משתנים.

ParameterServerStrategy

tf.distribute.experimental.ParameterServerStrategy תומך באימוני שרתי פרמטרים במספר מכונות. בהתקנה זו, מכונות מסוימות מיועדות כעובדות וחלקן כשרתי פרמטרים. כל משתנה של המודל ממוקם בשרת פרמטר אחד. חישוב משוכפל בכל GPUs של כל העובדים.

מבחינת קוד זה נראה לאסטרטגיות אחרות:

 ps_strategy = tf.distribute.experimental.ParameterServerStrategy()
 

לצורך אימון רב עובדים, TF_CONFIG צריך לציין את התצורה של שרתי פרמטרים ועובדים באשכול שלך, עליהם תוכלו לקרוא עוד על TF_CONFIG להלן .

אסטרטגיות אחרות

בנוסף לאסטרטגיות שלעיל, ישנן שתי אסטרטגיות אחרות tf.distribute באגים בעת שימוש בממשקי API של tf.distribute .

אסטרטגיית ברירת מחדל

אסטרטגיית ברירת מחדל היא אסטרטגיית הפצה אשר קיימת כאשר אין אסטרטגיית הפצה מפורשת בהיקפה. זה מיישם את ממשק tf.distribute.Strategy אך הוא מעבר ולא מספק חלוקה בפועל. לדוגמה, strategy.run(fn) פשוט יתקשר ל- fn . קוד שנכתב באמצעות אסטרטגיה זו צריך להתנהג בדיוק כמו קוד שנכתב ללא שום אסטרטגיה. אתה יכול לחשוב על זה כאל אסטרטגיית "אין-אופ".

אסטרטגיית ברירת מחדל היא סינגלטון - ואי אפשר ליצור יותר מקרים ממנה. ניתן להשיג אותו באמצעות tf.distribute.get_strategy() מחוץ לתחום של כל אסטרטגיה מפורשת (אותו ממשק API שניתן להשתמש בו כדי לקבל את האסטרטגיה הנוכחית בתוך היקף אסטרטגיה מפורשת).

 default_strategy = tf.distribute.get_strategy()
 

אסטרטגיה זו משרתת שתי מטרות עיקריות:

 # In optimizer or other library code
# Get currently active strategy
strategy = tf.distribute.get_strategy()
strategy.reduce("SUM", 1., axis=None)  # reduce some values
 
1.0
  • בדומה לקוד הספרייה, ניתן להשתמש בו לכתיבת תוכניות של משתמשי קצה כדי לעבוד עם ובלי אסטרטגיית הפצה, מבלי לדרוש היגיון מותנה. קטע קוד לדוגמא המדגים זאת:
 if tf.config.list_physical_devices('gpu'):
  strategy = tf.distribute.MirroredStrategy()
else:  # use default strategy
  strategy = tf.distribute.get_strategy() 

with strategy.scope():
  # do something interesting
  print(tf.Variable(1.))
 
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0>

OneDeviceStrategy

tf.distribute.OneDeviceStrategy היא אסטרטגיה להציב את כל המשתנים והחישובים במכשיר שצוין יחיד.

 strategy = tf.distribute.OneDeviceStrategy(device="/gpu:0")
 

אסטרטגיה זו נבדלת מאסטרטגיית ברירת המחדל במספר דרכים. באסטרטגיית ברירת מחדל, היגיון המיקומים המשתנים נותר ללא שינוי בהשוואה להפעלת TensorFlow ללא אסטרטגיית הפצה. אך בעת השימוש ב- OneDeviceStrategy , כל המשתנים שנוצרו בהיקפו ממוקמים במפורש במכשיר שצוין. יתר על כן, כל הפונקציות הנקראות באמצעות OneDeviceStrategy.run יוצבו גם במכשיר שצוין.

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

בדומה לאסטרטגיית ברירת המחדל, אסטרטגיה זו יכולה לשמש גם לבדיקת הקוד שלך לפני המעבר לאסטרטגיות אחרות המפיצות בפועל למספר מכשירים / מכונות. זה יפעיל את מכונות אסטרטגיית ההפצה מעט יותר מאסטרטגיית ברירת מחדל, אך לא במלואן TPUStrategy MirroredStrategy או TPUStrategy וכו '. אם אתה רוצה קוד שמתנהג כאילו אין אסטרטגיה, השתמש באסטרטגיית ברירת מחדל.

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

באמצעות tf.distribute.Strategy עם tf.keras.Model.fit

שילבנו את tf.distribute.Strategy ב- tf.keras שהיא היישום של TensorFlow של מפרט ה- API של Keras . tf.keras הוא ממשק API ברמה גבוהה לבניית tf.keras של דגמים. על ידי השתלבות tf.keras , הפכנו את עצמך לחלק את ההדרכה שנכתבה במסגרת ההדרכה של model.fit באמצעות model.fit .

להלן מה שאתה צריך לשנות בקוד שלך:

  1. צור מופע של tf.distribute.Strategy המתאים.
  2. העבר את היצירה של מודל Keras, מיטוב ומדדים בתוך strategy.scope .

אנו תומכים בכל סוגי דגמי Keras - רציפים, פונקציונליים ותת-דרגיים.

להלן קטע קוד כדי לעשות זאת עבור דגם Keras פשוט מאוד עם שכבה צפופה אחת:

 mirrored_strategy = tf.distribute.MirroredStrategy()

with mirrored_strategy.scope():
  model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])

model.compile(loss='mse', optimizer='sgd')
 
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0',)

בדוגמה זו השתמשנו ב- MirroredStrategy כדי שנוכל להריץ זאת במחשב עם מספר GPUs. strategy.scope() מציין בפני Keras באיזו אסטרטגיה להשתמש כדי להפיץ את האימונים. יצירת מודלים / מיטוב / מדדים בתוך היקף זה מאפשרת לנו ליצור משתנים מבוזרים במקום משתנים רגילים. ברגע שזה מוגדר, אתה יכול להתאים לדגם שלך כמו שהיה בדרך כלל. MirroredStrategy דואג לשכפל את אימון המודל על ה- GPUs הזמינים, הדרגת צבירה ועוד.

 dataset = tf.data.Dataset.from_tensors(([1.], [1.])).repeat(100).batch(10)
model.fit(dataset, epochs=2)
model.evaluate(dataset)
 
Epoch 1/2
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/data/ops/multi_device_iterator_ops.py:601: get_next_as_optional (from tensorflow.python.data.ops.iterator_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Iterator.get_next_as_optional()` instead.
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
10/10 [==============================] - 0s 2ms/step - loss: 1.0035
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
Epoch 2/2
10/10 [==============================] - 0s 1ms/step - loss: 0.4436
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
INFO:tensorflow:Reduce to /job:localhost/replica:0/task:0/device:CPU:0 then broadcast to ('/job:localhost/replica:0/task:0/device:CPU:0',).
10/10 [==============================] - 0s 1ms/step - loss: 0.2755

0.27546340227127075

כאן השתמשנו ב- tf.data.Dataset כדי לספק את קלט ההדרכה tf.data.Dataset . אתה יכול גם להשתמש במערכים מנוקבים:

 import numpy as np
inputs, targets = np.ones((100, 1)), np.ones((100, 1))
model.fit(inputs, targets, epochs=2, batch_size=10)
 
Epoch 1/2
10/10 [==============================] - 0s 1ms/step - loss: 0.1961
Epoch 2/2
10/10 [==============================] - 0s 1ms/step - loss: 0.0867

<tensorflow.python.keras.callbacks.History at 0x7fa17808e240>

בשני המקרים (מערך נתונים או numpy), כל אצווה של הקלט הנתון מחולקת באופן שווה בין העתקים המרובים. לדוגמה, אם משתמשים ב- MirroredStrategy עם 2 GPUs, כל אצווה בגודל 10 תחולק בין 2 GPUs, כאשר כל אחת מקבלת 5 דוגמאות קלט בכל שלב. לאחר מכן, כל תקופה תתאמן מהר יותר כאשר תוסיפו יותר GPUs. בדרך כלל תרצה להגדיל את גודל האצווה שלך כשאתה מוסיף מאיצים נוספים בכדי לנצל את היעילות בכוח המחשוב הנוסף. תצטרך גם לכוונן מחדש את קצב הלמידה שלך, בהתאם לדגם. אתה יכול להשתמש ב strategy.num_replicas_in_sync כדי לקבל את מספר העותקים המשוכפלים.

 # Compute global batch size using number of replicas.
BATCH_SIZE_PER_REPLICA = 5
global_batch_size = (BATCH_SIZE_PER_REPLICA *
                     mirrored_strategy.num_replicas_in_sync)
dataset = tf.data.Dataset.from_tensors(([1.], [1.])).repeat(100)
dataset = dataset.batch(global_batch_size)

LEARNING_RATES_BY_BATCH_SIZE = {5: 0.1, 10: 0.15}
learning_rate = LEARNING_RATES_BY_BATCH_SIZE[global_batch_size]
 

מה נתמך כעת?

API להכשרה MirroredStrategy TPUS אסטרטגיה MultiWorkerMirroredStrategy CentralStorageStrategy ParameterServerStrategy
ממשקי API של Keras נתמך נתמך תמיכה ניסיונית תמיכה ניסיונית תמיכה בפוסט 2.3 מתוכנן

דוגמאות ומדריכים

להלן רשימת הדרכות ודוגמאות המדגימות את סיום האינטגרציה שלמעלה עם Keras:

  1. הדרכה להדרכת MNIST באמצעות MirroredStrategy .
  2. הדרכה להדרכת MNIST באמצעות MultiWorkerMirroredStrategy .
  3. מדריך להכשרת MNIST באמצעות TPUStrategy .
  4. מאגר גן מודלים TensorFlow המכיל אוספים של מודלים חדישים המיושמים באמצעות אסטרטגיות שונות.

באמצעות tf.distribute.Strategy עם לולאות אימון בהתאמה אישית

כפי שראית, השימוש ב- tf.distribute.Strategy עם Keras model.fit דורש שינוי רק של כמה שורות מהקוד שלך. עם קצת יותר מאמץ, אתה יכול גם להשתמש tf.distribute.Strategy עם לולאות אימון מותאמות אישית.

אם אתה זקוק לגמישות רבה יותר ושליטה על לולאות האימונים שלך ממה שאפשר באמצעות Estimator או Keras, אתה יכול לכתוב לולאות אימון בהתאמה אישית. לדוגמה, בעת שימוש ב- GAN, מומלץ לבצע מספר שונה של צעדים גנרטורים או מפלים בכל סיבוב. באופן דומה, המסגרות ברמה הגבוהה אינן מתאימות במיוחד לאימוני חיזוק למידה.

כדי לתמוך בלולאות אימון בהתאמה אישית, אנו מספקים מערך שיטות ליבה דרך שיעורי tf.distribute.Strategy . השימוש בהם עשוי לדרוש ארגון מחדש של הקוד בהתחלה, אך ברגע שזה ייעשה, אתה אמור להיות מסוגל לעבור בין GPUs, TPUs ומכונות מרובות פשוט על ידי שינוי מופע האסטרטגיה.

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

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

 with mirrored_strategy.scope():
  model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
  optimizer = tf.keras.optimizers.SGD()
 

בשלב הבא, אנו יוצרים את מערך הקלט tf.distribute.Strategy.experimental_distribute_dataset כדי להפיץ את מערך הנתונים על בסיס האסטרטגיה.

 dataset = tf.data.Dataset.from_tensors(([1.], [1.])).repeat(100).batch(
    global_batch_size)
dist_dataset = mirrored_strategy.experimental_distribute_dataset(dataset)
 

לאחר מכן, אנו מגדירים שלב אחד של האימונים. אנו נשתמש ב- tf.GradientTape כדי לחשב מעבר צבע וכלי האופטימיזציה כדי להחיל אותם מעברים כדי לעדכן את המשתנים של המודל שלנו. כדי להפיץ שלב אימונים זה, הכנסנו train_step לפונקציה ומעבירים אותו ל- tf.distrbute.Strategy.run יחד עם כניסות הנתונים שאנו מקבלים מ- dist_dataset שנוצרו לפני:

 loss_object = tf.keras.losses.BinaryCrossentropy(
  from_logits=True,
  reduction=tf.keras.losses.Reduction.NONE)

def compute_loss(labels, predictions):
  per_example_loss = loss_object(labels, predictions)
  return tf.nn.compute_average_loss(per_example_loss, global_batch_size=global_batch_size)

def train_step(inputs):
  features, labels = inputs

  with tf.GradientTape() as tape:
    predictions = model(features, training=True)
    loss = compute_loss(labels, predictions)

  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))
  return loss

@tf.function
def distributed_train_step(dist_inputs):
  per_replica_losses = mirrored_strategy.run(train_step, args=(dist_inputs,))
  return mirrored_strategy.reduce(tf.distribute.ReduceOp.SUM, per_replica_losses,
                         axis=None)
 

כמה דברים נוספים שיש לציין בקוד שלמעלה:

  1. השתמשנו ב- tf.nn.compute_average_loss כדי לחשב את ההפסד. tf.nn.compute_average_loss מסכם את ההפסד לדוגמה ומחלק את הסכום לפי global_batch_size. זה חשוב מכיוון שמאוחר יותר לאחר חישוב ההחלפות על כל העתק, הם מצטברים על פני העתקים על ידי סיכומם .
  2. השתמשנו ב- tf.distribute.Strategy.reduce API כדי לצבור את התוצאות שהוחזרו על ידי tf.distribute.Strategy.run . tf.distribute.Strategy.run מחזירה תוצאות מכל העתק מקומי באסטרטגיה, וישנן דרכים רבות לצרוך תוצאה זו. אתה יכול reduce אותם כדי לקבל ערך מצטבר. אתה יכול גם לבצע את tf.distribute.Strategy.experimental_local_results כדי לקבל את רשימת הערכים הכלולים בתוצאה, אחד לכל העתק מקומי.
  3. כאשר נקרא apply_gradients במסגרת אסטרטגיית הפצה, התנהגותה משתנה. באופן ספציפי, לפני החלת מעברי צבע על כל מופע מקביל במהלך אימונים סינכרוניים, הוא מבצע סכומי העתק של סכום-על-כל של הדרגות.

לבסוף, לאחר שהגדרנו את שלב האימונים, נוכל לחזור על dist_dataset ולהפעיל את האימונים בלולאה:

 for dist_inputs in dist_dataset:
  print(distributed_train_step(dist_inputs))
 
tf.Tensor(0.4155251, shape=(), dtype=float32)
tf.Tensor(0.41321823, shape=(), dtype=float32)
tf.Tensor(0.4109319, shape=(), dtype=float32)
tf.Tensor(0.40866604, shape=(), dtype=float32)
tf.Tensor(0.40642032, shape=(), dtype=float32)
tf.Tensor(0.40419456, shape=(), dtype=float32)
tf.Tensor(0.4019885, shape=(), dtype=float32)
tf.Tensor(0.399802, shape=(), dtype=float32)
tf.Tensor(0.39763477, shape=(), dtype=float32)
tf.Tensor(0.3954866, shape=(), dtype=float32)
tf.Tensor(0.39335734, shape=(), dtype=float32)
tf.Tensor(0.3912467, shape=(), dtype=float32)
tf.Tensor(0.38915452, shape=(), dtype=float32)
tf.Tensor(0.38708064, shape=(), dtype=float32)
tf.Tensor(0.38502476, shape=(), dtype=float32)
tf.Tensor(0.38298675, shape=(), dtype=float32)
tf.Tensor(0.38096642, shape=(), dtype=float32)
tf.Tensor(0.3789635, shape=(), dtype=float32)
tf.Tensor(0.3769779, shape=(), dtype=float32)
tf.Tensor(0.37500936, shape=(), dtype=float32)

בדוגמה שלמעלה, חזרנו על dist_dataset כדי לספק קלט לאימונים שלך. אנו מספקים גם את tf.distribute.Strategy.make_experimental_numpy_dataset כדי לתמוך בכניסות tf.distribute.Strategy.make_experimental_numpy_dataset . אתה יכול להשתמש בממשק API זה כדי ליצור מערך נתונים לפני שאתה מתקשר אל tf.distribute.Strategy.experimental_distribute_dataset .

דרך נוספת לחזור על הנתונים שלך היא להשתמש באופן מפורש באיטטורים. יתכן שתרצה לעשות זאת כשאתה רוצה לרוץ למספר נתון של צעדים לעומת איטרציה על מערך הנתונים כולו. האמור לעיל איטרציה עכשיו יהיה שונה כדי ליצור באיטרטור הראשון ולאחר מכן קורא באופן מפורש next עליו כדי לקבל את נתוני הקלט.

 iterator = iter(dist_dataset)
for _ in range(10):
  print(distributed_train_step(next(iterator)))
 
tf.Tensor(0.37305772, shape=(), dtype=float32)
tf.Tensor(0.3711228, shape=(), dtype=float32)
tf.Tensor(0.3692044, shape=(), dtype=float32)
tf.Tensor(0.36730233, shape=(), dtype=float32)
tf.Tensor(0.3654165, shape=(), dtype=float32)
tf.Tensor(0.36354658, shape=(), dtype=float32)
tf.Tensor(0.36169255, shape=(), dtype=float32)
tf.Tensor(0.3598542, shape=(), dtype=float32)
tf.Tensor(0.35803124, shape=(), dtype=float32)
tf.Tensor(0.3562236, shape=(), dtype=float32)

זה מכסה את המקרה הפשוט ביותר של השימוש ב- tf.distribute.Strategy API להפצת לולאות הדרכה בהתאמה אישית. אנו נמצאים בתהליך של שיפור ממשקי API אלה. מכיוון שמקרה שימוש זה דורש עבודה רבה יותר להתאמת הקוד שלך, אנו נפרסם בעתיד מדריך מפורט נפרד.

מה נתמך כעת?

API להכשרה MirroredStrategy TPUS אסטרטגיה MultiWorkerMirroredStrategy CentralStorageStrategy ParameterServerStrategy
לולאת אימונים מותאמת אישית נתמך נתמך תמיכה ניסיונית תמיכה ניסיונית תמיכה בפוסט 2.3 מתוכנן

דוגמאות ומדריכים

להלן כמה דוגמאות לשימוש באסטרטגיית הפצה עם לולאות אימונים מותאמות אישית:

  1. הדרכה להדרכת MNIST באמצעות MirroredStrategy .
  2. מדריך להכשרת MNIST באמצעות TPUStrategy .
  3. מאגר גן מודלים TensorFlow המכיל אוספים של מודלים חדישים המיושמים באמצעות אסטרטגיות שונות.

שימוש ב tf.distribute.Strategy עם Estimator (תמיכה מוגבלת)

tf.estimator הוא ממשק API של TensorFlow אימונים מבוזרים שתמך במקור בגישה של שרת פרמטרים async. כמו עם Keras, שילבנו את tf.distribute.Strategy ב- tf.Estimator . אם אתה משתמש ב- Estimator לאימוניך, אתה יכול בקלות לעבור לאימונים מבוזרים עם מעט מאוד שינויים בקוד שלך. בכך, משתמשי Estimator יכולים כעת לבצע אימונים מבוזרים סינכרוניים במספר GPUs ועובדים מרובים, כמו גם להשתמש ב- TPUs. עם זאת, תמיכה זו ב- Estimator מוגבלת. לפרטים נוספים, ראה מה נתמך כעת בהמשך.

השימוש tf.distribute.Strategy עם Estimator שונה מעט ממקרה Keras. במקום להשתמש strategy.scope , עכשיו אנחנו עוברים את אובייקט האסטרטגיה לתוך RunConfig עבור ההערכה.

להלן קטע קוד שמראה זאת באמצעות LinearRegressor Estimator LinearRegressor ו- MirroredStrategy :

 mirrored_strategy = tf.distribute.MirroredStrategy()
config = tf.estimator.RunConfig(
    train_distribute=mirrored_strategy, eval_distribute=mirrored_strategy)
regressor = tf.estimator.LinearRegressor(
    feature_columns=[tf.feature_column.numeric_column('feats')],
    optimizer='SGD',
    config=config)
 
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0',)
INFO:tensorflow:Initializing RunConfig with distribution strategies.
INFO:tensorflow:Not using Distribute Coordinator.
WARNING:tensorflow:Using temporary folder as model directory: /tmp/tmp2ack9oru
INFO:tensorflow:Using config: {'_model_dir': '/tmp/tmp2ack9oru', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': <tensorflow.python.distribute.mirrored_strategy.MirroredStrategy object at 0x7fa124522b38>, '_device_fn': None, '_protocol': None, '_eval_distribute': <tensorflow.python.distribute.mirrored_strategy.MirroredStrategy object at 0x7fa124522b38>, '_experimental_distribute': None, '_experimental_max_worker_delay_secs': None, '_session_creation_timeout_secs': 7200, '_service': None, '_cluster_spec': ClusterSpec({}), '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1, '_distribute_coordinator_mode': None}

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

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

 def input_fn():
  dataset = tf.data.Dataset.from_tensors(({"feats":[1.]}, [1.]))
  return dataset.repeat(1000).batch(10)
regressor.train(input_fn=input_fn, steps=10)
regressor.evaluate(input_fn=input_fn, steps=10)
 
INFO:tensorflow:Calling model_fn.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow_estimator/python/estimator/canned/linear.py:1481: Layer.add_variable (from tensorflow.python.keras.engine.base_layer_v1) is deprecated and will be removed in a future version.
Instructions for updating:
Please use `layer.add_weight` method instead.
INFO:tensorflow:Done calling model_fn.
WARNING:tensorflow:AutoGraph could not transform <function _combine_distributed_scaffold.<locals>.<lambda> at 0x7fa12452cb70> and will run it as-is.
Cause: could not parse the source code:

      lambda scaffold: scaffold.ready_op, args=(grouped_scaffold,))

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function _combine_distributed_scaffold.<locals>.<lambda> at 0x7fa12452cb70> and will run it as-is.
Cause: could not parse the source code:

      lambda scaffold: scaffold.ready_op, args=(grouped_scaffold,))

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
INFO:tensorflow:Create CheckpointSaverHook.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow_estimator/python/estimator/util.py:96: DistributedIteratorV1.initialize (from tensorflow.python.distribute.input_lib) is deprecated and will be removed in a future version.
Instructions for updating:
Use the iterator's `initializer` property instead.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 0...
INFO:tensorflow:Saving checkpoints for 0 into /tmp/tmp2ack9oru/model.ckpt.
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 0...
INFO:tensorflow:loss = 1.0, step = 0
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 10...
INFO:tensorflow:Saving checkpoints for 10 into /tmp/tmp2ack9oru/model.ckpt.
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 10...
INFO:tensorflow:Loss for final step: 2.877698e-13.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
WARNING:tensorflow:AutoGraph could not transform <function _combine_distributed_scaffold.<locals>.<lambda> at 0x7fa1e9768d08> and will run it as-is.
Cause: could not parse the source code:

      lambda scaffold: scaffold.ready_op, args=(grouped_scaffold,))

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function _combine_distributed_scaffold.<locals>.<lambda> at 0x7fa1e9768d08> and will run it as-is.
Cause: could not parse the source code:

      lambda scaffold: scaffold.ready_op, args=(grouped_scaffold,))

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
INFO:tensorflow:Starting evaluation at 2020-08-04T20:28:12Z
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmp2ack9oru/model.ckpt-10
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Evaluation [1/10]
INFO:tensorflow:Evaluation [2/10]
INFO:tensorflow:Evaluation [3/10]
INFO:tensorflow:Evaluation [4/10]
INFO:tensorflow:Evaluation [5/10]
INFO:tensorflow:Evaluation [6/10]
INFO:tensorflow:Evaluation [7/10]
INFO:tensorflow:Evaluation [8/10]
INFO:tensorflow:Evaluation [9/10]
INFO:tensorflow:Evaluation [10/10]
INFO:tensorflow:Inference Time : 0.20350s
INFO:tensorflow:Finished evaluation at 2020-08-04-20:28:12
INFO:tensorflow:Saving dict for global step 10: average_loss = 1.4210855e-14, global_step = 10, label/mean = 1.0, loss = 1.4210855e-14, prediction/mean = 0.99999994
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 10: /tmp/tmp2ack9oru/model.ckpt-10

{'average_loss': 1.4210855e-14,
 'label/mean': 1.0,
 'loss': 1.4210855e-14,
 'prediction/mean': 0.99999994,
 'global_step': 10}

הבדל נוסף שיש להדגיש כאן בין Estimator ל- Keras הוא הטיפול בקלט. ב- Keras הזכרנו שכל קבוצה של מערך הנתונים מפוצלת אוטומטית בין העותקים המשוכפלים. עם זאת, באומדן הערכות איננו מבצעים פיצול אוטומטי של האצווה, ואיננו מנקים את הנתונים באופן אוטומטי על פני עובדים שונים. יש לך שליטה מלאה כיצד ברצונך שהנתונים שלך יופצו על פני עובדים ומכשירים, ועליך לספק input_fn כדי לציין כיצד להפיץ את הנתונים שלך.

ה- input_fn שלך נקרא פעם לעובד, ובכך נותן מערך נתונים אחד לעובד. ואז אצווה אחת מאותו מערך נתונים מועברת לעותק מסונכרן אחד על אותו עובד, ובכך צורכת אצוות N עבור העתקים N עבור עובד אחד. במילים אחרות, מערך הנתונים שהוחזר על ידי input_fn אמור לספק קבוצות בגודל PER_REPLICA_BATCH_SIZE . ואת גודל האצווה הגלובלי עבור שלב ניתן להשיג כ- PER_REPLICA_BATCH_SIZE * strategy.num_replicas_in_sync . PER_REPLICA_BATCH_SIZE * strategy.num_replicas_in_sync .

כשעושים הכשרה רב עובדים, עליך לפצל את הנתונים שלך על העובדים, או לערבב עם זרע אקראי על כל אחד מהם. אתה יכול לראות דוגמא כיצד לעשות זאת בהדרכה רב עובדים עם מעריך .

ובאופן דומה, תוכלו להשתמש גם באסטרטגיות שרת רב-עובד ופרמטרים. הקוד נותר זהה, אך עליכם להשתמש tf.estimator.train_and_evaluate ולהגדיר משתני סביבה TF_CONFIG עבור כל בינארי הפועל באשכול שלכם.

מה נתמך כעת?

קיימת תמיכה מוגבלת באימונים עם Estimator תוך שימוש בכל האסטרטגיות למעט TPUStrategy . אימונים בסיסיים והערכה אמורים לעבוד, אך מספר מאפיינים מתקדמים כמו פיגום עדיין לא עובדים. יכול להיות שיש גם מספר באגים בשילוב זה. נכון לעכשיו, איננו מתכננים לשפר את התמיכה הזו באופן פעיל, ובמקום זאת אנו ממוקדים בקרס ותמיכה בלולאת אימונים בהתאמה אישית. אם בכלל אפשרי, עליך במקום זאת להשתמש tf.distribute עם אותם ממשקי API.

API להכשרה MirroredStrategy TPUS אסטרטגיה MultiWorkerMirroredStrategy CentralStorageStrategy ParameterServerStrategy
ממשק API תמיכה מוגבלת אינו נתמך תמיכה מוגבלת תמיכה מוגבלת תמיכה מוגבלת

דוגמאות ומדריכים

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

  1. אימון רב עובדים עם מעריך להכשיר MNIST עם עובדים רבים באמצעות MultiWorkerMirroredStrategy .
  2. דוגמה מקצה לקצה לאימוני עובדים רבים בזרימת מים / מערכת אקולוגית באמצעות תבניות Kubernetes. דוגמה זו מתחילה במודל של Keras וממירה אותו ל- Estimator באמצעות ממשק ה- API tf.keras.estimator.model_to_estimator .
  3. דגם ResNet50 הרשמי, אותו ניתן לאמן באמצעות MirroredStrategy או MultiWorkerMirroredStrategy .

נושאים אחרים

בחלק זה נעסוק בכמה נושאים הרלוונטיים למקרים בהם נעשה שימוש מרובה.

הגדרת משתנה סביבה TF_CONFIG

לאימונים רב עובדים, כאמור, עליך להגדיר TF_CONFIG משתנה סביבתי עבור כל ריצה בינארית באשכול שלך. משתנה הסביבה TF_CONFIG הוא מחרוזת JSON המציינת אילו משימות מהוות אשכול, הכתובות שלהם ותפקיד כל משימה באשכול. אנו מספקים תבנית Kubernetes ברישום מחדש של tensorflow / ecosystem המגדיר את TF_CONFIG למשימות האימונים שלך.

ישנם שני רכיבים של TF_CONFIG: אשכול ומשימה. האשכול מספק מידע על אשכול ההדרכה שהוא דיקט המורכב מסוגים שונים של משרות כמו עובד. בהכשרה רב עובדים יש בדרך כלל עובד אחד שלוקח על עצמו קצת יותר אחריות כמו שמירת מחסום וכתיבת קובץ סיכום עבור TensorBoard בנוסף למה שעובד רגיל עושה. עובד כזה מכונה "העובד הראשי", ונהוג שהעובד עם אינדקס 0 ימונה כעובד הראשי (למעשה כך מיושם tf.distribute.Strategy). משימה מצד שני מספקת מידע על המשימה הנוכחית. אשכול הרכיבים הראשון זהה לכל העובדים, ומשימת הרכיבים השנייה שונה בכל עובד ומציינת את סוג ואינדקס של אותו עובד.

דוגמה אחת ל TF_CONFIG היא:

 os.environ["TF_CONFIG"] = json.dumps({
    "cluster": {
        "worker": ["host1:port", "host2:port", "host3:port"],
        "ps": ["host4:port", "host5:port"]
    },
   "task": {"type": "worker", "index": 1}
})
 

TF_CONFIG זה מציין שיש שלושה עובדים ושתי משימות ps באשכול יחד עם המארחים TF_CONFIG שלהם. החלק "המשימה" מציין שתפקיד המשימה הנוכחית באשכול, עובד 1 (העובד השני). תפקידים תקפים באשכול הם "ראשי", "עובד", "ps" ו- "מעריך". לא אמורה להיות עבודת "ps" למעט בעת השימוש ב tf.distribute.experimental.ParameterServerStrategy .

מה הלאה?

tf.distribute.Strategy נמצא בפיתוח פעיל. אנו מברכים אותך לנסות ולספק את המשוב שלך באמצעות סוגיות של GitHub .