1use sha2::{Digest, Sha256};
2use std::{collections::BTreeSet, sync::Arc};
3
4use super::super::{
5 classify_device_error, AllocationKind, AllocationLifetime, BackingInitializationEncodeError,
6 BufferUsage, CompletionHandle, CompletionReaper, CompletionReservation,
7 DefinitelyNotSubmittedRetryAuthority, DeviceBatchingForm, DeviceCommandBatch,
8 DeviceCommandLogicalWork, DeviceComputePathRequirement, DeviceReusableExecutionCapture,
9 DeviceReusableExecutionInvocation, DeviceReusableExecutionProgram,
10 DeviceReusableExecutionProgramId, DeviceReusableExecutionTopologyFingerprint, DeviceRuntime,
11 DeviceTimingMode, ExecutablePlanView, ExecutionIdentityEnvelope, ExecutionIdentityParts,
12 ExecutionLane, InvocationResourceLease, LaneSubmitOutcome, NodeId, NodeInvocationId,
13 OperationId, ParticipantNodeKey, PreparedStepSubmissionWave, ProgramBindingNodeBinding,
14 ProviderId, ResourceId, SpanId, StepParticipantFrameAssignment, SubmissionWavePurpose,
15 TrustedActiveSequenceBinding, VNextError, EXECUTION_IDENTITY_VERSION,
16};
17use super::backing_upload::encode_submission_wave_backing_upload;
18use super::determinism::{
19 SubmissionWaveDeterminismHandle, SubmissionWaveDeterminismReadbackPlan,
20 SubmissionWaveDeterminismRestore,
21};
22use super::dispatch_contract::{
23 BoundDeviceSubmissionAttribution, DisabledSubmissionWaveDispatchTimingSink,
24 DispatchRetryAuthority, OperationDispatchError, ProfiledSubmissionHandle,
25 SubmissionExecutionPolicy, SubmissionScratchInitialization, SubmissionWaveDispatchError,
26 SubmissionWaveDispatchStage, SubmissionWaveDispatchStageTimer,
27 SubmissionWaveDispatchTimingSink, SubmissionWaveInputUpload,
28};
29use super::foundation::invalid_operation;
30use super::invocation::OperationInvocationResources;
31use super::workspace_encoding::{
32 encode_provider_workspace_initialization, encode_submission_wave_workspace_initializations,
33};
34use super::{
35 translate_step_participant_upload_range, BatchOperationIdentity, BatchOperationNodeIdentity,
36 BatchOperationParticipantIdentity, BatchedOperationInvocation, BoundOperationProvider,
37 OperationInvocation, ProviderReplayEquivalence, ResolvedValueRole, ReusableExecutionTopology,
38 ReusableExecutionTopologyRequest, TensorAccess,
39};
40
41fn validate_program_binding_patch(
43 resolved: &dyn ExecutablePlanView,
44 node_identity: &BatchOperationNodeIdentity,
45 program_binding: Option<&ProgramBindingNodeBinding>,
46 encoded_program_binding_count: usize,
47 program_binding_resources: &mut BTreeSet<ResourceId>,
48) -> Result<(), VNextError> {
49 if program_binding.is_some() != (encoded_program_binding_count == 1) {
50 return Err(invalid_operation(
51 "compiled program binding slot and provider patch cardinality differ",
52 ));
53 }
54 let Some(program_binding) = program_binding else {
55 return Ok(());
56 };
57 let (plan_node_index, binding_resource) = resolved
58 .execution_plan()
59 .payload()
60 .nodes()
61 .iter()
62 .enumerate()
63 .find(|(_, node)| node.id() == node_identity.node_id())
64 .and_then(|(node_index, node)| {
65 node.binding_resource()
66 .map(|resource| (node_index, resource))
67 })
68 .ok_or_else(|| {
69 invalid_operation("program binding requires a provider-owned binding workspace")
70 })?;
71 if program_binding.node_index() != plan_node_index
72 || program_binding.slot().node_id() != node_identity.node_id()
73 || program_binding.slot().resource_id() != binding_resource
74 {
75 return Err(invalid_operation(
76 "provider program binding differs from its compiled node slot",
77 ));
78 }
79 if !program_binding_resources.insert(binding_resource.clone()) {
80 return Err(invalid_operation(
81 "program bindings from different nodes alias one binding workspace",
82 ));
83 }
84 Ok(())
85}
86
87fn validate_determinism_replay_coverage(
88 program: &DeviceReusableExecutionProgram,
89 node_count: usize,
90 eager_boundary_node_indices: &[u32],
91 batch_identity: &BatchOperationIdentity,
92) -> Result<(), VNextError> {
93 if node_count == 0
94 || eager_boundary_node_indices
95 .windows(2)
96 .any(|pair| pair[0] >= pair[1])
97 {
98 return Err(invalid_operation(
99 "determinism replay topology has no nodes or non-canonical eager boundaries",
100 ));
101 }
102 if program.node_count() as usize != node_count
103 || program.eager_boundary_node_indices() != eager_boundary_node_indices
104 {
105 return Err(invalid_operation(
106 "determinism reusable program coverage differs from the live provider topology",
107 ));
108 }
109 if !program.is_determinism_ready() {
110 let gaps = program
111 .gaps()
112 .iter()
113 .take(8)
114 .map(|gap| {
115 let node_index = gap.node_index() as usize;
116 format!(
117 "node_index={} node_id={} provider_id={} operation_id={} reason={}",
118 gap.node_index(),
119 batch_identity
120 .node_id_at(node_index)
121 .map_or("<missing>", NodeId::as_str),
122 batch_identity
123 .provider_id_at(node_index)
124 .map_or("<missing>", ProviderId::as_str),
125 batch_identity
126 .operation_id_at(node_index)
127 .map_or("<missing>", OperationId::as_str),
128 gap.reason().as_str(),
129 )
130 })
131 .collect::<Vec<_>>()
132 .join("; ");
133 return Err(invalid_operation(format!(
134 "determinism reusable program has {} non-resident replay-eligible gap(s): {gaps}",
135 program.gaps().len()
136 )));
137 }
138 let mut coverage = vec![0_u8; node_count];
139 for node_index in eager_boundary_node_indices {
140 let node_index = usize::try_from(*node_index)
141 .map_err(|_| invalid_operation("determinism eager boundary exceeds usize"))?;
142 let marker = coverage.get_mut(node_index).ok_or_else(|| {
143 invalid_operation("determinism eager boundary is outside the submission wave")
144 })?;
145 *marker = 1;
146 }
147 for segment in program.segments() {
148 let start = usize::try_from(segment.start_node_index())
149 .map_err(|_| invalid_operation("determinism replay segment start exceeds usize"))?;
150 let end = usize::try_from(segment.end_node_index())
151 .map_err(|_| invalid_operation("determinism replay segment end exceeds usize"))?;
152 let range = coverage.get_mut(start..end).ok_or_else(|| {
153 invalid_operation("determinism replay segment is outside the submission wave")
154 })?;
155 if range.iter().any(|marker| *marker != 0) {
156 return Err(invalid_operation(
157 "determinism replay segment overlaps another segment or a declared eager boundary",
158 ));
159 }
160 range.fill(2);
161 }
162 if coverage.iter().any(|marker| *marker == 0) {
163 return Err(invalid_operation(
164 "determinism reusable program does not cover every replay-eligible node",
165 ));
166 }
167 if !coverage.iter().any(|marker| *marker == 2) {
168 return Err(invalid_operation(
169 "determinism replay topology contains no resident replay node",
170 ));
171 }
172 Ok(())
173}
174
175struct ReusableExecutionWaveAuthority {
176 program_id: DeviceReusableExecutionProgramId,
177 eager_boundary_node_indices: Vec<u32>,
178}
179
180pub struct OperationDispatch;
181
182impl OperationDispatch {
183 fn trusted_submission_node_identity(
184 resolved: &dyn ExecutablePlanView,
185 active: &TrustedActiveSequenceBinding,
186 frame: StepParticipantFrameAssignment,
187 node_index: usize,
188 ) -> Result<ExecutionIdentityEnvelope, VNextError> {
189 active.ensure_open_for_emission()?;
190 let plan = resolved.execution_plan();
191 let node_count = u64::try_from(plan.payload().nodes().len())
192 .map_err(|_| invalid_operation("immutable plan node count exceeds u64"))?;
193 let node_index = u64::try_from(node_index)
194 .map_err(|_| invalid_operation("submission wave node index exceeds u64"))?;
195 let node = plan
196 .payload()
197 .nodes()
198 .get(node_index as usize)
199 .ok_or_else(|| invalid_operation("submission wave node index is out of bounds"))?;
200 let completed_frames = frame.frame_id().get() - 1;
201 let node_invocation = completed_frames
202 .checked_mul(node_count)
203 .and_then(|value| value.checked_add(node_index))
204 .and_then(|value| value.checked_add(1))
205 .ok_or_else(|| invalid_operation("node invocation id space is exhausted"))?;
206 let node_invocation_id = NodeInvocationId::try_from(node_invocation)?;
207
208 let events_per_frame = node_count
214 .checked_mul(3)
215 .and_then(|value| value.checked_add(2))
216 .ok_or_else(|| invalid_operation("execution event sequence space is exhausted"))?;
217 let sequence = completed_frames
218 .checked_mul(events_per_frame)
219 .and_then(|value| value.checked_add(node_index.checked_mul(3)?))
220 .and_then(|value| value.checked_add(5))
221 .ok_or_else(|| invalid_operation("execution event sequence space is exhausted"))?;
222
223 let span_root = format!("vnext/request/{}", active.fingerprint());
224 let node_span = SpanId::new(format!(
225 "{span_root}/frame/{}/node/{node_invocation}",
226 frame.frame_id()
227 ))?;
228 let operation_span = SpanId::new(format!("{node_span}/operation"))?;
229 let provisioning = active.static_provisioning_identity();
230 ExecutionIdentityEnvelope::new(ExecutionIdentityParts {
231 version: EXECUTION_IDENTITY_VERSION,
232 run_id: active.run_id().clone(),
233 request_id: active.request_id().clone(),
234 sequence,
235 plan_id: Some(plan.payload().plan_id().clone()),
236 plan_hash: Some(plan.plan_hash().clone()),
237 frame_id: Some(frame.frame_id()),
238 node_invocation_id: Some(node_invocation_id),
239 node_id: Some(node.id().clone()),
240 operation_id: Some(node.operation_id().clone()),
241 provider_id: Some(node.selection().selected_provider().clone()),
242 device_id: Some(plan.payload().device_id().clone()),
243 resource_pool_id: active.static_pool_id(),
244 resource_pool_identity_fingerprint: active
245 .static_pool_identity_fingerprint_ref()
246 .map(str::to_owned),
247 provisioning_run_id: provisioning.map(|identity| identity.run_id().clone()),
248 provisioning_request_id: provisioning.map(|identity| identity.request_id().clone()),
249 transaction_id: provisioning.map(|identity| identity.transaction_id().clone()),
250 active_sequence_slot: Some(active.sequence_authority().sparse_id()),
251 admission_generation: Some(active.sequence_authority().generation()),
252 activation_epoch: Some(active.activation_epoch()),
253 runtime_implementation_fingerprint: Some(
254 active.runtime_implementation_fingerprint().to_owned(),
255 ),
256 active_sequence_fingerprint: Some(active.fingerprint().to_owned()),
257 completed_sequence_fingerprint: None,
258 aborted_sequence_fingerprint: None,
259 resource_id: None,
260 resource_generation: None,
261 resource_batch_fingerprint: None,
262 span_id: operation_span,
263 parent_span_id: Some(node_span),
264 async_links: Vec::new(),
265 })
266 }
267
268 #[allow(clippy::too_many_arguments)]
269 fn bind_node_identity<'binding, R, I>(
270 resolved: &dyn ExecutablePlanView,
271 participant_identities: Vec<ExecutionIdentityEnvelope>,
272 active_bindings: I,
273 resources: OperationInvocationResources<'_, R>,
274 lane: &Arc<ExecutionLane<R>>,
275 node_index: u32,
276 participant_start: u32,
277 ) -> Result<BatchOperationNodeIdentity, VNextError>
278 where
279 R: DeviceRuntime,
280 I: ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
281 {
282 let plan = resolved.execution_plan();
283 let node_id = resources.node_id()?;
284 let node = plan
285 .payload()
286 .nodes()
287 .iter()
288 .find(|node| node.id() == node_id)
289 .ok_or_else(|| invalid_operation(format!("plan has no node `{node_id}`")))?;
290 let validate_common_authority = match resources {
292 OperationInvocationResources::Invocation(_) => true,
293 OperationInvocationResources::Wave {
294 node_index: resource_node_index,
295 ..
296 } => resource_node_index == 0,
297 };
298 let participant_count = resources.participant_count()?;
299 let frames = resources.participant_frames()?;
300 let plan_identity_matches = validate_common_authority
301 .then(|| {
302 resources.plan_identity_matches(
303 plan.payload().plan_id(),
304 plan.plan_hash(),
305 plan.payload().device_id(),
306 )
307 })
308 .transpose()?;
309 if participant_identities.is_empty()
310 || participant_identities.len() != participant_count
311 || participant_identities.len() != frames.len()
312 || participant_identities.len() != active_bindings.len()
313 || resources.prepared_participant_count()? != participant_count
314 || validate_common_authority && {
315 !plan_identity_matches.expect("common wave authority requested plan evidence")
316 || !Arc::ptr_eq(resources.runtime(), lane.runtime_arc())
317 || resources.step_resources().execution_lane().id() != lane.id()
318 || lane.descriptor() != resolved.device()
319 || lane.descriptor() != resolved.capabilities().device()
320 || lane.descriptor().runtime_implementation_fingerprint
321 != plan.payload().device_runtime_implementation_fingerprint()
322 }
323 {
324 return Err(invalid_operation(
325 "batch node identity inputs differ from submission resources, plan, or lane",
326 ));
327 }
328 let mut participant_projections = Vec::with_capacity(participant_identities.len());
329 for (local_index, (identity, active)) in participant_identities
330 .into_iter()
331 .zip(active_bindings)
332 .enumerate()
333 {
334 let participant = resources.participant(local_index)?;
335 let frame = *frames
336 .get(local_index)
337 .ok_or_else(|| invalid_operation("operation participant frame is missing"))?;
338 let session = validate_common_authority
339 .then(|| resources.participant_session_identity(local_index))
340 .transpose()?;
341 let key =
342 ParticipantNodeKey::new(frame.participant(), frame.frame_id(), node.id().clone());
343 let parts = identity.parts();
344 if key.sequence_authority() != participant.sequence_authority()
345 || key.request_authority() != participant.request_authority()
346 || key.frame_id() != frame.frame_id()
347 || validate_common_authority && {
348 let session = session
349 .as_ref()
350 .expect("common wave authority requested session evidence");
351 active.sequence_authority() != participant.sequence_authority()
352 || active.coordinator_id() != resources.coordinator_id()?
353 || active.run_id() != participant.run_id()
354 || active.request_id() != participant.request_id()
355 || !active.matches_sequence_session(session.0, session.1)
356 || active.plan().plan_id() != plan.payload().plan_id()
357 || active.plan().plan_hash() != plan.plan_hash()
358 || active.plan().device_id() != plan.payload().device_id()
359 || active.runtime_implementation_fingerprint()
360 != plan.payload().device_runtime_implementation_fingerprint()
361 }
362 || parts.run_id != *active.run_id()
363 || parts.request_id != *active.request_id()
364 || parts.plan_id.as_ref() != Some(plan.payload().plan_id())
365 || parts.plan_hash.as_ref() != Some(plan.plan_hash())
366 || parts.frame_id != Some(frame.frame_id())
367 || parts.node_invocation_id.is_none()
368 || parts.node_id.as_ref() != Some(node.id())
369 || parts.operation_id.as_ref() != Some(node.operation_id())
370 || parts.provider_id.as_ref() != Some(node.selection().selected_provider())
371 || parts.device_id.as_ref() != Some(plan.payload().device_id())
372 || parts.active_sequence_slot != Some(active.sequence_authority().sparse_id())
373 || parts.admission_generation != Some(active.sequence_authority().generation())
374 || parts.activation_epoch != Some(active.activation_epoch())
375 || parts.runtime_implementation_fingerprint.as_deref()
376 != Some(active.runtime_implementation_fingerprint())
377 || parts.active_sequence_fingerprint.as_deref() != Some(active.fingerprint())
378 || parts.completed_sequence_fingerprint.is_some()
379 || parts.aborted_sequence_fingerprint.is_some()
380 || parts.resource_id.is_some()
381 || parts.resource_generation.is_some()
382 || parts.resource_batch_fingerprint.is_some()
383 {
384 return Err(invalid_operation(format!(
385 "batch node {node_index} participant {local_index} differs from its resource, frame, session, or plan identity"
386 )));
387 }
388 let local_index = u32::try_from(local_index)
389 .map_err(|_| invalid_operation("batch participant index exceeds u32"))?;
390 participant_projections.push(BatchOperationParticipantIdentity::new(
391 participant_start.checked_add(local_index).ok_or_else(|| {
392 invalid_operation("physical batch participant index overflows u32")
393 })?,
394 key,
395 identity,
396 ));
397 }
398 BatchOperationNodeIdentity::from_validated(
399 node_index,
400 node.id().clone(),
401 node.operation_id().clone(),
402 node.selection().selected_provider().clone(),
403 node.provider_implementation_fingerprint().to_owned(),
404 node.provider_execution_semantics(),
405 resources.work_shape()?.fingerprint().to_owned(),
406 participant_projections,
407 )
408 }
409
410 #[allow(clippy::too_many_arguments)]
411 pub fn bind_batch_identity<R>(
412 resolved: &dyn ExecutablePlanView,
413 participant_identities: Vec<ExecutionIdentityEnvelope>,
414 active_bindings: &[TrustedActiveSequenceBinding],
415 invocation_resources: &InvocationResourceLease<R>,
416 lane: &Arc<ExecutionLane<R>>,
417 ) -> Result<BatchOperationIdentity, VNextError>
418 where
419 R: DeviceRuntime,
420 {
421 let plan = resolved.execution_plan();
422 let resources = OperationInvocationResources::Invocation(invocation_resources);
423 let node_identity = Self::bind_node_identity(
424 resolved,
425 participant_identities,
426 active_bindings.iter(),
427 resources,
428 lane,
429 0,
430 0,
431 )?;
432 BatchOperationIdentity::from_validated(
433 resources.batch_step_id(),
434 resources.batch_invocation_id(),
435 plan.payload().plan_id().clone(),
436 plan.plan_hash().clone(),
437 plan.payload().device_id().clone(),
438 plan.payload()
439 .device_runtime_implementation_fingerprint()
440 .to_owned(),
441 lane.id(),
442 resources.backing_fingerprint().to_owned(),
443 vec![node_identity],
444 )
445 }
446
447 fn bind_submission_wave_identity_from_envelopes<'binding, R, I>(
448 resolved: &dyn ExecutablePlanView,
449 participant_identities: Vec<Vec<ExecutionIdentityEnvelope>>,
450 active_bindings: I,
451 wave: &PreparedStepSubmissionWave<R>,
452 lane: &Arc<ExecutionLane<R>>,
453 ) -> Result<BatchOperationIdentity, VNextError>
454 where
455 R: DeviceRuntime,
456 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
457 {
458 let plan = resolved.execution_plan();
459 if wave.nodes().is_empty()
460 || wave.execution_lane_id() != lane.id()
461 || participant_identities.len() != wave.nodes().len()
462 || active_bindings.len() == 0
463 || wave.claimed_backing().node_count() != wave.nodes().len()
464 || wave.claimed_backing().plan_node_count() != plan.payload().nodes().len()
465 || wave
466 .nodes()
467 .windows(2)
468 .any(|pair| pair[0].plan_node_index() >= pair[1].plan_node_index())
469 || wave.nodes().iter().any(|prepared| {
470 plan.payload()
471 .nodes()
472 .get(prepared.plan_node_index())
473 .is_none_or(|planned| prepared.node_id() != planned.id())
474 })
475 {
476 return Err(invalid_operation(
477 "submission wave identity must cover one exact canonical immutable-plan node scope",
478 ));
479 }
480 let mut participant_start = 0_u32;
481 let mut nodes = Vec::with_capacity(wave.nodes().len());
482 for (node_index, (identities, _node)) in participant_identities
483 .into_iter()
484 .zip(wave.nodes())
485 .enumerate()
486 {
487 let node_index = u32::try_from(node_index)
488 .map_err(|_| invalid_operation("submission wave node index exceeds u32"))?;
489 let participant_count = u32::try_from(identities.len())
490 .map_err(|_| invalid_operation("submission wave participant count exceeds u32"))?;
491 let node_identity = Self::bind_node_identity(
492 resolved,
493 identities,
494 active_bindings.clone(),
495 OperationInvocationResources::Wave {
496 wave,
497 node_index: node_index as usize,
498 },
499 lane,
500 node_index,
501 participant_start,
502 )?;
503 participant_start = participant_start
504 .checked_add(participant_count)
505 .ok_or_else(|| {
506 invalid_operation("submission wave participant index space overflows u32")
507 })?;
508 nodes.push(node_identity);
509 }
510 BatchOperationIdentity::from_validated(
511 wave.batch_step_id(),
512 wave.batch_invocation_id(),
513 plan.payload().plan_id().clone(),
514 plan.plan_hash().clone(),
515 plan.payload().device_id().clone(),
516 plan.payload()
517 .device_runtime_implementation_fingerprint()
518 .to_owned(),
519 lane.id(),
520 wave.fingerprint().to_owned(),
521 nodes,
522 )
523 }
524
525 pub fn bind_submission_wave_identity<'binding, R, I>(
529 resolved: &dyn ExecutablePlanView,
530 active_bindings: I,
531 wave: &PreparedStepSubmissionWave<R>,
532 lane: &Arc<ExecutionLane<R>>,
533 ) -> Result<BatchOperationIdentity, VNextError>
534 where
535 R: DeviceRuntime,
536 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
537 {
538 if active_bindings.len() == 0 {
539 return Err(invalid_operation(
540 "submission wave requires a non-empty active participant set",
541 ));
542 }
543 let participant_identities = wave
544 .nodes()
545 .iter()
546 .enumerate()
547 .map(|(node_index, node)| {
548 if active_bindings.len() != node.participant_frames().len() {
549 return Err(invalid_operation(format!(
550 "submission wave node {node_index} active binding count differs from its participant frames"
551 )));
552 }
553 node.participant_frames()
554 .iter()
555 .copied()
556 .zip(active_bindings.clone())
557 .map(|(frame, active)| {
558 Self::trusted_submission_node_identity(
559 resolved,
560 active,
561 frame,
562 node.plan_node_index(),
563 )
564 })
565 .collect::<Result<Vec<_>, _>>()
566 })
567 .collect::<Result<Vec<_>, _>>()?;
568 Self::bind_submission_wave_identity_from_envelopes(
569 resolved,
570 participant_identities,
571 active_bindings,
572 wave,
573 lane,
574 )
575 }
576
577 pub fn reusable_execution_program_id_for_wave<R>(
586 providers: &[BoundOperationProvider<'_, R>],
587 resolved: &dyn ExecutablePlanView,
588 wave: &PreparedStepSubmissionWave<R>,
589 lane: &Arc<ExecutionLane<R>>,
590 ) -> Result<Option<DeviceReusableExecutionProgramId>, VNextError>
591 where
592 R: DeviceRuntime,
593 {
594 Ok(
595 Self::reusable_execution_wave_authority(providers, resolved, wave, lane)?
596 .map(|authority| authority.program_id),
597 )
598 }
599
600 fn reusable_execution_wave_authority<R>(
601 providers: &[BoundOperationProvider<'_, R>],
602 resolved: &dyn ExecutablePlanView,
603 wave: &PreparedStepSubmissionWave<R>,
604 lane: &Arc<ExecutionLane<R>>,
605 ) -> Result<Option<ReusableExecutionWaveAuthority>, VNextError>
606 where
607 R: DeviceRuntime,
608 {
609 let plan = resolved.execution_plan();
610 let plan_nodes = plan.payload().nodes();
611 if providers.is_empty()
612 || providers.len() != wave.nodes().len()
613 || wave.execution_lane_id() != lane.id()
614 || lane.descriptor() != resolved.device()
615 {
616 return Err(invalid_operation(
617 "reusable execution topology requires one exact provider per wave node and lane",
618 ));
619 }
620 let Some(program_id) = wave.claimed_backing().reusable_execution_program_id(
621 &lane.descriptor().runtime_implementation_fingerprint,
622 lane.id(),
623 )?
624 else {
625 return Ok(None);
626 };
627
628 const DOMAIN: &[u8] = b"ferrum.runtime-vnext.reusable-program-topology.v3\0";
629 let mut digest = Sha256::new();
630 let mut eager_boundary_node_indices = Vec::new();
631 digest.update(DOMAIN);
632 digest.update(
633 u64::try_from(wave.nodes().len())
634 .map_err(|_| invalid_operation("reusable topology node count exceeds u64"))?
635 .to_le_bytes(),
636 );
637 for (wave_node_index, (provider, prepared_node)) in
638 providers.iter().zip(wave.nodes()).enumerate()
639 {
640 let plan_node_index = prepared_node.plan_node_index();
641 let node = plan_nodes.get(plan_node_index).ok_or_else(|| {
642 invalid_operation("reusable execution wave node is absent from the immutable plan")
643 })?;
644 if !provider.matches_plan_node(plan.payload().plan_id(), plan.plan_hash(), node.id())
645 || prepared_node.node_id() != node.id()
646 {
647 return Err(invalid_operation(
648 "reusable execution topology provider or node differs from the immutable plan",
649 ));
650 }
651 let request = ReusableExecutionTopologyRequest::new(
652 node.id(),
653 node.operation_id(),
654 node.attributes(),
655 node.values(),
656 node.scratch_resource(),
657 node.binding_resource(),
658 node.persistent_resource(),
659 plan.payload().memory(),
660 prepared_node.work_shape(),
661 wave.claimed_backing(),
662 wave.step_resources().backing_slices(),
663 )?;
664 let declared = node.provider_execution_semantics().replay_equivalence();
665 let topology = provider.provider().reusable_execution_topology(request)?;
666 if topology == ReusableExecutionTopology::EagerBoundary {
667 eager_boundary_node_indices.push(u32::try_from(wave_node_index).map_err(|_| {
668 invalid_operation("reusable topology wave node index exceeds u32")
669 })?);
670 }
671 let (topology_kind, topology_bytes) = match (
672 declared,
673 &topology,
674 ) {
675 (
676 ProviderReplayEquivalence::BitwiseEagerEquivalent,
677 ReusableExecutionTopology::Static,
678 ) => (0_u8, &[][..]),
679 (
680 ProviderReplayEquivalence::BitwiseEagerEquivalent,
681 ReusableExecutionTopology::Dynamic(topology),
682 ) => (1_u8, &topology.as_bytes()[..]),
683 (_, ReusableExecutionTopology::EagerBoundary) => (2_u8, &[][..]),
684 (
685 ProviderReplayEquivalence::Ineligible,
686 ReusableExecutionTopology::Static | ReusableExecutionTopology::Dynamic(_),
687 ) => {
688 return Err(invalid_operation(format!(
689 "provider `{}` returned reusable topology without a bitwise eager-equivalence contract",
690 provider.descriptor().provider_id()
691 )))
692 }
693 };
694 let wave_node_index = u64::try_from(wave_node_index)
695 .map_err(|_| invalid_operation("reusable topology wave node index exceeds u64"))?;
696 let plan_node_index = u64::try_from(plan_node_index)
697 .map_err(|_| invalid_operation("reusable topology plan node index exceeds u64"))?;
698 let node_id = node.id().as_str().as_bytes();
699 let provider_id = provider.descriptor().provider_id().as_str().as_bytes();
700 let node_id_len = u64::try_from(node_id.len())
701 .map_err(|_| invalid_operation("reusable topology node id exceeds u64"))?;
702 let provider_id_len = u64::try_from(provider_id.len())
703 .map_err(|_| invalid_operation("reusable topology provider id exceeds u64"))?;
704 let topology_len = u64::try_from(topology_bytes.len())
705 .map_err(|_| invalid_operation("reusable topology payload exceeds u64"))?;
706 digest.update(wave_node_index.to_le_bytes());
707 digest.update(plan_node_index.to_le_bytes());
708 digest.update(node_id_len.to_le_bytes());
709 digest.update(node_id);
710 digest.update(provider_id_len.to_le_bytes());
711 digest.update(provider_id);
712 digest.update([topology_kind]);
713 digest.update(topology_len.to_le_bytes());
714 digest.update(topology_bytes);
715 }
716 Ok(Some(ReusableExecutionWaveAuthority {
717 program_id: program_id.with_topology_fingerprint(
718 DeviceReusableExecutionTopologyFingerprint::from_sha256(digest.finalize().into()),
719 ),
720 eager_boundary_node_indices,
721 }))
722 }
723
724 #[allow(clippy::too_many_arguments)]
725 pub fn encode_and_submit<R>(
726 provider: &BoundOperationProvider<'_, R>,
727 resolved: &dyn ExecutablePlanView,
728 batch_identity: &BatchOperationIdentity,
729 active_bindings: &[TrustedActiveSequenceBinding],
730 mut invocation_resources: InvocationResourceLease<R>,
731 lane: &Arc<ExecutionLane<R>>,
732 reaper: &Arc<CompletionReaper<R>>,
733 ) -> Result<CompletionHandle<R>, OperationDispatchError<R>>
734 where
735 R: DeviceRuntime,
736 {
737 let node_identity = batch_identity.single_node().ok_or_else(|| {
738 OperationDispatchError::Contract(invalid_operation(
739 "single-operation dispatch requires a one-node batch identity",
740 ))
741 })?;
742 provider
743 .validate_binding(resolved, node_identity.node_id())
744 .map_err(OperationDispatchError::Contract)?;
745 if active_bindings.is_empty()
746 || active_bindings.len() != batch_identity.participants().len()
747 || lane.id() != batch_identity.lane_id()
748 || lane.descriptor().id != *batch_identity.device_id()
749 || lane.descriptor().runtime_implementation_fingerprint
750 != batch_identity.runtime_implementation_fingerprint()
751 {
752 return Err(OperationDispatchError::Contract(invalid_operation(
753 "operation execution lane or participant set differs from batch identity",
754 )));
755 }
756 invocation_resources
757 .begin_dispatch()
758 .map_err(OperationDispatchError::Contract)?;
759 let mut completion = CompletionReaper::reserve(
760 reaper,
761 invocation_resources,
762 Arc::clone(lane),
763 batch_identity.clone(),
764 )
765 .map_err(OperationDispatchError::Contract)?;
766 let runtime = lane.runtime();
767 if !lane.current_descriptor_matches_snapshot() {
768 return Err(OperationDispatchError::Contract(invalid_operation(
769 "operation encode runtime differs from its execution lane snapshot",
770 )));
771 }
772 let mut commands = DeviceCommandBatch::with_capacity(3);
773 completion
774 .encode_backing_initializations(runtime, &mut commands)
775 .map_err(|error| map_backing_initialization_error(runtime, batch_identity, error))?;
776 let invocation = BatchedOperationInvocation::from_resolved(
777 runtime,
778 resolved,
779 provider.dispatch(),
780 batch_identity,
781 completion.invocation(),
782 active_bindings,
783 )
784 .map_err(OperationDispatchError::Contract)?;
785 let plan_node = resolved
786 .execution_plan()
787 .payload()
788 .nodes()
789 .iter()
790 .find(|node| node.id() == node_identity.node_id())
791 .ok_or_else(|| {
792 OperationDispatchError::Contract(invalid_operation(
793 "operation workspace node is absent from the immutable plan",
794 ))
795 })?;
796 if let Some(requirement) = plan_node.provider_resources().scratch() {
797 let scratch_view = invocation
798 .participants()
799 .first()
800 .and_then(OperationInvocation::scratch_view)
801 .ok_or_else(|| {
802 OperationDispatchError::Contract(invalid_operation(
803 "operation scratch requirement has no invocation view",
804 ))
805 })?;
806 encode_provider_workspace_initialization::<R, DefinitelyNotSubmittedRetryAuthority<R>>(
807 runtime,
808 0,
809 node_identity,
810 requirement,
811 invocation.work_shape().resource_work(),
812 scratch_view,
813 SubmissionScratchInitialization::ProviderContract,
814 &mut commands,
815 )?;
816 }
817 let expected_phase = invocation.operation().profile_phase;
818 let operation = match provider.provider().encode_selected(invocation) {
819 Ok(operation) => operation,
820 Err(failure)
821 if batch_identity.contains_identity(failure.identity())
822 && failure.phase() == expected_phase =>
823 {
824 return Err(OperationDispatchError::Provider(failure));
825 }
826 Err(_) => {
827 return Err(OperationDispatchError::Contract(invalid_operation(
828 "operation provider returned a failure for a different execution identity or profile phase",
829 )));
830 }
831 };
832 if !lane.current_descriptor_matches_snapshot() {
833 return Err(OperationDispatchError::Contract(invalid_operation(
834 "operation encode completion runtime drifted",
835 )));
836 }
837 commands.push_operation(0, operation);
838 let timing_mode = commands.timing_mode();
839 let mut lane_reservation = lane
840 .reserve_enqueue()
841 .map_err(OperationDispatchError::Contract)?;
842 completion.mark_submission_started();
843 match lane_reservation.submit(commands) {
844 LaneSubmitOutcome::DefinitelyNotSubmitted(error) => {
845 drop(lane_reservation);
846 let retry = completion
847 .definitely_not_submitted()
848 .map_err(OperationDispatchError::Contract)?;
849 let failures = batch_identity
850 .participants()
851 .iter()
852 .map(|participant| {
853 classify_device_error(runtime, participant.identity().clone(), &error)
854 })
855 .collect::<Result<Vec<_>, _>>()
856 .map_err(OperationDispatchError::Contract)?;
857 Err(OperationDispatchError::DefinitelyNotSubmitted { failures, retry })
858 }
859 LaneSubmitOutcome::PossiblySubmittedPanic => {
860 drop(lane_reservation);
861 let recovery = completion.submission_indeterminate();
862 Err(OperationDispatchError::SubmissionIndeterminate { recovery })
863 }
864 LaneSubmitOutcome::Submitted(fence) => {
865 drop(lane_reservation);
866 let completion = match completion.arm(fence, timing_mode) {
867 Ok(completion) => completion,
868 Err((error, completion)) => {
869 return Err(OperationDispatchError::PostSubmitContract {
870 error,
871 completion,
872 });
873 }
874 };
875 if !lane.current_descriptor_matches_snapshot() {
876 lane.fail_closed();
877 return Err(OperationDispatchError::PostSubmitContract {
878 error: invalid_operation("operation submit completion runtime drifted"),
879 completion,
880 });
881 }
882 Ok(completion)
883 }
884 }
885 }
886
887 #[allow(clippy::too_many_arguments)]
888 pub fn encode_and_submit_wave<'binding, R, I>(
889 providers: &[BoundOperationProvider<'_, R>],
890 resolved: &dyn ExecutablePlanView,
891 batch_identity: &BatchOperationIdentity,
892 active_bindings: I,
893 timing_mode: DeviceTimingMode,
894 wave: PreparedStepSubmissionWave<R>,
895 lane: &Arc<ExecutionLane<R>>,
896 reaper: &Arc<CompletionReaper<R>>,
897 ) -> Result<CompletionHandle<R>, SubmissionWaveDispatchError<R>>
898 where
899 R: DeviceRuntime,
900 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
901 {
902 Self::encode_and_submit_wave_with_inputs(
903 providers,
904 resolved,
905 batch_identity,
906 active_bindings,
907 timing_mode,
908 &[],
909 wave,
910 lane,
911 reaper,
912 )
913 }
914
915 #[allow(clippy::too_many_arguments)]
916 pub fn encode_and_submit_wave_with_inputs<'binding, R, I>(
917 providers: &[BoundOperationProvider<'_, R>],
918 resolved: &dyn ExecutablePlanView,
919 batch_identity: &BatchOperationIdentity,
920 active_bindings: I,
921 timing_mode: DeviceTimingMode,
922 input_uploads: &[SubmissionWaveInputUpload],
923 wave: PreparedStepSubmissionWave<R>,
924 lane: &Arc<ExecutionLane<R>>,
925 reaper: &Arc<CompletionReaper<R>>,
926 ) -> Result<CompletionHandle<R>, SubmissionWaveDispatchError<R>>
927 where
928 R: DeviceRuntime,
929 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
930 {
931 Self::encode_and_submit_wave_with_inputs_timed(
932 providers,
933 resolved,
934 batch_identity,
935 active_bindings,
936 timing_mode,
937 input_uploads,
938 SubmissionExecutionPolicy::adaptive(),
939 None,
940 None,
941 &DisabledSubmissionWaveDispatchTimingSink,
942 wave,
943 lane,
944 reaper,
945 )
946 .map(|profiled| profiled.into_parts().0)
947 }
948
949 #[allow(clippy::too_many_arguments)]
950 pub fn encode_and_submit_wave_with_inputs_and_policy<'binding, R, I>(
951 providers: &[BoundOperationProvider<'_, R>],
952 resolved: &dyn ExecutablePlanView,
953 batch_identity: &BatchOperationIdentity,
954 active_bindings: I,
955 timing_mode: DeviceTimingMode,
956 input_uploads: &[SubmissionWaveInputUpload],
957 execution_policy: SubmissionExecutionPolicy,
958 wave: PreparedStepSubmissionWave<R>,
959 lane: &Arc<ExecutionLane<R>>,
960 reaper: &Arc<CompletionReaper<R>>,
961 ) -> Result<CompletionHandle<R>, SubmissionWaveDispatchError<R>>
962 where
963 R: DeviceRuntime,
964 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
965 {
966 Self::encode_and_submit_wave_with_inputs_timed(
967 providers,
968 resolved,
969 batch_identity,
970 active_bindings,
971 timing_mode,
972 input_uploads,
973 execution_policy,
974 None,
975 None,
976 &DisabledSubmissionWaveDispatchTimingSink,
977 wave,
978 lane,
979 reaper,
980 )
981 .map(|profiled| profiled.into_parts().0)
982 }
983
984 #[allow(clippy::too_many_arguments)]
988 pub fn encode_and_submit_wave_with_inputs_and_timing<'binding, R, I, S>(
989 providers: &[BoundOperationProvider<'_, R>],
990 resolved: &dyn ExecutablePlanView,
991 batch_identity: &BatchOperationIdentity,
992 active_bindings: I,
993 timing_mode: DeviceTimingMode,
994 input_uploads: &[SubmissionWaveInputUpload],
995 execution_policy: SubmissionExecutionPolicy,
996 timing_sink: &S,
997 wave: PreparedStepSubmissionWave<R>,
998 lane: &Arc<ExecutionLane<R>>,
999 reaper: &Arc<CompletionReaper<R>>,
1000 ) -> Result<ProfiledSubmissionHandle<R>, SubmissionWaveDispatchError<R>>
1001 where
1002 R: DeviceRuntime,
1003 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1004 S: SubmissionWaveDispatchTimingSink,
1005 {
1006 Self::encode_and_submit_wave_with_inputs_timed(
1007 providers,
1008 resolved,
1009 batch_identity,
1010 active_bindings,
1011 timing_mode,
1012 input_uploads,
1013 execution_policy,
1014 None,
1015 None,
1016 timing_sink,
1017 wave,
1018 lane,
1019 reaper,
1020 )
1021 }
1022
1023 #[allow(clippy::too_many_arguments)]
1027 pub fn encode_and_submit_determinism_eager_wave<'binding, R, I>(
1028 providers: &[BoundOperationProvider<'_, R>],
1029 resolved: &dyn ExecutablePlanView,
1030 batch_identity: &BatchOperationIdentity,
1031 active_bindings: I,
1032 timing_mode: DeviceTimingMode,
1033 restore: &SubmissionWaveDeterminismRestore,
1034 scratch_fill: u8,
1035 wave: PreparedStepSubmissionWave<R>,
1036 lane: &Arc<ExecutionLane<R>>,
1037 reaper: &Arc<CompletionReaper<R>>,
1038 ) -> Result<SubmissionWaveDeterminismHandle<R>, SubmissionWaveDispatchError<R>>
1039 where
1040 R: DeviceRuntime,
1041 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1042 {
1043 let readback_plan = SubmissionWaveDeterminismReadbackPlan::from_restore(
1044 resolved,
1045 batch_identity,
1046 &wave,
1047 restore,
1048 )
1049 .map_err(SubmissionWaveDispatchError::Contract)?;
1050 let restore_fingerprint = restore
1051 .logical_fingerprint()
1052 .map_err(SubmissionWaveDispatchError::Contract)?;
1053 let initialization_identity = restore
1054 .initialization_identity()
1055 .map_err(SubmissionWaveDispatchError::Contract)?;
1056 let profiled = Self::encode_and_submit_wave_with_inputs_timed(
1057 providers,
1058 resolved,
1059 batch_identity,
1060 active_bindings,
1061 timing_mode,
1062 &[],
1063 SubmissionExecutionPolicy::determinism_eager(scratch_fill),
1064 Some(restore),
1065 None,
1066 &DisabledSubmissionWaveDispatchTimingSink,
1067 wave,
1068 lane,
1069 reaper,
1070 )?;
1071 Ok(SubmissionWaveDeterminismHandle::from_profiled_eager(
1072 profiled,
1073 readback_plan,
1074 restore_fingerprint,
1075 initialization_identity,
1076 ))
1077 }
1078
1079 #[allow(clippy::too_many_arguments)]
1082 pub fn encode_and_submit_determinism_replayed_wave<'binding, R, I>(
1083 providers: &[BoundOperationProvider<'_, R>],
1084 resolved: &dyn ExecutablePlanView,
1085 batch_identity: &BatchOperationIdentity,
1086 active_bindings: I,
1087 timing_mode: DeviceTimingMode,
1088 restore: &SubmissionWaveDeterminismRestore,
1089 scratch_fill: u8,
1090 reusable_program: &DeviceReusableExecutionProgram,
1091 wave: PreparedStepSubmissionWave<R>,
1092 lane: &Arc<ExecutionLane<R>>,
1093 reaper: &Arc<CompletionReaper<R>>,
1094 ) -> Result<SubmissionWaveDeterminismHandle<R>, SubmissionWaveDispatchError<R>>
1095 where
1096 R: DeviceRuntime,
1097 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1098 {
1099 let readback_plan = SubmissionWaveDeterminismReadbackPlan::from_restore(
1100 resolved,
1101 batch_identity,
1102 &wave,
1103 restore,
1104 )
1105 .map_err(SubmissionWaveDispatchError::Contract)?;
1106 let restore_fingerprint = restore
1107 .logical_fingerprint()
1108 .map_err(SubmissionWaveDispatchError::Contract)?;
1109 let initialization_identity = restore
1110 .initialization_identity()
1111 .map_err(SubmissionWaveDispatchError::Contract)?;
1112 let replay_authority =
1113 Self::reusable_execution_wave_authority(providers, resolved, &wave, lane)
1114 .map_err(SubmissionWaveDispatchError::Contract)?
1115 .ok_or_else(|| {
1116 SubmissionWaveDispatchError::Contract(invalid_operation(
1117 "determinism replay wave has no reusable execution authority",
1118 ))
1119 })?;
1120 if &replay_authority.program_id != reusable_program.program_id() {
1121 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1122 "determinism replay program differs from the live wave topology",
1123 )));
1124 }
1125 let mut declared_eager_boundary_node_ids = replay_authority
1126 .eager_boundary_node_indices
1127 .iter()
1128 .map(|node_index| {
1129 usize::try_from(*node_index)
1130 .ok()
1131 .and_then(|node_index| batch_identity.node_id_at(node_index))
1132 .cloned()
1133 .ok_or_else(|| {
1134 SubmissionWaveDispatchError::Contract(invalid_operation(
1135 "determinism eager boundary is absent from the batch identity",
1136 ))
1137 })
1138 })
1139 .collect::<Result<Vec<_>, _>>()?;
1140 declared_eager_boundary_node_ids.sort();
1141 let profiled = Self::encode_and_submit_wave_with_inputs_timed(
1142 providers,
1143 resolved,
1144 batch_identity,
1145 active_bindings,
1146 timing_mode,
1147 &[],
1148 SubmissionExecutionPolicy::determinism_replayed(scratch_fill),
1149 Some(restore),
1150 Some(reusable_program),
1151 &DisabledSubmissionWaveDispatchTimingSink,
1152 wave,
1153 lane,
1154 reaper,
1155 )?;
1156 SubmissionWaveDeterminismHandle::from_profiled_replayed(
1157 profiled,
1158 readback_plan,
1159 restore_fingerprint,
1160 initialization_identity,
1161 replay_authority.program_id,
1162 declared_eager_boundary_node_ids,
1163 )
1164 .map_err(SubmissionWaveDispatchError::Contract)
1165 }
1166
1167 #[allow(clippy::too_many_arguments)]
1168 pub fn encode_and_submit_reusable_wave_with_inputs<'binding, R, I>(
1169 providers: &[BoundOperationProvider<'_, R>],
1170 resolved: &dyn ExecutablePlanView,
1171 batch_identity: &BatchOperationIdentity,
1172 active_bindings: I,
1173 timing_mode: DeviceTimingMode,
1174 input_uploads: &[SubmissionWaveInputUpload],
1175 reusable_program: &DeviceReusableExecutionProgram,
1176 wave: PreparedStepSubmissionWave<R>,
1177 lane: &Arc<ExecutionLane<R>>,
1178 reaper: &Arc<CompletionReaper<R>>,
1179 ) -> Result<CompletionHandle<R>, SubmissionWaveDispatchError<R>>
1180 where
1181 R: DeviceRuntime,
1182 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1183 {
1184 Self::encode_and_submit_wave_with_inputs_timed(
1185 providers,
1186 resolved,
1187 batch_identity,
1188 active_bindings,
1189 timing_mode,
1190 input_uploads,
1191 SubmissionExecutionPolicy::adaptive(),
1192 None,
1193 Some(reusable_program),
1194 &DisabledSubmissionWaveDispatchTimingSink,
1195 wave,
1196 lane,
1197 reaper,
1198 )
1199 .map(|profiled| profiled.into_parts().0)
1200 }
1201
1202 #[allow(clippy::too_many_arguments)]
1203 pub fn encode_and_submit_reusable_wave_with_inputs_and_policy<'binding, R, I>(
1204 providers: &[BoundOperationProvider<'_, R>],
1205 resolved: &dyn ExecutablePlanView,
1206 batch_identity: &BatchOperationIdentity,
1207 active_bindings: I,
1208 timing_mode: DeviceTimingMode,
1209 input_uploads: &[SubmissionWaveInputUpload],
1210 reusable_program: &DeviceReusableExecutionProgram,
1211 execution_policy: SubmissionExecutionPolicy,
1212 wave: PreparedStepSubmissionWave<R>,
1213 lane: &Arc<ExecutionLane<R>>,
1214 reaper: &Arc<CompletionReaper<R>>,
1215 ) -> Result<CompletionHandle<R>, SubmissionWaveDispatchError<R>>
1216 where
1217 R: DeviceRuntime,
1218 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1219 {
1220 Self::encode_and_submit_wave_with_inputs_timed(
1221 providers,
1222 resolved,
1223 batch_identity,
1224 active_bindings,
1225 timing_mode,
1226 input_uploads,
1227 execution_policy,
1228 None,
1229 Some(reusable_program),
1230 &DisabledSubmissionWaveDispatchTimingSink,
1231 wave,
1232 lane,
1233 reaper,
1234 )
1235 .map(|profiled| profiled.into_parts().0)
1236 }
1237
1238 #[allow(clippy::too_many_arguments)]
1239 pub fn encode_and_submit_reusable_wave_with_inputs_and_timing<'binding, R, I, S>(
1240 providers: &[BoundOperationProvider<'_, R>],
1241 resolved: &dyn ExecutablePlanView,
1242 batch_identity: &BatchOperationIdentity,
1243 active_bindings: I,
1244 timing_mode: DeviceTimingMode,
1245 input_uploads: &[SubmissionWaveInputUpload],
1246 reusable_program: &DeviceReusableExecutionProgram,
1247 execution_policy: SubmissionExecutionPolicy,
1248 timing_sink: &S,
1249 wave: PreparedStepSubmissionWave<R>,
1250 lane: &Arc<ExecutionLane<R>>,
1251 reaper: &Arc<CompletionReaper<R>>,
1252 ) -> Result<ProfiledSubmissionHandle<R>, SubmissionWaveDispatchError<R>>
1253 where
1254 R: DeviceRuntime,
1255 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1256 S: SubmissionWaveDispatchTimingSink,
1257 {
1258 Self::encode_and_submit_wave_with_inputs_timed(
1259 providers,
1260 resolved,
1261 batch_identity,
1262 active_bindings,
1263 timing_mode,
1264 input_uploads,
1265 execution_policy,
1266 None,
1267 Some(reusable_program),
1268 timing_sink,
1269 wave,
1270 lane,
1271 reaper,
1272 )
1273 }
1274
1275 #[allow(clippy::too_many_arguments)]
1276 fn encode_and_submit_wave_with_inputs_timed<'binding, R, I, S>(
1277 providers: &[BoundOperationProvider<'_, R>],
1278 resolved: &dyn ExecutablePlanView,
1279 batch_identity: &BatchOperationIdentity,
1280 active_bindings: I,
1281 timing_mode: DeviceTimingMode,
1282 input_uploads: &[SubmissionWaveInputUpload],
1283 execution_policy: SubmissionExecutionPolicy,
1284 determinism_restore: Option<&SubmissionWaveDeterminismRestore>,
1285 reusable_program: Option<&DeviceReusableExecutionProgram>,
1286 timing_sink: &S,
1287 mut wave: PreparedStepSubmissionWave<R>,
1288 lane: &Arc<ExecutionLane<R>>,
1289 reaper: &Arc<CompletionReaper<R>>,
1290 ) -> Result<ProfiledSubmissionHandle<R>, SubmissionWaveDispatchError<R>>
1291 where
1292 R: DeviceRuntime,
1293 I: Clone + ExactSizeIterator<Item = &'binding TrustedActiveSequenceBinding>,
1294 S: SubmissionWaveDispatchTimingSink,
1295 {
1296 let contract_stage = SubmissionWaveDispatchStageTimer::start(
1297 timing_sink,
1298 SubmissionWaveDispatchStage::ContractValidateAndReserve,
1299 );
1300 let active_participant_count = active_bindings.len();
1301 if providers.is_empty()
1302 || providers.len() != wave.nodes().len()
1303 || providers.len() != batch_identity.node_count()
1304 || active_participant_count == 0
1305 || batch_identity.batch_step_id() != wave.batch_step_id()
1306 || batch_identity.batch_invocation_id() != wave.batch_invocation_id()
1307 || batch_identity.claimed_backing_fingerprint() != wave.fingerprint()
1308 || lane.id() != batch_identity.lane_id()
1309 || lane.descriptor().id != *batch_identity.device_id()
1310 || lane.descriptor().runtime_implementation_fingerprint
1311 != batch_identity.runtime_implementation_fingerprint()
1312 {
1313 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1314 "wave execution lane, resources, nodes, or participants differ from batch identity",
1315 )));
1316 }
1317 if !matches!(
1318 (wave.purpose(), determinism_restore.is_some()),
1319 (SubmissionWavePurpose::FullPlan, false)
1320 | (SubmissionWavePurpose::DeterminismProbe, true)
1321 ) {
1322 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1323 "submission wave purpose differs from its product or determinism dispatch path",
1324 )));
1325 }
1326 if let Some(restore) = determinism_restore {
1327 restore
1328 .validate_for_submission(
1329 lane.runtime(),
1330 providers,
1331 resolved,
1332 batch_identity,
1333 active_bindings.clone(),
1334 &wave,
1335 )
1336 .map_err(SubmissionWaveDispatchError::Contract)?;
1337 if !input_uploads.is_empty()
1338 || usize::try_from(restore.participant_count()).ok()
1339 != Some(active_participant_count)
1340 || execution_policy.compute_path() == DeviceComputePathRequirement::Adaptive
1341 || execution_policy.scratch_initialization()
1342 == SubmissionScratchInitialization::ProviderContract
1343 {
1344 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1345 "determinism submission requires complete restore coverage, explicit scratch fill, and one forced compute path",
1346 )));
1347 }
1348 }
1349 for (node_index, (provider, prepared_node)) in
1350 providers.iter().zip(wave.nodes()).enumerate()
1351 {
1352 let node_id = batch_identity.node_id_at(node_index).ok_or_else(|| {
1353 SubmissionWaveDispatchError::Contract(invalid_operation(
1354 "physical batch is missing a compiled plan node",
1355 ))
1356 })?;
1357 provider
1358 .validate_binding(resolved, node_id)
1359 .map_err(SubmissionWaveDispatchError::Contract)?;
1360 if prepared_node.node_id() != node_id
1361 || prepared_node.work_shape().fingerprint()
1362 != batch_identity
1363 .work_shape_fingerprint_at(node_index)
1364 .expect("compiled physical batch node has work identity")
1365 || Some(provider.descriptor().provider_id())
1366 != batch_identity.provider_id_at(node_index)
1367 || Some(provider.descriptor().operation_id())
1368 != batch_identity.operation_id_at(node_index)
1369 || batch_identity.node_participant_count(node_index)
1370 != Some(
1371 usize::try_from(prepared_node.participant_count())
1372 .expect("prepared wave participant count fits usize"),
1373 )
1374 || batch_identity.node_participant_count(node_index)
1375 != Some(active_participant_count)
1376 {
1377 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1378 "wave provider or node identity differs from its prepared node",
1379 )));
1380 }
1381 }
1382 let reusable_execution_authority =
1383 Self::reusable_execution_wave_authority(providers, resolved, &wave, lane)
1384 .map_err(SubmissionWaveDispatchError::Contract)?;
1385 let mut effective_compute_path = execution_policy.compute_path();
1386 let mut declared_eager_compute_node_indices = Vec::new();
1387 if let Some(reusable_program) = reusable_program {
1388 if !timing_mode.direct_reusable_execution_allowed()
1389 || execution_policy.compute_path() == DeviceComputePathRequirement::EagerOnly
1390 {
1391 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1392 "submission timing or compute-path policy requires eager provider encoding",
1393 )));
1394 }
1395 if execution_policy.compute_path() == DeviceComputePathRequirement::Adaptive
1396 && !reusable_program.has_resident_segments()
1397 {
1398 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1399 "product reusable execution requires at least one resident segment",
1400 )));
1401 }
1402 let actual_authority = reusable_execution_authority.as_ref().ok_or_else(|| {
1403 SubmissionWaveDispatchError::Contract(invalid_operation(
1404 "reusable execution program has no live program binding authority",
1405 ))
1406 })?;
1407 if &actual_authority.program_id != reusable_program.program_id()
1408 || reusable_program.segments().iter().any(|segment| {
1409 segment.end_node_index() as usize > providers.len()
1410 || segment.logical_command_count()
1411 != segment.end_node_index() - segment.start_node_index()
1412 })
1413 {
1414 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1415 "reusable execution program differs from the exact wave topology",
1416 )));
1417 }
1418 if execution_policy.compute_path() == DeviceComputePathRequirement::ReplayedOnly {
1419 validate_determinism_replay_coverage(
1420 reusable_program,
1421 providers.len(),
1422 &actual_authority.eager_boundary_node_indices,
1423 batch_identity,
1424 )
1425 .map_err(SubmissionWaveDispatchError::Contract)?;
1426 if !actual_authority.eager_boundary_node_indices.is_empty() {
1427 effective_compute_path =
1428 DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries;
1429 declared_eager_compute_node_indices =
1430 actual_authority.eager_boundary_node_indices.clone();
1431 }
1432 }
1433 } else if matches!(
1434 execution_policy.compute_path(),
1435 DeviceComputePathRequirement::ReplayedOnly
1436 | DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries
1437 ) {
1438 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1439 "replay-required submission requires one sealed reusable execution program",
1440 )));
1441 }
1442 wave.begin_dispatch()
1443 .map_err(SubmissionWaveDispatchError::Contract)?;
1444 let mut completion =
1445 CompletionReaper::reserve_wave(reaper, wave, Arc::clone(lane), batch_identity.clone())
1446 .map_err(SubmissionWaveDispatchError::Contract)?;
1447 let runtime = lane.runtime();
1448 if !lane.current_descriptor_matches_snapshot() {
1449 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1450 "wave encode runtime differs from its execution lane snapshot",
1451 )));
1452 }
1453 drop(contract_stage);
1454
1455 let backing_stage = SubmissionWaveDispatchStageTimer::start(
1456 timing_sink,
1457 SubmissionWaveDispatchStage::BackingAndInputEncode,
1458 );
1459 let restore_capacity = determinism_restore.map_or(0, |restore| {
1460 restore
1461 .initializations()
1462 .len()
1463 .saturating_mul(active_participant_count)
1464 });
1465 let mut commands = DeviceCommandBatch::with_capacity_timing_and_compute_path(
1466 providers
1467 .len()
1468 .saturating_add(input_uploads.len())
1469 .saturating_add(restore_capacity),
1470 timing_mode,
1471 effective_compute_path,
1472 );
1473 if effective_compute_path
1474 == DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries
1475 {
1476 commands
1477 .set_declared_eager_compute_node_indices(declared_eager_compute_node_indices)
1478 .map_err(SubmissionWaveDispatchError::Contract)?;
1479 }
1480 if determinism_restore.is_some() {
1481 commands.require_logical_execution_path_attribution();
1482 }
1483 let backing_initialization_command_count = completion
1484 .encode_backing_initializations(runtime, &mut commands)
1485 .map_err(|error| map_backing_initialization_error(runtime, batch_identity, error))?;
1486 let workspace_initialization_command_count =
1487 encode_submission_wave_workspace_initializations(
1488 runtime,
1489 resolved,
1490 batch_identity,
1491 execution_policy.scratch_initialization(),
1492 &completion,
1493 &mut commands,
1494 )?;
1495 if commands.len()
1496 != backing_initialization_command_count
1497 .saturating_add(workspace_initialization_command_count)
1498 {
1499 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1500 "backing initialization command accounting differs from encoded commands",
1501 )));
1502 }
1503 if let Some(restore) = determinism_restore {
1504 let restore_start = commands.len();
1505 let restore_command_count = encode_submission_wave_determinism_restore(
1506 runtime,
1507 resolved,
1508 batch_identity,
1509 &completion,
1510 restore,
1511 &mut commands,
1512 )?;
1513 if commands.len()
1514 != restore_start
1515 .checked_add(restore_command_count)
1516 .ok_or_else(|| {
1517 SubmissionWaveDispatchError::Contract(invalid_operation(
1518 "determinism restore command accounting overflows usize",
1519 ))
1520 })?
1521 {
1522 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1523 "determinism restore command accounting differs from encoded commands",
1524 )));
1525 }
1526 }
1527 encode_submission_wave_inputs(
1528 runtime,
1529 resolved,
1530 batch_identity,
1531 &completion,
1532 input_uploads,
1533 &mut commands,
1534 )?;
1535 drop(backing_stage);
1536
1537 let provider_stage = SubmissionWaveDispatchStageTimer::start(
1538 timing_sink,
1539 SubmissionWaveDispatchStage::ProviderNodeEncode,
1540 );
1541 let pre_provider_command_count = commands.len();
1542 let mut encoded_provider_command_count = 0_usize;
1543 let mut program_bindings = Vec::new();
1544 let mut program_binding_resources = BTreeSet::new();
1545 let mut reusable_execution_binding_nodes = Vec::new();
1546 let mut encoded_operations = Vec::with_capacity(providers.len());
1547 if let Some(reusable_program) = reusable_program {
1548 let mut node_index = 0_usize;
1549 let mut segment_index = 0_usize;
1550 while node_index < providers.len() {
1551 let segment = reusable_program
1552 .segments()
1553 .get(segment_index)
1554 .filter(|segment| segment.start_node_index() as usize == node_index);
1555 if let Some(segment) = segment {
1556 let mut segment_dynamic_bindings = Vec::new();
1557 let mut segment_result_bindings = Vec::new();
1558 for binding_node_index in reusable_program
1559 .per_wave_binding_node_indices()
1560 .iter()
1561 .copied()
1562 .filter(|binding_node_index| segment.contains_node(*binding_node_index))
1563 {
1564 let binding_node_index =
1565 usize::try_from(binding_node_index).map_err(|_| {
1566 SubmissionWaveDispatchError::Contract(invalid_operation(
1567 "reusable execution binding node exceeds usize",
1568 ))
1569 })?;
1570 let provider = &providers[binding_node_index];
1571 let node_identity = batch_identity
1572 .materialize_node(binding_node_index)
1573 .map_err(SubmissionWaveDispatchError::Contract)?;
1574 let invocation = BatchedOperationInvocation::from_wave_node(
1575 runtime,
1576 resolved,
1577 provider.dispatch(),
1578 batch_identity,
1579 node_identity,
1580 completion.wave(),
1581 binding_node_index,
1582 active_bindings.clone(),
1583 )
1584 .map_err(SubmissionWaveDispatchError::Contract)?;
1585 let expected_phase = invocation.operation().profile_phase;
1586 let program_binding = invocation.program_binding().cloned();
1587 let bindings = match provider
1588 .provider()
1589 .encode_reusable_execution_bindings(invocation)
1590 {
1591 Ok(bindings) => bindings,
1592 Err(failure)
1593 if node_identity.contains_identity(failure.identity())
1594 && failure.phase() == expected_phase =>
1595 {
1596 return Err(SubmissionWaveDispatchError::Provider(failure));
1597 }
1598 Err(_) => {
1599 return Err(SubmissionWaveDispatchError::Contract(
1600 invalid_operation(
1601 "reusable provider returned a failure for another node identity or profile phase",
1602 ),
1603 ));
1604 }
1605 };
1606 let program_binding_count = bindings.program_binding_count();
1607 validate_program_binding_patch(
1608 resolved,
1609 node_identity,
1610 program_binding.as_ref(),
1611 program_binding_count,
1612 &mut program_binding_resources,
1613 )
1614 .map_err(SubmissionWaveDispatchError::Contract)?;
1615 let (mut node_program_bindings, mut dynamic_bindings, mut result_bindings) =
1616 bindings.into_parts();
1617 program_bindings.append(&mut node_program_bindings);
1618 segment_dynamic_bindings.append(&mut dynamic_bindings);
1619 segment_result_bindings.append(&mut result_bindings);
1620 }
1621 let invocation = DeviceReusableExecutionInvocation::new(
1622 reusable_program.program_id().clone(),
1623 segment.clone(),
1624 u32::try_from(active_participant_count).map_err(|_| {
1625 SubmissionWaveDispatchError::Contract(invalid_operation(
1626 "reusable execution participant count exceeds u32",
1627 ))
1628 })?,
1629 completion
1630 .wave()
1631 .claimed_backing()
1632 .work_shape()
1633 .immediate_tokens(),
1634 )
1635 .map_err(SubmissionWaveDispatchError::Contract)?;
1636 let compute = runtime
1637 .encode_reusable_execution(invocation)
1638 .map_err(|error| {
1639 SubmissionWaveDispatchError::Contract(invalid_operation(format!(
1640 "device runtime rejected a sealed reusable program: {error}"
1641 )))
1642 })?
1643 .ok_or_else(|| {
1644 SubmissionWaveDispatchError::Contract(invalid_operation(
1645 "device runtime published a reusable program it cannot encode",
1646 ))
1647 })?;
1648 let operation_command_count = segment_dynamic_bindings
1649 .len()
1650 .checked_add(1)
1651 .and_then(|count| count.checked_add(segment_result_bindings.len()))
1652 .ok_or_else(|| {
1653 SubmissionWaveDispatchError::Contract(invalid_operation(
1654 "reusable execution command count overflows usize",
1655 ))
1656 })?;
1657 encoded_provider_command_count = encoded_provider_command_count
1658 .checked_add(operation_command_count)
1659 .ok_or_else(|| {
1660 SubmissionWaveDispatchError::Contract(invalid_operation(
1661 "submission wave command count overflows usize",
1662 ))
1663 })?;
1664 encoded_operations.push((
1665 segment.start_node_index(),
1666 segment_dynamic_bindings,
1667 compute,
1668 segment_result_bindings,
1669 ));
1670 node_index = segment.end_node_index() as usize;
1671 segment_index += 1;
1672 continue;
1673 }
1674 if reusable_program
1675 .segments()
1676 .get(segment_index)
1677 .is_some_and(|segment| (segment.start_node_index() as usize) < node_index)
1678 {
1679 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1680 "reusable execution segment traversal is not canonical",
1681 )));
1682 }
1683 let provider = &providers[node_index];
1684 let node_identity = batch_identity
1685 .materialize_node(node_index)
1686 .map_err(SubmissionWaveDispatchError::Contract)?;
1687 let invocation = BatchedOperationInvocation::from_wave_node(
1688 runtime,
1689 resolved,
1690 provider.dispatch(),
1691 batch_identity,
1692 node_identity,
1693 completion.wave(),
1694 node_index,
1695 active_bindings.clone(),
1696 )
1697 .map_err(SubmissionWaveDispatchError::Contract)?;
1698 let expected_phase = invocation.operation().profile_phase;
1699 let program_binding = invocation.program_binding().cloned();
1700 let operation = match provider.provider().encode_selected(invocation) {
1701 Ok(operation) => operation,
1702 Err(failure)
1703 if node_identity.contains_identity(failure.identity())
1704 && failure.phase() == expected_phase =>
1705 {
1706 return Err(SubmissionWaveDispatchError::Provider(failure));
1707 }
1708 Err(_) => {
1709 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1710 "wave provider returned a failure for another node identity or profile phase",
1711 )));
1712 }
1713 };
1714 let program_binding_count = operation.program_binding_count();
1715 validate_program_binding_patch(
1716 resolved,
1717 node_identity,
1718 program_binding.as_ref(),
1719 program_binding_count,
1720 &mut program_binding_resources,
1721 )
1722 .map_err(SubmissionWaveDispatchError::Contract)?;
1723 let operation_command_count = operation
1724 .dynamic_binding_count()
1725 .checked_add(1)
1726 .and_then(|count| count.checked_add(operation.result_binding_count()))
1727 .ok_or_else(|| {
1728 SubmissionWaveDispatchError::Contract(invalid_operation(
1729 "provider operation command count overflows usize",
1730 ))
1731 })?;
1732 encoded_provider_command_count = encoded_provider_command_count
1733 .checked_add(operation_command_count)
1734 .ok_or_else(|| {
1735 SubmissionWaveDispatchError::Contract(invalid_operation(
1736 "submission wave command count overflows usize",
1737 ))
1738 })?;
1739 let encoded_node_index = u32::try_from(node_index).map_err(|_| {
1740 SubmissionWaveDispatchError::Contract(invalid_operation(
1741 "submission wave node index exceeds u32",
1742 ))
1743 })?;
1744 let (mut node_program_bindings, dynamic_bindings, compute, result_bindings) =
1745 operation.into_parts();
1746 program_bindings.append(&mut node_program_bindings);
1747 encoded_operations.push((
1748 encoded_node_index,
1749 dynamic_bindings,
1750 compute,
1751 result_bindings,
1752 ));
1753 node_index += 1;
1754 }
1755 if segment_index != reusable_program.segments().len() {
1756 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1757 "reusable execution program contains an unreachable segment",
1758 )));
1759 }
1760 } else {
1761 for (node_index, (provider, node_identity)) in
1762 providers.iter().zip(batch_identity.nodes()).enumerate()
1763 {
1764 let invocation = BatchedOperationInvocation::from_wave_node(
1765 runtime,
1766 resolved,
1767 provider.dispatch(),
1768 batch_identity,
1769 node_identity,
1770 completion.wave(),
1771 node_index,
1772 active_bindings.clone(),
1773 )
1774 .map_err(SubmissionWaveDispatchError::Contract)?;
1775 let expected_phase = invocation.operation().profile_phase;
1776 let program_binding = invocation.program_binding().cloned();
1777 let operation = match provider.provider().encode_selected(invocation) {
1778 Ok(operation) => operation,
1779 Err(failure)
1780 if node_identity.contains_identity(failure.identity())
1781 && failure.phase() == expected_phase =>
1782 {
1783 return Err(SubmissionWaveDispatchError::Provider(failure));
1784 }
1785 Err(_) => {
1786 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1787 "wave provider returned a failure for another node identity or profile phase",
1788 )));
1789 }
1790 };
1791 let program_binding_count = operation.program_binding_count();
1792 validate_program_binding_patch(
1793 resolved,
1794 node_identity,
1795 program_binding.as_ref(),
1796 program_binding_count,
1797 &mut program_binding_resources,
1798 )
1799 .map_err(SubmissionWaveDispatchError::Contract)?;
1800 let operation_command_count = operation
1801 .dynamic_binding_count()
1802 .checked_add(1)
1803 .and_then(|count| count.checked_add(operation.result_binding_count()))
1804 .ok_or_else(|| {
1805 SubmissionWaveDispatchError::Contract(invalid_operation(
1806 "provider operation command count overflows usize",
1807 ))
1808 })?;
1809 let has_per_wave_bindings = program_binding_count > 0
1810 || operation.dynamic_binding_count() > 0
1811 || operation.result_binding_count() > 0;
1812 encoded_provider_command_count = encoded_provider_command_count
1813 .checked_add(operation_command_count)
1814 .ok_or_else(|| {
1815 SubmissionWaveDispatchError::Contract(invalid_operation(
1816 "submission wave command count overflows usize",
1817 ))
1818 })?;
1819 let node_index = u32::try_from(node_index).map_err(|_| {
1820 SubmissionWaveDispatchError::Contract(invalid_operation(
1821 "submission wave node index exceeds u32",
1822 ))
1823 })?;
1824 if has_per_wave_bindings {
1825 reusable_execution_binding_nodes.push(node_index);
1826 }
1827 let (mut node_program_bindings, dynamic_bindings, compute, result_bindings) =
1828 operation.into_parts();
1829 program_bindings.append(&mut node_program_bindings);
1830 encoded_operations.push((node_index, dynamic_bindings, compute, result_bindings));
1831 }
1832 }
1833 if let Some(layout) = completion.wave().claimed_backing().program_binding_layout() {
1834 let selected_plan_nodes = completion
1835 .wave()
1836 .nodes()
1837 .iter()
1838 .map(|node| node.plan_node_index())
1839 .collect::<BTreeSet<_>>();
1840 let expected_resources = layout
1841 .slots()
1842 .iter()
1843 .filter(|slot| selected_plan_nodes.contains(&slot.node_index()))
1844 .map(|slot| slot.resource_id())
1845 .collect::<BTreeSet<_>>();
1846 if program_binding_resources.len() != expected_resources.len()
1847 || expected_resources
1848 .iter()
1849 .any(|resource| !program_binding_resources.contains(*resource))
1850 {
1851 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1852 "provider program binding patches do not cover the selected compiled layout exactly",
1853 )));
1854 }
1855 }
1856 let uncoalesced_program_binding_count = program_bindings.len();
1857 let coalesced_program_bindings = runtime
1858 .coalesce_program_bindings(program_bindings)
1859 .map_err(|error| {
1860 SubmissionWaveDispatchError::Contract(invalid_operation(format!(
1861 "device runtime could not coalesce program bindings: {error}"
1862 )))
1863 })?;
1864 if (uncoalesced_program_binding_count == 0) != coalesced_program_bindings.is_empty()
1865 || coalesced_program_bindings.len() > uncoalesced_program_binding_count
1866 {
1867 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1868 "device runtime changed the program binding boundary cardinality illegally",
1869 )));
1870 }
1871 encoded_provider_command_count = encoded_provider_command_count
1872 .checked_add(coalesced_program_bindings.len())
1873 .ok_or_else(|| {
1874 SubmissionWaveDispatchError::Contract(invalid_operation(
1875 "coalesced program binding command count overflows usize",
1876 ))
1877 })?;
1878 for command in coalesced_program_bindings {
1879 commands.push_dynamic_binding(command);
1880 }
1881 for (node_index, dynamic_bindings, compute, result_bindings) in encoded_operations {
1882 commands.push_operation_parts(node_index, dynamic_bindings, compute, result_bindings);
1883 }
1884 if reusable_program.is_none() {
1885 if let Some(authority) = reusable_execution_authority {
1886 commands
1887 .set_reusable_execution_capture(
1888 DeviceReusableExecutionCapture::new(
1889 authority.program_id,
1890 u32::try_from(providers.len()).map_err(|_| {
1891 SubmissionWaveDispatchError::Contract(invalid_operation(
1892 "reusable execution capture node count exceeds u32",
1893 ))
1894 })?,
1895 authority.eager_boundary_node_indices,
1896 reusable_execution_binding_nodes,
1897 )
1898 .map_err(SubmissionWaveDispatchError::Contract)?,
1899 )
1900 .map_err(SubmissionWaveDispatchError::Contract)?;
1901 }
1902 }
1903 if commands.len()
1904 != pre_provider_command_count.saturating_add(encoded_provider_command_count)
1905 || !lane.current_descriptor_matches_snapshot()
1906 {
1907 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
1908 "wave encode command phases differ from provider-declared operation boundaries",
1909 )));
1910 }
1911 drop(provider_stage);
1912
1913 let lane_stage = SubmissionWaveDispatchStageTimer::start(
1914 timing_sink,
1915 SubmissionWaveDispatchStage::LaneReserveSubmitAndArm,
1916 );
1917 let lane_reserve_stage = SubmissionWaveDispatchStageTimer::start(
1918 timing_sink,
1919 SubmissionWaveDispatchStage::LaneReserve,
1920 );
1921 let mut lane_reservation = lane
1922 .reserve_enqueue()
1923 .map_err(SubmissionWaveDispatchError::Contract)?;
1924 drop(lane_reserve_stage);
1925
1926 let device_submit_stage = SubmissionWaveDispatchStageTimer::start(
1927 timing_sink,
1928 SubmissionWaveDispatchStage::DeviceRuntimeSubmit,
1929 );
1930 completion.mark_submission_started();
1931 let submit_outcome = lane_reservation.submit_with_timing(commands, timing_sink);
1932 drop(lane_reservation);
1933 drop(device_submit_stage);
1934
1935 let outcome = match submit_outcome {
1936 LaneSubmitOutcome::DefinitelyNotSubmitted(error) => {
1937 let retry = completion
1938 .definitely_not_submitted_wave()
1939 .map_err(SubmissionWaveDispatchError::Contract)?;
1940 let failures = batch_identity
1941 .participants()
1942 .iter()
1943 .map(|participant| {
1944 classify_device_error(runtime, participant.identity().clone(), &error)
1945 })
1946 .collect::<Result<Vec<_>, _>>()
1947 .map_err(SubmissionWaveDispatchError::Contract)?;
1948 Err(SubmissionWaveDispatchError::DefinitelyNotSubmitted { failures, retry })
1949 }
1950 LaneSubmitOutcome::PossiblySubmittedPanic => {
1951 let recovery = completion.submission_indeterminate();
1952 Err(SubmissionWaveDispatchError::SubmissionIndeterminate { recovery })
1953 }
1954 LaneSubmitOutcome::Submitted(fence) => {
1955 let device_attribution = runtime.submission_attribution(&fence);
1956 let completion_arm_stage = SubmissionWaveDispatchStageTimer::start(
1957 timing_sink,
1958 SubmissionWaveDispatchStage::CompletionArm,
1959 );
1960 let completion = match completion.arm(fence, timing_mode) {
1961 Ok(completion) => completion,
1962 Err((error, completion)) => {
1963 return Err(SubmissionWaveDispatchError::PostSubmitContract {
1964 error,
1965 completion,
1966 });
1967 }
1968 };
1969 if !lane.current_descriptor_matches_snapshot() {
1970 lane.fail_closed();
1971 return Err(SubmissionWaveDispatchError::PostSubmitContract {
1972 error: invalid_operation("wave submit completion runtime drifted"),
1973 completion,
1974 });
1975 }
1976 let attribution = match device_attribution
1977 .map(|device| {
1978 BoundDeviceSubmissionAttribution::new(
1979 batch_identity.clone(),
1980 completion.receipt().fingerprint().to_owned(),
1981 device,
1982 )
1983 })
1984 .transpose()
1985 {
1986 Ok(attribution) => attribution,
1987 Err(error) => {
1988 return Err(SubmissionWaveDispatchError::PostSubmitContract {
1989 error,
1990 completion,
1991 });
1992 }
1993 };
1994 drop(completion_arm_stage);
1995 Ok(ProfiledSubmissionHandle::new(completion, attribution))
1996 }
1997 };
1998 drop(lane_stage);
1999 outcome
2000 }
2001}
2002
2003fn map_backing_initialization_error<R, Retry>(
2004 runtime: &R,
2005 batch_identity: &BatchOperationIdentity,
2006 error: BackingInitializationEncodeError<R::Error>,
2007) -> OperationDispatchError<R, Retry>
2008where
2009 R: DeviceRuntime,
2010 Retry: DispatchRetryAuthority,
2011{
2012 match error {
2013 BackingInitializationEncodeError::Contract(error) => {
2014 OperationDispatchError::Contract(error)
2015 }
2016 BackingInitializationEncodeError::Runtime { participant, error } => {
2017 let identity = batch_identity
2018 .nodes()
2019 .iter()
2020 .flat_map(BatchOperationNodeIdentity::participants)
2021 .find(|candidate| {
2022 candidate.node_key().sequence_authority() == participant.sequence_authority()
2023 && candidate.node_key().request_authority()
2024 == participant.request_authority()
2025 })
2026 .map(BatchOperationParticipantIdentity::identity)
2027 .cloned();
2028 let Some(identity) = identity else {
2029 return OperationDispatchError::Contract(invalid_operation(
2030 "backing initialization failure has no matching batch participant",
2031 ));
2032 };
2033 match classify_device_error(runtime, identity, &error) {
2034 Ok(failure) => OperationDispatchError::Initialization(failure),
2035 Err(error) => OperationDispatchError::Contract(error),
2036 }
2037 }
2038 }
2039}
2040
2041fn encode_submission_wave_determinism_restore<R>(
2042 runtime: &R,
2043 resolved: &dyn ExecutablePlanView,
2044 batch_identity: &BatchOperationIdentity,
2045 completion: &CompletionReservation<R>,
2046 restore: &SubmissionWaveDeterminismRestore,
2047 commands: &mut DeviceCommandBatch<R::Command>,
2048) -> Result<usize, SubmissionWaveDispatchError<R>>
2049where
2050 R: DeviceRuntime,
2051{
2052 restore
2053 .validate_for(resolved)
2054 .map_err(SubmissionWaveDispatchError::Contract)?;
2055 let participant_count = usize::try_from(restore.participant_count()).map_err(|_| {
2056 SubmissionWaveDispatchError::Contract(invalid_operation(
2057 "determinism restore participant count exceeds host address space",
2058 ))
2059 })?;
2060 let mut command_count = 0_usize;
2061 for (initialization_index, initialization) in restore.initializations().iter().enumerate() {
2062 let location = initialization.location();
2063 if !initialization
2064 .consumer_node_ids()
2065 .iter()
2066 .any(|node_id| node_id == location.node_id())
2067 || initialization.consumer_node_ids().iter().any(|node_id| {
2068 batch_identity
2069 .node_index(node_id)
2070 .and_then(|index| batch_identity.node_participant_count(index))
2071 != Some(participant_count)
2072 })
2073 {
2074 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
2075 "determinism restore consumer coverage differs from its submitted wave",
2076 )));
2077 }
2078 let node_index = batch_identity
2079 .node_index(location.node_id())
2080 .ok_or_else(|| {
2081 SubmissionWaveDispatchError::Contract(invalid_operation(
2082 "determinism restore anchor node is absent from its submitted wave",
2083 ))
2084 })?;
2085 let node_index_u32 = u32::try_from(node_index).map_err(|_| {
2086 SubmissionWaveDispatchError::Contract(invalid_operation(
2087 "determinism restore node index exceeds u32",
2088 ))
2089 })?;
2090 let node_identity = batch_identity
2091 .materialize_node(node_index)
2092 .map_err(SubmissionWaveDispatchError::Contract)?;
2093 let prepared_node = completion.wave().nodes().get(node_index).ok_or_else(|| {
2094 SubmissionWaveDispatchError::Contract(invalid_operation(
2095 "determinism restore anchor node is absent from its prepared wave",
2096 ))
2097 })?;
2098 for participant_index in 0..participant_count {
2099 let participant_index_u32 = u32::try_from(participant_index).map_err(|_| {
2100 SubmissionWaveDispatchError::Contract(invalid_operation(
2101 "determinism restore participant index exceeds u32",
2102 ))
2103 })?;
2104 let participant = node_identity
2105 .participants()
2106 .get(participant_index)
2107 .ok_or_else(|| {
2108 SubmissionWaveDispatchError::Contract(invalid_operation(
2109 "determinism restore participant is absent from its anchor node",
2110 ))
2111 })?;
2112 let participant_work = prepared_node
2113 .work_shape()
2114 .participant_work()
2115 .get(participant_index)
2116 .ok_or_else(|| {
2117 SubmissionWaveDispatchError::Contract(invalid_operation(
2118 "determinism restore participant lacks exact token work",
2119 ))
2120 })?;
2121 let logical_work = DeviceCommandLogicalWork::for_participant_range(
2122 DeviceBatchingForm::Scalar,
2123 participant_index_u32,
2124 1,
2125 participant_work.token_span().immediate_tokens(),
2126 )
2127 .map_err(SubmissionWaveDispatchError::Contract)?;
2128 let bytes = restore
2129 .participant_payloads(participant_index_u32)
2130 .and_then(|payloads| payloads.get(initialization_index))
2131 .ok_or_else(|| {
2132 SubmissionWaveDispatchError::Contract(invalid_operation(
2133 "determinism restore payload matrix is incomplete",
2134 ))
2135 })?;
2136 let range = restore
2137 .initialization_range(participant_index_u32, initialization_index)
2138 .ok_or_else(|| {
2139 SubmissionWaveDispatchError::Contract(invalid_operation(
2140 "determinism restore range matrix is incomplete",
2141 ))
2142 })?;
2143 let backing = completion
2144 .backing_view(
2145 location.node_id(),
2146 participant_index_u32,
2147 location.resource_id(),
2148 )
2149 .map_err(SubmissionWaveDispatchError::Contract)?;
2150 let encoded = encode_submission_wave_backing_upload(
2151 runtime,
2152 participant.identity(),
2153 &backing,
2154 location.usage(),
2155 location.element_type(),
2156 range.logical_offset_bytes(),
2157 bytes,
2158 "determinism restore",
2159 |command| {
2160 commands.push_node_initialization(node_index_u32, logical_work, command);
2161 },
2162 )?;
2163 command_count = command_count.checked_add(encoded).ok_or_else(|| {
2164 SubmissionWaveDispatchError::Contract(invalid_operation(
2165 "determinism restore command count overflows usize",
2166 ))
2167 })?;
2168 }
2169 }
2170 Ok(command_count)
2171}
2172
2173fn encode_submission_wave_inputs<R>(
2174 runtime: &R,
2175 resolved: &dyn ExecutablePlanView,
2176 batch_identity: &BatchOperationIdentity,
2177 completion: &CompletionReservation<R>,
2178 uploads: &[SubmissionWaveInputUpload],
2179 commands: &mut DeviceCommandBatch<R::Command>,
2180) -> Result<(), SubmissionWaveDispatchError<R>>
2181where
2182 R: DeviceRuntime,
2183{
2184 struct ValidatedInputUpload<'a> {
2185 upload: &'a SubmissionWaveInputUpload,
2186 participant: &'a BatchOperationParticipantIdentity,
2187 destination: std::ops::Range<u64>,
2188 }
2189
2190 let mut upload_cursor = 0_usize;
2191 while upload_cursor < uploads.len() {
2192 let first_upload = &uploads[upload_cursor];
2193 let mut run_end = upload_cursor + 1;
2194 while uploads.get(run_end).is_some_and(|upload| {
2195 upload.node_id() == first_upload.node_id()
2196 && upload.input_ordinal() == first_upload.input_ordinal()
2197 }) {
2198 run_end += 1;
2199 }
2200 let node = resolved
2201 .execution_plan()
2202 .payload()
2203 .nodes()
2204 .iter()
2205 .find(|node| node.id() == first_upload.node_id())
2206 .ok_or_else(|| {
2207 SubmissionWaveDispatchError::Contract(invalid_operation(
2208 "submission input upload references an unknown plan node",
2209 ))
2210 })?;
2211 let identity_node_index = batch_identity
2212 .node_index(first_upload.node_id())
2213 .ok_or_else(|| {
2214 SubmissionWaveDispatchError::Contract(invalid_operation(
2215 "submission input upload has no physical node identity",
2216 ))
2217 })?;
2218 let node_identity = batch_identity
2219 .materialize_node(identity_node_index)
2220 .map_err(SubmissionWaveDispatchError::Contract)?;
2221 let value = node
2222 .values()
2223 .iter()
2224 .find(|value| {
2225 value.role() == ResolvedValueRole::Input
2226 && value.ordinal() == first_upload.input_ordinal()
2227 })
2228 .ok_or_else(|| {
2229 SubmissionWaveDispatchError::Contract(invalid_operation(
2230 "submission input upload references an unknown node input",
2231 ))
2232 })?;
2233 let [component] = value.storage().components() else {
2234 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
2235 "submission input upload requires one activation storage component",
2236 )));
2237 };
2238 let descriptor = completion
2239 .wave()
2240 .step_resources()
2241 .dynamic_descriptor(component.resource_id())
2242 .map_err(SubmissionWaveDispatchError::Contract)?;
2243 let participant_packed = descriptor.lifetime() == AllocationLifetime::Step
2244 && descriptor.kind() == &AllocationKind::Value;
2245 let prepared_work_shape = if participant_packed {
2246 Some(
2247 completion
2248 .wave()
2249 .nodes()
2250 .iter()
2251 .find(|wave_node| wave_node.node_id() == first_upload.node_id())
2252 .ok_or_else(|| {
2253 SubmissionWaveDispatchError::Contract(invalid_operation(
2254 "submission input upload has no prepared wave node",
2255 ))
2256 })?
2257 .work_shape(),
2258 )
2259 } else {
2260 None
2261 };
2262
2263 let mut validated = Vec::with_capacity(run_end - upload_cursor);
2264 for upload in &uploads[upload_cursor..run_end] {
2265 let participant_index = usize::try_from(upload.participant_index()).map_err(|_| {
2266 SubmissionWaveDispatchError::Contract(invalid_operation(
2267 "submission input upload participant index exceeds host address space",
2268 ))
2269 })?;
2270 let participant = node_identity
2271 .participants()
2272 .get(participant_index)
2273 .ok_or_else(|| {
2274 SubmissionWaveDispatchError::Contract(invalid_operation(
2275 "submission input upload participant is absent from its plan node",
2276 ))
2277 })?;
2278 let byte_len = upload.source_layout().byte_len().map_err(|error| {
2279 SubmissionWaveDispatchError::Contract(invalid_operation(error.to_string()))
2280 })?;
2281 let value_end = upload
2282 .logical_offset_bytes()
2283 .checked_add(byte_len)
2284 .ok_or_else(|| {
2285 SubmissionWaveDispatchError::Contract(invalid_operation(
2286 "submission input upload value range overflows",
2287 ))
2288 })?;
2289 if value.usage() != BufferUsage::Activations
2290 || !matches!(value.access(), TensorAccess::Read | TensorAccess::ReadWrite)
2291 || value.tensor().element_type() != upload.source_layout().element_type()
2292 || component.element_type() != upload.source_layout().element_type()
2293 || value_end > component.length_bytes()
2294 {
2295 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
2296 "submission input upload differs from its resolved activation binding",
2297 )));
2298 }
2299 let semantic_destination_start = component
2300 .offset_bytes()
2301 .checked_add(upload.logical_offset_bytes())
2302 .ok_or_else(|| {
2303 SubmissionWaveDispatchError::Contract(invalid_operation(
2304 "submission input upload destination overflows",
2305 ))
2306 })?;
2307 let semantic_destination_end = semantic_destination_start
2308 .checked_add(byte_len)
2309 .ok_or_else(|| {
2310 SubmissionWaveDispatchError::Contract(invalid_operation(
2311 "submission input upload destination range overflows",
2312 ))
2313 })?;
2314 let destination = if let Some(work_shape) = prepared_work_shape {
2315 translate_step_participant_upload_range(
2316 descriptor.demand(),
2317 work_shape,
2318 participant_index,
2319 semantic_destination_start..semantic_destination_end,
2320 )
2321 .map_err(SubmissionWaveDispatchError::Contract)?
2322 } else {
2323 semantic_destination_start..semantic_destination_end
2324 };
2325 if destination.end.checked_sub(destination.start) != Some(byte_len) {
2326 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
2327 "submission input upload translated range differs from its source layout",
2328 )));
2329 }
2330 let backing = completion
2331 .backing_view(
2332 upload.node_id(),
2333 upload.participant_index(),
2334 component.resource_id(),
2335 )
2336 .map_err(SubmissionWaveDispatchError::Contract)?;
2337 if backing.usage() != BufferUsage::Activations
2338 || backing.element_type() != upload.source_layout().element_type()
2339 || destination.end > backing.size_bytes()
2340 {
2341 return Err(SubmissionWaveDispatchError::Contract(invalid_operation(
2342 "submission input upload backing differs from its resolved activation",
2343 )));
2344 }
2345 validated.push(ValidatedInputUpload {
2346 upload,
2347 participant,
2348 destination,
2349 });
2350 }
2351
2352 let mut validated_cursor = 0_usize;
2353 while validated_cursor < validated.len() {
2354 let mut contiguous_end = validated_cursor + 1;
2355 if participant_packed {
2356 while contiguous_end < validated.len()
2357 && validated[contiguous_end - 1].destination.end
2358 == validated[contiguous_end].destination.start
2359 {
2360 contiguous_end += 1;
2361 }
2362 }
2363 let first = &validated[validated_cursor];
2364 let backing = completion
2365 .backing_view(
2366 first.upload.node_id(),
2367 first.upload.participant_index(),
2368 component.resource_id(),
2369 )
2370 .map_err(SubmissionWaveDispatchError::Contract)?;
2371 if contiguous_end == validated_cursor + 1 {
2372 encode_submission_wave_backing_upload(
2373 runtime,
2374 first.participant.identity(),
2375 &backing,
2376 BufferUsage::Activations,
2377 first.upload.source_layout().element_type(),
2378 first.destination.start,
2379 first.upload.bytes(),
2380 "submission input upload",
2381 |command| commands.push_dynamic_binding(command),
2382 )?;
2383 } else {
2384 let aggregate_byte_len = validated[validated_cursor..contiguous_end]
2385 .iter()
2386 .try_fold(0_usize, |total, input| {
2387 total
2388 .checked_add(input.upload.bytes().len())
2389 .ok_or_else(|| {
2390 SubmissionWaveDispatchError::Contract(invalid_operation(
2391 "submission input upload aggregate byte count overflows usize",
2392 ))
2393 })
2394 })?;
2395 let mut aggregate_bytes = Vec::with_capacity(aggregate_byte_len);
2396 for input in &validated[validated_cursor..contiguous_end] {
2397 aggregate_bytes.extend_from_slice(input.upload.bytes());
2398 }
2399 encode_submission_wave_backing_upload(
2400 runtime,
2401 first.participant.identity(),
2402 &backing,
2403 BufferUsage::Activations,
2404 first.upload.source_layout().element_type(),
2405 first.destination.start,
2406 &aggregate_bytes,
2407 "submission input upload aggregate",
2408 |command| commands.push_dynamic_binding(command),
2409 )?;
2410 }
2411 validated_cursor = contiguous_end;
2412 }
2413 upload_cursor = run_end;
2414 }
2415 Ok(())
2416}