1use std::cell::OnceCell;
10use std::collections::HashMap;
11use std::collections::HashSet;
12use std::collections::hash_map::Entry;
13use std::future::Future;
14use std::sync::Arc;
15use std::sync::OnceLock;
16use std::time::Duration;
17
18use anyhow::Context as _;
19use anyhow::Result;
20use anyhow::anyhow;
21use anyhow::bail;
22use anyhow::ensure;
23use async_trait::async_trait;
24use hyperactor as reference;
25use hyperactor::Actor;
26use hyperactor::Context;
27use hyperactor::Endpoint as _;
28use hyperactor::HandleClient;
29use hyperactor::Handler;
30use hyperactor::Instance;
31use hyperactor::PortHandle;
32use hyperactor::actor::ActorHandle;
33use hyperactor::handle;
34use hyperactor::id::Label;
35use hyperactor::mailbox::OncePortHandle;
36use hyperactor::mailbox::PortReceiver;
37use hyperactor::proc::Proc;
38use hyperactor::runtime_identity::RuntimeKind;
39use hyperactor::runtime_identity::tag_current_thread;
40use monarch_gil::GilSite;
41use monarch_gil::monarch_with_gil_blocking;
42use monarch_hyperactor::actor::PythonMessage;
43use monarch_hyperactor::actor::PythonMessageKind;
44use monarch_hyperactor::local_state_broker::BrokerId;
45use monarch_hyperactor::local_state_broker::LocalState;
46use monarch_hyperactor::local_state_broker::LocalStateBrokerMessage;
47use monarch_hyperactor::pickle::pickle;
48use monarch_messages::controller::ControllerMessageClient;
49use monarch_messages::controller::Seq;
50use monarch_messages::controller::WorkerError;
51use monarch_messages::worker::ActorCallParams;
52use monarch_messages::worker::ActorMethodParams;
53use monarch_messages::worker::ArgsKwargs;
54use monarch_messages::worker::CallFunctionError;
55use monarch_messages::worker::CallFunctionParams;
56use monarch_messages::worker::SeqError;
57use monarch_messages::worker::StreamRef;
58use monarch_types::PyTree;
59use monarch_types::SerializablePyErr;
60use monarch_types::TryIntoPyObjectUnsafe;
61use pyo3::prelude::*;
62use tokio::runtime::Handle;
63use tokio::sync::Mutex;
64use tokio::task::JoinHandle;
65use torch_sys_cuda::cuda::Event;
66use torch_sys_cuda::cuda::Stream;
67use torch_sys2::CloneUnsafe;
68use torch_sys2::CudaDevice;
69use torch_sys2::TensorCell;
70use torch_sys2::deep_clone;
71use torch_sys2::factory_empty;
72use torch_sys2::factory_zeros;
73use tracing_subscriber::fmt::Subscriber;
74use typeuri::Named;
75
76use crate::ControllerActor;
77use crate::DeviceMesh;
78use crate::Factory;
79use crate::Reduction;
80use crate::Ref;
81use crate::ResolvableFunction;
82use crate::StreamCreationMode;
83use crate::WireValue;
84use crate::comm::CommMessage;
85use crate::comm::CommMessageClient;
86use crate::comm::NcclCommActor;
87
88pub type TensorCellResult = Result<TensorCell, Arc<SeqError>>;
89
90thread_local! {
92 pub static CONTROLLER_ACTOR_REF: OnceCell<reference::ActorRef<ControllerActor>> = const { OnceCell::new() };
93 pub static PROC: OnceCell<Proc> = const { OnceCell::new() };
94 pub static ROOT_ACTOR_ID: OnceCell<reference::ActorId> = const { OnceCell::new() };
95}
96
97fn pickle_python_result(
98 py: Python<'_>,
99 result: Bound<'_, PyAny>,
100 worker_rank: usize,
101) -> Result<PythonMessage, anyhow::Error> {
102 let mut state = pickle(py, result.unbind(), false, false)
103 .map_err(|pyerr| anyhow::Error::from(SerializablePyErr::from(py, &pyerr)))?;
104 let inner = state
105 .take_inner()
106 .map_err(|pyerr| anyhow::Error::from(SerializablePyErr::from(py, &pyerr)))?;
107 Ok(PythonMessage::new_from_buf(
108 PythonMessageKind::Result {
109 rank: Some(worker_rank),
110 },
111 inner.take_buffer(),
112 ))
113}
114
115#[derive(Debug)]
116struct Recording {
117 messages: Vec<StreamMessage>,
118}
119
120impl Recording {
121 fn new() -> Self {
122 Self {
123 messages: Vec::new(),
124 }
125 }
126}
127
128#[derive(Debug, PartialEq)]
129enum RecordingState {
130 Defining {
131 recording: Ref,
132 defined_borrows: HashSet<u64>,
135 },
136 Running,
137}
138
139#[derive(Handler, HandleClient, Debug, Named)]
142pub enum StreamMessage {
143 CallFunction(
144 CallFunctionParams,
145 HashMap<Ref, DeviceMesh>,
146 HashMap<Ref, (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>)>,
147 ),
148
149 BorrowCreate {
150 borrow: u64,
152 tensor: Ref,
154 first_use_sender: PortHandle<(Option<Event>, TensorCellResult)>,
157 },
158
159 BorrowFirstUse {
160 borrow: u64,
162 result: Ref,
164 first_use_receiver: Arc<Mutex<PortReceiver<(Option<Event>, TensorCellResult)>>>,
167 },
168
169 BorrowLastUse {
170 borrow: u64,
172 result: Ref,
174 last_use_sender: PortHandle<(Option<Event>, TensorCellResult)>,
176 },
177
178 BorrowDrop {
179 borrow: u64,
180 last_use_receiver: Arc<Mutex<PortReceiver<(Option<Event>, TensorCellResult)>>>,
182 },
183
184 DeleteRefs(Vec<Ref>),
185
186 RequestStatus(#[reply] OncePortHandle<()>),
187
188 InitComm(ActorHandle<NcclCommActor>),
189
190 Reduce {
191 comm: Arc<ActorHandle<NcclCommActor>>,
192 dim_size: i64,
193 result: Ref,
194 local_tensor: Ref,
195 factory: Factory,
196 reduction: Reduction,
197 scatter: bool,
198 in_place: bool,
199 out: Option<Ref>,
200 },
201
202 SendTensor {
203 result: Ref,
204 from_rank: Option<usize>,
205 to_rank: Option<usize>,
206 tensor: Ref,
207 factory: Factory,
208 comm: Option<Arc<ActorHandle<NcclCommActor>>>,
209 },
210
211 SendValue {
212 seq: Seq,
213 worker_actor_id: reference::ActorAddr,
214 mutates: Vec<Ref>,
215 function: Option<ResolvableFunction>,
216 args_kwargs: ArgsKwargs,
217 device_meshes: HashMap<Ref, DeviceMesh>,
218 },
219
220 DefineRecording {
221 recording: Ref,
222 },
223
224 FinalizeRecording {
225 recording: Ref,
226 },
227
228 CallRecording {
229 seq: Seq,
230 recording: Ref,
231 results: Vec<Ref>,
232 actuals: Vec<Ref>,
233 },
234
235 RecordingFormal {
236 result: Ref,
237 argument_index: usize,
238 },
239
240 RecordingResult {
241 result: Ref,
242 output_index: usize,
243 },
244
245 SetRefUnitTestsOnly(Ref, WireValue),
246
247 SetTensorRefUnitTestsOnly(Ref, TensorCellResult),
248
249 GetRefUnitTestsOnly(
250 Ref, #[reply] OncePortHandle<Option<Result<WireValue, String>>>,
252 ),
253
254 GetTensorRefUnitTestsOnly(Ref, #[reply] OncePortHandle<Option<TensorCellResult>>),
255
256 SendResultOfActorCall(ActorCallParams),
257 CallActorMethod(ActorMethodParams),
258}
259
260impl StreamMessage {
261 fn clone_for_recording(&self) -> Self {
262 match self {
263 StreamMessage::RecordingFormal {
264 result,
265 argument_index,
266 } => StreamMessage::RecordingFormal {
267 result: *result,
268 argument_index: *argument_index,
269 },
270 StreamMessage::RecordingResult {
271 result,
272 output_index,
273 } => StreamMessage::RecordingResult {
274 result: *result,
275 output_index: *output_index,
276 },
277 StreamMessage::DeleteRefs(refs) => StreamMessage::DeleteRefs(refs.clone()),
278 StreamMessage::CallFunction(params, device_meshes, remote_process_groups) => {
279 StreamMessage::CallFunction(
280 params.clone(),
281 device_meshes.clone(),
282 remote_process_groups.clone(),
283 )
284 }
285 StreamMessage::BorrowCreate {
286 borrow,
287 tensor,
288 first_use_sender,
289 } => StreamMessage::BorrowCreate {
290 borrow: *borrow,
291 tensor: *tensor,
292 first_use_sender: first_use_sender.clone(),
293 },
294 StreamMessage::BorrowFirstUse {
295 borrow,
296 result,
297 first_use_receiver,
298 } => StreamMessage::BorrowFirstUse {
299 borrow: *borrow,
300 result: *result,
301 first_use_receiver: first_use_receiver.clone(),
302 },
303 StreamMessage::BorrowLastUse {
304 borrow,
305 result,
306 last_use_sender,
307 } => StreamMessage::BorrowLastUse {
308 borrow: *borrow,
309 result: *result,
310 last_use_sender: last_use_sender.clone(),
311 },
312 StreamMessage::BorrowDrop {
313 borrow,
314 last_use_receiver,
315 } => StreamMessage::BorrowDrop {
316 borrow: *borrow,
317 last_use_receiver: last_use_receiver.clone(),
318 },
319 StreamMessage::Reduce {
320 comm,
321 dim_size,
322 result,
323 local_tensor,
324 factory,
325 reduction,
326 scatter,
327 in_place,
328 out,
329 } => StreamMessage::Reduce {
330 comm: comm.clone(),
331 dim_size: *dim_size,
332 result: *result,
333 local_tensor: *local_tensor,
334 factory: factory.clone(),
335 reduction: reduction.clone(),
336 scatter: *scatter,
337 in_place: *in_place,
338 out: *out,
339 },
340 StreamMessage::SendTensor {
341 result,
342 from_rank,
343 to_rank,
344 tensor,
345 factory,
346 comm,
347 } => StreamMessage::SendTensor {
348 result: *result,
349 from_rank: *from_rank,
350 to_rank: *to_rank,
351 tensor: *tensor,
352 factory: factory.clone(),
353 comm: comm.clone(),
354 },
355 other => panic!(
356 "StreamMessage variant not supported in recording: {:?}",
357 other
358 ),
359 }
360 }
361
362 fn get_defined_refs(&self) -> HashSet<Ref> {
364 match self {
365 StreamMessage::RecordingFormal { result, .. } => HashSet::from([*result]),
366 StreamMessage::CallFunction(params, ..) => {
367 params.results.iter().filter_map(|&ref_| ref_).collect()
368 }
369 StreamMessage::BorrowFirstUse { result, .. } => HashSet::from([*result]),
370 StreamMessage::Reduce { result, .. } => HashSet::from([*result]),
371 StreamMessage::SendTensor {
372 result, from_rank, ..
373 } => {
374 if from_rank.is_some() {
375 HashSet::from([*result])
376 } else {
377 HashSet::new()
378 }
379 }
380 _ => HashSet::new(),
382 }
383 }
384
385 fn get_mutated_refs(&self) -> HashSet<Ref> {
387 match self {
388 StreamMessage::CallFunction(params, ..) => HashSet::from_iter(params.mutates.clone()),
389 StreamMessage::Reduce {
390 out,
391 in_place,
392 local_tensor,
393 ..
394 } => {
395 if *in_place {
396 HashSet::from([*local_tensor])
397 } else if let Some(out) = out {
398 HashSet::from([*out])
399 } else {
400 HashSet::new()
401 }
402 }
403 _ => HashSet::new(),
405 }
406 }
407}
408
409#[derive(Debug)]
418pub struct StreamActor {
419 _world_size: usize,
420 rank: usize,
421 env: HashMap<Ref, Result<Py<PyAny>, Arc<SeqError>>>,
425 creation_mode: StreamCreationMode,
427 cuda_stream: OnceLock<Option<Stream>>,
433 device: Option<CudaDevice>,
435 comm: Option<ActorHandle<NcclCommActor>>,
437 controller_actor: reference::ActorRef<ControllerActor>,
439 remote_process_groups: HashMap<Ref, Py<PyAny>>,
440 recordings: HashMap<Ref, Recording>,
441 active_recording: Option<RecordingState>,
442 respond_with_python_message: bool,
443 last_seq_error: Option<Arc<SeqError>>,
444}
445
446#[derive(Debug, Clone)]
448pub struct StreamParams {
449 pub world_size: usize,
450 pub rank: usize,
451 pub creation_mode: StreamCreationMode,
453 pub id: StreamRef,
455 pub device: Option<CudaDevice>,
458 pub controller_actor: reference::ActorRef<ControllerActor>,
460 pub respond_with_python_message: bool,
461}
462
463impl StreamActor {
464 pub fn new(
465 StreamParams {
466 world_size,
467 rank,
468 id: _,
469 device,
470 controller_actor,
471 creation_mode,
472 respond_with_python_message,
473 }: StreamParams,
474 ) -> Self {
475 Self {
476 _world_size: world_size,
477 rank,
478 env: HashMap::new(),
479 creation_mode,
480 cuda_stream: OnceLock::new(),
481 device,
482 comm: None,
483 controller_actor,
484 remote_process_groups: HashMap::new(),
485 recordings: HashMap::new(),
486 active_recording: None,
487 respond_with_python_message,
488 last_seq_error: None,
489 }
490 }
491}
492
493#[async_trait]
494impl Actor for StreamActor {
495 async fn init(&mut self, cx: &Instance<Self>) -> Result<()> {
496 CONTROLLER_ACTOR_REF.with(
500 |controller_actor_ref: &OnceCell<reference::ActorRef<ControllerActor>>| {
501 controller_actor_ref.set(self.controller_actor.clone()).ok()
502 },
503 );
504 PROC.with(|proc| proc.set(cx.proc().clone()).ok());
505 ROOT_ACTOR_ID.with(|root_actor_id: &OnceCell<reference::ActorId>| {
506 let root_label = cx
507 .self_addr()
508 .label()
509 .cloned()
510 .unwrap_or_else(|| Label::new("stream").unwrap());
511 root_actor_id
512 .set(reference::ActorId::singleton(
513 root_label,
514 cx.self_addr().proc_addr().id().clone(),
515 ))
516 .ok()
517 });
518 if let Some(stream) = self.cuda_stream()? {
520 Stream::set_current_stream(stream)?;
521 }
522 Ok(())
523 }
524
525 fn spawn_server_task<F>(future: F) -> JoinHandle<F::Output>
531 where
532 F: Future + Send + 'static,
533 F::Output: Send + 'static,
534 {
535 let (join_tx, join_rx) = tokio::sync::oneshot::channel();
536 let builder = std::thread::Builder::new().name("worker-stream".to_string());
544 let _thread_handle = builder.spawn(move || {
545 tag_current_thread(RuntimeKind::DataPlane("stream"));
553 let rt = tokio::runtime::Builder::new_multi_thread()
557 .worker_threads(1)
558 .on_thread_start(|| tag_current_thread(RuntimeKind::DataPlane("stream")))
559 .enable_all()
560 .build()
561 .unwrap();
562 #[allow(clippy::disallowed_methods)]
567 let result = rt.block_on(async {
568 tokio::task::block_in_place(|| {
569 Python::attach(|py| {
574 py.detach(|| {
575 let result = Handle::current().block_on(future);
576 if join_tx.send(result).is_err() {
577 panic!("could not send join result")
578 }
579 })
580 })
581 })
582 });
583 rt.shutdown_timeout(Duration::from_weeks(1));
584 result
585 });
586
587 tokio::spawn(async move { join_rx.await.unwrap() })
590 }
591}
592
593#[derive(Debug)]
595enum PyArg {
596 Object(Py<PyAny>),
597}
598
599impl<'py> TryIntoPyObjectUnsafe<'py, PyAny> for &PyArg {
601 unsafe fn try_to_object_unsafe(self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
602 match self {
603 PyArg::Object(obj) => Ok(obj.clone_ref(py).into_bound(py)),
604 }
605 }
606}
607
608impl StreamActor {
609 fn tensor_to_pyobject(tensor_cell: TensorCell) -> Py<PyAny> {
610 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
611 let tensor = unsafe {
616 tensor_cell.get_unchecked().clone_unsafe()
618 };
619 tensor.into_pyobject(py).unwrap().unbind()
620 })
621 }
622
623 fn pyobject_to_tensor(py: Python<'_>, pyobj: &Py<PyAny>) -> PyResult<TensorCell> {
627 use torch_sys2::Tensor;
628 let tensor = pyobj.bind(py).extract::<Tensor>()?;
629 Ok(TensorCell::new(tensor))
631 }
632
633 fn cuda_stream(&self) -> PyResult<Option<&Stream>> {
634 if self.cuda_stream.get().is_none() {
635 let stream = match self.device {
636 Some(device) => Some(match self.creation_mode {
637 StreamCreationMode::UseDefaultStream => {
638 Stream::get_current_stream_on_device(device)?
639 }
640 StreamCreationMode::CreateNewStream => Stream::new_with_device(device)?,
641 }),
642 None => None,
643 };
644 let _ = self.cuda_stream.set(stream);
646 }
647 Ok(self
648 .cuda_stream
649 .get()
650 .expect("stream initialized above")
651 .as_ref())
652 }
653
654 fn ref_to_pyobject(&self, ref_: &Ref) -> Result<Py<PyAny>, CallFunctionError> {
655 let pyobject = self
656 .env
657 .get(ref_)
658 .ok_or_else(|| CallFunctionError::RefNotFound(*ref_))?;
659 match pyobject {
660 Ok(val) => Ok(val.clone()),
661 Err(err) => Err(CallFunctionError::DependentError(err.clone())),
662 }
663 }
664
665 async fn report_seq_error(
666 &mut self,
667 cx: &Context<'_, Self>,
668 seq: Seq,
669 error: CallFunctionError,
670 ) -> Result<Arc<SeqError>, anyhow::Error> {
671 match error {
672 CallFunctionError::DependentError(root) => Ok(root),
673 CallFunctionError::Error(e) => {
674 if self.active_recording.is_none() {
675 let worker_error = WorkerError {
676 backtrace: format!("{e}"),
677 worker_actor_id: cx.self_addr().clone(),
678 };
679 tracing::info!("Propagating remote function error to client: {worker_error}");
680 self.controller_actor
681 .remote_function_failed(cx, seq, worker_error)
682 .await?
683 }
684 let err = Arc::new(SeqError { seq, error: e });
685 self.last_seq_error = Some(err.clone());
686 Ok(err)
687 }
688 }
689 }
690
691 async fn try_define<F>(
692 &mut self,
693 cx: &Context<'_, Self>,
694 seq: Seq,
695 result_refs: Vec<Option<Ref>>,
696 mutates: &Vec<Ref>,
697 f: F,
698 ) -> Result<()>
699 where
700 F: AsyncFnOnce(&mut Self) -> Result<Vec<Py<PyAny>>, CallFunctionError>,
701 {
702 let actual_results = f(self).await;
703 let op_results = actual_results.and_then(|actual_results| {
706 if result_refs.len() == actual_results.len() {
707 Ok(actual_results
708 .into_iter()
709 .zip(result_refs.iter())
710 .filter_map(|(result, ref_)| ref_.map(|ref_| (ref_, result)))
711 .collect::<Vec<(Ref, Py<PyAny>)>>())
712 } else {
713 Err(CallFunctionError::UnexpectedNumberOfReturns(
714 result_refs.len(),
715 actual_results.len(),
716 ))
717 }
718 });
719
720 match op_results {
723 Ok(op_results) => {
724 for (ref_, pyobject) in op_results.into_iter() {
725 let prev = self.env.insert(ref_, Ok(pyobject));
726 assert!(prev.is_none(), "Duplicate write to reference: {:?}", ref_);
727 }
728 }
729 Err(err) => {
730 let err = self.report_seq_error(cx, seq, err).await?;
731 for ref_ in result_refs {
732 match ref_ {
733 Some(ref_) => {
734 let prev = self.env.insert(ref_, Err(err.clone()));
735 assert!(prev.is_none(), "Duplicate write to reference: {:?}", ref_);
736 }
737 None => {}
738 }
739 }
740 for ref_ in mutates {
741 self.env.insert(*ref_, Err(err.clone()));
742 }
743 }
744 }
745 Ok(())
746 }
747
748 fn call_python_fn<'py>(
749 &mut self,
750 py: Python<'py>,
751 _cx: &Context<Self>,
752 function: Option<ResolvableFunction>,
753 args_kwargs: ArgsKwargs,
754 _mutates: &[Ref],
755 device_meshes: HashMap<Ref, DeviceMesh>,
756 remote_process_groups: HashMap<
757 Ref,
758 (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>),
759 >,
760 ) -> Result<Bound<'py, PyAny>, CallFunctionError> {
761 let (args_tuple, kwargs_dict) = args_kwargs
762 .to_python(py)
763 .map_err(|e| CallFunctionError::Error(e.into()))?;
764 let function = function
765 .map(|function| {
766 function.resolve(py).map_err(|e| {
767 CallFunctionError::InvalidRemoteFunction(format!(
768 "failed to resolve function {}: {}",
769 function,
770 SerializablePyErr::from(py, &e)
771 ))
772 })
773 })
774 .transpose()?;
775
776 let remote_process_groups = remote_process_groups
777 .into_iter()
778 .map(|(gref, (_mesh, _dims, _comm))| {
779 let group = match self.remote_process_groups.entry(gref) {
780 Entry::Occupied(ent) => ent.get().clone_ref(py),
781 Entry::Vacant(_ent) => {
782 panic!("no longer implemented");
783 }
784 };
785 PyResult::Ok((gref, group))
786 })
787 .collect::<Result<HashMap<_, _>, _>>()
788 .map_err(SerializablePyErr::from_fn(py))?;
789
790 let resolve = |val: Bound<'py, PyAny>| {
791 val.extract::<PyTree<Py<PyAny>>>()
792 .map_err(SerializablePyErr::from_fn(py))?
793 .try_into_map(|obj| {
794 Ok(if let Ok(ref_) = Ref::from_py_object(obj.bind(py)) {
795 if let Some(mesh) = device_meshes.get(&ref_) {
796 PyArg::Object(
797 Py::new(py, mesh.clone())
798 .map_err(SerializablePyErr::from_fn(py))?
799 .into(),
800 )
801 } else if let Some(pg) = remote_process_groups.get(&ref_) {
802 PyArg::Object(pg.clone_ref(py))
803 } else {
804 let pyobj = self.ref_to_pyobject(&ref_)?;
805 PyArg::Object(pyobj)
806 }
807 } else {
808 PyArg::Object(obj)
809 })
810 })
811 };
812
813 let py_args: Vec<PyTree<PyArg>> = args_tuple
815 .iter()
816 .map(&resolve)
817 .collect::<Result<_, CallFunctionError>>()?;
818
819 let py_kwargs: HashMap<String, PyTree<PyArg>> = kwargs_dict
820 .iter()
821 .map(|(k, v)| {
822 let key = k
823 .extract::<String>()
824 .map_err(SerializablePyErr::from_fn(py))?;
825 let value = resolve(v)?;
826 Ok((key, value))
827 })
828 .collect::<Result<_, CallFunctionError>>()?;
829
830 let scoped_subscriber = Subscriber::builder().with_writer(std::io::stdout).finish();
833 let result: Bound<'_, PyAny> =
834 tracing::subscriber::with_default(scoped_subscriber, || {
835 let args = unsafe { py_args.try_to_object_unsafe(py) }
844 .map_err(SerializablePyErr::from_fn(py))?;
845 let kwargs = &unsafe { py_kwargs.try_to_object_unsafe(py) }
847 .map_err(SerializablePyErr::from_fn(py))?;
848
849 if let Some(function) = function {
850 function
851 .call(args, Some(kwargs))
852 .map_err(SerializablePyErr::from_fn(py))
853 } else {
854 Ok(args.get_item(0).unwrap())
855 }
856 })?;
857 Ok(result)
858 }
859
860 fn call_python_fn_pytree(
861 &mut self,
862 cx: &hyperactor::Context<Self>,
863 function: ResolvableFunction,
864 args_kwargs: ArgsKwargs,
865 mutates: &[Ref],
866 device_meshes: HashMap<Ref, DeviceMesh>,
867 remote_process_groups: HashMap<
868 Ref,
869 (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>),
870 >,
871 ) -> Result<PyTree<Py<PyAny>>, CallFunctionError> {
872 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
873 let result = self.call_python_fn(
874 py,
875 cx,
876 Some(function),
877 args_kwargs,
878 mutates,
879 device_meshes,
880 remote_process_groups,
881 )?;
882 Ok(result
883 .extract::<PyTree<Py<PyAny>>>()
884 .map_err(SerializablePyErr::from_fn(py))?)
885 })
886 }
887 fn get_or_fake_on_err(&self, ref_: Ref, factory: &Factory) -> Result<TensorCell> {
895 let pyobject = self
896 .env
897 .get(&ref_)
898 .ok_or_else(|| anyhow!("tensor not found in stream: {ref_:#?}"))?;
899
900 match pyobject {
901 Ok(val) => monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
902 Self::pyobject_to_tensor(py, val)
903 .map_err(|pyerr| anyhow::Error::from(SerializablePyErr::from(py, &pyerr)))
904 }),
905 Err(_) => {
906 let t = factory_zeros(&factory.size, factory.dtype, factory.layout, factory.device);
907 Ok(TensorCell::new(t))
908 }
909 }
910 }
911
912 fn get_defining_recording(&mut self) -> Option<(&mut Recording, &mut HashSet<u64>)> {
913 self.active_recording
914 .as_mut()
915 .and_then(|state| match state {
916 RecordingState::Defining {
917 recording,
918 defined_borrows,
919 } => {
920 match self.recordings.get_mut(recording) {
921 Some(recording) => Some((recording, defined_borrows)),
922 None => panic!("recording not found: {:?}", recording),
924 }
925 }
926 RecordingState::Running => None,
927 })
928 }
929
930 fn get_first_error(&self, refs: &[Ref]) -> Result<Option<Arc<SeqError>>> {
931 for ref_ in refs {
932 let rvalue_or_err = self
933 .env
934 .get(ref_)
935 .ok_or_else(|| anyhow!("tensor not found in stream: {ref_:#?}"))?;
936 if let Err(err) = rvalue_or_err {
937 return Ok(Some(err.clone()));
938 }
939 }
940 Ok(None)
941 }
942 async fn send_value_python_message(
943 &mut self,
944 cx: &hyperactor::Context<'_, Self>,
945 seq: Seq,
946 mutates: Vec<Ref>,
947 function: Option<ResolvableFunction>,
948 args_kwargs: ArgsKwargs,
949 device_meshes: HashMap<Ref, DeviceMesh>,
950 ) -> Result<()> {
951 let rank = self.rank;
952 self.try_define(cx, seq, vec![], &vec![], async |self_| {
953 let python_message = monarch_with_gil_blocking(
954 GilSite::StreamCompute,
955 |py| -> Result<PythonMessage, CallFunctionError> {
956 let python_result = tokio::task::block_in_place(|| {
957 self_.call_python_fn(
958 py,
959 cx,
960 function,
961 args_kwargs,
962 &mutates,
963 device_meshes,
964 HashMap::new(),
965 )
966 })?;
967 pickle_python_result(py, python_result, rank).map_err(CallFunctionError::Error)
968 },
969 )?;
970 let ser = wirevalue::Any::serialize(&python_message).unwrap();
971 self_
972 .controller_actor
973 .fetch_result(cx, seq, Ok(ser))
974 .await?;
975 Ok(vec![])
976 })
977 .await
978 }
979 fn define_ref(&mut self, dest: Ref, src: Ref) -> Result<(), anyhow::Error> {
980 let rvalue = self
981 .env
982 .get(&src)
983 .ok_or_else(|| CallFunctionError::RefNotFound(src))?;
984 self.env.insert(
985 dest,
986 monarch_with_gil_blocking(GilSite::StreamCompute, |_py| rvalue.clone()),
987 );
988 Ok(())
989 }
990 async fn call_actor(
991 &mut self,
992 cx: &Context<'_, Self>,
993 params: ActorCallParams,
994 ) -> Result<Py<PyAny>, CallFunctionError> {
995 let local_state: Result<Vec<Py<PyAny>>> =
996 monarch_with_gil_blocking(GilSite::StreamCompute, |_py| {
997 params
998 .local_state
999 .into_iter()
1000 .map(|elem| {
1001 let pyobj = self.ref_to_pyobject(&elem)?;
1002 Ok(pyobj.into_any())
1003 })
1004 .collect()
1005 });
1006
1007 let (send, recv) = cx.open_once_port();
1008 let state = LocalState {
1009 response_port: send,
1010 state: local_state?,
1011 };
1012 let x: u64 = params.seq.into();
1013 let message = LocalStateBrokerMessage::Set(x as usize, state);
1014
1015 let broker = BrokerId::new(params.broker_id).resolve(cx).await;
1016 broker.post(cx, message);
1017 let result = recv
1018 .recv()
1019 .await
1020 .map_err(|e| CallFunctionError::Error(e.into()))?;
1021
1022 result.map_err(|pyerr| anyhow::Error::msg(pyerr.to_string()).into())
1023 }
1024}
1025
1026#[async_trait]
1027#[handle(StreamMessage)]
1028impl StreamMessageHandler for StreamActor {
1029 async fn call_function(
1030 &mut self,
1031 cx: &Context<Self>,
1032 params: CallFunctionParams,
1033 device_meshes: HashMap<Ref, DeviceMesh>,
1034 remote_process_groups: HashMap<
1035 Ref,
1036 (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>),
1037 >,
1038 ) -> Result<()> {
1039 if let Some((recording, _)) = self.get_defining_recording() {
1040 recording.messages.push(StreamMessage::CallFunction(
1041 params,
1042 device_meshes,
1043 remote_process_groups,
1044 ));
1045 return Ok(());
1046 }
1047
1048 params.function.panic_if_requested();
1049 self.try_define(
1050 cx,
1051 params.seq,
1052 params.results,
1053 ¶ms.mutates,
1054 async |self| {
1055 tokio::task::block_in_place(|| {
1056 self.call_python_fn_pytree(
1057 cx,
1058 params.function,
1059 params.args_kwargs,
1060 ¶ms.mutates,
1061 device_meshes,
1062 remote_process_groups,
1063 )
1064 .map(|results| results.into_leaves())
1065 })
1066 },
1067 )
1068 .await?;
1069 Ok(())
1070 }
1071
1072 async fn borrow_create(
1073 &mut self,
1074 cx: &Context<Self>,
1075 borrow: u64,
1076 tensor: Ref,
1077 first_use_sender: PortHandle<(Option<Event>, TensorCellResult)>,
1078 ) -> Result<()> {
1079 if let Some((recording, defined_borrows)) = self.get_defining_recording() {
1080 recording.messages.push(StreamMessage::BorrowCreate {
1081 borrow,
1082 tensor,
1083 first_use_sender,
1084 });
1085 ensure!(
1086 defined_borrows.insert(borrow),
1087 "duplicate borrow create in recording"
1088 );
1089 return Ok(());
1090 }
1091
1092 let pyobj_result = self
1093 .env
1094 .get(&tensor)
1095 .ok_or_else(|| anyhow!("invalid reference for borrow_create: {:#?}", tensor))?;
1096
1097 let result = match pyobj_result {
1098 Ok(pyobj) => monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1099 Ok(Self::pyobject_to_tensor(py, pyobj).unwrap())
1100 }),
1101 Err(e) => Err(e.clone()),
1102 };
1103
1104 let event = self
1105 .cuda_stream()?
1106 .map(|stream| stream.record_event(None))
1107 .transpose()?;
1108 first_use_sender.post(cx, (event, result));
1109 Ok(())
1110 }
1111
1112 async fn borrow_first_use(
1113 &mut self,
1114 _cx: &Context<Self>,
1115 borrow: u64,
1116 result: Ref,
1117 first_use_receiver: Arc<Mutex<PortReceiver<(Option<Event>, TensorCellResult)>>>,
1118 ) -> Result<()> {
1119 if let Some((recording, _)) = self.get_defining_recording() {
1120 recording.messages.push(StreamMessage::BorrowFirstUse {
1121 borrow,
1122 result,
1123 first_use_receiver: first_use_receiver.clone(),
1124 });
1125 return Ok(());
1126 }
1127
1128 let (first_use_event, cell) =
1129 first_use_receiver
1130 .lock()
1131 .await
1132 .recv()
1133 .await
1134 .map_err(|err| {
1135 anyhow!(
1136 "failed receiving first use event for borrow {:?}: {:?}",
1137 borrow,
1138 err
1139 )
1140 })?;
1141
1142 if let Some(stream) = self.cuda_stream()? {
1143 stream.wait_event(
1144 &mut first_use_event.expect("sent borrow to CUDA stream, expected a CUDA event"),
1145 )?;
1146 }
1147 match cell {
1148 Ok(cell) => {
1149 let pyobj = Self::tensor_to_pyobject(cell);
1150 self.env.insert(result, Ok(pyobj));
1151 }
1152 Err(err) => {
1153 self.env.insert(result, Err(err.clone()));
1154 }
1155 }
1156 Ok(())
1157 }
1158
1159 async fn borrow_last_use(
1160 &mut self,
1161 cx: &Context<Self>,
1162 borrow: u64,
1163 result: Ref,
1164 last_use_sender: PortHandle<(Option<Event>, TensorCellResult)>,
1165 ) -> Result<()> {
1166 if let Some((recording, _)) = self.get_defining_recording() {
1167 recording.messages.push(StreamMessage::BorrowLastUse {
1168 borrow,
1169 result,
1170 last_use_sender,
1171 });
1172 return Ok(());
1173 }
1174
1175 let event = self
1176 .cuda_stream()?
1177 .map(|stream| stream.record_event(None))
1178 .transpose()?;
1179 let pyobj_or_err = self.env.remove(&result).ok_or(anyhow!(
1180 "Invalid reference for borrow_last_use: {result:#?}"
1181 ))?;
1182 let tensor = match pyobj_or_err {
1183 Ok(pyobj) => Ok(monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1184 Self::pyobject_to_tensor(py, &pyobj).unwrap()
1185 })),
1186 Err(e) => Err(e),
1187 };
1188
1189 last_use_sender.post(cx, (event, tensor));
1190 Ok(())
1191 }
1192
1193 async fn borrow_drop(
1194 &mut self,
1195 _cx: &Context<Self>,
1196 borrow: u64,
1197 last_use_receiver: Arc<Mutex<PortReceiver<(Option<Event>, TensorCellResult)>>>,
1198 ) -> Result<()> {
1199 if let Some((recording, defined_borrows)) = self.get_defining_recording() {
1200 recording.messages.push(StreamMessage::BorrowDrop {
1201 borrow,
1202 last_use_receiver: last_use_receiver.clone(),
1203 });
1204 ensure!(
1205 defined_borrows.remove(&borrow),
1206 "borrow drop for borrow not defined in recording"
1207 );
1208 return Ok(());
1209 }
1210
1211 let (last_use_event, _cell) =
1215 last_use_receiver.lock().await.recv().await.map_err(|err| {
1216 anyhow!(
1217 "failed receiving last use event for borrow {:?}: {:?}",
1218 borrow,
1219 err
1220 )
1221 })?;
1222
1223 if let Some(stream) = self.cuda_stream()? {
1224 stream.wait_event(
1225 &mut last_use_event.expect("sent borrow to CUDA stream, expected a CUDA event"),
1226 )?;
1227 }
1228 Ok(())
1230 }
1231
1232 async fn delete_refs(&mut self, _cx: &Context<Self>, refs: Vec<Ref>) -> Result<()> {
1233 if let Some((recording, _)) = self.get_defining_recording() {
1234 recording.messages.push(StreamMessage::DeleteRefs(refs));
1235 return Ok(());
1236 }
1237
1238 for ref_ in refs.iter() {
1239 self.env.remove(ref_);
1240 }
1241 Ok(())
1242 }
1243
1244 async fn request_status(&mut self, _cx: &Context<Self>) -> Result<()> {
1245 if self.get_defining_recording().is_some() {
1246 bail!("request_status not allowed in recording");
1247 }
1248
1249 Ok(())
1250 }
1251
1252 async fn init_comm(
1253 &mut self,
1254 _cx: &Context<Self>,
1255 comm: ActorHandle<NcclCommActor>,
1256 ) -> Result<()> {
1257 if self.get_defining_recording().is_some() {
1258 bail!("init_comm not allowed in recording");
1259 }
1260
1261 self.comm = Some(comm);
1262 Ok(())
1263 }
1264
1265 async fn reduce(
1266 &mut self,
1267 cx: &Context<Self>,
1268 comm: Arc<ActorHandle<NcclCommActor>>,
1269 dim_size: i64,
1270 result: Ref,
1271 local_tensor: Ref,
1272 factory: Factory,
1273 reduction: Reduction,
1274 scatter: bool,
1275 in_place: bool,
1276 out: Option<Ref>,
1277 ) -> Result<()> {
1278 if let Some((recording, _)) = self.get_defining_recording() {
1279 recording.messages.push(StreamMessage::Reduce {
1280 comm,
1281 dim_size,
1282 result,
1283 local_tensor,
1284 factory,
1285 reduction,
1286 scatter,
1287 in_place,
1288 out,
1289 });
1290 return Ok(());
1291 }
1292
1293 let stream = self
1294 .cuda_stream()?
1295 .expect("reductions not yet supported for non-CUDA workers")
1296 .clone();
1297 let input_cell = self.get_or_fake_on_err(local_tensor, &factory)?;
1298 let out_cell = out
1299 .map(|out| self.get_or_fake_on_err(out, &factory))
1300 .transpose()?;
1301 let output_cell = match reduction {
1302 Reduction::Stack => {
1303 if scatter {
1304 let output_cell = if in_place {
1305 input_cell.clone()
1306 } else {
1307 out_cell.unwrap_or({
1308 let borrow = input_cell.try_borrow().map_err(|e| anyhow!("{e:?}"))?;
1309 let cloned = deep_clone(&borrow);
1310 TensorCell::new(cloned)
1311 })
1312 };
1313 comm.all_to_all_single(cx, output_cell.clone(), input_cell, stream)
1314 .await?;
1315 output_cell
1316 } else {
1317 ensure!(
1318 !in_place,
1319 "in-place, non-scatter not supported for stack reduce"
1320 );
1321
1322 let output_cell = out_cell.unwrap_or({
1323 let sizes = [&[dim_size][..], &factory.size[..]].concat();
1325 let output =
1326 factory_empty(&sizes, factory.dtype, factory.layout, factory.device);
1327 TensorCell::new(output)
1328 });
1329
1330 comm.all_gather_into_tensor(cx, output_cell.clone(), input_cell, stream)
1331 .await?;
1332 output_cell
1333 }
1334 }
1335 Reduction::ReduceOp(op) => {
1336 if scatter {
1337 ensure!(!in_place, "in-place, scatter not supported for reduce");
1338
1339 let output_cell = out_cell.unwrap_or({
1340 let output = factory_empty(
1341 &factory.size[1..],
1342 factory.dtype,
1343 factory.layout,
1344 factory.device,
1345 );
1346 TensorCell::new(output)
1347 });
1348 comm.reduce_scatter_tensor(cx, output_cell.clone(), input_cell, op, stream)
1349 .await?;
1350 output_cell
1351 } else {
1352 let output_cell = if in_place {
1353 input_cell.clone()
1354 } else {
1355 out_cell.map_or(
1356 {
1357 let borrow =
1358 input_cell.try_borrow().map_err(|e| anyhow!("{e:?}"))?;
1359 let cloned = deep_clone(&borrow);
1360 Ok(TensorCell::new(cloned))
1361 },
1362 |out_cell| -> Result<_, anyhow::Error> {
1363 let mut out_borrow =
1364 out_cell.try_borrow_mut().map_err(|e| anyhow!("{e:?}"))?;
1365 let in_borrow =
1366 input_cell.try_borrow().map_err(|e| anyhow!("{e:?}"))?;
1367 out_borrow.copy_(&in_borrow);
1368 drop(out_borrow);
1369 Ok(out_cell)
1370 },
1371 )?
1372 };
1373
1374 comm.all_reduce(cx, output_cell.clone(), op, stream).await?;
1375 output_cell
1376 }
1377 }
1378 };
1379
1380 let pyobj = Self::tensor_to_pyobject(output_cell);
1381 self.env.insert(result, Ok(pyobj));
1382 Ok(())
1383 }
1384
1385 async fn send_tensor(
1386 &mut self,
1387 cx: &Context<Self>,
1388 result: Ref,
1389 from_rank: Option<usize>,
1390 to_rank: Option<usize>,
1391 tensor: Ref,
1392 factory: Factory,
1393 comm: Option<Arc<ActorHandle<NcclCommActor>>>,
1394 ) -> Result<()> {
1395 if let Some((recording, _)) = self.get_defining_recording() {
1396 recording.messages.push(StreamMessage::SendTensor {
1397 result,
1398 from_rank,
1399 to_rank,
1400 tensor,
1401 factory,
1402 comm,
1403 });
1404 return Ok(());
1405 }
1406
1407 if to_rank.is_none() && from_rank.is_none() {
1408 bail!("tried to send tensor without a to/from rank");
1409 }
1410
1411 if from_rank == to_rank {
1413 let input_cell: &std::result::Result<Py<PyAny>, Arc<SeqError>> = self
1414 .env
1415 .get(&tensor)
1416 .ok_or_else(|| anyhow!("tensor not found in stream: {tensor:#?}"))?;
1417 let output_cell: Result<Py<PyAny>, Arc<SeqError>> = match input_cell {
1418 Ok(pyobj) => {
1419 monarch_with_gil_blocking(
1420 GilSite::StreamCompute,
1421 |py| -> Result<Py<PyAny>, Arc<SeqError>> {
1422 let input_tensor = Self::pyobject_to_tensor(py, pyobj).unwrap();
1423 let borrow = input_tensor.try_borrow().unwrap();
1428 let cloned = deep_clone(&borrow);
1429 let cloned_cell = TensorCell::new(cloned);
1430 Ok(Self::tensor_to_pyobject(cloned_cell))
1431 },
1432 )
1433 }
1434 Err(err) => Err(err.clone()),
1435 };
1436 self.env.insert(result, output_cell);
1437 return Ok(());
1438 }
1439
1440 let comm = comm.context("send_tensor requires backend comm")?;
1441
1442 let mut messages = Vec::new();
1443
1444 if let Some(to_rank) = to_rank {
1445 let input_cell = self.get_or_fake_on_err(tensor, &factory)?;
1446 messages.push(CommMessage::Send(
1447 input_cell,
1448 to_rank.try_into().unwrap(),
1449 self.cuda_stream()?
1450 .expect("tried to send_tensor on non-cuda stream")
1451 .clone(),
1452 cx.open_once_port().0,
1453 ));
1454 }
1455
1456 if let Some(from_rank) = from_rank {
1457 let output_cell = TensorCell::new(factory_empty(
1458 &factory.size,
1459 factory.dtype,
1460 factory.layout,
1461 factory.device,
1462 ));
1463 messages.push(CommMessage::Recv(
1464 output_cell.clone(),
1465 from_rank.try_into().unwrap(),
1466 self.cuda_stream()?
1467 .expect("tried to send_tensor on non-cuda stream")
1468 .clone(),
1469 cx.open_once_port().0,
1470 ));
1471 let pyobj = Self::tensor_to_pyobject(output_cell);
1472 self.env.insert(result, Ok(pyobj));
1473 }
1474
1475 comm.group(
1476 cx,
1477 messages,
1478 self.cuda_stream()?
1479 .expect("tried to send_tensor on non-cuda stream")
1480 .clone(),
1481 )
1482 .await?;
1483 Ok(())
1484 }
1485
1486 async fn send_value(
1487 &mut self,
1488 cx: &Context<Self>,
1489 seq: Seq,
1490 worker_actor_id: reference::ActorAddr,
1491 mutates: Vec<Ref>,
1492 function: Option<ResolvableFunction>,
1493 args_kwargs: ArgsKwargs,
1494 device_meshes: HashMap<Ref, DeviceMesh>,
1495 ) -> Result<()> {
1496 if self.respond_with_python_message {
1497 return self
1498 .send_value_python_message(cx, seq, mutates, function, args_kwargs, device_meshes)
1499 .await;
1500 }
1501
1502 let result = if let Some(function) = function {
1503 tokio::task::block_in_place(|| {
1505 self.call_python_fn_pytree(
1506 cx,
1507 function,
1508 args_kwargs,
1509 &mutates,
1510 device_meshes,
1511 HashMap::new(),
1512 )
1513 })
1514 } else {
1515 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1518 let (args, kwargs) = args_kwargs
1519 .to_python(py)
1520 .map_err(|e| CallFunctionError::Error(e.into()))?;
1521 match (args.len(), kwargs.len()) {
1522 (1, 0) => {
1523 let arg = args.get_item(0).map_err(SerializablePyErr::from_fn(py))?;
1524 arg.extract::<PyTree<Py<PyAny>>>()
1525 .map_err(SerializablePyErr::from_fn(py))?
1526 .try_into_map(|obj| {
1527 let bound_obj = obj.bind(py);
1528 if let Ok(ref_) = Ref::from_py_object(bound_obj) {
1529 self.ref_to_pyobject(&ref_)
1530 } else {
1531 Ok(obj)
1532 }
1533 })
1534 }
1535 _ => Err(CallFunctionError::TooManyArgsForValue(
1536 format!("args with {} elements", args.len()),
1537 format!("kwargs with {} elements", kwargs.len()),
1538 )),
1539 }
1540 })
1541 };
1542
1543 let value = match result {
1544 Ok(pyobject) => Ok(pyobject),
1545 Err(err) => {
1546 let err = self.report_seq_error(cx, seq, err).await?;
1547 for ref_ in mutates {
1548 self.env.insert(ref_, Err(err.clone()));
1549 }
1550 Err(WorkerError {
1551 backtrace: format!("{:?}", err),
1552 worker_actor_id,
1553 })
1554 }
1555 };
1556
1557 let result = match value {
1561 Ok(_value) => {
1562 unreachable!(
1564 "send_value should return early when respond_with_python_message is true"
1565 )
1566 }
1567 Err(e) => Err(e),
1568 };
1569 self.controller_actor.fetch_result(cx, seq, result).await?;
1570
1571 Ok(())
1572 }
1573
1574 async fn send_result_of_actor_call(
1575 &mut self,
1576 cx: &Context<Self>,
1577 params: ActorCallParams,
1578 ) -> anyhow::Result<()> {
1579 let seq = params.seq;
1580 let mutates = params.mutates.clone();
1581 self.try_define(cx, seq, vec![], &mutates, async |self| {
1582 let value = self.call_actor(cx, params).await?;
1583 let result = monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1584 pickle_python_result(py, value.into_bound(py), self.rank)
1585 })?;
1586 let result = wirevalue::Any::serialize(&result).unwrap();
1587 self.controller_actor
1588 .fetch_result(cx, seq, Ok(result))
1589 .await?;
1590 Ok(vec![])
1591 })
1592 .await
1593 }
1594
1595 async fn call_actor_method(
1596 &mut self,
1597 cx: &Context<Self>,
1598 params: ActorMethodParams,
1599 ) -> anyhow::Result<()> {
1600 let seq = params.call.seq;
1601 let mutates = params.call.mutates.clone();
1602 self.try_define(cx, seq, params.results, &mutates, async |self| {
1603 let result = self.call_actor(cx, params.call).await?;
1604 let result = monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1605 result
1606 .into_bound(py)
1607 .extract::<PyTree<Py<PyAny>>>()
1608 .map_err(SerializablePyErr::from_fn(py))
1609 })?;
1610 Ok(result.into_leaves())
1611 })
1612 .await
1613 }
1614
1615 async fn define_recording(&mut self, _cx: &Context<Self>, recording: Ref) -> Result<()> {
1616 if self.active_recording.is_some() {
1617 bail!("different recording already active");
1618 }
1619 match self.recordings.entry(recording) {
1620 Entry::Occupied(_) => bail!("recording {:?} already defined", recording),
1621 Entry::Vacant(entry) => entry.insert(Recording::new()),
1622 };
1623 self.active_recording = Some(RecordingState::Defining {
1624 recording,
1625 defined_borrows: HashSet::new(),
1626 });
1627 Ok(())
1628 }
1629
1630 async fn finalize_recording(&mut self, _cx: &Context<Self>, recording: Ref) -> Result<()> {
1631 match self.active_recording {
1632 Some(RecordingState::Defining {
1633 recording: active_recording,
1634 ref defined_borrows,
1635 }) if active_recording == recording => {
1636 ensure!(
1637 defined_borrows.is_empty(),
1638 "all borrows created within recording must be dropped within recording"
1639 );
1640 self.active_recording = None;
1641 }
1642 _ => bail!("cannot finalize recording that isn't active"),
1643 }
1644 Ok(())
1645 }
1646
1647 async fn recording_formal(
1648 &mut self,
1649 _cx: &Context<Self>,
1650 result: Ref,
1651 argument_index: usize,
1652 ) -> Result<()> {
1653 match self.get_defining_recording() {
1654 Some((recording, _)) => {
1655 recording.messages.push(StreamMessage::RecordingFormal {
1656 result,
1657 argument_index,
1658 });
1659 }
1660 None => bail!("recording_formal called outside of recording"),
1661 };
1662 Ok(())
1663 }
1664
1665 async fn recording_result(
1666 &mut self,
1667 _cx: &Context<Self>,
1668 result: Ref,
1669 output_index: usize,
1670 ) -> Result<()> {
1671 match self.get_defining_recording() {
1672 Some((recording, _)) => {
1673 recording.messages.push(StreamMessage::RecordingResult {
1674 result,
1675 output_index,
1676 });
1677 }
1678 None => bail!("recording_result called outside of recording"),
1679 };
1680 Ok(())
1681 }
1682
1683 async fn call_recording(
1684 &mut self,
1685 cx: &Context<Self>,
1686 seq: Seq,
1687 recording: Ref,
1688 results: Vec<Ref>,
1689 actuals: Vec<Ref>,
1690 ) -> Result<()> {
1691 if self.active_recording.is_some() {
1692 bail!("cannot call recording while another recording is active");
1693 }
1694
1695 let messages = match self.recordings.get(&recording) {
1696 Some(recording) => recording
1697 .messages
1698 .iter()
1699 .map(|message| message.clone_for_recording())
1700 .collect::<Vec<_>>(),
1701 None => bail!("recording {:?} not found", recording),
1702 };
1703
1704 self.active_recording = Some(RecordingState::Running);
1705
1706 let mut error: Option<Arc<SeqError>> = None;
1711 let mut all_defined_refs = HashSet::new();
1714 let mut all_mutated_refs = HashSet::new();
1717 let mut formal_to_actual_refs = HashMap::new();
1723 self.last_seq_error = None;
1725 for message in messages.into_iter() {
1726 let defined_refs = message.get_defined_refs();
1727 all_defined_refs.extend(defined_refs.clone());
1728
1729 let mutated_refs_with_formals = message.get_mutated_refs();
1730 all_mutated_refs.extend(mutated_refs_with_formals.iter().filter_map(|ref_| {
1731 match formal_to_actual_refs.get(ref_) {
1732 Some(actual_ref) => Some(*actual_ref),
1733 None => {
1734 if all_defined_refs.contains(ref_) {
1735 None
1736 } else {
1737 Some(*ref_)
1738 }
1739 }
1740 }
1741 }));
1742
1743 match message {
1744 StreamMessage::RecordingFormal {
1745 result: formal_ref,
1746 argument_index,
1747 } => match actuals.get(argument_index) {
1748 None => bail!("recording_formal called with too few arguments"),
1749 Some(actual_ref) => {
1750 formal_to_actual_refs.insert(formal_ref, *actual_ref);
1751 self.define_ref(formal_ref, *actual_ref)?;
1752 }
1753 },
1754 StreamMessage::RecordingResult {
1755 result: result_ref,
1756 output_index,
1757 } => match results.get(output_index) {
1758 None => bail!("recording_result called with too few results"),
1759 Some(actual_result_ref) => {
1760 self.define_ref(*actual_result_ref, result_ref)?;
1761 }
1762 },
1763 StreamMessage::DeleteRefs(ref refs) => {
1764 for ref_ in refs {
1765 all_defined_refs.remove(ref_);
1766 }
1767 StreamMessageHandler::handle(self, cx, message).await?;
1768 }
1769 StreamMessage::CallFunction { .. } if error.is_some() => {
1770 let error = error.clone().unwrap();
1776 for ref_ in defined_refs.iter().chain(mutated_refs_with_formals.iter()) {
1777 self.env.insert(*ref_, Err(error.clone()));
1778 }
1779 }
1780 StreamMessage::BorrowLastUse { ref result, .. } => {
1781 all_defined_refs.remove(result);
1782 StreamMessageHandler::handle(self, cx, message).await?;
1783 }
1784 StreamMessage::Reduce {
1785 local_tensor,
1786 ref out,
1787 ..
1788 } => {
1789 if error.is_none() {
1793 let inputs_to_check = [Some(local_tensor), *out]
1794 .iter()
1795 .filter_map(|r| *r)
1796 .collect::<Vec<_>>();
1797 error = self.get_first_error(inputs_to_check.as_slice())?;
1798 }
1799 StreamMessageHandler::handle(self, cx, message).await?;
1800 }
1801 StreamMessage::SendTensor {
1802 ref tensor,
1803 ref to_rank,
1804 ..
1805 } => {
1806 if to_rank.is_some() && error.is_none() {
1811 error = self.get_first_error(&[*tensor])?;
1812 }
1813 StreamMessageHandler::handle(self, cx, message).await?;
1814 }
1815 _ => {
1816 StreamMessageHandler::handle(self, cx, message).await?;
1817 }
1818 };
1819
1820 match (&error, self.last_seq_error.take()) {
1828 (None, Some(seq_err)) => {
1829 self.controller_actor
1831 .remote_function_failed(
1832 cx,
1833 seq,
1834 WorkerError {
1835 backtrace: format!("recording failed: {}", seq_err),
1836 worker_actor_id: cx.self_addr().clone(),
1837 },
1838 )
1839 .await?;
1840 error = Some(seq_err)
1841 }
1842 _ => {}
1843 }
1844 }
1849
1850 StreamMessageHandler::handle(
1854 self,
1855 cx,
1856 StreamMessage::DeleteRefs(all_defined_refs.into_iter().collect()),
1857 )
1858 .await?;
1859
1860 if error.is_some() {
1863 for ref_ in results.iter().chain(all_mutated_refs.iter()) {
1864 self.env.insert(*ref_, Err(error.clone().unwrap()));
1865 }
1866 }
1867
1868 self.active_recording = None;
1869 Ok(())
1870 }
1871
1872 async fn set_ref_unit_tests_only(
1873 &mut self,
1874 _cx: &Context<Self>,
1875 reference: Ref,
1876 value: WireValue,
1877 ) -> Result<()> {
1878 let pyobj =
1879 monarch_with_gil_blocking(GilSite::StreamCompute, |py| -> PyResult<Py<PyAny>> {
1880 Ok(value.into_pyobject(py)?.unbind())
1881 })?;
1882 self.env.insert(reference, Ok(pyobj));
1883 Ok(())
1884 }
1885
1886 async fn set_tensor_ref_unit_tests_only(
1887 &mut self,
1888 _cx: &Context<Self>,
1889 reference: Ref,
1890 tensor_result: TensorCellResult,
1891 ) -> Result<()> {
1892 match tensor_result {
1893 Ok(tensor_cell) => {
1894 let pyobj = Self::tensor_to_pyobject(tensor_cell);
1895 self.env.insert(reference, Ok(pyobj));
1896 }
1897 Err(err) => {
1898 self.env.insert(reference, Err(err));
1899 }
1900 }
1901 Ok(())
1902 }
1903
1904 async fn get_ref_unit_tests_only(
1905 &mut self,
1906 _cx: &Context<Self>,
1907 reference: Ref,
1908 ) -> Result<Option<Result<WireValue, String>>> {
1909 use pyo3::types::PyBool;
1910 use pyo3::types::PyFloat;
1911 use pyo3::types::PyInt;
1912 use pyo3::types::PyList;
1913 use pyo3::types::PyNone;
1914 use pyo3::types::PyString;
1915 fn pyobject_to_wire(
1917 value: Result<Py<PyAny>, Arc<SeqError>>,
1918 ) -> Result<WireValue, Arc<SeqError>> {
1919 let pyobj = value?;
1920 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1921 let bound = pyobj.bind(py);
1922 if bound.is_instance_of::<PyBool>() {
1924 Ok(WireValue::Bool(bound.extract::<bool>().unwrap()))
1925 } else if bound.is_instance_of::<PyInt>() {
1926 Ok(WireValue::Int(bound.extract::<i64>().unwrap()))
1927 } else if bound.is_instance_of::<PyList>() {
1928 if let Ok(val) = bound.extract::<Vec<i64>>() {
1929 Ok(WireValue::IntList(val))
1930 } else {
1931 Ok(WireValue::String(format!(
1932 "unsupported list type: {:?}",
1933 bound
1934 )))
1935 }
1936 } else if bound.is_instance_of::<PyFloat>() {
1937 Ok(WireValue::Double(bound.extract::<f64>().unwrap()))
1938 } else if bound.is_instance_of::<PyString>() {
1939 Ok(WireValue::String(bound.extract::<String>().unwrap()))
1940 } else if bound.is_instance_of::<PyNone>() {
1941 Ok(WireValue::None(()))
1942 } else {
1943 Ok(WireValue::String(format!(
1944 "unsupported pyobject type: {:?}",
1945 bound
1946 )))
1947 }
1948 })
1949 }
1950 Ok(self.env.get(&reference).map(|pyobj| {
1951 pyobject_to_wire(monarch_with_gil_blocking(GilSite::StreamCompute, |_py| {
1952 pyobj.clone()
1953 }))
1954 .map_err(|err| err.to_string())
1955 }))
1956 }
1957
1958 async fn get_tensor_ref_unit_tests_only(
1959 &mut self,
1960 _cx: &Context<Self>,
1961 reference: Ref,
1962 ) -> Result<Option<TensorCellResult>> {
1963 match self.env.get(&reference) {
1964 Some(Ok(pyobj)) => monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1965 match Self::pyobject_to_tensor(py, pyobj) {
1966 Ok(tensor) => Ok(Some(Ok(tensor.try_cpu().unwrap()))),
1967 Err(e) => bail!("expected tensor, got extraction error: {:?}", e),
1968 }
1969 }),
1970 Some(Err(err)) => Ok(Some(Err(err.clone()))),
1971 None => Ok(None),
1972 }
1973 }
1974}
1975
1976#[cfg(all(test, fbcode_build))]
1977mod tests {
1978 use hyperactor::actor::ActorStatus;
1979 use hyperactor::context;
1980 use hyperactor::supervision::ActorSupervisionEvent;
1981 use monarch_messages::controller::ControllerMessage;
1982 use monarch_messages::worker::StreamCreationMode;
1983 use monarch_types::PickledPyObject;
1984 use monarch_types::UniqueId;
1985 use pyo3::IntoPyObjectExt;
1986 use timed_test::async_timed_test;
1987 use tokio::sync::watch;
1988 use torch_sys_cuda::nccl::UniqueIdExt;
1989 use torch_sys2::factory_float_tensor;
1990 use torch_sys2::testing::allclose;
1991
1992 use super::*;
1993 use crate::comm::CommParams;
1994 use crate::test_util;
1995
1996 #[allow(dead_code)]
1997 fn fake_seq_error(err: anyhow::Error) -> Arc<SeqError> {
1998 Arc::new(SeqError {
1999 seq: 0.into(),
2000 error: err,
2001 })
2002 }
2003
2004 struct TestSetup {
2005 proc: Proc,
2006 stream_actor: ActorHandle<StreamActor>,
2007 client: reference::Client,
2008 #[allow(dead_code)]
2011 supervision_rx: PortReceiver<ActorSupervisionEvent>,
2012 #[allow(dead_code)]
2013 controller_rx: PortReceiver<ControllerMessage>,
2014 #[allow(dead_code)]
2015 controller_actor: reference::ActorRef<ControllerActor>,
2016 next_ref: Ref,
2017 }
2018
2019 impl TestSetup {
2020 async fn new() -> Result<Self> {
2021 Self::new_with_world_size(1).await
2022 }
2023
2024 async fn new_with_world_size(world_size: usize) -> Result<Self> {
2025 test_util::test_setup()?;
2026
2027 let proc = Proc::isolated();
2028 let (_, controller_actor, controller_rx) =
2029 proc.attach_actor::<ControllerActor, ControllerMessage>("controller")?;
2030 let client = proc.client("client");
2031 let (supervision_tx, supervision_rx) = client.open_port();
2032 proc.set_supervision_coordinator(supervision_tx.bind())?;
2033 let stream_actor = proc.spawn(StreamActor::new(StreamParams {
2034 world_size,
2035 rank: 0,
2036 creation_mode: StreamCreationMode::UseDefaultStream,
2037 id: 0.into(),
2038 device: Some(CudaDevice::new(0.into())),
2039 controller_actor: controller_actor.clone(),
2040 respond_with_python_message: false,
2041 }));
2042
2043 Ok(Self {
2044 proc,
2045 stream_actor,
2046 client,
2047 supervision_rx,
2048 controller_rx,
2049 controller_actor,
2050 next_ref: 0.into(),
2051 })
2052 }
2053
2054 fn next_ref(&mut self) -> Ref {
2055 let ref_ = self.next_ref;
2056 self.next_ref = Ref {
2057 id: self.next_ref.id + 1,
2058 };
2059 ref_
2060 }
2061
2062 async fn set_tensor(&mut self, reference: Ref, data: &[f32]) -> Result<()> {
2063 let tensor = TensorCell::new(factory_float_tensor(data, "cuda".parse().unwrap()));
2064 self.stream_actor
2065 .set_tensor_ref_unit_tests_only(&self.client, reference, Ok(tensor))
2066 .await
2067 }
2068
2069 async fn allclose(&mut self, reference: Ref, data: &[f32]) -> bool {
2070 let actual = self
2071 .stream_actor
2072 .get_tensor_ref_unit_tests_only(&self.client, reference)
2073 .await
2074 .unwrap()
2075 .unwrap()
2076 .unwrap();
2077
2078 allclose(
2080 &factory_float_tensor(data, "cpu".parse().unwrap()),
2081 &actual.borrow(),
2082 )
2083 .unwrap()
2084 }
2085
2086 #[allow(dead_code)]
2087 async fn validate_dependent_error(&mut self, reference: Ref, error: Arc<SeqError>) {
2088 let result_error = self
2089 .stream_actor
2090 .get_tensor_ref_unit_tests_only(&self.client, reference)
2091 .await
2092 .unwrap()
2093 .unwrap()
2094 .unwrap_err();
2095
2096 assert!(Arc::ptr_eq(&result_error, &error));
2097 }
2098 }
2099
2100 async fn assert_actor_failed_with_msg(
2101 status_rx: &mut watch::Receiver<ActorStatus>,
2102 expected_msg: String,
2103 ) {
2104 status_rx
2105 .wait_for(|s| matches!(s, ActorStatus::Failed(_)))
2106 .await
2107 .unwrap();
2108 let status = status_rx.borrow().clone();
2109 if let ActorStatus::Failed(msg) = status {
2110 assert!(msg.to_string().contains(&expected_msg));
2111 } else {
2112 panic!("expected ActorStatus::Failed, got {:?}", status);
2113 }
2114 }
2115
2116 async fn assert_refs_do_not_exist(test_setup: &TestSetup, refs: &[Ref]) {
2117 for ref_ in refs {
2118 assert!(
2119 test_setup
2120 .stream_actor
2121 .get_tensor_ref_unit_tests_only(&test_setup.client, *ref_)
2122 .await
2123 .unwrap()
2124 .is_none()
2125 );
2126 }
2127 }
2128
2129 #[allow(dead_code)]
2130 async fn fetch_result(
2131 cx: &impl context::Actor,
2132 stream_actor: ActorHandle<StreamActor>,
2133 seq: Seq,
2134 reference: Ref,
2135 ) {
2136 let ref_to_send = monarch_with_gil_blocking(GilSite::Test, |py| {
2137 PickledPyObject::pickle(&reference.into_bound_py_any(py).unwrap()).unwrap()
2138 });
2139
2140 stream_actor
2141 .send_value(
2142 cx,
2143 seq,
2144 stream_actor.actor_addr().clone(),
2145 Vec::new(),
2146 None,
2147 ArgsKwargs::from_wire_values(
2148 vec![WireValue::PyObject(ref_to_send)],
2149 HashMap::new(),
2150 )
2151 .unwrap(),
2152 HashMap::new(),
2153 )
2154 .await
2155 .unwrap()
2156 }
2157
2158 #[allow(dead_code)]
2159 async fn check_fetch_result_error(
2160 cx: &impl context::Actor,
2161 stream_actor: ActorHandle<StreamActor>,
2162 seq: Seq,
2163 reference: Ref,
2164 controller_rx: &mut PortReceiver<ControllerMessage>,
2165 expected_backtrace: &str,
2166 ) {
2167 fetch_result(cx, stream_actor, seq, reference).await;
2168
2169 let controller_msg = controller_rx.recv().await.unwrap();
2170 match controller_msg {
2171 ControllerMessage::FetchResult {
2172 seq: actual_seq,
2173 value: Err(err),
2174 } => {
2175 assert_eq!(actual_seq, seq);
2176 assert!(
2177 err.backtrace.contains(expected_backtrace),
2178 "backtrace did not contain {:?}: {:?}",
2179 expected_backtrace,
2180 err.backtrace
2181 );
2182 }
2183 _ => panic!("Unexpected controller message: {:?}", controller_msg),
2184 };
2185 }
2186
2187 #[allow(dead_code)]
2188 async fn check_fetch_result_value(
2189 cx: &impl context::Actor,
2190 stream_actor: ActorHandle<StreamActor>,
2191 seq: Seq,
2192 reference: Ref,
2193 controller_rx: &mut PortReceiver<ControllerMessage>,
2194 ) {
2195 fetch_result(cx, stream_actor, seq, reference).await;
2196
2197 let controller_msg = controller_rx.recv().await.unwrap();
2198 match controller_msg {
2199 ControllerMessage::FetchResult {
2200 value: Ok(_),
2201 seq: actual_seq,
2202 } => assert_eq!(seq, actual_seq),
2203 _ => panic!("Unexpected controller message: {:?}", controller_msg),
2204 };
2205 }
2206
2207 #[async_timed_test(timeout_secs = 60)]
2208 async fn test_define_recording_other_recording_active() -> Result<()> {
2209 let test_setup = TestSetup::new().await?;
2210 test_setup
2211 .stream_actor
2212 .define_recording(&test_setup.client, 0.into())
2213 .await?;
2214 test_setup
2215 .stream_actor
2216 .define_recording(&test_setup.client, 1.into())
2217 .await?;
2218 assert_actor_failed_with_msg(
2219 &mut test_setup.stream_actor.status(),
2220 "different recording already active".into(),
2221 )
2222 .await;
2223 Ok(())
2224 }
2225
2226 #[async_timed_test(timeout_secs = 60)]
2227 async fn test_define_recording_already_defined() -> Result<()> {
2228 let test_setup = TestSetup::new().await?;
2229 test_setup
2230 .stream_actor
2231 .define_recording(&test_setup.client, 0.into())
2232 .await?;
2233 test_setup
2234 .stream_actor
2235 .finalize_recording(&test_setup.client, 0.into())
2236 .await?;
2237 test_setup
2238 .stream_actor
2239 .define_recording(&test_setup.client, 0.into())
2240 .await?;
2241 assert_actor_failed_with_msg(
2242 &mut test_setup.stream_actor.status(),
2243 "already defined".into(),
2244 )
2245 .await;
2246 Ok(())
2247 }
2248
2249 #[async_timed_test(timeout_secs = 60)]
2250 async fn test_finalize_recording_other_recording_active() -> Result<()> {
2251 let test_setup = TestSetup::new().await?;
2252 test_setup
2253 .stream_actor
2254 .define_recording(&test_setup.client, 0.into())
2255 .await?;
2256 test_setup
2257 .stream_actor
2258 .finalize_recording(&test_setup.client, 1.into())
2259 .await?;
2260 assert_actor_failed_with_msg(
2261 &mut test_setup.stream_actor.status(),
2262 "cannot finalize recording that isn't active".into(),
2263 )
2264 .await;
2265 Ok(())
2266 }
2267
2268 #[async_timed_test(timeout_secs = 60)]
2269 async fn test_recording_formal_outside_recording() -> Result<()> {
2270 let test_setup = TestSetup::new().await?;
2271 test_setup
2272 .stream_actor
2273 .recording_formal(&test_setup.client, 0.into(), 0)
2274 .await?;
2275 assert_actor_failed_with_msg(
2276 &mut test_setup.stream_actor.status(),
2277 "recording_formal called outside of recording".into(),
2278 )
2279 .await;
2280 Ok(())
2281 }
2282
2283 #[async_timed_test(timeout_secs = 60)]
2284 async fn test_recording_result_outside_recording() -> Result<()> {
2285 let test_setup = TestSetup::new().await?;
2286 test_setup
2287 .stream_actor
2288 .recording_result(&test_setup.client, 0.into(), 0)
2289 .await?;
2290 assert_actor_failed_with_msg(
2291 &mut test_setup.stream_actor.status(),
2292 "recording_result called outside of recording".into(),
2293 )
2294 .await;
2295 Ok(())
2296 }
2297
2298 #[async_timed_test(timeout_secs = 60)]
2299 async fn test_call_recording_other_recording_active() -> Result<()> {
2300 let test_setup = TestSetup::new().await?;
2301 test_setup
2302 .stream_actor
2303 .define_recording(&test_setup.client, 0.into())
2304 .await?;
2305 test_setup
2306 .stream_actor
2307 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2308 .await?;
2309 assert_actor_failed_with_msg(
2310 &mut test_setup.stream_actor.status(),
2311 "cannot call recording while another recording is active".into(),
2312 )
2313 .await;
2314 Ok(())
2315 }
2316
2317 #[async_timed_test(timeout_secs = 60)]
2318 async fn test_call_recording_not_found() -> Result<()> {
2319 let test_setup = TestSetup::new().await?;
2320 test_setup
2321 .stream_actor
2322 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2323 .await?;
2324 assert_actor_failed_with_msg(&mut test_setup.stream_actor.status(), "not found".into())
2325 .await;
2326 Ok(())
2327 }
2328
2329 #[async_timed_test(timeout_secs = 60)]
2330 async fn test_recording_formal_too_few_arguments() -> Result<()> {
2331 let test_setup = TestSetup::new().await?;
2332
2333 test_setup
2334 .stream_actor
2335 .define_recording(&test_setup.client, 0.into())
2336 .await?;
2337
2338 test_setup
2339 .stream_actor
2340 .recording_formal(&test_setup.client, 1.into(), 0)
2341 .await?;
2342
2343 test_setup
2344 .stream_actor
2345 .finalize_recording(&test_setup.client, 0.into())
2346 .await?;
2347
2348 test_setup
2349 .stream_actor
2350 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2351 .await?;
2352
2353 assert_actor_failed_with_msg(
2354 &mut test_setup.stream_actor.status(),
2355 "recording_formal called with too few arguments".into(),
2356 )
2357 .await;
2358 Ok(())
2359 }
2360
2361 #[async_timed_test(timeout_secs = 60)]
2362 async fn test_recording_result_too_few_results() -> Result<()> {
2363 let test_setup = TestSetup::new().await?;
2364
2365 test_setup
2366 .stream_actor
2367 .define_recording(&test_setup.client, 0.into())
2368 .await?;
2369
2370 test_setup
2371 .stream_actor
2372 .recording_result(&test_setup.client, 1.into(), 0)
2373 .await?;
2374
2375 test_setup
2376 .stream_actor
2377 .finalize_recording(&test_setup.client, 0.into())
2378 .await?;
2379
2380 test_setup
2381 .stream_actor
2382 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2383 .await?;
2384
2385 assert_actor_failed_with_msg(
2386 &mut test_setup.stream_actor.status(),
2387 "recording_result called with too few results".into(),
2388 )
2389 .await;
2390 Ok(())
2391 }
2392
2393 #[async_timed_test(timeout_secs = 60)]
2394 async fn test_basic_call_recording() -> Result<()> {
2395 let mut test_setup = TestSetup::new().await?;
2396
2397 test_setup
2401 .stream_actor
2402 .define_recording(&test_setup.client, 0.into())
2403 .await?;
2404
2405 let formal0_ref = 1.into();
2406 let formal0_index = 1;
2407 test_setup
2408 .stream_actor
2409 .recording_formal(&test_setup.client, formal0_ref, formal0_index)
2410 .await?;
2411
2412 let formal1_ref = 2.into();
2413 let formal1_index = 0;
2414 test_setup
2415 .stream_actor
2416 .recording_formal(&test_setup.client, formal1_ref, formal1_index)
2417 .await?;
2418
2419 let result0_ref = formal0_ref;
2420 let result0_index = 0;
2421 test_setup
2422 .stream_actor
2423 .recording_result(&test_setup.client, result0_ref, result0_index)
2424 .await?;
2425
2426 let result1_ref = formal1_ref;
2427 let result1_index = 1;
2428 test_setup
2429 .stream_actor
2430 .recording_result(&test_setup.client, result1_ref, result1_index)
2431 .await?;
2432
2433 test_setup
2434 .stream_actor
2435 .finalize_recording(&test_setup.client, 0.into())
2436 .await?;
2437
2438 let actual0_ref = 3.into();
2439 test_setup.set_tensor(actual0_ref, &[1.0, 2.0, 3.0]).await?;
2440
2441 let actual1_ref = 4.into();
2442 test_setup.set_tensor(actual1_ref, &[4.0, 5.0]).await?;
2443
2444 let actual_result0_ref = 5.into();
2447 let actual_result1_ref = 6.into();
2448 test_setup
2449 .stream_actor
2450 .call_recording(
2451 &test_setup.client,
2452 0.into(),
2453 0.into(),
2454 vec![actual_result0_ref, actual_result1_ref],
2455 vec![actual0_ref, actual1_ref],
2456 )
2457 .await?;
2458
2459 assert!(test_setup.allclose(actual_result0_ref, &[4.0, 5.0]).await);
2461 assert!(
2462 test_setup
2463 .allclose(actual_result1_ref, &[1.0, 2.0, 3.0])
2464 .await
2465 );
2466
2467 assert_refs_do_not_exist(&test_setup, &[formal0_ref, formal1_ref]).await;
2470 Ok(())
2471 }
2472
2473 #[async_timed_test(timeout_secs = 60)]
2474 async fn test_request_status_in_recording() -> Result<()> {
2475 let test_setup = TestSetup::new().await?;
2476 test_setup
2477 .stream_actor
2478 .define_recording(&test_setup.client, 0.into())
2479 .await?;
2480 test_setup
2481 .stream_actor
2482 .request_status(&test_setup.client)
2483 .await
2484 .expect_err("request_status should have failed");
2485 assert_actor_failed_with_msg(
2486 &mut test_setup.stream_actor.status(),
2487 "request_status not allowed in recording".into(),
2488 )
2489 .await;
2490 Ok(())
2491 }
2492
2493 #[async_timed_test(timeout_secs = 60)]
2494 async fn test_init_comm_in_recording() -> Result<()> {
2495 let test_setup = TestSetup::new().await?;
2496 test_setup
2497 .stream_actor
2498 .define_recording(&test_setup.client, 0.into())
2499 .await?;
2500
2501 let dummy_comm = test_setup.proc.spawn(
2502 NcclCommActor::new(CommParams::New {
2503 device: CudaDevice::new(0.into()),
2504 unique_id: UniqueId::new_nccl()?,
2505 world_size: 1,
2506 rank: 0,
2507 })
2508 .await
2509 .unwrap(),
2510 );
2511
2512 test_setup
2513 .stream_actor
2514 .init_comm(&test_setup.client, dummy_comm)
2515 .await?;
2516 assert_actor_failed_with_msg(
2517 &mut test_setup.stream_actor.status(),
2518 "init_comm not allowed in recording".into(),
2519 )
2520 .await;
2521 Ok(())
2522 }
2523
2524 #[async_timed_test(timeout_secs = 60)]
2525 async fn test_borrow_create_duplicate_borrow() -> Result<()> {
2526 let mut test_setup = TestSetup::new().await?;
2527 test_setup
2528 .stream_actor
2529 .define_recording(&test_setup.client, 0.into())
2530 .await?;
2531
2532 let borrow_id = 1;
2533 let tensor_ref = test_setup.next_ref();
2534 let (first_use_sender, _first_use_receiver) = test_setup.client.open_port();
2535
2536 test_setup
2537 .stream_actor
2538 .borrow_create(
2539 &test_setup.client,
2540 borrow_id,
2541 tensor_ref,
2542 first_use_sender.clone(),
2543 )
2544 .await?;
2545
2546 test_setup
2547 .stream_actor
2548 .borrow_create(&test_setup.client, borrow_id, tensor_ref, first_use_sender)
2549 .await?;
2550
2551 assert_actor_failed_with_msg(
2552 &mut test_setup.stream_actor.status(),
2553 "duplicate borrow create in recording".into(),
2554 )
2555 .await;
2556
2557 Ok(())
2558 }
2559
2560 #[async_timed_test(timeout_secs = 60)]
2561 async fn test_borrow_drop_borrow_not_defined() -> Result<()> {
2562 let test_setup = TestSetup::new().await?;
2563 test_setup
2564 .stream_actor
2565 .define_recording(&test_setup.client, 0.into())
2566 .await?;
2567
2568 let borrow_id = 1;
2569 let (_last_use_sender, last_use_receiver) = test_setup.client.open_port();
2570
2571 test_setup
2572 .stream_actor
2573 .borrow_drop(
2574 &test_setup.client,
2575 borrow_id,
2576 Arc::new(Mutex::new(last_use_receiver)),
2577 )
2578 .await?;
2579
2580 assert_actor_failed_with_msg(
2581 &mut test_setup.stream_actor.status(),
2582 "borrow drop for borrow not defined in recording".into(),
2583 )
2584 .await;
2585
2586 Ok(())
2587 }
2588
2589 #[async_timed_test(timeout_secs = 60)]
2590 async fn test_borrow_not_dropped_before_finalize() -> Result<()> {
2591 let mut test_setup = TestSetup::new().await?;
2592 test_setup
2593 .stream_actor
2594 .define_recording(&test_setup.client, 0.into())
2595 .await?;
2596
2597 let borrow_id = 1;
2598 let tensor_ref = test_setup.next_ref();
2599 let (first_use_sender, _first_use_receiver) = test_setup.client.open_port();
2600
2601 test_setup
2602 .stream_actor
2603 .borrow_create(
2604 &test_setup.client,
2605 borrow_id,
2606 tensor_ref,
2607 first_use_sender.clone(),
2608 )
2609 .await?;
2610
2611 test_setup
2613 .stream_actor
2614 .finalize_recording(&test_setup.client, 0.into())
2615 .await?;
2616
2617 assert_actor_failed_with_msg(
2618 &mut test_setup.stream_actor.status(),
2619 "all borrows created within recording must be dropped within recording".into(),
2620 )
2621 .await;
2622
2623 Ok(())
2624 }
2625}