1use std::collections::{BTreeMap, BTreeSet};
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use super::super::{
6 AllocationLifetime, BufferUsage, DynamicResourceDemand, MemoryPlan, ProgramValueId, ResourceId,
7 VNextError, WeightId,
8};
9use super::foundation::invalid_operation;
10use super::{
11 AliasPolicy, DynamicStorageRequirement, ElementType, ResolvedTensorSpec, ResolvedWeightBinding,
12 ResolvedWeightLogicalValidation, TensorAccess,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum ResolvedValueRole {
18 Input,
19 Output,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
25pub struct ProviderStorageBindingRequirement {
26 role: ResolvedValueRole,
27 ordinal: u32,
28 storage: DynamicStorageRequirement,
29}
30
31impl ProviderStorageBindingRequirement {
32 pub fn new(role: ResolvedValueRole, ordinal: u32, storage: DynamicStorageRequirement) -> Self {
33 Self {
34 role,
35 ordinal,
36 storage,
37 }
38 }
39
40 pub const fn role(&self) -> ResolvedValueRole {
41 self.role
42 }
43
44 pub const fn ordinal(&self) -> u32 {
45 self.ordinal
46 }
47
48 pub fn storage(&self) -> &DynamicStorageRequirement {
49 &self.storage
50 }
51}
52
53#[derive(Deserialize)]
54#[serde(deny_unknown_fields)]
55struct ProviderStorageBindingRequirementWire {
56 role: ResolvedValueRole,
57 ordinal: u32,
58 storage: DynamicStorageRequirement,
59}
60
61impl<'de> Deserialize<'de> for ProviderStorageBindingRequirement {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: Deserializer<'de>,
65 {
66 let wire = ProviderStorageBindingRequirementWire::deserialize(deserializer)?;
67 Ok(Self::new(wire.role, wire.ordinal, wire.storage))
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
72pub struct ResolvedStorageComponent {
73 component_id: Option<WeightId>,
74 resource_id: ResourceId,
75 offset_bytes: u64,
76 length_bytes: u64,
77 element_type: ElementType,
78}
79
80#[derive(Deserialize)]
81#[serde(deny_unknown_fields)]
82struct ResolvedStorageComponentWire {
83 component_id: Option<WeightId>,
84 resource_id: ResourceId,
85 offset_bytes: u64,
86 length_bytes: u64,
87 element_type: ElementType,
88}
89
90impl<'de> Deserialize<'de> for ResolvedStorageComponent {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92 where
93 D: Deserializer<'de>,
94 {
95 let wire = ResolvedStorageComponentWire::deserialize(deserializer)?;
96 Self::new(
97 wire.component_id,
98 wire.resource_id,
99 wire.offset_bytes,
100 wire.length_bytes,
101 wire.element_type,
102 )
103 .map_err(serde::de::Error::custom)
104 }
105}
106
107impl ResolvedStorageComponent {
108 pub fn new(
109 component_id: Option<WeightId>,
110 resource_id: ResourceId,
111 offset_bytes: u64,
112 length_bytes: u64,
113 element_type: ElementType,
114 ) -> Result<Self, VNextError> {
115 if length_bytes == 0
116 || offset_bytes.checked_add(length_bytes).is_none()
117 || offset_bytes % element_type.size_bytes() != 0
118 || length_bytes % element_type.size_bytes() != 0
119 {
120 return Err(invalid_operation(
121 "resolved storage component is empty or overflows u64",
122 ));
123 }
124 Ok(Self {
125 component_id,
126 resource_id,
127 offset_bytes,
128 length_bytes,
129 element_type,
130 })
131 }
132
133 pub fn component_id(&self) -> Option<&WeightId> {
134 self.component_id.as_ref()
135 }
136
137 pub fn resource_id(&self) -> &ResourceId {
138 &self.resource_id
139 }
140
141 pub const fn offset_bytes(&self) -> u64 {
142 self.offset_bytes
143 }
144
145 pub const fn length_bytes(&self) -> u64 {
146 self.length_bytes
147 }
148
149 pub const fn element_type(&self) -> ElementType {
150 self.element_type
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
158pub struct ResolvedValueStorage {
159 components: Vec<ResolvedStorageComponent>,
160}
161
162#[derive(Deserialize)]
163#[serde(deny_unknown_fields)]
164struct ResolvedValueStorageWire {
165 components: Vec<ResolvedStorageComponent>,
166}
167
168impl<'de> Deserialize<'de> for ResolvedValueStorage {
169 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170 where
171 D: Deserializer<'de>,
172 {
173 let wire = ResolvedValueStorageWire::deserialize(deserializer)?;
174 Self::new(wire.components).map_err(serde::de::Error::custom)
175 }
176}
177
178impl ResolvedValueStorage {
179 pub fn single(
180 resource_id: ResourceId,
181 offset_bytes: u64,
182 length_bytes: u64,
183 element_type: ElementType,
184 ) -> Result<Self, VNextError> {
185 Self::new(vec![ResolvedStorageComponent::new(
186 None,
187 resource_id,
188 offset_bytes,
189 length_bytes,
190 element_type,
191 )?])
192 }
193
194 pub fn composite(components: Vec<ResolvedStorageComponent>) -> Result<Self, VNextError> {
195 if components
196 .iter()
197 .any(|component| component.component_id.is_none())
198 {
199 return Err(invalid_operation(
200 "composite value storage requires a physical component identity",
201 ));
202 }
203 Self::new(components)
204 }
205
206 fn new(mut components: Vec<ResolvedStorageComponent>) -> Result<Self, VNextError> {
207 if components.is_empty() {
208 return Err(invalid_operation("resolved value storage is empty"));
209 }
210 if components.len() > 1
211 && components
212 .iter()
213 .any(|component| component.component_id.is_none())
214 {
215 return Err(invalid_operation(
216 "multi-component value storage requires physical component identities",
217 ));
218 }
219 components.sort_by(|left, right| {
220 left.component_id
221 .cmp(&right.component_id)
222 .then(left.resource_id.cmp(&right.resource_id))
223 .then(left.offset_bytes.cmp(&right.offset_bytes))
224 });
225 let mut component_ids = BTreeSet::new();
226 for (index, component) in components.iter().enumerate() {
227 if component.length_bytes == 0
228 || component
229 .offset_bytes
230 .checked_add(component.length_bytes)
231 .is_none()
232 || component
233 .component_id
234 .as_ref()
235 .is_some_and(|component_id| !component_ids.insert(component_id.clone()))
236 {
237 return Err(invalid_operation(
238 "resolved value storage has invalid or duplicate components",
239 ));
240 }
241 if components[..index].iter().any(|previous| {
242 previous.resource_id == component.resource_id
243 && previous.offset_bytes
244 < component
245 .offset_bytes
246 .saturating_add(component.length_bytes)
247 && component.offset_bytes
248 < previous.offset_bytes.saturating_add(previous.length_bytes)
249 }) {
250 return Err(invalid_operation(
251 "resolved value storage components overlap in one resource",
252 ));
253 }
254 }
255 Ok(Self { components })
256 }
257
258 pub fn components(&self) -> &[ResolvedStorageComponent] {
259 &self.components
260 }
261
262 pub fn resource_ids(&self) -> BTreeSet<&ResourceId> {
263 self.components
264 .iter()
265 .map(|component| &component.resource_id)
266 .collect()
267 }
268
269 pub fn total_physical_bytes(&self) -> Result<u64, VNextError> {
270 self.components.iter().try_fold(0_u64, |total, component| {
271 total
272 .checked_add(component.length_bytes)
273 .ok_or_else(|| invalid_operation("resolved storage byte count overflows u64"))
274 })
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
282pub struct ResolvedValueBinding {
283 value_id: ProgramValueId,
284 role: ResolvedValueRole,
285 ordinal: u32,
286 tensor: ResolvedTensorSpec,
287 access: TensorAccess,
288 alias: AliasPolicy,
289 usage: BufferUsage,
290 weight: Option<ResolvedWeightBinding>,
291 storage: ResolvedValueStorage,
292}
293
294#[derive(Deserialize)]
295#[serde(deny_unknown_fields)]
296struct ResolvedValueBindingWire {
297 value_id: ProgramValueId,
298 role: ResolvedValueRole,
299 ordinal: u32,
300 tensor: ResolvedTensorSpec,
301 access: TensorAccess,
302 alias: AliasPolicy,
303 usage: BufferUsage,
304 weight: Option<ResolvedWeightBinding>,
305 storage: ResolvedValueStorage,
306}
307
308impl<'de> Deserialize<'de> for ResolvedValueBinding {
309 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
310 where
311 D: Deserializer<'de>,
312 {
313 let wire = ResolvedValueBindingWire::deserialize(deserializer)?;
314 Self::new(
315 wire.value_id,
316 wire.role,
317 wire.ordinal,
318 wire.tensor,
319 wire.access,
320 wire.alias,
321 wire.usage,
322 wire.weight,
323 wire.storage,
324 )
325 .map_err(serde::de::Error::custom)
326 }
327}
328
329impl ResolvedValueBinding {
330 pub fn new(
331 value_id: ProgramValueId,
332 role: ResolvedValueRole,
333 ordinal: u32,
334 tensor: ResolvedTensorSpec,
335 access: TensorAccess,
336 alias: AliasPolicy,
337 usage: BufferUsage,
338 weight: Option<ResolvedWeightBinding>,
339 storage: ResolvedValueStorage,
340 ) -> Result<Self, VNextError> {
341 if (role == ResolvedValueRole::Input
342 && !matches!(access, TensorAccess::Read | TensorAccess::ReadWrite))
343 || (role == ResolvedValueRole::Output
344 && !matches!(access, TensorAccess::Write | TensorAccess::ReadWrite))
345 || (role == ResolvedValueRole::Input && !matches!(alias, AliasPolicy::NoAlias))
346 {
347 return Err(invalid_operation(
348 "resolved value role, access, and alias policy are inconsistent",
349 ));
350 }
351 if usage != BufferUsage::Weights && storage.components.len() != 1 {
352 return Err(invalid_operation(
353 "only a weight value may use composite physical storage",
354 ));
355 }
356 if storage.components.len() == 1
357 && storage.components[0].component_id.is_none()
358 && storage.components[0].element_type != tensor.element_type()
359 {
360 return Err(invalid_operation(
361 "single-resource value dtype differs from its logical tensor dtype",
362 ));
363 }
364 if usage != BufferUsage::Weights
365 && storage.components[0].length_bytes < tensor.minimum_storage_bytes()?
366 {
367 return Err(invalid_operation(
368 "resolved value storage is smaller than its tensor span",
369 ));
370 }
371 match (usage, weight.as_ref()) {
372 (BufferUsage::Weights, Some(weight)) => {
373 ResolvedWeightLogicalValidation::validate_logical_contract(
374 weight,
375 tensor.dimensions(),
376 tensor.element_type(),
377 )?;
378 validate_resolved_weight_storage(weight, &storage)?;
379 }
380 (BufferUsage::Weights, None) => {
381 return Err(invalid_operation(
382 "weight value lacks its resolved physical layout contract",
383 ));
384 }
385 (_, Some(_)) => {
386 return Err(invalid_operation(
387 "non-weight value carries a resolved weight layout contract",
388 ));
389 }
390 (_, None) => {}
391 }
392 Ok(Self {
393 value_id,
394 role,
395 ordinal,
396 tensor,
397 access,
398 alias,
399 usage,
400 weight,
401 storage,
402 })
403 }
404
405 pub fn value_id(&self) -> &ProgramValueId {
406 &self.value_id
407 }
408
409 pub fn role(&self) -> ResolvedValueRole {
410 self.role
411 }
412
413 pub fn ordinal(&self) -> u32 {
414 self.ordinal
415 }
416
417 pub fn tensor(&self) -> &ResolvedTensorSpec {
418 &self.tensor
419 }
420
421 pub fn access(&self) -> TensorAccess {
422 self.access
423 }
424
425 pub fn alias(&self) -> &AliasPolicy {
426 &self.alias
427 }
428
429 pub const fn usage(&self) -> BufferUsage {
430 self.usage
431 }
432
433 pub fn weight(&self) -> Option<&ResolvedWeightBinding> {
434 self.weight.as_ref()
435 }
436
437 pub fn storage(&self) -> &ResolvedValueStorage {
438 &self.storage
439 }
440}
441
442pub(super) fn resource_uses_packed_batch_coordinates(
443 memory: &MemoryPlan,
444 resource_id: &ResourceId,
445) -> Result<bool, VNextError> {
446 if memory
447 .static_allocations()
448 .binary_search_by(|allocation| allocation.resource_id().cmp(resource_id))
449 .is_ok()
450 {
451 return Ok(false);
452 }
453 let descriptor = memory
454 .dynamic_descriptors()
455 .binary_search_by(|descriptor| descriptor.base_resource_id().cmp(resource_id))
456 .ok()
457 .and_then(|index| memory.dynamic_descriptors().get(index))
458 .ok_or_else(|| invalid_operation("value binding references an unknown memory resource"))?;
459 Ok(
460 matches!(descriptor.demand(), DynamicResourceDemand::Tokens { .. })
461 && matches!(
462 descriptor.lifetime(),
463 AllocationLifetime::Step | AllocationLifetime::Invocation
464 ),
465 )
466}
467
468fn validate_resolved_weight_storage(
469 weight: &ResolvedWeightBinding,
470 storage: &ResolvedValueStorage,
471) -> Result<(), VNextError> {
472 let expected = weight
473 .components()
474 .iter()
475 .map(|component| (component.component_id(), component))
476 .collect::<BTreeMap<_, _>>();
477 if storage.components().len() != expected.len() {
478 return Err(invalid_operation(
479 "resolved weight storage component count differs from its layout contract",
480 ));
481 }
482 let mut seen = BTreeSet::new();
483 for stored in storage.components() {
484 let component_id = stored.component_id().ok_or_else(|| {
485 invalid_operation("resolved weight storage component lacks its physical identity")
486 })?;
487 let component = expected.get(component_id).ok_or_else(|| {
488 invalid_operation(format!(
489 "resolved weight storage contains unknown component `{component_id}`"
490 ))
491 })?;
492 if !seen.insert(component_id)
493 || stored.length_bytes() != component.physical_bytes()?
494 || stored.element_type() != component.physical_element_type()
495 {
496 return Err(invalid_operation(format!(
497 "resolved weight storage component `{component_id}` differs from its layout contract"
498 )));
499 }
500 }
501 Ok(())
502}