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; a manifest with two inputs gets it twice, because the ceiling is per evaluation and not per call. Inference is not counted, 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 that is not taken pays nothing.
  • A constant-folded subtree costs nothing, and a subtree the evaluator recognises twice is charged once. Both are optimisations, and both can move between versions.
  • Every tensor operator also charges for the elements it moves, by its own rule below. The charge is made before the work, so an operator that would exceed the budget is refused rather than run.

{"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

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

These numbers are not a contract. The engine’s own documentation says an operation count is not stable across versions — a new fast path or a constant fold changes what gets dispatched. Budget for the work you want to do, not for a 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 — seven planes in, read in full in A real manifest, piece by piece — measured with tinybrains adapt over the reference observations on 19 September 2026, over 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,1271%
48 × 643,07243,1094%
120 × 12414,880208,42321%

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

What over budget means

Evaluation stops 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. It is never retried: the same adapter on the same observation costs the same on any machine.

Passing admission does not prove every later turn fits. The count is taken 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, is 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 what 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 rather than deriving it. It is one rle_expand, and deriving it is not expressible anyway (why).
  • Prefer one input over several. Each is its own evaluation with its own million, but each also re-reads the observation.
  • The head costs you nothing: the referee reads it. An earlier contract charged an entry ~82,000 operations at 128 × 128 for a gather everybody wrote identically.

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