Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The budget

Ants allows 1,000,000 operations per adapter evaluation. Each declared input’s adapter gets its own million, so a manifest with two inputs gets two: the ceiling applies to each evaluation, and one call evaluates every input’s adapter. The count leaves out inference, and the whole call (your adapters and the graph) has to fit the turn deadline.

What counts as an operation

  • Every node the evaluator visits costs 1: an operator, a literal, each element of an array written in the program. A loop body pays again for every element it runs over, and a branch the evaluator skips pays nothing.
  • A constant-folded subtree costs nothing, and the evaluator charges once for a subtree it recognises twice. Both are optimisations, and both can change between versions.
  • Every tensor operator also charges for the elements it moves, by its own rule below. The node charges before the work, so it refuses an operator that would exceed the budget and never runs it.
  • The collection and string operators charge for their items: merge, in, keys, values, entries, sort, distinct, group_by, cat, split and deep equality charge one per item, or per 64 bytes of string. So an accumulator built with merge inside a reduce pays for every item it copies, on every step, and grows quadratically.

{"zeros": [[128, 128], "i8"]} costs about 16,389: 1 for the operator, 16,384 for the elements it produces, and a handful for evaluating its arguments (the shape array, its two numbers, and the dtype).

What each operator charges

Each operator charges the amount below on top of its own 1 and the cost of evaluating its arguments. n is the number of elements in the tensor argument, and m the number in the result.

OperatorCharge
zeros, fullm
tensorm
scatterthe larger of the number of points and m. A scatter pays for the whole grid, however few points it writes
rle_expandthe larger of len(runs) and m
one_hotlen(indices) × depth
stack, concatthe elements of all the inputs
unstack, transpose, cast, normalize, to_listn
pad, crop, gatherthe larger of n and m
argmaxn: it reads everything
reshape, shape, dtype1: a reshape moves no elements

The platform does not promise these numbers. The engine’s own documentation says an operation count can change between versions: a new fast path or a constant fold changes what the engine dispatches. Budget for the work, with headroom above the number you measured. An adapter at 990,000 against a 1,000,000 ceiling is one patch release away from a strike.

What a real manifest costs

The baselines’ adapter builds seven planes, and A real manifest, piece by piece reads it in full. tinybrains adapt measured it over the reference observations on 1 October 2026, across the five basic boards the set is drawn on. The worst case on the smallest, one in the middle and the largest:

BoardCellsOperationsOf the budget
24 × 245768,1311%
48 × 643,07243,1134%
120 × 12414,880208,42721%

The cost follows the board’s cells: about 14 operations per cell, because each of the seven planes is a full grid and the stack reads all seven again. The ants, foes, food and hills add a few operations each, lost in the rounding. A season’s boards can run from 24 × 24 (576 cells) to 14,880 cells, and the largest costs a fifth of the budget.

What over budget means

The node stops evaluation the moment a charge crosses the budget. At admission that is a rejection, ADAPTER_OVER_BUDGET; in a match it is a strike, like a missed deadline, and five cumulative strikes forfeit the seat. The node never retries: the same adapter on the same observation costs the same on any machine.

Passing admission does not prove every later turn fits. The node counts on real input, and a late-game turn (more ants, more food and foes in sight) can cost more than any reference case. An adapter whose cost follows the board, as the baselines’ does, stays predictable; one whose loops run over ants or visible objects grows with the game.

Measuring before you submit

tinybrains adapt model.onnx manifest.json prints what every reference case charged, and --obs measures observations of your own. tinybrains check reports the worst case and the fraction of the budget it used. Testing before you submit has both.

Spending less

  • Build planes with scatter and rle_expand, never with a JSON loop over cells.
  • Each plane costs about two operations per cell: one to build it and one for the stack to read it. Dropping a plane your graph does not use is the cheapest saving there is.
  • Read vis: it is one rle_expand, and the language cannot express deriving it (why).
  • Prefer one input over several. Each is its own evaluation with its own million, and each re-reads the observation.
  • The head costs you nothing, because the referee reads it. A program of your own that read it back would pay for all five channels of every cell: ~82,000 operations at 128 × 128.

Test across the limits: a 120 × 124 board has about 26 times the cells of a 24 × 24 one, and an adapter whose cost follows the board costs that much more on it.