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) -> Option<&Stream> {
634 self.cuda_stream
635 .get_or_init(|| {
636 self.device.map(|device| 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 })
643 .as_ref()
644 }
645
646 fn ref_to_pyobject(&self, ref_: &Ref) -> Result<Py<PyAny>, CallFunctionError> {
647 let pyobject = self
648 .env
649 .get(ref_)
650 .ok_or_else(|| CallFunctionError::RefNotFound(*ref_))?;
651 match pyobject {
652 Ok(val) => Ok(val.clone()),
653 Err(err) => Err(CallFunctionError::DependentError(err.clone())),
654 }
655 }
656
657 async fn report_seq_error(
658 &mut self,
659 cx: &Context<'_, Self>,
660 seq: Seq,
661 error: CallFunctionError,
662 ) -> Result<Arc<SeqError>, anyhow::Error> {
663 match error {
664 CallFunctionError::DependentError(root) => Ok(root),
665 CallFunctionError::Error(e) => {
666 if self.active_recording.is_none() {
667 let worker_error = WorkerError {
668 backtrace: format!("{e}"),
669 worker_actor_id: cx.self_addr().clone(),
670 };
671 tracing::info!("Propagating remote function error to client: {worker_error}");
672 self.controller_actor
673 .remote_function_failed(cx, seq, worker_error)
674 .await?
675 }
676 let err = Arc::new(SeqError { seq, error: e });
677 self.last_seq_error = Some(err.clone());
678 Ok(err)
679 }
680 }
681 }
682
683 async fn try_define<F>(
684 &mut self,
685 cx: &Context<'_, Self>,
686 seq: Seq,
687 result_refs: Vec<Option<Ref>>,
688 mutates: &Vec<Ref>,
689 f: F,
690 ) -> Result<()>
691 where
692 F: AsyncFnOnce(&mut Self) -> Result<Vec<Py<PyAny>>, CallFunctionError>,
693 {
694 let actual_results = f(self).await;
695 let op_results = actual_results.and_then(|actual_results| {
698 if result_refs.len() == actual_results.len() {
699 Ok(actual_results
700 .into_iter()
701 .zip(result_refs.iter())
702 .filter_map(|(result, ref_)| ref_.map(|ref_| (ref_, result)))
703 .collect::<Vec<(Ref, Py<PyAny>)>>())
704 } else {
705 Err(CallFunctionError::UnexpectedNumberOfReturns(
706 result_refs.len(),
707 actual_results.len(),
708 ))
709 }
710 });
711
712 match op_results {
715 Ok(op_results) => {
716 for (ref_, pyobject) in op_results.into_iter() {
717 let prev = self.env.insert(ref_, Ok(pyobject));
718 assert!(prev.is_none(), "Duplicate write to reference: {:?}", ref_);
719 }
720 }
721 Err(err) => {
722 let err = self.report_seq_error(cx, seq, err).await?;
723 for ref_ in result_refs {
724 match ref_ {
725 Some(ref_) => {
726 let prev = self.env.insert(ref_, Err(err.clone()));
727 assert!(prev.is_none(), "Duplicate write to reference: {:?}", ref_);
728 }
729 None => {}
730 }
731 }
732 for ref_ in mutates {
733 self.env.insert(*ref_, Err(err.clone()));
734 }
735 }
736 }
737 Ok(())
738 }
739
740 fn call_python_fn<'py>(
741 &mut self,
742 py: Python<'py>,
743 _cx: &Context<Self>,
744 function: Option<ResolvableFunction>,
745 args_kwargs: ArgsKwargs,
746 _mutates: &[Ref],
747 device_meshes: HashMap<Ref, DeviceMesh>,
748 remote_process_groups: HashMap<
749 Ref,
750 (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>),
751 >,
752 ) -> Result<Bound<'py, PyAny>, CallFunctionError> {
753 let (args_tuple, kwargs_dict) = args_kwargs
754 .to_python(py)
755 .map_err(|e| CallFunctionError::Error(e.into()))?;
756 let function = function
757 .map(|function| {
758 function.resolve(py).map_err(|e| {
759 CallFunctionError::InvalidRemoteFunction(format!(
760 "failed to resolve function {}: {}",
761 function,
762 SerializablePyErr::from(py, &e)
763 ))
764 })
765 })
766 .transpose()?;
767
768 let remote_process_groups = remote_process_groups
769 .into_iter()
770 .map(|(gref, (_mesh, _dims, _comm))| {
771 let group = match self.remote_process_groups.entry(gref) {
772 Entry::Occupied(ent) => ent.get().clone_ref(py),
773 Entry::Vacant(_ent) => {
774 panic!("no longer implemented");
775 }
776 };
777 PyResult::Ok((gref, group))
778 })
779 .collect::<Result<HashMap<_, _>, _>>()
780 .map_err(SerializablePyErr::from_fn(py))?;
781
782 let resolve = |val: Bound<'py, PyAny>| {
783 val.extract::<PyTree<Py<PyAny>>>()
784 .map_err(SerializablePyErr::from_fn(py))?
785 .try_into_map(|obj| {
786 Ok(if let Ok(ref_) = Ref::from_py_object(obj.bind(py)) {
787 if let Some(mesh) = device_meshes.get(&ref_) {
788 PyArg::Object(
789 Py::new(py, mesh.clone())
790 .map_err(SerializablePyErr::from_fn(py))?
791 .into(),
792 )
793 } else if let Some(pg) = remote_process_groups.get(&ref_) {
794 PyArg::Object(pg.clone_ref(py))
795 } else {
796 let pyobj = self.ref_to_pyobject(&ref_)?;
797 PyArg::Object(pyobj)
798 }
799 } else {
800 PyArg::Object(obj)
801 })
802 })
803 };
804
805 let py_args: Vec<PyTree<PyArg>> = args_tuple
807 .iter()
808 .map(&resolve)
809 .collect::<Result<_, CallFunctionError>>()?;
810
811 let py_kwargs: HashMap<String, PyTree<PyArg>> = kwargs_dict
812 .iter()
813 .map(|(k, v)| {
814 let key = k
815 .extract::<String>()
816 .map_err(SerializablePyErr::from_fn(py))?;
817 let value = resolve(v)?;
818 Ok((key, value))
819 })
820 .collect::<Result<_, CallFunctionError>>()?;
821
822 let scoped_subscriber = Subscriber::builder().with_writer(std::io::stdout).finish();
825 let result: Bound<'_, PyAny> =
826 tracing::subscriber::with_default(scoped_subscriber, || {
827 let args = unsafe { py_args.try_to_object_unsafe(py) }
836 .map_err(SerializablePyErr::from_fn(py))?;
837 let kwargs = &unsafe { py_kwargs.try_to_object_unsafe(py) }
839 .map_err(SerializablePyErr::from_fn(py))?;
840
841 if let Some(function) = function {
842 function
843 .call(args, Some(kwargs))
844 .map_err(SerializablePyErr::from_fn(py))
845 } else {
846 Ok(args.get_item(0).unwrap())
847 }
848 })?;
849 Ok(result)
850 }
851
852 fn call_python_fn_pytree(
853 &mut self,
854 cx: &hyperactor::Context<Self>,
855 function: ResolvableFunction,
856 args_kwargs: ArgsKwargs,
857 mutates: &[Ref],
858 device_meshes: HashMap<Ref, DeviceMesh>,
859 remote_process_groups: HashMap<
860 Ref,
861 (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>),
862 >,
863 ) -> Result<PyTree<Py<PyAny>>, CallFunctionError> {
864 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
865 let result = self.call_python_fn(
866 py,
867 cx,
868 Some(function),
869 args_kwargs,
870 mutates,
871 device_meshes,
872 remote_process_groups,
873 )?;
874 Ok(PyTree::<Py<PyAny>>::extract_bound(&result)
875 .map_err(SerializablePyErr::from_fn(py))?)
876 })
877 }
878 fn get_or_fake_on_err(&self, ref_: Ref, factory: &Factory) -> Result<TensorCell> {
886 let pyobject = self
887 .env
888 .get(&ref_)
889 .ok_or_else(|| anyhow!("tensor not found in stream: {ref_:#?}"))?;
890
891 match pyobject {
892 Ok(val) => monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
893 Self::pyobject_to_tensor(py, val)
894 .map_err(|pyerr| anyhow::Error::from(SerializablePyErr::from(py, &pyerr)))
895 }),
896 Err(_) => {
897 let t = factory_zeros(&factory.size, factory.dtype, factory.layout, factory.device);
898 Ok(TensorCell::new(t))
899 }
900 }
901 }
902
903 fn get_defining_recording(&mut self) -> Option<(&mut Recording, &mut HashSet<u64>)> {
904 self.active_recording
905 .as_mut()
906 .and_then(|state| match state {
907 RecordingState::Defining {
908 recording,
909 defined_borrows,
910 } => {
911 match self.recordings.get_mut(recording) {
912 Some(recording) => Some((recording, defined_borrows)),
913 None => panic!("recording not found: {:?}", recording),
915 }
916 }
917 RecordingState::Running => None,
918 })
919 }
920
921 fn get_first_error(&self, refs: &[Ref]) -> Result<Option<Arc<SeqError>>> {
922 for ref_ in refs {
923 let rvalue_or_err = self
924 .env
925 .get(ref_)
926 .ok_or_else(|| anyhow!("tensor not found in stream: {ref_:#?}"))?;
927 if let Err(err) = rvalue_or_err {
928 return Ok(Some(err.clone()));
929 }
930 }
931 Ok(None)
932 }
933 async fn send_value_python_message(
934 &mut self,
935 cx: &hyperactor::Context<'_, Self>,
936 seq: Seq,
937 mutates: Vec<Ref>,
938 function: Option<ResolvableFunction>,
939 args_kwargs: ArgsKwargs,
940 device_meshes: HashMap<Ref, DeviceMesh>,
941 ) -> Result<()> {
942 let rank = self.rank;
943 self.try_define(cx, seq, vec![], &vec![], async |self_| {
944 let python_message = monarch_with_gil_blocking(
945 GilSite::StreamCompute,
946 |py| -> Result<PythonMessage, CallFunctionError> {
947 let python_result = tokio::task::block_in_place(|| {
948 self_.call_python_fn(
949 py,
950 cx,
951 function,
952 args_kwargs,
953 &mutates,
954 device_meshes,
955 HashMap::new(),
956 )
957 })?;
958 pickle_python_result(py, python_result, rank).map_err(CallFunctionError::Error)
959 },
960 )?;
961 let ser = wirevalue::Any::serialize(&python_message).unwrap();
962 self_
963 .controller_actor
964 .fetch_result(cx, seq, Ok(ser))
965 .await?;
966 Ok(vec![])
967 })
968 .await
969 }
970 fn define_ref(&mut self, dest: Ref, src: Ref) -> Result<(), anyhow::Error> {
971 let rvalue = self
972 .env
973 .get(&src)
974 .ok_or_else(|| CallFunctionError::RefNotFound(src))?;
975 self.env.insert(
976 dest,
977 monarch_with_gil_blocking(GilSite::StreamCompute, |_py| rvalue.clone()),
978 );
979 Ok(())
980 }
981 async fn call_actor(
982 &mut self,
983 cx: &Context<'_, Self>,
984 params: ActorCallParams,
985 ) -> Result<Py<PyAny>, CallFunctionError> {
986 let local_state: Result<Vec<Py<PyAny>>> =
987 monarch_with_gil_blocking(GilSite::StreamCompute, |_py| {
988 params
989 .local_state
990 .into_iter()
991 .map(|elem| {
992 let pyobj = self.ref_to_pyobject(&elem)?;
993 Ok(pyobj.into_any())
994 })
995 .collect()
996 });
997
998 let (send, recv) = cx.open_once_port();
999 let state = LocalState {
1000 response_port: send,
1001 state: local_state?,
1002 };
1003 let x: u64 = params.seq.into();
1004 let message = LocalStateBrokerMessage::Set(x as usize, state);
1005
1006 let broker = BrokerId::new(params.broker_id).resolve(cx).await;
1007 broker.post(cx, message);
1008 let result = recv
1009 .recv()
1010 .await
1011 .map_err(|e| CallFunctionError::Error(e.into()))?;
1012
1013 result.map_err(|pyerr| anyhow::Error::msg(pyerr.to_string()).into())
1014 }
1015}
1016
1017#[async_trait]
1018#[handle(StreamMessage)]
1019impl StreamMessageHandler for StreamActor {
1020 async fn call_function(
1021 &mut self,
1022 cx: &Context<Self>,
1023 params: CallFunctionParams,
1024 device_meshes: HashMap<Ref, DeviceMesh>,
1025 remote_process_groups: HashMap<
1026 Ref,
1027 (DeviceMesh, Vec<String>, Arc<ActorHandle<NcclCommActor>>),
1028 >,
1029 ) -> Result<()> {
1030 if let Some((recording, _)) = self.get_defining_recording() {
1031 recording.messages.push(StreamMessage::CallFunction(
1032 params,
1033 device_meshes,
1034 remote_process_groups,
1035 ));
1036 return Ok(());
1037 }
1038
1039 params.function.panic_if_requested();
1040 self.try_define(
1041 cx,
1042 params.seq,
1043 params.results,
1044 ¶ms.mutates,
1045 async |self| {
1046 tokio::task::block_in_place(|| {
1047 self.call_python_fn_pytree(
1048 cx,
1049 params.function,
1050 params.args_kwargs,
1051 ¶ms.mutates,
1052 device_meshes,
1053 remote_process_groups,
1054 )
1055 .map(|results| results.into_leaves())
1056 })
1057 },
1058 )
1059 .await?;
1060 Ok(())
1061 }
1062
1063 async fn borrow_create(
1064 &mut self,
1065 cx: &Context<Self>,
1066 borrow: u64,
1067 tensor: Ref,
1068 first_use_sender: PortHandle<(Option<Event>, TensorCellResult)>,
1069 ) -> Result<()> {
1070 if let Some((recording, defined_borrows)) = self.get_defining_recording() {
1071 recording.messages.push(StreamMessage::BorrowCreate {
1072 borrow,
1073 tensor,
1074 first_use_sender,
1075 });
1076 ensure!(
1077 defined_borrows.insert(borrow),
1078 "duplicate borrow create in recording"
1079 );
1080 return Ok(());
1081 }
1082
1083 let pyobj_result = self
1084 .env
1085 .get(&tensor)
1086 .ok_or_else(|| anyhow!("invalid reference for borrow_create: {:#?}", tensor))?;
1087
1088 let result = match pyobj_result {
1089 Ok(pyobj) => monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1090 Ok(Self::pyobject_to_tensor(py, pyobj).unwrap())
1091 }),
1092 Err(e) => Err(e.clone()),
1093 };
1094
1095 let event = self.cuda_stream().map(|stream| stream.record_event(None));
1096 first_use_sender.post(cx, (event, result));
1097 Ok(())
1098 }
1099
1100 async fn borrow_first_use(
1101 &mut self,
1102 _cx: &Context<Self>,
1103 borrow: u64,
1104 result: Ref,
1105 first_use_receiver: Arc<Mutex<PortReceiver<(Option<Event>, TensorCellResult)>>>,
1106 ) -> Result<()> {
1107 if let Some((recording, _)) = self.get_defining_recording() {
1108 recording.messages.push(StreamMessage::BorrowFirstUse {
1109 borrow,
1110 result,
1111 first_use_receiver: first_use_receiver.clone(),
1112 });
1113 return Ok(());
1114 }
1115
1116 let (first_use_event, cell) =
1117 first_use_receiver
1118 .lock()
1119 .await
1120 .recv()
1121 .await
1122 .map_err(|err| {
1123 anyhow!(
1124 "failed receiving first use event for borrow {:?}: {:?}",
1125 borrow,
1126 err
1127 )
1128 })?;
1129
1130 if let Some(stream) = self.cuda_stream() {
1131 stream.wait_event(
1132 &mut first_use_event.expect("sent borrow to CUDA stream, expected a CUDA event"),
1133 );
1134 }
1135 match cell {
1136 Ok(cell) => {
1137 let pyobj = Self::tensor_to_pyobject(cell);
1138 self.env.insert(result, Ok(pyobj));
1139 }
1140 Err(err) => {
1141 self.env.insert(result, Err(err.clone()));
1142 }
1143 }
1144 Ok(())
1145 }
1146
1147 async fn borrow_last_use(
1148 &mut self,
1149 cx: &Context<Self>,
1150 borrow: u64,
1151 result: Ref,
1152 last_use_sender: PortHandle<(Option<Event>, TensorCellResult)>,
1153 ) -> Result<()> {
1154 if let Some((recording, _)) = self.get_defining_recording() {
1155 recording.messages.push(StreamMessage::BorrowLastUse {
1156 borrow,
1157 result,
1158 last_use_sender,
1159 });
1160 return Ok(());
1161 }
1162
1163 let event = self.cuda_stream().map(|stream| stream.record_event(None));
1164 let pyobj_or_err = self.env.remove(&result).ok_or(anyhow!(
1165 "Invalid reference for borrow_last_use: {result:#?}"
1166 ))?;
1167 let tensor = match pyobj_or_err {
1168 Ok(pyobj) => Ok(monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1169 Self::pyobject_to_tensor(py, &pyobj).unwrap()
1170 })),
1171 Err(e) => Err(e),
1172 };
1173
1174 last_use_sender.post(cx, (event, tensor));
1175 Ok(())
1176 }
1177
1178 async fn borrow_drop(
1179 &mut self,
1180 _cx: &Context<Self>,
1181 borrow: u64,
1182 last_use_receiver: Arc<Mutex<PortReceiver<(Option<Event>, TensorCellResult)>>>,
1183 ) -> Result<()> {
1184 if let Some((recording, defined_borrows)) = self.get_defining_recording() {
1185 recording.messages.push(StreamMessage::BorrowDrop {
1186 borrow,
1187 last_use_receiver: last_use_receiver.clone(),
1188 });
1189 ensure!(
1190 defined_borrows.remove(&borrow),
1191 "borrow drop for borrow not defined in recording"
1192 );
1193 return Ok(());
1194 }
1195
1196 let (last_use_event, _cell) =
1200 last_use_receiver.lock().await.recv().await.map_err(|err| {
1201 anyhow!(
1202 "failed receiving last use event for borrow {:?}: {:?}",
1203 borrow,
1204 err
1205 )
1206 })?;
1207
1208 if let Some(stream) = self.cuda_stream() {
1209 stream.wait_event(
1210 &mut last_use_event.expect("sent borrow to CUDA stream, expected a CUDA event"),
1211 );
1212 }
1213 Ok(())
1215 }
1216
1217 async fn delete_refs(&mut self, _cx: &Context<Self>, refs: Vec<Ref>) -> Result<()> {
1218 if let Some((recording, _)) = self.get_defining_recording() {
1219 recording.messages.push(StreamMessage::DeleteRefs(refs));
1220 return Ok(());
1221 }
1222
1223 for ref_ in refs.iter() {
1224 self.env.remove(ref_);
1225 }
1226 Ok(())
1227 }
1228
1229 async fn request_status(&mut self, _cx: &Context<Self>) -> Result<()> {
1230 if self.get_defining_recording().is_some() {
1231 bail!("request_status not allowed in recording");
1232 }
1233
1234 Ok(())
1235 }
1236
1237 async fn init_comm(
1238 &mut self,
1239 _cx: &Context<Self>,
1240 comm: ActorHandle<NcclCommActor>,
1241 ) -> Result<()> {
1242 if self.get_defining_recording().is_some() {
1243 bail!("init_comm not allowed in recording");
1244 }
1245
1246 self.comm = Some(comm);
1247 Ok(())
1248 }
1249
1250 async fn reduce(
1251 &mut self,
1252 cx: &Context<Self>,
1253 comm: Arc<ActorHandle<NcclCommActor>>,
1254 dim_size: i64,
1255 result: Ref,
1256 local_tensor: Ref,
1257 factory: Factory,
1258 reduction: Reduction,
1259 scatter: bool,
1260 in_place: bool,
1261 out: Option<Ref>,
1262 ) -> Result<()> {
1263 if let Some((recording, _)) = self.get_defining_recording() {
1264 recording.messages.push(StreamMessage::Reduce {
1265 comm,
1266 dim_size,
1267 result,
1268 local_tensor,
1269 factory,
1270 reduction,
1271 scatter,
1272 in_place,
1273 out,
1274 });
1275 return Ok(());
1276 }
1277
1278 let stream = self
1279 .cuda_stream()
1280 .expect("reductions not yet supported for non-CUDA workers")
1281 .clone();
1282 let input_cell = self.get_or_fake_on_err(local_tensor, &factory)?;
1283 let out_cell = out
1284 .map(|out| self.get_or_fake_on_err(out, &factory))
1285 .transpose()?;
1286 let output_cell = match reduction {
1287 Reduction::Stack => {
1288 if scatter {
1289 let output_cell = if in_place {
1290 input_cell.clone()
1291 } else {
1292 out_cell.unwrap_or({
1293 let borrow = input_cell.try_borrow().map_err(|e| anyhow!("{e:?}"))?;
1294 let cloned = deep_clone(&borrow);
1295 TensorCell::new(cloned)
1296 })
1297 };
1298 comm.all_to_all_single(cx, output_cell.clone(), input_cell, stream)
1299 .await?;
1300 output_cell
1301 } else {
1302 ensure!(
1303 !in_place,
1304 "in-place, non-scatter not supported for stack reduce"
1305 );
1306
1307 let output_cell = out_cell.unwrap_or({
1308 let sizes = [&[dim_size][..], &factory.size[..]].concat();
1310 let output =
1311 factory_empty(&sizes, factory.dtype, factory.layout, factory.device);
1312 TensorCell::new(output)
1313 });
1314
1315 comm.all_gather_into_tensor(cx, output_cell.clone(), input_cell, stream)
1316 .await?;
1317 output_cell
1318 }
1319 }
1320 Reduction::ReduceOp(op) => {
1321 if scatter {
1322 ensure!(!in_place, "in-place, scatter not supported for reduce");
1323
1324 let output_cell = out_cell.unwrap_or({
1325 let output = factory_empty(
1326 &factory.size[1..],
1327 factory.dtype,
1328 factory.layout,
1329 factory.device,
1330 );
1331 TensorCell::new(output)
1332 });
1333 comm.reduce_scatter_tensor(cx, output_cell.clone(), input_cell, op, stream)
1334 .await?;
1335 output_cell
1336 } else {
1337 let output_cell = if in_place {
1338 input_cell.clone()
1339 } else {
1340 out_cell.map_or(
1341 {
1342 let borrow =
1343 input_cell.try_borrow().map_err(|e| anyhow!("{e:?}"))?;
1344 let cloned = deep_clone(&borrow);
1345 Ok(TensorCell::new(cloned))
1346 },
1347 |out_cell| -> Result<_, anyhow::Error> {
1348 let mut out_borrow =
1349 out_cell.try_borrow_mut().map_err(|e| anyhow!("{e:?}"))?;
1350 let in_borrow =
1351 input_cell.try_borrow().map_err(|e| anyhow!("{e:?}"))?;
1352 out_borrow.copy_(&in_borrow);
1353 drop(out_borrow);
1354 Ok(out_cell)
1355 },
1356 )?
1357 };
1358
1359 comm.all_reduce(cx, output_cell.clone(), op, stream).await?;
1360 output_cell
1361 }
1362 }
1363 };
1364
1365 let pyobj = Self::tensor_to_pyobject(output_cell);
1366 self.env.insert(result, Ok(pyobj));
1367 Ok(())
1368 }
1369
1370 async fn send_tensor(
1371 &mut self,
1372 cx: &Context<Self>,
1373 result: Ref,
1374 from_rank: Option<usize>,
1375 to_rank: Option<usize>,
1376 tensor: Ref,
1377 factory: Factory,
1378 comm: Option<Arc<ActorHandle<NcclCommActor>>>,
1379 ) -> Result<()> {
1380 if let Some((recording, _)) = self.get_defining_recording() {
1381 recording.messages.push(StreamMessage::SendTensor {
1382 result,
1383 from_rank,
1384 to_rank,
1385 tensor,
1386 factory,
1387 comm,
1388 });
1389 return Ok(());
1390 }
1391
1392 if to_rank.is_none() && from_rank.is_none() {
1393 bail!("tried to send tensor without a to/from rank");
1394 }
1395
1396 if from_rank == to_rank {
1398 let input_cell: &std::result::Result<Py<PyAny>, Arc<SeqError>> = self
1399 .env
1400 .get(&tensor)
1401 .ok_or_else(|| anyhow!("tensor not found in stream: {tensor:#?}"))?;
1402 let output_cell: Result<Py<PyAny>, Arc<SeqError>> = match input_cell {
1403 Ok(pyobj) => {
1404 monarch_with_gil_blocking(
1405 GilSite::StreamCompute,
1406 |py| -> Result<Py<PyAny>, Arc<SeqError>> {
1407 let input_tensor = Self::pyobject_to_tensor(py, pyobj).unwrap();
1408 let borrow = input_tensor.try_borrow().unwrap();
1413 let cloned = deep_clone(&borrow);
1414 let cloned_cell = TensorCell::new(cloned);
1415 Ok(Self::tensor_to_pyobject(cloned_cell))
1416 },
1417 )
1418 }
1419 Err(err) => Err(err.clone()),
1420 };
1421 self.env.insert(result, output_cell);
1422 return Ok(());
1423 }
1424
1425 let comm = comm.context("send_tensor requires backend comm")?;
1426
1427 let mut messages = Vec::new();
1428
1429 if let Some(to_rank) = to_rank {
1430 let input_cell = self.get_or_fake_on_err(tensor, &factory)?;
1431 messages.push(CommMessage::Send(
1432 input_cell,
1433 to_rank.try_into().unwrap(),
1434 self.cuda_stream()
1435 .expect("tried to send_tensor on non-cuda stream")
1436 .clone(),
1437 cx.open_once_port().0,
1438 ));
1439 }
1440
1441 if let Some(from_rank) = from_rank {
1442 let output_cell = TensorCell::new(factory_empty(
1443 &factory.size,
1444 factory.dtype,
1445 factory.layout,
1446 factory.device,
1447 ));
1448 messages.push(CommMessage::Recv(
1449 output_cell.clone(),
1450 from_rank.try_into().unwrap(),
1451 self.cuda_stream()
1452 .expect("tried to send_tensor on non-cuda stream")
1453 .clone(),
1454 cx.open_once_port().0,
1455 ));
1456 let pyobj = Self::tensor_to_pyobject(output_cell);
1457 self.env.insert(result, Ok(pyobj));
1458 }
1459
1460 comm.group(
1461 cx,
1462 messages,
1463 self.cuda_stream()
1464 .expect("tried to send_tensor on non-cuda stream")
1465 .clone(),
1466 )
1467 .await?;
1468 Ok(())
1469 }
1470
1471 async fn send_value(
1472 &mut self,
1473 cx: &Context<Self>,
1474 seq: Seq,
1475 worker_actor_id: reference::ActorAddr,
1476 mutates: Vec<Ref>,
1477 function: Option<ResolvableFunction>,
1478 args_kwargs: ArgsKwargs,
1479 device_meshes: HashMap<Ref, DeviceMesh>,
1480 ) -> Result<()> {
1481 if self.respond_with_python_message {
1482 return self
1483 .send_value_python_message(cx, seq, mutates, function, args_kwargs, device_meshes)
1484 .await;
1485 }
1486
1487 let result = if let Some(function) = function {
1488 tokio::task::block_in_place(|| {
1490 self.call_python_fn_pytree(
1491 cx,
1492 function,
1493 args_kwargs,
1494 &mutates,
1495 device_meshes,
1496 HashMap::new(),
1497 )
1498 })
1499 } else {
1500 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1503 let (args, kwargs) = args_kwargs
1504 .to_python(py)
1505 .map_err(|e| CallFunctionError::Error(e.into()))?;
1506 match (args.len(), kwargs.len()) {
1507 (1, 0) => {
1508 let arg = args.get_item(0).map_err(SerializablePyErr::from_fn(py))?;
1509 arg.extract::<PyTree<Py<PyAny>>>()
1510 .map_err(SerializablePyErr::from_fn(py))?
1511 .try_into_map(|obj| {
1512 let bound_obj = obj.bind(py);
1513 if let Ok(ref_) = Ref::from_py_object(bound_obj) {
1514 self.ref_to_pyobject(&ref_)
1515 } else {
1516 Ok(obj)
1517 }
1518 })
1519 }
1520 _ => Err(CallFunctionError::TooManyArgsForValue(
1521 format!("args with {} elements", args.len()),
1522 format!("kwargs with {} elements", kwargs.len()),
1523 )),
1524 }
1525 })
1526 };
1527
1528 let value = match result {
1529 Ok(pyobject) => Ok(pyobject),
1530 Err(err) => {
1531 let err = self.report_seq_error(cx, seq, err).await?;
1532 for ref_ in mutates {
1533 self.env.insert(ref_, Err(err.clone()));
1534 }
1535 Err(WorkerError {
1536 backtrace: format!("{:?}", err),
1537 worker_actor_id,
1538 })
1539 }
1540 };
1541
1542 let result = match value {
1546 Ok(_value) => {
1547 unreachable!(
1549 "send_value should return early when respond_with_python_message is true"
1550 )
1551 }
1552 Err(e) => Err(e),
1553 };
1554 self.controller_actor.fetch_result(cx, seq, result).await?;
1555
1556 Ok(())
1557 }
1558
1559 async fn send_result_of_actor_call(
1560 &mut self,
1561 cx: &Context<Self>,
1562 params: ActorCallParams,
1563 ) -> anyhow::Result<()> {
1564 let seq = params.seq;
1565 let mutates = params.mutates.clone();
1566 self.try_define(cx, seq, vec![], &mutates, async |self| {
1567 let value = self.call_actor(cx, params).await?;
1568 let result = monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1569 pickle_python_result(py, value.into_bound(py), self.rank)
1570 })?;
1571 let result = wirevalue::Any::serialize(&result).unwrap();
1572 self.controller_actor
1573 .fetch_result(cx, seq, Ok(result))
1574 .await?;
1575 Ok(vec![])
1576 })
1577 .await
1578 }
1579
1580 async fn call_actor_method(
1581 &mut self,
1582 cx: &Context<Self>,
1583 params: ActorMethodParams,
1584 ) -> anyhow::Result<()> {
1585 let seq = params.call.seq;
1586 let mutates = params.call.mutates.clone();
1587 self.try_define(cx, seq, params.results, &mutates, async |self| {
1588 let result = self.call_actor(cx, params.call).await?;
1589 let result = monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1590 PyTree::<Py<PyAny>>::extract_bound(&result.into_bound(py))
1591 .map_err(SerializablePyErr::from_fn(py))
1592 })?;
1593 Ok(result.into_leaves())
1594 })
1595 .await
1596 }
1597
1598 async fn define_recording(&mut self, _cx: &Context<Self>, recording: Ref) -> Result<()> {
1599 if self.active_recording.is_some() {
1600 bail!("different recording already active");
1601 }
1602 match self.recordings.entry(recording) {
1603 Entry::Occupied(_) => bail!("recording {:?} already defined", recording),
1604 Entry::Vacant(entry) => entry.insert(Recording::new()),
1605 };
1606 self.active_recording = Some(RecordingState::Defining {
1607 recording,
1608 defined_borrows: HashSet::new(),
1609 });
1610 Ok(())
1611 }
1612
1613 async fn finalize_recording(&mut self, _cx: &Context<Self>, recording: Ref) -> Result<()> {
1614 match self.active_recording {
1615 Some(RecordingState::Defining {
1616 recording: active_recording,
1617 ref defined_borrows,
1618 }) if active_recording == recording => {
1619 ensure!(
1620 defined_borrows.is_empty(),
1621 "all borrows created within recording must be dropped within recording"
1622 );
1623 self.active_recording = None;
1624 }
1625 _ => bail!("cannot finalize recording that isn't active"),
1626 }
1627 Ok(())
1628 }
1629
1630 async fn recording_formal(
1631 &mut self,
1632 _cx: &Context<Self>,
1633 result: Ref,
1634 argument_index: usize,
1635 ) -> Result<()> {
1636 match self.get_defining_recording() {
1637 Some((recording, _)) => {
1638 recording.messages.push(StreamMessage::RecordingFormal {
1639 result,
1640 argument_index,
1641 });
1642 }
1643 None => bail!("recording_formal called outside of recording"),
1644 };
1645 Ok(())
1646 }
1647
1648 async fn recording_result(
1649 &mut self,
1650 _cx: &Context<Self>,
1651 result: Ref,
1652 output_index: usize,
1653 ) -> Result<()> {
1654 match self.get_defining_recording() {
1655 Some((recording, _)) => {
1656 recording.messages.push(StreamMessage::RecordingResult {
1657 result,
1658 output_index,
1659 });
1660 }
1661 None => bail!("recording_result called outside of recording"),
1662 };
1663 Ok(())
1664 }
1665
1666 async fn call_recording(
1667 &mut self,
1668 cx: &Context<Self>,
1669 seq: Seq,
1670 recording: Ref,
1671 results: Vec<Ref>,
1672 actuals: Vec<Ref>,
1673 ) -> Result<()> {
1674 if self.active_recording.is_some() {
1675 bail!("cannot call recording while another recording is active");
1676 }
1677
1678 let messages = match self.recordings.get(&recording) {
1679 Some(recording) => recording
1680 .messages
1681 .iter()
1682 .map(|message| message.clone_for_recording())
1683 .collect::<Vec<_>>(),
1684 None => bail!("recording {:?} not found", recording),
1685 };
1686
1687 self.active_recording = Some(RecordingState::Running);
1688
1689 let mut error: Option<Arc<SeqError>> = None;
1694 let mut all_defined_refs = HashSet::new();
1697 let mut all_mutated_refs = HashSet::new();
1700 let mut formal_to_actual_refs = HashMap::new();
1706 self.last_seq_error = None;
1708 for message in messages.into_iter() {
1709 let defined_refs = message.get_defined_refs();
1710 all_defined_refs.extend(defined_refs.clone());
1711
1712 let mutated_refs_with_formals = message.get_mutated_refs();
1713 all_mutated_refs.extend(mutated_refs_with_formals.iter().filter_map(|ref_| {
1714 match formal_to_actual_refs.get(ref_) {
1715 Some(actual_ref) => Some(*actual_ref),
1716 None => {
1717 if all_defined_refs.contains(ref_) {
1718 None
1719 } else {
1720 Some(*ref_)
1721 }
1722 }
1723 }
1724 }));
1725
1726 match message {
1727 StreamMessage::RecordingFormal {
1728 result: formal_ref,
1729 argument_index,
1730 } => match actuals.get(argument_index) {
1731 None => bail!("recording_formal called with too few arguments"),
1732 Some(actual_ref) => {
1733 formal_to_actual_refs.insert(formal_ref, *actual_ref);
1734 self.define_ref(formal_ref, *actual_ref)?;
1735 }
1736 },
1737 StreamMessage::RecordingResult {
1738 result: result_ref,
1739 output_index,
1740 } => match results.get(output_index) {
1741 None => bail!("recording_result called with too few results"),
1742 Some(actual_result_ref) => {
1743 self.define_ref(*actual_result_ref, result_ref)?;
1744 }
1745 },
1746 StreamMessage::DeleteRefs(ref refs) => {
1747 for ref_ in refs {
1748 all_defined_refs.remove(ref_);
1749 }
1750 StreamMessageHandler::handle(self, cx, message).await?;
1751 }
1752 StreamMessage::CallFunction { .. } if error.is_some() => {
1753 let error = error.clone().unwrap();
1759 for ref_ in defined_refs.iter().chain(mutated_refs_with_formals.iter()) {
1760 self.env.insert(*ref_, Err(error.clone()));
1761 }
1762 }
1763 StreamMessage::BorrowLastUse { ref result, .. } => {
1764 all_defined_refs.remove(result);
1765 StreamMessageHandler::handle(self, cx, message).await?;
1766 }
1767 StreamMessage::Reduce {
1768 local_tensor,
1769 ref out,
1770 ..
1771 } => {
1772 if error.is_none() {
1776 let inputs_to_check = [Some(local_tensor), *out]
1777 .iter()
1778 .filter_map(|r| *r)
1779 .collect::<Vec<_>>();
1780 error = self.get_first_error(inputs_to_check.as_slice())?;
1781 }
1782 StreamMessageHandler::handle(self, cx, message).await?;
1783 }
1784 StreamMessage::SendTensor {
1785 ref tensor,
1786 ref to_rank,
1787 ..
1788 } => {
1789 if to_rank.is_some() && error.is_none() {
1794 error = self.get_first_error(&[*tensor])?;
1795 }
1796 StreamMessageHandler::handle(self, cx, message).await?;
1797 }
1798 _ => {
1799 StreamMessageHandler::handle(self, cx, message).await?;
1800 }
1801 };
1802
1803 match (&error, self.last_seq_error.take()) {
1811 (None, Some(seq_err)) => {
1812 self.controller_actor
1814 .remote_function_failed(
1815 cx,
1816 seq,
1817 WorkerError {
1818 backtrace: format!("recording failed: {}", &seq_err),
1819 worker_actor_id: cx.self_addr().clone(),
1820 },
1821 )
1822 .await?;
1823 error = Some(seq_err)
1824 }
1825 _ => {}
1826 }
1827 }
1832
1833 StreamMessageHandler::handle(
1837 self,
1838 cx,
1839 StreamMessage::DeleteRefs(all_defined_refs.into_iter().collect()),
1840 )
1841 .await?;
1842
1843 if error.is_some() {
1846 for ref_ in results.iter().chain(all_mutated_refs.iter()) {
1847 self.env.insert(*ref_, Err(error.clone().unwrap()));
1848 }
1849 }
1850
1851 self.active_recording = None;
1852 Ok(())
1853 }
1854
1855 async fn set_ref_unit_tests_only(
1856 &mut self,
1857 _cx: &Context<Self>,
1858 reference: Ref,
1859 value: WireValue,
1860 ) -> Result<()> {
1861 let pyobj =
1862 monarch_with_gil_blocking(GilSite::StreamCompute, |py| -> PyResult<Py<PyAny>> {
1863 Ok(value.into_pyobject(py)?.unbind())
1864 })?;
1865 self.env.insert(reference, Ok(pyobj));
1866 Ok(())
1867 }
1868
1869 async fn set_tensor_ref_unit_tests_only(
1870 &mut self,
1871 _cx: &Context<Self>,
1872 reference: Ref,
1873 tensor_result: TensorCellResult,
1874 ) -> Result<()> {
1875 match tensor_result {
1876 Ok(tensor_cell) => {
1877 let pyobj = Self::tensor_to_pyobject(tensor_cell);
1878 self.env.insert(reference, Ok(pyobj));
1879 }
1880 Err(err) => {
1881 self.env.insert(reference, Err(err));
1882 }
1883 }
1884 Ok(())
1885 }
1886
1887 async fn get_ref_unit_tests_only(
1888 &mut self,
1889 _cx: &Context<Self>,
1890 reference: Ref,
1891 ) -> Result<Option<Result<WireValue, String>>> {
1892 use pyo3::types::PyBool;
1893 use pyo3::types::PyFloat;
1894 use pyo3::types::PyInt;
1895 use pyo3::types::PyList;
1896 use pyo3::types::PyNone;
1897 use pyo3::types::PyString;
1898 fn pyobject_to_wire(
1900 value: Result<Py<PyAny>, Arc<SeqError>>,
1901 ) -> Result<WireValue, Arc<SeqError>> {
1902 let pyobj = value?;
1903 monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1904 let bound = pyobj.bind(py);
1905 if bound.is_instance_of::<PyBool>() {
1907 Ok(WireValue::Bool(bound.extract::<bool>().unwrap()))
1908 } else if bound.is_instance_of::<PyInt>() {
1909 Ok(WireValue::Int(bound.extract::<i64>().unwrap()))
1910 } else if bound.is_instance_of::<PyList>() {
1911 if let Ok(val) = bound.extract::<Vec<i64>>() {
1912 Ok(WireValue::IntList(val))
1913 } else {
1914 Ok(WireValue::String(format!(
1915 "unsupported list type: {:?}",
1916 bound
1917 )))
1918 }
1919 } else if bound.is_instance_of::<PyFloat>() {
1920 Ok(WireValue::Double(bound.extract::<f64>().unwrap()))
1921 } else if bound.is_instance_of::<PyString>() {
1922 Ok(WireValue::String(bound.extract::<String>().unwrap()))
1923 } else if bound.is_instance_of::<PyNone>() {
1924 Ok(WireValue::None(()))
1925 } else {
1926 Ok(WireValue::String(format!(
1927 "unsupported pyobject type: {:?}",
1928 bound
1929 )))
1930 }
1931 })
1932 }
1933 Ok(self.env.get(&reference).map(|pyobj| {
1934 pyobject_to_wire(monarch_with_gil_blocking(GilSite::StreamCompute, |_py| {
1935 pyobj.clone()
1936 }))
1937 .map_err(|err| err.to_string())
1938 }))
1939 }
1940
1941 async fn get_tensor_ref_unit_tests_only(
1942 &mut self,
1943 _cx: &Context<Self>,
1944 reference: Ref,
1945 ) -> Result<Option<TensorCellResult>> {
1946 match self.env.get(&reference) {
1947 Some(Ok(pyobj)) => monarch_with_gil_blocking(GilSite::StreamCompute, |py| {
1948 match Self::pyobject_to_tensor(py, pyobj) {
1949 Ok(tensor) => Ok(Some(Ok(tensor.try_cpu().unwrap()))),
1950 Err(e) => bail!("expected tensor, got extraction error: {:?}", e),
1951 }
1952 }),
1953 Some(Err(err)) => Ok(Some(Err(err.clone()))),
1954 None => Ok(None),
1955 }
1956 }
1957}
1958
1959#[cfg(all(test, fbcode_build))]
1960mod tests {
1961 use hyperactor::actor::ActorStatus;
1962 use hyperactor::context;
1963 use hyperactor::supervision::ActorSupervisionEvent;
1964 use monarch_messages::controller::ControllerMessage;
1965 use monarch_messages::worker::StreamCreationMode;
1966 use monarch_types::PickledPyObject;
1967 use monarch_types::UniqueId;
1968 use pyo3::IntoPyObjectExt;
1969 use timed_test::async_timed_test;
1970 use tokio::sync::watch;
1971 use torch_sys_cuda::nccl::UniqueIdExt;
1972 use torch_sys2::factory_float_tensor;
1973 use torch_sys2::testing::allclose;
1974
1975 use super::*;
1976 use crate::comm::CommParams;
1977 use crate::test_util;
1978
1979 #[allow(dead_code)]
1980 fn fake_seq_error(err: anyhow::Error) -> Arc<SeqError> {
1981 Arc::new(SeqError {
1982 seq: 0.into(),
1983 error: err,
1984 })
1985 }
1986
1987 struct TestSetup {
1988 proc: Proc,
1989 stream_actor: ActorHandle<StreamActor>,
1990 client: reference::Client,
1991 #[allow(dead_code)]
1994 supervision_rx: PortReceiver<ActorSupervisionEvent>,
1995 #[allow(dead_code)]
1996 controller_rx: PortReceiver<ControllerMessage>,
1997 #[allow(dead_code)]
1998 controller_actor: reference::ActorRef<ControllerActor>,
1999 next_ref: Ref,
2000 }
2001
2002 impl TestSetup {
2003 async fn new() -> Result<Self> {
2004 Self::new_with_world_size(1).await
2005 }
2006
2007 async fn new_with_world_size(world_size: usize) -> Result<Self> {
2008 test_util::test_setup()?;
2009
2010 let proc = Proc::isolated();
2011 let (_, controller_actor, controller_rx) =
2012 proc.attach_actor::<ControllerActor, ControllerMessage>("controller")?;
2013 let client = proc.client("client");
2014 let (supervision_tx, supervision_rx) = client.open_port();
2015 proc.set_supervision_coordinator(supervision_tx.bind())?;
2016 let stream_actor = proc.spawn(StreamActor::new(StreamParams {
2017 world_size,
2018 rank: 0,
2019 creation_mode: StreamCreationMode::UseDefaultStream,
2020 id: 0.into(),
2021 device: Some(CudaDevice::new(0.into())),
2022 controller_actor: controller_actor.clone(),
2023 respond_with_python_message: false,
2024 }));
2025
2026 Ok(Self {
2027 proc,
2028 stream_actor,
2029 client,
2030 supervision_rx,
2031 controller_rx,
2032 controller_actor,
2033 next_ref: 0.into(),
2034 })
2035 }
2036
2037 fn next_ref(&mut self) -> Ref {
2038 let ref_ = self.next_ref;
2039 self.next_ref = Ref {
2040 id: self.next_ref.id + 1,
2041 };
2042 ref_
2043 }
2044
2045 async fn set_tensor(&mut self, reference: Ref, data: &[f32]) -> Result<()> {
2046 let tensor = TensorCell::new(factory_float_tensor(data, "cuda".parse().unwrap()));
2047 self.stream_actor
2048 .set_tensor_ref_unit_tests_only(&self.client, reference, Ok(tensor))
2049 .await
2050 }
2051
2052 async fn allclose(&mut self, reference: Ref, data: &[f32]) -> bool {
2053 let actual = self
2054 .stream_actor
2055 .get_tensor_ref_unit_tests_only(&self.client, reference)
2056 .await
2057 .unwrap()
2058 .unwrap()
2059 .unwrap();
2060
2061 allclose(
2063 &factory_float_tensor(data, "cpu".parse().unwrap()),
2064 &actual.borrow(),
2065 )
2066 .unwrap()
2067 }
2068
2069 #[allow(dead_code)]
2070 async fn validate_dependent_error(&mut self, reference: Ref, error: Arc<SeqError>) {
2071 let result_error = self
2072 .stream_actor
2073 .get_tensor_ref_unit_tests_only(&self.client, reference)
2074 .await
2075 .unwrap()
2076 .unwrap()
2077 .unwrap_err();
2078
2079 assert!(Arc::ptr_eq(&result_error, &error));
2080 }
2081 }
2082
2083 async fn assert_actor_failed_with_msg(
2084 status_rx: &mut watch::Receiver<ActorStatus>,
2085 expected_msg: String,
2086 ) {
2087 status_rx
2088 .wait_for(|s| matches!(s, ActorStatus::Failed(_)))
2089 .await
2090 .unwrap();
2091 let status = status_rx.borrow().clone();
2092 if let ActorStatus::Failed(msg) = status {
2093 assert!(msg.to_string().contains(&expected_msg));
2094 } else {
2095 panic!("expected ActorStatus::Failed, got {:?}", status);
2096 }
2097 }
2098
2099 async fn assert_refs_do_not_exist(test_setup: &TestSetup, refs: &[Ref]) {
2100 for ref_ in refs {
2101 assert!(
2102 test_setup
2103 .stream_actor
2104 .get_tensor_ref_unit_tests_only(&test_setup.client, *ref_)
2105 .await
2106 .unwrap()
2107 .is_none()
2108 );
2109 }
2110 }
2111
2112 #[allow(dead_code)]
2113 async fn fetch_result(
2114 cx: &impl context::Actor,
2115 stream_actor: ActorHandle<StreamActor>,
2116 seq: Seq,
2117 reference: Ref,
2118 ) {
2119 let ref_to_send = monarch_with_gil_blocking(GilSite::Test, |py| {
2120 PickledPyObject::pickle(&reference.into_bound_py_any(py).unwrap()).unwrap()
2121 });
2122
2123 stream_actor
2124 .send_value(
2125 cx,
2126 seq,
2127 stream_actor.actor_addr().clone(),
2128 Vec::new(),
2129 None,
2130 ArgsKwargs::from_wire_values(
2131 vec![WireValue::PyObject(ref_to_send)],
2132 HashMap::new(),
2133 )
2134 .unwrap(),
2135 HashMap::new(),
2136 )
2137 .await
2138 .unwrap()
2139 }
2140
2141 #[allow(dead_code)]
2142 async fn check_fetch_result_error(
2143 cx: &impl context::Actor,
2144 stream_actor: ActorHandle<StreamActor>,
2145 seq: Seq,
2146 reference: Ref,
2147 controller_rx: &mut PortReceiver<ControllerMessage>,
2148 expected_backtrace: &str,
2149 ) {
2150 fetch_result(cx, stream_actor, seq, reference).await;
2151
2152 let controller_msg = controller_rx.recv().await.unwrap();
2153 match controller_msg {
2154 ControllerMessage::FetchResult {
2155 seq: actual_seq,
2156 value: Err(err),
2157 } => {
2158 assert_eq!(actual_seq, seq);
2159 assert!(
2160 err.backtrace.contains(expected_backtrace),
2161 "backtrace did not contain {:?}: {:?}",
2162 expected_backtrace,
2163 err.backtrace
2164 );
2165 }
2166 _ => panic!("Unexpected controller message: {:?}", controller_msg),
2167 };
2168 }
2169
2170 #[allow(dead_code)]
2171 async fn check_fetch_result_value(
2172 cx: &impl context::Actor,
2173 stream_actor: ActorHandle<StreamActor>,
2174 seq: Seq,
2175 reference: Ref,
2176 controller_rx: &mut PortReceiver<ControllerMessage>,
2177 ) {
2178 fetch_result(cx, stream_actor, seq, reference).await;
2179
2180 let controller_msg = controller_rx.recv().await.unwrap();
2181 match controller_msg {
2182 ControllerMessage::FetchResult {
2183 value: Ok(_),
2184 seq: actual_seq,
2185 } => assert_eq!(seq, actual_seq),
2186 _ => panic!("Unexpected controller message: {:?}", controller_msg),
2187 };
2188 }
2189
2190 #[async_timed_test(timeout_secs = 60)]
2191 async fn test_define_recording_other_recording_active() -> Result<()> {
2192 let test_setup = TestSetup::new().await?;
2193 test_setup
2194 .stream_actor
2195 .define_recording(&test_setup.client, 0.into())
2196 .await?;
2197 test_setup
2198 .stream_actor
2199 .define_recording(&test_setup.client, 1.into())
2200 .await?;
2201 assert_actor_failed_with_msg(
2202 &mut test_setup.stream_actor.status(),
2203 "different recording already active".into(),
2204 )
2205 .await;
2206 Ok(())
2207 }
2208
2209 #[async_timed_test(timeout_secs = 60)]
2210 async fn test_define_recording_already_defined() -> Result<()> {
2211 let test_setup = TestSetup::new().await?;
2212 test_setup
2213 .stream_actor
2214 .define_recording(&test_setup.client, 0.into())
2215 .await?;
2216 test_setup
2217 .stream_actor
2218 .finalize_recording(&test_setup.client, 0.into())
2219 .await?;
2220 test_setup
2221 .stream_actor
2222 .define_recording(&test_setup.client, 0.into())
2223 .await?;
2224 assert_actor_failed_with_msg(
2225 &mut test_setup.stream_actor.status(),
2226 "already defined".into(),
2227 )
2228 .await;
2229 Ok(())
2230 }
2231
2232 #[async_timed_test(timeout_secs = 60)]
2233 async fn test_finalize_recording_other_recording_active() -> Result<()> {
2234 let test_setup = TestSetup::new().await?;
2235 test_setup
2236 .stream_actor
2237 .define_recording(&test_setup.client, 0.into())
2238 .await?;
2239 test_setup
2240 .stream_actor
2241 .finalize_recording(&test_setup.client, 1.into())
2242 .await?;
2243 assert_actor_failed_with_msg(
2244 &mut test_setup.stream_actor.status(),
2245 "cannot finalize recording that isn't active".into(),
2246 )
2247 .await;
2248 Ok(())
2249 }
2250
2251 #[async_timed_test(timeout_secs = 60)]
2252 async fn test_recording_formal_outside_recording() -> Result<()> {
2253 let test_setup = TestSetup::new().await?;
2254 test_setup
2255 .stream_actor
2256 .recording_formal(&test_setup.client, 0.into(), 0)
2257 .await?;
2258 assert_actor_failed_with_msg(
2259 &mut test_setup.stream_actor.status(),
2260 "recording_formal called outside of recording".into(),
2261 )
2262 .await;
2263 Ok(())
2264 }
2265
2266 #[async_timed_test(timeout_secs = 60)]
2267 async fn test_recording_result_outside_recording() -> Result<()> {
2268 let test_setup = TestSetup::new().await?;
2269 test_setup
2270 .stream_actor
2271 .recording_result(&test_setup.client, 0.into(), 0)
2272 .await?;
2273 assert_actor_failed_with_msg(
2274 &mut test_setup.stream_actor.status(),
2275 "recording_result called outside of recording".into(),
2276 )
2277 .await;
2278 Ok(())
2279 }
2280
2281 #[async_timed_test(timeout_secs = 60)]
2282 async fn test_call_recording_other_recording_active() -> Result<()> {
2283 let test_setup = TestSetup::new().await?;
2284 test_setup
2285 .stream_actor
2286 .define_recording(&test_setup.client, 0.into())
2287 .await?;
2288 test_setup
2289 .stream_actor
2290 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2291 .await?;
2292 assert_actor_failed_with_msg(
2293 &mut test_setup.stream_actor.status(),
2294 "cannot call recording while another recording is active".into(),
2295 )
2296 .await;
2297 Ok(())
2298 }
2299
2300 #[async_timed_test(timeout_secs = 60)]
2301 async fn test_call_recording_not_found() -> Result<()> {
2302 let test_setup = TestSetup::new().await?;
2303 test_setup
2304 .stream_actor
2305 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2306 .await?;
2307 assert_actor_failed_with_msg(&mut test_setup.stream_actor.status(), "not found".into())
2308 .await;
2309 Ok(())
2310 }
2311
2312 #[async_timed_test(timeout_secs = 60)]
2313 async fn test_recording_formal_too_few_arguments() -> Result<()> {
2314 let test_setup = TestSetup::new().await?;
2315
2316 test_setup
2317 .stream_actor
2318 .define_recording(&test_setup.client, 0.into())
2319 .await?;
2320
2321 test_setup
2322 .stream_actor
2323 .recording_formal(&test_setup.client, 1.into(), 0)
2324 .await?;
2325
2326 test_setup
2327 .stream_actor
2328 .finalize_recording(&test_setup.client, 0.into())
2329 .await?;
2330
2331 test_setup
2332 .stream_actor
2333 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2334 .await?;
2335
2336 assert_actor_failed_with_msg(
2337 &mut test_setup.stream_actor.status(),
2338 "recording_formal called with too few arguments".into(),
2339 )
2340 .await;
2341 Ok(())
2342 }
2343
2344 #[async_timed_test(timeout_secs = 60)]
2345 async fn test_recording_result_too_few_results() -> Result<()> {
2346 let test_setup = TestSetup::new().await?;
2347
2348 test_setup
2349 .stream_actor
2350 .define_recording(&test_setup.client, 0.into())
2351 .await?;
2352
2353 test_setup
2354 .stream_actor
2355 .recording_result(&test_setup.client, 1.into(), 0)
2356 .await?;
2357
2358 test_setup
2359 .stream_actor
2360 .finalize_recording(&test_setup.client, 0.into())
2361 .await?;
2362
2363 test_setup
2364 .stream_actor
2365 .call_recording(&test_setup.client, 0.into(), 0.into(), vec![], vec![])
2366 .await?;
2367
2368 assert_actor_failed_with_msg(
2369 &mut test_setup.stream_actor.status(),
2370 "recording_result called with too few results".into(),
2371 )
2372 .await;
2373 Ok(())
2374 }
2375
2376 #[async_timed_test(timeout_secs = 60)]
2377 async fn test_basic_call_recording() -> Result<()> {
2378 let mut test_setup = TestSetup::new().await?;
2379
2380 test_setup
2384 .stream_actor
2385 .define_recording(&test_setup.client, 0.into())
2386 .await?;
2387
2388 let formal0_ref = 1.into();
2389 let formal0_index = 1;
2390 test_setup
2391 .stream_actor
2392 .recording_formal(&test_setup.client, formal0_ref, formal0_index)
2393 .await?;
2394
2395 let formal1_ref = 2.into();
2396 let formal1_index = 0;
2397 test_setup
2398 .stream_actor
2399 .recording_formal(&test_setup.client, formal1_ref, formal1_index)
2400 .await?;
2401
2402 let result0_ref = formal0_ref;
2403 let result0_index = 0;
2404 test_setup
2405 .stream_actor
2406 .recording_result(&test_setup.client, result0_ref, result0_index)
2407 .await?;
2408
2409 let result1_ref = formal1_ref;
2410 let result1_index = 1;
2411 test_setup
2412 .stream_actor
2413 .recording_result(&test_setup.client, result1_ref, result1_index)
2414 .await?;
2415
2416 test_setup
2417 .stream_actor
2418 .finalize_recording(&test_setup.client, 0.into())
2419 .await?;
2420
2421 let actual0_ref = 3.into();
2422 test_setup.set_tensor(actual0_ref, &[1.0, 2.0, 3.0]).await?;
2423
2424 let actual1_ref = 4.into();
2425 test_setup.set_tensor(actual1_ref, &[4.0, 5.0]).await?;
2426
2427 let actual_result0_ref = 5.into();
2430 let actual_result1_ref = 6.into();
2431 test_setup
2432 .stream_actor
2433 .call_recording(
2434 &test_setup.client,
2435 0.into(),
2436 0.into(),
2437 vec![actual_result0_ref, actual_result1_ref],
2438 vec![actual0_ref, actual1_ref],
2439 )
2440 .await?;
2441
2442 assert!(test_setup.allclose(actual_result0_ref, &[4.0, 5.0]).await);
2444 assert!(
2445 test_setup
2446 .allclose(actual_result1_ref, &[1.0, 2.0, 3.0])
2447 .await
2448 );
2449
2450 assert_refs_do_not_exist(&test_setup, &[formal0_ref, formal1_ref]).await;
2453 Ok(())
2454 }
2455
2456 #[async_timed_test(timeout_secs = 60)]
2457 async fn test_request_status_in_recording() -> Result<()> {
2458 let test_setup = TestSetup::new().await?;
2459 test_setup
2460 .stream_actor
2461 .define_recording(&test_setup.client, 0.into())
2462 .await?;
2463 test_setup
2464 .stream_actor
2465 .request_status(&test_setup.client)
2466 .await
2467 .expect_err("request_status should have failed");
2468 assert_actor_failed_with_msg(
2469 &mut test_setup.stream_actor.status(),
2470 "request_status not allowed in recording".into(),
2471 )
2472 .await;
2473 Ok(())
2474 }
2475
2476 #[async_timed_test(timeout_secs = 60)]
2477 async fn test_init_comm_in_recording() -> Result<()> {
2478 let test_setup = TestSetup::new().await?;
2479 test_setup
2480 .stream_actor
2481 .define_recording(&test_setup.client, 0.into())
2482 .await?;
2483
2484 let dummy_comm = test_setup.proc.spawn(
2485 NcclCommActor::new(CommParams::New {
2486 device: CudaDevice::new(0.into()),
2487 unique_id: UniqueId::new_nccl()?,
2488 world_size: 1,
2489 rank: 0,
2490 })
2491 .await
2492 .unwrap(),
2493 );
2494
2495 test_setup
2496 .stream_actor
2497 .init_comm(&test_setup.client, dummy_comm)
2498 .await?;
2499 assert_actor_failed_with_msg(
2500 &mut test_setup.stream_actor.status(),
2501 "init_comm not allowed in recording".into(),
2502 )
2503 .await;
2504 Ok(())
2505 }
2506
2507 #[async_timed_test(timeout_secs = 60)]
2508 async fn test_borrow_create_duplicate_borrow() -> Result<()> {
2509 let mut test_setup = TestSetup::new().await?;
2510 test_setup
2511 .stream_actor
2512 .define_recording(&test_setup.client, 0.into())
2513 .await?;
2514
2515 let borrow_id = 1;
2516 let tensor_ref = test_setup.next_ref();
2517 let (first_use_sender, _first_use_receiver) = test_setup.client.open_port();
2518
2519 test_setup
2520 .stream_actor
2521 .borrow_create(
2522 &test_setup.client,
2523 borrow_id,
2524 tensor_ref,
2525 first_use_sender.clone(),
2526 )
2527 .await?;
2528
2529 test_setup
2530 .stream_actor
2531 .borrow_create(&test_setup.client, borrow_id, tensor_ref, first_use_sender)
2532 .await?;
2533
2534 assert_actor_failed_with_msg(
2535 &mut test_setup.stream_actor.status(),
2536 "duplicate borrow create in recording".into(),
2537 )
2538 .await;
2539
2540 Ok(())
2541 }
2542
2543 #[async_timed_test(timeout_secs = 60)]
2544 async fn test_borrow_drop_borrow_not_defined() -> Result<()> {
2545 let test_setup = TestSetup::new().await?;
2546 test_setup
2547 .stream_actor
2548 .define_recording(&test_setup.client, 0.into())
2549 .await?;
2550
2551 let borrow_id = 1;
2552 let (_last_use_sender, last_use_receiver) = test_setup.client.open_port();
2553
2554 test_setup
2555 .stream_actor
2556 .borrow_drop(
2557 &test_setup.client,
2558 borrow_id,
2559 Arc::new(Mutex::new(last_use_receiver)),
2560 )
2561 .await?;
2562
2563 assert_actor_failed_with_msg(
2564 &mut test_setup.stream_actor.status(),
2565 "borrow drop for borrow not defined in recording".into(),
2566 )
2567 .await;
2568
2569 Ok(())
2570 }
2571
2572 #[async_timed_test(timeout_secs = 60)]
2573 async fn test_borrow_not_dropped_before_finalize() -> Result<()> {
2574 let mut test_setup = TestSetup::new().await?;
2575 test_setup
2576 .stream_actor
2577 .define_recording(&test_setup.client, 0.into())
2578 .await?;
2579
2580 let borrow_id = 1;
2581 let tensor_ref = test_setup.next_ref();
2582 let (first_use_sender, _first_use_receiver) = test_setup.client.open_port();
2583
2584 test_setup
2585 .stream_actor
2586 .borrow_create(
2587 &test_setup.client,
2588 borrow_id,
2589 tensor_ref,
2590 first_use_sender.clone(),
2591 )
2592 .await?;
2593
2594 test_setup
2596 .stream_actor
2597 .finalize_recording(&test_setup.client, 0.into())
2598 .await?;
2599
2600 assert_actor_failed_with_msg(
2601 &mut test_setup.stream_actor.status(),
2602 "all borrows created within recording must be dropped within recording".into(),
2603 )
2604 .await;
2605
2606 Ok(())
2607 }
2608}