1use super::*;
2
3pub const MAX_VNEXT_DETERMINISM_PARTICIPANTS: usize = 32;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum VNextDeterminismPhase {
7 Prefill,
8 Decode,
9}
10
11impl VNextDeterminismPhase {
12 const fn wave_kind(self) -> VNextExecutionWaveKind {
13 match self {
14 Self::Prefill => VNextExecutionWaveKind::Prefill,
15 Self::Decode => VNextExecutionWaveKind::Decode,
16 }
17 }
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum VNextDeterminismInitialState {
22 Zero,
23 Nonzero,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum VNextDeterminismWorkspacePoison {
28 Zero,
29 A5,
30}
31
32impl VNextDeterminismWorkspacePoison {
33 const fn fill_byte(self) -> u8 {
34 match self {
35 Self::Zero => 0,
36 Self::A5 => 0xa5,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum VNextDeterminismExecutionMode {
43 Eager,
44 Replayed,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct VNextDeterminismParticipantSpec {
49 token_ids: Vec<u32>,
50 immediate_range: Range<usize>,
51 maximum_sequence_tokens: usize,
52}
53
54impl VNextDeterminismParticipantSpec {
55 pub fn new(
56 token_ids: Vec<u32>,
57 immediate_range: Range<usize>,
58 maximum_sequence_tokens: usize,
59 ) -> Result<Self> {
60 if token_ids.is_empty()
61 || immediate_range.start >= immediate_range.end
62 || immediate_range.end != token_ids.len()
63 || maximum_sequence_tokens < token_ids.len()
64 {
65 return Err(FerrumError::request_validation(
66 "vNext determinism participant requires a non-empty terminal token span covered by its sequence ceiling",
67 ));
68 }
69 Ok(Self {
70 token_ids,
71 immediate_range,
72 maximum_sequence_tokens,
73 })
74 }
75
76 pub fn token_ids(&self) -> &[u32] {
77 &self.token_ids
78 }
79
80 pub fn immediate_range(&self) -> Range<usize> {
81 self.immediate_range.clone()
82 }
83
84 pub const fn maximum_sequence_tokens(&self) -> usize {
85 self.maximum_sequence_tokens
86 }
87
88 fn token_span(&self) -> Result<TokenSpanWork> {
89 TokenSpanWork::from_token_ids_with_fit(
90 &self.token_ids,
91 self.immediate_range.clone(),
92 self.maximum_sequence_tokens,
93 )
94 .map_err(|error| FerrumError::backend(error.to_string()))
95 }
96
97 fn full_extension(&self) -> Result<ResourceWorkShape> {
98 let span = TokenSpanWork::from_token_ids_with_fit(
99 &self.token_ids,
100 0..self.token_ids.len(),
101 self.maximum_sequence_tokens,
102 )
103 .map_err(|error| FerrumError::backend(error.to_string()))?;
104 ResourceWorkShape::single(span).map_err(|error| FerrumError::backend(error.to_string()))
105 }
106
107 fn immediate_token_ids(&self) -> &[u32] {
108 &self.token_ids[self.immediate_range.clone()]
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct VNextDeterminismExecutionSpec {
114 phase: VNextDeterminismPhase,
115 participants: Vec<VNextDeterminismParticipantSpec>,
116 initial_state: VNextDeterminismInitialState,
117 workspace_poison: VNextDeterminismWorkspacePoison,
118 mode: VNextDeterminismExecutionMode,
119}
120
121impl VNextDeterminismExecutionSpec {
122 pub fn new(
123 phase: VNextDeterminismPhase,
124 participants: Vec<VNextDeterminismParticipantSpec>,
125 initial_state: VNextDeterminismInitialState,
126 workspace_poison: VNextDeterminismWorkspacePoison,
127 mode: VNextDeterminismExecutionMode,
128 ) -> Result<Self> {
129 if participants.is_empty()
130 || participants.len() > MAX_VNEXT_DETERMINISM_PARTICIPANTS
131 || (phase == VNextDeterminismPhase::Decode
132 && participants
133 .iter()
134 .any(|participant| participant.immediate_range.len() != 1))
135 {
136 return Err(FerrumError::request_validation(format!(
137 "vNext determinism execution requires 1..={MAX_VNEXT_DETERMINISM_PARTICIPANTS} participants and one immediate token per decode participant"
138 )));
139 }
140 Ok(Self {
141 phase,
142 participants,
143 initial_state,
144 workspace_poison,
145 mode,
146 })
147 }
148
149 pub const fn phase(&self) -> VNextDeterminismPhase {
150 self.phase
151 }
152
153 pub fn participants(&self) -> &[VNextDeterminismParticipantSpec] {
154 &self.participants
155 }
156
157 pub const fn initial_state(&self) -> VNextDeterminismInitialState {
158 self.initial_state
159 }
160
161 pub const fn workspace_poison(&self) -> VNextDeterminismWorkspacePoison {
162 self.workspace_poison
163 }
164
165 pub const fn mode(&self) -> VNextDeterminismExecutionMode {
166 self.mode
167 }
168}
169
170struct PendingDeterminismAdmissions<'executor, R: DeviceRuntime> {
171 executor: &'executor VNextModelExecutor<R>,
172 request_ids: Vec<RequestId>,
173 armed: bool,
174}
175
176impl<'executor, R: DeviceRuntime> PendingDeterminismAdmissions<'executor, R> {
177 fn new(executor: &'executor VNextModelExecutor<R>) -> Self {
178 Self {
179 executor,
180 request_ids: Vec::new(),
181 armed: true,
182 }
183 }
184
185 fn track(&mut self, request_id: RequestId) {
186 self.request_ids.push(request_id);
187 }
188
189 fn disarm(&mut self) {
190 self.armed = false;
191 }
192}
193
194impl<R: DeviceRuntime> Drop for PendingDeterminismAdmissions<'_, R> {
195 fn drop(&mut self) {
196 if !self.armed {
197 return;
198 }
199 let mut sequences = self.executor.sequences.lock();
200 for request_id in &self.request_ids {
201 sequences.cancel_prefill(request_id);
202 }
203 }
204}
205
206impl<R: DeviceRuntime> VNextModelExecutor<R> {
207 async fn admit_determinism_participants(
208 &self,
209 spec: &VNextDeterminismExecutionSpec,
210 ) -> Result<Vec<(Arc<VNextPrefillSlot<R>>, Arc<VNextSequence<R>>)>> {
211 if spec.participants.iter().any(|participant| {
212 participant.maximum_sequence_tokens > self.maximum_model_tokens
213 || participant.token_ids.iter().any(|token| {
214 usize::try_from(*token).map_or(true, |token| token >= self.info.vocab_size)
215 })
216 }) {
217 return Err(FerrumError::request_validation(
218 "vNext determinism participant exceeds the resolved model token or vocabulary bound",
219 ));
220 }
221
222 let mut pending = PendingDeterminismAdmissions::new(self);
223 for participant in &spec.participants {
224 let request_id = RequestId::new();
225 pending.track(request_id.clone());
226 let input_tokens = participant
227 .token_ids
228 .iter()
229 .copied()
230 .map(TokenId::new)
231 .collect::<Vec<_>>();
232 let mut maintenance_attempts = 0_u32;
233 loop {
234 match self.try_admit_prefill(ExecutorPrefillAdmission::for_diagnostic(
235 &request_id,
236 &input_tokens,
237 participant.maximum_sequence_tokens,
238 ))? {
239 ExecutorPrefillAdmissionDecision::Admitted(receipt) => {
240 if receipt.request_id != request_id {
241 return Err(FerrumError::internal(
242 "vNext determinism admission changed request identity",
243 ));
244 }
245 break;
246 }
247 ExecutorPrefillAdmissionDecision::MaintenanceDeferred(_) => {
248 if maintenance_attempts >= MAX_BACKING_MAINTENANCE_ATTEMPTS {
249 return Err(FerrumError::resource_exhausted(
250 "vNext determinism admission backing did not converge",
251 ));
252 }
253 maintenance_attempts += 1;
254 match self.maintain_prefill_backing(&request_id)? {
255 ExecutorPrefillMaintenanceOutcome::Maintained { .. }
256 | ExecutorPrefillMaintenanceOutcome::RetryAdmission { .. } => continue,
257 ExecutorPrefillMaintenanceOutcome::WaitForRelease { .. } => {
258 return Err(FerrumError::resource_exhausted(
259 "vNext determinism admission is waiting for capacity release",
260 ))
261 }
262 ExecutorPrefillMaintenanceOutcome::NoLongerPending => {
263 return Err(FerrumError::internal(
264 "vNext determinism admission lost its retained request",
265 ))
266 }
267 }
268 }
269 ExecutorPrefillAdmissionDecision::Deferred(deferred) => {
270 return Err(Self::deferred(
271 "determinism participant admission",
272 &deferred,
273 ))
274 }
275 ExecutorPrefillAdmissionDecision::PermanentRejected(rejected) => {
276 return Err(FerrumError::resource_exhausted(format!(
277 "vNext determinism participant exceeds its immutable plan: {rejected:?}"
278 )))
279 }
280 }
281 }
282 }
283
284 let request_ids = pending.request_ids.clone();
285 let admitted = self
286 .sequences
287 .lock()
288 .begin_prefill_batch_execution(&request_ids)?;
289 pending.disarm();
290 Ok(admitted)
291 }
292
293 fn prepare_determinism_wave(
294 &self,
295 step: &Arc<StepResourceLease<R>>,
296 sequences: &[Arc<VNextSequence<R>>],
297 spans: &[TokenSpanWork],
298 ) -> Result<PreparedStepSubmissionWave<R>> {
299 if sequences.len() != spans.len() {
300 return Err(FerrumError::internal(
301 "vNext determinism-wave maintenance participants differ from the work spans",
302 ));
303 }
304 Self::validate_step_maintenance_participants(step, sequences)?;
305 let work_shape = step
306 .shared_all_invocation_work_shape(spans)
307 .map_err(|error| FerrumError::backend(error.to_string()))?;
308 let requests = self
309 .resolved_plan
310 .execution_plan()
311 .payload()
312 .nodes()
313 .iter()
314 .map(|node| {
315 InvocationResourceAdmissionRequest::for_all_step_participants(
316 node.id().clone(),
317 Arc::clone(&work_shape),
318 AdmissionFitPolicy::ImmediateOnly,
319 AdmissionPressureAction::WaitForRelease,
320 )
321 .map_err(|error| FerrumError::backend(error.to_string()))
322 })
323 .collect::<Result<Vec<_>>>()?;
324 let mut backing_attempts = 0_u32;
325 let mut maintenance_receipts = Vec::new();
326 loop {
327 match step
328 .try_prepare_determinism_submission_wave(requests.clone())
329 .map_err(|error| FerrumError::backend(error.to_string()))?
330 {
331 StepSubmissionWaveAdmissionDecision::Prepared(wave) => return Ok(wave),
332 StepSubmissionWaveAdmissionDecision::Deferred(deferred) => {
333 if deferred.action() != DeferredAction::AwaitBackingGrowth {
334 return Err(Self::deferred("determinism submission wave", &deferred));
335 }
336 if backing_attempts >= MAX_BACKING_MAINTENANCE_ATTEMPTS {
337 let deferred = ExecutorExecutionCapacityDeferral::from_pending_maintenance(
338 &deferred,
339 ExecutorExecutionCapacityStage::SubmissionWave,
340 )?;
341 return Err(Self::execution_capacity_error(&deferred));
342 }
343 backing_attempts += 1;
344 let outcome = self
345 .plan_resources
346 .maintain_for_admission_deferred(&deferred)
347 .map_err(|error| FerrumError::backend(error.to_string()))?;
348 if let Some(deferred) = self.execution_maintenance_decision(
349 ExecutorExecutionCapacityStage::SubmissionWave,
350 outcome,
351 VNextExecutionMaintenanceSource::Logical(&deferred),
352 sequences.iter().map(Arc::as_ref),
353 &mut maintenance_receipts,
354 )? {
355 return Err(Self::execution_capacity_error(&deferred));
356 }
357 }
358 StepSubmissionWaveAdmissionDecision::BackingDeferred(deferred) => {
359 if backing_attempts >= MAX_BACKING_MAINTENANCE_ATTEMPTS {
360 let deferred = ExecutorExecutionCapacityDeferral::from_backing(
361 deferred.evidence(),
362 ExecutorExecutionCapacityStage::SubmissionWave,
363 )?;
364 return Err(Self::execution_capacity_error(&deferred));
365 }
366 backing_attempts += 1;
367 let outcome = deferred
368 .maintain()
369 .map_err(|error| FerrumError::backend(error.to_string()))?;
370 if let Some(deferred) = self.execution_maintenance_decision(
371 ExecutorExecutionCapacityStage::SubmissionWave,
372 outcome,
373 VNextExecutionMaintenanceSource::Backing(deferred.evidence()),
374 sequences.iter().map(Arc::as_ref),
375 &mut maintenance_receipts,
376 )? {
377 return Err(Self::execution_capacity_error(&deferred));
378 }
379 }
380 StepSubmissionWaveAdmissionDecision::PermanentRejected(rejected) => {
381 return Err(FerrumError::backend(format!(
382 "vNext determinism wave exceeds its immutable plan: {rejected:?}"
383 )))
384 }
385 StepSubmissionWaveAdmissionDecision::RequestStateDeferred(deferred) => {
386 return Err(FerrumError::resource_exhausted(format!(
387 "vNext determinism wave is waiting for Request-state hazards: {:?}",
388 deferred.blockers()
389 )))
390 }
391 StepSubmissionWaveAdmissionDecision::RequestStateSplitRequired(split) => {
392 return Err(FerrumError::request_validation(format!(
393 "vNext determinism wave requires sibling split for request {:?}: {:?}",
394 split.request(),
395 split.resource_ids()
396 )))
397 }
398 StepSubmissionWaveAdmissionDecision::RequestStatePoisoned(poison) => {
399 return Err(FerrumError::backend(format!(
400 "vNext determinism Request-state resource is poisoned: {poison:?}"
401 )))
402 }
403 }
404 }
405 }
406
407 fn deterministic_scalar_bytes(element_type: ElementType, nonzero: bool) -> &'static [u8] {
408 if !nonzero {
409 return &[0, 0, 0, 0];
410 }
411 match element_type {
412 ElementType::Bool | ElementType::U8 | ElementType::I8 => &[1],
413 ElementType::F16 => &[0x00, 0x3c],
414 ElementType::Bf16 => &[0x80, 0x3f],
415 ElementType::U32 | ElementType::I32 => &[1, 0, 0, 0],
416 ElementType::F32 => &[0x00, 0x00, 0x80, 0x3f],
417 }
418 }
419
420 fn repeated_deterministic_scalar(
421 element_type: ElementType,
422 length_bytes: usize,
423 nonzero: bool,
424 ) -> Result<Vec<u8>> {
425 let element_width = usize::try_from(element_type.size_bytes())
426 .map_err(|_| FerrumError::internal("determinism element width exceeds usize"))?;
427 if length_bytes == 0 || length_bytes % element_width != 0 {
428 return Err(FerrumError::internal(
429 "determinism payload length differs from its element type",
430 ));
431 }
432 let scalar = Self::deterministic_scalar_bytes(element_type, nonzero);
433 let scalar = &scalar[..element_width];
434 Ok(scalar.iter().copied().cycle().take(length_bytes).collect())
435 }
436
437 fn determinism_external_payload(
438 &self,
439 participant: &VNextDeterminismParticipantSpec,
440 initialization: &ExecutionDeterminismInitializationSpec,
441 length_bytes: usize,
442 ) -> Result<Vec<u8>> {
443 let location = initialization.location();
444 let node_id = location.node_id();
445 let ordinal = location.ordinal();
446 if node_id == &self.io.input_node_id && ordinal == self.io.input_ordinal {
447 if location.element_type() != ElementType::U32 {
448 return Err(FerrumError::internal(
449 "vNext determinism token input is not U32",
450 ));
451 }
452 let bytes = participant
453 .immediate_token_ids()
454 .iter()
455 .flat_map(|token| token.to_le_bytes())
456 .collect::<Vec<_>>();
457 if bytes.len() != length_bytes {
458 return Err(FerrumError::internal(
459 "vNext determinism token bytes differ from the prepared input range",
460 ));
461 }
462 return Ok(bytes);
463 }
464 if node_id == &self.io.token_mask_input_node_id
465 && ordinal == self.io.token_mask_input_ordinal
466 {
467 return Self::repeated_deterministic_scalar(
468 location.element_type(),
469 length_bytes,
470 true,
471 );
472 }
473 if node_id == &self.io.repetition_penalty_input_node_id
474 && ordinal == self.io.repetition_penalty_input_ordinal
475 {
476 if location.element_type() != ElementType::F32 {
477 return Err(FerrumError::internal(
478 "vNext determinism repetition penalty is not F32",
479 ));
480 }
481 return Self::repeated_deterministic_scalar(
482 location.element_type(),
483 length_bytes,
484 true,
485 );
486 }
487 Self::repeated_deterministic_scalar(location.element_type(), length_bytes, false)
488 }
489
490 fn bind_determinism_restore(
491 &self,
492 participants: &[&VNextDeterminismParticipantSpec],
493 participant_order: SubmissionWaveDeterminismParticipantOrder,
494 initial_state: VNextDeterminismInitialState,
495 identity: &BatchOperationIdentity,
496 active_bindings: &[&TrustedActiveSequenceBinding],
497 wave: &PreparedStepSubmissionWave<R>,
498 ) -> Result<SubmissionWaveDeterminismRestore> {
499 let layout =
500 SubmissionWaveDeterminismRestoreLayout::from_prepared_wave_with_participant_order(
501 self.runtime.as_ref(),
502 self.providers.providers(),
503 &self.resolved_plan,
504 identity,
505 active_bindings.iter().copied(),
506 participant_order,
507 wave,
508 )
509 .map_err(|error| FerrumError::backend(error.to_string()))?;
510 if usize::try_from(layout.participant_count()).ok() != Some(participants.len()) {
511 return Err(FerrumError::internal(
512 "vNext determinism restore participant count drifted",
513 ));
514 }
515 let payloads = participants
516 .iter()
517 .enumerate()
518 .map(|(participant_index, participant)| {
519 let ranges = layout
520 .participant_initialization_ranges(u32::try_from(participant_index).map_err(
521 |_| {
522 FerrumError::internal("vNext determinism participant index exceeds u32")
523 },
524 )?)
525 .ok_or_else(|| {
526 FerrumError::internal("vNext determinism restore lost a participant range")
527 })?;
528 layout
529 .witness_plan()
530 .initializations()
531 .iter()
532 .zip(ranges)
533 .map(|(initialization, range)| {
534 let length_bytes = usize::try_from(range.length_bytes()).map_err(|_| {
535 FerrumError::internal("vNext determinism restore range exceeds usize")
536 })?;
537 match initialization.kind() {
538 ExecutionDeterminismInitializationKind::ExternalInput { .. } => self
539 .determinism_external_payload(
540 participant,
541 initialization,
542 length_bytes,
543 ),
544 ExecutionDeterminismInitializationKind::State { .. } => {
545 Self::repeated_deterministic_scalar(
546 initialization.location().element_type(),
547 length_bytes,
548 initial_state == VNextDeterminismInitialState::Nonzero,
549 )
550 }
551 }
552 })
553 .collect::<Result<Vec<_>>>()
554 })
555 .collect::<Result<Vec<_>>>()?;
556 layout
557 .bind(payloads)
558 .map_err(|error| FerrumError::backend(error.to_string()))
559 }
560
561 async fn determinism_dispatch_failure(
562 &self,
563 step: Arc<StepResourceLease<R>>,
564 error: SubmissionWaveDispatchError<R>,
565 ) -> FerrumError {
566 match error {
567 SubmissionWaveDispatchError::DefinitelyNotSubmitted { failures, retry } => {
568 let message = format!(
569 "operation attempt {} with {} participants was definitely not submitted: {}",
570 retry.prior_attempt(),
571 failures.len(),
572 failures
573 .first()
574 .map(|failure| failure.failure().message())
575 .unwrap_or("missing classified participant failure")
576 );
577 drop(retry);
578 self.abort_unsubmitted_step(step, FerrumError::backend(message))
579 }
580 error @ (SubmissionWaveDispatchError::Contract(_)
581 | SubmissionWaveDispatchError::Provider(_)
582 | SubmissionWaveDispatchError::Initialization(_)
583 | SubmissionWaveDispatchError::InputUpload(_)) => {
584 self.abort_unsubmitted_step(step, FerrumError::backend(error.to_string()))
585 }
586 SubmissionWaveDispatchError::SubmissionIndeterminate { recovery } => {
587 let reaper = Arc::clone(&self.reaper);
588 let recovered = self
589 .completion_worker
590 .execute(VNextCompletionTaskKind::IndeterminateRecovery, move || {
591 let recovered = recovery.recover_by_draining_lane();
592 drop(reaper);
593 recovered
594 })
595 .await;
596 let message = match recovered {
597 Ok(Ok(_)) => "vNext determinism submission was indeterminate".to_owned(),
598 Ok(Err(error)) => format!(
599 "vNext determinism submission was indeterminate and recovery failed: {error}"
600 ),
601 Err(error) => format!(
602 "vNext determinism submission recovery task failed: {error}"
603 ),
604 };
605 self.abort_step(step, message).await
606 }
607 SubmissionWaveDispatchError::PostSubmitContract { error, completion } => {
608 let message = error.to_string();
609 let reaper = Arc::clone(&self.reaper);
610 let _ = self
611 .completion_worker
612 .execute(VNextCompletionTaskKind::PostSubmitDrain, move || {
613 let observation = completion.wait();
614 drop(reaper);
615 observation
616 })
617 .await;
618 self.abort_step(step, message).await
619 }
620 }
621 }
622
623 fn abort_prepared_unsubmitted_step(
624 &self,
625 step: Arc<StepResourceLease<R>>,
626 wave: PreparedStepSubmissionWave<R>,
627 error: FerrumError,
628 ) -> FerrumError {
629 drop(wave);
630 self.abort_unsubmitted_step(step, error)
631 }
632
633 pub async fn collect_determinism_execution(
634 &self,
635 spec: &VNextDeterminismExecutionSpec,
636 ) -> Result<SubmissionWaveDeterminismEvidence> {
637 if !self.startup_preparation.lock().is_ready() {
638 return Err(FerrumError::internal(
639 "vNext determinism collection requires completed startup preparation",
640 ));
641 }
642
643 let admitted = self.admit_determinism_participants(spec).await?;
644 let mut execution_guards = admitted
645 .iter()
646 .map(|(slot, sequence)| {
647 VNextPrefillExecutionGuard::new(
648 &self.sequences,
649 Arc::clone(slot),
650 Arc::clone(sequence),
651 )
652 })
653 .collect::<Vec<_>>();
654 let mut participant_by_authority = BTreeMap::new();
655 for (participant_index, (_, sequence)) in admitted.iter().enumerate() {
656 if participant_by_authority
657 .insert(sequence.session.sequence_authority(), participant_index)
658 .is_some()
659 {
660 return Err(FerrumError::internal(
661 "vNext determinism admission duplicated sequence authority",
662 ));
663 }
664 }
665 let batch = ExecutionBatchParticipants::new(
666 admitted
667 .iter()
668 .map(|(_, sequence)| Arc::clone(&sequence.session))
669 .collect(),
670 )
671 .map_err(|error| FerrumError::backend(error.to_string()))?;
672 let canonical_indices = batch
673 .sessions()
674 .iter()
675 .map(|session| {
676 participant_by_authority
677 .remove(&session.sequence_authority())
678 .ok_or_else(|| {
679 FerrumError::internal("vNext determinism canonical participant is absent")
680 })
681 })
682 .collect::<Result<Vec<_>>>()?;
683 if !participant_by_authority.is_empty() {
684 return Err(FerrumError::internal(
685 "vNext determinism participant is absent from its canonical batch",
686 ));
687 }
688 let sequences = canonical_indices
689 .iter()
690 .map(|index| Arc::clone(&admitted[*index].1))
691 .collect::<Vec<_>>();
692 let participants = canonical_indices
693 .iter()
694 .map(|index| &spec.participants[*index])
695 .collect::<Vec<_>>();
696 let mut operation_guards = Vec::with_capacity(sequences.len());
697 for sequence in &sequences {
698 operation_guards.push(sequence.operation.lock().await);
699 }
700 for (sequence, participant) in sequences.iter().zip(&participants) {
701 self.extend_sequence(sequence, participant.full_extension()?)?;
702 }
703 let spans = participants
704 .iter()
705 .map(|participant| participant.token_span())
706 .collect::<Result<Vec<_>>>()?;
707 let step = match self.begin_step_for_spans_with_capacity(
708 &batch,
709 &sequences,
710 &spans,
711 spec.phase.wave_kind(),
712 )? {
713 VNextExecutionCapacityDecision::Ready(step) => step,
714 VNextExecutionCapacityDecision::Deferred(deferred) => {
715 return Err(Self::execution_capacity_error(&deferred))
716 }
717 VNextExecutionCapacityDecision::RequestStateDeferred(_) => {
718 return Err(FerrumError::internal(
719 "determinism step admission unexpectedly produced a Request-state deferral",
720 ))
721 }
722 };
723 let wave = match self.prepare_determinism_wave(&step, &sequences, &spans) {
724 Ok(wave) => wave,
725 Err(error) => return Err(self.abort_unsubmitted_step(step, error)),
726 };
727 let participant_order =
728 SubmissionWaveDeterminismParticipantOrder::from_logical_participant_sessions(
729 &wave,
730 &admitted
731 .iter()
732 .map(|(_, sequence)| Arc::clone(&sequence.session))
733 .collect::<Vec<_>>(),
734 )
735 .map_err(|error| FerrumError::backend(error.to_string()))?;
736 let active_bindings = sequences
737 .iter()
738 .map(|sequence| sequence.active_binding.as_ref())
739 .collect::<Vec<_>>();
740 let identity = match OperationDispatch::bind_compiled_submission_wave_identity(
741 &self.submission_wave_identity,
742 active_bindings.iter().copied(),
743 &wave,
744 &self.lane,
745 ) {
746 Ok(identity) => identity,
747 Err(error) => {
748 return Err(self.abort_prepared_unsubmitted_step(
749 step,
750 wave,
751 FerrumError::backend(error.to_string()),
752 ))
753 }
754 };
755 let restore = match self.bind_determinism_restore(
756 &participants,
757 participant_order,
758 spec.initial_state,
759 &identity,
760 &active_bindings,
761 &wave,
762 ) {
763 Ok(restore) => restore,
764 Err(error) => {
765 return Err(self.abort_prepared_unsubmitted_step(step, wave, error));
766 }
767 };
768
769 let submission = match spec.mode {
770 VNextDeterminismExecutionMode::Eager => {
771 OperationDispatch::encode_and_submit_determinism_eager_wave(
772 self.providers.providers(),
773 &self.resolved_plan,
774 &identity,
775 active_bindings.iter().copied(),
776 DeviceTimingMode::Off,
777 &restore,
778 spec.workspace_poison.fill_byte(),
779 wave,
780 &self.lane,
781 &self.reaper,
782 )
783 }
784 VNextDeterminismExecutionMode::Replayed => {
785 let program_id =
786 match OperationDispatch::reusable_execution_program_id_for_wave(
787 self.providers.providers(),
788 &self.resolved_plan,
789 &wave,
790 &self.lane,
791 ) {
792 Ok(Some(program_id)) => program_id,
793 Ok(None) => return Err(self.abort_prepared_unsubmitted_step(
794 step,
795 wave,
796 FerrumError::backend(
797 "vNext determinism replay has no exact reusable program identity",
798 ),
799 )),
800 Err(error) => {
801 return Err(self.abort_prepared_unsubmitted_step(
802 step,
803 wave,
804 FerrumError::backend(error.to_string()),
805 ))
806 }
807 };
808 let catalog = match self.reusable_execution_catalog.get() {
809 Some(catalog) if catalog.lane_epoch == self.lane.reusable_execution_epoch() => {
810 catalog
811 }
812 _ => {
813 return Err(self.abort_prepared_unsubmitted_step(
814 step,
815 wave,
816 FerrumError::backend(
817 "vNext determinism replay catalog is absent or stale",
818 ),
819 ))
820 }
821 };
822 let reusable_program = match catalog.programs.get(&program_id) {
823 Some(program) => program,
824 None => {
825 return Err(self.abort_prepared_unsubmitted_step(
826 step,
827 wave,
828 FerrumError::backend(
829 "vNext determinism replay program is absent from the sealed exact catalog",
830 ),
831 ))
832 }
833 };
834 OperationDispatch::encode_and_submit_determinism_replayed_wave(
835 self.providers.providers(),
836 &self.resolved_plan,
837 &identity,
838 active_bindings.iter().copied(),
839 DeviceTimingMode::Off,
840 &restore,
841 spec.workspace_poison.fill_byte(),
842 reusable_program,
843 wave,
844 &self.lane,
845 &self.reaper,
846 )
847 }
848 };
849 let handle = match submission {
850 Ok(handle) => handle,
851 Err(error) => return Err(self.determinism_dispatch_failure(step, error).await),
852 };
853 let reaper = Arc::clone(&self.reaper);
854 let evidence = self
855 .completion_worker
856 .execute(VNextCompletionTaskKind::WaveReadback, move || {
857 let evidence = handle.wait_into_evidence();
858 drop(reaper);
859 evidence
860 })
861 .await
862 .map_err(|error| {
863 FerrumError::backend(format!("vNext determinism completion task failed: {error}"))
864 })?
865 .map_err(|error| FerrumError::backend(error.to_string()));
866 let evidence = match evidence {
867 Ok(evidence) => evidence,
868 Err(error) => return Err(self.abort_step(step, error.to_string()).await),
869 };
870 step.try_retire_normal().map_err(|failure| {
871 FerrumError::backend(format!(
872 "vNext determinism step retirement failed: {}",
873 failure.error()
874 ))
875 })?;
876 drop(operation_guards);
877 for guard in &mut execution_guards {
878 guard.disarm();
879 }
880 {
881 let mut registry = self.sequences.lock();
882 for (slot, sequence) in &admitted {
883 registry.finish_prefill_execution(slot, sequence);
884 }
885 }
886 for (_, sequence) in admitted {
887 sequence.abort();
888 }
889 Ok(evidence)
890 }
891}
892
893#[cfg(test)]
894mod tests {
895 use super::*;
896
897 fn participant(start: usize, end: usize) -> VNextDeterminismParticipantSpec {
898 VNextDeterminismParticipantSpec::new(vec![0; end], start..end, end + 8).unwrap()
899 }
900
901 #[test]
902 fn participant_spec_requires_one_terminal_immediate_span() {
903 assert!(VNextDeterminismParticipantSpec::new(vec![], 0..0, 1).is_err());
904 assert!(VNextDeterminismParticipantSpec::new(vec![0, 1], 0..1, 2).is_err());
905 assert!(VNextDeterminismParticipantSpec::new(vec![0, 1], 1..2, 2).is_ok());
906 }
907
908 #[test]
909 fn decode_spec_rejects_multi_token_participants_and_unbounded_width() {
910 assert!(VNextDeterminismExecutionSpec::new(
911 VNextDeterminismPhase::Decode,
912 vec![participant(0, 2)],
913 VNextDeterminismInitialState::Zero,
914 VNextDeterminismWorkspacePoison::Zero,
915 VNextDeterminismExecutionMode::Eager,
916 )
917 .is_err());
918 assert!(VNextDeterminismExecutionSpec::new(
919 VNextDeterminismPhase::Decode,
920 (0..=MAX_VNEXT_DETERMINISM_PARTICIPANTS)
921 .map(|_| participant(0, 1))
922 .collect(),
923 VNextDeterminismInitialState::Zero,
924 VNextDeterminismWorkspacePoison::Zero,
925 VNextDeterminismExecutionMode::Eager,
926 )
927 .is_err());
928 }
929
930 #[test]
931 fn prefill_spec_accepts_chunk_boundary_shape() {
932 let spec = VNextDeterminismExecutionSpec::new(
933 VNextDeterminismPhase::Prefill,
934 vec![participant(4, 8)],
935 VNextDeterminismInitialState::Nonzero,
936 VNextDeterminismWorkspacePoison::A5,
937 VNextDeterminismExecutionMode::Replayed,
938 )
939 .unwrap();
940 assert_eq!(spec.participants()[0].immediate_range(), 4..8);
941 assert_eq!(spec.workspace_poison(), VNextDeterminismWorkspacePoison::A5);
942 }
943}