1use anyhow::Result;
11use std::ffi::c_void;
12use std::ptr;
13use std::sync::atomic::Ordering;
14use std::time::Instant;
15
16use super::{ALL_REDUCE_DTYPE_BYTES, COLLECTIVE_TIMEOUT_SECS, NcclBackend};
17use crate::CommBackend;
18use crate::nccl::{self, NcclDataType, NcclRedOp};
19
20impl CommBackend for NcclBackend {
21 fn all_reduce(&self, ptr: u64, bytes: usize) -> Result<()> {
22 if self.world_size == 2 && self.add_kernel.load(Ordering::Relaxed) != 0 {
23 return self.all_reduce_2rank(ptr, bytes, self.legacy_stream);
24 }
25 let count = bytes / ALL_REDUCE_DTYPE_BYTES;
28 let comm = *self.comm.lock();
29 let result = unsafe {
30 nccl::ncclAllReduce(
31 ptr as *const _,
32 ptr as *mut _,
33 count,
34 NcclDataType::Bfloat16,
35 NcclRedOp::Sum,
36 comm,
37 self.legacy_stream,
38 )
39 };
40 nccl::check_nccl(result, "ncclAllReduce")?;
41 self.check_async_error(comm);
42 Ok(())
43 }
44
45 fn all_reduce_async(&self, ptr: u64, bytes: usize, compute_stream: u64) -> Result<()> {
46 if self.world_size == 2 && self.add_kernel.load(Ordering::Relaxed) != 0 {
47 nccl::record_event(self.compute_done_event, compute_stream)?;
49 nccl::stream_wait_event(self.comm_stream, self.compute_done_event)?;
50
51 self.all_reduce_2rank(ptr, bytes, self.comm_stream)?;
52
53 nccl::record_event(self.comm_done_event, self.comm_stream)?;
54 nccl::stream_wait_event(compute_stream, self.comm_done_event)?;
55 return Ok(());
56 }
57
58 let count = bytes / ALL_REDUCE_DTYPE_BYTES;
60 let comm = *self.comm.lock();
61
62 nccl::record_event(self.compute_done_event, compute_stream)?;
64
65 nccl::stream_wait_event(self.comm_stream, self.compute_done_event)?;
67
68 let result = unsafe {
70 nccl::ncclAllReduce(
71 ptr as *const _,
72 ptr as *mut _,
73 count,
74 NcclDataType::Bfloat16,
75 NcclRedOp::Sum,
76 comm,
77 self.comm_stream,
78 )
79 };
80 nccl::check_nccl(result, "ncclAllReduce (async)")?;
81
82 nccl::record_event(self.comm_done_event, self.comm_stream)?;
84
85 nccl::stream_wait_event(compute_stream, self.comm_done_event)?;
87
88 self.check_async_error(comm);
90
91 Ok(())
92 }
93
94 fn register_buffer(&self, ptr: u64, bytes: usize) -> Result<u64> {
95 let mut handle: *mut c_void = ptr::null_mut();
96 let comm = *self.comm.lock();
97 let result =
98 unsafe { nccl::ncclCommRegister(comm, ptr as *mut c_void, bytes, &mut handle) };
99 nccl::check_nccl(result, "ncclCommRegister")?;
100 self.registered_handles.lock().push(handle);
101 Ok(handle as u64)
102 }
103
104 fn deregister_buffer(&self, handle: u64) -> Result<()> {
105 let comm = *self.comm.lock();
106 let result = unsafe { nccl::ncclCommDeregister(comm, handle as *mut c_void) };
107 nccl::check_nccl(result, "ncclCommDeregister")
108 }
109
110 fn symmetric_alloc(&self, bytes: usize) -> Result<u64> {
111 let ptr = unsafe { nccl::nccl_mem_alloc(bytes) }?;
114 Ok(ptr as u64)
115 }
116
117 fn symmetric_free(&self, ptr: u64) -> Result<()> {
118 unsafe { nccl::nccl_mem_free(ptr as *mut c_void) }
120 }
121
122 fn set_add_kernel(&self, handle: u64) {
123 self.add_kernel.store(handle, Ordering::Relaxed);
124 tracing::info!(
125 "NCCL backend: bf16_add_inplace kernel set \
126 (2-rank send/recv enabled)"
127 );
128 }
129
130 fn all_gather(&self, send_ptr: u64, recv_ptr: u64, bytes: usize) -> Result<()> {
131 let comm = *self.comm.lock();
132 let result = unsafe {
133 nccl::ncclAllGather(
134 send_ptr as *const c_void,
135 recv_ptr as *mut c_void,
136 bytes,
137 NcclDataType::Uint8,
138 comm,
139 self.legacy_stream,
140 )
141 };
142 nccl::check_nccl(result, "ncclAllGather")?;
143 self.check_async_error(comm);
144 Ok(())
145 }
146
147 fn reduce_scatter(&self, send_ptr: u64, recv_ptr: u64, bytes: usize) -> Result<()> {
148 let comm = *self.comm.lock();
149 let result = unsafe {
150 nccl::ncclReduceScatter(
151 send_ptr as *const c_void,
152 recv_ptr as *mut c_void,
153 bytes,
154 NcclDataType::Uint8,
155 NcclRedOp::Sum,
156 comm,
157 self.legacy_stream,
158 )
159 };
160 nccl::check_nccl(result, "ncclReduceScatter")?;
161 self.check_async_error(comm);
162 Ok(())
163 }
164
165 fn broadcast(&self, ptr: u64, bytes: usize, root: usize) -> Result<()> {
166 let start = Instant::now();
167 let comm = *self.comm.lock();
168
169 let result = unsafe {
171 nccl::ncclBroadcast(
172 ptr as *const _,
173 ptr as *mut _,
174 bytes,
175 NcclDataType::Uint8,
176 root as i32,
177 comm,
178 self.legacy_stream,
179 )
180 };
181 nccl::check_nccl(result, "ncclBroadcast")?;
182
183 nccl::sync_stream(self.legacy_stream)?;
185
186 let elapsed = start.elapsed();
187 if elapsed.as_secs() >= COLLECTIVE_TIMEOUT_SECS {
188 tracing::error!(
189 "NCCL broadcast took {:.1}s (threshold: {}s) \
190 — marking communicator unhealthy",
191 elapsed.as_secs_f64(),
192 COLLECTIVE_TIMEOUT_SECS,
193 );
194 self.unhealthy.store(true, Ordering::Release);
195 }
196
197 self.check_async_error(comm);
199
200 Ok(())
201 }
202
203 fn barrier(&self) -> Result<()> {
204 let comm = *self.comm.lock();
205 let result = unsafe {
206 nccl::ncclAllReduce(
207 ptr::null(),
208 ptr::null_mut(),
209 0,
210 NcclDataType::Float32,
211 NcclRedOp::Sum,
212 comm,
213 self.legacy_stream,
214 )
215 };
216 nccl::check_nccl(result, "barrier (ncclAllReduce count=0)")?;
217 self.check_async_error(comm);
218 Ok(())
219 }
220
221 fn send_to(&self, ptr: u64, bytes: usize, dest_rank: usize, stream: u64) -> Result<()> {
222 let comm = *self.comm.lock();
223 let result = unsafe {
224 nccl::ncclSend(
225 ptr as *const c_void,
226 bytes,
227 NcclDataType::Uint8,
228 dest_rank as i32,
229 comm,
230 stream,
231 )
232 };
233 nccl::check_nccl(result, "ncclSend (send_to)")
234 }
235
236 fn recv_from(&self, ptr: u64, bytes: usize, src_rank: usize, stream: u64) -> Result<()> {
237 let comm = *self.comm.lock();
238 let result = unsafe {
239 nccl::ncclRecv(
240 ptr as *mut c_void,
241 bytes,
242 NcclDataType::Uint8,
243 src_rank as i32,
244 comm,
245 stream,
246 )
247 };
248 nccl::check_nccl(result, "ncclRecv (recv_from)")
249 }
250
251 fn group_start(&self) -> Result<()> {
252 let result = unsafe { nccl::ncclGroupStart() };
253 nccl::check_nccl(result, "ncclGroupStart")
254 }
255
256 fn group_end(&self) -> Result<()> {
257 let result = unsafe { nccl::ncclGroupEnd() };
258 nccl::check_nccl(result, "ncclGroupEnd")
259 }
260
261 fn is_healthy(&self) -> bool {
262 if self.unhealthy.load(Ordering::Acquire) {
263 return false;
264 }
265 let comm = *self.comm.lock();
267 self.check_async_error(comm)
268 }
269
270 fn attempt_reconnect(&self) -> Result<()> {
271 self.reconnect_inner()
272 }
273
274 fn rank(&self) -> usize {
275 self.rank
276 }
277
278 fn world_size(&self) -> usize {
279 self.world_size
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 #[allow(clippy::assertions_on_constants)]
289 fn test_collective_timeout_constant() {
290 assert!(COLLECTIVE_TIMEOUT_SECS >= 10);
292 assert!(COLLECTIVE_TIMEOUT_SECS <= 300);
293 }
294
295 }