danbrooks commited on
Commit
a8351e8
·
verified ·
1 Parent(s): 877ee86

DraftZero FDN graph network (full_r1, best_policy_calibrated): weights, vocab, loader, goldens

Browse files
Files changed (10) hide show
  1. README.md +158 -0
  2. SHA256SUMS +9 -0
  3. check.json +9 -0
  4. config.json +96 -0
  5. coverage.json +34 -0
  6. gnn_release.py +147 -0
  7. goldens.jsonl.gz +3 -0
  8. graph_net.py +344 -0
  9. model.safetensors +3 -0
  10. vocab.json +1 -0
README.md ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-4.0
3
+ tags:
4
+ - magic-the-gathering
5
+ - game-playing
6
+ - graph-neural-network
7
+ - xmage
8
+ ---
9
+
10
+ # DraftZero FDN graph network
11
+
12
+ A graph neural network that plays Magic: The Gathering Limited with Foundations (FDN) decks, trained by imitating
13
+ 17lands' top players. It is [DraftZero](https://github.com/danieljbrooks/draft-zero)'s strongest network on the
14
+ XMage engine, built on Will Wroble's [MageZero](https://github.com/WillWroble/MageZero) graph encoder and network.
15
+
16
+ - **What it does:** given the acting player's view of an XMage game as a graph, it scores every legal option
17
+ (*policy*) and estimates the chance of winning (*value*). A tree search can use both, or a bot can play the
18
+ policy's top choice directly.
19
+ - **What it saw in training:** 10.9 million decisions from 145,898 FDN Premier Draft games by players in 17lands'
20
+ 60%-and-up win-rate group, replayed inside XMage. The opponent's hand is not part of its input.
21
+ - **Size:** 13.4M parameters (width 256), 1,932 leaf features, 22 edge labels. Float32 weights, 54 MB.
22
+ - **Source:** `gnn/full_r1/best_policy_calibrated` in DraftZero's runs
23
+ ([docs/024](https://github.com/danieljbrooks/draft-zero/blob/main/docs/024-gnn-local-layers-and-games.md)).
24
+
25
+ ## How strong it is
26
+
27
+ Its bot played MageZero's heuristic bot, which searches 100 simulations a decision. A search at N *simulations*
28
+ plays N short look-ahead games before each decision. Both bots used PIMC search: each decision deals the hidden
29
+ cards once into a world that fits what the bot has seen, guessing the opponent's deck from real 17lands decks, and
30
+ runs MageZero's tree search on it. Every deck pair was played twice with the seats swapped (paired games), which
31
+ lowers the variance.
32
+
33
+ | Network's simulations a decision | Won | Time a decision |
34
+ |---|---|---|
35
+ | 0: the policy's top choice | 44.7% of 103 games | ~0.2 s |
36
+ | 100 | 62.5% of 104 | ~7 s |
37
+ | 300 | 69.2% of 104 | ~20 s |
38
+ | 1,000 | **69.3%** of 101 | 40-65 s |
39
+
40
+ Times are from those games: up to 30 games at once on one rented RTX 3090 machine, the network served in
41
+ bfloat16, most of the time spent in XMage rather than in the network. On one CPU thread of a laptop, one network
42
+ call takes about 20 ms (batch 1, median 226 nodes a state). On held-out test games, at decisions where the human
43
+ made a play rather than passing, the policy's top choice was a play the human made 85.9% of the time.
44
+
45
+ ## Files
46
+
47
+ | File | What |
48
+ |---|---|
49
+ | `model.safetensors` | the weights, float32 |
50
+ | `config.json` | the architecture, the encoder's constants (node types, value buckets, hash) and the source pins |
51
+ | `vocab.json` | `leaf_ids` and `edge_ids`: the raw feature ids that have embedding rows, in row order |
52
+ | `graph_net.py` | the network (MageZero's `NetGraph`), as DraftZero trains and serves it |
53
+ | `gnn_release.py` | a loader with no pickles: weights, vocab, state -> network input, option logits, the golden check |
54
+ | `goldens.jsonl.gz` | 300 encoded test states with this network's outputs (below) |
55
+ | `coverage.json` | how much of the test data the vocab covers, a baseline for checking an encoder |
56
+ | `check.json` | `gnn_release.py check` on this release |
57
+ | `SHA256SUMS` | the files' hashes |
58
+
59
+ Requirements: Python 3.10+, `torch` (2.1 or later), `numpy`, `safetensors`.
60
+
61
+ ```python
62
+ import gnn_release as r
63
+
64
+ model, leaves, edges, config = r.load(".")
65
+ goldens = r.read_goldens("goldens.jsonl.gz")
66
+ g = goldens[0]
67
+ out = r.evaluate(model, [g["state"]], leaves, edges)[0]
68
+ print(r.option_logits(out, g["graph_type"], g["options"]), out["value"])
69
+ ```
70
+
71
+ ## How it plays: what a port needs
72
+
73
+ **1. The encoder.** The input is MageZero's state graph: typed nodes (ROOT, PLAYER, ZONE, STACK_OBJECT, PERMANENT,
74
+ CARD, ABILITY) with LEAF children, and edges from child to parent with a label. Leaves are XMage strings: card names,
75
+ type and subtype names, `Ability.toString()` rules text, decision text, step names. Each string becomes a feature id,
76
+ `StateEncoder.indexFor(StateEncoder.hash64(name))` (`graph_net.feature_id` in Python). Some leaves also carry a
77
+ number (power, toughness, counters, life). The encoder is Java, at
78
+ `WillWroble/mage@e4afc9c77ba7e4a6dbc24966cbdc6dc0819e0bff` (branch `graph-encoder`,
79
+ `Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/encoder`). DraftZero runs a vendored copy in
80
+ `java/mzbridge/src/org/draftzero/mzbridge/graph` on XMage `danieljbrooks/mage@48e49184` (branch
81
+ `v0.2-generalist`). It encodes the acting player's view, with the opponent's hand hidden (`perfectInfo` false).
82
+
83
+ A leaf outside the vocab has no embedding: its edges are dropped silently, without an error. So an encoder running on
84
+ another XMage version whose strings differ (rules text, decision text) plays worse without failing. To catch this,
85
+ count how many leaf occurrences hit `vocab.json`. On DraftZero's test data the vocab covers more than 99.99% of
86
+ them in priority, attack and target decisions, and 99.3% in block decisions (`coverage.json`).
87
+
88
+ **2. Decisions and options.** `graph_type` is MageZero's `ActionType` ordinal. Each legal option is a list of graph
89
+ nodes, and its logit is the log-sum-exp of its nodes' scores under the head the decision reads:
90
+
91
+ | Decision | Head | An option's nodes |
92
+ |---|---|---|
93
+ | Priority (`graph_type` 0) | `priority` | the ability's node, one per copy of a card (two copies of a card are one option); Pass has its own node |
94
+ | Target, including blocks (3) | `target` | the target's node (CARD, PERMANENT, STACK_OBJECT or PLAYER); Stop Choosing is a CARD node |
95
+ | Attack with a creature (3) | `target` | no = Stop Choosing's node, yes = the defending player's node |
96
+ | Other yes/no (5) | `use` | [no, yes] logits, no nodes |
97
+
98
+ Mulligans, trigger ordering, damage assignment and similar choices have no head. In DraftZero's games mulligans were
99
+ off (every opening hand was kept), and MageZero's player made the other choices without the network's policy.
100
+
101
+ **3. The value.** `value = tanh(value_x)` is the acting player's expected result in -1..1, and
102
+ `P(win) = sigmoid(2 * value_x)`. A temperature fitted on validation games (T = 1.607) is already folded into the value
103
+ head's last layer.
104
+
105
+ **4. The search in DraftZero's games.** This is MageZero's tree search, run fresh at every decision with no tree
106
+ reuse, on one sampled world (PIMC):
107
+
108
+ - selection by PUCT with c = 1;
109
+ - priors from the policy: softmax of the option logits at temperature 1.5, then 0.1 added to every option that
110
+ isn't Pass or a mana ability (not renormalized: MageZero's `setPriors`);
111
+ - uniform priors at the opponent's decisions;
112
+ - leaves scored by the value head, discounted by 0.99 per ply;
113
+ - the move with the most visits is played.
114
+
115
+ The code is `java/mzbridge/src/mage/player/ai` (`BenchPlayer`, `BenchSearch`, `GraphNet`, `GraphMCTSPlayer`) at
116
+ `danieljbrooks/draft-zero@2a461518`. Playing the policy's top choice alone needs no search and no hidden-card sampling.
117
+
118
+ ## Checking a port with the goldens
119
+
120
+ `goldens.jsonl.gz` holds 300 decisions from the test split: 160 priority, 50 attack, 50 target and 40 block
121
+ decisions. They come from games the network never trained on, encoded by the Java encoder above. Each line has:
122
+
123
+ - `state`: the encoder's output in MageZero's graph-server layout: `indices` (raw feature id per node), `values`,
124
+ `edge_child`, `edge_parent`, `edge_label`;
125
+ - `graph_type`, `options` (node indices per legal option), `option_is_pass`, `human_choice` (the option the
126
+ 17lands player took, for interest);
127
+ - `expected`: `option_logits`, `use`, `value` and `value_x`.
128
+
129
+ The expected outputs come from DraftZero's games inference server (`tools/imitation_scale/graph_server.py`, loading
130
+ the original checkpoint) on CPU in float32, one state at a time. `python gnn_release.py check .` loads this release
131
+ and compares. A faithful port agrees to about 1e-5 and picks the same top option in all 300 (`check.json`).
132
+
133
+ The goldens check the network, its weights and the option mapping. They don't check an encoder, because states can't
134
+ move between XMage versions as is; for that, use the vocab coverage above.
135
+
136
+ ## Limits
137
+
138
+ - **Imitation only.** It learned from top players' moves and game results and has not trained by self-play yet.
139
+ - **Measured against one opponent:** MageZero's heuristic bot, with PIMC and guessed decks. There is no Elo or
140
+ human-play result yet.
141
+ - **XMage-specific input.** The features are XMage's own strings, so other engines need either XMage as the encoder
142
+ or a re-implementation that reproduces those strings exactly.
143
+ - **Search is not optional for strength.** The policy alone lost to the heuristic bot (44.7%); 100 simulations or
144
+ more are needed to beat it.
145
+
146
+ ## Sources
147
+
148
+ | What | Where |
149
+ |---|---|
150
+ | Network | `danieljbrooks/draft-zero@2a461518985e3b2bb393bddac4649e217f8fddfe`, `src/draftzero/gameplay/graph_net.py`, a copy of MageZero's `NetGraph` (`WillWroble/MageZero@de225045`, branch `graph-encoder`) |
151
+ | Encoder | `WillWroble/mage@e4afc9c77ba7e4a6dbc24966cbdc6dc0819e0bff` (branch `graph-encoder`) |
152
+ | Engine | `danieljbrooks/mage@48e4918413392523a5f9d7915e1f33941ddea21e` (branch `v0.2-generalist`: DraftZero's FDN changes on MageZero v0.2) |
153
+ | Training | [docs/023](https://github.com/danieljbrooks/draft-zero/blob/main/docs/023-gnn-run-log.md) and [docs/024](https://github.com/danieljbrooks/draft-zero/blob/main/docs/024-gnn-local-layers-and-games.md) |
154
+ | Data | [17lands public game data](https://www.17lands.com/public_datasets), FDN Premier Draft, CC BY 4.0 |
155
+
156
+ The weights are released under CC BY 4.0, like the 17lands data they were trained on. The code (`graph_net.py`,
157
+ `gnn_release.py`) is MIT, as are DraftZero and MageZero. The network and encoder are Will Wroble's MageZero design;
158
+ DraftZero trained these weights.
SHA256SUMS ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ 102d27b0433b0ab194dc98e012e66bfe02bdee2534385b76f317da12f8613973 README.md
2
+ ff5182a65c337210305ffb689263b9a8e773c2aafad68b6cfb211232f0122e05 check.json
3
+ 6b48d3de2bc4c40b7b2f68d52823a276ec777a68908c03ab11a58206c2d77b10 config.json
4
+ 4e57a5c497b0aca0890f3327da9ab09cf00b51ffdaec4e6eeb4b0226dcd07374 coverage.json
5
+ c4cfa96626fd731022f5b37e70db473bb56a4cd52712e981b96fbb4825793189 gnn_release.py
6
+ 46d5949bb97528a1e0ea02ae255c22287fcc49d84a77feef778f2e1eb08318c8 goldens.jsonl.gz
7
+ 8e27fc756f99460ce17418142e2243926400cbea3a72ae3d0801d9726d1cddce graph_net.py
8
+ d5ee8323ccca11f18b1667bc2312407d9d5696b6dacf7243d9a30461cdc29b56 model.safetensors
9
+ 9b8ecf250963bbdbb4c18560d037e3ed547cbb154275188ac363bbcf07f0127d vocab.json
check.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "states": 300,
3
+ "max_abs_diff": {
4
+ "option_logits": 4.76837158203125e-06,
5
+ "value_x": 8.344650268554688e-07,
6
+ "use": 2.682209014892578e-07
7
+ },
8
+ "argmax_agree": 300
9
+ }
config.json ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "draftzero-fdn-gnn-full-r1",
3
+ "arch": {
4
+ "type": "graph",
5
+ "d_model": 256,
6
+ "heads": 4,
7
+ "ff": 1024,
8
+ "dropout": 0.1,
9
+ "passes": 2,
10
+ "global_layers": 2,
11
+ "head_hidden": 256,
12
+ "leaf_dropout": 0.0,
13
+ "local_depth": 1
14
+ },
15
+ "num_leaves": 1932,
16
+ "num_edge_labels": 22,
17
+ "parameters": 13412101,
18
+ "node_types": {
19
+ "ROOT": 0,
20
+ "PLAYER": 1,
21
+ "ZONE": 2,
22
+ "STACK_OBJECT": 3,
23
+ "PERMANENT": 4,
24
+ "CARD": 5,
25
+ "ABILITY": 6,
26
+ "LEAF": 7
27
+ },
28
+ "type_node_ids": {
29
+ "ROOT": 0,
30
+ "PLAYER": 499558809,
31
+ "ZONE": 1485202092,
32
+ "STACK_OBJECT": 191223077,
33
+ "PERMANENT": 11906542,
34
+ "CARD": 587455872,
35
+ "ABILITY": 1229966541
36
+ },
37
+ "value_bounds": [
38
+ -5,
39
+ -4,
40
+ -3,
41
+ -2,
42
+ -1,
43
+ 0,
44
+ 1,
45
+ 2,
46
+ 3,
47
+ 4,
48
+ 5,
49
+ 6,
50
+ 7,
51
+ 8,
52
+ 9,
53
+ 10,
54
+ 11,
55
+ 12,
56
+ 13,
57
+ 14,
58
+ 15,
59
+ 16,
60
+ 17,
61
+ 18,
62
+ 19,
63
+ 20,
64
+ 21,
65
+ 26,
66
+ 31,
67
+ 41,
68
+ 61
69
+ ],
70
+ "feature_hash": {
71
+ "algorithm": "xmage_feature_hash",
72
+ "version": 2,
73
+ "bins": 2147483647,
74
+ "python": "graph_net.feature_id(name)",
75
+ "java": "StateEncoder.indexFor(StateEncoder.hash64(name))"
76
+ },
77
+ "graph_types": {
78
+ "PRIORITY": 0,
79
+ "CHOOSE_TARGET": 3,
80
+ "CHOOSE_USE": 5
81
+ },
82
+ "heads": {
83
+ "priority": "per node, read at ABILITY nodes (a play is its ability's node; Pass has one)",
84
+ "target": "per node, read at CARD / PERMANENT / STACK_OBJECT / PLAYER nodes (Stop Choosing is a CARD); 'attack with X?' is a target choice between Stop Choosing (no) and the defending player (yes)",
85
+ "use": "[no, yes] logits for a yes/no question that isn't an attack",
86
+ "value": "tanh(value_x), the acting player's expected result in -1..1; the fitted temperature (T = 1.607) is folded into the value head's last layer, so P(win) = sigmoid(2 * value_x)"
87
+ },
88
+ "source": {
89
+ "checkpoint": "danbrooks/draftzero-checkpoints:gnn/full_r1/best_policy_calibrated.pt.gz",
90
+ "checkpoint_sha256": "3ee0839fbb6ed9a11f414ee290d2730335cf07490d76649c49d960cdc1f60706",
91
+ "model_code": "danieljbrooks/draft-zero@2a461518985e3b2bb393bddac4649e217f8fddfe:src/draftzero/gameplay/graph_net.py (MageZero's NetGraph, WillWroble/MageZero@de225045c82a2ddc107b99ac833e21ab6d566279, graph-encoder)",
92
+ "encoder": "WillWroble/mage@e4afc9c77ba7e4a6dbc24966cbdc6dc0819e0bff (graph-encoder) Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/encoder, vendored at danieljbrooks/draft-zero@2a461518985e3b2bb393bddac4649e217f8fddfe:java/mzbridge/src/org/draftzero/mzbridge/graph",
93
+ "engine": "danieljbrooks/mage@48e4918413392523a5f9d7915e1f33941ddea21e (v0.2-generalist)",
94
+ "search": "danieljbrooks/draft-zero@2a461518985e3b2bb393bddac4649e217f8fddfe:java/mzbridge/src/mage/player/ai (BenchPlayer, BenchSearch, GraphNet, GraphMCTSPlayer)"
95
+ }
96
+ }
coverage.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "replay_priority": {
3
+ "rows": 269820,
4
+ "leaf_occurrences": 38247895,
5
+ "leaf_occurrences_in_vocab": 1.0,
6
+ "distinct_leaf_ids": 1777,
7
+ "distinct_leaf_ids_in_vocab": 0.99831,
8
+ "edge_labels_in_vocab": 1.0
9
+ },
10
+ "replay_attack": {
11
+ "rows": 69025,
12
+ "leaf_occurrences": 10927062,
13
+ "leaf_occurrences_in_vocab": 1.0,
14
+ "distinct_leaf_ids": 1437,
15
+ "distinct_leaf_ids_in_vocab": 1.0,
16
+ "edge_labels_in_vocab": 1.0
17
+ },
18
+ "replay_target": {
19
+ "rows": 10336,
20
+ "leaf_occurrences": 1652432,
21
+ "leaf_occurrences_in_vocab": 1.0,
22
+ "distinct_leaf_ids": 1652,
23
+ "distinct_leaf_ids_in_vocab": 0.99879,
24
+ "edge_labels_in_vocab": 1.0
25
+ },
26
+ "block": {
27
+ "rows": 17106,
28
+ "leaf_occurrences": 2581374,
29
+ "leaf_occurrences_in_vocab": 0.99337,
30
+ "distinct_leaf_ids": 1598,
31
+ "distinct_leaf_ids_in_vocab": 0.88924,
32
+ "edge_labels_in_vocab": 1.0
33
+ }
34
+ }
gnn_release.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load and run DraftZero's FDN graph network from its public release, without pickles or MageZero.
2
+
3
+ The release (built by export_gnn_release.py) holds:
4
+
5
+ model.safetensors the network's weights, float32
6
+ config.json the architecture (`arch`) and the encoder's constants
7
+ vocab.json leaf_ids and edge_ids: the raw feature ids that have embedding rows, row order
8
+ graph_net.py the network (a copy of draft-zero's src/draftzero/gameplay/graph_net.py)
9
+ goldens.jsonl.gz encoded test states with this network's outputs, for checking a port
10
+
11
+ A state is the graph encoder's output in MageZero's graph-server layout (one dict per state):
12
+
13
+ indices [nodes] raw feature id per node: StateEncoder.indexFor(hash64(name)); typed nodes
14
+ are the hash of their type name, the root is 0
15
+ values [nodes] numeric value per node (0 for non-numeric nodes)
16
+ edge_child [edges] node index, local to the state
17
+ edge_parent [edges] node index, local to the state
18
+ edge_label [edges] raw hashed edge label
19
+
20
+ python gnn_release.py check <release dir> # run the goldens, print the largest differences
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import gzip
25
+ import json
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ import numpy as np
30
+ import torch
31
+
32
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
33
+ try:
34
+ import graph_net as gn # the copy shipped beside this file in the release
35
+ except ImportError: # in draft-zero's tree
36
+ from draftzero.gameplay import graph_net as gn
37
+
38
+ # MageZero ActionType ordinals (graph_type): which head scores a decision's options
39
+ PRIORITY, CHOOSE_TARGET, CHOOSE_USE = 0, 3, 5
40
+
41
+
42
+ class Vocab:
43
+ """Raw feature id -> embedding row (-1 outside the vocab)."""
44
+
45
+ def __init__(self, ids):
46
+ self.ids = np.asarray(ids, np.int64)
47
+ self._order = np.argsort(self.ids, kind="stable")
48
+ self._sorted = self.ids[self._order]
49
+
50
+ def __len__(self) -> int:
51
+ return len(self.ids)
52
+
53
+ def lookup(self, ids) -> np.ndarray:
54
+ ids = np.asarray(ids, np.int64)
55
+ pos = np.minimum(np.searchsorted(self._sorted, ids), len(self.ids) - 1)
56
+ return np.where(self._sorted[pos] == ids, self._order[pos], -1)
57
+
58
+
59
+ def load(release_dir, device: str = "cpu"):
60
+ """(model in eval mode, leaf vocab, edge vocab, config) from a release directory."""
61
+ from safetensors.torch import load_file
62
+ d = Path(release_dir)
63
+ cfg = json.loads((d / "config.json").read_text())
64
+ vocab = json.loads((d / "vocab.json").read_text())
65
+ leaves, edges = Vocab(vocab["leaf_ids"]), Vocab(vocab["edge_ids"])
66
+ model = gn.NetGraph(len(leaves), len(edges), cfg["arch"])
67
+ model.load_state_dict(load_file(str(d / "model.safetensors")))
68
+ return model.to(device).eval(), leaves, edges, cfg
69
+
70
+
71
+ def to_graphs(states: list[dict], leaves: Vocab, edges: Vocab) -> gn.Graphs:
72
+ """A batch of encoded states -> the network's input (as graph_server.py maps them)."""
73
+ parts = {k: [] for k in ("type", "row", "value", "child", "parent", "label")}
74
+ offsets, base = [0], 0
75
+ for s in states:
76
+ ids = np.asarray(s["indices"], np.int64)
77
+ types = gn.node_types(ids)
78
+ parts["type"].append(types.astype(np.int64))
79
+ parts["row"].append(np.where(types == gn.NodeType.LEAF, leaves.lookup(ids), -1))
80
+ parts["value"].append(np.asarray(s["values"], np.int64))
81
+ parts["child"].append(np.asarray(s["edge_child"], np.int64) + base)
82
+ parts["parent"].append(np.asarray(s["edge_parent"], np.int64) + base)
83
+ parts["label"].append(edges.lookup(s["edge_label"]) + 1)
84
+ base += len(ids)
85
+ offsets.append(base)
86
+ t = {k: torch.from_numpy(np.concatenate(v).astype(np.int64)) for k, v in parts.items()}
87
+ return gn.Graphs(t["type"], t["row"], t["value"], torch.tensor(offsets), t["child"], t["parent"], t["label"])
88
+
89
+
90
+ @torch.no_grad()
91
+ def evaluate(model, states: list[dict], leaves: Vocab, edges: Vocab) -> list[dict]:
92
+ """Per state: priority and target (one score per node, -inf at leaves), use [no, yes], value, value_x."""
93
+ g = to_graphs(states, leaves, edges)
94
+ out = model(g)
95
+ res = []
96
+ for i in range(len(states)):
97
+ a, b = int(g.node_offsets[i]), int(g.node_offsets[i + 1])
98
+ res.append({"priority": out.priority[a:b].numpy(), "target": out.target[a:b].numpy(),
99
+ "use": out.use[i].numpy(), "value": float(out.value[i]), "value_x": float(out.value_x[i])})
100
+ return res
101
+
102
+
103
+ def option_logits(res: dict, graph_type: int, options: list[list[int]]) -> np.ndarray:
104
+ """One logit per legal option. An option is a list of node indices (one ability node per copy of a
105
+ card for a play, Pass's node, a target's node, Stop Choosing's node; "attack with X?" is a target
106
+ choice between Stop Choosing and the defending player). Its logit is the log-sum-exp of its nodes'
107
+ scores under the head the decision reads: priority at PRIORITY, target otherwise, and the use head's
108
+ [no, yes] at a CHOOSE_USE question that isn't an attack."""
109
+ if graph_type == CHOOSE_USE:
110
+ return np.asarray(res["use"][:len(options)], np.float64)
111
+ scores = res["priority"] if graph_type == PRIORITY else res["target"]
112
+ out = []
113
+ for nodes in options:
114
+ x = scores[np.asarray(nodes, np.int64)].astype(np.float64)
115
+ m = x.max()
116
+ out.append(m + np.log(np.exp(x - m).sum()) if np.isfinite(m) else -np.inf)
117
+ return np.asarray(out)
118
+
119
+
120
+ def read_goldens(path) -> list[dict]:
121
+ with gzip.open(path, "rt") as f:
122
+ return [json.loads(line) for line in f]
123
+
124
+
125
+ def check(release_dir, batch: int = 16) -> dict:
126
+ """Run the release's goldens through this loader and report the largest differences."""
127
+ d = Path(release_dir)
128
+ model, leaves, edges, _ = load(d)
129
+ goldens = read_goldens(d / "goldens.jsonl.gz")
130
+ worst = {"option_logits": 0.0, "value_x": 0.0, "use": 0.0}
131
+ argmax_agree = 0
132
+ for a in range(0, len(goldens), batch):
133
+ chunk = goldens[a:a + batch]
134
+ for g, r in zip(chunk, evaluate(model, [g["state"] for g in chunk], leaves, edges)):
135
+ want = g["expected"]
136
+ got = option_logits(r, g["graph_type"], g["options"])
137
+ worst["option_logits"] = max(worst["option_logits"], float(np.max(np.abs(got - want["option_logits"]))))
138
+ worst["value_x"] = max(worst["value_x"], abs(r["value_x"] - want["value_x"]))
139
+ worst["use"] = max(worst["use"], float(np.max(np.abs(r["use"] - want["use"]))))
140
+ argmax_agree += int(np.argmax(got) == np.argmax(want["option_logits"]))
141
+ return {"states": len(goldens), "max_abs_diff": worst, "argmax_agree": argmax_agree}
142
+
143
+
144
+ if __name__ == "__main__":
145
+ if len(sys.argv) != 3 or sys.argv[1] != "check":
146
+ sys.exit(__doc__)
147
+ print(json.dumps(check(sys.argv[2]), indent=1))
goldens.jsonl.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:46d5949bb97528a1e0ea02ae255c22287fcc49d84a77feef778f2e1eb08318c8
3
+ size 542894
graph_net.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MageZero's graph network (Will Wroble, WillWroble/MageZero branch graph-encoder), for DraftZero's
2
+ imitation training and search (docs/022).
3
+
4
+ The network is MageZero's `NetGraph` (src/magezero/model.py at de225045, 2026-10-04), copied here
5
+ rather than imported: that branch's modules import each other as top-level names (`from vocab import
6
+ ...`), and DraftZero pins MageZero v0.2 for the flat networks. The code below is the upstream model with
7
+ four changes, none of which changes what a default network computes:
8
+
9
+ * its sizes are constructor arguments (`arch`), so a sweep can vary them; the defaults are
10
+ upstream's constants, and a default network's state dict is upstream's, so it loads in
11
+ MageZero's own graph server (`load_checkpoint` there);
12
+ * `leaf_dropout`: drop a share of leaf edges while training, the graph analogue of the flat
13
+ networks' token dropout (0 = upstream);
14
+ * `local_depth`: LocalLayers per type within each pass (1 = upstream). Each extra layer re-attends
15
+ from the node's updated embedding to the same children; extra layers live in `local_extra`, so a
16
+ depth-1 network's state dict is upstream's;
17
+ * `forward` also returns the value head's input to its Tanh (`value_x`), which the imitation
18
+ trainer's cross-entropy needs, as supervised.value_logit does for the flat networks.
19
+
20
+ Input: the graph encoder's state graph (graph_tables), nodes typed ROOT, PLAYER, ZONE, STACK_OBJECT,
21
+ PERMANENT, CARD, ABILITY or LEAF, edges child -> parent with a label:
22
+
23
+ embeddings: typed node = type embedding; leaf = leaf vocab row + numeric value bucket
24
+ local layers: `passes` bottom-up passes, each `local_depth` LocalLayers per type, run in order
25
+ ABILITY -> CARD -> PERMANENT -> STACK_OBJECT -> ZONE -> PLAYER -> ROOT
26
+ every node attends over [itself, children + edge label embedding]
27
+ global layers: TransformerEncoder over [CLS, the state's internal nodes]
28
+ ├── MLP per node priority (read at ABILITY nodes: a play is its ability's node; Pass has one)
29
+ ├── MLP per node target (read at CARD / PERMANENT / STACK_OBJECT / PLAYER nodes; Stop Choosing is a CARD)
30
+ ├── MLP on CLS choose_use (false, true)
31
+ └── MLP on CLS value (tanh, -1..1)
32
+ """
33
+ from __future__ import annotations
34
+
35
+ import math
36
+ from enum import IntEnum
37
+ from typing import NamedTuple
38
+
39
+ import numpy as np
40
+ import torch
41
+ import torch.nn.functional as F
42
+ from torch import nn
43
+
44
+ UPSTREAM = "WillWroble/MageZero@de225045c82a2ddc107b99ac833e21ab6d566279 (graph-encoder)"
45
+ GLOBAL_MAX = 2 ** 31 - 1
46
+
47
+ # upstream's constants (model.py): the default architecture
48
+ ARCH_DEFAULT = {"type": "graph", "d_model": 512, "heads": 4, "ff": 1024, "dropout": 0.25, "passes": 2,
49
+ "global_layers": 2, "head_hidden": 256, "leaf_dropout": 0.0, "local_depth": 1}
50
+
51
+ # numeric value buckets: exact -5..20 (P/T, counters, mana, costs, small counts), then 21-25, 26-30,
52
+ # 31-40, 41-60, 61+ (life totals, library and zone sizes); everything below -5 is one bucket
53
+ VALUE_BOUNDS = list(range(-5, 22)) + [26, 31, 41, 61]
54
+
55
+
56
+ class NodeType(IntEnum):
57
+ """Java FeatureGraph.Node.Type, plus the root."""
58
+ ROOT = 0
59
+ PLAYER = 1
60
+ ZONE = 2
61
+ STACK_OBJECT = 3
62
+ PERMANENT = 4
63
+ CARD = 5
64
+ ABILITY = 6
65
+ LEAF = 7
66
+
67
+
68
+ STAGES = (NodeType.ABILITY, NodeType.CARD, NodeType.PERMANENT, NodeType.STACK_OBJECT,
69
+ NodeType.ZONE, NodeType.PLAYER, NodeType.ROOT)
70
+ PRIORITY_TYPES = (NodeType.ABILITY,)
71
+ TARGET_TYPES = (NodeType.CARD, NodeType.PERMANENT, NodeType.STACK_OBJECT, NodeType.PLAYER)
72
+
73
+
74
+ # ------------------------------------------------------------------------------------------------
75
+ # ids: the Java encoder's hash (upstream vocab.py), to tell typed nodes from leaves
76
+ # ------------------------------------------------------------------------------------------------
77
+
78
+ _MASK = (1 << 64) - 1
79
+ _GOLDEN = 0x9E3779B185EBCA87
80
+
81
+
82
+ def _mix64(z: int) -> int:
83
+ z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & _MASK
84
+ z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & _MASK
85
+ return z ^ (z >> 31)
86
+
87
+
88
+ def feature_id(name: str) -> int:
89
+ """The id XMage stores for a leaf name or edge label: StateEncoder.indexFor(hash64(name))."""
90
+ data = name.encode("utf-8")
91
+ h = _mix64(_GOLDEN ^ (len(data) * _GOLDEN & _MASK))
92
+ full = len(data) // 8 * 8
93
+ for i in range(0, full, 8):
94
+ h ^= _mix64(int.from_bytes(data[i:i + 8], "little"))
95
+ h = (((h << 27 | h >> 37) & _MASK) * _GOLDEN + 0x165667B19E3779F9) & _MASK
96
+ h ^= _mix64(int.from_bytes(data[full:], "little"))
97
+ h = ((h ^ (h >> 33)) * 0xFF51AFD7ED558CCD) & _MASK
98
+ h = ((h ^ (h >> 33)) * 0xC4CEB9FE1A85EC53) & _MASK
99
+ h ^= h >> 33
100
+ if h == 1 << 63: # Long.MIN_VALUE: Java's -h overflows and the remainder keeps the sign
101
+ return -((1 << 63) % GLOBAL_MAX)
102
+ return (h if h < 1 << 63 else (1 << 64) - h) % GLOBAL_MAX
103
+
104
+
105
+ # a typed node's id is the hash of its type name and the root's id is 0; every other node is a leaf
106
+ TYPE_IDS = {0: NodeType.ROOT}
107
+ for _t in NodeType:
108
+ if _t not in (NodeType.ROOT, NodeType.LEAF):
109
+ TYPE_IDS[feature_id(_t.name)] = _t
110
+
111
+
112
+ def node_types(ids: np.ndarray) -> np.ndarray:
113
+ types = np.full(ids.shape, NodeType.LEAF, dtype=np.int8)
114
+ for type_id, t in TYPE_IDS.items():
115
+ types[ids == type_id] = t
116
+ return types
117
+
118
+
119
+ def full_arch(arch: dict | None) -> dict:
120
+ a = {**ARCH_DEFAULT, **(arch or {})}
121
+ unknown = set(a) - set(ARCH_DEFAULT)
122
+ if unknown:
123
+ raise ValueError(f"unknown graph arch keys {sorted(unknown)} (known: {sorted(ARCH_DEFAULT)})")
124
+ if a["type"] != "graph":
125
+ raise ValueError(f"arch.type {a['type']!r}: graph_net builds only 'graph'")
126
+ if a["d_model"] % a["heads"]:
127
+ raise ValueError(f"d_model {a['d_model']} is not a multiple of heads {a['heads']}")
128
+ return a
129
+
130
+
131
+ def upstream_loadable(arch: dict) -> bool:
132
+ """A network MageZero's own graph server loads as it is (upstream's constants)."""
133
+ a = full_arch(arch)
134
+ return all(a[k] == ARCH_DEFAULT[k] for k in ("d_model", "heads", "ff", "passes", "global_layers", "head_hidden",
135
+ "local_depth"))
136
+
137
+
138
+ # ------------------------------------------------------------------------------------------------
139
+ # the network
140
+ # ------------------------------------------------------------------------------------------------
141
+
142
+ class Graphs(NamedTuple):
143
+ """A batch of state graphs, concatenated: the network's input."""
144
+ node_type: torch.Tensor # [N] NodeType
145
+ node_row: torch.Tensor # [N] leaf vocab row; -1 for typed nodes and leaves outside the vocab
146
+ node_value: torch.Tensor # [N] numeric value (0 for non-numeric)
147
+ node_offsets: torch.Tensor # [B+1] first node of each state
148
+ edge_child: torch.Tensor # [E] node index (batch-wide)
149
+ edge_parent: torch.Tensor # [E] node index (batch-wide)
150
+ edge_label: torch.Tensor # [E] edge vocab row + 1; 0 for labels outside the vocab
151
+
152
+ def to(self, device):
153
+ return Graphs(*(t.to(device, non_blocking=True) for t in self))
154
+
155
+
156
+ class Out(NamedTuple):
157
+ priority: torch.Tensor # [N] per-node priority score (-inf at leaves)
158
+ target: torch.Tensor # [N] per-node target score (-inf at leaves)
159
+ use: torch.Tensor # [B, 2] choose_use logits (false, true)
160
+ value: torch.Tensor # [B] tanh(value_x)
161
+ value_x: torch.Tensor # [B] the value head's input to its Tanh
162
+
163
+
164
+ def graph_index(offsets: torch.Tensor) -> torch.Tensor:
165
+ """State of each node, from a batch's node offsets."""
166
+ return torch.repeat_interleave(torch.arange(offsets.numel() - 1, device=offsets.device), offsets.diff())
167
+
168
+
169
+ def segment_logsumexp(x: torch.Tensor, seg: torch.Tensor, n: int) -> torch.Tensor:
170
+ """Log-sum-exp over the rows of x that share an index in seg (values 0..n-1); returns n rows
171
+ (-inf for an index with no row)."""
172
+ index = seg.view(-1, *([1] * (x.dim() - 1))).expand_as(x)
173
+ with torch.no_grad(): # the max only stabilizes; the result and its gradient don't depend on it
174
+ m = x.new_full((n, *x.shape[1:]), float("-inf")).scatter_reduce(0, index, x, "amax")
175
+ m = torch.where(torch.isfinite(m), m, torch.zeros_like(m))
176
+ return m + x.new_zeros((n, *x.shape[1:])).index_add(0, seg, (x - m[seg]).exp()).log()
177
+
178
+
179
+ class LocalLayer(nn.TransformerEncoderLayer):
180
+ """A pre-norm transformer layer run over each node's [node, children...] sequence, keeping only
181
+ the node's output token (upstream's LocalLayer)."""
182
+
183
+ def __init__(self, d_model: int, heads: int, ff: int, dropout: float):
184
+ super().__init__(d_model, heads, ff, dropout, activation="gelu", batch_first=True, norm_first=True)
185
+
186
+ def forward(self, node: torch.Tensor, child: torch.Tensor, seg: torch.Tensor) -> torch.Tensor:
187
+ """node: [S, d] this stage's nodes; child: [E, d] child tokens (child embedding + edge label
188
+ embedding); seg: [E] position in `node` of each child's parent. Returns the nodes' new embeddings."""
189
+ S, d = node.shape
190
+ H, dh = self.self_attn.num_heads, self.self_attn.head_dim
191
+ w, b = self.self_attn.in_proj_weight, self.self_attn.in_proj_bias
192
+ q, k, v = F.linear(self.norm1(node), w, b).chunk(3, dim=-1)
193
+ k_child, v_child = F.linear(self.norm1(child), w[d:], b[d:]).chunk(2, dim=-1)
194
+ seg = torch.cat([torch.arange(S, device=seg.device), seg]) # each node also attends to itself
195
+ k = torch.cat([k, k_child]).view(-1, H, dh)
196
+ v = torch.cat([v, v_child]).view(-1, H, dh)
197
+ score = (q.reshape(S, H, dh)[seg] * k).sum(-1).float() / math.sqrt(dh) # [S+E, H]
198
+ attn = (score - segment_logsumexp(score, seg, S)[seg]).exp()
199
+ attn = F.dropout(attn, self.self_attn.dropout, self.training)
200
+ out = torch.zeros(S, H, dh, device=node.device).index_add_(
201
+ 0, seg, (attn.to(v.dtype).unsqueeze(-1) * v).float())
202
+ node = node + self.dropout1(self.self_attn.out_proj(out.view(S, d).to(node.dtype)))
203
+ return node + self._ff_block(self.norm2(node))
204
+
205
+
206
+ def _mlp(d: int, hidden: int, out_dim: int, *tail: nn.Module) -> nn.Sequential:
207
+ return nn.Sequential(nn.Linear(d, hidden), nn.ReLU(), nn.Linear(hidden, out_dim), *tail)
208
+
209
+
210
+ class NetGraph(nn.Module):
211
+ def __init__(self, num_embeddings: int = 0, num_edge_labels: int = 0, arch: dict | None = None):
212
+ super().__init__()
213
+ a = self.arch = full_arch(arch)
214
+ d = a["d_model"]
215
+ self.embedding = nn.Embedding(num_embeddings, d) # leaves
216
+ self.edge_embedding = nn.Embedding(num_edge_labels + 1, d, padding_idx=0) # row 0: unknown label
217
+ self.type_embedding = nn.Embedding(len(NodeType), d)
218
+ self.value_embedding = nn.Embedding(len(VALUE_BOUNDS) + 1, d)
219
+ self.register_buffer("value_bounds", torch.tensor(VALUE_BOUNDS), persistent=False)
220
+ self.local_layers = nn.ModuleList(
221
+ nn.ModuleDict({t.name: LocalLayer(d, a["heads"], a["ff"], a["dropout"]) for t in STAGES})
222
+ for _ in range(a["passes"]))
223
+ if a["local_depth"] < 1:
224
+ raise ValueError(f"local_depth {a['local_depth']}: at least 1")
225
+ if a["local_depth"] > 1: # the layers after each type's first, per pass (absent at depth 1: upstream's layout)
226
+ self.local_extra = nn.ModuleList(
227
+ nn.ModuleDict({t.name: nn.ModuleList(LocalLayer(d, a["heads"], a["ff"], a["dropout"])
228
+ for _ in range(a["local_depth"] - 1)) for t in STAGES})
229
+ for _ in range(a["passes"]))
230
+ self.cls = nn.Parameter(torch.randn(d))
231
+ global_layer = nn.TransformerEncoderLayer(d, a["heads"], a["ff"], a["dropout"], activation="gelu",
232
+ batch_first=True, norm_first=True)
233
+ self.global_layers = nn.TransformerEncoder(global_layer, a["global_layers"], norm=nn.LayerNorm(d),
234
+ enable_nested_tensor=False)
235
+ h = a["head_hidden"]
236
+ self.priority_head = _mlp(d, h, 1)
237
+ self.target_head = _mlp(d, h, 1)
238
+ self.binary_head = _mlp(d, h, 2)
239
+ self.value_head = _mlp(d, h, 1, nn.Tanh())
240
+
241
+ def forward(self, g: Graphs) -> Out:
242
+ B, N = g.node_offsets.numel() - 1, g.node_type.numel()
243
+
244
+ # initial embeddings: typed nodes their type, leaves their name and numeric value
245
+ leaves = (g.node_row >= 0).nonzero().squeeze(1)
246
+ value = torch.bucketize(g.node_value[leaves], self.value_bounds, right=True)
247
+ h = self.type_embedding(g.node_type).index_add(
248
+ 0, leaves, self.embedding(g.node_row[leaves]) + self.value_embedding(value))
249
+
250
+ # leaves outside the vocab have no embedding, so their edges are dropped (and, while
251
+ # training, a leaf_dropout share of the others)
252
+ child_leaf = g.node_type[g.edge_child] == NodeType.LEAF
253
+ keep = ~child_leaf | (g.node_row[g.edge_child] >= 0)
254
+ if self.training and self.arch["leaf_dropout"] > 0:
255
+ keep = keep & ~(child_leaf & (torch.rand(keep.shape, device=keep.device) < self.arch["leaf_dropout"]))
256
+ child, parent, label = g.edge_child[keep], g.edge_parent[keep], g.edge_label[keep]
257
+
258
+ # one stage per type: every node of that type, its children, and each child's parent position
259
+ parent_type = g.node_type[parent]
260
+ stages = []
261
+ for t in STAGES:
262
+ nodes = (g.node_type == t).nonzero().squeeze(1)
263
+ if len(nodes):
264
+ edges = (parent_type == t).nonzero().squeeze(1)
265
+ stages.append((t.name, nodes, child[edges], label[edges], torch.searchsorted(nodes, parent[edges])))
266
+
267
+ # local layers, bottom-up passes: children earlier in the order arrive updated in this pass,
268
+ # children of the same type or later in the order (attachments, targets, linked exile) from the last one
269
+ extra = getattr(self, "local_extra", None)
270
+ for p, layers in enumerate(self.local_layers):
271
+ for name, nodes, c, lbl, seg in stages:
272
+ kids = h[c] + self.edge_embedding(lbl)
273
+ x = layers[name](h[nodes], kids, seg).to(h.dtype)
274
+ for layer in (extra[p][name] if extra is not None else ()):
275
+ x = layer(x, kids, seg).to(h.dtype)
276
+ h = h.index_copy(0, nodes, x)
277
+
278
+ # global layers: self-attention over [CLS, internal nodes], one padded sequence per state
279
+ internal = (g.node_type != NodeType.LEAF).nonzero().squeeze(1)
280
+ gi = graph_index(g.node_offsets)[internal]
281
+ counts = torch.bincount(gi, minlength=B)
282
+ pos = torch.arange(len(internal), device=h.device) - (counts.cumsum(0) - counts)[gi] + 1
283
+ x = h.new_zeros(B, int(counts.max()) + 1, h.shape[1])
284
+ x[:, 0] = self.cls.to(h.dtype)
285
+ x[gi, pos] = h[internal]
286
+ pad = torch.arange(x.shape[1], device=h.device) > counts.unsqueeze(1)
287
+ x = self.global_layers(x, src_key_padding_mask=pad)
288
+ cls, z = x[:, 0], x[gi, pos]
289
+
290
+ def per_node(head):
291
+ return torch.full((N,), float("-inf"), device=h.device).index_copy(
292
+ 0, internal, head(z).squeeze(-1).float())
293
+
294
+ vx = self.value_head[:-1](cls).squeeze(-1).float()
295
+ return Out(per_node(self.priority_head), per_node(self.target_head), self.binary_head(cls).float(),
296
+ torch.tanh(vx), vx)
297
+
298
+
299
+ def parameter_counts(model: NetGraph) -> dict:
300
+ out = {"total": 0}
301
+ for name, p in model.named_parameters():
302
+ part = name.split(".")[0]
303
+ out[part] = out.get(part, 0) + p.numel()
304
+ out["total"] += p.numel()
305
+ return out
306
+
307
+
308
+ # ------------------------------------------------------------------------------------------------
309
+ # batches: graph rows -> the network's input, and options -> logits
310
+ # ------------------------------------------------------------------------------------------------
311
+
312
+ def gather(ptr: np.ndarray, sel: np.ndarray, *arrays: np.ndarray) -> tuple:
313
+ """The CSR slices of rows `sel` (ptr over `arrays`), concatenated, and their new ptr."""
314
+ lens = (ptr[sel + 1] - ptr[sel]).astype(np.int64)
315
+ new = np.zeros(len(sel) + 1, np.int64)
316
+ np.cumsum(lens, out=new[1:])
317
+ pos = np.repeat(ptr[sel] - new[:-1], lens) + np.arange(new[-1])
318
+ return (new,) + tuple(a[pos] for a in arrays)
319
+
320
+
321
+ class OptionBatch(NamedTuple):
322
+ """Per option of a batch: its row, its nodes (batch-wide) and which node belongs to which option."""
323
+ opt_row: torch.Tensor # [O] row of each option
324
+ node: torch.Tensor # [K] batch-wide node index of each option node
325
+ node_opt: torch.Tensor # [K] option of each option node
326
+ n_opt: int
327
+
328
+ def to(self, device):
329
+ return OptionBatch(self.opt_row.to(device), self.node.to(device), self.node_opt.to(device), self.n_opt)
330
+
331
+
332
+ def option_logits(out: Out, ob: OptionBatch, graph_type: torch.Tensor, opt_index: torch.Tensor) -> torch.Tensor:
333
+ """[O] logit of each option: log-sum-exp of its nodes' scores under the head its row's decision type
334
+ reads (priority at PRIORITY rows, target at CHOOSE_TARGET rows; two copies of a card are one option,
335
+ so their probabilities add), or the choose_use logit at CHOOSE_USE rows (options [no, yes]).
336
+ opt_index: [O] each option's position within its row."""
337
+ gt = graph_type[ob.opt_row[ob.node_opt]]
338
+ s = torch.where(gt == 0, out.priority[ob.node], out.target[ob.node])
339
+ lg = segment_logsumexp(s.float(), ob.node_opt, ob.n_opt)
340
+ is_use = graph_type[ob.opt_row] == 5
341
+ if is_use.any():
342
+ u = out.use.float()[ob.opt_row, opt_index.clamp(max=1)]
343
+ lg = torch.where(is_use, u, lg)
344
+ return lg
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d5ee8323ccca11f18b1667bc2312407d9d5696b6dacf7243d9a30461cdc29b56
3
+ size 53671100
vocab.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"leaf_ids": [1440341, 1962182, 2208587, 2544383, 5626501, 7400809, 7847295, 9608361, 9942472, 15576439, 15842078, 18336802, 19294824, 19611187, 20442916, 21647753, 22212785, 24715442, 26348976, 26411638, 26576046, 26743471, 27123869, 27201461, 27617022, 29261465, 31021900, 31456499, 34398885, 35827476, 36016871, 36857849, 38420094, 39834060, 40892126, 41801327, 42434614, 42874147, 43674225, 43896700, 44824265, 46121060, 46841098, 47702099, 47708227, 47728856, 47894163, 48545723, 49433952, 50136961, 50414051, 51634314, 51930769, 56219660, 56381963, 56477097, 56634758, 56751205, 57069400, 58235508, 58674378, 60817109, 61441050, 61802711, 63336643, 64790511, 69053869, 69243589, 70143062, 70529767, 71579209, 71928681, 72522655, 75342627, 75445463, 75559423, 77799283, 79068143, 79921356, 80987291, 86889390, 87211654, 87978658, 89368553, 89403159, 89937610, 90996423, 91411481, 92775046, 95226462, 95954591, 96456902, 96502244, 97814345, 98901711, 99301007, 99427890, 99812325, 102244748, 104062034, 105259026, 105552121, 111543627, 112984472, 113987043, 114727717, 115078649, 115640700, 116292132, 116437944, 117570743, 117946022, 118083407, 120900771, 122396342, 123866549, 124716521, 127557047, 129540009, 131807887, 132043099, 134216449, 135814834, 139376906, 141362100, 142245513, 142610748, 142628996, 143242829, 143763009, 145314718, 148331029, 149397475, 149517531, 149548185, 150543704, 151915608, 152071582, 152483195, 152569543, 152770126, 152904535, 154783486, 154806888, 155153733, 155420588, 156860798, 157849559, 158177950, 161769807, 162813817, 164493930, 169656228, 170691259, 172015683, 176159025, 176543331, 177240602, 178856769, 179101397, 180508954, 181279754, 181540797, 182904966, 185769792, 188941692, 190278963, 192248464, 193465718, 194711281, 197231207, 198997643, 200515638, 200586786, 200587528, 201348254, 202332436, 202444000, 203409592, 205091846, 205231537, 205621798, 205788260, 208075154, 208422199, 209187028, 209303386, 210222412, 210313912, 210942411, 211631465, 212671933, 212925767, 214792765, 216526367, 217014610, 218205224, 219944482, 220209414, 222737121, 223570041, 224799333, 225929782, 226172277, 228001398, 229257030, 231214138, 231550675, 232638495, 233567658, 235177798, 235697470, 236109572, 236634927, 239024287, 239439331, 240188622, 240557881, 241885934, 242019349, 242560623, 244565090, 244682578, 245215842, 246314782, 246903153, 248221686, 248690035, 250401928, 250937488, 251051695, 253304701, 254778635, 255124921, 255905666, 256539945, 257229202, 258218267, 260055758, 262367082, 262583584, 262927112, 263583980, 263914887, 264131501, 264698822, 264905349, 265748383, 267819643, 268263696, 268272175, 268416926, 268614795, 269446478, 269633968, 270264912, 270806140, 272173153, 274335623, 275058553, 276271436, 276566837, 277872022, 279097986, 280175250, 281002777, 281471535, 282936815, 283277349, 283616657, 283743219, 286192078, 286208123, 286340658, 290754915, 292146107, 293302369, 294672588, 294916352, 295689009, 295949540, 298236303, 299170348, 299233256, 299339413, 302183216, 302266203, 302958216, 303007393, 303217747, 303391387, 306140224, 306767986, 308226169, 309375254, 310049329, 310610276, 311520533, 313569312, 313950034, 314919095, 315670052, 316271288, 317001748, 317097845, 317737238, 320282077, 321235475, 323085886, 324275269, 324864251, 326607987, 327564059, 327822642, 328615012, 332335403, 333119429, 334254253, 334487786, 335162619, 336976093, 337465183, 337887673, 338750121, 338831475, 340259405, 341214915, 342749688, 343360971, 344532036, 347907674, 349400708, 349614487, 350218938, 350592763, 351140735, 352071770, 352146621, 352296083, 354605954, 354871153, 358447958, 360500561, 361306221, 362223846, 362512325, 364656144, 364903885, 365100823, 369274168, 369720408, 371393579, 372210837, 373177303, 373264905, 373374922, 373811555, 374238703, 374644088, 375345723, 378167955, 378758831, 380729067, 380902880, 381106181, 381724562, 382520221, 385110298, 388170884, 391305087, 392355780, 392373278, 392967445, 393183340, 394362358, 397813009, 397842519, 397913634, 398208133, 398524568, 399579108, 401688395, 403468142, 404759524, 404989165, 405074863, 406177337, 407310688, 408598499, 409209600, 411672151, 411798609, 413331273, 413817868, 414289503, 414668887, 414715689, 417483125, 419772856, 422838138, 425039099, 425908922, 426752089, 427289935, 427730915, 427749997, 428553857, 430172399, 430599117, 431810040, 432262754, 432393266, 432622045, 434545204, 436004689, 436628072, 437371816, 438489751, 439353963, 439766774, 440191805, 440560406, 441820716, 442005403, 442272731, 443355591, 444786919, 449007826, 449086460, 452011879, 452978675, 454117479, 454199117, 454818615, 455776412, 455871989, 456110902, 456389764, 457058308, 457207635, 457831743, 458886651, 461156297, 462337250, 463252858, 465051482, 467712254, 468184418, 468916271, 469041193, 471044639, 472141738, 472247022, 473244528, 475514177, 475857585, 475872756, 476053588, 477822875, 479627258, 480192604, 480286359, 484513670, 485092985, 488515436, 489863666, 491403483, 492745338, 494740336, 495781028, 497505774, 498628461, 500979731, 501479600, 501860663, 502725437, 504579951, 505791077, 507729751, 508411864, 511302454, 511585387, 511774120, 512297293, 514658547, 517542001, 518411903, 519018980, 519662460, 521059290, 524283637, 524310728, 525563645, 526432400, 526575519, 527724311, 530130816, 531148739, 531861041, 532132901, 532153805, 532155808, 535196011, 535543957, 538741739, 540059387, 542985218, 545037260, 547726523, 548482694, 548633120, 549278577, 550648132, 550755172, 554801475, 554999823, 555782980, 559073695, 560812410, 562430808, 563178319, 567113238, 567608014, 568176078, 570146786, 572458796, 574075174, 574232134, 574570132, 576528642, 576995363, 577345960, 577984950, 578116103, 578192564, 578327451, 579818273, 580373828, 580486195, 581487291, 582294620, 583150795, 586079310, 586782611, 591843915, 593429204, 594808990, 595753163, 596482569, 597108327, 597161745, 597621914, 598452969, 599115692, 599245905, 600355528, 602047541, 602300856, 602679994, 604769639, 605771104, 607365014, 609861612, 611004085, 611652800, 612462330, 612906220, 613884535, 617050187, 617208948, 621146309, 624706238, 625753859, 629029047, 629330354, 629521660, 631315783, 633316148, 634049231, 634192656, 635102301, 636221106, 637504013, 638162895, 639302983, 639708755, 639941244, 641350743, 642324338, 642859683, 643713223, 644207749, 644290504, 645209224, 645654806, 646268637, 646278469, 648879301, 649508587, 649947097, 651063818, 651451523, 651607466, 658183045, 658311497, 660209914, 663063215, 663503735, 663784229, 663917152, 666003473, 666181616, 667380597, 668820438, 669245941, 671155727, 671390736, 672020766, 672890283, 673162425, 675972228, 678809176, 679047906, 679880620, 681991136, 682890320, 685311367, 686027225, 688870735, 689557210, 690016366, 691113461, 691579864, 693652572, 693898931, 694217607, 697164346, 700800114, 702588243, 704705076, 705374685, 705745572, 707239051, 707422687, 708990901, 709079214, 710668801, 710843686, 711619772, 711830598, 712826141, 712987826, 713073832, 716506758, 716560146, 718332679, 719162734, 721819394, 723303765, 724184920, 724838751, 724866303, 727783171, 729839090, 731394855, 732700468, 733750371, 736534376, 736842200, 737020356, 737954394, 740702845, 741633978, 742449621, 742631720, 743828030, 744924559, 745831164, 746266618, 747538775, 749393443, 749459732, 750679424, 750962180, 751410268, 752544771, 752547265, 753339829, 754086067, 755208037, 755690624, 756813771, 758562378, 758597665, 760807551, 762770871, 763852860, 764166466, 765599598, 766232307, 766616159, 766964003, 769004324, 771139323, 772229338, 774066050, 774403490, 777502366, 779252576, 780645407, 782408165, 783233761, 783971384, 784539389, 784832823, 786146011, 786642483, 789347214, 789631674, 790482088, 790978259, 793795445, 795001832, 795269605, 795422486, 795518945, 795795670, 795970052, 795975657, 796126366, 796165132, 796793800, 797018104, 797685979, 800033900, 800301881, 802103953, 806211228, 806457699, 808580132, 809138705, 809139094, 809222994, 813567067, 813916931, 815378051, 815542409, 817127959, 817791801, 818364420, 818790138, 819365591, 821033680, 821734656, 822553484, 823337364, 824375317, 825213549, 826968191, 827170562, 828979377, 829985223, 830204431, 830808238, 831915530, 832722393, 833483646, 834489206, 834573350, 835758716, 835767629, 836053716, 836650302, 837524050, 837784352, 838373840, 839022469, 839268956, 839337520, 841117176, 844032974, 844583464, 844702554, 845783909, 846159577, 846297969, 848314086, 849044060, 849191803, 850147946, 850909901, 856024454, 856231674, 857724748, 857830313, 860119761, 863146563, 863203390, 864140418, 864385355, 864850333, 865343802, 868208821, 872258727, 872594898, 872912522, 874883667, 875149788, 875193495, 875636869, 877334315, 881516106, 881956341, 882350249, 882457993, 883008540, 883715582, 885781761, 886899836, 887479417, 887539812, 887611392, 888015031, 888345622, 891193908, 891734026, 893766352, 893786252, 901446593, 901919395, 902774501, 906665816, 907918895, 909199831, 909427960, 909668328, 911770935, 914997573, 915098839, 916846327, 919855937, 920678023, 921493867, 921631808, 921811794, 922593208, 923378710, 923878123, 924822343, 926456199, 926676452, 927409569, 927457981, 927720972, 928273724, 929067687, 931707989, 932043175, 933195534, 936590391, 936648825, 937661954, 940242667, 941213167, 941967510, 943052660, 944191473, 945728694, 946488534, 948371067, 948592947, 954687402, 956953308, 957913728, 958092476, 963066985, 963473726, 963489793, 963944376, 965064875, 965355899, 966052514, 966771154, 967717900, 968586863, 969226375, 969907499, 972483381, 973690790, 974297332, 974754870, 975265061, 975575057, 975919299, 976326008, 977875024, 978098940, 978816330, 981152716, 982541258, 984469748, 985013148, 986529310, 986841842, 987129450, 987880070, 990894996, 992240222, 993879784, 994911044, 994987088, 995132474, 995763248, 996479982, 998998723, 1002433237, 1002732121, 1003002939, 1003758255, 1004735815, 1006815371, 1008271012, 1009082291, 1010759091, 1010866558, 1012027495, 1012246236, 1016349931, 1016496912, 1021809046, 1022763761, 1024424923, 1026966006, 1028870905, 1031294989, 1031591848, 1031701667, 1032225436, 1032527342, 1033020014, 1035818762, 1036250420, 1038761862, 1041462975, 1041598386, 1041772012, 1043280365, 1043731426, 1044467862, 1044600422, 1045216541, 1045538240, 1046173029, 1047612572, 1048190424, 1051060502, 1051516614, 1051660799, 1051942730, 1052033983, 1052143011, 1052227471, 1056195217, 1061407237, 1062637749, 1063103572, 1065249099, 1065465613, 1065500702, 1072383679, 1072835681, 1073120889, 1075165126, 1075917882, 1077745239, 1078820679, 1080545818, 1082923246, 1085134962, 1085372879, 1086967859, 1090703398, 1090890249, 1091995962, 1093851430, 1096076277, 1096320962, 1097589286, 1097782627, 1098992058, 1101784046, 1101823536, 1102511984, 1102673364, 1104015502, 1105362161, 1106991229, 1107615472, 1108130868, 1108696927, 1108755083, 1110063838, 1111281526, 1111567825, 1113261272, 1114064064, 1115796582, 1118324162, 1119618306, 1120392030, 1121777847, 1122108686, 1125160637, 1127171266, 1127955552, 1128500303, 1130156138, 1132494783, 1134588121, 1137616768, 1137852415, 1138895796, 1138901551, 1139288701, 1142760525, 1143065909, 1143711313, 1144137830, 1145579697, 1146220376, 1146778074, 1147724697, 1148167324, 1149081070, 1149478102, 1150333065, 1151820202, 1153133824, 1153411793, 1153815393, 1155289733, 1155926884, 1158218063, 1159939975, 1160227108, 1161616093, 1162449478, 1162626843, 1166653598, 1166808723, 1167545182, 1173790343, 1176880987, 1177319729, 1177401913, 1178081703, 1178460016, 1180929545, 1182632794, 1183413348, 1183767101, 1190854247, 1190963813, 1191044628, 1191521919, 1191928366, 1193449544, 1194966515, 1195470234, 1196521984, 1199996132, 1201340426, 1201458248, 1201491749, 1201600676, 1203881081, 1203885987, 1204323350, 1204345493, 1205603632, 1205767567, 1206415501, 1206849870, 1209570056, 1210240132, 1210311466, 1210564714, 1211229539, 1212663909, 1212693301, 1215039279, 1215367514, 1216932940, 1217254588, 1218681705, 1219390124, 1219939353, 1220552786, 1221928229, 1222364261, 1223367703, 1224398856, 1224486184, 1225067558, 1226759343, 1226815658, 1227996381, 1228491159, 1228876010, 1232627619, 1232828157, 1233238628, 1233860440, 1237750770, 1238804999, 1239575746, 1239601424, 1240876452, 1242035149, 1242708112, 1244131853, 1245499350, 1248115155, 1248333796, 1248404031, 1249115487, 1250546917, 1251505846, 1253988378, 1254209094, 1254598873, 1255052726, 1255611452, 1256322244, 1259457107, 1260555562, 1261148613, 1261496261, 1261740561, 1261970224, 1262543592, 1265823978, 1266108865, 1266283177, 1267021923, 1267202747, 1267280238, 1269333892, 1270859407, 1275009797, 1278008730, 1278980716, 1279264161, 1279953850, 1281310502, 1286694231, 1287504847, 1288086675, 1288278799, 1289441348, 1289540664, 1289634190, 1292828424, 1295047703, 1296458253, 1298018959, 1298023021, 1298734760, 1299923408, 1300250147, 1300489328, 1300647664, 1300673505, 1301325317, 1301609321, 1305177036, 1306194388, 1306397134, 1307585817, 1309664126, 1310145507, 1312802178, 1313387814, 1313793596, 1314540606, 1317604776, 1317925607, 1318581361, 1319068114, 1319088234, 1320394979, 1324237059, 1324685147, 1326046029, 1326602522, 1327579312, 1328315112, 1328512021, 1329837058, 1330806963, 1331602191, 1331812810, 1332479050, 1333159154, 1333461274, 1335208565, 1337004176, 1337445992, 1337913581, 1337966615, 1338310815, 1339814551, 1340550775, 1341171512, 1341274615, 1341996981, 1342316165, 1343262418, 1344246097, 1347335713, 1347366700, 1347415266, 1348525055, 1348873293, 1349647218, 1351238428, 1351352044, 1354460161, 1355636464, 1358029957, 1358050940, 1359057716, 1361398034, 1361581868, 1363691382, 1366066223, 1366409913, 1368983767, 1369116271, 1373000685, 1374319827, 1374633028, 1374867353, 1376344532, 1376821117, 1377688873, 1378263271, 1380651818, 1381421847, 1381536199, 1382093708, 1382489545, 1383953299, 1386293723, 1387315513, 1389079211, 1391792941, 1391920914, 1391956311, 1394538678, 1394705997, 1396320168, 1398437567, 1399305827, 1399651887, 1401249263, 1401665883, 1401837032, 1403182846, 1404004684, 1404241694, 1404420744, 1404582499, 1404957988, 1405467409, 1405509803, 1406704459, 1407060034, 1408602148, 1410036018, 1411327229, 1414199937, 1414953882, 1415453416, 1415487825, 1416053613, 1416848329, 1418027442, 1418303697, 1421503314, 1423389434, 1424308292, 1424513543, 1424636819, 1425184804, 1430919052, 1431558667, 1433399539, 1433744916, 1435094476, 1435384586, 1436127790, 1436959901, 1436977320, 1438796667, 1438987987, 1440054325, 1441181011, 1441460597, 1441724456, 1441754421, 1442239077, 1446376790, 1446465022, 1446809330, 1447452886, 1447799608, 1447872745, 1449839197, 1451078378, 1454042798, 1456729017, 1459496244, 1460247009, 1464570357, 1467267836, 1467320682, 1468845644, 1470062109, 1473402765, 1476428281, 1476589492, 1476964227, 1478578929, 1478604633, 1479193592, 1480616940, 1483472125, 1484249143, 1485670843, 1486217091, 1487332078, 1489739048, 1491135339, 1491264596, 1491561942, 1493389329, 1493735396, 1494491276, 1494808828, 1495922554, 1498456400, 1498908739, 1500568942, 1500726125, 1504591987, 1504677084, 1505264779, 1506051407, 1507569015, 1509892176, 1510264190, 1511059194, 1511059996, 1511875421, 1516381911, 1516651427, 1517277589, 1517614333, 1519512919, 1519892322, 1522283465, 1522547874, 1523810313, 1525027814, 1525230934, 1525506943, 1526871878, 1528978576, 1530591246, 1530966476, 1534936880, 1535438454, 1536204296, 1537330116, 1543041882, 1544099811, 1544654287, 1545266126, 1545648253, 1546475677, 1546798618, 1547130225, 1547943461, 1548614769, 1551931523, 1552229373, 1553574944, 1555329312, 1556720711, 1557876860, 1558391349, 1558586455, 1560406279, 1562677455, 1563170529, 1565169958, 1565494715, 1566338322, 1567400036, 1567774493, 1568532068, 1571515843, 1571648925, 1572734123, 1573136250, 1575184414, 1575262688, 1575977463, 1576146646, 1579956476, 1580020343, 1580288482, 1580915149, 1581802262, 1582582718, 1583927102, 1584991580, 1586800640, 1587129629, 1590557331, 1592106456, 1593090703, 1593188127, 1594006767, 1596019020, 1597187323, 1598774335, 1599549281, 1600492098, 1601237027, 1602617680, 1602832197, 1603324833, 1606118479, 1606536323, 1606702150, 1606867760, 1607684655, 1607957554, 1608168143, 1608854641, 1609848545, 1610917002, 1610992972, 1611598521, 1611798727, 1612733246, 1614623469, 1615989607, 1617381482, 1619179041, 1619644828, 1620310502, 1621011674, 1621918109, 1626234254, 1626389490, 1627462075, 1627777523, 1628066570, 1628127388, 1630098533, 1634525658, 1634541406, 1634647356, 1638131297, 1641465148, 1644129858, 1644801890, 1647458725, 1648092054, 1648111691, 1648559601, 1648592634, 1652362143, 1652803292, 1653809240, 1654891439, 1655176678, 1657318634, 1659223487, 1661535554, 1662142036, 1662324311, 1664959718, 1665247084, 1667084037, 1668885651, 1669412841, 1669929121, 1673135531, 1673251338, 1674460949, 1677091148, 1678529523, 1681091965, 1681300078, 1685384760, 1686367411, 1686926556, 1687921433, 1690210857, 1690805963, 1693761823, 1694066180, 1695464938, 1696060949, 1698160230, 1700389242, 1703111592, 1703887919, 1704160815, 1709980835, 1711850431, 1712596310, 1716262237, 1718659221, 1719595689, 1720547376, 1725569201, 1726851586, 1726888158, 1728101712, 1728577316, 1730660905, 1731554446, 1732804489, 1732904379, 1734604069, 1734701661, 1735983628, 1736201125, 1737690732, 1738502424, 1738860061, 1739620528, 1740605093, 1741160447, 1744391118, 1744635442, 1745482268, 1746284504, 1747242617, 1747434857, 1748642158, 1749068263, 1749127665, 1753072895, 1753272157, 1753904629, 1754084889, 1754376498, 1754595349, 1754882594, 1755931840, 1756894885, 1756927776, 1759390534, 1760501653, 1761339889, 1762950461, 1763074197, 1763140237, 1763608259, 1764752820, 1765083413, 1765095371, 1765770498, 1769313379, 1771098816, 1772417173, 1773375808, 1773508832, 1773661387, 1774635561, 1776181032, 1778412649, 1778525763, 1779746832, 1780554180, 1780875480, 1782799145, 1782936016, 1782946817, 1783895505, 1786737988, 1788342066, 1788911643, 1789389410, 1789564114, 1790220894, 1790842572, 1791576872, 1792252280, 1793416395, 1794501672, 1795411725, 1796141387, 1797874328, 1798519828, 1799310415, 1803965875, 1804352883, 1805534332, 1808808711, 1809524919, 1809578260, 1809594952, 1810379922, 1811003112, 1811482446, 1811594385, 1813125077, 1813766187, 1814767134, 1818848995, 1825173448, 1825558173, 1825908517, 1830230144, 1830753198, 1830978969, 1832386671, 1832970557, 1835964348, 1836741861, 1836753146, 1837012607, 1838294806, 1839216853, 1839444415, 1839903956, 1840319516, 1840332604, 1841699400, 1846983136, 1847393555, 1847566665, 1847817250, 1849359196, 1849756243, 1849786295, 1852146538, 1852480575, 1854004839, 1854625842, 1856517976, 1857433120, 1860190378, 1860795251, 1861899597, 1862086822, 1862914194, 1865242116, 1866180129, 1867972240, 1868416733, 1868938547, 1869120239, 1870093990, 1870476722, 1871736803, 1872971068, 1875012172, 1875703365, 1876588807, 1877053939, 1877204025, 1880038052, 1880547027, 1881366860, 1881443182, 1882046025, 1883151525, 1883572528, 1885148302, 1885927732, 1887193370, 1887973180, 1888231902, 1888454660, 1889219445, 1889413136, 1889576626, 1890124111, 1890713437, 1891530088, 1892727780, 1893681424, 1895357763, 1897634176, 1900572196, 1904510579, 1905044589, 1906567674, 1906960588, 1908518233, 1909207500, 1910677241, 1911057345, 1911869299, 1912655534, 1913917030, 1915146792, 1915394465, 1916314863, 1917842184, 1919919724, 1920356479, 1920861661, 1921600360, 1922333472, 1924244430, 1924461904, 1927066070, 1927846999, 1930368742, 1931700064, 1938064141, 1938235053, 1938345885, 1938707714, 1940252474, 1940723445, 1941263681, 1941313131, 1941500774, 1942353976, 1942525558, 1945818983, 1947300738, 1949090963, 1949483524, 1950588977, 1952163402, 1952980467, 1955410600, 1956480656, 1956633586, 1957388114, 1957618715, 1958677583, 1959734883, 1960874455, 1961873273, 1962006775, 1963938656, 1968511604, 1969972599, 1972414716, 1972420302, 1972995261, 1973855053, 1974399284, 1975225910, 1976135564, 1978210199, 1978942335, 1980867800, 1981596294, 1983403706, 1983415450, 1984450872, 1985841599, 1986902709, 1987558056, 1988240819, 1989264189, 1990361627, 1991563887, 1991695597, 1991761759, 1992057595, 1992408471, 1993127818, 1993616065, 1995939726, 1996593031, 1996738526, 1997415667, 1998387933, 1999578169, 1999757610, 2000015925, 2001070402, 2004009760, 2005430969, 2005662755, 2006732796, 2007448564, 2009615883, 2010045548, 2011155039, 2014193714, 2014617229, 2014793219, 2018967574, 2020651166, 2021668383, 2022035007, 2022634865, 2022806479, 2023253043, 2023366677, 2023900047, 2024223132, 2025205671, 2026332002, 2028717834, 2029838755, 2030850011, 2032514240, 2032569875, 2034428455, 2034688288, 2035262033, 2035448228, 2037975339, 2038171878, 2039114906, 2040709684, 2041500204, 2042918394, 2043462538, 2043910215, 2044029692, 2045947198, 2048177486, 2050316874, 2050699899, 2051331837, 2056097208, 2056774551, 2057546366, 2057929596, 2058392467, 2060348569, 2060522047, 2062726113, 2062728620, 2063974849, 2067205574, 2070281532, 2070573163, 2071145909, 2072757853, 2075210489, 2077197067, 2078479133, 2078851734, 2079252796, 2080142296, 2080835733, 2082612272, 2083452294, 2083662743, 2086617880, 2088185620, 2088255131, 2088559127, 2090170612, 2090308508, 2090336439, 2090765527, 2091244518, 2091714137, 2093511117, 2094163149, 2096003183, 2096155729, 2096273635, 2099108364, 2099471626, 2102840233, 2103041636, 2103161743, 2105935009, 2106047090, 2106591401, 2107071728, 2107736230, 2110105715, 2110131977, 2110485418, 2111871239, 2112917376, 2113768134, 2114589289, 2114981333, 2115785779, 2115860189, 2116517901, 2118565195, 2118692269, 2122442898, 2122984373, 2123233057, 2123871691, 2124361786, 2127485484, 2127541030, 2127882625, 2128062247, 2128565539, 2131944766, 2132991830, 2133081231, 2133574102, 2133786271, 2134278980, 2136337594, 2138433949, 2139035162, 2143087177, 2144083981, 2144566527, 2144963772, 2145046133, 2145462307, 2146619477, 2147090793], "edge_ids": [101213851, 142628996, 158676006, 198258688, 207409853, 240751648, 474720742, 571077171, 576496726, 609685571, 1247051105, 1292107022, 1372811207, 1391785934, 1756731660, 1899612498, 1951335677, 2010485505, 2033378404, 2058020975, 2076243686, 2101699769], "note": "raw feature ids (StateEncoder.indexFor(hash64(name))) with an embedding row, in row order; a leaf outside leaf_ids has no embedding and its edges are dropped; an edge label outside edge_ids uses the unknown-label row"}