Mojo · Topology

Topology

from forkunion import Fabric, Topology, ThreadInDomain, … 4.0.0-rc.1 · d927445f

A machine is described along two independent axes. A compute domain is a bindable group of cores sharing a Quality-of-Service class and locality — it is what a pool spawns onto, and the index a worker callback receives. A memory domain is a bank of memory with its own capacity and access cost — it is what the allocator targets, may be cpuless like a CXL expander, and may serve several compute domains at once. The axes stay separate because they don’t line up: performance and efficiency cores often share one memory controller, and equally-fast cores are often split across cache clusters or sockets.

Each axis carries a level, a dense ordinal grouping domains of like performance. Levels can be fewer than domains, since several domains may share one, and the two axes count in opposite directions: compute levels grow with performance, memory levels grow with distance, placing HBM below DDR and CXL above. Two objects split the answers by provenance, not by axis: a Topology holds what the platform declares — domains, cores, QoS classes, volumes, on both axes — while a Fabric holds what ForkUnion observes: per-edge latencies, bandwidths, and distances, and the per-medium tiers derived from them. The pipeline is harvest all the way down: a Topology harvests the declared structure from the OS and stays immutable, a pool spawns on it, and a Fabric then harvests the observed performance from the silicon through that pool’s pinned workers, pointer-chasing and streaming every reachable edge. A memory level is a property of the medium, independent of the querying core: it keys on the best bandwidth any initiator sustains to the pool, ties split by the best latency, so a 3 TB/s HBM pool outranks DDR even at equal latency.

Compute axisMemory axis
Count domainscompute_domains_countmemory_domains_count
Count levelscompute_levels_countmemory_levels_count
Level of a domaincompute_level_inmemory_level_in
Faster meanshigherlower

Names are spelled here as in Rust, which Mojo matches; C prefixes them with fu_, and Zig spells them in camelCase. Rust, Zig, and Mojo all carry the domain coordinates as distinct types — ComputeDomain, MemoryDomain, and MemoryDomainId — while C passes plain indices. Domain counts and compute levels answer from the Topology; memory levels and the per-edge magnitudes answer from a harvested Fabric, which C prefixes with fu_fabric_.

Beyond the level ordinals, two magnitudes describe a compute domain, both best-effort. compute_capacity_in forwards the kernel’s own rating of a core, read from /sys/devices/system/cpu/cpuN/cpu_capacity, normalized so the fastest core on the machine reads 1024. Linux publishes that file on arm64 and on Intel hybrids, and nothing like it exists on Windows or Apple, which read 0. A kernel that rates every core alike rates nothing, so a tie also reads 0; Linux then splits the classes by their core PMUs, cpu_core and cpu_atom on Intel hybrids. compute_cache_bytes_per_core_in reports each logical core’s share of the deepest cache confined to a domain: one instance’s bytes over the cores sharing it. It is enumerated exactly rather than measured — from the per-core cache hierarchy in sysfs on Linux, CPUID leaves elsewhere on x86, sysctl on Apple, and the cache relationships on Windows. Domains of identical throughput can sit behind very differently sized caches, so neither value follows from the other. Treat both as hints, present only where the hardware volunteers them, and never as a number a program requires.

The memory axis carries its own magnitudes, all answered by the harvested Fabric. memory_distance gives the relative cost of reaching a memory domain from a compute domain — 10 for local, higher for remote, on ACPI SLIT’s scale but from measured ratios — while memory_bandwidth and memory_latency give the observed saturated read bandwidth and dependent-load latency per edge. Back on the Topology, volume_ram and volume_ram_in report installed RAM, total or per domain, while huge_pages_count_in and volume_huge_pages_in report the free huge pages a domain can still back an allocation with, as a count or in bytes, and huge_pages_count and volume_huge_pages total them. Like the compute magnitudes, these are best-effort: fabric queries read 0 before a harvest or on edges no pinned worker could reach.

Finally, local_memory_of bridges the axes, naming the memory domain a given compute domain should allocate from. Where no topology is harvested, every query degrades to a single compute domain and a single memory domain rather than failing, so these loops need no conditional compilation.

API

StructFabricWhat ForkUnion measured about this machine's memory, as opposed to what it declares.8
struct Fabric

A Topology holds what the platform reports; a Fabric holds per-edge latencies, bandwidths, and distances observed in-process, plus the per-medium tiers derived from them.

Fields

libraryLibraryThe loaded core, held so its symbol table outlives every call made through the handle.
handlePointer[NoneType, MutUntrackedOrigin]The opaque fu_fabric_t, which answers zeroes until a harvest has filled it.

Conforms to

Deinitable, Movable
__init__Allocates an empty fabric; every query answers zero until harvest has run.
def __init__(out self, library: Library)

Parameters

libraryLibraryThe loaded core the fabric is allocated through and later queried against.

Raises

Error if the allocation is refused.
__deinit__Frees the fabric along with the observations it snapshotted.
def __deinit__(deinit self)
harvestMeasures every reachable edge, using the pool to drive the probes. Takes seconds.
def harvest(mut self, topology: Topology, pool: Pool)

Parameters

topologyTopologyThe machine description; snapshotted here, so it may be freed once this returns.
poolPoolA pool spawned across the whole machine, whose pinned workers run the probes.

Raises

Error if the pool spans no memory domains - flat, pinned to one domain, or terminated.
memory_latencyObserved access latency from one compute domain to one memory domain.
def memory_latency(self, compute: ComputeDomain, memory: MemoryDomain) -> Int

Parameters

computeComputeDomainThe initiating domain, whose worker pointer-chases the edge.
memoryMemoryDomainThe target domain, whose pages the probe first-touched.

Returns

Dependent-load latency in nanoseconds, best of the recordings; zero for an unwalked edge.

Raises

Error if the core refuses the query; an unharvested edge answers zero rather than failing.
memory_bandwidthObserved bandwidth from one compute domain to one memory domain.
def memory_bandwidth(self, compute: ComputeDomain, memory: MemoryDomain) -> Int

Parameters

computeComputeDomainThe initiating domain, whose whole worker set streams the edge at once.
memoryMemoryDomainThe target domain, whose pages the probe first-touched.

Returns

Saturated read bandwidth in megabytes per second; zero for an unwalked edge.

Raises

Error if the core refuses the query; an unharvested edge answers zero rather than failing.
memory_distanceThe SLIT-style distance, where a domain's distance to its own memory is 10.
def memory_distance(self, compute: ComputeDomain, memory: MemoryDomain) -> Int

Parameters

computeComputeDomainThe initiating domain, whose local memory is clamped to its row's minimum.
memoryMemoryDomainThe target domain, whose pages the probe first-touched.

Returns

The measured latency ratio to the initiator's local memory, larger meaning farther.

Raises

Error if the core refuses the query; an unwalked edge falls back to 10 local, 20 remote.
memory_level_inWhich performance tier a memory domain landed in, counted from the fastest.
def memory_level_in(self, memory: MemoryDomain) -> Int

Parameters

memoryMemoryDomainThe domain to classify, independently of any initiator.

Returns

A tier ordinal with zero the fastest, keyed by best sustained bandwidth and split by latency.

Raises

Error if the core refuses the query; an unharvested fabric answers zero rather than failing.
memory_levels_countDistinct memory tiers this machine turned out to have.
def memory_levels_count(self) -> Int

Returns

One on a uniform machine, more once HBM, DDR, or CXL are mixed; domains may share a tier.

Raises

Error if the core refuses the query; an unharvested fabric answers a single tier.
StructTopologyThe hardware view. Released when the last reference to it goes out of use.18
struct Topology

Fields

libraryLibraryThe loaded core, kept so the release can still reach fu_topology_delete.
handlePointer[NoneType, MutUntrackedOrigin]The fu_topology_t this struct owns, non-null for as long as it lives.

Conforms to

Deinitable, Movable
__init__Harvests this machine's description through an already loaded core.
def __init__(out self, library: Library)

Parameters

libraryLibraryA loaded core, kept by the topology so the shared object stays mapped.

Raises

Error if the harvest fails, which a machine that will not describe itself does with TOPOLOGY_UNAVAILABLE and an exhausted allocator with BAD_ALLOC.
__deinit__Releases the harvested description; the loader reference goes with it.
def __deinit__(deinit self)
logical_cores_countEvery hardware thread this machine exposes.
def logical_cores_count(self) -> Int

Returns

The count, with SMT siblings counted separately.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
logical_cores_count_inThe hardware threads belonging to one compute domain.
def logical_cores_count_in(self, domain: ComputeDomain) -> Int

Parameters

domainComputeDomainA dense compute-domain index, below compute_domains_count.

Returns

The count of logical cores in that domain.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
compute_domains_countSame-QoS core clusters, each local to one memory domain.
def compute_domains_count(self) -> Int

Returns

The number of domains, which bounds every ComputeDomain index.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
compute_levels_countDistinct core classes; may be fewer than the domains, since domains can share a level.
def compute_levels_count(self) -> Int

Returns

The number of classes, which bounds every compute_level_in answer.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
compute_level_inWhich core class a compute domain belongs to, counted from the fastest.
def compute_level_in(self, domain: ComputeDomain) -> Int

Parameters

domainComputeDomainA dense compute-domain index, below compute_domains_count.

Returns

The level, zero for the fastest class this machine has.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
compute_capacity_inA relative throughput weight for one compute domain.
def compute_capacity_in(self, domain: ComputeDomain) -> Int

Parameters

domainComputeDomainA dense compute-domain index, below compute_domains_count.

Returns

The weight, comparable only against the other domains of this machine.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
compute_cache_bytes_per_core_inEach logical core's share of the deepest cache confined to one compute domain.
def compute_cache_bytes_per_core_in(self, domain: ComputeDomain) -> Int

Parameters

domainComputeDomainA dense compute-domain index, below compute_domains_count.

Returns

Bytes of one cache instance over the logical cores sharing it, or 0 if unknown.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
memory_domains_countBanks of memory with their own capacity and access cost.
def memory_domains_count(self) -> Int

Returns

The number of domains, which bounds every MemoryDomain index.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
local_memory_ofThe memory domain nearest a compute domain, as a dense index.
def local_memory_of(self, domain: ComputeDomain) -> MemoryDomain

This is an index, not an id. Pass it through memory_domain_id_at_index before handing it to an allocator, which keys off the operating system's own numbering.

Parameters

domainComputeDomainA dense compute-domain index, below compute_domains_count.

Returns

The nearest memory domain's own dense index, below memory_domains_count.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
memory_domain_id_at_indexResolves a dense memory-domain index to the OS id the allocators take.
def memory_domain_id_at_index(self, domain: MemoryDomain) -> MemoryDomainId

A real domain may itself carry -1 where the OS names none, so an index this machine does not have is a reported refusal rather than that same value.

Parameters

domainMemoryDomainThe dense memory-domain index to resolve, below memory_domains_count.

Returns

The OS id, which the allocators take and which no index may substitute for.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
volume_ramBytes of RAM installed, regardless of page size.
def volume_ram(self) -> Int

Returns

The byte count for the whole machine, not for any one domain.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
volume_ram_inBytes of RAM in one memory domain.
def volume_ram_in(self, domain: MemoryDomain) -> Int

Parameters

domainMemoryDomainA dense memory-domain index, below memory_domains_count, never an OS id.

Returns

The byte count for that domain alone.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
volume_huge_pagesBytes backed by free huge pages across every memory domain.
def volume_huge_pages(self) -> Int

Returns

The free byte total, summed over every page size the machine offers.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
volume_huge_pages_inBytes backed by free huge pages in one memory domain.
def volume_huge_pages_in(self, domain: MemoryDomain) -> Int

Parameters

domainMemoryDomainA dense memory-domain index, below memory_domains_count, never an OS id.

Returns

That domain's free byte total, summed over its page sizes.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
huge_pages_countFree huge pages of any size across every memory domain.
def huge_pages_count(self) -> Int

Returns

The page total, counting a 1 GB page the same as a 2 MB one.

Raises

Error if the core refuses the query, which a handle it cannot read does with INVALID_ARGUMENT.
huge_pages_count_inFree huge pages of any size in one memory domain.
def huge_pages_count_in(self, domain: MemoryDomain) -> Int

Parameters

domainMemoryDomainA dense memory-domain index, below memory_domains_count, never an OS id.

Returns

That domain's page total, across every page size it offers.

Raises

Error if the core refuses the lookup, which an index this machine does not have does with INVALID_ARGUMENT.
StructThreadInDomainOne thread, situated in one compute domain - the "where I am" every callback receives.
struct ThreadInDomain

Every dispatch hands its callback two things: the work, and this. A callback placing memory reads compute_domain to find the node it runs on; one indexing per-thread scratch reads thread.

Fields

threadIntThe physical thread executing the work.
compute_domainIntThe compute domain - a same-QoS core cluster - the thread runs on.

Conforms to

Copyable, Deinitable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable
StructComputeDomainA dense index into this machine's compute domains, counted from zero.1
struct ComputeDomain

Fields

indexIntThe zero-based position among this machine's compute domains.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable, Writable
write_toWrites the index with the kind of domain spelled out, so a log line cannot be misread.
def write_to(self, mut writer: T)

Parameters

writerTmutThe sink the label is appended to.
StructMemoryDomainA dense index into this machine's memory domains, counted from zero.1
struct MemoryDomain

Fields

indexIntThe zero-based position among this machine's memory domains.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable, Writable
write_toWrites the index labeled as a dense one, never bare, so it cannot pass for an OS id.
def write_to(self, mut writer: T)

Parameters

writerTmutThe sink the label is appended to.
StructMemoryDomainIdThe operating system's own id for a memory domain, which is not an index.1
struct MemoryDomainId

The allocators key off this rather than off the topology, so an allocation can outlive the handle that found it. Reach it through Topology.memory_domain_id_at_index, never by reusing a dense index.

Fields

identifierc_intThe id the operating system uses, which need not match any dense index.

Conforms to

Copyable, Deinitable, Equatable, ImplicitlyCopyable, Movable, RegisterPassable, TrivialRegisterPassable, Writable
write_toWrites it as the NUMA node the operating system itself calls it.
def write_to(self, mut writer: T)

Parameters

writerTmutThe sink the label is appended to.