File size: 9,824 Bytes
8a7719b
 
 
 
cb44915
 
 
 
 
 
 
 
 
 
8a7719b
 
cb44915
8a7719b
 
 
 
cb44915
8a7719b
 
 
cb44915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a7719b
cb44915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a7719b
cb44915
8a7719b
 
 
 
 
 
 
cb44915
 
8a7719b
cb44915
8a7719b
 
 
cb44915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a7719b
 
 
cb44915
8a7719b
cb44915
 
 
 
39a0b64
cb44915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df21660
cb44915
8a7719b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb44915
8a7719b
cb44915
 
8a7719b
cb44915
 
 
8a7719b
cb44915
8a7719b
cb44915
 
 
 
 
 
 
8a7719b
 
cb44915
8a7719b
 
 
cb44915
 
 
 
 
8a7719b
 
 
cb44915
8a7719b
 
 
cb44915
 
 
8a7719b
 
 
 
cb44915
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#!/usr/bin/env python3
"""
Submission script for the Chess Challenge.

This script validates and uploads your trained model to the Hugging Face Hub
under the LLM-course organization.

Your model directory must contain:
- config.json: Model configuration with auto_map for custom architecture
- model.safetensors (or pytorch_model.bin): Model weights
- tokenizer_config.json: Tokenizer configuration with auto_map
- vocab.json: Vocabulary file
- model.py: Your custom model architecture (for trust_remote_code)
- tokenizer.py: Your custom tokenizer (for trust_remote_code)

Usage:
    python submit.py --model_path ./my_model --model_name my-chess-model
"""

import argparse
import os
import sys
from pathlib import Path


# Required files for a valid submission
REQUIRED_FILES = {
    "config.json": "Model configuration (must include auto_map)",
    "tokenizer_config.json": "Tokenizer configuration (must include auto_map)",
    "vocab.json": "Vocabulary file",
    "model.py": "Custom model architecture (for trust_remote_code=True)",
    "tokenizer.py": "Custom tokenizer class (for trust_remote_code=True)",
}

# At least one of these weight files must exist
WEIGHT_FILES = ["model.safetensors", "pytorch_model.bin"]


def validate_model_directory(model_path: Path) -> tuple[bool, list[str]]:
    """
    Validate that the model directory contains all required files.
    
    Returns:
        Tuple of (is_valid, list of error messages).
    """
    errors = []
    
    # Check required files
    for filename, description in REQUIRED_FILES.items():
        if not (model_path / filename).exists():
            errors.append(f"Missing {filename}: {description}")
    
    # Check weight files (need at least one)
    has_weights = any((model_path / f).exists() for f in WEIGHT_FILES)
    if not has_weights:
        errors.append(f"Missing model weights: need {' or '.join(WEIGHT_FILES)}")
    
    return len(errors) == 0, errors


def validate_auto_map(model_path: Path) -> tuple[bool, list[str]]:
    """
    Validate that config.json and tokenizer_config.json have auto_map fields.
    
    Returns:
        Tuple of (is_valid, list of error messages).
    """
    import json
    
    errors = []
    
    # Check config.json for auto_map
    config_path = model_path / "config.json"
    if config_path.exists():
        with open(config_path) as f:
            config = json.load(f)
        if "auto_map" not in config:
            errors.append(
                "config.json missing 'auto_map' field. Add:\n"
                '  "auto_map": {\n'
                '    "AutoConfig": "model.YourConfig",\n'
                '    "AutoModelForCausalLM": "model.YourModel"\n'
                '  }'
            )
    
    # Check tokenizer_config.json for auto_map
    tokenizer_config_path = model_path / "tokenizer_config.json"
    if tokenizer_config_path.exists():
        with open(tokenizer_config_path) as f:
            tokenizer_config = json.load(f)
        if "auto_map" not in tokenizer_config:
            errors.append(
                "tokenizer_config.json missing 'auto_map' field. Add:\n"
                '  "auto_map": {\n'
                '    "AutoTokenizer": ["tokenizer.YourTokenizer", null]\n'
                '  }\n'
                'Note: AutoTokenizer value must be a list [slow_class, fast_class].'
            )
        elif "AutoTokenizer" in tokenizer_config.get("auto_map", {}):
            auto_tok = tokenizer_config["auto_map"]["AutoTokenizer"]
            if isinstance(auto_tok, str):
                errors.append(
                    "tokenizer_config.json auto_map.AutoTokenizer must be a list, not a string.\n"
                    'Change from: "AutoTokenizer": "tokenizer.YourTokenizer"\n'
                    'To: "AutoTokenizer": ["tokenizer.YourTokenizer", null]'
                )
    
    return len(errors) == 0, errors


def count_parameters(model_path: Path) -> int:
    """Count parameters in the model."""
    from transformers import AutoModelForCausalLM
    
    model = AutoModelForCausalLM.from_pretrained(
        model_path,
        trust_remote_code=True,
        local_files_only=True,
    )
    return sum(p.numel() for p in model.parameters())


def main():
    parser = argparse.ArgumentParser(
        description="Submit your chess model to the Hugging Face Hub",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Required files in your model directory:
  - config.json          Model configuration with auto_map
  - model.safetensors    Model weights (or pytorch_model.bin)
  - tokenizer_config.json Tokenizer configuration with auto_map
  - vocab.json           Vocabulary file
  - model.py             Custom model architecture
  - tokenizer.py         Custom tokenizer class

Example:
  python submit.py --model_path ./my_model --model_name my-chess-model
        """
    )
    parser.add_argument(
        "--model_path", type=str, required=True,
        help="Path to your trained model directory"
    )
    parser.add_argument(
        "--model_name", type=str, required=True,
        help="Name for your model on the Hub (e.g., 'my-chess-model')"
    )
    args = parser.parse_args()
    
    model_path = Path(args.model_path)
    organization = "LLM-course"
    
    print("=" * 60)
    print("CHESS CHALLENGE - MODEL SUBMISSION")
    print("=" * 60)
    
    # Check model path exists
    if not model_path.exists():
        print(f"\nError: Model path '{model_path}' does not exist.")
        return 1
    
    # Validate required files
    print("\n[1/5] Checking required files...")
    is_valid, errors = validate_model_directory(model_path)
    if not is_valid:
        print("\nError: Model directory is incomplete:")
        for error in errors:
            print(f"  - {error}")
        print("\nSee example_solution/ for a complete example.")
        return 1
    print("  All required files present.")
    
    # Validate auto_map fields
    print("\n[2/5] Validating auto_map configuration...")
    is_valid, errors = validate_auto_map(model_path)
    if not is_valid:
        print("\nError: Configuration files need auto_map:")
        for error in errors:
            print(f"  - {error}")
        return 1
    print("  auto_map configuration valid.")
    
    # Count parameters
    print("\n[3/5] Counting parameters...")
    try:
        n_params = count_parameters(model_path)
        print(f"  Parameters: {n_params:,}")
        if n_params > 1_000_000:
            print(f"\n  WARNING: Model exceeds 1M parameter limit!")
            print(f"  Your model has {n_params:,} parameters.")
            print(f"  It will fail the evaluation parameter check.")
    except Exception as e:
        print(f"\nError: Could not load model to count parameters: {e}")
        return 1
    
    # Hugging Face login
    print("\n[4/5] Checking Hugging Face authentication...")
    try:
        from huggingface_hub import HfApi, whoami
    except ImportError:
        print("\nError: huggingface_hub not installed.")
        print("Install with: pip install huggingface_hub")
        return 1
    
    try:
        user_info = whoami()
        username = user_info["name"]
        print(f"  Logged in as: {username}")
    except Exception:
        print("\n  Not logged in. Starting login process...")
        print("  You need a Hugging Face account and access token.")
        print("  Get your token at: https://huggingface.co/settings/tokens")
        print()
        
        # Interactive login
        from huggingface_hub import login
        try:
            login()
            user_info = whoami()
            username = user_info["name"]
            print(f"\n  Successfully logged in as: {username}")
        except Exception as e:
            print(f"\nError: Login failed: {e}")
            return 1
    
    # Upload to Hub
    print("\n[5/5] Uploading to Hugging Face Hub...")
    repo_id = f"{organization}/{args.model_name}"
    print(f"  Repository: {repo_id}")
    
    api = HfApi()
    
    try:
        # Create repo if it doesn't exist
        api.create_repo(repo_id=repo_id, exist_ok=True)
        
        # Create a model card
        model_card = f"""---
library_name: transformers
tags:
- chess
- llm-course
- chess-challenge
license: mit
---

# {args.model_name}

Chess model submitted to the LLM Course Chess Challenge.

## Submission Info

- **Submitted by**: [{username}](https://huggingface.co/{username})
- **Parameters**: {n_params:,}
- **Organization**: {organization}

## Usage

```python
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained("{repo_id}", trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained("{repo_id}", trust_remote_code=True)
```

## Evaluation

This model is evaluated at the [Chess Challenge Arena](https://huggingface.co/spaces/LLM-course/Chess1MChallenge).
"""
        
        # Write model card
        readme_path = model_path / "README.md"
        readme_path.write_text(model_card)
        
        # Upload all files
        api.upload_folder(
            folder_path=model_path,
            repo_id=repo_id,
            commit_message=f"Chess Challenge submission by {username}",
        )
        
    except Exception as e:
        print(f"\nError: Upload failed: {e}")
        return 1
    
    print("\n" + "=" * 60)
    print("SUBMISSION COMPLETE!")
    print("=" * 60)
    print(f"\nYour model is available at:")
    print(f"  https://huggingface.co/{repo_id}")
    print(f"\nSubmitted by: {username}")
    print(f"Parameters: {n_params:,}")
    print(f"\nNext step: Go to the Chess Challenge Arena to run evaluation:")
    print(f"  https://huggingface.co/spaces/LLM-course/Chess1MChallenge")
    
    return 0


if __name__ == "__main__":
    sys.exit(main())