Changelog¶
Notable API and core changes only. This project follows semantic versioning.
[0.3.0] - 16. Oktober 2026¶
Changed¶
align_inputsraisesKpnn2Errorwhen a feature name matches a hidden or output node, whose column the model would never read. Its missing-name message counts the input nodes found and explains that the edgelist starts at the measured features.PackedMultiheadAttentionwith a positivechunk_sizechecks on every call that each query's softmax sums to 1 (0 only for a query without a finite live score) and its gradient to 0, and waits for the device once per forward and once per backward.MaskedLinearrejects a mask with a value other than 0 and 1, an empty dimension, or no live entry. Put per-edge scaling or signs inconstraint=.MaskedLinear.forwardraisesKpnn2Error(was a raw torch error) when the input is not a tensor or its last dimension is notin_features.MaskedLinear,PackedLinear, andPackedMultiheadAttentionreject an integer,bool, or complex input inforwardinstead of casting it to the parameter dtype. Convert with.float()first.- Arguments whose items are matched by position reject a set
or mapping:
source_index/target_indexofPackedLinearandPackedMultiheadAttention, anddimsandcoordsvalues ofmap_node_attributions. The packed layers also reject a complex index tensor. gather_hop_inputsraisesKpnn2Error(was aRuntimeErrorfromtorch.cat) when the saved source layers differ in a dimension other than the last, such as the batch size.- Flag arguments (
bias,tie,batch_first,add_self_loops,need_weights,average_attn_weights) must bebool, andPackedMultiheadAttentiondropoutmust be in [0, 1]; other values raiseKpnn2Error. parse_layeredandparse_adjacencyreject node names that differ only by leading or trailing whitespace (for example'A'and'A ').- Node names are text.
parse_layered,parse_adjacency, andfrom_dictstill read integers throughstr(), warn on a float orbool(1.0becomes'1.0'), and reject any other value, such as a list or a tensor. widths=onparse_layered,parse_adjacency, andfrom_dictrejects a width above 1 on an input node.align_inputsreturns one column per input node and no longer repeats columns. Give each measurement its own input node.map_node_attributionsgives everyLayeredSpecresult alayercoordinate alongnode, oneint64depth per unit;hop_inputresults get each unit's source layer. It was a scalar, absent forhop_input, andxr.concatalongnodecould give units of other layers the first array's depth. Read one depth withint(da["layer"][0]).
Added¶
unit_indexon both specs: every unit of the named nodes, in order, for nodes outsideinput_nodesandoutput_nodes.AdjacencySpec.unit_index(names)indexes the state vector,LayeredSpec.unit_index(names, layer=)one layer.edge_indexon both specs: every packed slot of the named edges, in order, so per-edge values kept on your own table (signs, prior flags) land on their slots without a loop overedge_location.LayeredSpec.edge_index(sources, targets, hop=)indexes one hop.slot_table()on both specs: the source and target name of every packed slot, hop by hop onLayeredSpec, so trained weights and per-slot scores get edge names by assignment.node_table()on both specs: one row per node with its layer (layered only), width, in- and out-degree, and counts of ancestors, descendants, and input ancestors, in unit order.PackedLinear.from_hop(hop),PackedLinear.from_adjacency(spec), andPackedMultiheadAttention.from_adjacency(spec, embed_dim, num_heads): build the layer from a spec, so source and target indices cannot be swapped by hand. They are the recommended path; the index constructors stay for pairs from no spec.
[0.2.0] - 23. September 2026¶
Changed¶
These can require edits to code written for 0.1.
align_inputsreturns a 1-Dint64column index, not a tensor. Index your data with it:X[:, col].Hopstores packedsource_index/target_indexinstead of a mask, soPackedLinearnow works on hops. CallHop.to_mask()for the dense mask.Skip.source_index/target_indexare renamed tosource_in_layer/target_in_layer.MaskedLinear.weightis a plain parameter (state_dictkeyweight, wasparametrizations.weight.original); readeffective_weight()for the masked map. 0.1 checkpoints still load.MaskedLinear,PackedLinear, andPackedMultiheadAttentionignoretorch.autocastand compute in their parameter dtype.PackedLinear.forwardraisesKpnn2Errorwhen the input's last dimension is notin_features.
Added¶
PackedMultiheadAttention: attention restricted to the prior's edges, with optionalchunk_sizeto bound memory.aggregate_node_attributionsandlist_aggregation_methods: fold named node scores with a registered method. The default method,rauter_mangano_2026, is not yet implemented.PackedLinear.transpose()andscatter_hop_outputs, for tied decoders.widths=on both parsers, so a node can own several units, andranks=onparse_layered, to set depths yourself.constraint=onMaskedLinearandPackedLinear(for example positive or frozen edges), witheffective_weight()andinit_bound().identity=on all three layers: a spec fingerprint stored in the checkpoint and checked on load.generator=on all three layers, for isolated parameter init.edge_location()andnode_units()on both specs, andhop_units()onLayeredSpec: find a named edge's weight slots or a node's units.hop_input=,hop_output=, andaxis="inputs"onmap_node_attributions: say which side of a hop, or which input units, a tensor holds.
Fixed¶
map_node_attributionsno longer shares memory with the input tensor, and it accepts bfloat16 scores (stored as float32).
Dependencies¶
xarray>=2024.11, with no upper bound.
[0.1.0] - 1. September 2026¶
First release of kpnn2.