Skip to main content

ferrum_models/executor/vnext_executor/
determinism.rs

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}