Skip to content

TileFoundry Spec — Target

A Target is the immutable capability context compilation and the compiler algorithms read. Architecture describes compilation identity and instruction structure. Device describes fixed product resources. A target is a value that answers questions about hardware; it does not own the operations that ask, and it answers only by projecting the facts an asking algorithm declared.

1. Target

TargetT = TypeVar("TargetT", bound="Target")
FactsT = TypeVar("FactsT")


class Target:
    """Identify a compilation backend."""

    name: ClassVar[str]
    topology_levels: ClassVar[tuple[str, ...]]

    @property
    def identity(self) -> str: ...

    @classmethod
    def available(cls) -> tuple[Target, ...]: ...

    def get_analyzer(self, selector: str) -> Analyzer: ...
    def get_code_generator(self) -> CodeGenerator: ...
    def validate_program_topology(self, topology: Topology) -> None: ...
    def get_facts(
        self,
        facts_type: type[FactsT],
        query: object | None = None,
    ) -> FactsT: ...


def register_target(cls: type[TargetT]) -> type[TargetT]: ...


def registered_targets() -> Mapping[str, type[Target]]: ...
  • constraints:
  • A custom Target MUST use one of two modes. A document-backed product adds complete Architecture and Device documents to an existing Target class; it MUST NOT add a product-specific Target subclass. A new backend implements the Target interface directly and answers its hardware through get_facts; it need not expose Architecture, Device, or hardware documents.
  • Inheriting bare Architecture or Device values and injecting them into an existing document-backed Target is not a supported custom Target mode. It bypasses both document schema validation and the facts_result projection boundary.
  • Target.get_analyzer MUST select the standard compute-cost, memory, roofline, and performance analyzers for every Target. Those algorithms consume only requested Facts, so a backend reuses them by answering get_facts, not by inheriting a backend-specific analysis base class. A missing Facts projection MUST fail when the selected analyzer requests it.
  • facts_result, TargetFactsError, TopologyLimitFacts, MemoryHierarchyFacts, ThroughputFacts, PerformanceServiceFacts, and ParallelCapacityFacts MUST be importable from tilefoundry.target for provider implementations.
  • name MUST be a non-empty class variable declared directly by every concrete registered Target class. It is the stable class registration identity, not a backend-family selector and not an instance value field.
  • @register_target MUST be the only Target registration form. It takes the class directly, accepts no decorator arguments, never constructs the class, and returns it unchanged.
  • Re-registering a class with the same module, qualified name, and registered name MUST be idempotent, including after a provider reload. A different provider claiming the same name MUST fail rather than replace the owner.
  • registered_targets() MUST expose one read-only name -> class view. The view MAY be used for inspection but MUST NOT construct a Target.
  • identity MUST name one concrete Target value. It MUST be the device document ID for a document-backed Target and the registered class name for a Target with no device document.
  • available() MUST return every value of that registered class which can be constructed in the current environment. A class with no hardware documents MUST return its one parameterless value by default.
  • Authored Target parameters MUST accept a constructed Target instance or their documented omitted state. A string MUST fail and MUST NOT be resolved through registration.
  • Target values MUST remain immutable hardware values. Their service getters select immutable descriptors and Facts; normal Python inheritance carries those selections to a subclass unless it overrides or refuses them.
  • validate_program_topology MUST reject a level outside topology_levels and any resolved static extent that is not positive or exceeds the finite TopologyLimitFacts bound for that level. The shared program check MUST use this method rather than reproduce a backend's topology limits.
  • A provider MAY import Analyzer from tilefoundry.target to construct getter results. That package MUST NOT expose CodeGenerator or LinkableModule as provider API.
  • A missing getter capability MUST fail and name the concrete Target class, its registration name, and the requested selector, topology, or Facts type.
  • Target values MUST NOT own code emission, linking, loading, or the public Analyze, compile, build, or jit orchestration.

1.1 Architecture

class Architecture:
    """Describe compilation architecture identity and structural facts."""

    name: str
    max_threads_per_cta: int
  • constraints:
  • Architecture is a Target-level marker for the architecture-side value passed into a Facts projection and for its Python reconstruction support; it is not a provider extension point.
  • A document-backed Target MUST accept direct values only through its backend-specific concrete Architecture type. A further product adds a document of that backend's schema; a further backend implements Target and get_facts directly.
  • Concrete backend architecture values MUST be immutable.
  • name MUST be the stable architecture identity used by compilation.
  • max_threads_per_cta MUST describe the architecture's static CTA thread limit when the architecture has a CTA thread level.

1.2 Device

class Device:
    """Describe one concrete device's fixed resource facts."""

    name: str
    sm_count: int
  • constraints:
  • Device is a Target-level marker for the device-side value passed into a Facts projection and for its Python reconstruction support; it is not a provider extension point.
  • A document-backed Target MUST accept direct values only through its backend-specific concrete Device type. A further product adds a document of that backend's schema; a further backend implements Target and get_facts directly.
  • Concrete backend device values MUST be immutable and describe one device.
  • name MUST be the stable product identity.
  • Device-specific capacity, bandwidth, and compute-throughput facts belong to concrete subclasses.
  • The split between the two is by what the fact is a property of, not by which consumer reads it. An Architecture owns instruction legality and the per-parallel-unit structural limits, which every product built on it shares. A Device owns how many such units the product has, its memory system, and its measured or published throughput. A fact MUST be recorded on exactly one side, and the other side MUST NOT restate it.

4. CudaTarget

class CudaTarget(Target):
    """CUDA target composed from one architecture and one device."""

    name: ClassVar[str] = "cuda"
    architecture: Architecture
    device: Device
    architecture_id: str | None
    device_id: str | None
    architecture_digest: str | None
    device_digest: str | None
    arch: str
    topology_levels: tuple[str, ...]

    def __init__(
        self,
        device: Device | str | Path,
        architecture: Architecture | str | Path | None = None,
        *,
        arch: str | None = None,
    ) -> None: ...

    def validate_program_topology(self, topology: Topology) -> None: ...
    def topology_limit(self, name: str) -> int: ...
    def get_analyzer(self, selector: str) -> Analyzer: ...
    def get_code_generator(self) -> CodeGenerator: ...
    def get_facts(self, facts_type: type[FactsT], query=None) -> FactsT: ...
    def __repr__(self) -> str: ...
  • constraints:
  • device and architecture MUST each accept an installed document ID or a concrete value. An ID MUST resolve immediately to the typed value, and the resolved ID and content digest MUST be retained (§10.2).
  • arch, when provided, MUST equal the resolved architecture's name; it is a consistency check and MUST NOT select or override an architecture.
  • device MUST be required. The constructor MUST NOT select hardware for a caller who named none: a target nobody stated would answer about a machine nobody has.
  • An omitted architecture MUST be read from the device document's declared compatibility, and MUST fail unless that document names exactly one. A Device supplied directly carries no document, so it MUST be given an architecture as well.
  • arch MUST equal architecture.name.
  • A pair selected by ID MUST be checked for declared compatibility. A value supplied directly carries no document, so it has no ID or digest and is exempt from that check: it is a distinct hardware value rather than a revision of an installed one.
  • CudaTarget MUST compose any installed architecture and device pair that declares compatibility. A further CUDA product is its two documents (§10.2) and nothing else: no Target subclass, and no architecture or device type of its own. The services and Facts are selected by the value already, and the numbers are in the documents, so either addition would carry nothing.
  • CudaTarget.available() MUST contain one value per device document whose sole compatible architecture document is available. Its identity MUST be that device document's ID.
  • CUDA MUST select its standard Analyzer, Facts, and CodeGenerator services through the corresponding getters. These selections MUST NOT use Target.name, an exact-concrete-type table, or a second extension registration.
  • CUDA's ThroughputFacts MUST publish the whole-device compute and HBM-bandwidth rates and nothing per unit; what one CTA gets through is PerformanceServiceFacts.
  • CUDA's PerformanceServiceFacts MUST derive unit_flops and unit_bandwidth by dividing each whole-device rate by device.sm_count, MUST take unit_ops from the device document unchanged, and MUST name its unit cta; compiler policy such as ParallelCapacityFacts MUST NOT enter this derivation. unit_ops is not a division of a device peak, because no vendor publishes a device-wide integer, predicate, select, special or local-move rate to divide.
  • topology_limit("cta") MUST equal device.sm_count, and topology_limit("thread") MUST equal the architecture's corresponding structural limit. An unsupported name MUST be refused.
  • __repr__ MUST use the concrete class name and return a constructor expression that rebuilds an equal Target. A subclass retaining the CUDA constructor shape inherits it; a subclass with another constructor MUST override __repr__.
  • The store the threads of one CTA cooperate in MUST be projected as architecture.shared_memory_per_cta_bytes, and MUST be reported as belonging to the cta scope even when the level being asked about is thread.
  • The tensor-memory level MUST be projected with architecture.tensor_memory_per_cta_bytes only where the architecture states a capacity, and MUST be absent from explicit_levels where it states None. A level on hardware that has no such store would offer a plan somewhere to hold accumulators that does not exist.

Topology levels

A target's topology levels define the names a program may declare. The program hierarchy stops at those levels; warp, lane, and warpgroup structure belongs in thread mesh layouts.

  • constraints:
  • CudaTarget.topology_levels MUST be ("cta", "thread") for this single-device target.
  • A declared program topology name MUST be one of its target's topology_levels. A name outside that set MUST be refused naming the levels the target declares.
  • get_facts(TopologyLimitFacts, "cta").max_static_extent MUST be None: the CUDA grid is a launch shape rather than an SM allocation, so its static extent is unbounded here. The "thread" Facts projection MUST equal architecture.max_threads_per_cta.
  • Topology.size MUST be an explicit ShapeDim; construction with None MUST fail for every topology level.
  • Static declared topology extents MUST be positive integers within their target resource limits.
  • Unsupported topology levels MUST fail at the generic lowering boundary.

4.1 CudaArchitecture

class CudaArchitecture(Architecture):
    """What one CUDA architecture states about itself."""

    name: str
    supported_compute_dtypes: tuple[DType, ...]
    instruction_capabilities: tuple[str, ...]
    max_threads_per_cta: int
    max_threads_per_warp: int
    max_warps_per_cta: int
    max_resident_ctas_per_sm: int
    shared_memory_per_sm_bytes: int
    shared_memory_per_cta_bytes: int
    smem_owner: str
    unified_l1_shared_per_sm_bytes: int
    registers_per_sm_32bit: int
    rmem_owner: str
    tensor_memory_per_cta_bytes: int | None
    tmem_owner: str

    def supports_compute_dtype(self, dtype: DType) -> bool: ...

    def topology_limit(self, name: str) -> int: ...
  • constraints:
  • One value type MUST answer for every CUDA architecture, and a concrete value MUST be immutable. What separates one architecture from another is what its installed document records (§4.1.1, §4.1.2), so an architecture MUST NOT be given a type of its own: a class holding no number would restate an identity the value already carries.
  • name MUST be the architecture identity CUDA compilation uses.
  • A CUDA architecture MUST own supported compute DTypes, instruction capabilities, and the thread/CTA structural limits.
  • It MUST own the per-SM resource limits: resident CTAs, shared-memory capacity per SM and per CTA, and register-file capacity per SM. These are properties of the microarchitecture, so every product built on it shares them, and a device MUST NOT restate them.
  • It MUST NOT carry a compute-throughput rate. A FLOP/s figure depends on the clock of one product, so it is a device fact (§4.2) even though the instruction it rates is the architecture's.
  • unified_l1_shared_per_sm_bytes MUST be the size of the one physical block the shared-memory carveout and the L1 data cache are both taken from, and MUST be at least shared_memory_per_sm_bytes. The architecture MUST NOT state an L1 capacity: how much L1 remains depends on how much shared memory a program asked for, which is not a property of the hardware.
  • tensor_memory_per_cta_bytes MUST be the whole tensor-memory store on an architecture whose MMA accumulates in one, because that store is allocated in columns spanning every lane and one CTA may hold all of them. It MUST be None where the architecture has no such store, which is the same statement the document makes by recording that leaf unavailable.
  • CUDA explicit memory ownership MUST be read from the installed hardware values as smem -> cta, rmem -> thread, and tmem -> cta. scope MUST NOT be used as an ownership map: register capacity is stated per SM while each thread owns its register values.
  • Every architecture document MUST declare the leaf behind every field, so a value the architecture does not have is recorded as absent rather than left out (§10.2).
  • No field MAY carry a default: every value comes from the installed document (§10), so the type declares shape and never content.

4.1.1 SM90

The installed nvidia.sm90 architecture document.

  • constraints:
  • Its recorded identity MUST be sm_90.
  • Storage and scale DTypes f4e2m1 and f8e8m0 MUST NOT be recorded as compute DTypes.
  • It MUST record no tensor-memory capacity: the SM90 MMA accumulates in the register file, so there is no separate store to size.

4.1.2 SM100

The installed nvidia.sm100 architecture document.

  • constraints:
  • Its recorded identity MUST be sm_100.
  • It MUST record f4e2m1 as a compute DType, because the SM100 MMA takes 4-bit operands directly. f8e8m0 MUST NOT be recorded as one: it scales those operands rather than being multiplied.
  • It MUST record a tensor-memory capacity, and its instruction capabilities MUST name the MMA family that accumulates there rather than the SM90 family, which this architecture does not run.

4.2 CudaDevice

class CudaDevice(Device):
    """One CUDA device: how many SMs, and the memory and compute rates."""

    name: str
    sm_count: int
    hbm_capacity_bytes: int
    gmem_owner: str
    hbm_bandwidth_bytes_per_second: int
    l2_capacity_bytes: int | None
    _dense_flops: tuple[tuple[DType, int], ...]
    _service_ops: tuple[tuple[str, int], ...]

    def peak_for(self, dtype: DType) -> int: ...

    @property
    def service_ops_per_second(self) -> dict[str, int]: ...
  • constraints:
  • One value type MUST answer for every CUDA device, on the same terms as the architecture (§4.1): a product is its document (§4.2.1, §4.2.2), not a type.
  • A concrete value MUST be immutable, MUST describe one device, and MUST NOT carry a GPU count.
  • It MUST describe how many SMs the product has and how its memory system and compute units perform. Per-SM structural limits belong to the architecture (§4.1).
  • peak_for MUST answer from the installed document for every compute DType the product's tensor cores have a mode for, and MUST raise an actionable error for any other DType. A product with no such mode records that leaf unavailable, so asking for its rate fails instead of reading as a rate nobody published.
  • _dense_flops MUST hold a dense integer FLOP/s entry per DType. A published sparse peak MUST be halved and the division stated as the document's evidence, so no plan is priced against a rate structured sparsity is required to reach.
  • _service_ops MUST hold one integer results/s entry per service kind the installed document records, stated per CTA rather than per device. A kind the document does not record MUST be absent rather than zero, so a consumer that needs it refuses instead of pricing the work at nothing.
  • l2_capacity_bytes MUST be None when the installed document records no value for it. A recorded absence and a number are both statements about the product; a substituted figure would not be.
  • gmem_owner MUST be target: all execution units of this single-device Target share one HBM allocation.
  • No field MAY carry a default, and no resource value MAY be written as a Python literal: the installed document is the single source (§10). Selecting a different installed document by ID is not an override; supplying a partial or edited number without a document behind it is, and is not admitted.

4.2.1 H200SXM

The installed nvidia.h200_sxm device document.

  • constraints:
  • Its recorded identity MUST be h200_sxm, and it MUST declare nvidia.sm90 as the architecture it composes with.
  • It MUST record a dense peak for each of f32, f16, bf16, and fp8e4m3.
  • throughput.f4e2m1 MUST be recorded unavailable rather than omitted: the Hopper tensor cores have no FP4 mode, so the product has no such rate, which is a fact about it and not a number nobody published.
  • It MUST record a per-CTA service rate for each of integer, predicate, select, and special, because a program that compares, selects or indexes asks for work no FLOP/s figure prices. Each MUST state its derivation in conditions: the instruction throughput in results/clock/SM from the vendor's arithmetic-instruction table, times the clock the published f32 peak implies (67e12 / (132 SM * 128 results/clock * 2 FLOP/result)), stated per CTA. These are peak-style analytical envelopes rather than measured calibrations, and conditions MUST say so, along with the proxy each one stands for. A service rate MUST NOT stand in for a bandwidth: movement at a level with no published one is stated as traffic and left untimed.

4.2.2 B200SXM

The installed nvidia.b200_sxm device document.

  • constraints:
  • Its recorded identity MUST be b200_sxm, and it MUST declare nvidia.sm100 as the architecture it composes with.
  • It MUST record a dense peak for each of f32, f16, bf16, fp8e4m3, and f4e2m1.
  • Resource facts the vendor does not publish for this product, its SM count and its L2 capacity among them, MUST be recorded as measured on the described host rather than estimated or borrowed from a related part (§10.1).

5. CpuTarget

class CpuTarget(Target):
    """Identify the CPU host backend."""

    name: ClassVar[str] = "cpu"
  • constraints:
  • name MUST be "cpu".
  • CPU host Functions MAY coexist with CUDA Functions in one module and are exempt from CUDA hardware-fact equality checks.

6. Target ownership and compile resolution

  • tilefoundry.target MUST be the sole Target implementation package. The IR package MUST NOT own Target classes or Target imports.
  • default_target() MUST return a new CudaTarget on the installed nvidia.h200_sxm device. It is a compiler-owned omitted-target policy, not a string lookup and not a default offered by the CudaTarget constructor.
  • There MUST be no global string-to-Target resolver. A registered name returns class identity only through registered_targets() and never constructs a value.
  • A Target belongs to a Module rather than an authored HIR Function. Target inheritance and its declaration rules are defined by core-ir target-inheritance.
  • Analyze MUST obtain the Target from Module.resolve_target() and from nowhere else. It does not accept a bare Function, and it does not resolve an undeclared Target to a default: it reports hardware-dependent results, so measuring against a device the author never declared is a silent wrong answer. In particular it does not read a Target out of Module.metadata; the metadata["target"] the compile pipeline carries is the codegen boundary's own record (passes §6), not a Target source for Analyze.
  • The compile boundary MAY resolve an omitted Module Target to default_target() for lowering, because jit(fn) on a plain Function is a documented entry point (runtime §1.3). It MUST attach that exact value to the normalized Module before lowering.
  • A lowered TIR PrimFunction retains its own target: after lowering it MUST be the exact Target instance resolved from its Module. It selects the CodeGenerator service that emits it. A synthesized host entry carries a CpuTarget().
  • CUDA Functions are grouped by equal Target values in source order. More than one unequal CUDA Target group MUST fail before any generator runs.

7. AppleAmx

class AppleAmx:
    """Describe AMX compilation identity and structural capabilities."""

    name: str
    supported_compute_dtypes: tuple[DType, ...]
    instruction_capabilities: tuple[str, ...]
    amx_units_per_core: int
    staging_bytes: int
    accumulator_bytes: int
    rmem_owner: str

    def supports_compute_dtype(self, dtype: DType) -> bool: ...

    def topology_limit(self, name: str) -> int: ...
  • constraints:
  • name MUST be the architecture identity used by AMX compilation.
  • AppleAmx MUST own the supported compute DTypes and the per-core AMX unit count. The modelled atom catalogue MAY be narrower than the supported compute DTypes.
  • AppleAmx MUST own the X/Y staging and Z accumulator register files. They are ISA geometry, so every part carrying this coprocessor shares them and a device MUST NOT restate them. staging_bytes MUST be the size of one staging file, the X and Y files being equal.
  • rmem_owner MUST be amx: each AMX unit owns its register-file values.
  • Product- and frequency-dependent throughput values MUST NOT be stored on AppleAmx.
  • AMX has no CTA thread level, so AppleAmx MUST carry no CTA thread limit.
  • No field MAY carry a default: every value comes from the installed document (§10).

8. AppleM2Pro

class AppleM2Pro:
    """Describe the apple_m2_pro package's fixed hard resource limits."""

    name: str
    sm_count: int
    performance_core_count: int
    efficiency_core_count: int
    l1d_bytes_per_performance_core: int
    l1d_bytes_per_efficiency_core: int
    l2_bytes_per_performance_cluster: int
    l2_bytes_per_efficiency_cluster: int
    cache_line_bytes: int
    unified_memory_capacity_bytes: int
    unified_memory_owner: str
    unified_memory_bandwidth_bytes_per_second: int
    _unit_flops: tuple[tuple[str, tuple[tuple[DType, int], ...]], ...]

    def throughput_for(self, unit: str, dtype: DType) -> int: ...
  • constraints:
  • AppleM2Pro MUST describe one package and MUST NOT carry a machine count.
  • sm_count MUST be the number of independent AMX units, which is the parallel-unit count a makespan divides work over. It MUST NOT be read as a core count: the performance cores outnumber the units and share them, so it MUST NOT exceed performance_core_count.
  • Cache and core facts MUST distinguish the performance core from the efficiency core, and every value MUST come from the installed document (§10). No field MAY carry a default.
  • A core-level tile's resident footprint MUST be bounded by l1d_bytes_per_performance_core. The AMX register files bound one atom instance instead, which the storage filter enforces rather than a per-tile capacity, so the two MUST NOT be conflated.
  • unified_memory_owner MUST be target; both the host and gmem explicit levels project that one target-wide ownership fact.
  • throughput_for MUST be keyed by execution unit as well as DType, because the AMX coprocessor and the core's NEON pipes have separate measured rates.
  • A tile's traffic MUST be charged against unified_memory_bandwidth_bytes_per_second, which unified memory backs.
  • throughput_for MUST return a measured per-unit throughput recorded in the installed document, and MUST raise an actionable error for a unit or compute DType with no measured entry rather than return an estimate.

9. AmxTarget

class AmxTarget(Target):
    """Compose one AMX target from one architecture and one device."""

    name: ClassVar[str] = "amx"
    architecture: Architecture
    device: Device
    architecture_id: str | None
    device_id: str | None
    architecture_digest: str | None
    device_digest: str | None
    arch: str
    topology_levels: tuple[str, ...]

    def __init__(
        self,
        device: Device | str | Path | None = None,
        architecture: Architecture | str | Path | None = None,
    ) -> None: ...

    def topology_limit(self, name: str) -> int: ...

    def validate_program_topology(self, topology: Topology) -> None: ...
    def get_analyzer(self, selector: str) -> Analyzer: ...
    def get_facts(self, facts_type: type[FactsT], query=None) -> FactsT: ...
  • constraints:
  • device and architecture MUST accept an installed document ID, a document path, or a concrete value, on the same terms as §4. An omitted architecture MUST be read from the device document's sole compatibility declaration.
  • AmxTarget() MUST select the installed apple.amx and apple.m2_pro documents, and arch MUST equal architecture.name.
  • AmxTarget.available() MUST contain one value per device document whose sole compatible architecture document is available. Its identity MUST be that device document's ID.
  • topology_levels MUST be ("core", "amx"): the performance core one tile stream runs on, and the AMX unit inside that core which issues one atom.
  • topology_limit("core") MUST equal device.performance_core_count and topology_limit("amx") MUST equal architecture.amx_units_per_core.
  • Declared topology extents MUST be positive static integers within their level's limit. AMX has no launch shape, so a deferred or symbolic extent MUST NOT be admitted at either level.
  • Unsupported topology levels MUST raise an actionable error naming the supported levels, from both the limit lookup and topology validation.

10. Installed hardware resources

Architecture and Device documents are the canonical authored hardware database, and the only place a hardware number is written. Each is a complete document in its own right; a target is the pair composed through a declared compatibility, never a single combined record.

Each document belongs to the Target class that understands its versioned schema. The class declares one HardwareSpec containing its immutable schema builders and the package holding its built-in documents. There is no cross-backend hardware registry.

10.1 Document envelope

[spec]
schema = "tilefoundry.cuda.device/v3"
kind = "device"
id = "nvidia.h200_sxm"

[compatibility]
architectures = ["nvidia.sm90"]

[facts.memory.hbm.bandwidth]
value = 4800000000000
unit = "byte/s"
origin = "vendor"
source = "https://www.nvidia.com/en-us/data-center/h200/"
conditions = "4.8 TB/s peak HBM3e bandwidth, decimal"

[facts.memory.l2.bandwidth]
status = "unavailable"
conditions = "No validated number."
  • constraints:
  • The envelope MUST carry exactly schema, kind, and id. kind MUST be architecture or device. An unknown envelope key MUST fail.
  • An architecture document MUST declare compatibility under devices and a device document under architectures. A pair MUST compose only when at least one side names the other; neither MUST be inferred.
  • Tables under facts are freely nestable namespaces owned by the target package named by schema. A leaf is identified by carrying value or an explicit status.
  • An available leaf MUST carry value and origin. An unavailable leaf MUST omit value, record status = "unavailable", and state the reason in conditions. The string "unavailable" MUST NOT be used as a value, so no caller can read a placeholder as a number.
  • origin MUST name how the value was obtained: vendor from the vendor's published figure, measured on the described host, reference from a cited third party, derived from other facts, or estimated where it is a reading that no source states. A value not measured on the described host MUST NOT be recorded as measured; a reading rather than a citation MUST be recorded as estimated. derived and estimated MUST state how in conditions.
  • Compiler policy and a program's Topology extents MUST NOT appear in a hardware document. They are compiler inputs, not immutable hardware truth: a fixed-wave parallel capacity is a policy even when its current value equals a device count. An explicit memory level's owner is different: it MUST name one topology from the Target's hardware vocabulary, or the reserved word target for an allocation shared by the whole device. The document states that ownership directly and MUST NOT encode it as an absent value.
  • Only explicit memory levels MUST record an owner. Implicit caches receive no program residency and MUST NOT add an owner leaf merely to make their capacity scope look like a Target topology.

10.2 Registry and resolution

  • constraints:
  • A Target class that consumes hardware documents MUST declare one HardwareSpec. Its package MUST contain that Target's built-in documents, and its schema mapping MUST state every document format the Target accepts.
  • A HardwareSpec MUST resolve its documents by exact ID. There MUST be no cross-backend document table, search path, overlay, or partial document. Built-in package documents MAY be scanned lazily, but resolution MUST NOT depend on a previously constructed Target instance.
  • A schema name carries a version. Requiring a leaf a previous version did not MUST take a new version, because a document written against the old one no longer loads and the failure is otherwise a missing fact rather than a contract that moved.
  • A Target's schema mapping is fixed by its class. A complete document MAY be adopted as another product only when its schema is in that mapping; adding another schema is adding a backend, not adding a hardware product.
  • Explicitly registered documents MUST be routed to the one registered Target class that declares their schema in its own HardwareSpec. An inherited reference to another Target's HardwareSpec MUST NOT claim that schema. Removing such a document MUST discard its resolved cache entry as well as its ID, without changing the Target's built-in package data.
  • An adopted device document MUST NOT become available until its declared architecture document is present in the same HardwareSpec. Explicit registration MUST reject that incomplete pair and name the architecture document required first.
  • Device document IDs and every provider Target identity share one uniqueness boundary. A collision MUST fail before persistent registration and name the existing Target value; it MUST NOT choose one source by load order.
  • A schema MAY validate the documents of several products when they state the same fact paths, and MUST build one value type from all of them. A product is what its document records, so a schema MUST NOT select a type by the identity a document declares.
  • A typed schema MUST validate exact fact paths, value types, units, required fields, and cross-field invariants, and MUST reject any leaf the document carries that the schema does not model, so a misspelled key cannot become an unused fact.
  • Units MUST be normalized while constructing the typed value: algorithms see canonical integers such as bytes and bytes per second, never source strings or unit conversion.
  • A schema MAY model a leaf as optional, which yields the recorded number or None for a leaf recorded unavailable. The leaf MUST still be declared: a document says either what the value is or that there is none, and a missing key MUST remain an error rather than becoming an absent value.
  • Resolution MUST retain each document's ID and content digest on the composed value, so a compiled artifact can name the exact resources it was built against. Editing any recorded value or its evidence MUST change the digest.
  • A Target constructor MUST accept a custom document through a filesystem path. The document MUST be complete, retain its declared ID and digest, and MUST NOT enter that Target's available-ID namespace, so it can neither shadow nor replace an available resource.
  • Unknown IDs, unsupported schemas, unmodelled or malformed facts, malformed envelopes, duplicate document IDs, and incompatible pairs MUST each raise their own actionable diagnostic rather than one shared parse failure.
  • Reporting the resources behind a target MUST name both documents and their digests. A target composed from a directly supplied value has no document to report and MUST say so rather than name the installed resource it resembles.

11. Target Facts projection

A target-aware algorithm declares the immutable aggregate of facts it needs; the concrete Target's get_facts method builds that aggregate. This is the one boundary between a hardware specification and an algorithm's own view of it.

class Target:
    def get_facts(
        self,
        facts_type: type[FactsT],
        query: object | None = None,
    ) -> FactsT: ...
  • constraints:
  • A subclass MUST inherit its base Target's projections through normal Python inheritance. It MAY override get_facts for hardware that differs and delegate unknown requests to super().
  • A missing projection MUST fail immediately and MUST NOT fall back to a built-in Target or substitute built-in hardware values.
  • A Facts aggregate MUST be a frozen dataclass. Aggregates MUST NOT inherit one universal Facts base.
  • query is owned by the requesting algorithm. A hardware-only projection MUST require it to be absent, while a program-dependent one MAY validate its own private query type. There is no common query base or mandatory public program-view type.
  • A returned value that is not an instance of the requested Facts type MUST fail at the projection boundary, not inside the consuming algorithm.
  • Projection MUST be a read. It MUST NOT analyze IR, build a constraint model, solve, export a plan, or mutate the Target, the IR, or runtime state. It only converts what the specification already records.
  • There MUST be no public Facts registration step or global Target Facts table. A custom Target provider registers only its Target class.