1use super::{
2 invalid_plan, Deserialize, Deserializer, Digest, Range, Serialize, Sha256, VNextError,
3};
4use std::sync::Arc;
5
6pub const MAX_PROVIDER_WORKSPACE_SHAPE_BUCKETS: usize = 64;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub(crate) struct DynamicResourceShape {
12 pub(super) sequences: u32,
13 pub(super) tokens: u64,
14 pub(super) pages: u64,
15}
16
17impl DynamicResourceShape {
18 pub(crate) fn new(sequences: u32, tokens: u64, pages: u64) -> Result<Self, VNextError> {
19 if sequences == 0 || tokens == 0 || pages == 0 {
20 return Err(invalid_plan(
21 "dynamic resource shape dimensions must be non-zero",
22 ));
23 }
24 Ok(Self {
25 sequences,
26 tokens,
27 pages,
28 })
29 }
30
31 pub(crate) const fn sequences(self) -> u32 {
32 self.sequences
33 }
34
35 pub(crate) const fn tokens(self) -> u64 {
36 self.tokens
37 }
38
39 pub(crate) const fn pages(self) -> u64 {
40 self.pages
41 }
42
43 pub(crate) const fn from_validated(sequences: u32, tokens: u64, pages: u64) -> Self {
44 Self {
45 sequences,
46 tokens,
47 pages,
48 }
49 }
50}
51
52#[derive(Clone, Serialize)]
56pub struct TokenSpanWork {
57 pub(super) immediate_tokens: u64,
58 pub(super) full_input_tokens: u64,
59 pub(super) fit_input_tokens: u64,
60 pub(super) immediate_start_token: u64,
61 pub(super) immediate_end_token: u64,
62 pub(super) fingerprint: String,
63 #[serde(skip)]
66 checkpoint_tokens: Option<Arc<[u32]>>,
67}
68
69impl std::fmt::Debug for TokenSpanWork {
70 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 formatter
72 .debug_struct("TokenSpanWork")
73 .field("immediate_tokens", &self.immediate_tokens)
74 .field("full_input_tokens", &self.full_input_tokens)
75 .field("fit_input_tokens", &self.fit_input_tokens)
76 .field("immediate_start_token", &self.immediate_start_token)
77 .field("immediate_end_token", &self.immediate_end_token)
78 .field("fingerprint", &self.fingerprint)
79 .finish()
80 }
81}
82
83impl PartialEq for TokenSpanWork {
84 fn eq(&self, other: &Self) -> bool {
85 self.immediate_tokens == other.immediate_tokens
86 && self.full_input_tokens == other.full_input_tokens
87 && self.fit_input_tokens == other.fit_input_tokens
88 && self.immediate_start_token == other.immediate_start_token
89 && self.immediate_end_token == other.immediate_end_token
90 && self.fingerprint == other.fingerprint
91 }
92}
93
94impl Eq for TokenSpanWork {}
95
96impl TokenSpanWork {
97 pub fn from_token_ids(
98 full_input: &[u32],
99 immediate_range: Range<usize>,
100 ) -> Result<Self, VNextError> {
101 Self::from_token_ids_with_fit(full_input, immediate_range, full_input.len())
102 }
103
104 pub fn from_token_ids_with_fit(
108 full_input: &[u32],
109 immediate_range: Range<usize>,
110 fit_input_tokens: usize,
111 ) -> Result<Self, VNextError> {
112 if full_input.is_empty()
113 || immediate_range.start >= immediate_range.end
114 || immediate_range.end > full_input.len()
115 || fit_input_tokens < full_input.len()
116 {
117 return Err(invalid_plan(
118 "token work requires a non-empty in-bounds immediate span and a non-regressing fit ceiling",
119 ));
120 }
121 let immediate_tokens = u64::try_from(immediate_range.len())
122 .map_err(|_| invalid_plan("immediate token span exceeds u64"))?;
123 let full_input_tokens = u64::try_from(full_input.len())
124 .map_err(|_| invalid_plan("full token input exceeds u64"))?;
125 let fit_input_tokens = u64::try_from(fit_input_tokens)
126 .map_err(|_| invalid_plan("fit token input exceeds u64"))?;
127 let immediate_start_token = u64::try_from(immediate_range.start)
128 .map_err(|_| invalid_plan("token span start exceeds u64"))?;
129 let immediate_end_token = u64::try_from(immediate_range.end)
130 .map_err(|_| invalid_plan("token span end exceeds u64"))?;
131 let mut digest = Sha256::new();
132 digest.update(b"ferrum.runtime-vnext.token-span-work.v3\0");
133 digest.update(full_input_tokens.to_le_bytes());
134 digest.update(fit_input_tokens.to_le_bytes());
135 digest.update(immediate_start_token.to_le_bytes());
136 digest.update(immediate_end_token.to_le_bytes());
137 for token in full_input {
138 digest.update(token.to_le_bytes());
139 }
140 Ok(Self {
141 immediate_tokens,
142 full_input_tokens,
143 fit_input_tokens,
144 immediate_start_token,
145 immediate_end_token,
146 fingerprint: format!("{:x}", digest.finalize()),
147 checkpoint_tokens: None,
148 })
149 }
150
151 pub fn with_checkpoint_tokens(mut self, tokens: Arc<[u32]>) -> Result<Self, VNextError> {
155 let start = usize::try_from(self.immediate_start_token)
156 .map_err(|_| invalid_plan("checkpoint span start exceeds usize"))?;
157 let end = usize::try_from(self.immediate_end_token)
158 .map_err(|_| invalid_plan("checkpoint span end exceeds usize"))?;
159 let fit = usize::try_from(self.fit_input_tokens)
160 .map_err(|_| invalid_plan("checkpoint fit ceiling exceeds usize"))?;
161 if Self::from_token_ids_with_fit(&tokens, start..end, fit)? != self {
162 return Err(invalid_plan(
163 "checkpoint tokens differ from admitted token work",
164 ));
165 }
166 self.checkpoint_tokens = Some(tokens);
167 Ok(self)
168 }
169
170 pub(crate) fn checkpoint_tokens(&self) -> Option<&Arc<[u32]>> {
171 self.checkpoint_tokens.as_ref()
172 }
173
174 pub const fn immediate_tokens(&self) -> u64 {
175 self.immediate_tokens
176 }
177
178 pub const fn full_input_tokens(&self) -> u64 {
179 self.full_input_tokens
180 }
181
182 pub const fn fit_input_tokens(&self) -> u64 {
183 self.fit_input_tokens
184 }
185
186 pub fn immediate_token_range(&self) -> Range<u64> {
187 self.immediate_start_token..self.immediate_end_token
188 }
189
190 pub fn fingerprint(&self) -> &str {
191 &self.fingerprint
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
196pub(crate) struct CommittedPageWork {
197 pub(super) pages: u64,
198 pub(super) fingerprint: String,
199}
200
201impl CommittedPageWork {
202 pub(crate) fn new(pages: u64, fingerprint: String) -> Result<Self, VNextError> {
203 if pages == 0
204 || fingerprint.len() != 64
205 || !fingerprint
206 .bytes()
207 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
208 {
209 return Err(invalid_plan("committed page work evidence is invalid"));
210 }
211 Ok(Self { pages, fingerprint })
212 }
213
214 pub(crate) const fn pages(&self) -> u64 {
215 self.pages
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
222pub struct ResourceWorkShape {
223 pub(super) token_spans: Vec<TokenSpanWork>,
224 pub(super) committed_pages: Vec<CommittedPageWork>,
225 pub(super) immediate_sequences: u32,
226 pub(super) immediate_tokens: u64,
227 pub(super) immediate_pages: u64,
228 pub(super) fit_sequences: u32,
229 pub(super) fit_tokens: u64,
230 pub(super) fit_pages: u64,
231 pub(super) fingerprint: String,
232}
233
234impl ResourceWorkShape {
235 pub fn from_token_spans(token_spans: Vec<TokenSpanWork>) -> Result<Self, VNextError> {
236 Self::from_sources(token_spans, Vec::new())
237 }
238
239 pub fn single(token_span: TokenSpanWork) -> Result<Self, VNextError> {
240 Self::from_token_spans(vec![token_span])
241 }
242
243 pub(crate) fn validate_single_checkpoint_input(
246 &self,
247 tokens: Arc<[u32]>,
248 ) -> Result<(), VNextError> {
249 let [span] = self.token_spans.as_slice() else {
250 return Err(invalid_plan(
251 "checkpoint input requires one admitted sequence token span",
252 ));
253 };
254 span.clone().with_checkpoint_tokens(tokens)?;
255 Ok(())
256 }
257
258 pub(crate) fn from_sources(
259 token_spans: Vec<TokenSpanWork>,
260 committed_pages: Vec<CommittedPageWork>,
261 ) -> Result<Self, VNextError> {
262 if token_spans.is_empty() {
263 return Err(invalid_plan("resource work requires token evidence"));
264 }
265 let immediate_sequences = u32::try_from(token_spans.len())
266 .map_err(|_| invalid_plan("resource work sequence count exceeds u32"))?;
267 let immediate_tokens = token_spans.iter().try_fold(0_u64, |total, span| {
268 total
269 .checked_add(span.immediate_tokens())
270 .ok_or_else(|| invalid_plan("resource work immediate tokens overflow u64"))
271 })?;
272 let fit_tokens = token_spans.iter().try_fold(0_u64, |total, span| {
273 total
274 .checked_add(span.fit_input_tokens())
275 .ok_or_else(|| invalid_plan("resource work fit-input tokens overflow u64"))
276 })?;
277 let pages = committed_pages.iter().try_fold(0_u64, |total, page_work| {
278 total
279 .checked_add(page_work.pages())
280 .ok_or_else(|| invalid_plan("resource work pages overflow u64"))
281 })?;
282 #[derive(Serialize)]
283 struct FingerprintInput<'a> {
284 domain: &'static str,
285 token_spans: &'a [TokenSpanWork],
286 committed_pages: &'a [CommittedPageWork],
287 }
288 let bytes = serde_json::to_vec(&FingerprintInput {
289 domain: "ferrum.runtime-vnext.resource-work-shape.v1",
290 token_spans: &token_spans,
291 committed_pages: &committed_pages,
292 })
293 .map_err(|error| invalid_plan(format!("resource work encode failed: {error}")))?;
294 Ok(Self {
295 token_spans,
296 committed_pages,
297 immediate_sequences,
298 immediate_tokens,
299 immediate_pages: pages,
300 fit_sequences: immediate_sequences,
301 fit_tokens,
302 fit_pages: pages,
303 fingerprint: format!("{:x}", Sha256::digest(bytes)),
304 })
305 }
306
307 pub const fn immediate_sequences(&self) -> u32 {
308 self.immediate_sequences
309 }
310
311 pub const fn immediate_tokens(&self) -> u64 {
312 self.immediate_tokens
313 }
314
315 pub const fn immediate_pages(&self) -> u64 {
316 self.immediate_pages
317 }
318
319 pub const fn fit_sequences(&self) -> u32 {
320 self.fit_sequences
321 }
322
323 pub const fn fit_tokens(&self) -> u64 {
324 self.fit_tokens
325 }
326
327 pub const fn fit_pages(&self) -> u64 {
328 self.fit_pages
329 }
330
331 pub fn fingerprint(&self) -> &str {
332 &self.fingerprint
333 }
334
335 pub(crate) const fn immediate_shape(&self) -> DynamicResourceShape {
336 DynamicResourceShape::from_validated(
337 self.immediate_sequences,
338 self.immediate_tokens,
339 self.immediate_pages,
340 )
341 }
342
343 pub(crate) const fn fit_shape(&self) -> DynamicResourceShape {
344 DynamicResourceShape::from_validated(self.fit_sequences, self.fit_tokens, self.fit_pages)
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
349#[serde(deny_unknown_fields)]
350pub struct DynamicResourceShapeBucket {
351 pub(super) maximum_sequences: u32,
352 pub(super) maximum_tokens: u64,
353 pub(super) maximum_pages: u64,
354 pub(super) bytes: u64,
355}
356
357#[derive(Deserialize)]
358#[serde(deny_unknown_fields)]
359pub(super) struct DynamicResourceShapeBucketWire {
360 pub(super) maximum_sequences: u32,
361 pub(super) maximum_tokens: u64,
362 pub(super) maximum_pages: u64,
363 pub(super) bytes: u64,
364}
365
366impl DynamicResourceShapeBucket {
367 pub fn new(
368 maximum_sequences: u32,
369 maximum_tokens: u64,
370 maximum_pages: u64,
371 bytes: u64,
372 ) -> Result<Self, VNextError> {
373 if maximum_sequences == 0 || maximum_tokens == 0 || maximum_pages == 0 || bytes == 0 {
374 return Err(invalid_plan(
375 "workspace shape bucket bounds and bytes must be non-zero",
376 ));
377 }
378 Ok(Self {
379 maximum_sequences,
380 maximum_tokens,
381 maximum_pages,
382 bytes,
383 })
384 }
385
386 pub(super) fn covers(&self, shape: DynamicResourceShape) -> bool {
387 shape.sequences <= self.maximum_sequences
388 && shape.tokens <= self.maximum_tokens
389 && shape.pages <= self.maximum_pages
390 }
391
392 pub const fn maximum_sequences(&self) -> u32 {
393 self.maximum_sequences
394 }
395
396 pub const fn maximum_tokens(&self) -> u64 {
397 self.maximum_tokens
398 }
399
400 pub const fn maximum_pages(&self) -> u64 {
401 self.maximum_pages
402 }
403
404 pub const fn bytes(&self) -> u64 {
405 self.bytes
406 }
407}
408
409impl<'de> Deserialize<'de> for DynamicResourceShapeBucket {
410 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
411 where
412 D: Deserializer<'de>,
413 {
414 let wire = DynamicResourceShapeBucketWire::deserialize(deserializer)?;
415 Self::new(
416 wire.maximum_sequences,
417 wire.maximum_tokens,
418 wire.maximum_pages,
419 wire.bytes,
420 )
421 .map_err(serde::de::Error::custom)
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
429#[serde(rename_all = "snake_case")]
430pub enum DynamicResourceDemand {
431 Fixed {
432 bytes: u64,
433 },
434 ActualSequences {
435 bytes_per_sequence: u64,
436 maximum_sequences: u32,
437 },
438 Tokens {
439 bytes_per_token: u64,
440 maximum_tokens: u64,
441 },
442 Affine {
443 fixed_bytes: u64,
444 bytes_per_sequence: u64,
445 maximum_sequences: u32,
446 bytes_per_token: u64,
447 maximum_tokens: u64,
448 },
449 Pages {
450 bytes_per_page: u64,
451 maximum_pages: u64,
452 },
453 BoundedShapeBuckets {
454 buckets: Vec<DynamicResourceShapeBucket>,
455 },
456}
457
458#[derive(Deserialize)]
459#[serde(rename_all = "snake_case", deny_unknown_fields)]
460pub(super) enum DynamicResourceDemandWire {
461 Fixed {
462 bytes: u64,
463 },
464 ActualSequences {
465 bytes_per_sequence: u64,
466 maximum_sequences: u32,
467 },
468 Tokens {
469 bytes_per_token: u64,
470 maximum_tokens: u64,
471 },
472 Affine {
473 fixed_bytes: u64,
474 bytes_per_sequence: u64,
475 maximum_sequences: u32,
476 bytes_per_token: u64,
477 maximum_tokens: u64,
478 },
479 Pages {
480 bytes_per_page: u64,
481 maximum_pages: u64,
482 },
483 BoundedShapeBuckets {
484 buckets: Vec<DynamicResourceShapeBucket>,
485 },
486}
487
488impl DynamicResourceDemand {
489 pub fn fixed(bytes: u64) -> Result<Self, VNextError> {
490 Self::validated(Self::Fixed { bytes })
491 }
492
493 pub fn actual_sequences(
494 bytes_per_sequence: u64,
495 maximum_sequences: u32,
496 ) -> Result<Self, VNextError> {
497 Self::validated(Self::ActualSequences {
498 bytes_per_sequence,
499 maximum_sequences,
500 })
501 }
502
503 pub fn tokens(bytes_per_token: u64, maximum_tokens: u64) -> Result<Self, VNextError> {
504 Self::validated(Self::Tokens {
505 bytes_per_token,
506 maximum_tokens,
507 })
508 }
509
510 pub fn affine(
511 fixed_bytes: u64,
512 bytes_per_sequence: u64,
513 maximum_sequences: u32,
514 bytes_per_token: u64,
515 maximum_tokens: u64,
516 ) -> Result<Self, VNextError> {
517 Self::validated(Self::Affine {
518 fixed_bytes,
519 bytes_per_sequence,
520 maximum_sequences,
521 bytes_per_token,
522 maximum_tokens,
523 })
524 }
525
526 pub fn pages(bytes_per_page: u64, maximum_pages: u64) -> Result<Self, VNextError> {
527 Self::validated(Self::Pages {
528 bytes_per_page,
529 maximum_pages,
530 })
531 }
532
533 pub fn bounded_shape_buckets(
534 buckets: Vec<DynamicResourceShapeBucket>,
535 ) -> Result<Self, VNextError> {
536 Self::validated(Self::BoundedShapeBuckets { buckets })
537 }
538
539 pub(super) fn validated(demand: Self) -> Result<Self, VNextError> {
540 demand.validate()?;
541 Ok(demand)
542 }
543
544 pub(super) fn validate(&self) -> Result<(), VNextError> {
545 let valid = match self {
546 Self::Fixed { bytes } => *bytes > 0,
547 Self::ActualSequences {
548 bytes_per_sequence,
549 maximum_sequences,
550 } => {
551 *bytes_per_sequence > 0
552 && *maximum_sequences > 0
553 && bytes_per_sequence
554 .checked_mul(u64::from(*maximum_sequences))
555 .is_some()
556 }
557 Self::Tokens {
558 bytes_per_token,
559 maximum_tokens,
560 } => {
561 *bytes_per_token > 0
562 && *maximum_tokens > 0
563 && bytes_per_token.checked_mul(*maximum_tokens).is_some()
564 }
565 Self::Affine {
566 fixed_bytes,
567 bytes_per_sequence,
568 maximum_sequences,
569 bytes_per_token,
570 maximum_tokens,
571 } => {
572 (*bytes_per_sequence > 0 || *bytes_per_token > 0)
573 && *maximum_sequences > 0
574 && *maximum_tokens > 0
575 && bytes_per_sequence
576 .checked_mul(u64::from(*maximum_sequences))
577 .and_then(|sequence_bytes| fixed_bytes.checked_add(sequence_bytes))
578 .and_then(|bytes| {
579 bytes_per_token
580 .checked_mul(*maximum_tokens)
581 .and_then(|token_bytes| bytes.checked_add(token_bytes))
582 })
583 .is_some()
584 }
585 Self::Pages {
586 bytes_per_page,
587 maximum_pages,
588 } => {
589 *bytes_per_page > 0
590 && *maximum_pages > 0
591 && bytes_per_page.checked_mul(*maximum_pages).is_some()
592 }
593 Self::BoundedShapeBuckets { buckets } => {
594 !buckets.is_empty()
595 && buckets.len() <= MAX_PROVIDER_WORKSPACE_SHAPE_BUCKETS
596 && buckets.windows(2).all(|pair| {
597 let previous = &pair[0];
598 let next = &pair[1];
599 next.maximum_sequences >= previous.maximum_sequences
600 && next.maximum_tokens >= previous.maximum_tokens
601 && next.maximum_pages >= previous.maximum_pages
602 && (next.maximum_sequences > previous.maximum_sequences
603 || next.maximum_tokens > previous.maximum_tokens
604 || next.maximum_pages > previous.maximum_pages)
605 && next.bytes >= previous.bytes
606 })
607 }
608 };
609 if !valid {
610 return Err(invalid_plan(
611 "dynamic resource formula is zero, overflowing, or non-canonical",
612 ));
613 }
614 Ok(())
615 }
616
617 pub fn evaluate_bytes(&self, work: &ResourceWorkShape) -> Result<u64, VNextError> {
618 self.evaluate_shape_bytes(work.immediate_shape())
619 }
620
621 pub fn evaluate_fit_bytes(&self, work: &ResourceWorkShape) -> Result<u64, VNextError> {
622 self.evaluate_shape_bytes(work.fit_shape())
623 }
624
625 pub(crate) fn evaluate_shape_bytes(
626 &self,
627 shape: DynamicResourceShape,
628 ) -> Result<u64, VNextError> {
629 self.validate()?;
630 let bytes = match self {
631 Self::Fixed { bytes } => *bytes,
632 Self::ActualSequences {
633 bytes_per_sequence,
634 maximum_sequences,
635 } if shape.sequences <= *maximum_sequences => bytes_per_sequence
636 .checked_mul(u64::from(shape.sequences))
637 .ok_or_else(|| invalid_plan("sequence-scaled resource request overflows u64"))?,
638 Self::Tokens {
639 bytes_per_token,
640 maximum_tokens,
641 } if shape.tokens <= *maximum_tokens => bytes_per_token
642 .checked_mul(shape.tokens)
643 .ok_or_else(|| invalid_plan("token-scaled resource request overflows u64"))?,
644 Self::Affine {
645 fixed_bytes,
646 bytes_per_sequence,
647 maximum_sequences,
648 bytes_per_token,
649 maximum_tokens,
650 } if shape.sequences <= *maximum_sequences && shape.tokens <= *maximum_tokens => {
651 fixed_bytes
652 .checked_add(
653 bytes_per_sequence
654 .checked_mul(u64::from(shape.sequences))
655 .ok_or_else(|| {
656 invalid_plan("affine sequence resource request overflows u64")
657 })?,
658 )
659 .and_then(|bytes| {
660 bytes_per_token
661 .checked_mul(shape.tokens)
662 .and_then(|token_bytes| bytes.checked_add(token_bytes))
663 })
664 .ok_or_else(|| invalid_plan("affine token resource request overflows u64"))?
665 }
666 Self::Pages {
667 bytes_per_page,
668 maximum_pages,
669 } if shape.pages <= *maximum_pages => bytes_per_page
670 .checked_mul(shape.pages)
671 .ok_or_else(|| invalid_plan("page-scaled resource request overflows u64"))?,
672 Self::BoundedShapeBuckets { buckets } => buckets
673 .iter()
674 .find(|bucket| bucket.covers(shape))
675 .map(DynamicResourceShapeBucket::bytes)
676 .ok_or_else(|| invalid_plan("actual invocation shape exceeds workspace buckets"))?,
677 _ => {
678 return Err(invalid_plan(
679 "actual invocation shape exceeds its bounded resource formula",
680 ))
681 }
682 };
683 if bytes == 0 {
684 return Err(invalid_plan(
685 "dynamic resource request evaluates to zero bytes",
686 ));
687 }
688 Ok(bytes)
689 }
690
691 pub(crate) fn minimum_shape(&self) -> DynamicResourceShape {
692 DynamicResourceShape {
693 sequences: 1,
694 tokens: 1,
695 pages: 1,
696 }
697 }
698
699 pub(crate) fn theoretical_maximum_shape(&self) -> DynamicResourceShape {
700 match self {
701 Self::Fixed { .. } => self.minimum_shape(),
702 Self::ActualSequences {
703 maximum_sequences, ..
704 } => DynamicResourceShape {
705 sequences: *maximum_sequences,
706 tokens: 1,
707 pages: 1,
708 },
709 Self::Tokens { maximum_tokens, .. } => DynamicResourceShape {
710 sequences: 1,
711 tokens: *maximum_tokens,
712 pages: 1,
713 },
714 Self::Affine {
715 maximum_sequences,
716 maximum_tokens,
717 ..
718 } => DynamicResourceShape {
719 sequences: *maximum_sequences,
720 tokens: *maximum_tokens,
721 pages: 1,
722 },
723 Self::Pages { maximum_pages, .. } => DynamicResourceShape {
724 sequences: 1,
725 tokens: 1,
726 pages: *maximum_pages,
727 },
728 Self::BoundedShapeBuckets { buckets } => {
729 let bucket = buckets
730 .last()
731 .expect("validated bounded formula has at least one bucket");
732 DynamicResourceShape {
733 sequences: bucket.maximum_sequences,
734 tokens: bucket.maximum_tokens,
735 pages: bucket.maximum_pages,
736 }
737 }
738 }
739 }
740
741 pub(super) fn is_fixed(&self) -> bool {
742 matches!(self, Self::Fixed { .. })
743 }
744
745 pub(super) fn is_valid_for_sequence_scope(&self) -> bool {
746 match self {
747 Self::Fixed { .. } | Self::Tokens { .. } | Self::Pages { .. } => true,
748 Self::Affine {
749 bytes_per_sequence, ..
750 } => *bytes_per_sequence == 0,
751 Self::BoundedShapeBuckets { buckets } => {
752 buckets.iter().all(|bucket| bucket.maximum_sequences == 1)
753 }
754 Self::ActualSequences { .. } => false,
755 }
756 }
757}
758
759impl<'de> Deserialize<'de> for DynamicResourceDemand {
760 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
761 where
762 D: Deserializer<'de>,
763 {
764 let demand = match DynamicResourceDemandWire::deserialize(deserializer)? {
765 DynamicResourceDemandWire::Fixed { bytes } => Self::Fixed { bytes },
766 DynamicResourceDemandWire::ActualSequences {
767 bytes_per_sequence,
768 maximum_sequences,
769 } => Self::ActualSequences {
770 bytes_per_sequence,
771 maximum_sequences,
772 },
773 DynamicResourceDemandWire::Tokens {
774 bytes_per_token,
775 maximum_tokens,
776 } => Self::Tokens {
777 bytes_per_token,
778 maximum_tokens,
779 },
780 DynamicResourceDemandWire::Affine {
781 fixed_bytes,
782 bytes_per_sequence,
783 maximum_sequences,
784 bytes_per_token,
785 maximum_tokens,
786 } => Self::Affine {
787 fixed_bytes,
788 bytes_per_sequence,
789 maximum_sequences,
790 bytes_per_token,
791 maximum_tokens,
792 },
793 DynamicResourceDemandWire::Pages {
794 bytes_per_page,
795 maximum_pages,
796 } => Self::Pages {
797 bytes_per_page,
798 maximum_pages,
799 },
800 DynamicResourceDemandWire::BoundedShapeBuckets { buckets } => {
801 Self::BoundedShapeBuckets { buckets }
802 }
803 };
804 Self::validated(demand).map_err(serde::de::Error::custom)
805 }
806}