Skip to main content

ferrum_interfaces/vnext/execution/
reusable.rs

1use super::{
2    canonical_fingerprint, invalid_plan, BTreeMap, Deserialize, Deserializer, DynamicBackingPoolId,
3    Serialize, VNextError,
4};
5
6pub const MAX_REUSABLE_EXECUTION_BUCKETS: usize = 64;
7pub const MAX_REUSABLE_EXECUTION_PROGRAM_SHAPES: usize = 256;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
10#[serde(transparent)]
11pub struct ReusableExecutionClassId(String);
12
13impl ReusableExecutionClassId {
14    pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
15        let value = value.into();
16        if value.is_empty()
17            || value.len() > 160
18            || !value.bytes().all(|byte| {
19                byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
20            })
21        {
22            return Err(invalid_plan(
23                "reusable execution class id is empty, too long, or non-portable",
24            ));
25        }
26        Ok(Self(value))
27    }
28
29    pub fn as_str(&self) -> &str {
30        &self.0
31    }
32}
33
34impl<'de> Deserialize<'de> for ReusableExecutionClassId {
35    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36    where
37        D: Deserializer<'de>,
38    {
39        Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
40    }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
44#[serde(transparent)]
45pub struct ReusableExecutionBucketId(String);
46
47impl ReusableExecutionBucketId {
48    fn derive(
49        class_id: &ReusableExecutionClassId,
50        capacity: &ReusableExecutionCapacity,
51    ) -> Result<Self, VNextError> {
52        #[derive(Serialize)]
53        struct BucketIdentity<'a> {
54            domain: &'static str,
55            class_id: &'a ReusableExecutionClassId,
56            capacity: &'a ReusableExecutionCapacity,
57        }
58
59        Ok(Self(format!(
60            "reusable-bucket/sha256/{}",
61            canonical_fingerprint(
62                &BucketIdentity {
63                    domain: "ferrum.runtime-vnext.reusable-execution-bucket.v1",
64                    class_id,
65                    capacity,
66                },
67                "fingerprint reusable execution bucket",
68            )?
69        )))
70    }
71
72    fn validate_for(
73        &self,
74        class_id: &ReusableExecutionClassId,
75        capacity: &ReusableExecutionCapacity,
76    ) -> Result<(), VNextError> {
77        if self != &Self::derive(class_id, capacity)? {
78            return Err(invalid_plan(
79                "reusable execution bucket id is not derived from its class and capacity",
80            ));
81        }
82        Ok(())
83    }
84
85    pub fn as_str(&self) -> &str {
86        &self.0
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
91#[serde(deny_unknown_fields)]
92pub struct ReusableExecutionCapacity {
93    maximum_sequences: u32,
94    maximum_tokens: u64,
95    maximum_pages: u64,
96}
97
98#[derive(Deserialize)]
99#[serde(deny_unknown_fields)]
100struct ReusableExecutionCapacityWire {
101    maximum_sequences: u32,
102    maximum_tokens: u64,
103    maximum_pages: u64,
104}
105
106impl ReusableExecutionCapacity {
107    pub fn new(
108        maximum_sequences: u32,
109        maximum_tokens: u64,
110        maximum_pages: u64,
111    ) -> Result<Self, VNextError> {
112        if maximum_sequences == 0 || maximum_tokens == 0 || maximum_pages == 0 {
113            return Err(invalid_plan(
114                "reusable execution capacity dimensions must be non-zero",
115            ));
116        }
117        Ok(Self {
118            maximum_sequences,
119            maximum_tokens,
120            maximum_pages,
121        })
122    }
123
124    pub const fn maximum_sequences(self) -> u32 {
125        self.maximum_sequences
126    }
127
128    pub const fn maximum_tokens(self) -> u64 {
129        self.maximum_tokens
130    }
131
132    pub const fn maximum_pages(self) -> u64 {
133        self.maximum_pages
134    }
135
136    pub const fn covers(self, sequences: u32, tokens: u64, pages: u64) -> bool {
137        sequences > 0
138            && tokens > 0
139            && sequences <= self.maximum_sequences
140            && tokens <= self.maximum_tokens
141            && pages <= self.maximum_pages
142    }
143
144    fn strictly_extends(self, previous: Self) -> bool {
145        self.maximum_sequences >= previous.maximum_sequences
146            && self.maximum_tokens >= previous.maximum_tokens
147            && self.maximum_pages >= previous.maximum_pages
148            && self != previous
149    }
150}
151
152impl<'de> Deserialize<'de> for ReusableExecutionCapacity {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        let wire = ReusableExecutionCapacityWire::deserialize(deserializer)?;
158        Self::new(
159            wire.maximum_sequences,
160            wire.maximum_tokens,
161            wire.maximum_pages,
162        )
163        .map_err(serde::de::Error::custom)
164    }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
168#[serde(deny_unknown_fields)]
169pub struct ReusableExecutionBucketSpec {
170    bucket_id: ReusableExecutionBucketId,
171    class_id: ReusableExecutionClassId,
172    capacity: ReusableExecutionCapacity,
173}
174
175#[derive(Deserialize)]
176#[serde(deny_unknown_fields)]
177struct ReusableExecutionBucketSpecWire {
178    bucket_id: String,
179    class_id: ReusableExecutionClassId,
180    capacity: ReusableExecutionCapacity,
181}
182
183impl ReusableExecutionBucketSpec {
184    pub fn new(
185        class_id: ReusableExecutionClassId,
186        capacity: ReusableExecutionCapacity,
187    ) -> Result<Self, VNextError> {
188        let bucket_id = ReusableExecutionBucketId::derive(&class_id, &capacity)?;
189        Ok(Self {
190            bucket_id,
191            class_id,
192            capacity,
193        })
194    }
195
196    fn validate(&self) -> Result<(), VNextError> {
197        self.bucket_id.validate_for(&self.class_id, &self.capacity)
198    }
199
200    pub fn bucket_id(&self) -> &ReusableExecutionBucketId {
201        &self.bucket_id
202    }
203
204    pub fn class_id(&self) -> &ReusableExecutionClassId {
205        &self.class_id
206    }
207
208    pub const fn capacity(&self) -> ReusableExecutionCapacity {
209        self.capacity
210    }
211}
212
213impl<'de> Deserialize<'de> for ReusableExecutionBucketSpec {
214    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215    where
216        D: Deserializer<'de>,
217    {
218        let wire = ReusableExecutionBucketSpecWire::deserialize(deserializer)?;
219        let spec = Self {
220            bucket_id: ReusableExecutionBucketId(wire.bucket_id),
221            class_id: wire.class_id,
222            capacity: wire.capacity,
223        };
224        spec.validate().map_err(serde::de::Error::custom)?;
225        Ok(spec)
226    }
227}
228
229/// Exact logical startup capture case for a reusable device-program catalog.
230///
231/// Workspace buckets are capacity envelopes and may cover smaller work. These
232/// shapes are different: each row requests one exact logical startup case.
233/// The resulting physical program identity additionally includes pages,
234/// provider topology, lane layout, and runtime fingerprints. Consequently one
235/// logical width does not claim replay coverage for every context/topology
236/// variant. v0.8.0 deliberately does not imply padded replay.
237#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
238#[serde(tag = "topology", rename_all = "snake_case", deny_unknown_fields)]
239pub enum ReusableExecutionProgramShape {
240    UniformDecode {
241        request_capacity: u32,
242        token_capacity: u64,
243        query_tokens_per_sequence: u32,
244    },
245    Prefill {
246        tokens_processed: u64,
247        token_capacity: u64,
248        total_prompt_tokens: u64,
249    },
250}
251
252impl ReusableExecutionProgramShape {
253    pub fn uniform_decode(
254        request_capacity: u32,
255        query_tokens_per_sequence: u32,
256    ) -> Result<Self, VNextError> {
257        let token_capacity = u64::from(request_capacity)
258            .checked_mul(u64::from(query_tokens_per_sequence))
259            .ok_or_else(|| invalid_plan("reusable decode program token capacity overflows u64"))?;
260        let shape = Self::UniformDecode {
261            request_capacity,
262            token_capacity,
263            query_tokens_per_sequence,
264        };
265        shape.validate()?;
266        Ok(shape)
267    }
268
269    pub fn prefill(
270        tokens_processed: u64,
271        token_capacity: u64,
272        total_prompt_tokens: u64,
273    ) -> Result<Self, VNextError> {
274        let shape = Self::Prefill {
275            tokens_processed,
276            token_capacity,
277            total_prompt_tokens,
278        };
279        shape.validate()?;
280        Ok(shape)
281    }
282
283    fn validate(self) -> Result<(), VNextError> {
284        match self {
285            Self::UniformDecode {
286                request_capacity,
287                token_capacity,
288                query_tokens_per_sequence,
289            } => {
290                let expected_tokens =
291                    u64::from(request_capacity).checked_mul(u64::from(query_tokens_per_sequence));
292                if request_capacity == 0
293                    || query_tokens_per_sequence == 0
294                    || expected_tokens != Some(token_capacity)
295                {
296                    return Err(invalid_plan(
297                        "reusable decode program shape is empty or internally inconsistent",
298                    ));
299                }
300            }
301            Self::Prefill {
302                tokens_processed,
303                token_capacity,
304                total_prompt_tokens,
305            } => {
306                if token_capacity == 0
307                    || total_prompt_tokens == 0
308                    || tokens_processed
309                        .checked_add(token_capacity)
310                        .is_none_or(|end| end > total_prompt_tokens)
311                {
312                    return Err(invalid_plan(
313                        "reusable prefill program shape is empty or exceeds its prompt frontier",
314                    ));
315                }
316            }
317        }
318        Ok(())
319    }
320
321    pub const fn request_capacity(self) -> u32 {
322        match self {
323            Self::UniformDecode {
324                request_capacity, ..
325            } => request_capacity,
326            Self::Prefill { .. } => 1,
327        }
328    }
329
330    pub const fn token_capacity(self) -> u64 {
331        match self {
332            Self::UniformDecode { token_capacity, .. } | Self::Prefill { token_capacity, .. } => {
333                token_capacity
334            }
335        }
336    }
337}
338
339impl<'de> Deserialize<'de> for ReusableExecutionProgramShape {
340    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341    where
342        D: Deserializer<'de>,
343    {
344        #[derive(Deserialize)]
345        #[serde(tag = "topology", rename_all = "snake_case", deny_unknown_fields)]
346        enum Wire {
347            UniformDecode {
348                request_capacity: u32,
349                token_capacity: u64,
350                query_tokens_per_sequence: u32,
351            },
352            Prefill {
353                tokens_processed: u64,
354                token_capacity: u64,
355                total_prompt_tokens: u64,
356            },
357        }
358
359        let shape = match Wire::deserialize(deserializer)? {
360            Wire::UniformDecode {
361                request_capacity,
362                token_capacity,
363                query_tokens_per_sequence,
364            } => Self::UniformDecode {
365                request_capacity,
366                token_capacity,
367                query_tokens_per_sequence,
368            },
369            Wire::Prefill {
370                tokens_processed,
371                token_capacity,
372                total_prompt_tokens,
373            } => Self::Prefill {
374                tokens_processed,
375                token_capacity,
376                total_prompt_tokens,
377            },
378        };
379        shape.validate().map_err(serde::de::Error::custom)?;
380        Ok(shape)
381    }
382}
383
384/// One logical startup capture case bound to its covering workspace class.
385///
386/// A spec budgets one observed physical variant. If startup observes more than
387/// one physical identity for the same case, backend capacity fails closed; it
388/// is never interpreted as universal coverage of that logical shape. Multiple
389/// logical cases may independently observe the same physical identity when
390/// their complete device-level identity is equal; that is physical reuse, not
391/// inferred coverage from another case.
392#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
393#[serde(deny_unknown_fields)]
394pub struct ReusableExecutionProgramSpec {
395    class_id: ReusableExecutionClassId,
396    shape: ReusableExecutionProgramShape,
397}
398
399impl ReusableExecutionProgramSpec {
400    pub fn new(
401        class_id: ReusableExecutionClassId,
402        shape: ReusableExecutionProgramShape,
403    ) -> Result<Self, VNextError> {
404        shape.validate()?;
405        Ok(Self { class_id, shape })
406    }
407
408    pub fn class_id(&self) -> &ReusableExecutionClassId {
409        &self.class_id
410    }
411
412    pub const fn shape(&self) -> ReusableExecutionProgramShape {
413        self.shape
414    }
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub enum ReusableExecutionProgramShapeSemantics {
420    Exact,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
424#[serde(rename_all = "snake_case")]
425pub enum ReusableExecutionCatalogMissPolicy {
426    EagerFallback,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
430#[serde(rename_all = "snake_case")]
431pub enum ReusableExecutionCatalogLifetime {
432    StartupSealed,
433}
434
435/// Fully resolved, fingerprinted logical startup-capture contract.
436///
437/// Physical catalog receipts remain authoritative for what was actually made
438/// resident. Runtime contexts producing another pages/topology identity use
439/// the explicit catalog-miss policy.
440#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
441#[serde(deny_unknown_fields)]
442pub struct ReusableExecutionProgramPolicy {
443    shape_semantics: ReusableExecutionProgramShapeSemantics,
444    catalog_miss_policy: ReusableExecutionCatalogMissPolicy,
445    catalog_lifetime: ReusableExecutionCatalogLifetime,
446    warmup_passes: u32,
447    capture_passes: u32,
448    replay_validation_passes: u32,
449    programs: Vec<ReusableExecutionProgramSpec>,
450}
451
452#[derive(Deserialize)]
453#[serde(deny_unknown_fields)]
454struct ReusableExecutionProgramPolicyWire {
455    shape_semantics: ReusableExecutionProgramShapeSemantics,
456    catalog_miss_policy: ReusableExecutionCatalogMissPolicy,
457    catalog_lifetime: ReusableExecutionCatalogLifetime,
458    warmup_passes: u32,
459    capture_passes: u32,
460    replay_validation_passes: u32,
461    programs: Vec<ReusableExecutionProgramSpec>,
462}
463
464impl ReusableExecutionProgramPolicy {
465    pub fn exact_startup_sealed(
466        warmup_passes: u32,
467        capture_passes: u32,
468        replay_validation_passes: u32,
469        mut programs: Vec<ReusableExecutionProgramSpec>,
470    ) -> Result<Self, VNextError> {
471        let prerequisite_prefill_programs = programs
472            .iter()
473            .filter_map(|program| match program.shape() {
474                ReusableExecutionProgramShape::Prefill {
475                    tokens_processed,
476                    total_prompt_tokens,
477                    ..
478                } if tokens_processed > 0 => Some(ReusableExecutionProgramSpec {
479                    class_id: program.class_id().clone(),
480                    shape: ReusableExecutionProgramShape::Prefill {
481                        tokens_processed: 0,
482                        token_capacity: tokens_processed,
483                        total_prompt_tokens,
484                    },
485                }),
486                _ => None,
487            })
488            .collect::<Vec<_>>();
489        programs.extend(prerequisite_prefill_programs);
490        programs.sort_unstable();
491        programs.dedup();
492        let policy = Self {
493            shape_semantics: ReusableExecutionProgramShapeSemantics::Exact,
494            catalog_miss_policy: ReusableExecutionCatalogMissPolicy::EagerFallback,
495            catalog_lifetime: ReusableExecutionCatalogLifetime::StartupSealed,
496            warmup_passes,
497            capture_passes,
498            replay_validation_passes,
499            programs,
500        };
501        policy.validate()?;
502        Ok(policy)
503    }
504
505    fn validate(&self) -> Result<(), VNextError> {
506        if self.shape_semantics != ReusableExecutionProgramShapeSemantics::Exact
507            || self.catalog_miss_policy != ReusableExecutionCatalogMissPolicy::EagerFallback
508            || self.catalog_lifetime != ReusableExecutionCatalogLifetime::StartupSealed
509            || self.warmup_passes == 0
510            || self.capture_passes == 0
511            || self.replay_validation_passes == 0
512            || self.programs.is_empty()
513            || self.programs.len() > MAX_REUSABLE_EXECUTION_PROGRAM_SHAPES
514        {
515            return Err(invalid_plan(
516                "reusable execution program policy has invalid semantics, passes, or shape count",
517            ));
518        }
519        if self.programs.windows(2).any(|pair| pair[0] >= pair[1]) {
520            return Err(invalid_plan(
521                "reusable execution program shapes are duplicate or non-canonical",
522            ));
523        }
524        for program in &self.programs {
525            program.shape().validate()?;
526        }
527        let mut shape_classes = BTreeMap::new();
528        for program in &self.programs {
529            if shape_classes
530                .insert(program.shape(), program.class_id())
531                .is_some()
532            {
533                return Err(invalid_plan(
534                    "reusable execution logical shape is assigned to multiple workspace classes",
535                ));
536            }
537        }
538        if self.programs.iter().any(|program| match program.shape() {
539            ReusableExecutionProgramShape::Prefill {
540                tokens_processed,
541                total_prompt_tokens,
542                ..
543            } if tokens_processed > 0 => !self.programs.contains(&ReusableExecutionProgramSpec {
544                class_id: program.class_id().clone(),
545                shape: ReusableExecutionProgramShape::Prefill {
546                    tokens_processed: 0,
547                    token_capacity: tokens_processed,
548                    total_prompt_tokens,
549                },
550            }),
551            _ => false,
552        }) {
553            return Err(invalid_plan(
554                "reusable prefill program policy omits a prerequisite prefix shape",
555            ));
556        }
557        Ok(())
558    }
559
560    pub const fn shape_semantics(&self) -> ReusableExecutionProgramShapeSemantics {
561        self.shape_semantics
562    }
563
564    pub const fn catalog_miss_policy(&self) -> ReusableExecutionCatalogMissPolicy {
565        self.catalog_miss_policy
566    }
567
568    pub const fn catalog_lifetime(&self) -> ReusableExecutionCatalogLifetime {
569        self.catalog_lifetime
570    }
571
572    pub const fn warmup_passes(&self) -> u32 {
573        self.warmup_passes
574    }
575
576    pub const fn capture_passes(&self) -> u32 {
577        self.capture_passes
578    }
579
580    pub const fn replay_validation_passes(&self) -> u32 {
581        self.replay_validation_passes
582    }
583
584    pub fn programs(&self) -> &[ReusableExecutionProgramSpec] {
585        &self.programs
586    }
587}
588
589impl<'de> Deserialize<'de> for ReusableExecutionProgramPolicy {
590    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
591    where
592        D: Deserializer<'de>,
593    {
594        let wire = ReusableExecutionProgramPolicyWire::deserialize(deserializer)?;
595        let policy = Self {
596            shape_semantics: wire.shape_semantics,
597            catalog_miss_policy: wire.catalog_miss_policy,
598            catalog_lifetime: wire.catalog_lifetime,
599            warmup_passes: wire.warmup_passes,
600            capture_passes: wire.capture_passes,
601            replay_validation_passes: wire.replay_validation_passes,
602            programs: wire.programs,
603        };
604        policy.validate().map_err(serde::de::Error::custom)?;
605        Ok(policy)
606    }
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
610#[serde(deny_unknown_fields)]
611pub struct ReusableExecutionPolicy {
612    maximum_reusable_lanes: u32,
613    buckets: Vec<ReusableExecutionBucketSpec>,
614    #[serde(default, skip_serializing_if = "Option::is_none")]
615    program_policy: Option<ReusableExecutionProgramPolicy>,
616}
617
618#[derive(Deserialize)]
619#[serde(deny_unknown_fields)]
620struct ReusableExecutionPolicyWire {
621    maximum_reusable_lanes: u32,
622    buckets: Vec<ReusableExecutionBucketSpec>,
623    #[serde(default)]
624    program_policy: Option<ReusableExecutionProgramPolicy>,
625}
626
627impl ReusableExecutionPolicy {
628    pub fn new(
629        maximum_reusable_lanes: u32,
630        mut buckets: Vec<ReusableExecutionBucketSpec>,
631    ) -> Result<Self, VNextError> {
632        buckets.sort_by(|left, right| {
633            (left.class_id(), left.capacity()).cmp(&(right.class_id(), right.capacity()))
634        });
635        let policy = Self {
636            maximum_reusable_lanes,
637            buckets,
638            program_policy: None,
639        };
640        policy.validate()?;
641        Ok(policy)
642    }
643
644    pub fn with_program_policy(
645        mut self,
646        program_policy: ReusableExecutionProgramPolicy,
647    ) -> Result<Self, VNextError> {
648        self.program_policy = Some(program_policy);
649        self.validate()?;
650        Ok(self)
651    }
652
653    pub(crate) fn validate(&self) -> Result<(), VNextError> {
654        if self.maximum_reusable_lanes == 0
655            || self.buckets.is_empty()
656            || self.buckets.len() > MAX_REUSABLE_EXECUTION_BUCKETS
657        {
658            return Err(invalid_plan(
659                "reusable execution policy has an invalid lane or bucket count",
660            ));
661        }
662        for bucket in &self.buckets {
663            bucket.validate()?;
664        }
665        if let Some(program_policy) = &self.program_policy {
666            program_policy.validate()?;
667            if program_policy.programs().iter().any(|program| {
668                let shape = program.shape();
669                self.smallest_covering_bucket(
670                    program.class_id(),
671                    shape.request_capacity(),
672                    shape.token_capacity(),
673                    0,
674                )
675                .is_none()
676            }) {
677                return Err(invalid_plan(
678                    "reusable execution program shape has no covering workspace bucket in its class",
679                ));
680            }
681        }
682        if self.buckets.windows(2).any(|pair| {
683            let left = &pair[0];
684            let right = &pair[1];
685            left.class_id() > right.class_id()
686                || (left.class_id() == right.class_id()
687                    && !right.capacity().strictly_extends(left.capacity()))
688        }) {
689            return Err(invalid_plan(
690                "reusable execution buckets are not canonical monotonic class chains",
691            ));
692        }
693        Ok(())
694    }
695
696    pub const fn maximum_reusable_lanes(&self) -> u32 {
697        self.maximum_reusable_lanes
698    }
699
700    pub fn buckets(&self) -> &[ReusableExecutionBucketSpec] {
701        &self.buckets
702    }
703
704    pub fn program_policy(&self) -> Option<&ReusableExecutionProgramPolicy> {
705        self.program_policy.as_ref()
706    }
707
708    pub fn startup_capture_case_count(&self) -> usize {
709        self.program_policy
710            .as_ref()
711            .map_or(self.buckets.len(), |policy| policy.programs().len())
712    }
713
714    pub fn bucket(
715        &self,
716        bucket_id: &ReusableExecutionBucketId,
717    ) -> Option<&ReusableExecutionBucketSpec> {
718        self.buckets
719            .iter()
720            .find(|bucket| bucket.bucket_id() == bucket_id)
721    }
722
723    pub fn smallest_covering_bucket(
724        &self,
725        class_id: &ReusableExecutionClassId,
726        sequences: u32,
727        tokens: u64,
728        pages: u64,
729    ) -> Option<&ReusableExecutionBucketSpec> {
730        self.buckets.iter().find(|bucket| {
731            bucket.class_id() == class_id && bucket.capacity().covers(sequences, tokens, pages)
732        })
733    }
734}
735
736impl<'de> Deserialize<'de> for ReusableExecutionPolicy {
737    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
738    where
739        D: Deserializer<'de>,
740    {
741        let wire = ReusableExecutionPolicyWire::deserialize(deserializer)?;
742        let policy = Self {
743            maximum_reusable_lanes: wire.maximum_reusable_lanes,
744            buckets: wire.buckets,
745            program_policy: wire.program_policy,
746        };
747        policy.validate().map_err(serde::de::Error::custom)?;
748        Ok(policy)
749    }
750}
751
752#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
753#[serde(deny_unknown_fields)]
754pub struct ReusablePoolWorkspaceBudget {
755    pool_id: DynamicBackingPoolId,
756    step_bytes: u64,
757    invocation_bytes: u64,
758}
759
760#[derive(Deserialize)]
761#[serde(deny_unknown_fields)]
762struct ReusablePoolWorkspaceBudgetWire {
763    pool_id: DynamicBackingPoolId,
764    step_bytes: u64,
765    invocation_bytes: u64,
766}
767
768impl ReusablePoolWorkspaceBudget {
769    pub(crate) fn new(
770        pool_id: DynamicBackingPoolId,
771        step_bytes: u64,
772        invocation_bytes: u64,
773    ) -> Result<Self, VNextError> {
774        let budget = Self {
775            pool_id,
776            step_bytes,
777            invocation_bytes,
778        };
779        budget.validate()?;
780        Ok(budget)
781    }
782
783    fn validate(&self) -> Result<(), VNextError> {
784        self.step_bytes
785            .checked_add(self.invocation_bytes)
786            .filter(|total| *total > 0)
787            .ok_or_else(|| {
788                invalid_plan("reusable pool workspace budget is empty or overflows u64")
789            })?;
790        Ok(())
791    }
792
793    pub fn pool_id(&self) -> &DynamicBackingPoolId {
794        &self.pool_id
795    }
796
797    pub const fn step_bytes(&self) -> u64 {
798        self.step_bytes
799    }
800
801    pub const fn invocation_bytes(&self) -> u64 {
802        self.invocation_bytes
803    }
804
805    pub fn total_bytes(&self) -> Result<u64, VNextError> {
806        self.step_bytes
807            .checked_add(self.invocation_bytes)
808            .ok_or_else(|| invalid_plan("reusable pool workspace budget overflows u64"))
809    }
810}
811
812impl<'de> Deserialize<'de> for ReusablePoolWorkspaceBudget {
813    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
814    where
815        D: Deserializer<'de>,
816    {
817        let wire = ReusablePoolWorkspaceBudgetWire::deserialize(deserializer)?;
818        Self::new(wire.pool_id, wire.step_bytes, wire.invocation_bytes)
819            .map_err(serde::de::Error::custom)
820    }
821}
822
823#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
824#[serde(deny_unknown_fields)]
825pub struct ResolvedReusableExecutionBucket {
826    bucket: ReusableExecutionBucketSpec,
827    pool_budgets: Vec<ReusablePoolWorkspaceBudget>,
828}
829
830#[derive(Deserialize)]
831#[serde(deny_unknown_fields)]
832struct ResolvedReusableExecutionBucketWire {
833    bucket: ReusableExecutionBucketSpec,
834    pool_budgets: Vec<ReusablePoolWorkspaceBudget>,
835}
836
837impl ResolvedReusableExecutionBucket {
838    pub(crate) fn new(
839        bucket: ReusableExecutionBucketSpec,
840        pool_budgets: Vec<ReusablePoolWorkspaceBudget>,
841    ) -> Result<Self, VNextError> {
842        let resolved = Self {
843            bucket,
844            pool_budgets,
845        };
846        resolved.validate()?;
847        Ok(resolved)
848    }
849
850    fn validate(&self) -> Result<(), VNextError> {
851        self.bucket.validate()?;
852        if self
853            .pool_budgets
854            .windows(2)
855            .any(|pair| pair[0].pool_id() >= pair[1].pool_id())
856        {
857            return Err(invalid_plan(
858                "resolved reusable bucket pool budgets are duplicate or non-canonical",
859            ));
860        }
861        for budget in &self.pool_budgets {
862            budget.validate()?;
863        }
864        Ok(())
865    }
866
867    pub fn bucket(&self) -> &ReusableExecutionBucketSpec {
868        &self.bucket
869    }
870
871    pub fn pool_budgets(&self) -> &[ReusablePoolWorkspaceBudget] {
872        &self.pool_budgets
873    }
874}
875
876impl<'de> Deserialize<'de> for ResolvedReusableExecutionBucket {
877    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
878    where
879        D: Deserializer<'de>,
880    {
881        let wire = ResolvedReusableExecutionBucketWire::deserialize(deserializer)?;
882        Self::new(wire.bucket, wire.pool_budgets).map_err(serde::de::Error::custom)
883    }
884}
885
886#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
887#[serde(deny_unknown_fields)]
888pub struct ReusableExecutionMemoryPlan {
889    maximum_reusable_lanes: u32,
890    maximum_device_executables: u64,
891    buckets: Vec<ResolvedReusableExecutionBucket>,
892    #[serde(default, skip_serializing_if = "Option::is_none")]
893    program_policy: Option<ReusableExecutionProgramPolicy>,
894}
895
896#[derive(Deserialize)]
897#[serde(deny_unknown_fields)]
898struct ReusableExecutionMemoryPlanWire {
899    maximum_reusable_lanes: u32,
900    maximum_device_executables: u64,
901    buckets: Vec<ResolvedReusableExecutionBucket>,
902    #[serde(default)]
903    program_policy: Option<ReusableExecutionProgramPolicy>,
904}
905
906impl ReusableExecutionMemoryPlan {
907    pub(crate) fn new(
908        maximum_reusable_lanes: u32,
909        maximum_device_executables: u64,
910        buckets: Vec<ResolvedReusableExecutionBucket>,
911    ) -> Result<Self, VNextError> {
912        Self::new_with_program_policy(
913            maximum_reusable_lanes,
914            maximum_device_executables,
915            buckets,
916            None,
917        )
918    }
919
920    pub(crate) fn new_with_program_policy(
921        maximum_reusable_lanes: u32,
922        maximum_device_executables: u64,
923        buckets: Vec<ResolvedReusableExecutionBucket>,
924        program_policy: Option<ReusableExecutionProgramPolicy>,
925    ) -> Result<Self, VNextError> {
926        let plan = Self {
927            maximum_reusable_lanes,
928            maximum_device_executables,
929            buckets,
930            program_policy,
931        };
932        plan.validate_local()?;
933        Ok(plan)
934    }
935
936    pub(crate) fn validate_local(&self) -> Result<(), VNextError> {
937        if self.maximum_reusable_lanes == 0
938            || self.maximum_device_executables == 0
939            || self.buckets.is_empty()
940            || self.buckets.len() > MAX_REUSABLE_EXECUTION_BUCKETS
941        {
942            return Err(invalid_plan(
943                "reusable execution memory plan has an invalid lane, executable, or bucket count",
944            ));
945        }
946        for bucket in &self.buckets {
947            bucket.validate()?;
948        }
949        let mut canonical = ReusableExecutionPolicy::new(
950            self.maximum_reusable_lanes,
951            self.buckets
952                .iter()
953                .map(|bucket| bucket.bucket().clone())
954                .collect(),
955        )?;
956        if let Some(program_policy) = self.program_policy.clone() {
957            canonical = canonical.with_program_policy(program_policy)?;
958        }
959        if canonical.buckets().iter().ne(self
960            .buckets
961            .iter()
962            .map(ResolvedReusableExecutionBucket::bucket))
963        {
964            return Err(invalid_plan(
965                "reusable execution memory buckets are non-canonical",
966            ));
967        }
968        Ok(())
969    }
970
971    pub const fn maximum_reusable_lanes(&self) -> u32 {
972        self.maximum_reusable_lanes
973    }
974
975    pub const fn maximum_device_executables(&self) -> u64 {
976        self.maximum_device_executables
977    }
978
979    pub fn buckets(&self) -> &[ResolvedReusableExecutionBucket] {
980        &self.buckets
981    }
982
983    pub fn program_policy(&self) -> Option<&ReusableExecutionProgramPolicy> {
984        self.program_policy.as_ref()
985    }
986
987    pub fn bucket(
988        &self,
989        bucket_id: &ReusableExecutionBucketId,
990    ) -> Option<&ResolvedReusableExecutionBucket> {
991        self.buckets
992            .iter()
993            .find(|bucket| bucket.bucket().bucket_id() == bucket_id)
994    }
995
996    pub fn smallest_covering_bucket(
997        &self,
998        class_id: &ReusableExecutionClassId,
999        sequences: u32,
1000        tokens: u64,
1001        pages: u64,
1002    ) -> Option<&ResolvedReusableExecutionBucket> {
1003        self.buckets.iter().find(|bucket| {
1004            bucket.bucket().class_id() == class_id
1005                && bucket.bucket().capacity().covers(sequences, tokens, pages)
1006        })
1007    }
1008
1009    pub(crate) fn policy(&self) -> Result<ReusableExecutionPolicy, VNextError> {
1010        let mut policy = ReusableExecutionPolicy::new(
1011            self.maximum_reusable_lanes,
1012            self.buckets
1013                .iter()
1014                .map(|bucket| bucket.bucket().clone())
1015                .collect(),
1016        )?;
1017        if let Some(program_policy) = self.program_policy.clone() {
1018            policy = policy.with_program_policy(program_policy)?;
1019        }
1020        Ok(policy)
1021    }
1022
1023    pub(crate) fn pool_workspace_ceilings(
1024        &self,
1025    ) -> Result<BTreeMap<DynamicBackingPoolId, u64>, VNextError> {
1026        let lanes = u64::from(self.maximum_reusable_lanes);
1027        let mut totals = BTreeMap::new();
1028        for bucket in &self.buckets {
1029            for budget in bucket.pool_budgets() {
1030                let bytes = budget
1031                    .total_bytes()?
1032                    .checked_mul(lanes)
1033                    .ok_or_else(|| invalid_plan("reusable lane workspace budget overflows u64"))?;
1034                let total = totals.entry(budget.pool_id().clone()).or_insert(0_u64);
1035                *total = total
1036                    .checked_add(bytes)
1037                    .ok_or_else(|| invalid_plan("reusable pool workspace ceiling overflows u64"))?;
1038            }
1039        }
1040        Ok(totals)
1041    }
1042}
1043
1044impl<'de> Deserialize<'de> for ReusableExecutionMemoryPlan {
1045    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1046    where
1047        D: Deserializer<'de>,
1048    {
1049        let wire = ReusableExecutionMemoryPlanWire::deserialize(deserializer)?;
1050        Self::new_with_program_policy(
1051            wire.maximum_reusable_lanes,
1052            wire.maximum_device_executables,
1053            wire.buckets,
1054            wire.program_policy,
1055        )
1056        .map_err(serde::de::Error::custom)
1057    }
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062    use super::{
1063        ReusableExecutionBucketSpec, ReusableExecutionCapacity, ReusableExecutionClassId,
1064        ReusableExecutionPolicy, ReusableExecutionProgramPolicy, ReusableExecutionProgramShape,
1065        ReusableExecutionProgramSpec,
1066    };
1067
1068    fn bucket(class: &str, sequences: u32, tokens: u64) -> ReusableExecutionBucketSpec {
1069        ReusableExecutionBucketSpec::new(
1070            ReusableExecutionClassId::new(class).unwrap(),
1071            ReusableExecutionCapacity::new(sequences, tokens, 1).unwrap(),
1072        )
1073        .unwrap()
1074    }
1075
1076    fn program(class: &str, shape: ReusableExecutionProgramShape) -> ReusableExecutionProgramSpec {
1077        ReusableExecutionProgramSpec::new(ReusableExecutionClassId::new(class).unwrap(), shape)
1078            .unwrap()
1079    }
1080
1081    #[test]
1082    fn reusable_policy_canonicalizes_and_selects_within_one_class() {
1083        let policy = ReusableExecutionPolicy::new(
1084            1,
1085            vec![
1086                bucket("uniform-query", 4, 4),
1087                bucket("packed-prefill", 1, 64),
1088                bucket("uniform-query", 1, 1),
1089                bucket("uniform-query", 2, 2),
1090            ],
1091        )
1092        .unwrap();
1093
1094        let class = ReusableExecutionClassId::new("uniform-query").unwrap();
1095        assert_eq!(
1096            policy
1097                .smallest_covering_bucket(&class, 3, 3, 0)
1098                .unwrap()
1099                .capacity(),
1100            ReusableExecutionCapacity::new(4, 4, 1).unwrap()
1101        );
1102        assert!(policy.smallest_covering_bucket(&class, 5, 5, 0).is_none());
1103    }
1104
1105    #[test]
1106    fn reusable_policy_wire_rejects_derived_id_tampering() {
1107        let policy =
1108            ReusableExecutionPolicy::new(1, vec![bucket("packed-prefill", 1, 64)]).unwrap();
1109        let mut value = serde_json::to_value(&policy).unwrap();
1110        value["buckets"][0]["bucket_id"] = serde_json::Value::String("forged".to_owned());
1111        assert!(serde_json::from_value::<ReusableExecutionPolicy>(value).is_err());
1112    }
1113
1114    #[test]
1115    fn reusable_policy_rejects_incomparable_capacities_within_class() {
1116        let first = bucket("mixed", 1, 64);
1117        let second = bucket("mixed", 4, 4);
1118        assert!(ReusableExecutionPolicy::new(1, vec![first, second]).is_err());
1119    }
1120
1121    #[test]
1122    fn exact_program_policy_is_canonical_and_adds_prefill_prerequisite() {
1123        let decode_4 = program(
1124            "uniform-query",
1125            ReusableExecutionProgramShape::uniform_decode(4, 1).unwrap(),
1126        );
1127        let decode_1 = program(
1128            "uniform-query",
1129            ReusableExecutionProgramShape::uniform_decode(1, 1).unwrap(),
1130        );
1131        let final_prefill = program(
1132            "packed-prefill",
1133            ReusableExecutionProgramShape::prefill(4, 4, 8).unwrap(),
1134        );
1135        let prerequisite = program(
1136            "packed-prefill",
1137            ReusableExecutionProgramShape::prefill(0, 4, 8).unwrap(),
1138        );
1139
1140        let program_policy = ReusableExecutionProgramPolicy::exact_startup_sealed(
1141            1,
1142            1,
1143            1,
1144            vec![
1145                decode_4.clone(),
1146                final_prefill.clone(),
1147                decode_1.clone(),
1148                decode_4.clone(),
1149            ],
1150        )
1151        .unwrap();
1152
1153        assert_eq!(
1154            program_policy.programs(),
1155            &[prerequisite, final_prefill, decode_1, decode_4]
1156        );
1157    }
1158
1159    #[test]
1160    fn program_policy_wire_rejects_missing_prefill_prerequisite() {
1161        let program_policy = ReusableExecutionProgramPolicy::exact_startup_sealed(
1162            1,
1163            1,
1164            1,
1165            vec![program(
1166                "packed-prefill",
1167                ReusableExecutionProgramShape::prefill(4, 4, 8).unwrap(),
1168            )],
1169        )
1170        .unwrap();
1171        let mut value = serde_json::to_value(program_policy).unwrap();
1172        value["programs"].as_array_mut().unwrap().remove(0);
1173
1174        assert!(serde_json::from_value::<ReusableExecutionProgramPolicy>(value).is_err());
1175    }
1176
1177    #[test]
1178    fn legacy_reusable_policy_wire_omits_program_policy() {
1179        let policy =
1180            ReusableExecutionPolicy::new(1, vec![bucket("packed-prefill", 1, 64)]).unwrap();
1181        let value = serde_json::to_value(&policy).unwrap();
1182
1183        assert!(value.get("program_policy").is_none());
1184        assert_eq!(
1185            serde_json::from_value::<ReusableExecutionPolicy>(value).unwrap(),
1186            policy
1187        );
1188    }
1189
1190    #[test]
1191    fn exact_program_policy_rejects_inconsistent_decode_shape_wire() {
1192        let shape = ReusableExecutionProgramShape::uniform_decode(4, 1).unwrap();
1193        let mut value = serde_json::to_value(shape).unwrap();
1194        value["token_capacity"] = serde_json::json!(3);
1195
1196        assert!(serde_json::from_value::<ReusableExecutionProgramShape>(value).is_err());
1197    }
1198}