Source code for embodichain.data_pipeline.configuration

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

"""Derive bounded ODS consumption and cache settings from measured capacities.

This module plans settings; the sampler and consumer must enforce the returned
quota and active-rate cap. Profile qualification is supplied by the caller and
is never inferred from throughput. All counts refer to chunk selections, not
whole-trajectory passes or supervised frame appearances.
"""

from __future__ import annotations

from dataclasses import MISSING, dataclass
from fractions import Fraction
import math
from typing import Literal

from embodichain.utils import configclass

__all__ = [
    "ODSReuseProfileCfg",
    "ODSRuntimeCfg",
    "ODSConfiguration",
    "configure_ods",
    "ODSRateMeasurementCfg",
    "ODSThroughputMeasurement",
    "measure_ods_throughput",
]

_MAX_COUNT = (1 << 63) - 1


[docs] @configclass class ODSReuseProfileCfg: """Explicit task/model/sampler qualification and selection-budget bounds. ``scope`` identifies task, model, history/horizon and trajectory-length assumptions. ``qualified=False`` is a supported provisional profile, not a claim that its recommended bounds are scientifically safe. """ profile_id: str = MISSING scope: str = MISSING qualified: bool = MISSING min_chunks_per_episode: int = MISSING max_chunks_per_episode: int = MISSING hard_max_chunks_per_episode: int = MISSING latency_safety_factor: float = 1.25
[docs] @configclass class ODSRuntimeCfg: """Measured runtime settings and optional total cache-storage allowance. ``cache_memory_bytes`` and ``bytes_per_episode`` must be supplied together. The allowance is for episode storage only; the caller must reserve worker, model and allocator memory separately. B and P are preserved unchanged. """ batch_size: int = MISSING producer_envs: int = MISSING first_publication_p95_seconds: float = MISSING cache_memory_bytes: int | None = None bytes_per_episode: int | None = None min_cache_episodes: int = 1 measurement_scope: str = "unspecified"
[docs] @dataclass(frozen=True) class ODSConfiguration: """Derived settings and their qualifications; no runtime state is changed.""" episode_sample_budget: int episode_sample_hard_limit: int balance_chunks_per_episode: float batch_size: int producer_envs: int buffer_size: int cache_bytes: int | None initial_chunk_budget: int initial_max_updates: int initial_remainder_chunks: int refill_remaining_chunks: int active_chunks_per_second_cap: float training_chunks_per_second_target: float training_updates_per_second_target: float publication_episodes_per_second_target: float training_duty_fraction: float producer_duty_fraction: float expected_training_throttle_fraction: float expected_producer_throttle_fraction: float profile_id: str profile_scope: str profile_qualified: bool measurement_scope: str reasons: tuple[str, ...] end_of_supply_rule: str = ( "Stop when remaining unlocked per-episode quota cannot fund one full batch." )
def _integer(name: str, value: object, *, minimum: int = 1) -> int: if type(value) is not int or not minimum <= value <= _MAX_COUNT: raise ValueError(f"{name} must be an integer in [{minimum}, {_MAX_COUNT}]") return value def _number(name: str, value: object, *, zero: bool = False) -> Fraction: if isinstance(value, bool) or not isinstance(value, (int, float)): raise ValueError(f"{name} must be a finite number") try: finite = math.isfinite(value) except OverflowError: finite = False if not finite or value < 0 or (value == 0 and not zero): raise ValueError( f"{name} must be finite and {'nonnegative' if zero else 'positive'}" ) return Fraction(value) def _text(name: str, value: object) -> str: if not isinstance(value, str) or not value.strip(): raise ValueError(f"{name} must be a nonempty string") return value def _float(value: Fraction, name: str, *, conservative: bool = False) -> float: try: result = float(value) except OverflowError: raise ValueError(f"{name} exceeds finite floating-point range") from None if not math.isfinite(result) or (value > 0 and result == 0): raise ValueError( f"{name} is outside representable positive floating-point range" ) if conservative and Fraction(result) > value: result = math.nextafter(result, 0.0) if value > 0 and result == 0: raise ValueError(f"{name} cannot be represented conservatively") return result def _round_up(value: int, multiple: int) -> int: return _integer( "derived cache episodes", ((value + multiple - 1) // multiple) * multiple )
[docs] def configure_ods( accepted_episodes_per_second: float, training_chunks_per_second: float, *, profile: ODSReuseProfileCfg, runtime: ODSRuntimeCfg, ) -> ODSConfiguration: """Plan quota-protected ODS settings from two concurrent capacity estimates. Args: accepted_episodes_per_second: Published successful trajectories per production second, including retry cost; not an idle whole-run rate. training_chunks_per_second: Completed chunk selections per active learner second, including sampling, transfers and optimization. profile: Explicit qualified or provisional selection-budget policy. runtime: Batch, writer parallelism, latency and storage metadata. Returns: Budget, active and average pacing targets, cache and refill controls. The cache heuristic assumes one writer batch is unavailable. Its active cap must be enforced over short windows, not as post-burst average sleep. Raises: ValueError: Invalid metadata, unrepresentable derived values, or memory too small for the writer, diversity and a full unlocked batch. TypeError: Profile or runtime has the wrong configuration type. """ if not isinstance(profile, ODSReuseProfileCfg) or not isinstance( runtime, ODSRuntimeCfg ): raise TypeError( "profile and runtime must use the typed ODS configuration classes" ) profile.validate() runtime.validate() g = _number("accepted_episodes_per_second", accepted_episodes_per_second) t = _number("training_chunks_per_second", training_chunks_per_second) minimum = _integer("min_chunks_per_episode", profile.min_chunks_per_episode) maximum = _integer("max_chunks_per_episode", profile.max_chunks_per_episode) hard = _integer("hard_max_chunks_per_episode", profile.hard_max_chunks_per_episode) if not minimum <= maximum <= hard: raise ValueError( "Require min_chunks_per_episode <= max_chunks_per_episode <= hard cap" ) if type(profile.qualified) is not bool: raise ValueError("qualified must be explicitly True or False") identity = _text("profile_id", profile.profile_id) scope = _text("profile scope", profile.scope) measurement_scope = _text("measurement_scope", runtime.measurement_scope) batch = _integer("batch_size", runtime.batch_size) producers = _integer("producer_envs", runtime.producer_envs) diversity = _integer("min_cache_episodes", runtime.min_cache_episodes) latency = _number( "first_publication_p95_seconds", runtime.first_publication_p95_seconds ) safety = _number("latency_safety_factor", profile.latency_safety_factor) if safety < 1: raise ValueError("latency_safety_factor must be at least one") lead_seconds = latency * safety balance = t / g balance_float = _float(balance, "balance_chunks_per_episode") budget = min(max(math.floor(balance), minimum), maximum) reasons = [] if not profile.qualified: reasons.append( "Provisional profile: sampling bounds require independent qualification." ) if balance > maximum: reasons.append( "Supply cannot sustain learner capacity within the recommended selection budget." ) elif balance < minimum: reasons.append( "Production is paced to preserve the profile's minimum planned selection budget." ) # One complete batch must fit while P trajectories are locked by the writer. base_cache = _round_up( max(2 * producers, diversity, producers + math.ceil(Fraction(batch, budget))), producers, ) desired_cache = _round_up( max(base_cache, producers + math.ceil(t * lead_seconds / budget)), producers ) memory = runtime.cache_memory_bytes episode_bytes = runtime.bytes_per_episode if (memory is None) != (episode_bytes is None): raise ValueError( "cache_memory_bytes and bytes_per_episode must be supplied together" ) cache = desired_cache if memory is not None: memory = _integer("cache_memory_bytes", memory) episode_bytes = _integer("bytes_per_episode", episode_bytes) capacity = (memory // episode_bytes // producers) * producers if capacity < base_cache: raise ValueError( "Infeasible cache memory: cannot retain writer space, diversity and one full unlocked batch" ) cache = min(cache, capacity) if cache < desired_cache: reasons.append( "Cache memory limits short-window active consumption; enforce the returned active-rate cap." ) unlocked_budget = _integer("unlocked chunk budget", (cache - producers) * budget) initial = _integer("initial chunk budget", cache * budget) active = min(t, Fraction(unlocked_budget) / lead_seconds) # Round a memory-constrained public rate down, never above its exact bound. active_float = _float(active, "active training cap", conservative=True) active = Fraction(active_float) target = min(active, g * budget) publication_target = target / budget threshold = _integer( "refill remaining chunks", max(batch, math.ceil(active * lead_seconds)) ) if threshold > unlocked_budget: raise ValueError("Refill threshold exceeds unlocked quota") reasons.append( "Refill sizing is a latency heuristic; validate subsequent publication spacing and remeasure after pacing changes." ) if measurement_scope == "unspecified": reasons.append( "Measurement scope is unspecified; concurrent saturation capacity is not established." ) return ODSConfiguration( episode_sample_budget=budget, episode_sample_hard_limit=hard, balance_chunks_per_episode=balance_float, batch_size=batch, producer_envs=producers, buffer_size=cache, cache_bytes=( None if episode_bytes is None else _integer("cache bytes", cache * episode_bytes) ), initial_chunk_budget=initial, initial_max_updates=initial // batch, initial_remainder_chunks=initial % batch, refill_remaining_chunks=threshold, active_chunks_per_second_cap=active_float, training_chunks_per_second_target=_float( target, "training target", conservative=True ), training_updates_per_second_target=_float( target / batch, "update target", conservative=True ), publication_episodes_per_second_target=_float( publication_target, "publication target", conservative=True ), training_duty_fraction=_float(target / t, "training duty"), producer_duty_fraction=_float(publication_target / g, "producer duty"), expected_training_throttle_fraction=_float(1 - target / t, "training throttle"), expected_producer_throttle_fraction=_float( 1 - publication_target / g, "producer throttle" ), profile_id=identity, profile_scope=scope, profile_qualified=profile.qualified, measurement_scope=measurement_scope, reasons=tuple(reasons), )
[docs] @configclass class ODSRateMeasurementCfg: """Count and active duration within an explicit absolute monotonic window.""" unit: Literal["accepted_episodes", "completed_chunks"] = MISSING count: int = MISSING start_monotonic_seconds: float = MISSING end_monotonic_seconds: float = MISSING active_seconds: float = MISSING
[docs] @dataclass(frozen=True) class ODSThroughputMeasurement: """Active capacities and realized wall rates, kept distinct by unit.""" accepted_episodes_per_active_second: float accepted_episodes_per_wall_second: float training_chunks_per_active_second: float training_chunks_per_wall_second: float window_seconds: float
[docs] def measure_ods_throughput( production: ODSRateMeasurementCfg, training: ODSRateMeasurementCfg ) -> ODSThroughputMeasurement: """Calculate rates for matching monotonic windows with explicit count units. Counts may be zero; active durations must be positive and no longer than the window. A shared window alone does not prove simultaneous saturation: callers must record production/learner overlap and measurement scope separately. """ rates = [] windows = [] for measurement, expected in ( (production, "accepted_episodes"), (training, "completed_chunks"), ): if not isinstance(measurement, ODSRateMeasurementCfg): raise TypeError("measurement must be ODSRateMeasurementCfg") measurement.validate() if measurement.unit != expected: raise ValueError(f"Expected measurement unit {expected}") count = _integer("count", measurement.count, minimum=0) start = _number( "start_monotonic_seconds", measurement.start_monotonic_seconds, zero=True ) end = _number( "end_monotonic_seconds", measurement.end_monotonic_seconds, zero=True ) active = _number("active_seconds", measurement.active_seconds) if end <= start or active > end - start: raise ValueError( "Measurement window must be positive and contain its active duration" ) windows.append((start, end)) rates.extend( ( _float(Fraction(count) / active, "active rate"), _float(Fraction(count) / (end - start), "wall rate"), ) ) if windows[0] != windows[1]: raise ValueError( "Production and training require the same absolute monotonic window" ) return ODSThroughputMeasurement( *rates, window_seconds=_float(windows[0][1] - windows[0][0], "window seconds") )