1#[cfg(feature = "collectives")]
11mod replicated;
12#[cfg(feature = "collectives")]
13#[allow(unused_imports)]
14pub use replicated::ReplicatedDataWorker;
15
16use std::collections::HashMap;
17use std::sync::{Arc, RwLock};
18
19#[cfg(feature = "nccl")]
20use cudarc::driver::CudaEvent;
21use derive_builder::Builder;
22use futures::future::BoxFuture;
23
24use crate::object::ObjectBlockOps;
25use kvbm_physical::layout::PhysicalLayout;
26use kvbm_physical::{
27 manager::{SerializedLayout, TransferManager},
28 transfer::{BounceBuffer, TransferOptions, context::TransferCompleteNotification},
29};
30
31use super::*;
32
33#[derive(Builder)]
74#[builder(pattern = "owned")]
75pub struct PhysicalWorker {
76 manager: TransferManager,
81
82 #[builder(default, setter(strip_option))]
85 g1_handle: Option<LayoutHandle>,
86
87 #[builder(default, setter(strip_option))]
90 g2_handle: Option<LayoutHandle>,
91
92 #[builder(default, setter(strip_option))]
95 g3_handle: Option<LayoutHandle>,
96
97 #[builder(default = "RwLock::new(HashMap::new())")]
108 remote_handles: RwLock<HashMap<(InstanceId, LogicalLayoutHandle), LayoutHandle>>,
109
110 #[builder(default, setter(strip_option))]
116 rank: Option<usize>,
117
118 #[builder(default, setter(strip_option))]
121 object_client: Option<Arc<dyn ObjectBlockOps>>,
122}
123
124impl PhysicalWorker {
125 pub fn builder() -> PhysicalWorkerBuilder {
137 PhysicalWorkerBuilder::default()
138 }
139
140 pub fn rank(&self) -> Option<usize> {
142 self.rank
143 }
144
145 pub fn object_client(&self) -> Option<&Arc<dyn ObjectBlockOps>> {
147 self.object_client.as_ref()
148 }
149
150 pub fn g1_handle(&self) -> Option<LayoutHandle> {
152 self.g1_handle
153 }
154
155 pub fn g2_handle(&self) -> Option<LayoutHandle> {
157 self.g2_handle
158 }
159
160 pub fn g3_handle(&self) -> Option<LayoutHandle> {
162 self.g3_handle
163 }
164
165 pub fn transfer_manager(&self) -> &TransferManager {
167 &self.manager
168 }
169
170 pub fn resolve_layout(&self, logical: LogicalLayoutHandle) -> Result<PhysicalLayout> {
178 use LogicalLayoutHandle::*;
179
180 let physical_handle = match logical {
181 G1 => self.g1_handle(),
182 G2 => self.g2_handle(),
183 G3 => self.g3_handle(),
184 _ => None,
185 }
186 .ok_or_else(|| anyhow::anyhow!("No layout registered for {:?}", logical))?;
187
188 self.manager
189 .get_physical_layout(physical_handle)
190 .ok_or_else(|| {
191 anyhow::anyhow!(
192 "Layout handle {:?} not found in TransferManager",
193 physical_handle
194 )
195 })
196 }
197
198 pub fn create_bounce_buffer(
200 &self,
201 handle: LayoutHandle,
202 block_ids: Vec<BlockId>,
203 ) -> Result<BounceBuffer> {
204 Ok(BounceBuffer::from_handle(handle, block_ids))
205 }
206
207 pub fn export_metadata(&self) -> Result<SerializedLayout> {
213 self.export_metadata_with_logical_types()
214 }
215
216 fn export_metadata_with_logical_types(&self) -> Result<SerializedLayout> {
218 let mut descriptors = Vec::new();
219
220 if let Some(handle) = self.g1_handle() {
222 descriptors.push(
223 self.manager
224 .build_logical_descriptor(handle, LogicalLayoutHandle::G1)?,
225 );
226 }
227 if let Some(handle) = self.g2_handle() {
228 descriptors.push(
229 self.manager
230 .build_logical_descriptor(handle, LogicalLayoutHandle::G2)?,
231 );
232 }
233 if let Some(handle) = self.g3_handle() {
234 descriptors.push(
235 self.manager
236 .build_logical_descriptor(handle, LogicalLayoutHandle::G3)?,
237 );
238 }
239
240 let worker_address = self.manager.worker_address();
242 let nixl_metadata = self.manager.get_nixl_metadata()?;
243
244 SerializedLayout::pack(worker_address, nixl_metadata, descriptors)
245 }
246
247 pub fn import_metadata(&self, metadata: SerializedLayout) -> Result<Vec<LayoutHandle>> {
249 self.manager.import_metadata(metadata)
250 }
251
252 #[cfg(feature = "nccl")]
277 pub fn execute_local_layerwise_onboard(
278 &self,
279 src_block_ids: &[BlockId],
280 dst_block_ids: &[BlockId],
281 layer_events: &[Arc<CudaEvent>],
282 ) -> Result<()> {
283 if src_block_ids.len() != dst_block_ids.len() {
285 return Err(anyhow::anyhow!(
286 "Block ID length mismatch: src={}, dst={}",
287 src_block_ids.len(),
288 dst_block_ids.len()
289 ));
290 }
291
292 let g2_handle = self
294 .g2_handle()
295 .ok_or_else(|| anyhow::anyhow!("G2 layout not registered"))?;
296 let g1_handle = self
297 .g1_handle()
298 .ok_or_else(|| anyhow::anyhow!("G1 layout not registered"))?;
299
300 let g2_config = self.manager.get_layout_config(g2_handle)?;
302 let num_layers = g2_config.num_layers;
303
304 if layer_events.len() != num_layers {
306 return Err(anyhow::anyhow!(
307 "layer_events length ({}) doesn't match num_layers ({})",
308 layer_events.len(),
309 num_layers
310 ));
311 }
312
313 let stream = self.manager.context().acquire_h2d_stream();
315
316 tracing::debug!(
317 num_layers,
318 num_blocks = src_block_ids.len(),
319 "Starting layer-wise onboard from G2 to G1"
320 );
321
322 for layer in 0..num_layers {
324 let options = TransferOptions::builder()
326 .layer_range(layer..layer + 1)
327 .cuda_stream(stream.clone())
328 .build()?;
329
330 self.manager.execute_transfer(
331 g2_handle,
332 src_block_ids,
333 g1_handle,
334 dst_block_ids,
335 options,
336 )?;
337
338 layer_events[layer].record(stream.as_ref())?;
340 }
341
342 tracing::debug!(num_layers, "Layer-wise onboard complete - events recorded");
343
344 Ok(())
345 }
346}
347
348impl WorkerTransfers for PhysicalWorker {
349 fn execute_local_transfer(
350 &self,
351 src: LogicalLayoutHandle,
352 dst: LogicalLayoutHandle,
353 src_block_ids: Arc<[BlockId]>,
354 dst_block_ids: Arc<[BlockId]>,
355 options: TransferOptions,
356 ) -> Result<TransferCompleteNotification> {
357 use LogicalLayoutHandle::*;
358
359 let src_layout = match &src {
360 G1 => self.g1_handle(),
361 G2 => self.g2_handle(),
362 G3 => self.g3_handle(),
363 G4 => return Err(anyhow::anyhow!("G4 is not supported for local transfers")),
364 }
365 .ok_or_else(|| anyhow::anyhow!("Source layout not registered: {:?}", src))?;
366
367 let dst_layout = match &dst {
368 G1 => self.g1_handle(),
369 G2 => self.g2_handle(),
370 G3 => self.g3_handle(),
371 G4 => return Err(anyhow::anyhow!("G4 is not supported for local transfers")),
372 }
373 .ok_or_else(|| anyhow::anyhow!("Destination layout not registered: {:?}", dst))?;
374
375 self.manager.execute_transfer(
376 src_layout,
377 &src_block_ids,
378 dst_layout,
379 &dst_block_ids,
380 options,
381 )
382 }
383
384 fn execute_remote_onboard(
385 &self,
386 src: RemoteDescriptor,
387 dst: LogicalLayoutHandle,
388 dst_block_ids: Arc<[BlockId]>,
389 options: TransferOptions,
390 ) -> Result<TransferCompleteNotification> {
391 use LogicalLayoutHandle::*;
392
393 let dst_layout = match &dst {
394 G1 => self.g1_handle(),
395 G2 => self.g2_handle(),
396 G3 => self.g3_handle(),
397 G4 => return Err(anyhow::anyhow!("G4 is not supported for remote transfers")),
398 }
399 .ok_or_else(|| anyhow::anyhow!("Destination layout not registered: {:?}", dst))?;
400
401 match src {
402 RemoteDescriptor::Layout { handle, block_ids } => {
403 let block_ids_arc: Arc<[BlockId]> = block_ids.into();
405 self.manager.execute_transfer(
406 handle,
407 &block_ids_arc,
408 dst_layout,
409 &dst_block_ids,
410 options,
411 )
412 }
413 RemoteDescriptor::Object { keys } => {
414 let object_client = self
416 .object_client
417 .as_ref()
418 .ok_or_else(|| anyhow::anyhow!("Object client not configured"))?
419 .clone();
420
421 let dst_physical = self.resolve_layout(dst)?;
423 let block_ids_vec: Vec<BlockId> = dst_block_ids.to_vec();
424
425 let ctx = self.manager.context();
427 let event = ctx.event_system().new_event()?;
428 let handle = event.handle();
429 let awaiter = ctx.event_system().awaiter(handle)?;
430
431 ctx.tokio().spawn(async move {
433 let results = object_client
434 .get_blocks_with_layout(keys.clone(), dst_physical, block_ids_vec)
435 .await;
436
437 let failed: Vec<_> = results.iter().filter(|r| r.is_err()).collect();
439 if failed.is_empty() {
440 let _ = event.trigger();
441 } else {
442 let error_msg = format!(
443 "{} of {} blocks failed to download",
444 failed.len(),
445 results.len()
446 );
447 let _ = event.poison(error_msg);
448 }
449 });
450
451 Ok(TransferCompleteNotification::from_awaiter(awaiter))
452 }
453 }
454 }
455
456 fn execute_remote_offload(
457 &self,
458 src: LogicalLayoutHandle,
459 src_block_ids: Arc<[BlockId]>,
460 dst: RemoteDescriptor,
461 _options: TransferOptions,
462 ) -> Result<TransferCompleteNotification> {
463 match dst {
464 RemoteDescriptor::Layout { handle, block_ids } => {
465 let src_layout = match &src {
467 LogicalLayoutHandle::G1 => self.g1_handle(),
468 LogicalLayoutHandle::G2 => self.g2_handle(),
469 LogicalLayoutHandle::G3 => self.g3_handle(),
470 LogicalLayoutHandle::G4 => {
471 return Err(anyhow::anyhow!("G4 cannot be used as source for offload"));
472 }
473 }
474 .ok_or_else(|| anyhow::anyhow!("Source layout not registered: {:?}", src))?;
475
476 let block_ids_arc: Arc<[BlockId]> = block_ids.into();
477 self.manager.execute_transfer(
478 src_layout,
479 &src_block_ids,
480 handle,
481 &block_ids_arc,
482 _options,
483 )
484 }
485 RemoteDescriptor::Object { keys } => {
486 let object_client = self
488 .object_client
489 .as_ref()
490 .ok_or_else(|| anyhow::anyhow!("Object client not configured"))?
491 .clone();
492
493 let src_physical = self.resolve_layout(src)?;
495 let block_ids_vec: Vec<BlockId> = src_block_ids.to_vec();
496
497 let ctx = self.manager.context();
499 let event = ctx.event_system().new_event()?;
500 let handle = event.handle();
501 let awaiter = ctx.event_system().awaiter(handle)?;
502
503 ctx.tokio().spawn(async move {
505 let results = object_client
506 .put_blocks_with_layout(keys.clone(), src_physical, block_ids_vec)
507 .await;
508
509 let failed: Vec<_> = results.iter().filter(|r| r.is_err()).collect();
511 if failed.is_empty() {
512 let _ = event.trigger();
513 } else {
514 let error_msg = format!(
515 "{} of {} blocks failed to upload",
516 failed.len(),
517 results.len()
518 );
519 let _ = event.poison(error_msg);
520 }
521 });
522
523 Ok(TransferCompleteNotification::from_awaiter(awaiter))
524 }
525 }
526 }
527
528 fn connect_remote(
529 &self,
530 instance_id: InstanceId,
531 metadata: Vec<SerializedLayout>,
532 ) -> Result<ConnectRemoteResponse> {
533 if metadata.len() != 1 {
535 anyhow::bail!(
536 "PhysicalWorker expects exactly 1 metadata item, got {}",
537 metadata.len()
538 );
539 }
540 let meta = metadata.into_iter().next().unwrap();
541
542 let unpacked = meta.unpack()?;
544
545 {
547 let mut handles = self.remote_handles.write().unwrap();
548 for descriptor in &unpacked.layouts {
549 handles.insert((instance_id, descriptor.logical_type), descriptor.handle);
550 }
551 }
552
553 let repacked = SerializedLayout::pack(
555 unpacked.worker_address,
556 unpacked.nixl_metadata,
557 unpacked.layouts,
558 )?;
559 self.manager.import_metadata(repacked)?;
560
561 Ok(ConnectRemoteResponse::ready())
562 }
563
564 fn has_remote_metadata(&self, instance_id: InstanceId) -> bool {
565 let handles = self.remote_handles.read().unwrap();
566 handles.keys().any(|(id, _)| *id == instance_id)
567 }
568
569 fn execute_remote_onboard_for_instance(
570 &self,
571 instance_id: InstanceId,
572 remote_logical_type: LogicalLayoutHandle,
573 src_block_ids: Vec<BlockId>,
574 dst: LogicalLayoutHandle,
575 dst_block_ids: Arc<[BlockId]>,
576 options: TransferOptions,
577 ) -> Result<TransferCompleteNotification> {
578 let handles = self.remote_handles.read().unwrap();
579 let remote_handle = handles
580 .get(&(instance_id, remote_logical_type))
581 .ok_or_else(|| {
582 anyhow::anyhow!(
583 "No remote {:?} handle for instance {}",
584 remote_logical_type,
585 instance_id
586 )
587 })?;
588
589 let descriptor = RemoteDescriptor::Layout {
590 handle: *remote_handle,
591 block_ids: src_block_ids,
592 };
593
594 self.execute_remote_onboard(descriptor, dst, dst_block_ids, options)
595 }
596}
597
598impl Worker for PhysicalWorker {
599 fn g1_handle(&self) -> Option<LayoutHandle> {
600 self.g1_handle
601 }
602
603 fn g2_handle(&self) -> Option<LayoutHandle> {
604 self.g2_handle
605 }
606
607 fn g3_handle(&self) -> Option<LayoutHandle> {
608 self.g3_handle
609 }
610
611 fn export_metadata(&self) -> Result<SerializedLayoutResponse> {
612 self.export_metadata_with_logical_types()
614 .map(SerializedLayoutResponse::ready)
615 }
616
617 fn import_metadata(&self, metadata: SerializedLayout) -> Result<ImportMetadataResponse> {
618 self.manager
619 .import_metadata(metadata)
620 .map(ImportMetadataResponse::ready)
621 }
622}
623
624impl ObjectBlockOps for PhysicalWorker {
625 fn has_blocks(
626 &self,
627 keys: Vec<SequenceHash>,
628 ) -> BoxFuture<'static, Vec<(SequenceHash, Option<usize>)>> {
629 if let Some(client) = self.object_client.as_ref() {
631 client.has_blocks(keys)
632 } else {
633 Box::pin(async move { keys.into_iter().map(|k| (k, None)).collect() })
635 }
636 }
637
638 fn put_blocks(
639 &self,
640 keys: Vec<SequenceHash>,
641 src_layout: LogicalLayoutHandle,
642 block_ids: Vec<BlockId>,
643 ) -> BoxFuture<'static, Vec<Result<SequenceHash, SequenceHash>>> {
644 let physical_layout = match self.resolve_layout(src_layout) {
646 Ok(layout) => layout,
647 Err(e) => {
648 tracing::error!(?src_layout, error = %e, "Failed to resolve layout for put_blocks");
649 return Box::pin(async move { keys.into_iter().map(Err).collect() });
650 }
651 };
652
653 if let Some(client) = self.object_client.as_ref() {
655 client.put_blocks_with_layout(keys, physical_layout, block_ids)
656 } else {
657 tracing::warn!("put_blocks called but no object client configured");
659 Box::pin(async move { keys.into_iter().map(Err).collect() })
660 }
661 }
662
663 fn get_blocks(
664 &self,
665 keys: Vec<SequenceHash>,
666 dst_layout: LogicalLayoutHandle,
667 block_ids: Vec<BlockId>,
668 ) -> BoxFuture<'static, Vec<Result<SequenceHash, SequenceHash>>> {
669 let physical_layout = match self.resolve_layout(dst_layout) {
671 Ok(layout) => layout,
672 Err(e) => {
673 tracing::error!(?dst_layout, error = %e, "Failed to resolve layout for get_blocks");
674 return Box::pin(async move { keys.into_iter().map(Err).collect() });
675 }
676 };
677
678 if let Some(client) = self.object_client.as_ref() {
680 client.get_blocks_with_layout(keys, physical_layout, block_ids)
681 } else {
682 tracing::warn!("get_blocks called but no object client configured");
684 Box::pin(async move { keys.into_iter().map(Err).collect() })
685 }
686 }
687}