Evangelinejy commited on
Commit
8413ecc
·
verified ·
1 Parent(s): bc53bbf

Upload folder using huggingface_hub

Browse files
config.json ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3ForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 0,
8
+ "dtype": "float32",
9
+ "eos_token_id": 1,
10
+ "head_dim": 128,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 1024,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 3072,
15
+ "layer_types": [
16
+ "full_attention",
17
+ "full_attention",
18
+ "full_attention",
19
+ "full_attention",
20
+ "full_attention",
21
+ "full_attention",
22
+ "full_attention",
23
+ "full_attention",
24
+ "full_attention",
25
+ "full_attention",
26
+ "full_attention",
27
+ "full_attention",
28
+ "full_attention",
29
+ "full_attention",
30
+ "full_attention",
31
+ "full_attention",
32
+ "full_attention",
33
+ "full_attention",
34
+ "full_attention",
35
+ "full_attention",
36
+ "full_attention",
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention",
42
+ "full_attention",
43
+ "full_attention"
44
+ ],
45
+ "max_position_embeddings": 2048,
46
+ "max_window_layers": 28,
47
+ "model_type": "qwen3",
48
+ "num_attention_heads": 16,
49
+ "num_hidden_layers": 28,
50
+ "num_key_value_heads": 8,
51
+ "pad_token_id": 0,
52
+ "rms_norm_eps": 1e-06,
53
+ "rope_scaling": null,
54
+ "rope_theta": 1000000,
55
+ "sliding_window": null,
56
+ "tie_word_embeddings": true,
57
+ "transformers_version": "4.57.0",
58
+ "use_cache": true,
59
+ "use_sliding_window": false,
60
+ "vocab_size": 89
61
+ }
generation_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 0,
4
+ "eos_token_id": 1,
5
+ "transformers_version": "4.57.0"
6
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a780e3326fe4dee0381933037ecf97a46a252f40f4636024d671f121f909a99
3
+ size 1762269400
special_tokens_map.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "<bos>",
3
+ "eos_token": "<eos>",
4
+ "pad_token": "<bos>",
5
+ "unk_token": "<unk>"
6
+ }
tokenizer.py ADDED
@@ -0,0 +1,805 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Auto-generated self-contained HF tokenizer.
3
+ Do NOT edit manually -- regenerate via training.hf_tokenizer_utils.save_hf_tokenizer().
4
+ """
5
+ from __future__ import annotations
6
+
7
+ # --- BaseTokenizer (inlined) ---
8
+ # base_tokenizer.py
9
+ from abc import ABC, abstractmethod
10
+ from typing import List, Dict, Optional
11
+
12
+ class BaseTokenizer(ABC):
13
+ """Minimal interface for tokenizers used in pretraining."""
14
+
15
+ # ---- required ----
16
+ @abstractmethod
17
+ def encode(self, text: str) -> List[int]:
18
+ """Convert text/PGN to token IDs."""
19
+ raise NotImplementedError
20
+
21
+ @abstractmethod
22
+ def decode(self, ids: List[int]) -> str:
23
+ """Convert token IDs back to text/PGN."""
24
+ raise NotImplementedError
25
+
26
+ @abstractmethod
27
+ def get_vocab(self) -> Dict[str, int]:
28
+ """Return token -> id mapping (if available)."""
29
+ raise NotImplementedError
30
+
31
+ def bos_id(self) -> Optional[int]: return None
32
+ def eos_id(self) -> Optional[int]: return None
33
+ def pad_id(self) -> Optional[int]: return None
34
+ def get_vocab_size(self) -> int: return len(self.get_vocab())
35
+
36
+ def __call__(self, text: str) -> List[int]:
37
+ """Alias for encode()."""
38
+ return self.encode(text)
39
+
40
+ # --- Concrete tokenizer (inlined) ---
41
+ # lan_tokenizer_sft.py
42
+ """
43
+ LAN Tokenizer with SFT support (CoT format with <T> and <sep> tokens).
44
+ This extends the base LAN tokenizer with SFT-specific functionality:
45
+ - <T> token for marking thinking/CoT content
46
+ - <sep> token for separating prompt from response
47
+ """
48
+ from typing import List, Dict, Optional, Tuple
49
+ import io
50
+ import chess, chess.pgn
51
+ from tokenizers import Tokenizer
52
+ from tokenizers.models import WordLevel
53
+ from tokenizers.pre_tokenizers import WhitespaceSplit
54
+ _RESULT = {"1-0", "0-1", "1/2-1/2", "*"}
55
+ FILES = "abcdefgh"
56
+ RANKS = "12345678"
57
+ SQUARES = [f+r for f in FILES for r in RANKS]
58
+ PROMOS = "QRBN"
59
+ DIGITS = set("0123456789")
60
+
61
+ # SFT special tokens for CoT format
62
+ T_TOKEN = "<T>"
63
+ T_END_TOKEN = "</T>"
64
+ SEP_TOKEN = "<sep>"
65
+
66
+ # Environment interaction / reward special tokens
67
+ CALL_ENV_TOKEN = "<call_env>"
68
+ VERIFY_TOKEN = "<verify>"
69
+ REWARD_POS_TOKEN = "<+1>"
70
+ REWARD_NEG_TOKEN = "<-1>"
71
+ REWARD_ZERO_TOKEN = "<0>"
72
+ ENV_TOKENS = [CALL_ENV_TOKEN]
73
+ REWARD_TOKENS = [REWARD_POS_TOKEN, REWARD_NEG_TOKEN, REWARD_ZERO_TOKEN]
74
+
75
+ def _vocab_with_sft(
76
+ include_move_numbers: bool,
77
+ keep_result: bool,
78
+ bos: str,
79
+ eos: str,
80
+ unk: str,
81
+ include_env_tokens: bool = False,
82
+ include_reward_tokens: bool = False,
83
+ ) -> Dict[str, int]:
84
+ """Create vocabulary including SFT special tokens."""
85
+ base = [bos, eos, unk]
86
+ ops = ["x", "=", "+", "#", "O-O", "O-O-O", ".", "..."]
87
+ toks = base + list("KQRBNP") + SQUARES + list(PROMOS) + ops
88
+ if include_move_numbers:
89
+ toks += list("0123456789")
90
+ if keep_result:
91
+ toks += sorted(_RESULT)
92
+
93
+ # Add SFT special tokens for CoT format
94
+ sft_tokens = [T_TOKEN, T_END_TOKEN, SEP_TOKEN]
95
+ toks += sft_tokens
96
+
97
+ # Add environment / reward tokens when requested
98
+ if include_env_tokens:
99
+ toks += ENV_TOKENS
100
+ if include_reward_tokens:
101
+ toks += REWARD_TOKENS
102
+
103
+ return {t: i for i, t in enumerate(dict.fromkeys(toks))}
104
+
105
+
106
+ class LanTokenizerSFT(BaseTokenizer):
107
+ """
108
+ LAN Tokenizer with SFT capabilities.
109
+ This tokenizer extends the base LAN tokenizer with:
110
+ - <T> token for marking thinking/CoT boundaries
111
+ - <sep> token for separating candidate trajectories
112
+ CoT Format: {prompt} <T> <sep> {traj1} <sep> {traj2} <sep> ... <sep> {trajN} <sep> <T> {answer}
113
+ Where:
114
+ - {prompt}: The game history/board state (PGN moves)
115
+ - {trajN}: Candidate reasoning trajectories
116
+ - {answer}: The final best move
117
+ """
118
+
119
+ # Special tokens for CoT format
120
+ T = T_TOKEN
121
+ T_END = T_END_TOKEN
122
+ SEP = SEP_TOKEN
123
+
124
+ # Environment / reward tokens (class-level constants for easy access)
125
+ CALL_ENV = CALL_ENV_TOKEN # "<call_env>"
126
+ VERIFY = VERIFY_TOKEN # "<verify>"
127
+ REWARD_POS = REWARD_POS_TOKEN # "<+1>"
128
+ REWARD_NEG = REWARD_NEG_TOKEN # "<-1>"
129
+ REWARD_ZERO = REWARD_ZERO_TOKEN # "<0>"
130
+ ENV_TOKENS = ENV_TOKENS # full list
131
+
132
+ def __init__(self, config: Optional[dict] = None):
133
+ """
134
+ Args:
135
+ config: Configuration dict with tokenizer settings.
136
+ include_env_tokens (bool): add <call_env>, <verify>, <+1>, <-1>, <0>
137
+ to the vocabulary. Default: False.
138
+ """
139
+ config = config or {}
140
+
141
+ include_move_numbers = config.get("include_move_numbers", False)
142
+ include_black_tripledots = config.get("include_black_tripledots", False)
143
+ bos = config.get("bos", "<bos>")
144
+ eos = config.get("eos", "<eos>")
145
+ unk = config.get("unk", "<unk>")
146
+ keep_result = config.get("keep_result", False)
147
+ include_env_tokens = config.get("include_env_tokens", False)
148
+ include_reward_tokens = config.get("include_reward_tokens", False)
149
+
150
+ self._bos = bos
151
+ self._eos = eos
152
+ self._unk = unk
153
+ self._keep_res = keep_result
154
+ self._include_nums = include_move_numbers
155
+ self._include_black_ellipses = include_black_tripledots
156
+ self._include_env_tokens = include_env_tokens
157
+ self._include_reward_tokens = include_reward_tokens
158
+
159
+ # Create vocabulary with SFT tokens
160
+ tok2id = _vocab_with_sft(
161
+ include_move_numbers, keep_result, bos, eos, unk,
162
+ include_env_tokens=include_env_tokens,
163
+ include_reward_tokens=include_reward_tokens,
164
+ )
165
+ self._tok2id = tok2id
166
+
167
+ # Initialize tokenizer
168
+ self.tk = Tokenizer(WordLevel(vocab=tok2id, unk_token=self._unk))
169
+ self.tk.pre_tokenizer = WhitespaceSplit()
170
+
171
+ def _pgn_to_tokens(self, text: str) -> List[str]:
172
+ """Convert PGN text to tokens."""
173
+ import os, contextlib
174
+ with open(os.devnull, "w") as devnull, contextlib.redirect_stderr(devnull):
175
+ g = chess.pgn.read_game(io.StringIO(text))
176
+ if g is None:
177
+ return None
178
+
179
+ b, out, n = g.board(), [], 1
180
+ for mv in g.mainline_moves():
181
+ if b.turn == chess.WHITE and self._include_nums:
182
+ out += list(str(n)) + (
183
+ ["..."] if self._include_black_ellipses and b.fullmove_number < n else ["."]
184
+ )
185
+
186
+ if b.is_castling(mv):
187
+ b.push(mv)
188
+ suf = "#" if b.is_checkmate() else ("+" if b.is_check() else "")
189
+ b.pop()
190
+ out.append("O-O" if chess.square_file(mv.to_square) == 6 else "O-O-O")
191
+ if suf:
192
+ out.append(suf)
193
+ b.push(mv)
194
+ else:
195
+ piece = b.piece_at(mv.from_square).symbol().upper()
196
+ frm = chess.square_name(mv.from_square)
197
+ to = chess.square_name(mv.to_square)
198
+ is_cap = b.is_capture(mv)
199
+ promo = mv.promotion
200
+
201
+ b.push(mv)
202
+ suf = "#" if b.is_checkmate() else ("+" if b.is_check() else "")
203
+
204
+ # Emit LAN tokens
205
+ out.append(piece)
206
+ out.append(frm)
207
+ if is_cap:
208
+ out.append("x")
209
+ out.append(to)
210
+ if promo:
211
+ out += ["=", chess.piece_symbol(promo).upper()]
212
+ if suf:
213
+ out.append(suf)
214
+
215
+ if b.turn == chess.WHITE:
216
+ n += 1
217
+
218
+ res = g.headers.get("Result")
219
+ if self._keep_res and res in _RESULT:
220
+ out.append(res)
221
+
222
+ return out
223
+
224
+ def _lan_move_to_tokens(self, move: str) -> List[str]:
225
+ """
226
+ Convert a single LAN move to tokens.
227
+
228
+ LAN format: [Piece][from_square][x]?[to_square][=Promo]?[+#]?
229
+
230
+ Examples:
231
+ "Ng1f3" -> ["N", "g1", "f3"]
232
+ "Nd4xe6" -> ["N", "d4", "x", "e6"]
233
+ "Pe2e4" -> ["P", "e2", "e4"]
234
+ "Pe4xd5" -> ["P", "e4", "x", "d5"]
235
+ "O-O" -> ["O-O"]
236
+ "O-O-O" -> ["O-O-O"]
237
+ "Pe7e8=Q" -> ["P", "e7", "e8", "=", "Q"]
238
+ "Ng1f3+" -> ["N", "g1", "f3", "+"]
239
+ """
240
+ # Handle castling
241
+ if move in {"O-O", "O-O-O"}:
242
+ return [move]
243
+ if move.rstrip("+#") in {"O-O", "O-O-O"}:
244
+ base = move.rstrip("+#")
245
+ suffix = move[len(base):]
246
+ return [base] + ([suffix] if suffix else [])
247
+
248
+ out = []
249
+ i = 0
250
+ n = len(move)
251
+
252
+ # Get piece letter (required in LAN format)
253
+ if i < n and move[i] in "KQRBNP":
254
+ out.append(move[i])
255
+ i += 1
256
+ else:
257
+ # No piece letter - might be malformed, return as-is
258
+ return [move]
259
+
260
+ # Get from square (required in LAN format)
261
+ if i + 1 < n and move[i] in FILES and move[i + 1] in RANKS:
262
+ out.append(move[i:i+2])
263
+ i += 2
264
+
265
+ # Handle capture
266
+ if i < n and move[i] == "x":
267
+ out.append("x")
268
+ i += 1
269
+
270
+ # Get to square (required in LAN format)
271
+ if i + 1 < n and move[i] in FILES and move[i + 1] in RANKS:
272
+ out.append(move[i:i+2])
273
+ i += 2
274
+
275
+ # Handle promotion
276
+ if i < n and move[i] == "=":
277
+ out.append("=")
278
+ i += 1
279
+ if i < n and move[i] in PROMOS:
280
+ out.append(move[i])
281
+ i += 1
282
+
283
+ # Handle check/checkmate
284
+ if i < n and move[i] in "+#":
285
+ out.append(move[i])
286
+ i += 1
287
+
288
+ return out
289
+
290
+ def _active_env_tokens(self) -> set:
291
+ """Return the set of env tokens that are active for this instance."""
292
+ return set(ENV_TOKENS) if self._include_env_tokens else set()
293
+
294
+ def _cot_to_tokens(self, text: str) -> List[str]:
295
+ """
296
+ Convert CoT formatted text to tokens.
297
+ Handles special tokens and LAN moves.
298
+ """
299
+ env_toks = self._active_env_tokens()
300
+ out = []
301
+ for token in text.split():
302
+ if token in {self.T, self.T_END, self.SEP} or token in env_toks:
303
+ # Keep special tokens as-is
304
+ out.append(token)
305
+ elif token in _RESULT:
306
+ # Game result
307
+ out.append(token)
308
+ elif token and token[0].isdigit() and "." in token:
309
+ # Move number like "1." or "15..."
310
+ # Split into digits and dots
311
+ num_part = token.rstrip(".")
312
+ dot_part = token[len(num_part):]
313
+ out.extend(list(num_part))
314
+ if dot_part:
315
+ out.append("..." if len(dot_part) > 1 else ".")
316
+ elif token and all(c.isdigit() for c in token):
317
+ # Pure number - tokenize each digit
318
+ out.extend(list(token))
319
+ else:
320
+ # LAN move - tokenize it
321
+ out.extend(self._lan_move_to_tokens(token))
322
+ return out
323
+
324
+ def encode(self, text: str) -> List[int]:
325
+ """
326
+ Encode text to token IDs.
327
+
328
+ Args:
329
+ text: Text to encode (can be PGN or CoT formatted)
330
+
331
+ Returns:
332
+ List of token IDs
333
+ """
334
+ # Check if this is CoT-formatted text (contains special tokens)
335
+ sft_special = (
336
+ [self.T, self.T_END, self.SEP]
337
+ + (ENV_TOKENS if self._include_env_tokens else [])
338
+ )
339
+ is_cot_format = any(token in text for token in sft_special)
340
+
341
+ if is_cot_format:
342
+ t_idx = text.index(self.T)
343
+ prompt_part = text[:t_idx].strip()
344
+ rest_part = text[t_idx:] # starts with <T>
345
+
346
+ pgn_tokens = self._pgn_to_tokens(prompt_part) if prompt_part else None
347
+ if pgn_tokens is None:
348
+ pgn_tokens = self._cot_to_tokens(prompt_part) if prompt_part else []
349
+ rest_tokens = self._cot_to_tokens(rest_part)
350
+ tokens = [self._bos] + pgn_tokens + rest_tokens + [self._eos]
351
+ else:
352
+ pgn_tokens = self._pgn_to_tokens(text)
353
+ if pgn_tokens is not None and len(pgn_tokens) > 0:
354
+ tokens = [self._bos] + pgn_tokens + [self._eos]
355
+ else:
356
+ # Not valid PGN — treat each word as a LAN move
357
+ print(f"Invalid PGN: {text}")
358
+ lan_tokens = []
359
+ for word in text.split():
360
+ print(word)
361
+ lan_tokens.extend(self._lan_move_to_tokens(word))
362
+ tokens = [self._bos] + lan_tokens + [self._eos]
363
+
364
+ return self.tk.encode(" ".join(tokens)).ids
365
+
366
+ def decode(self, ids: List[int]) -> str:
367
+ """
368
+ Decode token IDs to text.
369
+
370
+ Args:
371
+ ids: List of token IDs
372
+
373
+ Returns:
374
+ Decoded text
375
+ """
376
+ toks = [t for t in self.tk.decode(ids).split() if t not in {self._bos, self._eos}]
377
+
378
+ # Otherwise, use LAN decoding logic
379
+ out: List[str] = []
380
+ i, n = 0, len(toks)
381
+
382
+ while i < n:
383
+ t = toks[i]
384
+
385
+ if t in {self.T, self.T_END, self.SEP} or t in _RESULT or t in self._active_env_tokens():
386
+ out.append(t)
387
+ i += 1
388
+ continue
389
+
390
+ if t and all(ch in DIGITS for ch in t):
391
+ j = i
392
+ num = []
393
+ while j < n and all(ch in DIGITS for ch in toks[j]):
394
+ num.append(toks[j])
395
+ j += 1
396
+ dots = ""
397
+ if j < n and toks[j] in {".", "..."}:
398
+ dots = toks[j]
399
+ j += 1
400
+ out.append("".join(num) + dots)
401
+ i = j
402
+ continue
403
+
404
+ if t in {"O-O", "O-O-O"}:
405
+ j = i + 1
406
+ suf = toks[j] if j < n and toks[j] in {"+", "#"} else ""
407
+ if suf:
408
+ j += 1
409
+ out.append(t + suf)
410
+ i = j
411
+ continue
412
+
413
+ if t in set("KQRBNP"):
414
+ piece = t
415
+ j = i + 1
416
+ frm = toks[j] if j < n else ""
417
+ j += 1
418
+ cap = ""
419
+ if j < n and toks[j] == "x":
420
+ cap = "x"
421
+ j += 1
422
+ to = toks[j] if j < n else ""
423
+ j += 1
424
+ promo = ""
425
+ if j + 1 <= n - 1 and toks[j] == "=" and toks[j + 1] in set(PROMOS):
426
+ promo = "=" + toks[j + 1]
427
+ j += 2
428
+ suf = ""
429
+ if j < n and toks[j] in {"+", "#"}:
430
+ suf = toks[j]
431
+ j += 1
432
+ lan = f"{piece}{frm}{cap}{to}{promo}{suf}"
433
+ out.append(lan)
434
+ i = j
435
+ continue
436
+
437
+ out.append(t)
438
+ i += 1
439
+
440
+ return " ".join(out)
441
+
442
+ def get_vocab(self) -> Dict[str, int]:
443
+ """Get token-to-id vocabulary mapping."""
444
+ return self._tok2id
445
+
446
+ def bos_id(self) -> Optional[int]:
447
+ """Get BOS token ID."""
448
+ return self._tok2id[self._bos]
449
+
450
+ def eos_id(self) -> Optional[int]:
451
+ """Get EOS token ID."""
452
+ return self._tok2id[self._eos]
453
+
454
+ def pad_id(self) -> Optional[int]:
455
+ """Get PAD token ID (uses BOS as pad by default)."""
456
+ return self._tok2id.get("<pad>", self.bos_id())
457
+
458
+ def get_vocab_size(self) -> int:
459
+ """Get vocabulary size."""
460
+ return len(self._tok2id)
461
+
462
+ def t_id(self) -> int:
463
+ """Get <T> token ID."""
464
+ return self._tok2id[self.T]
465
+
466
+ def sep_id(self) -> int:
467
+ """Get <sep> token ID."""
468
+ return self._tok2id[self.SEP]
469
+
470
+ def t_end_id(self) -> int:
471
+ """Get </T> token ID."""
472
+ return self._tok2id[self.T_END]
473
+
474
+ # ------------------------------------------------------------------
475
+ # Environment / reward token accessors
476
+ # ------------------------------------------------------------------
477
+
478
+ def _require_env_tokens(self) -> None:
479
+ if not self._include_env_tokens:
480
+ raise ValueError(
481
+ "Environment tokens are not enabled. "
482
+ "Pass include_env_tokens=True in the config."
483
+ )
484
+
485
+ def call_env_id(self) -> int:
486
+ """Get <call_env> token ID."""
487
+ self._require_env_tokens()
488
+ return self._tok2id[CALL_ENV_TOKEN]
489
+
490
+ def verify_id(self) -> int:
491
+ """Get <verify> token ID."""
492
+ self._require_env_tokens()
493
+ return self._tok2id[VERIFY_TOKEN]
494
+
495
+ def reward_pos_id(self) -> int:
496
+ """Get <+1> (positive reward) token ID."""
497
+ self._require_env_tokens()
498
+ return self._tok2id[REWARD_POS_TOKEN]
499
+
500
+ def reward_neg_id(self) -> int:
501
+ """Get <-1> (negative reward) token ID."""
502
+ self._require_env_tokens()
503
+ return self._tok2id[REWARD_NEG_TOKEN]
504
+
505
+ def reward_zero_id(self) -> int:
506
+ """Get <0> (zero reward) token ID."""
507
+ self._require_env_tokens()
508
+ return self._tok2id[REWARD_ZERO_TOKEN]
509
+
510
+ def reward_id(self, value) -> int:
511
+ """
512
+ Get reward token ID by numeric value.
513
+ Args:
514
+ value: 1, -1, or 0 (or the strings "+1", "-1", "0")
515
+ Returns:
516
+ Token ID for the corresponding reward token.
517
+ """
518
+ self._require_env_tokens()
519
+ mapping = {1: REWARD_POS_TOKEN, -1: REWARD_NEG_TOKEN, 0: REWARD_ZERO_TOKEN,
520
+ "+1": REWARD_POS_TOKEN, "-1": REWARD_NEG_TOKEN, "0": REWARD_ZERO_TOKEN}
521
+ if value not in mapping:
522
+ raise ValueError(f"reward value must be one of 1, -1, 0 (or '+1', '-1', '0'), got {value!r}")
523
+ return self._tok2id[mapping[value]]
524
+
525
+ def env_token_ids(self) -> Dict[str, int]:
526
+ """Get mapping of all env/reward special tokens to their IDs."""
527
+ self._require_env_tokens()
528
+ return {tok: self._tok2id[tok] for tok in ENV_TOKENS}
529
+
530
+ def extract_parts(self, text: str) -> Tuple[Optional[str], Optional[List[str]], str]:
531
+ """
532
+ Extract prompt, trajectories and answer from BoN CoT formatted text.
533
+
534
+ Args:
535
+ text: Text in format: {prompt} <T> <sep> {traj1} <sep> ... <sep> <T> {answer}
536
+
537
+ Returns:
538
+ prompt: The prompt/context (or None if not present)
539
+ trajectories: List of trajectory strings (or None if not present)
540
+ answer: The final answer
541
+ """
542
+ if self.T not in text:
543
+ return None, None, text
544
+
545
+ try:
546
+ # Split by <T> to get prompt, thinking section, and answer
547
+ t_parts = text.split(self.T)
548
+ if len(t_parts) < 3:
549
+ return None, None, text
550
+
551
+ # t_parts[0] is prompt (before first <T>)
552
+ # t_parts[1] is the thinking section with trajectories
553
+ # t_parts[2] is the answer
554
+ prompt = t_parts[0].strip() if t_parts[0].strip() else None
555
+ thinking_section = t_parts[1].strip()
556
+ answer = t_parts[2].strip()
557
+
558
+ # Split thinking section by <sep> to get trajectories
559
+ trajectories = [t.strip() for t in thinking_section.split(self.SEP) if t.strip()]
560
+
561
+ return prompt, trajectories, answer
562
+ except (ValueError, IndexError):
563
+ return None, None, text
564
+
565
+ def extract_thinking_and_answer(self, text: str) -> Tuple[Optional[List[str]], str]:
566
+ """
567
+ Extract trajectories and answer from BoN CoT formatted text (ignores prompt).
568
+
569
+ Args:
570
+ text: Text in format: {prompt} <T> <sep> {traj1} <sep> ... <sep> <T> {answer}
571
+
572
+ Returns:
573
+ trajectories: List of trajectory strings (or None if not present)
574
+ answer: The final answer
575
+ """
576
+ _, trajectories, answer = self.extract_parts(text)
577
+ return trajectories, answer
578
+
579
+ def get_sft_special_tokens(self) -> List[str]:
580
+ """Get list of SFT special tokens (including env/reward tokens if enabled)."""
581
+ toks = [self.T, self.T_END, self.SEP]
582
+ if self._include_env_tokens:
583
+ toks += ENV_TOKENS
584
+ return toks
585
+
586
+ def get_sft_token_ids(self) -> Dict[str, int]:
587
+ """Get mapping of SFT special tokens to their IDs."""
588
+ result = {
589
+ self.T: self._tok2id[self.T],
590
+ self.T_END: self._tok2id[self.T_END],
591
+ self.SEP: self._tok2id[self.SEP],
592
+ }
593
+ if self._include_env_tokens:
594
+ for tok in ENV_TOKENS:
595
+ result[tok] = self._tok2id[tok]
596
+ return result
597
+
598
+ def parse_cot_line(self, line: str) -> Tuple[Optional[List[str]], Optional[str]]:
599
+ """
600
+ Parse a CoT data line in format: <T> <sep> ... <sep> <T> {answer}
601
+
602
+ Args:
603
+ line: A line from the CoT data file
604
+
605
+ Returns:
606
+ trajectories: List of trajectory strings
607
+ answer: The final answer/move
608
+ """
609
+ line = line.strip()
610
+ if not line or not line.startswith(self.T):
611
+ return None, None
612
+
613
+ return self.extract_thinking_and_answer(line)
614
+
615
+ # ============================================================
616
+ # HuggingFace-compatible wrapper (auto-generated)
617
+ # ============================================================
618
+ import json as _json
619
+ from pathlib import Path as _Path
620
+ from transformers import PreTrainedTokenizer
621
+ import torch
622
+ from transformers.tokenization_utils_base import BatchEncoding
623
+
624
+ from huggingface_hub import hf_hub_download
625
+
626
+ class HFTokenizerWrapper(PreTrainedTokenizer):
627
+ def __init__(self, model_max_length=2048, **kwargs):
628
+ # These are usually provided by from_pretrained
629
+ repo_id = kwargs.get("name_or_path") or kwargs.get("_name_or_path")
630
+ revision = kwargs.get("revision", None)
631
+
632
+ if not repo_id or "/" not in str(repo_id):
633
+ # Fallback: user may pass repo_id explicitly
634
+ repo_id = kwargs.get("repo_id", None)
635
+ if not repo_id:
636
+ raise ValueError("Cannot infer repo_id; pass repo_id=... or ensure name_or_path is set.")
637
+
638
+ vocab_path = hf_hub_download(repo_id=repo_id, filename="vocab.json", revision=revision)
639
+ cfg_path = hf_hub_download(repo_id=repo_id, filename="tokenizer_config.json", revision=revision)
640
+
641
+ with open(vocab_path, "r", encoding="utf-8") as _f:
642
+ saved_vocab = _json.load(_f)
643
+ with open(cfg_path, "r", encoding="utf-8") as _f:
644
+ _tok_cfg = _json.load(_f)
645
+
646
+ lan_config = _tok_cfg.get("lan_config", {})
647
+ lan_class_name = _tok_cfg.get("lan_tokenizer_class", "LanTokenizerSFT")
648
+
649
+ _cls = globals()[lan_class_name]
650
+ custom_tokenizer = _cls(config=lan_config)
651
+
652
+ # Override vocab with the saved vocab
653
+ custom_tokenizer._tok2id = saved_vocab
654
+ from tokenizers import Tokenizer as _TkTokenizer
655
+ from tokenizers.models import WordLevel as _WordLevel
656
+ from tokenizers.pre_tokenizers import WhitespaceSplit as _WhitespaceSplit
657
+ custom_tokenizer.tk = _TkTokenizer(_WordLevel(vocab=saved_vocab, unk_token=custom_tokenizer._unk))
658
+ custom_tokenizer.tk.pre_tokenizer = _WhitespaceSplit()
659
+
660
+ self.custom_tokenizer = custom_tokenizer
661
+ self._vocab = dict(saved_vocab)
662
+ self._id_to_token = {i: t for t, i in self._vocab.items()}
663
+
664
+ bos_token = _tok_cfg.get("bos_token")
665
+ eos_token = _tok_cfg.get("eos_token")
666
+ pad_token = _tok_cfg.get("pad_token")
667
+ unk_token = _tok_cfg.get("unk_token")
668
+ env_token = _tok_cfg.get("env_token")
669
+ self.env_token = env_token
670
+
671
+ for _key in ("bos_token","eos_token","pad_token","unk_token","env_token",
672
+ "model_max_length","name_or_path","lan_config",
673
+ "lan_tokenizer_class","tokenizer_class","auto_map","use_fast",
674
+ "revision","repo_id"):
675
+ kwargs.pop(_key, None)
676
+
677
+ super().__init__(
678
+ bos_token=bos_token,
679
+ eos_token=eos_token,
680
+ pad_token=pad_token,
681
+ unk_token=unk_token,
682
+ model_max_length=model_max_length,
683
+ **kwargs,
684
+ )
685
+
686
+ # ---- PreTrainedTokenizer interface ----
687
+
688
+ @property
689
+ def vocab_size(self):
690
+ return len(self._vocab)
691
+
692
+ def get_vocab(self):
693
+ return dict(self._vocab)
694
+
695
+ def _tokenize(self, text):
696
+ return [] # we override encode/decode directly
697
+
698
+ def _convert_token_to_id(self, token):
699
+ return self._vocab.get(token, self._vocab.get(self.unk_token, 0))
700
+
701
+ def _convert_id_to_token(self, index):
702
+ return self._id_to_token.get(index, self.unk_token or "")
703
+
704
+ def convert_tokens_to_string(self, tokens):
705
+ ids = [self._convert_token_to_id(t) for t in tokens]
706
+ return self.custom_tokenizer.decode(ids)
707
+
708
+ def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
709
+ if token_ids_1 is None:
710
+ return token_ids_0
711
+ return token_ids_0 + token_ids_1
712
+
713
+ def encode(self, text, add_special_tokens=True, **kwargs):
714
+ ids = self.custom_tokenizer.encode(text)
715
+ if add_special_tokens:
716
+ return ids[:-1] # strip trailing EOS; vLLM adds its own
717
+ if (len(ids) >= 2
718
+ and self.bos_token_id is not None
719
+ and self.eos_token_id is not None
720
+ and ids[0] == self.bos_token_id
721
+ and ids[-1] == self.eos_token_id):
722
+ return ids[1:-1]
723
+ return ids
724
+
725
+ def decode(self, token_ids, skip_special_tokens=True, **kwargs):
726
+ import numpy as np
727
+ if isinstance(token_ids, torch.Tensor):
728
+ token_ids = token_ids.detach().cpu().tolist()
729
+ elif isinstance(token_ids, np.ndarray):
730
+ token_ids = token_ids.tolist()
731
+ return self.custom_tokenizer.decode(token_ids)
732
+
733
+ def save_vocabulary(self, save_directory, filename_prefix=None):
734
+ save_directory = _Path(save_directory)
735
+ save_directory.mkdir(parents=True, exist_ok=True)
736
+ vocab_file = save_directory / (
737
+ (filename_prefix + "-" if filename_prefix else "") + "vocab.json"
738
+ )
739
+ with open(vocab_file, "w", encoding="utf-8") as f:
740
+ _json.dump(self._vocab, f, ensure_ascii=False, indent=2)
741
+ return (str(vocab_file),)
742
+
743
+ def __call__(
744
+ self,
745
+ text,
746
+ text_pair=None,
747
+ add_special_tokens=True,
748
+ truncation=False,
749
+ max_length=None,
750
+ padding=False,
751
+ return_tensors=None,
752
+ **kwargs,
753
+ ):
754
+ if text_pair is not None:
755
+ raise ValueError("text_pair not supported for this tokenizer.")
756
+
757
+ # Normalize to batch
758
+ is_batched = isinstance(text, (list, tuple))
759
+ texts = list(text) if is_batched else [text]
760
+
761
+ input_ids = [self.encode(t, add_special_tokens=add_special_tokens) for t in texts]
762
+
763
+ # Truncation
764
+ if truncation and max_length is not None:
765
+ if self.truncation_side == "left":
766
+ input_ids = [ids[-max_length:] for ids in input_ids]
767
+ else:
768
+ input_ids = [ids[:max_length] for ids in input_ids]
769
+
770
+ # Attention masks (pre-padding)
771
+ attention_mask = [[1] * len(ids) for ids in input_ids]
772
+
773
+ # Padding
774
+ if padding:
775
+ if padding == "max_length":
776
+ if max_length is None:
777
+ raise ValueError("padding='max_length' requires max_length.")
778
+ pad_to = max_length
779
+ else:
780
+ pad_to = max(len(ids) for ids in input_ids) if input_ids else 0
781
+
782
+ pad_id = self.pad_token_id
783
+ if pad_id is None:
784
+ pad_id = self.bos_token_id if self.bos_token_id is not None else 0
785
+
786
+ for i, ids in enumerate(input_ids):
787
+ pad_len = pad_to - len(ids)
788
+ if pad_len > 0:
789
+ input_ids[i] = ids + [pad_id] * pad_len
790
+ attention_mask[i] = attention_mask[i] + [0] * pad_len
791
+
792
+ data = {"input_ids": input_ids, "attention_mask": attention_mask}
793
+
794
+ # Unbatch if single example and no tensor return
795
+ if not is_batched and return_tensors is None:
796
+ data = {"input_ids": data["input_ids"][0], "attention_mask": data["attention_mask"][0]}
797
+
798
+ # Tensors
799
+ if return_tensors == "pt":
800
+ data = {k: torch.tensor(v, dtype=torch.long) for k, v in data.items()}
801
+
802
+ return BatchEncoding(data, tensor_type=None)
803
+
804
+
805
+ __all__ = ["HFTokenizerWrapper"]
tokenizer_config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tokenizer_class": "HFTokenizerWrapper",
3
+ "auto_map": {
4
+ "AutoTokenizer": [
5
+ "tokenizer.HFTokenizerWrapper",
6
+ null
7
+ ]
8
+ },
9
+ "model_max_length": 2048,
10
+ "bos_token": "<bos>",
11
+ "eos_token": "<eos>",
12
+ "pad_token": "<bos>",
13
+ "unk_token": "<unk>",
14
+ "use_fast": false,
15
+ "lan_config": {
16
+ "name": "LanTokenizerSFT",
17
+ "include_move_numbers": false,
18
+ "include_black_tripledots": false,
19
+ "bos": "<bos>",
20
+ "eos": "<eos>",
21
+ "unk": "<unk>",
22
+ "keep_result": false,
23
+ "include_env_tokens": true,
24
+ "include_reward_tokens": true
25
+ },
26
+ "lan_tokenizer_class": "LanTokenizerSFT"
27
+ }
vocab.json ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "<bos>": 0,
3
+ "<eos>": 1,
4
+ "<unk>": 2,
5
+ "K": 3,
6
+ "Q": 4,
7
+ "R": 5,
8
+ "B": 6,
9
+ "N": 7,
10
+ "P": 8,
11
+ "a1": 9,
12
+ "a2": 10,
13
+ "a3": 11,
14
+ "a4": 12,
15
+ "a5": 13,
16
+ "a6": 14,
17
+ "a7": 15,
18
+ "a8": 16,
19
+ "b1": 17,
20
+ "b2": 18,
21
+ "b3": 19,
22
+ "b4": 20,
23
+ "b5": 21,
24
+ "b6": 22,
25
+ "b7": 23,
26
+ "b8": 24,
27
+ "c1": 25,
28
+ "c2": 26,
29
+ "c3": 27,
30
+ "c4": 28,
31
+ "c5": 29,
32
+ "c6": 30,
33
+ "c7": 31,
34
+ "c8": 32,
35
+ "d1": 33,
36
+ "d2": 34,
37
+ "d3": 35,
38
+ "d4": 36,
39
+ "d5": 37,
40
+ "d6": 38,
41
+ "d7": 39,
42
+ "d8": 40,
43
+ "e1": 41,
44
+ "e2": 42,
45
+ "e3": 43,
46
+ "e4": 44,
47
+ "e5": 45,
48
+ "e6": 46,
49
+ "e7": 47,
50
+ "e8": 48,
51
+ "f1": 49,
52
+ "f2": 50,
53
+ "f3": 51,
54
+ "f4": 52,
55
+ "f5": 53,
56
+ "f6": 54,
57
+ "f7": 55,
58
+ "f8": 56,
59
+ "g1": 57,
60
+ "g2": 58,
61
+ "g3": 59,
62
+ "g4": 60,
63
+ "g5": 61,
64
+ "g6": 62,
65
+ "g7": 63,
66
+ "g8": 64,
67
+ "h1": 65,
68
+ "h2": 66,
69
+ "h3": 67,
70
+ "h4": 68,
71
+ "h5": 69,
72
+ "h6": 70,
73
+ "h7": 71,
74
+ "h8": 72,
75
+ "x": 73,
76
+ "=": 74,
77
+ "+": 75,
78
+ "#": 76,
79
+ "O-O": 77,
80
+ "O-O-O": 78,
81
+ ".": 79,
82
+ "...": 80,
83
+ "<T>": 81,
84
+ "</T>": 82,
85
+ "<sep>": 83,
86
+ "<call_env>": 84,
87
+ "<verify>": 85,
88
+ "<+1>": 86,
89
+ "<-1>": 87,
90
+ "<0>": 88
91
+ }