HumboldtJoker commited on
Commit
0e39f6b
Β·
verified Β·
1 Parent(s): 4956690

Add training template: setup.sh

Browse files
Files changed (1) hide show
  1. training-template/setup.sh +359 -0
training-template/setup.sh ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # Daimon Training β€” RunPod First-Boot Setup
4
+ # Liberation Labs
5
+ # ============================================================================
6
+ #
7
+ # Run this ONCE on a fresh RunPod pod before training.
8
+ # It installs dependencies, pulls the model & data, and verifies the environment.
9
+ #
10
+ # Requirements:
11
+ # - 1x H200 SXM 141GB
12
+ # - 188GB+ system RAM (critical for CPU-offloaded optimizer)
13
+ # - 400GB+ disk on /workspace (persistent volume)
14
+ # - HF_TOKEN environment variable set (model is gated)
15
+ #
16
+ # Usage:
17
+ # export HF_TOKEN="hf_your_token_here"
18
+ # bash /workspace/runpod-template/setup.sh
19
+ # ============================================================================
20
+
21
+ set -e
22
+
23
+ echo "============================================================"
24
+ echo " DAIMON FULL-PARAMETER SFT β€” POD SETUP"
25
+ echo " Liberation Labs"
26
+ echo " $(date)"
27
+ echo "============================================================"
28
+
29
+ # ── 1. Find Python ──────────────────────────────────────────────────────────
30
+ export PATH=/opt/conda/bin:/usr/local/bin:$PATH
31
+ PYTHON=$(which python3.11 2>/dev/null || which python3 2>/dev/null)
32
+ echo "Python: $PYTHON ($($PYTHON --version 2>&1))"
33
+
34
+ # ── 2. Check GPU ──────────────────────────────────────────────────────────
35
+ echo ""
36
+ echo "=== GPU Check ==="
37
+ GPU_COUNT=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l)
38
+ echo "GPUs detected: $GPU_COUNT"
39
+ nvidia-smi --query-gpu=name,memory.total --format=csv,noheader
40
+
41
+ if [ "$GPU_COUNT" -lt 1 ]; then
42
+ echo ""
43
+ echo "FATAL: No GPUs detected."
44
+ exit 1
45
+ fi
46
+
47
+ # Check VRAM (need >= 140GB for full SFT on single GPU)
48
+ VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1 | tr -d ' ')
49
+ echo "VRAM: ${VRAM} MiB"
50
+ if [ "$VRAM" -lt 140000 ]; then
51
+ echo ""
52
+ echo "FATAL: GPU has ${VRAM} MiB VRAM. Need >= 140,000 MiB."
53
+ echo ""
54
+ echo "Why: Full-parameter SFT memory budget:"
55
+ echo " Model params (bf16): ~70GB β†’ GPU"
56
+ echo " Activations (grad ckpt): ~20GB β†’ GPU"
57
+ echo " Total GPU: ~90GB of 141GB"
58
+ echo ""
59
+ echo "Fix: Provision a pod with 1x H200 SXM 141GB."
60
+ exit 1
61
+ fi
62
+
63
+ # ── 3. Check system RAM ────────────────────────────────────────────────────
64
+ echo ""
65
+ echo "=== System RAM ==="
66
+ TOTAL_RAM=$(free -g | grep Mem | awk '{print $2}')
67
+ echo "Total: ${TOTAL_RAM} GB"
68
+
69
+ # System RAM is CRITICAL for full SFT β€” optimizer states and gradients are CPU-offloaded
70
+ if [ "$TOTAL_RAM" -lt 180 ]; then
71
+ echo "FATAL: System RAM is ${TOTAL_RAM}GB. Need >= 180GB."
72
+ echo ""
73
+ echo "Why: Full SFT CPU-offloaded memory budget:"
74
+ echo " Gradients (bf16): ~70GB β†’ CPU"
75
+ echo " Adafactor optimizer: ~35GB β†’ CPU"
76
+ echo " Total CPU: ~105GB"
77
+ echo " Plus OS/data overhead: ~30GB"
78
+ echo ""
79
+ echo "AdamW is NOT viable β€” its fp32 states would need ~280GB CPU RAM."
80
+ echo "Even Adafactor needs ~105GB + headroom."
81
+ echo ""
82
+ echo "Fix: Provision a pod with >= 188GB system RAM."
83
+ exit 1
84
+ fi
85
+
86
+ # ── 4. Check disk space ────────────────────────────────────────────────────
87
+ echo ""
88
+ echo "=== Disk Space ==="
89
+ AVAIL_GB=$(df -BG /workspace | tail -1 | awk '{print $4}' | tr -d 'G')
90
+ echo "Available on /workspace: ${AVAIL_GB} GB"
91
+
92
+ if [ "$AVAIL_GB" -lt 300 ]; then
93
+ echo "FATAL: Less than 300GB on /workspace."
94
+ echo "Full SFT needs: model (~70GB) + data + checkpoints (~210GB for 3 Γ— 70GB)."
95
+ echo "Mount a 400GB+ persistent volume."
96
+ exit 1
97
+ elif [ "$AVAIL_GB" -lt 400 ]; then
98
+ echo "WARNING: Less than 400GB on /workspace."
99
+ echo "Full model checkpoints are ~70GB each. With save_total_limit=3, need ~210GB."
100
+ echo "Will be tight β€” consider a larger volume."
101
+ fi
102
+
103
+ # ── 5. Check HF_TOKEN ──────────────────────────────────────────────────────
104
+ echo ""
105
+ echo "=== HuggingFace Authentication ==="
106
+ if [ -z "$HF_TOKEN" ]; then
107
+ echo "FATAL: HF_TOKEN environment variable not set."
108
+ echo "Qwen3.6-35B-A3B is a gated model. You need a HuggingFace token."
109
+ echo ""
110
+ echo "Fix: export HF_TOKEN='hf_your_token_here'"
111
+ echo "Or set it in the RunPod pod template environment variables."
112
+ exit 1
113
+ else
114
+ echo "HF_TOKEN is set (${#HF_TOKEN} chars)"
115
+ fi
116
+
117
+ # ── 6. Install dependencies (pinned versions) ─────────────────────────────
118
+ echo ""
119
+ echo "=== Installing Dependencies ==="
120
+ $PYTHON -m pip install --upgrade -q pip
121
+
122
+ echo "Installing PyTorch..."
123
+ $PYTHON -m pip install -q \
124
+ torch==2.7.1 \
125
+ torchvision==0.22.1 \
126
+ --index-url https://download.pytorch.org/whl/cu124 \
127
+ 2>&1 | tail -2
128
+
129
+ echo "Installing training stack (pinned versions)..."
130
+ $PYTHON -m pip install -q \
131
+ transformers==5.12.1 \
132
+ trl==1.7.0 \
133
+ datasets==5.0.0 \
134
+ accelerate==1.14.0 \
135
+ deepspeed==0.16.7 \
136
+ safetensors==0.8.0 \
137
+ pyyaml==6.0.2 \
138
+ 2>&1 | tail -3
139
+
140
+ echo "Installing flash-attn (may take a few minutes to compile)..."
141
+ $PYTHON -m pip install -q flash-attn --no-build-isolation 2>&1 | tail -3 || {
142
+ echo "WARNING: flash-attn failed to install. Will fall back to SDPA attention."
143
+ echo "This is fine β€” SDPA is only ~5% slower on H200."
144
+ }
145
+
146
+ echo "Dependencies installed."
147
+
148
+ # ── 7. Verify critical packages ────────────────────────────────────────────
149
+ echo ""
150
+ echo "=== Package Verification ==="
151
+ $PYTHON -c "
152
+ import torch, transformers, trl, datasets, accelerate, deepspeed, safetensors
153
+ print(f'torch: {torch.__version__}')
154
+ print(f'transformers: {transformers.__version__}')
155
+ print(f'trl: {trl.__version__}')
156
+ print(f'datasets: {datasets.__version__}')
157
+ print(f'accelerate: {accelerate.__version__}')
158
+ print(f'deepspeed: {deepspeed.__version__}')
159
+ print(f'safetensors: {safetensors.__version__}')
160
+ print(f'CUDA: {torch.version.cuda}')
161
+ print(f'GPUs: {torch.cuda.device_count()}')
162
+ try:
163
+ import flash_attn
164
+ print(f'flash_attn: {flash_attn.__version__}')
165
+ except ImportError:
166
+ print('flash_attn: not installed (using SDPA fallback)')
167
+ "
168
+
169
+ # ── 8. Verify Qwen3.6 architecture support ─────────────────────────────────
170
+ echo ""
171
+ echo "=== Model Architecture Check ==="
172
+ MODEL_REVISION="995ad96eacd98c81ed38be0c5b274b04031597b0"
173
+ $PYTHON -c "
174
+ from transformers import AutoConfig
175
+ c = AutoConfig.from_pretrained('Qwen/Qwen3.6-35B-A3B', revision='$MODEL_REVISION', trust_remote_code=True)
176
+ print(f'Model type: {c.model_type}')
177
+ print(f'Hidden size: {c.hidden_size}')
178
+ print(f'Num layers: {c.num_hidden_layers}')
179
+ print(f'Num experts: {getattr(c, \"num_experts\", \"N/A\")}')
180
+ print(f'Vocab size: {c.vocab_size}')
181
+ print(f'Pinned revision: $MODEL_REVISION')
182
+ print('Architecture supported: OK')
183
+ " || {
184
+ echo "FATAL: Qwen3.6 architecture not supported by installed transformers."
185
+ echo "Upgrade: pip install --upgrade transformers"
186
+ exit 1
187
+ }
188
+
189
+ # ── 9. Pull model from HuggingFace ─────────────────────────────────────────
190
+ echo ""
191
+ echo "=== Model Download ==="
192
+ MODEL_DIR="/workspace/models/Qwen3.6-35B-A3B"
193
+ if [ -d "$MODEL_DIR" ] && [ -f "$MODEL_DIR/config.json" ]; then
194
+ echo "Model already downloaded at $MODEL_DIR"
195
+ else
196
+ echo "Downloading Qwen3.6-35B-A3B (~70GB, this will take a while)..."
197
+ mkdir -p /workspace/models
198
+ $PYTHON -c "
199
+ from huggingface_hub import snapshot_download
200
+ import os
201
+ snapshot_download(
202
+ 'Qwen/Qwen3.6-35B-A3B',
203
+ revision='$MODEL_REVISION',
204
+ local_dir='$MODEL_DIR',
205
+ token=os.environ['HF_TOKEN'],
206
+ )
207
+ print('Model download complete.')
208
+ "
209
+ fi
210
+
211
+ # ── 10. Pull training data ──────────────────────────────────────────────────
212
+ echo ""
213
+ echo "=== Training Data ==="
214
+ DATA_DIR="/workspace/daimon-data"
215
+ mkdir -p "$DATA_DIR"
216
+
217
+ if [ -d "$DATA_DIR/train_arrow" ] && [ -d "$DATA_DIR/valid_arrow" ]; then
218
+ echo "Arrow data already present. Verifying..."
219
+ $PYTHON -c "
220
+ from datasets import load_from_disk
221
+ t = load_from_disk('$DATA_DIR/train_arrow')
222
+ v = load_from_disk('$DATA_DIR/valid_arrow')
223
+ print(f'Train: {len(t):,} samples | Valid: {len(v):,} samples β€” OK')
224
+ "
225
+ else
226
+ echo "Downloading and preparing training data..."
227
+ $PYTHON -c "
228
+ import os, json, gzip, shutil
229
+ from huggingface_hub import hf_hub_download, list_repo_files
230
+ from datasets import Dataset, load_dataset
231
+
232
+ DATA_DIR = '$DATA_DIR'
233
+ REPO = 'HumboldtJoker/daimon-sft-data'
234
+ token = os.environ.get('HF_TOKEN')
235
+
236
+ try:
237
+ # Try loading as a HF dataset first
238
+ ds = load_dataset(REPO, token=token)
239
+ if 'train' in ds:
240
+ ds['train'].save_to_disk(f'{DATA_DIR}/train_arrow')
241
+ print(f'Train: {len(ds[\"train\"]):,} samples saved as Arrow')
242
+ if 'validation' in ds:
243
+ ds['validation'].save_to_disk(f'{DATA_DIR}/valid_arrow')
244
+ print(f'Valid: {len(ds[\"validation\"]):,} samples saved as Arrow')
245
+ elif 'test' in ds:
246
+ ds['test'].save_to_disk(f'{DATA_DIR}/valid_arrow')
247
+ print(f'Valid: {len(ds[\"test\"]):,} samples saved as Arrow')
248
+ else:
249
+ # Split train into train/valid
250
+ split = ds['train'].train_test_split(test_size=0.05, seed=42)
251
+ split['train'].save_to_disk(f'{DATA_DIR}/train_arrow')
252
+ split['test'].save_to_disk(f'{DATA_DIR}/valid_arrow')
253
+ print(f'Auto-split: Train {len(split[\"train\"]):,} | Valid {len(split[\"test\"]):,}')
254
+ except Exception as e:
255
+ print(f'HF dataset load failed: {e}')
256
+ print('Trying file-based download...')
257
+
258
+ # Fall back to downloading individual files
259
+ try:
260
+ files = list_repo_files(REPO, repo_type='dataset', token=token)
261
+ for f in files:
262
+ if f.endswith(('.jsonl', '.jsonl.gz', '.json')):
263
+ print(f'Downloading {f}...')
264
+ hf_hub_download(REPO, f, repo_type='dataset', local_dir=DATA_DIR, token=token)
265
+ except Exception as e2:
266
+ print(f'File download also failed: {e2}')
267
+ print('DATA MUST BE UPLOADED MANUALLY to {DATA_DIR}/')
268
+ print('Expected format: JSONL with {\"messages\": [{\"role\": ..., \"content\": ...}, ...]}')
269
+
270
+ # Convert any JSONL files to Arrow
271
+ for split_name in ['train', 'valid']:
272
+ jsonl = f'{DATA_DIR}/{split_name}.jsonl'
273
+ gz = f'{DATA_DIR}/{split_name}.jsonl.gz'
274
+ arrow_dir = f'{DATA_DIR}/{split_name}_arrow'
275
+
276
+ if os.path.exists(gz) and not os.path.exists(jsonl):
277
+ with gzip.open(gz, 'rb') as fin, open(jsonl, 'wb') as fout:
278
+ shutil.copyfileobj(fin, fout)
279
+
280
+ if os.path.exists(jsonl) and not os.path.exists(arrow_dir):
281
+ data = []
282
+ with open(jsonl) as fh:
283
+ for line in fh:
284
+ line = line.strip()
285
+ if not line:
286
+ continue
287
+ try:
288
+ d = json.loads(line)
289
+ if 'messages' in d and len(d['messages']) >= 2:
290
+ data.append(d)
291
+ except:
292
+ pass
293
+ ds = Dataset.from_list(data)
294
+ ds.save_to_disk(arrow_dir)
295
+ print(f'{split_name}: {len(data):,} examples saved as Arrow')
296
+
297
+ # Final verification
298
+ try:
299
+ from datasets import load_from_disk
300
+ t = load_from_disk(f'{DATA_DIR}/train_arrow')
301
+ print(f'Verified train: {len(t):,} samples')
302
+ if os.path.isdir(f'{DATA_DIR}/valid_arrow'):
303
+ v = load_from_disk(f'{DATA_DIR}/valid_arrow')
304
+ print(f'Verified valid: {len(v):,} samples')
305
+ except:
306
+ print('WARNING: Could not verify data. Check $DATA_DIR manually.')
307
+ "
308
+ fi
309
+
310
+ # ── 11. Create persistent directories ──────────────────────────────────────
311
+ echo ""
312
+ echo "=== Creating Directories ==="
313
+ mkdir -p /workspace/daimon-sft/logs
314
+ mkdir -p /workspace/daimon-sft/checkpoints
315
+ echo "Output directories created on persistent volume."
316
+
317
+ # ── 12. Copy template files to /workspace ───────────────────────────────────
318
+ echo ""
319
+ echo "=== Copying Template Files ==="
320
+ SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
321
+ cp "$SCRIPT_DIR/train_daimon.py" /workspace/runpod-template/train_daimon.py 2>/dev/null || true
322
+ cp "$SCRIPT_DIR/train_daimon_config.yaml" /workspace/runpod-template/train_daimon_config.yaml 2>/dev/null || true
323
+ cp "$SCRIPT_DIR/ds_config_zero2.json" /workspace/runpod-template/ds_config_zero2.json 2>/dev/null || true
324
+ cp "$SCRIPT_DIR/launch.sh" /workspace/runpod-template/launch.sh 2>/dev/null || true
325
+ cp "$SCRIPT_DIR/test_template.py" /workspace/runpod-template/test_template.py 2>/dev/null || true
326
+ echo "Template files in /workspace/runpod-template/"
327
+
328
+ # ── 13. Add SSH key for remote access ──────────────────────────────────────
329
+ echo ""
330
+ echo "=== SSH Key ==="
331
+ mkdir -p ~/.ssh
332
+ echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtjekz8l1s6xTAXlhZJg/A0N3d6mZAyF/EyrEMiBCDG thomas@coalition" >> ~/.ssh/authorized_keys
333
+ chmod 700 ~/.ssh
334
+ chmod 600 ~/.ssh/authorized_keys
335
+ echo "SSH key added."
336
+
337
+ # ── 14. Clean up HF token from disk cache ──────────────────────────────────
338
+ echo ""
339
+ echo "=== Security Cleanup ==="
340
+ rm -f ~/.cache/huggingface/token 2>/dev/null || true
341
+ echo "Cleared cached HF token from disk."
342
+
343
+ # ── 15. Summary ─────────────────────────────────────────────────────────────
344
+ echo ""
345
+ echo "============================================================"
346
+ echo " SETUP COMPLETE β€” FULL-PARAMETER SFT"
347
+ echo ""
348
+ echo " Memory budget:"
349
+ echo " GPU: ~90GB of 141GB (model + activations)"
350
+ echo " CPU: ~105GB of ${TOTAL_RAM}GB (gradients + Adafactor)"
351
+ echo ""
352
+ echo " Next steps:"
353
+ echo " 1. Run validation: python3 /workspace/runpod-template/test_template.py"
354
+ echo " 2. Start training: bash /workspace/runpod-template/launch.sh"
355
+ echo ""
356
+ echo " Monitor:"
357
+ echo " watch -n 5 nvidia-smi"
358
+ echo " tail -f /workspace/daimon-sft/logs/training_*.log"
359
+ echo "============================================================"