Frozen dataclasses with cached properties¶
NumericalFunction is implemented as a frozen dataclass with cached properties and no other methods (in particular methods that take inputs).
This allows the code to be easier to read because:
- all the data is derived from a single source of truth that never changes: the dataclass attributes provided when creating the instance
- all the data is lazily computed
- all the data is only computed once
- dependencies between two properties are implicit (e.g. calling fn.input_bufs will also automatically compute fn.input_tensors)
- in tests and debugging we can easily inspect any intermediate value without worrying about the order in which we inspect them.
The only thing we have to be careful about is not introducing circular dependencies between properties.
Cached properties¶
Key cached properties in order of dependency:
- input_tensors → creates symbolic input tensors
- output_tensors → traces the function
- schedule → simplifies the traced graph, callifies it into tinygrad's CALL/PARAM form, then lowers it to ExecItems
- rendered_kernels → renders each kernel to C++
- constant_bufs, input_bufs, output_bufs, intermediate_bufs → buffer categorization
- header_code, source_code → final C++ generation