nathanael-fijalkow commited on
Commit
39126ad
·
1 Parent(s): 10cc877

fixed readme

Browse files
Files changed (2) hide show
  1. TEMPLATE_README.md +84 -368
  2. src/__init__.py +0 -20
TEMPLATE_README.md CHANGED
@@ -1,24 +1,28 @@
1
- # Chess Challenge
2
 
3
- Train a transformer with less than 1M parameters to play legal chess moves!
4
 
5
- ## Objective
 
 
6
 
7
- Design and train a transformer-based language model to predict chess moves. Your model must:
8
 
9
- 1. **Stay under 1M parameters** - This is the hard constraint!
10
- 2. **Create a custom tokenizer** - Design your own move-level tokenizer
11
- 3. **Create a custom model architecture** - Build your own transformer
12
- 4. **Play legal chess** - The model should learn to generate valid moves
13
- 5. **Do NOT use python-chess to filter moves** - The model must generate legal moves on its own
14
 
15
- ## Dataset
 
 
16
 
17
  We use the Lichess dataset: [`dlouapre/lichess_2025-01_1M`](https://huggingface.co/datasets/dlouapre/lichess_2025-01_1M)
18
 
19
- The dataset uses an extended UCI notation:
20
  - `W`/`B` prefix for White/Black
21
- - Piece letter: `P`=Pawn, `N`=Knight, `B`=Bishop, `R`=Rook, `Q`=Queen, `K`=King
22
  - Source and destination squares (e.g., `e2e4`)
23
  - Special suffixes: `(x)`=capture, `(+)`=check, `(+*)`=checkmate, `(o)`/`(O)`=castling
24
 
@@ -29,308 +33,73 @@ WPe2e4 BPe7e5 WNg1f3 BNb8c6 WBf1b5 BPa7a6 WBb5c6(x) BPd7c6(x) ...
29
 
30
  ---
31
 
32
- ## Building Your Solution
33
-
34
- You need to create **from scratch**:
35
 
36
- 1. A custom tokenizer class
37
- 2. A custom model architecture
38
- 3. A training script
39
- 4. Save everything in the correct format
40
 
41
- A complete working example is available in `example_solution/` - use it as reference, but build your own!
 
 
 
 
 
 
 
 
42
 
43
  ---
44
 
45
- ## Step 1: Create a Custom Tokenizer
46
-
47
- Your tokenizer must inherit from `PreTrainedTokenizer` and implement the required methods.
48
 
49
- ### Required Files
50
 
51
- Create a file called `tokenizer.py` with your tokenizer class:
52
 
53
- ```python
54
- import json
55
- from typing import Dict, List, Optional
56
- from transformers import PreTrainedTokenizer
57
-
58
-
59
- class MyChessTokenizer(PreTrainedTokenizer):
60
- """Custom tokenizer for chess moves."""
61
-
62
- # Tell HuggingFace which files to save/load
63
- vocab_files_names = {"vocab_file": "vocab.json"}
64
-
65
- def __init__(
66
- self,
67
- vocab_file: Optional[str] = None,
68
- **kwargs,
69
- ):
70
- # Define special tokens
71
- self.pad_token = "[PAD]"
72
- self.bos_token = "[BOS]"
73
- self.eos_token = "[EOS]"
74
- self.unk_token = "[UNK]"
75
-
76
- # Load or create vocabulary
77
- if vocab_file is not None:
78
- with open(vocab_file, "r") as f:
79
- self._vocab = json.load(f)
80
- else:
81
- # Create default vocab with special tokens
82
- self._vocab = {
83
- "[PAD]": 0,
84
- "[BOS]": 1,
85
- "[EOS]": 2,
86
- "[UNK]": 3,
87
- }
88
-
89
- self._ids_to_tokens = {v: k for k, v in self._vocab.items()}
90
-
91
- # Call parent init AFTER setting up vocab
92
- super().__init__(
93
- pad_token=self.pad_token,
94
- bos_token=self.bos_token,
95
- eos_token=self.eos_token,
96
- unk_token=self.unk_token,
97
- **kwargs,
98
- )
99
-
100
- @property
101
- def vocab_size(self) -> int:
102
- return len(self._vocab)
103
-
104
- def get_vocab(self) -> Dict[str, int]:
105
- return self._vocab.copy()
106
-
107
- def _tokenize(self, text: str) -> List[str]:
108
- """Split text into tokens (moves are space-separated)."""
109
- return text.strip().split()
110
-
111
- def _convert_token_to_id(self, token: str) -> int:
112
- return self._vocab.get(token, self._vocab.get(self.unk_token, 0))
113
-
114
- def _convert_id_to_token(self, index: int) -> str:
115
- return self._ids_to_tokens.get(index, self.unk_token)
116
-
117
- def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None):
118
- """Save vocabulary to a JSON file."""
119
- import os
120
- vocab_file = os.path.join(
121
- save_directory,
122
- (filename_prefix + "-" if filename_prefix else "") + "vocab.json"
123
- )
124
- with open(vocab_file, "w") as f:
125
- json.dump(self._vocab, f, indent=2)
126
- return (vocab_file,)
127
- ```
128
-
129
- ### Building the Vocabulary
130
-
131
- You need to build a vocabulary.
132
 
133
- It could be written from scratch, or inferred from the dataset:
134
 
135
  ```python
136
  from datasets import load_dataset
 
137
 
138
- # Load dataset
139
  dataset = load_dataset("dlouapre/lichess_2025-01_1M", split="train")
140
-
141
- # Collect all unique moves
142
  vocab = {"[PAD]": 0, "[BOS]": 1, "[EOS]": 2, "[UNK]": 3}
143
  for game in dataset:
144
- moves = game["text"].split()
145
- for move in moves:
146
  if move not in vocab:
147
  vocab[move] = len(vocab)
148
-
149
- print(f"Vocabulary size: {len(vocab)}")
150
-
151
- # Save vocabulary
152
- import json
153
  with open("vocab.json", "w") as f:
154
  json.dump(vocab, f, indent=2)
155
  ```
156
 
157
- ---
158
-
159
- ## Step 2: Create a Custom Model
160
 
161
- Your model must inherit from `PreTrainedModel` and use a config that inherits from `PretrainedConfig`.
162
 
163
- ### Required Files
 
 
 
164
 
165
- Create a file called `model.py` with your model class:
166
 
 
167
  ```python
168
- import torch
169
- import torch.nn as nn
170
- from transformers import PretrainedConfig, PreTrainedModel
171
- from transformers.modeling_outputs import CausalLMOutputWithPast
172
-
173
-
174
- class MyChessConfig(PretrainedConfig):
175
- """Configuration for the chess model."""
176
-
177
- model_type = "my_chess_model"
178
-
179
- def __init__(
180
- self,
181
- vocab_size: int = 1500,
182
- n_embd: int = 128,
183
- n_layer: int = 4,
184
- n_head: int = 4,
185
- n_ctx: int = 256,
186
- dropout: float = 0.1,
187
- **kwargs,
188
- ):
189
- super().__init__(**kwargs)
190
- self.vocab_size = vocab_size
191
- self.n_embd = n_embd
192
- self.n_layer = n_layer
193
- self.n_head = n_head
194
- self.n_ctx = n_ctx
195
- self.dropout = dropout
196
-
197
-
198
- class MyChessModel(PreTrainedModel):
199
- """A simple transformer for chess move prediction."""
200
-
201
- config_class = MyChessConfig
202
-
203
- def __init__(self, config: MyChessConfig):
204
- super().__init__(config)
205
-
206
- # Token and position embeddings
207
- self.token_emb = nn.Embedding(config.vocab_size, config.n_embd)
208
- self.pos_emb = nn.Embedding(config.n_ctx, config.n_embd)
209
- self.dropout = nn.Dropout(config.dropout)
210
-
211
- # Transformer layers
212
- encoder_layer = nn.TransformerEncoderLayer(
213
- d_model=config.n_embd,
214
- nhead=config.n_head,
215
- dim_feedforward=config.n_embd * 4,
216
- dropout=config.dropout,
217
- batch_first=True,
218
- )
219
- self.transformer = nn.TransformerEncoder(encoder_layer, config.n_layer)
220
-
221
- # Output head
222
- self.ln_f = nn.LayerNorm(config.n_embd)
223
- self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
224
-
225
- # Weight tying (saves parameters!)
226
- self.lm_head.weight = self.token_emb.weight
227
-
228
- self.post_init()
229
-
230
- def forward(
231
- self,
232
- input_ids,
233
- attention_mask=None,
234
- labels=None,
235
- **kwargs,
236
- ):
237
- batch_size, seq_len = input_ids.shape
238
- device = input_ids.device
239
-
240
- # Embeddings
241
- positions = torch.arange(seq_len, device=device).unsqueeze(0)
242
- x = self.token_emb(input_ids) + self.pos_emb(positions)
243
- x = self.dropout(x)
244
-
245
- # Causal mask for autoregressive generation
246
- causal_mask = torch.triu(
247
- torch.ones(seq_len, seq_len, device=device) * float('-inf'),
248
- diagonal=1
249
- )
250
-
251
- # Transformer
252
- x = self.transformer(x, mask=causal_mask)
253
- x = self.ln_f(x)
254
- logits = self.lm_head(x)
255
-
256
- # Compute loss if labels provided
257
- loss = None
258
- if labels is not None:
259
- shift_logits = logits[..., :-1, :].contiguous()
260
- shift_labels = labels[..., 1:].contiguous()
261
- loss = nn.functional.cross_entropy(
262
- shift_logits.view(-1, self.config.vocab_size),
263
- shift_labels.view(-1),
264
- ignore_index=-100,
265
- )
266
-
267
- return CausalLMOutputWithPast(loss=loss, logits=logits)
268
-
269
- def prepare_inputs_for_generation(self, input_ids, **kwargs):
270
- return {"input_ids": input_ids}
271
- ```
272
-
273
- ### Parameter Budget Tips
274
-
275
- With 1M parameters, you need to be careful:
276
-
277
- | Component | Formula | Example (128 dim, 1500 vocab) |
278
- |-----------|---------|------------------------------|
279
- | Token embeddings | vocab_size x n_embd | 1500 x 128 = 192,000 |
280
- | Position embeddings | n_ctx x n_embd | 256 x 128 = 32,768 |
281
- | Transformer layer | ~4 x n_embd^2 | ~65,536 per layer |
282
- | LM head | 0 (with weight tying) | 0 |
283
-
284
- **Key savings:**
285
- - **Weight tying**: Share token embeddings with output layer (saves vocab_size x n_embd)
286
- - **Smaller vocabulary**: Only include moves that appear in training data
287
- - **Fewer layers**: 4-6 layers is often enough
288
-
289
- ---
290
-
291
- ## Step 3: Train Your Model
292
-
293
- Create a training script:
294
-
295
- ```python
296
- import torch
297
- from datasets import load_dataset
298
- from transformers import Trainer, TrainingArguments
299
-
300
  from model import MyChessConfig, MyChessModel
301
  from tokenizer import MyChessTokenizer
 
 
302
 
303
- # Load tokenizer with your vocabulary
304
  tokenizer = MyChessTokenizer(vocab_file="vocab.json")
305
-
306
- # Create model
307
- config = MyChessConfig(
308
- vocab_size=tokenizer.vocab_size,
309
- n_embd=128,
310
- n_layer=4,
311
- n_head=4,
312
- )
313
  model = MyChessModel(config)
314
 
315
- # Check parameter count
316
- n_params = sum(p.numel() for p in model.parameters())
317
- print(f"Parameters: {n_params:,}")
318
- assert n_params < 1_000_000, f"Model too large: {n_params:,} > 1M"
319
-
320
- # Load and tokenize dataset
321
  dataset = load_dataset("dlouapre/lichess_2025-01_1M", split="train")
322
-
323
  def tokenize_function(examples):
324
- return tokenizer(
325
- examples["text"],
326
- truncation=True,
327
- max_length=256,
328
- padding="max_length",
329
- )
330
-
331
  tokenized_dataset = dataset.map(tokenize_function, batched=True)
332
 
333
- # Training
334
  training_args = TrainingArguments(
335
  output_dir="./my_model",
336
  num_train_epochs=3,
@@ -339,76 +108,47 @@ training_args = TrainingArguments(
339
  save_steps=1000,
340
  logging_steps=100,
341
  )
342
-
343
- trainer = Trainer(
344
- model=model,
345
- args=training_args,
346
- train_dataset=tokenized_dataset,
347
- )
348
-
349
  trainer.train()
350
-
351
- # Save final model
352
  model.save_pretrained("./my_model/final")
353
  tokenizer.save_pretrained("./my_model/final")
354
  ```
355
 
356
- ---
357
-
358
- ## Step 4: Prepare for Submission
359
-
360
- Your model directory must contain these files:
361
 
 
362
  ```
363
- my_model/
364
- config.json # Model configuration
365
- model.safetensors # Model weights
366
- tokenizer_config.json # Tokenizer configuration
367
- vocab.json # Vocabulary
368
- model.py # Your model class
369
- tokenizer.py # Your tokenizer class
370
  ```
371
 
372
- ### Adding auto_map for Remote Loading
373
-
374
- The `auto_map` field tells HuggingFace how to load your custom classes with `trust_remote_code=True`.
375
-
376
- **In config.json**, add:
377
  ```json
378
- {
379
  "auto_map": {
380
  "AutoConfig": "model.MyChessConfig",
381
  "AutoModelForCausalLM": "model.MyChessModel"
382
- },
383
- ...
384
- }
385
  ```
386
-
387
- **In tokenizer_config.json**, add:
388
  ```json
389
- {
390
  "auto_map": {
391
  "AutoTokenizer": "tokenizer.MyChessTokenizer"
392
- },
393
- ...
394
- }
395
  ```
396
-
397
- You can do this programmatically:
398
-
399
  ```python
400
- # Register for auto loading
401
  model.config.auto_map = {
402
  "AutoConfig": "model.MyChessConfig",
403
  "AutoModelForCausalLM": "model.MyChessModel",
404
  }
405
  tokenizer.register_for_auto_class("AutoTokenizer")
406
-
407
- # Save
408
  model.save_pretrained("./my_model/final")
409
  tokenizer.save_pretrained("./my_model/final")
410
-
411
- # Copy your Python files
412
  import shutil
413
  shutil.copy("model.py", "./my_model/final/model.py")
414
  shutil.copy("tokenizer.py", "./my_model/final/tokenizer.py")
@@ -416,79 +156,55 @@ shutil.copy("tokenizer.py", "./my_model/final/tokenizer.py")
416
 
417
  ---
418
 
419
- ## Local Evaluation (Optional but Recommended)
420
-
421
- Before submitting, you can evaluate your model locally to check its performance. Since the evaluation is **fully deterministic** (fixed seed, deterministic opponent engine), you will get the exact same results locally as on the HuggingFace Space after submission.
422
 
 
423
  ```bash
424
  python -m src --model ./my_model/final
425
  ```
426
-
427
- This runs the same evaluation procedure as the online leaderboard:
428
- - 500 moves against the deterministic opponent
429
- - Same random seed (42)
430
- - Same move generation parameters
431
-
432
- Use this to iterate quickly on your model before pushing to HuggingFace!
433
 
434
  ---
435
 
436
- ## Step 5: Submit
437
 
 
438
  ```bash
439
- python submit.py --model_path ./my_model/final --model_name my-chess-model
440
  ```
441
-
442
  The script will:
443
- 1. Validate all required files are present
444
- 2. Check that auto_map is configured
445
- 3. Count parameters and warn if over 1M
446
- 4. Log you into HuggingFace (if needed)
447
- 5. Upload to the LLM-course organization
448
 
449
  ---
450
 
451
- ## Evaluation
452
 
453
  After submission, go to the [Chess Challenge Arena](https://huggingface.co/spaces/LLM-course/Chess1MChallenge) to run evaluation.
454
 
455
- ### Evaluation Procedure
456
-
457
- 1. **Parameter Check**: Model must have < 1M parameters
458
- 2. **Security Check**: Code is scanned for illegal python-chess usage
459
- 3. **Game Play**: 500 moves against a deterministic opponent engine
460
- 4. **Move Generation**: 3 retries allowed per move (greedy on 1st try, then sampling)
461
- 5. **Scoring**: Legal move rate (first try and with retries)
462
-
463
- ### Scoring
464
 
 
465
  | Metric | Description |
466
  |--------|-------------|
467
  | **Legal Rate (1st try)** | % of moves legal on first attempt |
468
  | **Legal Rate (with retries)** | % of moves legal within 3 attempts |
469
 
470
- **Target**: >90% legal rate = excellent performance
471
 
472
  ---
473
 
474
- ## Example Solution
475
-
476
- A complete working example is in `example_solution/`:
477
 
478
- - `model.py` - Full transformer implementation
479
- - `tokenizer.py` - Complete tokenizer class
480
- - `train.py` - Training script with data loading
481
- - `data.py` - Dataset utilities
482
-
483
- Use it as reference to understand the expected format and structure.
484
 
485
  ---
486
-
487
- ## Rules
488
-
489
- 1. **< 1M parameters** - Hard limit, checked automatically
490
- 2. **No python-chess for move filtering** - Model must generate legal moves on its own
491
- 3. **Custom architecture required** - Must include model.py and tokenizer.py
492
- 4. **Use the submission script** - Required for leaderboard tracking
493
-
494
- Good luck!
 
1
+ # Chess Challenge: 1M Parameter Transformer
2
 
3
+ Train a transformer (from scratch!) with less than 1M parameters to play legal chess moves.
4
 
5
+ ---
6
+
7
+ ## 1. Overview & Objective
8
 
9
+ Your model must:
10
 
11
+ - **Stay under 1M parameters** (hard limit)
12
+ - **Create a custom tokenizer**
13
+ - **Create a custom model architecture** (your own transformer)
14
+ - **Play legal chess** (model must learn the rules)
15
+ - **Do NOT use python-chess to filter moves** (the model must generate legal moves itself)
16
 
17
+ ---
18
+
19
+ ## 2. Dataset & Notation
20
 
21
  We use the Lichess dataset: [`dlouapre/lichess_2025-01_1M`](https://huggingface.co/datasets/dlouapre/lichess_2025-01_1M)
22
 
23
+ **Notation:**
24
  - `W`/`B` prefix for White/Black
25
+ - Piece letter: `P`=Pawn, `N`=Knight, `B`=Bishop, `R`=Rook, `Q`=Queen, `K`=King
26
  - Source and destination squares (e.g., `e2e4`)
27
  - Special suffixes: `(x)`=capture, `(+)`=check, `(+*)`=checkmate, `(o)`/`(O)`=castling
28
 
 
33
 
34
  ---
35
 
36
+ ## 3. Directory Structure
 
 
37
 
38
+ Your project should look like this:
 
 
 
39
 
40
+ ```
41
+ my_model/
42
+ config.json
43
+ model.safetensors
44
+ tokenizer_config.json
45
+ vocab.json
46
+ model.py
47
+ tokenizer.py
48
+ ```
49
 
50
  ---
51
 
52
+ ## 4. Step-by-Step Instructions
 
 
53
 
54
+ ### Step 1: Build Your Tokenizer
55
 
56
+ Create `tokenizer.py` implementing a subclass of `PreTrainedTokenizer`, say MyChessTokenizer.
57
 
58
+ **Build the vocabulary:**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ One possibility is to look at the dataset, but it's by far not the only option:
61
 
62
  ```python
63
  from datasets import load_dataset
64
+ import json
65
 
 
66
  dataset = load_dataset("dlouapre/lichess_2025-01_1M", split="train")
 
 
67
  vocab = {"[PAD]": 0, "[BOS]": 1, "[EOS]": 2, "[UNK]": 3}
68
  for game in dataset:
69
+ for move in game["text"].split():
 
70
  if move not in vocab:
71
  vocab[move] = len(vocab)
 
 
 
 
 
72
  with open("vocab.json", "w") as f:
73
  json.dump(vocab, f, indent=2)
74
  ```
75
 
76
+ ### Step 2: Build Your Model
 
 
77
 
78
+ Create `model.py` implementing a subclass of `PreTrainedModel`, say MyChessModel, and a config class, say MyChessConfig.
79
 
80
+ **Tips:**
81
+ - Use weight tying to save parameters.
82
+ - Keep the vocabulary small.
83
+ - 4-6 transformer layers is usually enough.
84
 
85
+ ### Step 3: Training
86
 
87
+ Create `train.py` to train your model:
88
  ```python
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  from model import MyChessConfig, MyChessModel
90
  from tokenizer import MyChessTokenizer
91
+ from datasets import load_dataset
92
+ from transformers import Trainer, TrainingArguments
93
 
 
94
  tokenizer = MyChessTokenizer(vocab_file="vocab.json")
95
+ config = MyChessConfig(vocab_size=tokenizer.vocab_size, n_embd=128, n_layer=4, n_head=4)
 
 
 
 
 
 
 
96
  model = MyChessModel(config)
97
 
 
 
 
 
 
 
98
  dataset = load_dataset("dlouapre/lichess_2025-01_1M", split="train")
 
99
  def tokenize_function(examples):
100
+ return tokenizer(examples["text"], truncation=True, max_length=256, padding="max_length")
 
 
 
 
 
 
101
  tokenized_dataset = dataset.map(tokenize_function, batched=True)
102
 
 
103
  training_args = TrainingArguments(
104
  output_dir="./my_model",
105
  num_train_epochs=3,
 
108
  save_steps=1000,
109
  logging_steps=100,
110
  )
111
+ trainer = Trainer(model=model, args=training_args, train_dataset=tokenized_dataset)
 
 
 
 
 
 
112
  trainer.train()
 
 
113
  model.save_pretrained("./my_model/final")
114
  tokenizer.save_pretrained("./my_model/final")
115
  ```
116
 
117
+ ### Step 4: Prepare for Submission
 
 
 
 
118
 
119
+ Your model directory (`my_model/final/`) **must** contain:
120
  ```
121
+ config.json # Model configuration
122
+ model.safetensors # Model weights
123
+ tokenizer_config.json # Tokenizer configuration
124
+ vocab.json # Vocabulary
125
+ model.py # Your model class
126
+ tokenizer.py # Your tokenizer class
 
127
  ```
128
 
129
+ #### Add `auto_map` for remote loading
130
+ Edit `config.json`:
 
 
 
131
  ```json
 
132
  "auto_map": {
133
  "AutoConfig": "model.MyChessConfig",
134
  "AutoModelForCausalLM": "model.MyChessModel"
135
+ }
 
 
136
  ```
137
+ Edit `tokenizer_config.json`:
 
138
  ```json
 
139
  "auto_map": {
140
  "AutoTokenizer": "tokenizer.MyChessTokenizer"
141
+ }
 
 
142
  ```
143
+ Or do it programmatically:
 
 
144
  ```python
 
145
  model.config.auto_map = {
146
  "AutoConfig": "model.MyChessConfig",
147
  "AutoModelForCausalLM": "model.MyChessModel",
148
  }
149
  tokenizer.register_for_auto_class("AutoTokenizer")
 
 
150
  model.save_pretrained("./my_model/final")
151
  tokenizer.save_pretrained("./my_model/final")
 
 
152
  import shutil
153
  shutil.copy("model.py", "./my_model/final/model.py")
154
  shutil.copy("tokenizer.py", "./my_model/final/tokenizer.py")
 
156
 
157
  ---
158
 
159
+ ### Step 5: Local Evaluation (Recommended)
 
 
160
 
161
+ Before submitting, evaluate your model locally:
162
  ```bash
163
  python -m src --model ./my_model/final
164
  ```
165
+ This runs the same evaluation as the leaderboard (500 moves, deterministic seed).
 
 
 
 
 
 
166
 
167
  ---
168
 
169
+ ### Step 6: Submit
170
 
171
+ Submit your model to the leaderboard:
172
  ```bash
173
+ python submit.py --model_path ./my_model/final --model_name your-model-name
174
  ```
 
175
  The script will:
176
+ - Validate all required files
177
+ - Check auto_map
178
+ - Count parameters
179
+ - Log you into HuggingFace (if needed)
180
+ - Upload to the LLM-course organization
181
 
182
  ---
183
 
184
+ ## 5. Evaluation & Leaderboard
185
 
186
  After submission, go to the [Chess Challenge Arena](https://huggingface.co/spaces/LLM-course/Chess1MChallenge) to run evaluation.
187
 
188
+ **Evaluation steps:**
189
+ 1. Parameter check (<1M)
190
+ 2. Security check (no python-chess for move filtering)
191
+ 3. 500 moves against a deterministic opponent
192
+ 4. 3 retries per move (greedy, then sampling)
193
+ 5. Scoring: legal move rate (first try and with retries)
 
 
 
194
 
195
+ **Scoring Table:**
196
  | Metric | Description |
197
  |--------|-------------|
198
  | **Legal Rate (1st try)** | % of moves legal on first attempt |
199
  | **Legal Rate (with retries)** | % of moves legal within 3 attempts |
200
 
201
+ **Target:** >90% legal rate = excellent
202
 
203
  ---
204
 
205
+ ## 6. Example Solution
 
 
206
 
207
+ See `example_solution/` for a full working reference:
208
+ - `model.py`, `tokenizer.py`, `train.py`, `data.py`
 
 
 
 
209
 
210
  ---
 
 
 
 
 
 
 
 
 
src/__init__.py DELETED
@@ -1,20 +0,0 @@
1
- """Chess Challenge evaluation module."""
2
-
3
- # Lazy imports to avoid circular dependencies
4
- def __getattr__(name):
5
- if name == "ChessEvaluator":
6
- from .evaluate import ChessEvaluator
7
- return ChessEvaluator
8
- if name == "load_model_and_tokenizer":
9
- from .evaluate import load_model_and_tokenizer
10
- return load_model_and_tokenizer
11
- if name == "count_parameters":
12
- from .evaluate import count_parameters
13
- return count_parameters
14
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
15
-
16
- __all__ = [
17
- "ChessEvaluator",
18
- "load_model_and_tokenizer",
19
- "count_parameters",
20
- ]