CoolFace
Datasetpublic

Kalso42/WorldModelForMaze

WorldModelForMaze Code, datasets, and trained checkpoints for studying world-model representations in maze navigation, based on a modified NanoGPT. Contents *.py — training, testing, probing, and visualization scripts (see readme.md). model/ — architectures: transformer, transformer-rope, transformer-nextlat, mamba, mamba2, gated-deltanet, gru. data/maze/100/ — tokenized maze datasets for Tasks A/C/E/H/I (RWs paths, 100 nodes). out/ — final (10000-iter)… See the full description on the dataset page: https://huggingface.co/datasets/Kalso42/WorldModelForMaze.

sourceHugging Facemitupdated 3mo agoView on Hugging Face
0likes623downloads
prepare_multitask_minigpt.py323 linesDownload Raw Back to maze
1import os
2import sys
3import pickle
4import numpy as np
5import re
6import argparse
7from tqdm import tqdm
8
9# Ensure project root is importable when running this script directly
10sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
11from cli_utils import parse_count, format_count
12
13parser = argparse.ArgumentParser(description='Create the multitask dataset based on the given parameters.')
14parser.add_argument('--num_nodes', type=int, default=100, help='Number of nodes in the graph')
15parser.add_argument('--num_train_dataset', type=parse_count, default='10M',
16                    help='Number of training data entries to use (supports K/M/B, default: 50000)')
17parser.add_argument('--num_test_dataset', type=parse_count, default=10000,
18                    help='Number of test data entries to use (supports K/M/B, default: 10000)')
19parser.add_argument('--tasks', type=str, default='H1',
20                    help='Task specification (e.g., A1, A1B1, A3B2, A1D1F1). Default: A1')
21parser.add_argument('--CL', action=argparse.BooleanOptionalAction, default=False,
22                    help='Enable Task C label mode (append node labels after L/R turns) and add _CL_ in filenames')
23parser.add_argument('--path_type', type=str, default='RWs', choices=['RWc', 'RWa', 'RWs'],
24                    help='Path generation type: RWc (random walk with cycles), RWa (random walk acyclic, default), RWs (single source random walk).')
25# Arguments for task tag handling
26parser.add_argument('--no_task_tag', action='store_true', default=False,
27                    help='Data files do not contain task identifiers (A, B, C, etc.). When enabled, task tokens will not be included in vocabulary and data parsing will skip task tags.')
28parser.add_argument('--both', action='store_true', default=False,
29                    help='Process both versions (with and without task tags). When set, --no_task_tag is ignored and two sets of bin files and meta files are produced.')
30parser.add_argument('--num_labels', type=int, default=10,
31                    help='Number of distinct node labels (default: 10). Must match the value used in data generation.')
32parser.add_argument('--num_workers', type=int, default=1,
33                    help='Number of parallel worker processes for encoding (default: 1 = serial). Uses fork; requires Linux/macOS.')
34args = parser.parse_args()
35
36num_nodes = args.num_nodes
37tasks_str = args.tasks
38tasks_tag_base = f"{tasks_str}_CL" if args.CL else tasks_str
39# Add path type tag for filenames
40path_type_tag = args.path_type
41tasks_tag_base = f"{tasks_tag_base}_{path_type_tag}"
42# Include num_labels in tag when non-default (match create_multitask_maze.py)
43if args.num_labels != 10:
44    tasks_tag_base = f"{tasks_tag_base}_L{args.num_labels}"
45
46train_label = format_count(args.num_train_dataset)
47test_label = format_count(args.num_test_dataset)
48num_labels = args.num_labels
49
50def first_existing(paths):
51    for p in paths:
52        if os.path.exists(p):
53            return p
54    return paths[0]
55
56
57def process_data_for_tag_mode(no_task_tag_mode, tasks_tag_suffix=""):
58    """Process data for a specific task tag mode."""
59    # Construct tasks_tag for this mode
60    if no_task_tag_mode:
61        tasks_tag = f"{tasks_tag_base}_NT"
62        if tasks_tag_suffix:
63            tasks_tag = f"{tasks_tag}_{tasks_tag_suffix}"
64    else:
65        tasks_tag = tasks_tag_base
66        if tasks_tag_suffix:
67            tasks_tag = f"{tasks_tag}_{tasks_tag_suffix}"
68
69    # Find input files
70    train_file_path = first_existing([
71        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/train_{tasks_tag}_{train_label}.txt'),
72        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/train_{tasks_tag}_{args.num_train_dataset}.txt'),
73        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/train_{tasks_str}_{train_label}.txt'),
74        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/train_{tasks_str}_{args.num_train_dataset}.txt'),
75    ])
76    val_file_path = first_existing([
77        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/test_{tasks_tag}_{test_label}.txt'),
78        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/test_{tasks_tag}_{args.num_test_dataset}.txt'),
79        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/test_{tasks_str}_{test_label}.txt'),
80        os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/test_{tasks_str}_{args.num_test_dataset}.txt'),
81    ])
82
83    print(f"\nProcessing mode: {'Without task tags' if no_task_tag_mode else 'With task tags'}")
84    print(f"Training file: {train_file_path}")
85    print(f"Test file: {val_file_path}")
86
87    with open(train_file_path, 'r') as f:
88        train_data = f.read()
89    print(f"length of train dataset in characters: {len(train_data):,}")
90
91    with open(val_file_path, 'r') as f:
92        val_data = f.read()
93    print(f"length of val dataset in characters: {len(val_data):,}")
94
95    all_data = train_data + val_data
96
97    chars = sorted(list(find_characters(all_data)))
98    direction_tokens = ['N', 'S', 'E', 'W', 'L', 'R', 'F', 'T']
99    # Only include task tokens if no_task_tag_mode is False
100    task_tokens = [] if no_task_tag_mode else ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
101    label_tokens = ([chr(ord('a') + i) for i in range(num_labels)] if num_labels <= 26
102                    else [f'l{i}' for i in range(num_labels)])
103    label_tokens.append('/')  # separator token for neighbor labels
104    special_tokens = [':']
105    # Adjust vocab_size calculation
106    vocab_size = num_nodes + 2 + len(direction_tokens) + len(task_tokens) + len(label_tokens) + len(special_tokens)
107    print("all the unique characters:", ' '.join(chars))
108    print(f"vocab size: {vocab_size:,}")
109    print(f"No task tag mode: {'Enabled' if no_task_tag_mode else 'Disabled'}")
110
111    stoi = {}
112    itos = {}
113
114    for i in range(num_nodes):
115        stoi[str(i)] = i + 2
116        itos[i + 2] = str(i)
117
118    base = 2 + num_nodes
119    for idx, tok in enumerate(direction_tokens):
120        stoi[tok] = base + idx
121        itos[base + idx] = tok
122
123    # Only add task tokens to vocabulary if no_task_tag_mode is False
124    if not no_task_tag_mode:
125        base = 2 + num_nodes + len(direction_tokens)
126        for idx, tok in enumerate(task_tokens):
127            stoi[tok] = base + idx
128            itos[base + idx] = tok
129        base = 2 + num_nodes + len(direction_tokens) + len(task_tokens)
130    else:
131        base = 2 + num_nodes + len(direction_tokens)
132
133    for idx, tok in enumerate(label_tokens):
134        stoi[tok] = base + idx
135        itos[base + idx] = tok
136
137    base = base + len(label_tokens)
138    for idx, tok in enumerate(special_tokens):
139        stoi[tok] = base + idx
140        itos[base + idx] = tok
141
142    stoi['[PAD]'] = 0
143    itos[0] = '[PAD]'
144    stoi['\n'] = 1
145    itos[1] = '\n'
146
147    def encode(s):
148        ss = s.split(" ")
149        return [stoi[ch] for ch in ss]
150
151    def decode(l):
152        return ' '.join(itos[i] for i in l)
153
154    # Calculate block_size with theoretical minimum
155    n = int(num_nodes ** 0.5)  # grid size
156    if no_task_tag_mode:
157        theoretical_min_tokens = num_nodes + 3
158    else:
159        theoretical_min_tokens = num_nodes + 4
160
161    theoretical_min_block_size = (theoretical_min_tokens // 32 + 1) * 32
162
163    nw = args.num_workers
164
165    def get_block_size(s, desc="scan block size"):
166        split_text = s.split('\n')
167        if nw and nw > 1 and len(split_text) > 0:
168            import multiprocessing as mp
169            chunk_size = max(1, min(20000, len(split_text) // (nw * 50) or 1))
170            chunks = _chunk_list(split_text, chunk_size)
171            ctx = mp.get_context('fork')
172            bs = 0
173            with ctx.Pool(processes=nw) as pool:
174                with tqdm(total=len(split_text), desc=desc) as pbar:
175                    for r in pool.imap_unordered(_prep_max_len_batch, chunks):
176                        if r > bs:
177                            bs = r
178                        pbar.update(chunk_size if pbar.n + chunk_size <= len(split_text)
179                                    else len(split_text) - pbar.n)
180            return bs
181        # Serial
182        bs = 0
183        for st in tqdm(split_text, desc=desc):
184            if st != "":
185                enc_str = encode(st) + [1]
186                bs = max(bs, len(enc_str))
187        return bs
188
189    data_block_size = (max(get_block_size(train_data, desc="scan train block size"),
190                           get_block_size(val_data, desc="scan val block size")) // 32 + 1) * 32
191    block_size = max(theoretical_min_block_size, data_block_size)
192    print(
193        f"the block size is {block_size} (theoretical min: {theoretical_min_block_size}, data-based: {data_block_size})")
194
195    def process_reasoning(s, desc="encode"):
196        split_text = s.split('\n')
197        if nw and nw > 1 and len(split_text) > 0:
198            import multiprocessing as mp
199            chunk_size = max(1, min(10000, len(split_text) // (nw * 100) or 1))
200            chunks = _chunk_list(split_text, chunk_size)
201            ctx = mp.get_context('fork')
202            ret = []
203            with ctx.Pool(processes=nw,
204                          initializer=_prep_worker_init,
205                          initargs=(stoi, block_size)) as pool:
206                with tqdm(total=len(split_text), desc=desc) as pbar:
207                    # Use imap (ordered) to preserve original line order in output.
208                    for i, r in enumerate(pool.imap(_prep_encode_batch, chunks)):
209                        ret.extend(r)
210                        step = len(chunks[i])
211                        pbar.update(step)
212            return ret
213        # Serial
214        ret = []
215        for st in tqdm(split_text, desc=desc):
216            if st != "":
217                enc_str = encode(st) + [1]
218                ret += enc_str + [0] * (block_size + 1 - len(enc_str))
219        return ret
220
221    train_ids = process_reasoning(train_data, desc="encode train")
222    val_ids = process_reasoning(val_data, desc="encode val")
223
224    print(f"train has {len(train_ids):,} tokens")
225    print(f"val has {len(val_ids):,} tokens")
226
227    train_ids = np.array(train_ids, dtype=np.uint16)
228    val_ids = np.array(val_ids, dtype=np.uint16)
229
230    # Save bin files with appropriate tag
231    train_ids.tofile(os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/train_{tasks_tag}_{train_label}.bin'))
232    val_ids.tofile(os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/val_{tasks_tag}_{test_label}.bin'))
233
234    unreachable = 'x' in chars
235    simple_format = ':' not in chars
236
237    meta = {
238        'unreachable': unreachable,
239        'simple_format': simple_format,
240        'block_size': block_size,
241        'vocab_size': vocab_size,
242        'itos': itos,
243        'stoi': stoi,
244        'no_task_tag': no_task_tag_mode,
245    }
246
247    print(stoi)
248    print(itos)
249    with open(os.path.join(os.path.dirname(__file__), f'{args.num_nodes}/meta_{tasks_tag}.pkl'), 'wb') as f:
250        pickle.dump(meta, f)
251
252    print(f"Saved files with tag: {tasks_tag}")
253    return tasks_tag
254
255
256def find_characters(data_string):
257    pattern = r'\d+|\D'
258    matches = re.findall(pattern, data_string)
259    return set(matches)
260
261
262# ---- Parallel worker helpers (must be at module scope for pickling) ----
263_W_STOI = None
264_W_BLOCK_SIZE = None
265
266
267def _prep_worker_init(stoi_arg, block_size_arg):
268    global _W_STOI, _W_BLOCK_SIZE
269    _W_STOI = stoi_arg
270    _W_BLOCK_SIZE = block_size_arg
271
272
273def _prep_max_len_batch(lines):
274    """Return the max encoded length (tokens + EOL) over a batch of lines."""
275    bs = 0
276    for st in lines:
277        if st == "":
278            continue
279        # encoded length = number of space-separated tokens + 1 (EOL token)
280        L = st.count(" ") + 2
281        if L > bs:
282            bs = L
283    return bs
284
285
286def _prep_encode_batch(lines):
287    """Encode + pad a batch of lines; returns a flat list of token IDs."""
288    stoi = _W_STOI
289    bs1 = _W_BLOCK_SIZE + 1
290    out = []
291    for st in lines:
292        if st == "":
293            continue
294        enc = [stoi[ch] for ch in st.split(" ")]
295        enc.append(1)
296        out.extend(enc)
297        out.extend([0] * (bs1 - len(enc)))
298    return out
299
300
301def _chunk_list(lst, chunk_size):
302    return [lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)]
303
304
305# Main execution
306if args.both:
307    # Process both versions
308    print("=" * 60)
309    print("Generating both with-tag and without-tag versions")
310    print("=" * 60)
311
312    # Process with task tags
313    process_data_for_tag_mode(no_task_tag_mode=False)
314
315    # Process without task tags
316    process_data_for_tag_mode(no_task_tag_mode=True)
317
318    print("=" * 60)
319    print("Successfully generated both with-tag and without-tag datasets.")
320    print("=" * 60)
321else:
322    # Original logic: generate only one version based on no_task_tag
323    process_data_for_tag_mode(no_task_tag_mode=args.no_task_tag)