תקשורת RDMA נקודה-לנקודה עבור מערכות LLM
כלי אלגנטי לטיפול בדפוסי תקשורת מתפתחים של LLM
מערכות LLM הסתמכו באופן מסורתי על תקשורת קולקטיבית עבור דפוסים מקבילים מובנים באימון ובהסקה. עם זאת, עומסי עבודה מתפתחים, כגון הסקה מפורקת, ניתוב Mixture-of-Experts (MoE) ולמידת חיזוק אסינכרונית וכוונון עדין, דורשים את הגמישות של תקשורת נקודה-לנקודה.
המאמר המחקרי האחרון שלנו מציג את TransferEngine, ספריית תקשורת ניידת מבוססת RDMA שפועלת על פני בקרי ממשק רשת (NIC) שונים, כולל NVIDIA ConnectX-7 ו-AWS Elastic Fabric Adapter (EFA). אנו מדגימים את יעילותה בשלוש מערכות ייצור ב-Perplexity AI:
Prefill ו-Decode מפורקים: העברת KvCache שכבה אחר שכבה תוך שימוש בכתיבות עמודים וצופי UVM, המאפשרים למופעי prefill ו-decode לגדול באופן עצמאי.
העברת משקולות עבור אימון-המשך של RL בפחות משנייה אחת: סנכרון משקולות מהיר הבזק בין צמתאי אימון והסקה.
הפעלת מודלים בעלי טריליון פרמטרים ב-AWS EFA: פעולות ניתוב ושילוב MoE עם השהיה נמוכה תוך שימוש ביסודות scatter ו-barrier, המושגות השהיה מתקדמת ביותר ב-ConnectX-7 ואת היישום בר-הקיימא הראשון התואם ל-AWS EFA.
מאמר זה מתמקד ב-TransferEngine עצמו, ספריית התקשורת שמניעה את המערכות הללו. קראו את המאמר המחקרי המלא ב-arXiv וגלו את הקוד ב-GitHub.
מוטיבציה
תקשורת קולקטיבית מצטיינת בדפוסים מקבילים מובנים כגון מקבילות טנזור ומקבילות נתונים, אך היא מטילה מגבלות המגבילות את ישומן על עומסי עבודה מתפתחים:
חברות קבועה: קולקטיבים דורשים הקמת עולם תקשורת גלובלי בזמן האתחול, עם הגדרה מסונכרנת על פני כל המשתתפים. עבור הסקה מפורקת, הדבר בעייתי מכיוון שמספר מופעי ה-prefill וה-decode חייב לגדול באופן דינמי בתגובה לדפוסי תעבורה.
צורות טנסור אחידות: קיבוצים מניחים שכל המשתתפים מחליפים טנסורים בעלי צורות וסוגי נתונים זהים. בניתוב MoE, מספר הטוקנים הנשלחים לכל מומחה תלוי בנתונים ומתברר בזמן ריצה, ולכן קיבוצים dense all-to-all נאלצים לבצע ריפוד לגודלי מאגרי הזיכרון במקרה הגרוע ביותר.
סדר פעולות: ממשקי API קולקטיביים אוכפים סמנטיקה מסונכרנת, גם כאשר יישומים צריכים לדעת רק שקבוצת פעולות הסתיימה. לדוגמה, בהעברת KvCache, כל העמודים ממופע ה-prefill למופע ה-decode יכולים להגיע בכל סדר. באופן דומה, בעדכוני משקולות RL, ניתן להעביר את כל טנזורי המשקולות באופן עצמאי ללא מגבלות סדר.
תקשורת נקודה-לנקודה מבוססת RDMA מטפלת במגבלות אלו באופן טבעי, אך שלושה מחשולים מנעו את אימוצה:
מגבלות ספרייה: בעוד ש-torch.distributed ו-NCCL מספקים ממשקי API נקודה-לנקודה כמו Send/Recv, פעולות אלו עדיין יורשות סמנטיקה ומגבלות קולקטיביות.
מגוון חומרה: בקר רשת NVIDIA ConnectX עם העברת Reliable Connection (RC) הם הגדרת ה-RDMA הנפוצה ביותר, המבטיחה אספקה לפי סדר. ספקי ענן כמו AWS משתמשים בחומרה שונה, Elastic Fabric Adapter (EFA) עם העברת Scalable Reliable Datagram (SRD) המספקת הודעות שלא לפי סדר. EFA דורש גם איגוד של ארבעה בקר רשת של 100 Gbps (או שני בקר רשת של 200 Gbps) כדי להשיג רוחב פס של 400 Gbps.
ניידות תוכנה: ספריות RDMA קיימות סובלות מנעילת ספקים. DeepEP דורש RDMA ביוזמת GPU (IBGDA) הזמין רק ב-ConnectX. NVSHMEM מציג ירידת ביצועים חמורה ב-EFA. ספריות כמו Mooncake ו-NIXL חסרות תמיכה ב-EFA או נשארות בפיתוח מוקדם. כתוצאה מכך, לא קיים פתרון בר-קיימא חוצה-ספקים עבור הסקה של LLM.
TransferEngine
TransferEngine מטפל במכשולים אלו באמצעות ארבע יכולות ליבה:
הנחות תחבורה משותפות: במקום לכוון לבקר רשת ספציפי, TransferEngine מניח רק שהתחבורה הבסיסית אמינה אך אינה ממוינת, כלומר החיתוך של הערבויות המסופקות על ידי תחבורת RC ו-SRD כאחד.
סנכרון ImmCounter: פרימיטיבי חדש להתראת השלמה שפועל בלי להסתמך על סדר ההודעות, ומאפשר לאותו קוד לרוץ הן על תעבורה מסודרת (RC) והן על תעבורה לא מסודרת (SRD).
תמיכה בבקר רשת מרובה: ניהול שקוף של מספר בקר רשת לכל GPU, החלוקה אוטומטית של העברות כדי למקסם את רוחב הפס.
משטח API מינימלי: קבוצה קטנה של פעולות המספיקה לבניית דפוסי תקשורת מורכבים.
TransferEngine יוצר שרשור עובד אחד לכל GPU. כל עובד מנהל DomainGroup המתאם 1 עד 4 בקר רשת של RDMA, בהתאם לפלטפורמת החומרה (ConnectX-7 יחיד מספק 400 Gbps, בעוד ש-EFA דורש ארבעה מתאמי 100 Gbps או שני מתאמי 200 Gbps). בתוך DomainGroup, כל Domain הוא ספציפי לחומרה ומנהל בקר רשת יחיד, המטפל בניהול זוג תורים, הגשת עבודה וסקר השלמה.
כל מופע של TransferEngine חושף כתובת ראשית אחת לזיהוי וגילוי עמיתים. עמיתים מרוחקים מחליפים מבני NetAddr המקודדים כתובות רשת ויכולים להיות מנוסחים לתקשורת מחוץ לערוץ. כל העמיתים במערכת חייבים להשתמש באותו מספר בקר רשת לכל GPU. תצורה אחידה זו מאפשרת ל-TransferEngine לחלק העברות בא inteligente: בעת יזמת העברה, המקור והיעד יודעים בדיוק אילו בקר רשת זמינים, מה שמאפשר איזון עומס אוטומטי על פני כל המתאמים.
עיצוב API
TransferEngine חושף API מינימלי המופשט את המורכבות של ממשקי ה-RDMA הבסיסיים.
#[serde] struct NetAddr(Bytes);
#[serde] struct MrDesc{ ptr: u64, rkeys: Vec<(NetAddr, u64)> }
struct MrHandle(NonNull<c_void>); type Offset = u64;
struct Pages{ indices: Vec<u32>, stride: u64, offset: Offset }
struct PeerGroupHandle(u64);
struct ScatterDst{ len: u64, src: Offset, dst: (MrDesc,Offset)}
enum OnDone { Callback(fn () -> ()), Flag(Atomic<bool>) }
trait TransferEngine {
fn main_address() -> NetAddr;
// Memory Region Management
fn reg_mr(ptr, len, device) -> (MrHandle, MrDesc);
// Two-sided Send/Recv
fn submit_send(addr: NetAddr, msg: &[u8], cb: fn () -> ());
fn submit_recvs(len: u64, cnt: u64, cb: fn (&[u8]) -> ());
// One-sided Write
fn expect_imm_count(imm: u32, count: u32, cb: fn () -> ());
fn submit_single_write(len: u64, imm: Option<u32>
src: (MrHandle, Offset), dst: (MrDesc, Offset), OnDone);
fn submit_paged_writes(page_len: u64, imm: Option<u32>,
src: (MrHandle, Pages), dst: (MrDesc, Pages), OnDone);
// One-sided Write to a group of peers
fn add_peer_group(addrs: Vec<NetAddr>) -> PeerGroupHandle;
fn submit_scatter(h: Option<PeerGroupHandle>, OnDone,
imm: Option<u32>, src: MrHandle, dst: Vec<ScatterDst>);
fn submit_barrier(h: Option<PeerGroupHandle>, OnDone,
imm: u32, dst: Vec<MrDesc>);
// Watcher for CPU-GPU synchronization
fn alloc_uvm_watcher(cb: fn(u64,u64) -> ()) -> NonNull<u64>;
}ה-API מורכב מכמה רכיבי מפתח:
רישום זיכרון
פעולות RDMA דורשות רישום אזורי זיכרון בבקר הרשת לפני העברת נתונים. פונקציית ה-reg_mr מקבלת מצביע, אורך והתקן, ומחזירה שני ידיות:
MrHandle: ידית מקומית המשמשת כמקור להעברות
MrDesc: תיסורת הניתנת לסידוק שניתן להחליף עם עמיתים מרוחקים כדי לאפשר להם לכתוב לזיכרון הרשום
מבנה ה-MrDesc מכיל רשימה של כתובת רשת מרוחקת ומפתח מרוחק (אחד לכל בקר רשת) המעניקים גישה מרחוק לאזור הזיכרון. עיצוב זה מטפל באופן טבעי בהגדרות מרובות בקר רשת.
Send/Recv דו-צדדי
עבור הודעות קטנות והודעות מישור בקרה בסגנון RPC, TransferEngine מספק submit_send ו-submit_recvs:
submit_send: שולח את ההודעה לעמית מרוחקים המזוהה על ידי NetAddr. ההודעה מועתקת פנימית, כך שהקורא יכול לעשות שימוש חוזר או לשחרר מיד את החוצץ.
submit_recvs: מפרסם מאגר של חוצצי קבלה. כאשר הודעה מגיעה, מופעלת קולבק עם הנתונים שהתקבלו. החוצץ מוחזר אוטומטית למאגר לאחר שהקולבק הושלם.
כתיבה חד-צדדית
עבור העברות נתונים בכמות גדולה, TransferEngine מספק פעולות כתיבה חד-צדדיות ללא העתקה:
submit_single_write: מעביר אזור רציף ממקור ליעד
submit_paged_writes: מעביר עמודים שאינם רציפים המצוינים על ידי אינדקסים, פסיעות והסטות
שתי הפעולות מקבלות ערך מיידי אופציונלי של 32 סיביות המועבר למקבל אטומית עם השלמתו.
המנוע מחלק אוטומטית העברות גדולות על פני מספר בקר רשת כדי למקסם את רוחב הפס.
קבוצות עמיתים ופעולות בכמות גדולה
עבור פעולות המיועדות למספר עמיתים (נפוץ בניתוב MoE), TransferEngine מספק פרימיטיבים ממוטבים:
add_peer_group: רושם מראש קבוצת כתובות עמיתים לחיפוש מהיר
submit_scatter: שולח פרוסות נתונים שונות לכל עמית בקבוצה
submit_barrier: שולח הודעה מיידית בלבד לכל העמיתים (העברת אפס בתים)
פעולות אלו ממוטבות להשהיה נמוכה על ידי שמירת מבני נתונים פנימיים במטמון ויצירת תבניות לבקשות עבודה של RDMA, מה שמונע תקורה חוזרת של הגדרה.
צופה UVM: איתות מ-GPU ל-CPU
אתגר מרכזי במערכות LLM הוא תיאום העברות RDMA עם התקדמות ה-GPU. באופן אידיאלי, העברת הנתונים צריכה להתחיל ברגע שה-GPU מייצר פלט, אך בדיקת ה-GPU מה-CPU אינה יעילה.
TransferEngine מטפל בכך באמצעות צופי UVM: מיקומי זיכרון וירטואלי מאוחדים הניתנים לעדכון על ידי ליבות GPU (אפילו בתוך גרפים של CUDA) ונבדקים ביעילות על ידי ה-CPU באמצעות GDRCopy. כאשר צופה UVM מוקצה, נרשמת קולבק המופעלת בכל פעם שה-GPU מעדכן את הערך.
זה מאפשר דפוסים כמו העברת KvCache שכבה אחר שכבה: לאחר שכל שכבת transformer מסתיימת, ה-GPU מקדם את הצופה, מה שמפעיל העברת RDMA של ה-KvCache של אותה שכבה בזמן ששכבות עוקבות ממשיכות לחשב.
הודעת השלמה
TransferEngine מספק מנגנוני הודעת השלמה עבור צד היוזם (הקורא של submit_write) וצד המקבל (היעד של פעולות כתיבה).
הודעה בצד היוזם: כאשר מוגשת פעולת כתיבה, הקורא יכול לבחור לקבל הודעה עם השלמתה באמצעות פונקציית קולבק או דגל אטומי. הודעה זו מאשרת שהנתונים הועברו בהצלחה מחוצץ המקור.
הודעה בצד המקבל: החדשנות המרכזית היא ה-ImmCounter, רכיב העוקב אחר מונים הממוספרים לפי מספרים מיידיים של 32 סיביות. כאשר פעולת כתיבה נושאת ערך מיידי, ה-ImmCounter של המקבל מקודם אטומית עם אספקת הנתונים. פונקציית ה-expect_imm_count מאפשרת למקבל לרשם קולבק המופעל כאשר ערך מיידי מסוים מגיע לספירת יעד. ניתן לסנכרן את המונים בשקיפות עם ה-GPU באמצעות GDRCopy, לבדוק אותם ישירות, או לנטר אותם דרך קולבקים.
מנגנון צד-מקבל זה מספק סנכרון אמין ללא צורך בסדר הודעות. הוא פועל באותה מידה על בקר רשת ConnectX (המבטיחים אספקה לפי סדר מעל תחבורת RC) ובקר רשת EFA (המספקים הודעות שלא לפי סדר מעל תחבורת SRD).
יישום
TransferEngine מיושם ב-Rust, תוך אופטימיזציה מדוקדקת של הקצאות, שרשורים וסנכרון כדי למזער השהיה ולהשיג תפוקה גבוהה. המנוע יוצר שרשור עובד אחד לכל DomainGroup, כאשר כל אחד מהם נעוץ לליבת CPU בצומת NUMA שאליו מחוברים ההתקנים, וממזער הן את התזמון והן את השהיית גישת הזיכרון. מבני נתונים ספציפיים לתחום מוקצים לאחר הנעיצה כדי להבטיח שהזיכרון שמור בצומת NUMA הנכון. שרשור עובד אחד מטפל בעד 4 Domainים (כאשר כל אחד מנהל בקר רשת יחיד), בעוד ששרשור ייעודי אחר בודק את ה-GPU כדי לעדכן את צופי ה-UVM. תקשורת בין שרשורים מתבצעת דרך תורים ללא נעילה.
ה-API מעביר בקשות אל ה-DomainGroup שמשרת את ההתקן המתאים, כאשר הראשון שבהם משרת גם את המארח. בלולאה הדוקה, עובד ה-domain מבצע polling ל-work חדש, תוך מתן עדיפות לשליחת בקשות חדשות. בהתאם לחומרה ולתצורה, בקשות מחולקות ונעשות להן איזון עומסים בין ה-Domains הזמינים. ה-Write הראשון של בקשה מורכבת נשלח מיידית לתור השליחה של ה-NIC. לאחר שמוצו בקשות חדשות, העובד ממשיך לקדם פעולות ממתינות, ושולח writes כדי למלא את צינור החומרה. לאחר מכן, תורי ההשלמה נבקרים ב-polling כדי לברר אילו העברות הושלמו ואילו הוספות מונה מיידיות בוצעו. אירועים עוברים צבירה כדי לספק התראות לכל העברה, תוך העברת ההעברה לשרשור קריאה חוזרת ייעודי המשותף לכל הקבוצות.
החלוקה בתוך DomainGroup היא גמישה. העברות יכולות לכוון לבקר רשת ספציפיים לפי אינדקס. ניתן לפצל כתיבה יחידה. העברות עמודים, scatter ופעולות מחסום, שכולן מתורגמות לכתיבות מרובות, יכולות לחלק על פני כל בקר הרשת.
אופטימיזציות ספציפיות לחומרה
ה-Domainים בתוך TransferEngine מומחים וממוטבים לחומרה תחת שליטתם:
AWS EFA: יישום ה-EFA משתמש ב-libfabric, ומנהל תחום fabric לכל בקר רשת בתוך ה-DomainGroup. החלטות יישום ואופטימיזציות מרכזיות כוללות:
כתיבות בגודל אפס: EFA סוטה ממפרט ה-RDMA על ידי דרישת תיאורים יעד תקפים גם עבור כתיבות בגודל אפס. לפיכך, TransferEngine אוכף תיאורים תקפים עבור כל ההעברות.
יצירת תבניות לבקשות עבודה: ממלא מראש ושומר את השדות הנפוצים של מבני libfabric לפני הפרסום, מה שמפחית את התקורה לכל פעולה עבור העברות בכמות גדולה ופעולות קבוצת עמיתים
NVIDIA ConnectX-7: יישום ה-ConnectX-7 משתמש ב-libibverbs עם תחבורת RC. החלטות יישום ואופטימיזציות מרכזיות כוללות:
זוגות תורים UD עבור לחיצות ידיים: כל Domain משתמש בזוג תורים של Unreliable Datagram (UD) כדי להחליף לחיצות ידיים של חיבור RC.
זוגות תורים RC נפרדים: יוצר 2 זוגות תורים RC לכל עמית, אחד עבור פעולות Send/Recv דו-צדדיות ואחר עבור פעולות Write ו-WriteImm חד-צדדיות. הפרדה זו נחוצה מכיוון שגם השלמות Recv וגם WriteImm צורכות בקשות עבודה בסדר הפרסום, מה המאפשר סמנטיקה ברמה גבוהה של Recv תוך תמיכה ב-WriteImm ללא הפרעה.
יצירת תבניות לבקשות עבודה: ממלא מראש שדות נפוצים של מבני בקשות עבודה עבור העברות בכמות גדולה וקבוצות עמיתים.
שרשור בקשות עבודה: מקשר עד 4 בקשות עבודה דרך מצביע ה-next של ibv_send_wr, מה שמפחית את מספר צלצולי הפעמון (doorbell) לבקר הרשת.
סדר רפו: מאפשר IBV_ACCESS_RELAXED_ORDERING כדי לאפשר עסקאות PCIe שלא לפי סדר בין בקר הרשת לזיכרון ה-GPU.
סיכום
על ידי מיקוד בחיתוך הערבויות על פני תחבורות RDMA שונות והצגת סנכרון בלתי תלוי בסדר באמצעות ImmCounter, TransferEngine מספק בסיס נייד לתקשורת נקודה-לנקודה במערכות LLM. הספרייה היא קוד פתוח וזמינה ב-GitHub, ואנו מעודדים את הקהילה לחקור ולהבנות על גביה עבור דפוסי תקשורת מתפתחים במערכות למידת מכונה בקנה מידה גדול.
ציטוט
@online{perplexity2025RdmaPointToPointCommunicationForLlmSystems,
author = {{Perplexity} and Nandor Licker and Kevin Hu and Vladimir Zaytsev and Lequn Chen},
title = {{RDMA point-to-point communication for LLM systems}},
date = {2025-11-05},
url = {https://www.perplexity.ai/hub/blog/rdma-point-to-point-communication-for-llm-systems}
}