ferrum-interfaces 0.8.4

Core trait contracts for the Ferrum LLM inference engine
Documentation
use super::{
    canonical_fingerprint, invalid_plan, CapabilityCatalog, CompletionRetentionSpec,
    ContractVersion, DynamicStorageProfile, ExecutionWeightPlan, PlanNodeResolution,
    PreparedModelFamily, ReusableExecutionPolicy, Serialize, TrustedExecutionWeightPlan,
    VNextError,
};
use crate::vnext::ExecutionDeterminismRequirement;
use ferrum_types::AttentionExecutionPolicy;

/// Typed policy selected before planning. Memory capacity is part of the
/// public policy contract so a plan cannot depend on an undocumented env var.
pub trait RuntimePolicy:
    Clone + Send + Sync + Serialize + serde::de::DeserializeOwned + std::fmt::Debug + 'static
{
    fn version(&self) -> ContractVersion;

    /// Policy memory ceiling before the explicit reserve is subtracted. It may
    /// be lower than, but never exceed, the raw device capacity.
    fn memory_capacity_bytes(&self) -> u64;

    fn memory_reserve_bytes(&self) -> u64;

    /// A non-zero protocol ceiling only. Planning must not reserve, claim,
    /// iterate, or materialize resources up to this value.
    fn maximum_active_sequences(&self) -> u32;

    /// A non-zero ceiling for tokens selected into one scheduler step. This is
    /// not installed capacity: admission still evaluates the exact step shape
    /// against then-live backing before any provider work is encoded.
    fn maximum_scheduled_tokens(&self) -> u64;

    /// Provider-family policy sealed into the plan fingerprint. Physical
    /// kernel variants remain an invocation-time provider decision.
    fn attention_execution_policy(&self) -> AttentionExecutionPolicy;

    /// Minimum repeatability and reusable-execution equivalence that every
    /// selected operation provider must prove.
    fn execution_determinism_requirement(&self) -> ExecutionDeterminismRequirement;

    /// Ordered, non-empty allowlist used by planning after intersecting every
    /// selected provider requirement with the concrete runtime offers.
    fn dynamic_storage_profile_order(&self) -> &[DynamicStorageProfile];

    /// Optional reusable-execution capacity policy selected before planning.
    /// Core treats class identities as opaque and owns their memory derivation.
    fn reusable_execution_policy(&self) -> Option<&ReusableExecutionPolicy>;

    fn validate(&self) -> Result<(), VNextError>;
}

pub fn canonical_runtime_policy_fingerprint<P: RuntimePolicy>(
    policy: &P,
) -> Result<String, VNextError> {
    policy.validate()?;
    canonical_fingerprint(policy, "fingerprint validated runtime policy")
}

pub struct PlanBuildRequest<'a, P: RuntimePolicy> {
    pub(super) family: &'a PreparedModelFamily,
    pub(super) capabilities: &'a CapabilityCatalog,
    pub(super) policy: &'a P,
    pub(super) node_resolutions: Vec<PlanNodeResolution>,
    pub(super) completion_retention: CompletionRetentionSpec,
    pub(super) execution_weights: TrustedExecutionWeightPlan,
}

impl<'a, P: RuntimePolicy> PlanBuildRequest<'a, P> {
    pub fn new(
        family: &'a PreparedModelFamily,
        capabilities: &'a CapabilityCatalog,
        policy: &'a P,
        node_resolutions: Vec<PlanNodeResolution>,
    ) -> Result<Self, VNextError> {
        if node_resolutions.is_empty() {
            return Err(invalid_plan("plan build request has no node resolutions"));
        }
        policy.validate()?;
        Ok(Self {
            family,
            capabilities,
            policy,
            node_resolutions,
            completion_retention: CompletionRetentionSpec::default(),
            execution_weights: TrustedExecutionWeightPlan::identity(family)?,
        })
    }

    pub fn with_completion_retention(
        mut self,
        completion_retention: CompletionRetentionSpec,
    ) -> Result<Self, VNextError> {
        let produced_values = self
            .family
            .program()
            .blocks()
            .iter()
            .flat_map(|block| block.nodes.iter())
            .flat_map(|node| node.outputs.iter())
            .collect::<std::collections::BTreeSet<_>>();
        if completion_retention
            .values()
            .iter()
            .any(|value_id| !produced_values.contains(value_id))
        {
            return Err(invalid_plan(
                "completion retention must reference a semantic node output",
            ));
        }
        self.completion_retention = completion_retention;
        Ok(self)
    }

    pub fn with_execution_weights(
        mut self,
        execution_weights: TrustedExecutionWeightPlan,
    ) -> Result<Self, VNextError> {
        execution_weights.validate_against_catalog(self.family, self.capabilities)?;
        self.execution_weights = execution_weights;
        Ok(self)
    }

    pub fn family(&self) -> &PreparedModelFamily {
        self.family
    }

    pub fn capabilities(&self) -> &CapabilityCatalog {
        self.capabilities
    }

    pub fn policy(&self) -> &P {
        self.policy
    }

    pub fn execution_weights(&self) -> &ExecutionWeightPlan {
        self.execution_weights.plan()
    }
}