A Mica program can be told a few things when it starts, on its own command line, ahead of its own arguments:

./server +mica carriers=2 -mica --port 8080

Everything between +mica and -mica belongs to the runtime. It is read before any unit initializes and before the program’s first statement, and none of its words reach the program: ArgCount and Arg see --port 8080 and nothing else.

What a program may be told is decided when it is built, not when it starts. A program tested with two carriers and shipped with that promise keeps two carriers wherever it is started. No environment variable takes part: every choice that changes a running program stands on the line that started it, where anyone can read it.

The flags

FlagEffect
infoprints the program’s manifest, which says what the build decided, beside the configuration the start resolved from it and from the other flags in the group, as one line of JSON on standard output, and ends the start with exit status 0; nothing of the program runs
checkprints everything info prints and every fact the start weighed on the way, as one line of JSON on standard output, and ends the start with exit status 0: the dry run before a long job. A group naming both info and check answers once, with check’s document
carriers=<n>runs the program’s tasks on n carriers, from 1 to 1024, in place of the build’s count; only a program built with --tasking multicore has carriers to choose
processes=<n>lets at most n worker processes, from 1 to 1024, run the tasks the program places on processes at once, in place of the build’s --processes count or the processors the program may use; see how many worker processes run at once
gpu=<n>runs the program’s device work on card n of the machine, counted from 0 as NVIDIA’s driver numbers the cards, in place of the first; check lists the cards by these numbers; see which card a program uses
products=<file>runs each float32, bfloat16 and float16 matrix product on a GPU by the routine measured fastest on the card for its shape, and keeps each choice in file; a later run naming the same file reads the choices instead of measuring again, so the runs print the same digits; see products measured on the card
rtp=listenstarts the runtime protocol’s agent: the running program answers a client, such as the editor, over a Unix domain socket of its own at the default path, /run/user/<uid>/mica/<pid>.sock, or /tmp/mica-<uid>/<pid>.sock where the system has no runtime directory for the user
rtp=suspendstarts the agent and holds the program before its first statement until a client sends runtime/continue; the program names its socket on standard error while it waits
rtp=<path>starts the agent on the socket at path, which holds a /, so a socket in the working directory is rtp=./agent.sock; combines with rtp=suspend
stdout=linewrites each line of standard output through as soon as the program writes it. Without it, output to a pipe or a file waits until about 4 KiB have gathered or the program ends, so a notebook cell, a CI log or a supervising process sees nothing for a while. It costs one write to the operating system per line: a million short lines into a pipe took 0.63 s with the flag and 0.22 s without it

info answers after every other flag in the group was read, so +mica carriers=2 info -mica shows the pool the program would start with:

{"mica_runtime":1,"manifest":{"mica_manifest":1,...,"tasking":{"flavor":"multicore","carriers":4,"pinned":false,"processes":null},...},"configuration":{"carriers":{"effective":2,"source":"flag","oversubscribed":false},"processes":{"effective":16,"source":"processors","oversubscribed":false},"stdout":"default","products":null}}

manifest is the program’s manifest, the same document mica --inspect reads from the program file without starting it. configuration is what only a start can know:

MemberMeaning
carriers.effectivethe carrier count the pool sizes to
carriers.sourcewhere that count comes from: flag (the carriers flag), build (the build’s --tasking count, and the one carrier of a single build), or, for a multicore build without a count, the fact that limits the processors the program may use: processors, affinity or quota
carriers.oversubscribedtrue when the pool has more carriers than the processors the program may use, so some of them run in turn rather than at once
processes.effectivehow many worker processes may run tasks placed on processes at once
processes.sourcewhere that count comes from: flag (the processes flag), build (the build’s --processes count), or the fact that limits the processors the program may use: processors, affinity or quota
processes.oversubscribedtrue when that count is larger than the processors the program may use
stdoutline when the stdout=line flag was given, default otherwise: line by line on a terminal, in blocks to a pipe or a file

A program that places code on a GPU answers one more member, device, about the card it would run on:

"device":{"provider":"nvidia","card":0,"name":"NVIDIA GeForce RTX 4090","generation":"sm_89","precompiled":true}
MemberMeaning
device.providerthe provider that found a card, nvidia, or null when none did
device.cardthe card’s number: the one a gpu=<n> flag named, or 0, the first
device.namethe card’s name, as NVIDIA’s driver states it, or null
device.generationthe card’s generation, as sm_89, or null
device.precompiledtrue when every kernel the program runs, the device floor’s and its own, loads precompiled on this card, so NVIDIA’s runtime compiler never starts; false when some kernel compiles at the start, or no card was found
device.reasonpresent when no card was found: each provider’s own words for why
productsthe record a products=<file> flag named, null otherwise

check adds one member, check, with the facts behind the count:

taskset -c 0,1 ./server +mica check -mica
{"mica_runtime":1,"manifest":{...},"configuration":{"carriers":{"effective":2,"source":"affinity","oversubscribed":false},"processes":{"effective":2,"source":"affinity","oversubscribed":false},"stdout":"default","products":null},"check":{"processors":{"online":16,"affinity":2,"quota":{"readable":true,"cgroup":null,"microseconds":null,"period":null,"carriers":null},"usable":{"carriers":2,"source":"affinity"}}}}
MemberMeaning
processors.onlinethe processors online on the machine
processors.affinitythe processors the program’s affinity mask allows, or null when the mask cannot be read
processors.quota.readablewhether the cgroup hierarchy holding the program’s CPU controller could be read, v1 or v2; false when neither can be
processors.quota.cgroupthe cgroup whose quota is the tightest (cpu.max on cgroup v2, cpu.cfs_quota_us over cpu.cfs_period_us on v1), or null when no quota applies
processors.quota.microseconds, processors.quota.periodthat quota: the processor time granted per period, both in microseconds
processors.quota.carriersthe quota as whole processors, rounded up
processors.usablethe carriers the program may usefully run, and the fact that limits them
cardspresent where the program places code on a GPU: every card the machine offers, each with the number gpu=<n> names it by, its name and its generation, or null when no provider can count the cards, which device.reason explains

usable matters beside a count the build or a flag named: a program built with --tasking multicore,8 and started in a container limited to two processors still starts eight carriers, because the build’s count is a promise, and check shows that six of them can never run at once, with "oversubscribed":true.

How many carriers a pool starts

A program built with --tasking multicore and no count sizes its carrier pool when it starts, to the least of three facts the kernel states:

  • the processors online on the machine;
  • the processors its affinity mask allows, as taskset or a container’s --cpuset-cpus restricts it;
  • the CPU quota of its cgroup, as a container’s --cpus or a Kubernetes CPU limit sets it, rounded up to whole processors. The tightest quota on the path from the program’s cgroup up to the cgroup root applies.

A program in a container limited to one and a half processors on a sixteen-processor machine starts two carriers, not sixteen. The quota is read from the cgroup hierarchy that holds the CPU controller: cgroup v2, or cgroup v1 on older hosts and hybrid layouts where the controller still sits there.

Any count from 1 to 1024 runs. On a sixteen-processor machine, a thousand tasks ran on 1024 carriers in 0.3 seconds with about 100 MB of memory, and gave the same results as on one carrier. Carriers beyond the processors spend processor time looking for work rather than doing it, so the processors the program may use are the healthy count, which is what a pool without a count starts with.

Which card a program uses

A program that places code on a GPU runs its device work on one card: the first, unless the start names another.

./train +mica check -mica           # lists the machine's cards
./train +mica gpu=1 -mica           # trains on the second

check lists every card with the number the flag names it by, as NVIDIA’s driver numbers them:

"cards":[{"card":0,"name":"NVIDIA GeForce RTX 4090","generation":"sm_89"},{"card":1,"name":"NVIDIA GeForce RTX 3090 Ti","generation":"sm_86"}]

The number is checked against the machine’s cards before the program’s first statement, so a number with no card behind it is refused at the start rather than at the program’s first device use, which may come late in a run. Which card a program uses is a fact of the machine it runs on, not of its build, so every policy that reads a group admits the flag, sealed included.

How many worker processes run at once

A task placed on a process runs in a worker process of its own. However many tasks a program places, at most so many workers run at once; a task placed beyond the count waits until a worker ends, and then runs. A thousand placements on a two-processor machine run two at a time rather than starting a thousand processes together.

The count is, in order:

  • the processes=<n> flag, where the build’s policy admits it;
  • the build’s --processes <n>;
  • the processors the program may use when it starts, the same count a carrier pool without a count starts with.

A placement that waits holds no carrier: the program’s other tasks run on while it waits, and a cancelled scope ends the wait without starting a worker. A stream on a process takes no worker’s place, because its consumer paces it: a program may read a stream from a worker and place tasks on processes for what it reads, whatever the count.

Tasks placed on processes that wait for each other, such as a server and its client placed side by side, need a count at least as large as the tasks that wait together, which --processes states for every machine the program runs on.

Where the group stands

The group is read only at the very head of the arguments. A +mica anywhere later is an ordinary argument of the program, so a runtime never takes words that belong to the program. A program whose first argument must be the word +mica is started with an empty group in front of it:

./tool +mica -mica +mica      # the program receives one argument: +mica

The run mode passes the group through unchanged: mica --run tool.mica +mica info -mica.

The build’s policy

--runtime-flags states what the built program accepts:

PolicyAcceptsDefault for
openevery flag the build did not pinthe debug tier (--optimize debug, and --run unless it names a faster tier)
sealedonly info, check, gpu=<n> and stdout=lineevery release tier (O1, O2, release,...)
sealed,rtpinfo, check, gpu=<n>, stdout=line and the rtp= flags: a shipped program an editor or a monitoring tool may attach to when it is started to allow itnever a default
nonenothing: the runtime reads no group, and +mica ... -mica are ordinary argumentsnever a default

A shipped release binary therefore answers questions about itself and changes only where its build said so. stdout=line belongs with the questions: it decides when the lines arrive, not what the program does, so a release service under a supervisor or in CI shows its log as it writes it. gpu=<n> does too: the card is the machine’s, so a release program trains on the card the one starting it chooses.

Products measured on the card

A matrix product on a GPU runs by a routine of NVIDIA’s cuBLAS, which picks one by its own ranking. That choice is right for most shapes on most cards, and leaves time on some. With products=<file>, the first product of each shape times the routines cuBLAS’s own cuBLASLt ranks for it, and then searches past them through every configuration of the algorithms cuBLASLt lists for the product’s types, on the card the program runs on, and keeps the fastest; every later product of the shape runs by it. Products of float32, bfloat16 and float16 are measured; a float64 product keeps cuBLAS’s own choice. cuBLAS’s own routine takes part in every measurement, and a measured routine replaces it only when it is clearly faster, so a record never makes a program slower. On an RTX 3090 Ti, the ranked routines took nanoGPT’s bfloat16 training step from 25.9 ms with cuBLAS’s own choice to 24.4 ms, and the search past them to 23.8 ms.

The search costs its time once, in the run that writes the record: about twenty seconds over nanoGPT’s dozen product shapes, about forty over GPT-2 small’s. Every later run reads the record and measures nothing.

The routine decides in which order a product’s sums are added, so it decides the last digits of the answer. That is why the choice is kept: the record holds one line for each shape, and a run that names the same file reads its choices instead of measuring again. The run that measured and every run that reads its record print the same digits:

./train +mica products=train.products -mica     # measures, and keeps the choices
./train +mica products=train.products -mica     # reads them: the same digits

A record’s first line names the card’s architecture and the library’s version it was measured with. A record made for another card or another version is begun again, and the program says so on standard error. Which routines may be chosen follows the build: a program built without fp_half_intermediates chooses only among routines that add a product’s partial sums in float32, as cuBLAS’s own choice does.

The flag changes which digits a build prints, so a build admits it only under the open policy, the debug default.

Pinned values

A value spelled as fixed in the build cannot be changed at start, whatever the policy:

mica --tasking multicore,2,fixed ...

,fixed stands behind a count and nowhere else.

Refusals

A flag the build does not allow ends the start before anything of the program has run: one line on standard error, and exit status 2.

SituationThe line
the group has no endmica runtime flags: the runtime flag group opened by '+mica' is not closed by '-mica'
an unknown flagmica runtime flags: unknown runtime flag 'turbo'; the flags are 'info', 'check', 'carriers=N', 'processes=N', 'gpu=N', 'stdout=line', 'products=FILE' and 'rtp=...'
a record without a filemica runtime flags: 'products=' is refused: the record of measured products is named by a file, 'products=FILE'
an unknown bufferingmica runtime flags: 'stdout=full' is refused: standard output's buffering is chosen with 'stdout=line'
a count out of rangemica runtime flags: 'carriers=0' is refused: the carrier count must be an integer between 1 and 1024
a process count out of rangemica runtime flags: 'processes=0' is refused: the process count must be an integer between 1 and 1024
a number with no card behind itmica runtime flags: 'gpu=2' is refused: this machine has 2 cards, numbered 0 to 1
a word that is no card numbermica runtime flags: 'gpu=-1' is refused: a card is named by its number, counted from 0, as 'gpu=0'
no provider counting the cardsmica runtime flags: 'gpu=0' is refused: no GPU provider counts this machine's cards: nvidia: libcuda.so.1 not found
a card named for a program without device codemica runtime flags: 'gpu=0' is refused: this program places no code on a GPU
a sealed buildmica runtime flags: 'carriers=3' is refused: this program was built with '--runtime-flags sealed', which admits only 'info', 'check', 'gpu=N' and 'stdout=line'
a build sealed with the protocolmica runtime flags: 'carriers=3' is refused: this program was built with '--runtime-flags sealed,rtp', which admits only 'info', 'check', 'gpu=N', 'stdout=line' and 'rtp=...'
the protocol in a sealed buildmica runtime flags: 'rtp=listen' is refused: this program was built with '--runtime-flags sealed', which does not admit the runtime protocol; a sealed build admits it with '--runtime-flags sealed,rtp'
an unknown protocol wordmica runtime flags: 'rtp=fast' is refused: the runtime protocol is asked for with 'rtp=listen', 'rtp=suspend' or 'rtp=<socket path>', a path holding a '/'
a second socket pathmica runtime flags: 'rtp=./b.sock' is refused: the runtime protocol listens on one socket, and 'rtp=./a.sock' named it already
a socket that cannot be openedmica runtime flags: the runtime protocol could not start on './agent.sock': another process listens on the socket path, or the path is not a socket; the other reasons are named on the runtime protocol’s page
a pinned countmica runtime flags: 'carriers=3' is refused: this program's build pinned 2 carriers ('--tasking multicore,2,fixed')
a single-carrier buildmica runtime flags: 'carriers=3' is refused: this program was built for one carrier ('--tasking single')