byte-vortex commited on
Commit
19cebe9
·
verified ·
1 Parent(s): 1d25b0b

Update logbook: Repro - NonZero: Interaction-Guided Exploration for Multi-Agent Monte Carlo Tree Search

Browse files
logbook.json CHANGED
@@ -10,7 +10,7 @@
10
  "icml2026-repro",
11
  "paper-Jh6gq9QsFa"
12
  ],
13
- "updated_at": "2026-08-03T03:17:41+00:00",
14
  "root": {
15
  "slug": "index",
16
  "title": "Repro - NonZero: Interaction-Guided Exploration for Multi-Agent Monte Carlo Tree Search",
@@ -73,10 +73,10 @@
73
  "total_size": 0,
74
  "bucket_id": null
75
  },
76
- "agent_view_tokens": 3382,
77
  "trace_view_tokens": 10,
78
  "workspace_view_tokens": 8,
79
- "revision": "c01b16b6a6c366d50096",
80
  "workspace_ref": {
81
  "repo_id": "byte-vortex/nonzero-repro-artifacts",
82
  "repo_type": "bucket",
 
10
  "icml2026-repro",
11
  "paper-Jh6gq9QsFa"
12
  ],
13
+ "updated_at": "2026-08-03T03:18:42+00:00",
14
  "root": {
15
  "slug": "index",
16
  "title": "Repro - NonZero: Interaction-Guided Exploration for Multi-Agent Monte Carlo Tree Search",
 
73
  "total_size": 0,
74
  "bucket_id": null
75
  },
76
+ "agent_view_tokens": 3525,
77
  "trace_view_tokens": 10,
78
  "workspace_view_tokens": 8,
79
+ "revision": "319798aa4827526a64f7",
80
  "workspace_ref": {
81
  "repo_id": "byte-vortex/nonzero-repro-artifacts",
82
  "repo_type": "bucket",
pages/claim-4-matgame-8-agent-10-action-nonlinear/page.md CHANGED
@@ -6,3 +6,317 @@
6
  {"type": "markdown", "id": "cell_bc24ae0bf5e1", "created_at": "2026-08-03T03:15:00+00:00", "title": "[toy / partial] Built a generalized N-agent MatGame environment implementing th…"}
7
  -->
8
  **[toy / partial]** Built a generalized N-agent MatGame environment implementing the exact reward rule from Appendix D (linear = sum of agent action indices; nonlinear = linear sum + Gaussian(0,2^2) + Uniform(-3,3) noise). Sanity-checked reward scale against Table 1: random-policy episodes on the 8x10 nonlinear config average ~370-380 out of a 720 max, consistent with the paper's trained-baseline range of 587-712 sitting well above random. Confirmed the underlying MAZero codebase (which NonZero extends, per the paper's own Appendix C) builds and trains successfully on a single Kaggle T4 GPU, converging to the optimal joint policy (test score 100.0 +/- 0.0) on the repo's built-in matgame3 config. Began implementing NonUCT's selection rule as a C++ patch to MAZero's MCTS node scoring (cnode.h/cnode.cpp): added hooks for a precomputed surrogate score, blended additively into the existing pUCT formula, gated by a weight defaulting to 0 so baseline behavior is unaffected unless explicitly enabled. This patch compiles successfully. **Not completed**: the Cython bindings (cytree.pyx) needed to call these new C++ hooks from Python were not implemented, so no NonZero-vs-MAZero training comparison was run, and Table 1's actual numbers (697.1 vs 672.3) were not reproduced or falsified.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  {"type": "markdown", "id": "cell_bc24ae0bf5e1", "created_at": "2026-08-03T03:15:00+00:00", "title": "[toy / partial] Built a generalized N-agent MatGame environment implementing th…"}
7
  -->
8
  **[toy / partial]** Built a generalized N-agent MatGame environment implementing the exact reward rule from Appendix D (linear = sum of agent action indices; nonlinear = linear sum + Gaussian(0,2^2) + Uniform(-3,3) noise). Sanity-checked reward scale against Table 1: random-policy episodes on the 8x10 nonlinear config average ~370-380 out of a 720 max, consistent with the paper's trained-baseline range of 587-712 sitting well above random. Confirmed the underlying MAZero codebase (which NonZero extends, per the paper's own Appendix C) builds and trains successfully on a single Kaggle T4 GPU, converging to the optimal joint policy (test score 100.0 +/- 0.0) on the repo's built-in matgame3 config. Began implementing NonUCT's selection rule as a C++ patch to MAZero's MCTS node scoring (cnode.h/cnode.cpp): added hooks for a precomputed surrogate score, blended additively into the existing pUCT formula, gated by a weight defaulting to 0 so baseline behavior is unaffected unless explicitly enabled. This patch compiles successfully. **Not completed**: the Cython bindings (cytree.pyx) needed to call these new C++ hooks from Python were not implemented, so no NonZero-vs-MAZero training comparison was run, and Table 1's actual numbers (697.1 vs 672.3) were not reproduced or falsified.
9
+
10
+
11
+ ---
12
+ <!-- trackio-cell
13
+ {"type": "code", "id": "cell_e6b23a9eda0a", "created_at": "2026-08-03T03:18:41+00:00", "title": "Run: hf claim4_toy_comparison.py (exit 0)", "command": ["hf", "jobs", "uv", "run", "--flavor", "cpu-basic", "--secrets", "HF_TOKEN", "claim4_toy_comparison.py"], "exit_code": 0, "duration_s": 55.853}
14
+ -->
15
+ ````bash
16
+ $ hf jobs uv run --flavor cpu-basic --secrets HF_TOKEN claim4_toy_comparison.py
17
+ ````
18
+
19
+ exit 0 · 55.9s
20
+
21
+
22
+ ````python title=claim4_toy_comparison.py
23
+
24
+ # /// script
25
+ # dependencies = ["torch", "numpy"]
26
+ # ///
27
+ """
28
+ Claim 4 -- toy-scale head-to-head comparison on the paper's exact
29
+ 8-agent, 10-action nonlinear MatGame setting (Table 1). Vectorized
30
+ version: candidate generation uses precomputed batched tensor masks
31
+ instead of per-candidate Python loops, since the naive version is too
32
+ slow for 2000 decisions.
33
+ """
34
+
35
+ import torch
36
+ import torch.nn as nn
37
+ import numpy as np
38
+
39
+
40
+ class MatgameNonZeroEnv:
41
+ def __init__(self, n_agents=8, n_actions=10, reward_type="nonlinear",
42
+ noise_std=2.0, noise_uniform_range=3.0, seed=None):
43
+ self.n_agents = n_agents
44
+ self.n_actions = n_actions
45
+ self.reward_type = reward_type
46
+ self.noise_std = noise_std
47
+ self.noise_uniform_range = noise_uniform_range
48
+ self._rng = np.random.default_rng(seed)
49
+
50
+ def joint_reward(self, actions):
51
+ base = float(np.sum(actions))
52
+ if self.reward_type == "linear":
53
+ return base
54
+ u = self._rng.normal(0.0, self.noise_std)
55
+ v = self._rng.uniform(-self.noise_uniform_range, self.noise_uniform_range)
56
+ return base + u + v
57
+
58
+
59
+ class VectorizedProposer:
60
+ def __init__(self, n_agents, n_actions, c=1.0, alpha=1.0):
61
+ self.n_agents, self.n_actions = n_agents, n_actions
62
+ self.nd = n_agents * n_actions
63
+ self.c, self.alpha = c, alpha
64
+
65
+ rows1 = n_agents * n_actions
66
+ self.agent1 = torch.zeros(rows1, dtype=torch.long)
67
+ self.action1 = torch.zeros(rows1, dtype=torch.long)
68
+ self.block_mask1 = torch.zeros(rows1, self.nd)
69
+ self.target1 = torch.zeros(rows1, self.nd)
70
+ r = 0
71
+ for i in range(n_agents):
72
+ for j in range(n_actions):
73
+ self.agent1[r], self.action1[r] = i, j
74
+ self.block_mask1[r, i * n_actions:(i + 1) * n_actions] = 1.0
75
+ self.target1[r, i * n_actions + j] = 1.0
76
+ r += 1
77
+
78
+ pairs = [(i, j) for i in range(n_agents) for j in range(i + 1, n_agents)]
79
+ rows2 = len(pairs) * n_actions * n_actions
80
+ self.pair_i = torch.zeros(rows2, dtype=torch.long)
81
+ self.pair_j = torch.zeros(rows2, dtype=torch.long)
82
+ self.action_i = torch.zeros(rows2, dtype=torch.long)
83
+ self.action_j = torch.zeros(rows2, dtype=torch.long)
84
+ self.block_mask2 = torch.zeros(rows2, self.nd)
85
+ self.target2 = torch.zeros(rows2, self.nd)
86
+ r = 0
87
+ for (i, j) in pairs:
88
+ for ai in range(n_actions):
89
+ for aj in range(n_actions):
90
+ self.pair_i[r], self.pair_j[r] = i, j
91
+ self.action_i[r], self.action_j[r] = ai, aj
92
+ self.block_mask2[r, i * n_actions:(i + 1) * n_actions] = 1.0
93
+ self.block_mask2[r, j * n_actions:(j + 1) * n_actions] = 1.0
94
+ self.target2[r, i * n_actions + ai] = 1.0
95
+ self.target2[r, j * n_actions + aj] = 1.0
96
+ r += 1
97
+
98
+ def one_hot(self, agent_actions):
99
+ agent_actions = torch.as_tensor(agent_actions, dtype=torch.long)
100
+ oh = torch.nn.functional.one_hot(agent_actions, num_classes=self.n_actions)
101
+ return oh.reshape(-1).float()
102
+
103
+ def eta(self, theta, a_batch):
104
+ return self.c * torch.asinh(self.alpha * (theta.unsqueeze(0) * a_batch).sum(dim=-1))
105
+
106
+ def best_candidate(self, theta, cur_enc, current_actions):
107
+ cur_eta = self.eta(theta, cur_enc.unsqueeze(0)).item()
108
+
109
+ cand1 = cur_enc.unsqueeze(0) * (1 - self.block_mask1) + self.target1
110
+ eta1 = self.eta(theta, cand1)
111
+ current_action1 = torch.as_tensor(current_actions)[self.agent1]
112
+ self_mask1 = (self.action1 == current_action1)
113
+ eta1 = eta1.masked_fill(self_mask1, -float("inf"))
114
+ delta1 = eta1 - cur_eta
115
+ best1_val, best1_idx = delta1.max(dim=0)
116
+
117
+ cand2 = cur_enc.unsqueeze(0) * (1 - self.block_mask2) + self.target2
118
+ eta2 = self.eta(theta, cand2)
119
+ cur_ai = torch.as_tensor(current_actions)[self.pair_i]
120
+ cur_aj = torch.as_tensor(current_actions)[self.pair_j]
121
+ self_mask2 = (self.action_i == cur_ai) | (self.action_j == cur_aj)
122
+ eta2 = eta2.masked_fill(self_mask2, -float("inf"))
123
+ delta2 = eta2 - cur_eta
124
+ best2_val, best2_idx = delta2.max(dim=0)
125
+
126
+ if best1_val.item() >= best2_val.item():
127
+ agent_idx, new_action = self.agent1[best1_idx].item(), self.action1[best1_idx].item()
128
+ candidate_actions = current_actions.copy()
129
+ candidate_actions[agent_idx] = new_action
130
+ else:
131
+ i, j = self.pair_i[best2_idx].item(), self.pair_j[best2_idx].item()
132
+ ai, aj = self.action_i[best2_idx].item(), self.action_j[best2_idx].item()
133
+ candidate_actions = current_actions.copy()
134
+ candidate_actions[i], candidate_actions[j] = ai, aj
135
+
136
+ return candidate_actions
137
+
138
+
139
+ def fit_surrogate(env, n_agents, n_actions, n_samples=4000, n_epochs=1500, rng=None):
140
+ actions = rng.integers(0, n_actions, size=(n_samples, n_agents))
141
+ rewards = torch.tensor([env.joint_reward(a) for a in actions], dtype=torch.float32)
142
+ onehots = torch.nn.functional.one_hot(torch.tensor(actions, dtype=torch.long), num_classes=n_actions)
143
+ a_enc = onehots.reshape(n_samples, -1).float()
144
+
145
+ nd = n_agents * n_actions
146
+ theta = nn.Parameter(torch.zeros(nd))
147
+ log_c, log_alpha = nn.Parameter(torch.zeros(())), nn.Parameter(torch.zeros(()))
148
+ opt = torch.optim.Adam([theta, log_c, log_alpha], lr=0.05)
149
+ sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=n_epochs)
150
+
151
+ for _ in range(n_epochs):
152
+ opt.zero_grad()
153
+ pred = torch.exp(log_c) * torch.asinh(torch.exp(log_alpha) * (theta * a_enc).sum(dim=-1))
154
+ loss = ((pred - rewards) ** 2).mean()
155
+ loss.backward()
156
+ opt.step()
157
+ sched.step()
158
+ return theta.detach()
159
+
160
+
161
+ def nonzero_style_decision(env, proposer, theta, budget, rng):
162
+ n_agents, n_actions = env.n_agents, env.n_actions
163
+ current = rng.integers(0, n_actions, size=n_agents)
164
+ best_reward = env.joint_reward(current)
165
+ evals_used = 1
166
+
167
+ while evals_used < budget:
168
+ cur_enc = proposer.one_hot(current)
169
+ candidate_actions = proposer.best_candidate(theta, cur_enc, current)
170
+ real_reward = env.joint_reward(candidate_actions)
171
+ evals_used += 1
172
+
173
+ if real_reward > best_reward:
174
+ current = candidate_actions
175
+ best_reward = real_reward
176
+ else:
177
+ break
178
+
179
+ return best_reward
180
+
181
+
182
+ def mazero_style_decision(env, budget, rng):
183
+ best_reward = -float("inf")
184
+ for _ in range(budget):
185
+ actions = rng.integers(0, env.n_actions, size=env.n_agents)
186
+ r = env.joint_reward(actions)
187
+ if r > best_reward:
188
+ best_reward = r
189
+ return best_reward
190
+
191
+
192
+ def main():
193
+ print("=" * 70)
194
+ print("NonZero reproduction -- Claim 4 toy comparison (vectorized)")
195
+ print("Setting: 8 agents, 10 actions, nonlinear (Table 1's hardest config)")
196
+ print("2000 sequential decisions, equal real-eval budget per decision")
197
+ print("=" * 70)
198
+
199
+ n_agents, n_actions = 8, 10
200
+ n_steps = 2000
201
+ budget_per_step = 15
202
+
203
+ rng_fit = np.random.default_rng(0)
204
+ env_fit = MatgameNonZeroEnv(n_agents=n_agents, n_actions=n_actions,
205
+ reward_type="nonlinear", seed=1)
206
+ theta = fit_surrogate(env_fit, n_agents, n_actions, rng=rng_fit)
207
+ proposer = VectorizedProposer(n_agents, n_actions)
208
+ print(f"\nSurrogate fit complete. Candidate masks precomputed "
209
+ f"({proposer.block_mask1.shape[0]} first-order + "
210
+ f"{proposer.block_mask2.shape[0]} second-order candidates/decision).")
211
+
212
+ nonzero_rewards, mazero_rewards = [], []
213
+ for step in range(n_steps):
214
+ env_a = MatgameNonZeroEnv(n_agents=n_agents, n_actions=n_actions,
215
+ reward_type="nonlinear", seed=5000 + step)
216
+ rng_a = np.random.default_rng(3000 + step)
217
+ nonzero_rewards.append(nonzero_style_decision(env_a, proposer, theta, budget_per_step, rng_a))
218
+
219
+ env_b = MatgameNonZeroEnv(n_agents=n_agents, n_actions=n_actions,
220
+ reward_type="nonlinear", seed=5000 + step)
221
+ rng_b = np.random.default_rng(3000 + step)
222
+ mazero_rewards.append(mazero_style_decision(env_b, budget_per_step, rng_b))
223
+
224
+ if (step + 1) % 400 == 0:
225
+ print(f" ...{step + 1}/{n_steps} decisions completed")
226
+
227
+ nz_mean, nz_std = np.mean(nonzero_rewards), np.std(nonzero_rewards)
228
+ mz_mean, mz_std = np.mean(mazero_rewards), np.std(mazero_rewards)
229
+ improvement = (nz_mean - mz_mean) / abs(mz_mean) * 100
230
+
231
+ print(f"\n[Results over {n_steps} decisions, budget={budget_per_step}/decision]")
232
+ print(f" NonZero-style (guided proposal): {nz_mean:.2f} +/- {nz_std:.2f}")
233
+ print(f" MAZero-style (random sampled): {mz_mean:.2f} +/- {mz_std:.2f}")
234
+ print(f" Improvement: {improvement:+.1f}%")
235
+
236
+ print("\n" + "=" * 70)
237
+ print("SUMMARY")
238
+ if nz_mean > mz_mean:
239
+ print(f" PASS (toy scale): NonZero-style guided proposal outperforms")
240
+ print(f" MAZero-style random sampling by {improvement:.1f}% under equal budget.")
241
+ else:
242
+ print(f" FAIL: guided proposal did NOT outperform the baseline.")
243
+ print("=" * 70)
244
+
245
+
246
+ if __name__ == "__main__":
247
+ main()
248
+
249
+ ````
250
+
251
+
252
+ ````output
253
+ /usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_experimental.py:59: UserWarning: 'HfApi.run_uv_job' is experimental and might be subject to breaking changes in the future without prior notice. You can disable this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment variable.
254
+ warnings.warn(
255
+ Job started with ID: 6a70085a6b79c09949c20239
256
+ View at: https://huggingface.co/jobs/byte-vortex/6a70085a6b79c09949c20239
257
+ Downloading nvidia-cuda-runtime (2.1MiB)
258
+ Downloading nvidia-cudnn-cu13 (349.2MiB)
259
+ Downloading nvidia-nvjitlink (38.9MiB)
260
+ Downloading numpy (15.9MiB)
261
+ Downloading nvidia-cuda-cupti (10.2MiB)
262
+ Downloading nvidia-nvshmem-cu13 (57.6MiB)
263
+ Downloading torch (502.2MiB)
264
+ Downloading nvidia-cublas (403.5MiB)
265
+ Downloading sympy (6.0MiB)
266
+ Downloading nvidia-cusolver (191.6MiB)
267
+ Downloading triton (188.6MiB)
268
+ Downloading nvidia-cusparse (139.2MiB)
269
+ Downloading nvidia-cuda-nvrtc (86.0MiB)
270
+ Downloading nvidia-cusparselt-cu13 (162.3MiB)
271
+ Downloading nvidia-curand (56.8MiB)
272
+ Downloading nvidia-cufile (1.2MiB)
273
+ Downloading nvidia-nccl-cu13 (196.4MiB)
274
+ Downloading cuda-bindings (6.3MiB)
275
+ Downloading nvidia-cufft (204.2MiB)
276
+ Downloading networkx (2.0MiB)
277
+ Downloaded nvidia-cufile
278
+ Downloaded nvidia-cuda-runtime
279
+ Downloaded networkx
280
+ Downloaded cuda-bindings
281
+ Downloaded nvidia-cuda-cupti
282
+ Downloaded numpy
283
+ Downloaded nvidia-nvjitlink
284
+ Downloaded sympy
285
+ Downloaded nvidia-curand
286
+ Downloaded nvidia-nvshmem-cu13
287
+ Downloaded nvidia-cuda-nvrtc
288
+ Downloaded nvidia-cusparse
289
+ Downloaded nvidia-cusparselt-cu13
290
+ Downloaded nvidia-cusolver
291
+ Downloaded nvidia-nccl-cu13
292
+ Downloaded nvidia-cufft
293
+ Downloaded triton
294
+ Downloaded nvidia-cudnn-cu13
295
+ Downloaded nvidia-cublas
296
+ Downloaded torch
297
+ Installed 30 packages in 233ms
298
+ ======================================================================
299
+ NonZero reproduction -- Claim 4 toy comparison (vectorized)
300
+ Setting: 8 agents, 10 actions, nonlinear (Table 1's hardest config)
301
+ 2000 sequential decisions, equal real-eval budget per decision
302
+ ======================================================================
303
+
304
+ Surrogate fit complete. Candidate masks precomputed (80 first-order + 2800 second-order candidates/decision).
305
+ ...400/2000 decisions completed
306
+ ...800/2000 decisions completed
307
+ ...1200/2000 decisions completed
308
+ ...1600/2000 decisions completed
309
+ ...2000/2000 decisions completed
310
+
311
+ [Results over 2000 decisions, budget=15/decision]
312
+ NonZero-style (guided proposal): 72.97 +/- 2.59
313
+ MAZero-style (random sampled): 50.84 +/- 4.51
314
+ Improvement: +43.5%
315
+
316
+ ======================================================================
317
+ SUMMARY
318
+ PASS (toy scale): NonZero-style guided proposal outperforms
319
+ MAZero-style random sampling by 43.5% under equal budget.
320
+ ======================================================================
321
+
322
+ ````
workspace.json CHANGED
@@ -14,6 +14,11 @@
14
  "type": "Jobs",
15
  "label": "byte-vortex/6a7007c56b79c09949c20232"
16
  },
 
 
 
 
 
17
  {
18
  "url": "https://huggingface.co/buckets/byte-vortex/nonzero-repro-artifacts",
19
  "type": "Buckets",
 
14
  "type": "Jobs",
15
  "label": "byte-vortex/6a7007c56b79c09949c20232"
16
  },
17
+ {
18
+ "url": "https://huggingface.co/jobs/byte-vortex/6a70085a6b79c09949c20239",
19
+ "type": "Jobs",
20
+ "label": "byte-vortex/6a70085a6b79c09949c20239"
21
+ },
22
  {
23
  "url": "https://huggingface.co/buckets/byte-vortex/nonzero-repro-artifacts",
24
  "type": "Buckets",