hyperactor_mesh/
casting.rs1use std::collections::BTreeSet;
12
13use hyperactor::ActorRef;
14use hyperactor::RemoteEndpoint as _;
15use hyperactor::RemoteHandles;
16use hyperactor::RemoteMessage;
17use hyperactor::actor::Referable;
18use hyperactor::config::ENABLE_DEST_ACTOR_REORDERING_BUFFER;
19use hyperactor::context;
20use hyperactor::mailbox;
21use hyperactor::mailbox::MailboxSenderError;
22use hyperactor::mailbox::MessageEnvelope;
23use hyperactor::mailbox::Undeliverable;
24use hyperactor_config::Flattrs;
25use hyperactor_config::attrs::declare_attrs;
26use ndslice::Selection;
27use ndslice::Shape;
28use ndslice::ShapeError;
29use ndslice::SliceError;
30use ndslice::reshape::Limit;
31use ndslice::reshape::ReshapeError;
32use ndslice::reshape::ReshapeSliceExt;
33use ndslice::reshape::reshape_selection;
34use ndslice::selection;
35use ndslice::selection::EvalOpts;
36use ndslice::selection::ReifySlice;
37use ndslice::selection::normal;
38
39use crate::CommActor;
40use crate::comm::ENABLE_NATIVE_V1_CASTING;
41use crate::comm::multicast::CAST_ORIGINATING_SENDER;
42use crate::comm::multicast::CastMessage;
43use crate::comm::multicast::CastMessageEnvelope;
44use crate::comm::multicast::Uslice;
45use crate::config::MAX_CAST_DIMENSION_SIZE;
46use crate::mesh_id::ActorMeshId;
47use crate::metrics;
48
49pub(crate) fn v1_casting_enabled() -> bool {
52 let enabled = hyperactor_config::global::get(ENABLE_NATIVE_V1_CASTING);
53 if enabled {
54 assert!(
55 hyperactor_config::global::get(ENABLE_DEST_ACTOR_REORDERING_BUFFER),
56 "native V1 casting requires ENABLE_DEST_ACTOR_REORDERING_BUFFER to be enabled",
57 );
58 }
59 enabled
60}
61
62declare_attrs! {
63 pub attr CAST_ACTOR_MESH_ID: ActorMeshId;
67}
68
69pub fn update_undeliverable_envelope_for_casting(
73 mut envelope: Undeliverable<MessageEnvelope>,
74) -> Undeliverable<MessageEnvelope> {
75 let Some(message) = envelope.as_message_mut() else {
76 return envelope;
77 };
78 let old_actor = message.sender().clone();
79 if let Some(actor_id) = message.headers().get(CAST_ORIGINATING_SENDER) {
80 tracing::debug!(
81 actor_id = %old_actor,
82 "remapped comm-actor id to id from CAST_ORIGINATING_SENDER {}", actor_id
83 );
84 message.update_sender(actor_id);
85 }
86 envelope
88}
89
90#[allow(clippy::result_large_err)] #[tracing::instrument(level = "debug", skip_all)]
98pub(crate) fn actor_mesh_cast<A, M>(
99 cx: &impl context::Actor,
100 actor_mesh_id: ActorMeshId,
101 comm_actor_ref: &ActorRef<CommActor>,
102 selection_of_root: Selection,
103 root_mesh_shape: &Shape,
104 cast_mesh_shape: &Shape,
105 message: M,
106 caller_headers: &Flattrs,
107) -> Result<(), CastError>
108where
109 A: Referable + RemoteHandles<M>,
110 M: RemoteMessage,
111{
112 let _ = metrics::ACTOR_MESH_CAST_DURATION.start(hyperactor::kv_pairs!(
113 "message_type" => M::typename(),
114 "message_variant" => message.arm().unwrap_or_default(),
115 ));
116
117 let mut headers = caller_headers.clone();
121 mailbox::headers::set_send_timestamp(&mut headers);
122 mailbox::headers::set_rust_message_type::<M>(&mut headers);
123 headers.set(CAST_ACTOR_MESH_ID, actor_mesh_id.clone());
124 let message = CastMessageEnvelope::new::<A, M>(
125 actor_mesh_id.clone(),
126 cx.mailbox().actor_addr().clone(),
127 cast_mesh_shape.clone(),
128 headers,
129 message,
130 )?;
131
132 let slice_of_root = root_mesh_shape.slice();
150
151 let max_cast_dimension_size = hyperactor_config::global::get(MAX_CAST_DIMENSION_SIZE);
152
153 let slice_of_cast = slice_of_root.reshape_with_limit(Limit::from(max_cast_dimension_size));
154
155 let selection_of_cast =
156 reshape_selection(selection_of_root, root_mesh_shape.slice(), &slice_of_cast)?;
157
158 let cast_message = CastMessage {
159 dest: Uslice {
160 slice: slice_of_cast,
161 selection: selection_of_cast,
162 },
163 message,
164 };
165
166 let mut headers = caller_headers.clone();
170 headers.set(CAST_ACTOR_MESH_ID, actor_mesh_id);
171
172 comm_actor_ref
173 .port()
174 .post_with_headers(cx, headers, cast_message);
175
176 Ok(())
177}
178
179#[allow(clippy::result_large_err)] pub(crate) fn cast_to_sliced_mesh<A, M>(
181 cx: &impl context::Actor,
182 actor_mesh_id: ActorMeshId,
183 comm_actor_ref: &ActorRef<CommActor>,
184 sel_of_sliced: &Selection,
185 message: M,
186 sliced_shape: &Shape,
187 root_mesh_shape: &Shape,
188 caller_headers: &Flattrs,
189) -> Result<(), CastError>
190where
191 A: Referable + RemoteHandles<M>,
192 M: RemoteMessage,
193{
194 let root_slice = root_mesh_shape.slice();
195
196 let sel_of_root = if selection::normalize(sel_of_sliced) == normal::NormalizedSelection::True {
198 root_slice.reify_slice(sliced_shape.slice())?
200 } else {
201 let ranks = sel_of_sliced
203 .eval(&EvalOpts::strict(), sliced_shape.slice())?
204 .collect::<BTreeSet<_>>();
205 Selection::of_ranks(root_slice, &ranks)?
206 };
207
208 actor_mesh_cast::<A, M>(
210 cx,
211 actor_mesh_id,
212 comm_actor_ref,
213 sel_of_root,
214 root_mesh_shape,
215 sliced_shape,
216 message,
217 caller_headers,
218 )
219}
220
221#[derive(Debug, thiserror::Error)]
223pub enum CastError {
224 #[error("invalid selection {0}: {1}")]
225 InvalidSelection(Selection, ShapeError),
226
227 #[error("send on rank {0}: {1}")]
228 MailboxSenderError(usize, MailboxSenderError),
229
230 #[error("unsupported selection: {0}")]
231 SelectionNotSupported(String),
232
233 #[error(transparent)]
234 RootMailboxSenderError(#[from] MailboxSenderError),
235
236 #[error(transparent)]
237 ShapeError(#[from] ShapeError),
238
239 #[error(transparent)]
240 SliceError(#[from] SliceError),
241
242 #[error(transparent)]
243 SerializationEncodeError(#[from] bincode::error::EncodeError),
244
245 #[error(transparent)]
246 SerializationDecodeError(#[from] bincode::error::DecodeError),
247
248 #[error(transparent)]
249 Other(#[from] anyhow::Error),
250
251 #[error(transparent)]
252 ReshapeError(#[from] ReshapeError),
253}