briscoooe commited on
Commit
95c986f
·
verified ·
1 Parent(s): dc85ea2

Upload folder using huggingface_hub

Browse files
code/eval_value_search.py CHANGED
@@ -25,7 +25,12 @@ FIXTURE = Path(__file__).parent / "fixtures" / "canonical_states.json"
25
  def main():
26
  p = argparse.ArgumentParser(description=__doc__)
27
  p.add_argument("--value", required=True,
28
- help="Path to value.pt, or a Hub repo id to download it from.")
 
 
 
 
 
29
  p.add_argument("--beam", default="64",
30
  help="Beam width, or a comma-separated list to sweep. A sweep "
31
  "reuses one box for every width, which matters because "
@@ -47,10 +52,12 @@ def main():
47
  path = args.value
48
  if not os.path.exists(path):
49
  from huggingface_hub import hf_hub_download
50
- path = hf_hub_download(args.value, "value.pt", token=os.environ.get("HF_TOKEN"))
 
51
  device = "cuda" if torch.cuda.is_available() else "cpu"
52
  model = load_value_model(path, device)
53
- print(f"value model on {device} | beam {args.beam} | lam {args.lam}", flush=True)
 
54
 
55
  states = json.loads(FIXTURE.read_text())
56
  if args.depths:
 
25
  def main():
26
  p = argparse.ArgumentParser(description=__doc__)
27
  p.add_argument("--value", required=True,
28
+ help="Path to a value checkpoint, or a Hub repo id to fetch it "
29
+ "from. Either flavour works -- supervised (train_value.py) "
30
+ "or value iteration (train_value_iteration.py); search.py "
31
+ "detects which from the checkpoint.")
32
+ p.add_argument("--value-file", default="value.pt",
33
+ help="Filename within the Hub repo, when --value is a repo id.")
34
  p.add_argument("--beam", default="64",
35
  help="Beam width, or a comma-separated list to sweep. A sweep "
36
  "reuses one box for every width, which matters because "
 
52
  path = args.value
53
  if not os.path.exists(path):
54
  from huggingface_hub import hf_hub_download
55
+ path = hf_hub_download(args.value, args.value_file,
56
+ token=os.environ.get("HF_TOKEN"))
57
  device = "cuda" if torch.cuda.is_available() else "cpu"
58
  model = load_value_model(path, device)
59
+ print(f"value model ({model.value_kind}) on {device} | beam {args.beam} | "
60
+ f"lam {args.lam}", flush=True)
61
 
62
  states = json.loads(FIXTURE.read_text())
63
  if args.depths:
code/search.py CHANGED
@@ -13,11 +13,17 @@ as the transition function and a network supplying the heuristic. The weighting
13
  greedy on the heuristic (fast, longer solutions), higher lambda behaves more like
14
  uniform-cost search.
15
 
16
- The heuristic is Kociemba length, an upper bound rather than true optimal
17
- distance, so it is inadmissible and the search is not guaranteed to return a
18
- shortest solution. That is fine: the engine verifies that whatever comes back
19
- actually solves, and finding *a* solution at depth 20 is the open problem, not
20
- finding the shortest.
 
 
 
 
 
 
21
  """
22
  import sys
23
  from pathlib import Path
@@ -30,29 +36,55 @@ from gen_data import SOLVED, MOVES, apply_move
30
 
31
 
32
  def load_value_model(path, device):
33
- from train_value import ValueNet
 
 
 
 
 
 
 
 
34
  ckpt = torch.load(path, map_location=device)
35
- model = ValueNet(ckpt["hidden"], ckpt["layers"], ckpt["heads"]).to(device)
36
- model.load_state_dict(ckpt["state_dict"])
 
 
 
 
 
 
 
 
 
 
 
37
  model.eval()
 
38
  return model
39
 
40
 
41
  @torch.no_grad()
42
  def heuristic(model, states, device, batch_size=1024):
43
- """Expected distance-to-solved for each state.
44
 
45
- Uses the expectation over the predicted distribution rather than the argmax:
46
- the extra resolution matters when ranking siblings that all round to the same
47
- integer, which is most of the frontier.
 
48
  """
 
49
  out = []
50
  for i in range(0, len(states), batch_size):
51
  chunk = states[i:i + batch_size]
52
  ids = torch.tensor([T.encode_state(s) for s in chunk], device=device)
53
- probs = torch.softmax(model(ids).float(), dim=-1)
54
- values = torch.arange(probs.shape[-1], device=device, dtype=torch.float32)
55
- out.extend((probs * values).sum(-1).tolist())
 
 
 
 
56
  return out
57
 
58
 
 
13
  greedy on the heuristic (fast, longer solutions), higher lambda behaves more like
14
  uniform-cost search.
15
 
16
+ Two kinds of value model can drive this, and `load_value_model` tells them apart
17
+ by what the checkpoint carries:
18
+
19
+ - **supervised** (`train_value.py`): a distribution over distances, trained on
20
+ Kociemba lengths. Those are upper bounds rather than true optimal distances, so
21
+ the heuristic is inadmissible and no shortest-solution guarantee holds.
22
+ - **value iteration** (`train_value_iteration.py`): a scalar, bootstrapped from
23
+ the solved state with no solver involved.
24
+
25
+ Either way the engine verifies that whatever comes back actually solves, and
26
+ finding *a* solution at depth 20 is the open problem, not finding the shortest.
27
  """
28
  import sys
29
  from pathlib import Path
 
36
 
37
 
38
  def load_value_model(path, device):
39
+ """Loads either value-model flavour, detected from the checkpoint's own keys.
40
+
41
+ The two trainers save under different keys ("state_dict" vs "model"), which is
42
+ the only reliable discriminator: both carry identical hidden/layers/heads, so
43
+ shape alone cannot distinguish a 27-way distribution head from a scalar one
44
+ without guessing. The flavour is recorded on the model so `heuristic` reads
45
+ each correctly -- feeding a scalar head through softmax would silently return
46
+ a constant and turn the search into an untargeted walk.
47
+ """
48
  ckpt = torch.load(path, map_location=device)
49
+ if "state_dict" in ckpt:
50
+ from train_value import ValueNet
51
+ model = ValueNet(ckpt["hidden"], ckpt["layers"], ckpt["heads"]).to(device)
52
+ model.load_state_dict(ckpt["state_dict"])
53
+ kind = "distribution"
54
+ elif "model" in ckpt:
55
+ from train_value_iteration import ValueNet as ValueNetScalar
56
+ model = ValueNetScalar(ckpt["hidden"], ckpt["layers"], ckpt["heads"]).to(device)
57
+ model.load_state_dict(ckpt["model"])
58
+ kind = "scalar"
59
+ else:
60
+ raise ValueError(f"{path}: no 'state_dict' or 'model' key; not a value "
61
+ f"checkpoint (keys: {sorted(ckpt)[:8]})")
62
  model.eval()
63
+ model.value_kind = kind
64
  return model
65
 
66
 
67
  @torch.no_grad()
68
  def heuristic(model, states, device, batch_size=1024):
69
+ """Estimated distance-to-solved for each state.
70
 
71
+ For a distribution head, the expectation over the predicted distribution
72
+ rather than the argmax: the extra resolution matters when ranking siblings
73
+ that all round to the same integer, which is most of the frontier. A scalar
74
+ head already has that resolution and is read directly.
75
  """
76
+ kind = getattr(model, "value_kind", "distribution")
77
  out = []
78
  for i in range(0, len(states), batch_size):
79
  chunk = states[i:i + batch_size]
80
  ids = torch.tensor([T.encode_state(s) for s in chunk], device=device)
81
+ raw = model(ids).float()
82
+ if kind == "scalar":
83
+ out.extend(raw.clamp(min=0.0).tolist())
84
+ else:
85
+ probs = torch.softmax(raw, dim=-1)
86
+ values = torch.arange(probs.shape[-1], device=device, dtype=torch.float32)
87
+ out.extend((probs * values).sum(-1).tolist())
88
  return out
89
 
90
 
code/vast_bootstrap.sh CHANGED
@@ -79,6 +79,7 @@ if [ -n "${EVAL_VALUE_ONLY:-}" ]; then
79
  cd "${CODE_DIR:-/workspace/code}"
80
  echo "=== evaluating value search from ${EVAL_VALUE_ONLY} ==="
81
  /opt/venv/bin/python eval_value_search.py --value "$EVAL_VALUE_ONLY" \
 
82
  --beam "${SEARCH_BEAM:-64}" --limit-per-depth "${SEARCH_N:-25}" \
83
  ${SEARCH_DEPTHS:+--depths "$SEARCH_DEPTHS"} ${SEARCH_LAM:+--lam "$SEARCH_LAM"}
84
  echo "=== done ==="
 
79
  cd "${CODE_DIR:-/workspace/code}"
80
  echo "=== evaluating value search from ${EVAL_VALUE_ONLY} ==="
81
  /opt/venv/bin/python eval_value_search.py --value "$EVAL_VALUE_ONLY" \
82
+ --value-file "${VALUE_FILE:-value.pt}" \
83
  --beam "${SEARCH_BEAM:-64}" --limit-per-depth "${SEARCH_N:-25}" \
84
  ${SEARCH_DEPTHS:+--depths "$SEARCH_DEPTHS"} ${SEARCH_LAM:+--lam "$SEARCH_LAM"}
85
  echo "=== done ==="