Skip to main content

ferrum_interfaces/vnext/operation/
resolved_value.rs

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/// Provider-accepted physical profiles for one exact operation binding slot.
23/// Role and ordinal are contract identities, not model-specific names.
24#[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/// Physical resources backing one semantic value. A logical quantized weight
155/// can bind packed values, scales, zero-points, and indices without pretending
156/// they are one dense allocation.
157#[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/// Value/resource binding shared by the execution plan and provider
279/// invocation. Keeping one representation prevents a lossy translation at the
280/// runtime boundary.
281#[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}