Spaces:
Sleeping
Sleeping
Commit ·
b61d866
1
Parent(s): b055f87
Rewrite notebook: per-piece GRPO training on Qwen 3B
Browse filesComplete rewrite of training approach:
- Model sees board before EVERY piece (not 100 blind actions)
- Generates up to 20 action tokens per piece, stops when piece locks
- Custom REINFORCE/GRPO loop (no GRPOTrainer dependency)
- Two-phase: rollout without grad, then recompute log_probs with grad
- 8 games per iteration (same seed, GRPO-style comparison)
- 100 iterations with different seeds
Model changes:
- Qwen2.5-3B-Instruct (was 7B)
- LoRA r=16 (was r=32)
- Target GPU: L4 (was A100)
Reward structure:
- Engine per-step rewards summed across entire game
- L/R penalty: -0.1 per move
- Piece placed bonus: +1.0
- No-place penalty: -10.0 (forced drop after 20 tokens)
- Line clear bonus: +100 per line
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
docs/plans/2026-03-09-per-piece-training-design.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Per-Piece Tetris Training Design
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
Redesign training so the model sees the board before EVERY piece placement,
|
| 6 |
+
instead of outputting 100 blind actions from a single prompt.
|
| 7 |
+
|
| 8 |
+
## Key Parameters
|
| 9 |
+
|
| 10 |
+
- **Model**: Qwen2.5-3B-Instruct + LoRA
|
| 11 |
+
- **GPU**: L4
|
| 12 |
+
- **Games per iteration**: 8 (same seed, GRPO-style comparison)
|
| 13 |
+
- **Max steps per game**: 200 (total actions across all pieces)
|
| 14 |
+
- **Max tokens per piece**: 20 (if piece not placed -> forced drop + penalty)
|
| 15 |
+
- **Training iterations**: 100 (each with a new seed)
|
| 16 |
+
- **No history**: each model call = fresh prompt with current board only
|
| 17 |
+
|
| 18 |
+
## One Model Call (one piece)
|
| 19 |
+
|
| 20 |
+
1. Build prompt: system message + current board + current piece + next piece
|
| 21 |
+
2. Model generates up to 20 tokens (L/R/C/W/D/S)
|
| 22 |
+
3. Play actions one by one on the engine
|
| 23 |
+
4. Stop when piece locks (new piece spawns) or 20 tokens exhausted
|
| 24 |
+
5. If 20 tokens used and piece NOT placed: forced drop + penalty (-10)
|
| 25 |
+
|
| 26 |
+
### How to detect piece lock
|
| 27 |
+
|
| 28 |
+
After each `env.step()`, check if `current_piece` changed from the one
|
| 29 |
+
shown in the prompt. If it changed -> piece was placed, stop processing tokens.
|
| 30 |
+
|
| 31 |
+
## One Game (one playthrough)
|
| 32 |
+
|
| 33 |
+
```
|
| 34 |
+
seed = iteration_seed
|
| 35 |
+
env.reset(seed)
|
| 36 |
+
total_reward = 0
|
| 37 |
+
all_log_probs = []
|
| 38 |
+
steps = 0
|
| 39 |
+
|
| 40 |
+
while not game_over and steps < 200:
|
| 41 |
+
board_state = env.get_state()
|
| 42 |
+
current_piece = board_state['current_piece']
|
| 43 |
+
|
| 44 |
+
prompt = build_prompt(board_state) # fresh each time, no history
|
| 45 |
+
tokens, log_probs = model_generate(prompt, max_tokens=20)
|
| 46 |
+
|
| 47 |
+
piece_placed = False
|
| 48 |
+
for token in tokens:
|
| 49 |
+
action = token_to_action(token)
|
| 50 |
+
result = env.step(action)
|
| 51 |
+
total_reward += result['reward']
|
| 52 |
+
total_reward -= 0.1 if action in ('left', 'right') else 0
|
| 53 |
+
all_log_probs.append(log_probs[token])
|
| 54 |
+
steps += 1
|
| 55 |
+
|
| 56 |
+
if result['current_piece'] != current_piece:
|
| 57 |
+
# Piece was placed, new piece spawned
|
| 58 |
+
piece_placed = True
|
| 59 |
+
total_reward += 1.0 # bonus for placing piece
|
| 60 |
+
break
|
| 61 |
+
|
| 62 |
+
if result['done']:
|
| 63 |
+
game_over = True
|
| 64 |
+
break
|
| 65 |
+
|
| 66 |
+
if not piece_placed and not game_over:
|
| 67 |
+
# Piece not placed in 20 tokens -> force drop + penalty
|
| 68 |
+
env.step('drop')
|
| 69 |
+
total_reward -= 10.0
|
| 70 |
+
steps += 1
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Reward Structure
|
| 74 |
+
|
| 75 |
+
Per-step rewards from game engine (summed across ALL steps):
|
| 76 |
+
- Step penalty: -1 per step
|
| 77 |
+
- Line clears: +100/+300/+700/+1500 (1/2/3/4 lines)
|
| 78 |
+
- Height penalty: -2 * max_height (per step)
|
| 79 |
+
- Hole penalty: -5 * holes (per step)
|
| 80 |
+
- Game over: -500
|
| 81 |
+
|
| 82 |
+
Additional rewards added by training loop:
|
| 83 |
+
- L or R move: -0.1 (discourages aimless shuffling)
|
| 84 |
+
- Piece placed: +1.0 (encourages completing placements)
|
| 85 |
+
- Piece NOT placed in 20 tokens: -10.0 (forces learning to drop)
|
| 86 |
+
- Line clear bonus: +100 per line (1 line=+100, 2=+200, 3=+300, 4=+400)
|
| 87 |
+
|
| 88 |
+
Total game reward = sum of all the above across entire game.
|
| 89 |
+
|
| 90 |
+
## One Training Iteration
|
| 91 |
+
|
| 92 |
+
```
|
| 93 |
+
seed = random_seed_for_this_iteration
|
| 94 |
+
|
| 95 |
+
# Play 8 games with same seed
|
| 96 |
+
rewards = []
|
| 97 |
+
all_game_log_probs = []
|
| 98 |
+
|
| 99 |
+
for game_idx in range(8):
|
| 100 |
+
reward, log_probs_sum = play_one_game(model, seed)
|
| 101 |
+
rewards.append(reward)
|
| 102 |
+
all_game_log_probs.append(log_probs_sum)
|
| 103 |
+
|
| 104 |
+
# GRPO-style advantage
|
| 105 |
+
rewards = torch.tensor(rewards)
|
| 106 |
+
advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-8)
|
| 107 |
+
|
| 108 |
+
# Policy gradient loss
|
| 109 |
+
loss = 0
|
| 110 |
+
for i in range(8):
|
| 111 |
+
loss -= all_game_log_probs[i] * advantages[i]
|
| 112 |
+
loss = loss / 8
|
| 113 |
+
|
| 114 |
+
loss.backward()
|
| 115 |
+
optimizer.step()
|
| 116 |
+
optimizer.zero_grad()
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
## Full Training
|
| 120 |
+
|
| 121 |
+
```
|
| 122 |
+
for iteration in range(100):
|
| 123 |
+
seed = iteration # deterministic but different each time
|
| 124 |
+
train_one_iteration(model, seed)
|
| 125 |
+
|
| 126 |
+
if iteration % 10 == 0:
|
| 127 |
+
print(f"Iter {iteration}: avg_reward={mean}, std={std}")
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
## Action Token Mapping
|
| 131 |
+
|
| 132 |
+
Pre-compute token IDs for action characters:
|
| 133 |
+
```python
|
| 134 |
+
ACTION_TOKENS = {
|
| 135 |
+
tokenizer.encode('L', add_special_tokens=False)[0]: 'left',
|
| 136 |
+
tokenizer.encode('R', add_special_tokens=False)[0]: 'right',
|
| 137 |
+
tokenizer.encode('C', add_special_tokens=False)[0]: 'rotate_cw',
|
| 138 |
+
tokenizer.encode('W', add_special_tokens=False)[0]: 'rotate_ccw',
|
| 139 |
+
tokenizer.encode('D', add_special_tokens=False)[0]: 'drop',
|
| 140 |
+
tokenizer.encode('S', add_special_tokens=False)[0]: 'down',
|
| 141 |
+
}
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
During generation: mask logits to only allow these 6 tokens.
|
| 145 |
+
Sample from softmax over 6 logits -> get action + log_prob.
|
| 146 |
+
|
| 147 |
+
## Notebook Cell Structure
|
| 148 |
+
|
| 149 |
+
1. Install deps (peft, trl, accelerate, etc.)
|
| 150 |
+
2. Load Qwen2.5-3B-Instruct + LoRA
|
| 151 |
+
3. Download game_engine.py, define prompt builder
|
| 152 |
+
4. **Demo: untrained model plays one game** (show board after each piece)
|
| 153 |
+
5. Define `play_one_game()` and `train_one_iteration()`
|
| 154 |
+
6. Training loop (100 iterations)
|
| 155 |
+
7. Plot reward curve
|
| 156 |
+
8. **Demo: trained model plays one game** (compare with untrained)
|
| 157 |
+
9. Push model to HF Hub
|
| 158 |
+
|
| 159 |
+
## Time Estimate
|
| 160 |
+
|
| 161 |
+
- 3B model forward pass: ~0.05s per token on T4
|
| 162 |
+
- Per piece: ~20 tokens * 0.05s = ~1s (forward passes)
|
| 163 |
+
- Per game: ~30-50 pieces * 1s = ~40s
|
| 164 |
+
- Per iteration: 8 games (sequential) = ~320s OR batched = ~40-80s
|
| 165 |
+
- 100 iterations: ~70-130 min
|
| 166 |
+
|
| 167 |
+
Fits in a Colab T4 session (4h limit).
|
| 168 |
+
|
| 169 |
+
Note: batching 8 games in parallel requires handling variable-length episodes.
|
| 170 |
+
Simpler approach: run 8 games sequentially (~5 min/iteration, ~8h total).
|
| 171 |
+
Better approach: batch the forward passes across 8 games at each "step",
|
| 172 |
+
masking out finished games.
|
docs/plans/2026-03-09-per-piece-training-plan.md
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Per-Piece Tetris GRPO Training — Implementation Plan
|
| 2 |
+
|
| 3 |
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
| 4 |
+
|
| 5 |
+
**Goal:** Rewrite the Colab notebook so the model sees the board before every piece, plays actions until piece locks, and learns via GRPO-style policy gradient over full games.
|
| 6 |
+
|
| 7 |
+
**Architecture:** Custom REINFORCE/GRPO loop (no GRPOTrainer). Two-phase per iteration: (1) rollout 8 games without grad, storing (prompt, actions) pairs; (2) recompute log_probs with grad, multiply by advantage, backward. This keeps memory low — only one piece's activations in memory at a time.
|
| 8 |
+
|
| 9 |
+
**Tech Stack:** transformers, peft (LoRA), torch, game_engine.py (local Tetris)
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
### Task 1: Cell 1 — Install Dependencies
|
| 14 |
+
|
| 15 |
+
**Files:**
|
| 16 |
+
- Modify: `tetris_training.ipynb` Cell 1
|
| 17 |
+
|
| 18 |
+
**Step 1: Write cell content**
|
| 19 |
+
|
| 20 |
+
```python
|
| 21 |
+
# Cell 1: Install dependencies
|
| 22 |
+
!pip install peft accelerate -q
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
Note: we no longer need `trl` (no GRPOTrainer), `openenv-core`, or `datasets`.
|
| 26 |
+
|
| 27 |
+
**Step 2: Run cell, verify no errors**
|
| 28 |
+
|
| 29 |
+
**Step 3: Commit**
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
git add tetris_training.ipynb
|
| 33 |
+
git commit -m "cell 1: minimal deps for custom training loop"
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
### Task 2: Cell 2 — Load Model + LoRA
|
| 39 |
+
|
| 40 |
+
**Files:**
|
| 41 |
+
- Modify: `tetris_training.ipynb` Cell 2
|
| 42 |
+
|
| 43 |
+
**Step 1: Write cell content**
|
| 44 |
+
|
| 45 |
+
```python
|
| 46 |
+
# Cell 2: Load Qwen2.5-3B-Instruct + LoRA
|
| 47 |
+
import torch
|
| 48 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 49 |
+
from peft import LoraConfig, get_peft_model
|
| 50 |
+
|
| 51 |
+
model_name = "Qwen/Qwen2.5-3B-Instruct"
|
| 52 |
+
|
| 53 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 54 |
+
if tokenizer.pad_token is None:
|
| 55 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 56 |
+
|
| 57 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 58 |
+
model_name,
|
| 59 |
+
torch_dtype=torch.bfloat16,
|
| 60 |
+
device_map="auto",
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
lora_config = LoraConfig(
|
| 64 |
+
r=16,
|
| 65 |
+
lora_alpha=16,
|
| 66 |
+
lora_dropout=0,
|
| 67 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
|
| 68 |
+
"gate_proj", "up_proj", "down_proj"],
|
| 69 |
+
task_type="CAUSAL_LM",
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
model = get_peft_model(model, lora_config)
|
| 73 |
+
model.print_trainable_parameters()
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Note: 3B model, r=16 (smaller than 7B's r=32), bf16.
|
| 77 |
+
|
| 78 |
+
**Step 2: Run cell, verify output shows trainable params**
|
| 79 |
+
|
| 80 |
+
**Step 3: Commit**
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
### Task 3: Cell 3 — Game Engine + Constants + Prompt Builder
|
| 85 |
+
|
| 86 |
+
**Files:**
|
| 87 |
+
- Modify: `tetris_training.ipynb` Cell 3 (replaces old TetrisClient cell)
|
| 88 |
+
|
| 89 |
+
**Step 1: Write cell content**
|
| 90 |
+
|
| 91 |
+
```python
|
| 92 |
+
# Cell 3: Game engine + constants + prompt builder
|
| 93 |
+
import random
|
| 94 |
+
import torch.nn.functional as F
|
| 95 |
+
|
| 96 |
+
# Download game engine
|
| 97 |
+
!wget -q -O game_engine.py https://raw.githubusercontent.com/OutOfMystic/tetris-openenv/main/src/tetris_env/server/game_engine.py
|
| 98 |
+
from game_engine import TetrisEnv
|
| 99 |
+
|
| 100 |
+
# === Constants ===
|
| 101 |
+
MAX_ACTIONS_PER_PIECE = 20
|
| 102 |
+
MAX_STEPS_PER_GAME = 200
|
| 103 |
+
GAMES_PER_ITER = 8
|
| 104 |
+
NUM_ITERATIONS = 100
|
| 105 |
+
TEMPERATURE = 0.7
|
| 106 |
+
|
| 107 |
+
# Training reward modifiers (on top of engine rewards)
|
| 108 |
+
LR_PENALTY = -0.1 # per L/R move
|
| 109 |
+
PIECE_PLACED_BONUS = 1.0 # per piece successfully placed
|
| 110 |
+
NO_PLACE_PENALTY = -10.0 # if 20 tokens exhausted without placing
|
| 111 |
+
LINE_CLEAR_BONUS = 100.0 # per line cleared (1=+100, 2=+200, 3=+300, 4=+400)
|
| 112 |
+
|
| 113 |
+
# Action mapping
|
| 114 |
+
ACTION_CHARS = ['L', 'R', 'C', 'W', 'D', 'S']
|
| 115 |
+
ACTION_TO_ENGINE = {
|
| 116 |
+
'L': 'left', 'R': 'right', 'C': 'rotate_cw',
|
| 117 |
+
'W': 'rotate_ccw', 'D': 'drop', 'S': 'down'
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
# Pre-compute token IDs for action characters
|
| 121 |
+
ACTION_TOKEN_IDS = []
|
| 122 |
+
for ch in ACTION_CHARS:
|
| 123 |
+
ids = tokenizer.encode(ch, add_special_tokens=False)
|
| 124 |
+
ACTION_TOKEN_IDS.append(ids[0])
|
| 125 |
+
print(f" '{ch}' -> token_id {ids[0]}")
|
| 126 |
+
ACTION_TOKEN_IDS = torch.tensor(ACTION_TOKEN_IDS, device=model.device)
|
| 127 |
+
|
| 128 |
+
# Token ID -> action char lookup
|
| 129 |
+
TOKEN_TO_CHAR = {ACTION_TOKEN_IDS[i].item(): ACTION_CHARS[i] for i in range(6)}
|
| 130 |
+
|
| 131 |
+
SYSTEM_PROMPT = """You are a Tetris AI. You see the board and current piece.
|
| 132 |
+
Output actions as single letters: L=left R=right C=rotate_cw W=rotate_ccw D=drop S=down
|
| 133 |
+
Place the piece to fill complete rows. Drop when positioned."""
|
| 134 |
+
|
| 135 |
+
def build_prompt(result):
|
| 136 |
+
return f"""Board:
|
| 137 |
+
{result['board']}
|
| 138 |
+
|
| 139 |
+
Piece: {result['current_piece']} Next: {result['next_piece']}
|
| 140 |
+
Score: {result['score']} Lines: {result['total_lines']} Height: {result['max_height']} Holes: {result['holes']}
|
| 141 |
+
|
| 142 |
+
Your actions:"""
|
| 143 |
+
|
| 144 |
+
print("Game engine loaded. Action tokens mapped.")
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
**Step 2: Run cell, verify 6 token mappings printed**
|
| 148 |
+
|
| 149 |
+
**Step 3: Commit**
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
### Task 4: Cell 4 — Core Functions (play_one_game + train_one_iteration)
|
| 154 |
+
|
| 155 |
+
**Files:**
|
| 156 |
+
- Modify: `tetris_training.ipynb` Cell 4
|
| 157 |
+
|
| 158 |
+
This is the most critical cell. Two phases per iteration:
|
| 159 |
+
- **Rollout** (no grad): play 8 games, store (prompt_ids, action_token_ids) per piece
|
| 160 |
+
- **Update** (with grad): recompute log_probs, multiply by advantage, backward per piece
|
| 161 |
+
|
| 162 |
+
**Step 1: Write cell content**
|
| 163 |
+
|
| 164 |
+
```python
|
| 165 |
+
# Cell 4: Core training functions
|
| 166 |
+
|
| 167 |
+
def play_one_game(model, tokenizer, seed, temperature=TEMPERATURE):
|
| 168 |
+
"""
|
| 169 |
+
Play a full Tetris game. Model sees board before each piece,
|
| 170 |
+
generates up to 20 action tokens per piece.
|
| 171 |
+
Returns reward and piece data for gradient computation.
|
| 172 |
+
"""
|
| 173 |
+
env = TetrisEnv(seed=seed)
|
| 174 |
+
env.reset(seed=seed)
|
| 175 |
+
|
| 176 |
+
# Initial random offset (same as old prompt generation)
|
| 177 |
+
rng = random.Random(seed)
|
| 178 |
+
moves = rng.randint(0, 4)
|
| 179 |
+
direction = rng.choice(["left", "right"])
|
| 180 |
+
for _ in range(moves):
|
| 181 |
+
if env.done:
|
| 182 |
+
break
|
| 183 |
+
env.step(direction)
|
| 184 |
+
|
| 185 |
+
total_reward = 0.0
|
| 186 |
+
total_steps = 0
|
| 187 |
+
pieces_data = []
|
| 188 |
+
|
| 189 |
+
while not env.done and total_steps < MAX_STEPS_PER_GAME:
|
| 190 |
+
current_piece_name = env.current_piece_name
|
| 191 |
+
lines_before = env.total_lines
|
| 192 |
+
|
| 193 |
+
# Build fresh prompt for this piece
|
| 194 |
+
result = env._make_result(0)
|
| 195 |
+
messages = [
|
| 196 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 197 |
+
{"role": "user", "content": build_prompt(result)},
|
| 198 |
+
]
|
| 199 |
+
prompt_ids = tokenizer.apply_chat_template(
|
| 200 |
+
messages, return_tensors="pt", add_generation_prompt=True
|
| 201 |
+
).to(model.device)
|
| 202 |
+
|
| 203 |
+
# Autoregressive generation with KV cache (no grad)
|
| 204 |
+
input_ids = prompt_ids
|
| 205 |
+
action_ids_list = []
|
| 206 |
+
piece_placed = False
|
| 207 |
+
past_kv = None
|
| 208 |
+
|
| 209 |
+
with torch.no_grad():
|
| 210 |
+
for _ in range(MAX_ACTIONS_PER_PIECE):
|
| 211 |
+
if past_kv is None:
|
| 212 |
+
out = model(input_ids, use_cache=True)
|
| 213 |
+
past_kv = out.past_key_values
|
| 214 |
+
else:
|
| 215 |
+
out = model(input_ids[:, -1:], past_key_values=past_kv, use_cache=True)
|
| 216 |
+
past_kv = out.past_key_values
|
| 217 |
+
|
| 218 |
+
logits = out.logits[:, -1, :] # [1, vocab]
|
| 219 |
+
|
| 220 |
+
# Mask to only 6 action tokens
|
| 221 |
+
masked = torch.full_like(logits, float('-inf'))
|
| 222 |
+
masked[0, ACTION_TOKEN_IDS] = logits[0, ACTION_TOKEN_IDS]
|
| 223 |
+
probs = F.softmax(masked / temperature, dim=-1)
|
| 224 |
+
|
| 225 |
+
# Sample
|
| 226 |
+
token_id = torch.multinomial(probs, 1).item()
|
| 227 |
+
action_ids_list.append(token_id)
|
| 228 |
+
|
| 229 |
+
# Execute in engine
|
| 230 |
+
action_char = TOKEN_TO_CHAR[token_id]
|
| 231 |
+
action_name = ACTION_TO_ENGINE[action_char]
|
| 232 |
+
step_result = env.step(action_name)
|
| 233 |
+
|
| 234 |
+
total_reward += step_result['reward']
|
| 235 |
+
total_steps += 1
|
| 236 |
+
|
| 237 |
+
if action_name in ('left', 'right'):
|
| 238 |
+
total_reward += LR_PENALTY
|
| 239 |
+
|
| 240 |
+
# Check if piece placed (new piece spawned)
|
| 241 |
+
if env.current_piece_name != current_piece_name:
|
| 242 |
+
piece_placed = True
|
| 243 |
+
total_reward += PIECE_PLACED_BONUS
|
| 244 |
+
lines_cleared = env.total_lines - lines_before
|
| 245 |
+
if lines_cleared > 0:
|
| 246 |
+
total_reward += lines_cleared * LINE_CLEAR_BONUS
|
| 247 |
+
break
|
| 248 |
+
|
| 249 |
+
if env.done:
|
| 250 |
+
break
|
| 251 |
+
|
| 252 |
+
# Append token for next autoregressive step
|
| 253 |
+
input_ids = torch.cat([
|
| 254 |
+
input_ids,
|
| 255 |
+
torch.tensor([[token_id]], device=model.device)
|
| 256 |
+
], dim=-1)
|
| 257 |
+
|
| 258 |
+
# Force drop if piece not placed
|
| 259 |
+
if not piece_placed and not env.done:
|
| 260 |
+
env.step('drop')
|
| 261 |
+
total_reward += NO_PLACE_PENALTY
|
| 262 |
+
total_steps += 1
|
| 263 |
+
lines_cleared = env.total_lines - lines_before
|
| 264 |
+
if lines_cleared > 0:
|
| 265 |
+
total_reward += lines_cleared * LINE_CLEAR_BONUS
|
| 266 |
+
|
| 267 |
+
# Store for gradient computation
|
| 268 |
+
if action_ids_list:
|
| 269 |
+
pieces_data.append({
|
| 270 |
+
'prompt_ids': prompt_ids.cpu(),
|
| 271 |
+
'action_ids': torch.tensor(action_ids_list, dtype=torch.long),
|
| 272 |
+
})
|
| 273 |
+
|
| 274 |
+
return {
|
| 275 |
+
'reward': total_reward,
|
| 276 |
+
'pieces': pieces_data,
|
| 277 |
+
'total_steps': total_steps,
|
| 278 |
+
'total_lines': env.total_lines,
|
| 279 |
+
'pieces_placed': len(pieces_data),
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def train_one_iteration(model, optimizer, seed, temperature=TEMPERATURE):
|
| 284 |
+
"""
|
| 285 |
+
One GRPO iteration:
|
| 286 |
+
1. Play 8 games (same seed) without grad
|
| 287 |
+
2. Compute advantages
|
| 288 |
+
3. Recompute log_probs with grad, apply policy gradient
|
| 289 |
+
"""
|
| 290 |
+
# Phase 1: Rollout
|
| 291 |
+
games = []
|
| 292 |
+
for _ in range(GAMES_PER_ITER):
|
| 293 |
+
game = play_one_game(model, tokenizer, seed, temperature)
|
| 294 |
+
games.append(game)
|
| 295 |
+
|
| 296 |
+
rewards = torch.tensor([g['reward'] for g in games], dtype=torch.float32)
|
| 297 |
+
mean_r = rewards.mean().item()
|
| 298 |
+
std_r = rewards.std().item()
|
| 299 |
+
|
| 300 |
+
# Phase 2: Advantages (GRPO-style)
|
| 301 |
+
if std_r < 1e-8:
|
| 302 |
+
# All games got same reward — no learning signal
|
| 303 |
+
return {'mean_reward': mean_r, 'std_reward': 0.0, 'loss': 0.0,
|
| 304 |
+
'avg_steps': sum(g['total_steps'] for g in games) / GAMES_PER_ITER,
|
| 305 |
+
'avg_lines': sum(g['total_lines'] for g in games) / GAMES_PER_ITER}
|
| 306 |
+
|
| 307 |
+
advantages = ((rewards - rewards.mean()) / (rewards.std() + 1e-8)).tolist()
|
| 308 |
+
|
| 309 |
+
# Phase 3: Update — recompute log_probs with grad
|
| 310 |
+
optimizer.zero_grad()
|
| 311 |
+
total_pieces = sum(len(g['pieces']) for g in games)
|
| 312 |
+
total_loss = 0.0
|
| 313 |
+
|
| 314 |
+
for game_idx, game in enumerate(games):
|
| 315 |
+
adv = advantages[game_idx]
|
| 316 |
+
for piece in game['pieces']:
|
| 317 |
+
prompt = piece['prompt_ids'].to(model.device)
|
| 318 |
+
actions = piece['action_ids'].to(model.device)
|
| 319 |
+
if len(actions) == 0:
|
| 320 |
+
continue
|
| 321 |
+
|
| 322 |
+
# Teacher-forced forward pass: prompt + actions
|
| 323 |
+
full_input = torch.cat([prompt.squeeze(0), actions]).unsqueeze(0)
|
| 324 |
+
logits = model(full_input).logits
|
| 325 |
+
|
| 326 |
+
# Log_probs at positions where actions were generated
|
| 327 |
+
P = prompt.shape[-1]
|
| 328 |
+
action_logits = logits[0, P-1 : P-1+len(actions), :]
|
| 329 |
+
|
| 330 |
+
# Mask to action tokens, apply temperature
|
| 331 |
+
masked = torch.full_like(action_logits, float('-inf'))
|
| 332 |
+
masked[:, ACTION_TOKEN_IDS] = action_logits[:, ACTION_TOKEN_IDS]
|
| 333 |
+
log_probs = F.log_softmax(masked / temperature, dim=-1)
|
| 334 |
+
|
| 335 |
+
selected = log_probs.gather(1, actions.unsqueeze(1)).squeeze(1)
|
| 336 |
+
piece_loss = -(selected.sum() * adv) / total_pieces
|
| 337 |
+
|
| 338 |
+
piece_loss.backward()
|
| 339 |
+
total_loss += piece_loss.item()
|
| 340 |
+
|
| 341 |
+
# Gradient clipping
|
| 342 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
| 343 |
+
optimizer.step()
|
| 344 |
+
|
| 345 |
+
return {
|
| 346 |
+
'mean_reward': mean_r,
|
| 347 |
+
'std_reward': std_r,
|
| 348 |
+
'loss': total_loss,
|
| 349 |
+
'avg_steps': sum(g['total_steps'] for g in games) / GAMES_PER_ITER,
|
| 350 |
+
'avg_lines': sum(g['total_lines'] for g in games) / GAMES_PER_ITER,
|
| 351 |
+
'avg_pieces': sum(g['pieces_placed'] for g in games) / GAMES_PER_ITER,
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
print("Training functions defined.")
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
**Step 2: Run cell, verify "Training functions defined." printed**
|
| 358 |
+
|
| 359 |
+
**Step 3: Quick smoke test**
|
| 360 |
+
|
| 361 |
+
```python
|
| 362 |
+
# Smoke test: play 1 game
|
| 363 |
+
test_game = play_one_game(model, tokenizer, seed=0)
|
| 364 |
+
print(f"Reward: {test_game['reward']:.1f}, Steps: {test_game['total_steps']}, "
|
| 365 |
+
f"Pieces: {test_game['pieces_placed']}, Lines: {test_game['total_lines']}")
|
| 366 |
+
```
|
| 367 |
+
|
| 368 |
+
**Step 4: Commit**
|
| 369 |
+
|
| 370 |
+
---
|
| 371 |
+
|
| 372 |
+
### Task 5: Cell 5 — Demo Untrained Model
|
| 373 |
+
|
| 374 |
+
**Files:**
|
| 375 |
+
- Modify: `tetris_training.ipynb` Cell 5
|
| 376 |
+
|
| 377 |
+
**Step 1: Write cell content**
|
| 378 |
+
|
| 379 |
+
```python
|
| 380 |
+
# Cell 5: Demo — UNTRAINED model plays one game
|
| 381 |
+
print("=== UNTRAINED MODEL ===\n")
|
| 382 |
+
|
| 383 |
+
game = play_one_game(model, tokenizer, seed=42)
|
| 384 |
+
|
| 385 |
+
print(f"Total steps: {game['total_steps']}")
|
| 386 |
+
print(f"Pieces placed: {game['pieces_placed']}")
|
| 387 |
+
print(f"Lines cleared: {game['total_lines']}")
|
| 388 |
+
print(f"Game reward: {game['reward']:+.1f}")
|
| 389 |
+
|
| 390 |
+
# Show final board
|
| 391 |
+
env = TetrisEnv(seed=42)
|
| 392 |
+
env.reset(seed=42)
|
| 393 |
+
rng = random.Random(42)
|
| 394 |
+
moves = rng.randint(0, 4)
|
| 395 |
+
direction = rng.choice(["left", "right"])
|
| 396 |
+
for _ in range(moves):
|
| 397 |
+
env.step(direction)
|
| 398 |
+
|
| 399 |
+
# Replay the actions to get final board
|
| 400 |
+
for piece in game['pieces']:
|
| 401 |
+
for token_id in piece['action_ids'].tolist():
|
| 402 |
+
action_name = ACTION_TO_ENGINE[TOKEN_TO_CHAR[token_id]]
|
| 403 |
+
if not env.done:
|
| 404 |
+
env.step(action_name)
|
| 405 |
+
if not env.done:
|
| 406 |
+
# Check if piece was placed; if not, force drop
|
| 407 |
+
pass # engine handles lock internally
|
| 408 |
+
|
| 409 |
+
print(f"\nFinal board:")
|
| 410 |
+
print(env.board_to_text())
|
| 411 |
+
|
| 412 |
+
untrained_reward = game['reward']
|
| 413 |
+
```
|
| 414 |
+
|
| 415 |
+
**Step 2: Run cell, verify board prints**
|
| 416 |
+
|
| 417 |
+
**Step 3: Commit**
|
| 418 |
+
|
| 419 |
+
---
|
| 420 |
+
|
| 421 |
+
### Task 6: Cell 6 — Training Loop
|
| 422 |
+
|
| 423 |
+
**Files:**
|
| 424 |
+
- Modify: `tetris_training.ipynb` Cell 6
|
| 425 |
+
|
| 426 |
+
**Step 1: Write cell content**
|
| 427 |
+
|
| 428 |
+
```python
|
| 429 |
+
# Cell 6: Training loop — 100 iterations
|
| 430 |
+
optimizer = torch.optim.AdamW(
|
| 431 |
+
[p for p in model.parameters() if p.requires_grad],
|
| 432 |
+
lr=5e-6,
|
| 433 |
+
weight_decay=0.01,
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
history = []
|
| 437 |
+
|
| 438 |
+
print("Starting per-piece GRPO training...")
|
| 439 |
+
print(f"Config: {GAMES_PER_ITER} games/iter, max {MAX_STEPS_PER_GAME} steps, "
|
| 440 |
+
f"max {MAX_ACTIONS_PER_PIECE} tokens/piece, {NUM_ITERATIONS} iterations\n")
|
| 441 |
+
|
| 442 |
+
for iteration in range(NUM_ITERATIONS):
|
| 443 |
+
stats = train_one_iteration(model, optimizer, seed=iteration)
|
| 444 |
+
history.append(stats)
|
| 445 |
+
|
| 446 |
+
if iteration % 5 == 0 or iteration == NUM_ITERATIONS - 1:
|
| 447 |
+
print(f"[Iter {iteration:3d}] "
|
| 448 |
+
f"reward={stats['mean_reward']:+8.1f} "
|
| 449 |
+
f"std={stats['std_reward']:6.1f} "
|
| 450 |
+
f"loss={stats['loss']:7.3f} "
|
| 451 |
+
f"steps={stats['avg_steps']:5.1f} "
|
| 452 |
+
f"lines={stats['avg_lines']:4.1f} "
|
| 453 |
+
f"pieces={stats['avg_pieces']:4.1f}")
|
| 454 |
+
|
| 455 |
+
print("\nTraining complete!")
|
| 456 |
+
```
|
| 457 |
+
|
| 458 |
+
**Step 2: Run cell, verify reward logs appear**
|
| 459 |
+
|
| 460 |
+
**Step 3: Commit**
|
| 461 |
+
|
| 462 |
+
---
|
| 463 |
+
|
| 464 |
+
### Task 7: Cell 7 — Plot Reward Curve
|
| 465 |
+
|
| 466 |
+
**Files:**
|
| 467 |
+
- Modify: `tetris_training.ipynb` Cell 7
|
| 468 |
+
|
| 469 |
+
**Step 1: Write cell content**
|
| 470 |
+
|
| 471 |
+
```python
|
| 472 |
+
# Cell 7: Plot reward curve
|
| 473 |
+
import matplotlib.pyplot as plt
|
| 474 |
+
|
| 475 |
+
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
|
| 476 |
+
|
| 477 |
+
iters = range(len(history))
|
| 478 |
+
|
| 479 |
+
axes[0].plot(iters, [h['mean_reward'] for h in history])
|
| 480 |
+
axes[0].set_title('Mean Reward per Iteration')
|
| 481 |
+
axes[0].set_xlabel('Iteration')
|
| 482 |
+
axes[0].set_ylabel('Reward')
|
| 483 |
+
|
| 484 |
+
axes[1].plot(iters, [h['avg_lines'] for h in history])
|
| 485 |
+
axes[1].set_title('Avg Lines Cleared')
|
| 486 |
+
axes[1].set_xlabel('Iteration')
|
| 487 |
+
|
| 488 |
+
axes[2].plot(iters, [h['loss'] for h in history])
|
| 489 |
+
axes[2].set_title('Policy Gradient Loss')
|
| 490 |
+
axes[2].set_xlabel('Iteration')
|
| 491 |
+
|
| 492 |
+
plt.tight_layout()
|
| 493 |
+
plt.savefig('reward_curve.png', dpi=150)
|
| 494 |
+
plt.show()
|
| 495 |
+
```
|
| 496 |
+
|
| 497 |
+
**Step 2: Run, verify plots**
|
| 498 |
+
|
| 499 |
+
**Step 3: Commit**
|
| 500 |
+
|
| 501 |
+
---
|
| 502 |
+
|
| 503 |
+
### Task 8: Cell 8 — Demo Trained Model
|
| 504 |
+
|
| 505 |
+
**Files:**
|
| 506 |
+
- Modify: `tetris_training.ipynb` Cell 8
|
| 507 |
+
|
| 508 |
+
**Step 1: Write cell content**
|
| 509 |
+
|
| 510 |
+
```python
|
| 511 |
+
# Cell 8: Demo — TRAINED model plays 3 games
|
| 512 |
+
print("=== TRAINED MODEL ===\n")
|
| 513 |
+
|
| 514 |
+
trained_rewards = []
|
| 515 |
+
for seed in [42, 123, 7]:
|
| 516 |
+
game = play_one_game(model, tokenizer, seed=seed)
|
| 517 |
+
print(f"Seed {seed}: reward={game['reward']:+.1f}, "
|
| 518 |
+
f"steps={game['total_steps']}, lines={game['total_lines']}, "
|
| 519 |
+
f"pieces={game['pieces_placed']}")
|
| 520 |
+
trained_rewards.append(game['reward'])
|
| 521 |
+
|
| 522 |
+
avg_trained = sum(trained_rewards) / len(trained_rewards)
|
| 523 |
+
print(f"\n{'='*50}")
|
| 524 |
+
print(f"UNTRAINED reward (seed=42): {untrained_reward:+.1f}")
|
| 525 |
+
print(f"TRAINED avg reward (3 games): {avg_trained:+.1f}")
|
| 526 |
+
print(f"Improvement: {avg_trained - untrained_reward:+.1f}")
|
| 527 |
+
print('='*50)
|
| 528 |
+
```
|
| 529 |
+
|
| 530 |
+
**Step 2: Run, verify comparison**
|
| 531 |
+
|
| 532 |
+
**Step 3: Commit**
|
| 533 |
+
|
| 534 |
+
---
|
| 535 |
+
|
| 536 |
+
### Task 9: Cell 9 — Push to HF Hub
|
| 537 |
+
|
| 538 |
+
**Files:**
|
| 539 |
+
- Modify: `tetris_training.ipynb` Cell 9
|
| 540 |
+
|
| 541 |
+
**Step 1: Write cell content**
|
| 542 |
+
|
| 543 |
+
```python
|
| 544 |
+
# Cell 9: Push trained model to HF Hub
|
| 545 |
+
model.push_to_hub("VortexedSquirrel/tetris-agent-grpo")
|
| 546 |
+
tokenizer.push_to_hub("VortexedSquirrel/tetris-agent-grpo")
|
| 547 |
+
print("Model pushed to https://huggingface.co/VortexedSquirrel/tetris-agent-grpo")
|
| 548 |
+
```
|
| 549 |
+
|
| 550 |
+
**Step 2: Commit + push**
|
| 551 |
+
|
| 552 |
+
```bash
|
| 553 |
+
git add tetris_training.ipynb
|
| 554 |
+
git commit -m "rewrite notebook: per-piece GRPO training on 3B"
|
| 555 |
+
git push origin main
|
| 556 |
+
```
|
| 557 |
+
|
| 558 |
+
---
|
| 559 |
+
|
| 560 |
+
## Key Implementation Details
|
| 561 |
+
|
| 562 |
+
### Piece lock detection
|
| 563 |
+
After `env.step()`, check `env.current_piece_name != current_piece_name`.
|
| 564 |
+
The engine calls `_spawn_next()` when a piece locks, changing the current piece.
|
| 565 |
+
|
| 566 |
+
### Two-phase gradient computation
|
| 567 |
+
- **Rollout phase**: `torch.no_grad()`, KV cache for fast autoregressive generation
|
| 568 |
+
- **Update phase**: teacher-forced forward pass (prompt + actions in one call),
|
| 569 |
+
`piece_loss.backward()` after each piece to keep memory low
|
| 570 |
+
|
| 571 |
+
### Memory-safe gradient accumulation
|
| 572 |
+
Each piece does its own `.backward()` adding to accumulated gradients.
|
| 573 |
+
Only one piece's computation graph in memory at a time.
|
| 574 |
+
`optimizer.step()` called once after all 8 games processed.
|
| 575 |
+
|
| 576 |
+
### Time estimate (L4, 3B model)
|
| 577 |
+
- Rollout: 8 games × ~200 steps × ~0.02s/token ≈ 32s
|
| 578 |
+
- Update: 8 games × ~30 pieces × ~0.1s/piece ≈ 24s
|
| 579 |
+
- Total per iteration: ~56s
|
| 580 |
+
- 100 iterations: ~93 min ≈ 1.5 hours
|
tetris_training.ipynb
CHANGED
|
@@ -5,11 +5,11 @@
|
|
| 5 |
"metadata": {
|
| 6 |
"id": "jYiMCaOSFG8J"
|
| 7 |
},
|
| 8 |
-
"source": "# Tetris OpenEnv — GRPO Training\n\nTrain an LLM
|
| 9 |
},
|
| 10 |
{
|
| 11 |
"cell_type": "code",
|
| 12 |
-
"execution_count":
|
| 13 |
"metadata": {
|
| 14 |
"colab": {
|
| 15 |
"base_uri": "https://localhost:8080/"
|
|
@@ -17,35 +17,12 @@
|
|
| 17 |
"id": "PHNUG6nYFG8L",
|
| 18 |
"outputId": "e913c80a-f0ec-4231-a35c-f01106a333a1"
|
| 19 |
},
|
| 20 |
-
"outputs": [
|
| 21 |
-
|
| 22 |
-
"output_type": "stream",
|
| 23 |
-
"name": "stdout",
|
| 24 |
-
"text": [
|
| 25 |
-
"\u001b[33mWARNING: Skipping unsloth as it is not installed.\u001b[0m\u001b[33m\n",
|
| 26 |
-
"\u001b[0m\u001b[33mWARNING: Skipping unsloth-zoo as it is not installed.\u001b[0m\u001b[33m\n",
|
| 27 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m528.8/528.8 kB\u001b[0m \u001b[31m10.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 28 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m121.9/121.9 kB\u001b[0m \u001b[31m15.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 29 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m633.7/633.7 kB\u001b[0m \u001b[31m33.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 30 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m251.7/251.7 kB\u001b[0m \u001b[31m20.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 31 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m201.9/201.9 kB\u001b[0m \u001b[31m23.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 32 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m96.4/96.4 kB\u001b[0m \u001b[31m11.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 33 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m152.3/152.3 kB\u001b[0m \u001b[31m17.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 34 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m80.2/80.2 kB\u001b[0m \u001b[31m9.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 35 |
-
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m331.1/331.1 kB\u001b[0m \u001b[31m34.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
| 36 |
-
"\u001b[?25h"
|
| 37 |
-
]
|
| 38 |
-
}
|
| 39 |
-
],
|
| 40 |
-
"source": [
|
| 41 |
-
"# Cell 1: Install dependencies (remove Unsloth, use standard HF stack)\n",
|
| 42 |
-
"!pip uninstall unsloth unsloth-zoo -y -q\n",
|
| 43 |
-
"!pip install peft trl openenv-core datasets accelerate -q\n"
|
| 44 |
-
]
|
| 45 |
},
|
| 46 |
{
|
| 47 |
"cell_type": "code",
|
| 48 |
-
"execution_count":
|
| 49 |
"metadata": {
|
| 50 |
"colab": {
|
| 51 |
"base_uri": "https://localhost:8080/",
|
|
@@ -166,208 +143,12 @@
|
|
| 166 |
"id": "4CEG1_JMFG8L",
|
| 167 |
"outputId": "fe327915-556f-409f-c75c-fc2f74a7e5f6"
|
| 168 |
},
|
| 169 |
-
"outputs": [
|
| 170 |
-
|
| 171 |
-
"output_type": "stream",
|
| 172 |
-
"name": "stderr",
|
| 173 |
-
"text": [
|
| 174 |
-
"/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n",
|
| 175 |
-
"The secret `HF_TOKEN` does not exist in your Colab secrets.\n",
|
| 176 |
-
"To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n",
|
| 177 |
-
"You will be able to reuse this secret in all of your notebooks.\n",
|
| 178 |
-
"Please note that authentication is recommended but still optional to access public models or datasets.\n",
|
| 179 |
-
" warnings.warn(\n"
|
| 180 |
-
]
|
| 181 |
-
},
|
| 182 |
-
{
|
| 183 |
-
"output_type": "display_data",
|
| 184 |
-
"data": {
|
| 185 |
-
"text/plain": [
|
| 186 |
-
"config.json: 0%| | 0.00/663 [00:00<?, ?B/s]"
|
| 187 |
-
],
|
| 188 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 189 |
-
"version_major": 2,
|
| 190 |
-
"version_minor": 0,
|
| 191 |
-
"model_id": "6285b80f4b53409094ba1877193efba5"
|
| 192 |
-
}
|
| 193 |
-
},
|
| 194 |
-
"metadata": {}
|
| 195 |
-
},
|
| 196 |
-
{
|
| 197 |
-
"output_type": "display_data",
|
| 198 |
-
"data": {
|
| 199 |
-
"text/plain": [
|
| 200 |
-
"tokenizer_config.json: 0.00B [00:00, ?B/s]"
|
| 201 |
-
],
|
| 202 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 203 |
-
"version_major": 2,
|
| 204 |
-
"version_minor": 0,
|
| 205 |
-
"model_id": "c34a5bdf556842fa9faaab6bac674e4b"
|
| 206 |
-
}
|
| 207 |
-
},
|
| 208 |
-
"metadata": {}
|
| 209 |
-
},
|
| 210 |
-
{
|
| 211 |
-
"output_type": "display_data",
|
| 212 |
-
"data": {
|
| 213 |
-
"text/plain": [
|
| 214 |
-
"vocab.json: 0.00B [00:00, ?B/s]"
|
| 215 |
-
],
|
| 216 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 217 |
-
"version_major": 2,
|
| 218 |
-
"version_minor": 0,
|
| 219 |
-
"model_id": "ed82c562c8824a1ebf459cdcbad610c9"
|
| 220 |
-
}
|
| 221 |
-
},
|
| 222 |
-
"metadata": {}
|
| 223 |
-
},
|
| 224 |
-
{
|
| 225 |
-
"output_type": "display_data",
|
| 226 |
-
"data": {
|
| 227 |
-
"text/plain": [
|
| 228 |
-
"merges.txt: 0.00B [00:00, ?B/s]"
|
| 229 |
-
],
|
| 230 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 231 |
-
"version_major": 2,
|
| 232 |
-
"version_minor": 0,
|
| 233 |
-
"model_id": "fcd3965e71074108a5e932cbb97c40bc"
|
| 234 |
-
}
|
| 235 |
-
},
|
| 236 |
-
"metadata": {}
|
| 237 |
-
},
|
| 238 |
-
{
|
| 239 |
-
"output_type": "display_data",
|
| 240 |
-
"data": {
|
| 241 |
-
"text/plain": [
|
| 242 |
-
"tokenizer.json: 0.00B [00:00, ?B/s]"
|
| 243 |
-
],
|
| 244 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 245 |
-
"version_major": 2,
|
| 246 |
-
"version_minor": 0,
|
| 247 |
-
"model_id": "6c8415ffb68445619f115c60e32c616b"
|
| 248 |
-
}
|
| 249 |
-
},
|
| 250 |
-
"metadata": {}
|
| 251 |
-
},
|
| 252 |
-
{
|
| 253 |
-
"output_type": "stream",
|
| 254 |
-
"name": "stderr",
|
| 255 |
-
"text": [
|
| 256 |
-
"`torch_dtype` is deprecated! Use `dtype` instead!\n"
|
| 257 |
-
]
|
| 258 |
-
},
|
| 259 |
-
{
|
| 260 |
-
"output_type": "display_data",
|
| 261 |
-
"data": {
|
| 262 |
-
"text/plain": [
|
| 263 |
-
"model.safetensors.index.json: 0.00B [00:00, ?B/s]"
|
| 264 |
-
],
|
| 265 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 266 |
-
"version_major": 2,
|
| 267 |
-
"version_minor": 0,
|
| 268 |
-
"model_id": "f7918c9c71a34e178a45e889e33cec93"
|
| 269 |
-
}
|
| 270 |
-
},
|
| 271 |
-
"metadata": {}
|
| 272 |
-
},
|
| 273 |
-
{
|
| 274 |
-
"output_type": "display_data",
|
| 275 |
-
"data": {
|
| 276 |
-
"text/plain": [
|
| 277 |
-
"Downloading (incomplete total...): 0.00B [00:00, ?B/s]"
|
| 278 |
-
],
|
| 279 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 280 |
-
"version_major": 2,
|
| 281 |
-
"version_minor": 0,
|
| 282 |
-
"model_id": "8c7e3c13b85d4013bd571019d6efff5f"
|
| 283 |
-
}
|
| 284 |
-
},
|
| 285 |
-
"metadata": {}
|
| 286 |
-
},
|
| 287 |
-
{
|
| 288 |
-
"output_type": "display_data",
|
| 289 |
-
"data": {
|
| 290 |
-
"text/plain": [
|
| 291 |
-
"Fetching 4 files: 0%| | 0/4 [00:00<?, ?it/s]"
|
| 292 |
-
],
|
| 293 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 294 |
-
"version_major": 2,
|
| 295 |
-
"version_minor": 0,
|
| 296 |
-
"model_id": "ff3dd07d11e04ca8b5bef7c77d95c7ff"
|
| 297 |
-
}
|
| 298 |
-
},
|
| 299 |
-
"metadata": {}
|
| 300 |
-
},
|
| 301 |
-
{
|
| 302 |
-
"output_type": "display_data",
|
| 303 |
-
"data": {
|
| 304 |
-
"text/plain": [
|
| 305 |
-
"Loading weights: 0%| | 0/339 [00:00<?, ?it/s]"
|
| 306 |
-
],
|
| 307 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 308 |
-
"version_major": 2,
|
| 309 |
-
"version_minor": 0,
|
| 310 |
-
"model_id": "cfe6c9c2bc5d460d846f2af15f9d7ed6"
|
| 311 |
-
}
|
| 312 |
-
},
|
| 313 |
-
"metadata": {}
|
| 314 |
-
},
|
| 315 |
-
{
|
| 316 |
-
"output_type": "display_data",
|
| 317 |
-
"data": {
|
| 318 |
-
"text/plain": [
|
| 319 |
-
"generation_config.json: 0%| | 0.00/243 [00:00<?, ?B/s]"
|
| 320 |
-
],
|
| 321 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 322 |
-
"version_major": 2,
|
| 323 |
-
"version_minor": 0,
|
| 324 |
-
"model_id": "318e1f3e9c4f4c5992a1507cd9de6d9a"
|
| 325 |
-
}
|
| 326 |
-
},
|
| 327 |
-
"metadata": {}
|
| 328 |
-
},
|
| 329 |
-
{
|
| 330 |
-
"output_type": "stream",
|
| 331 |
-
"name": "stdout",
|
| 332 |
-
"text": [
|
| 333 |
-
"trainable params: 80,740,352 || all params: 7,696,356,864 || trainable%: 1.0491\n"
|
| 334 |
-
]
|
| 335 |
-
}
|
| 336 |
-
],
|
| 337 |
-
"source": [
|
| 338 |
-
"# Cell 2: Load model (standard HF stack — no Unsloth bug)\n",
|
| 339 |
-
"import torch\n",
|
| 340 |
-
"from transformers import AutoModelForCausalLM, AutoTokenizer\n",
|
| 341 |
-
"from peft import LoraConfig, get_peft_model\n",
|
| 342 |
-
"\n",
|
| 343 |
-
"model_name = \"Qwen/Qwen2.5-7B-Instruct\"\n",
|
| 344 |
-
"\n",
|
| 345 |
-
"tokenizer = AutoTokenizer.from_pretrained(model_name)\n",
|
| 346 |
-
"if tokenizer.pad_token is None:\n",
|
| 347 |
-
" tokenizer.pad_token = tokenizer.eos_token\n",
|
| 348 |
-
"\n",
|
| 349 |
-
"model = AutoModelForCausalLM.from_pretrained(\n",
|
| 350 |
-
" model_name,\n",
|
| 351 |
-
" torch_dtype=torch.bfloat16,\n",
|
| 352 |
-
" device_map=\"auto\",\n",
|
| 353 |
-
")\n",
|
| 354 |
-
"\n",
|
| 355 |
-
"lora_config = LoraConfig(\n",
|
| 356 |
-
" r=32,\n",
|
| 357 |
-
" lora_alpha=32,\n",
|
| 358 |
-
" lora_dropout=0,\n",
|
| 359 |
-
" target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",\n",
|
| 360 |
-
" \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
|
| 361 |
-
" task_type=\"CAUSAL_LM\",\n",
|
| 362 |
-
")\n",
|
| 363 |
-
"\n",
|
| 364 |
-
"model = get_peft_model(model, lora_config)\n",
|
| 365 |
-
"model.print_trainable_parameters()\n"
|
| 366 |
-
]
|
| 367 |
},
|
| 368 |
{
|
| 369 |
"cell_type": "code",
|
| 370 |
-
"execution_count":
|
| 371 |
"metadata": {
|
| 372 |
"colab": {
|
| 373 |
"base_uri": "https://localhost:8080/"
|
|
@@ -375,68 +156,12 @@
|
|
| 375 |
"id": "D3HDc8_3FG8L",
|
| 376 |
"outputId": "b28882c2-1497-4d3b-98b8-4b4cb03cbb0b"
|
| 377 |
},
|
| 378 |
-
"outputs": [
|
| 379 |
-
{
|
| 380 |
-
"output_type": "stream",
|
| 381 |
-
"name": "stdout",
|
| 382 |
-
"text": [
|
| 383 |
-
"Connected! Piece: L\n",
|
| 384 |
-
"Step OK. Reward: -7, Done: False\n",
|
| 385 |
-
"Environment connection verified!\n"
|
| 386 |
-
]
|
| 387 |
-
}
|
| 388 |
-
],
|
| 389 |
-
"source": [
|
| 390 |
-
"# Cell 3: Define Tetris client (standalone, no local package needed)\n",
|
| 391 |
-
"import json\n",
|
| 392 |
-
"import websockets.sync.client as ws_client\n",
|
| 393 |
-
"\n",
|
| 394 |
-
"TETRIS_URL = \"wss://vortexedsquirrel-tetris-env.hf.space/ws\"\n",
|
| 395 |
-
"\n",
|
| 396 |
-
"class TetrisClient:\n",
|
| 397 |
-
" \"\"\"Lightweight Tetris env client for Colab.\"\"\"\n",
|
| 398 |
-
" def __init__(self, url=TETRIS_URL):\n",
|
| 399 |
-
" self.url = url\n",
|
| 400 |
-
" self.ws = None\n",
|
| 401 |
-
"\n",
|
| 402 |
-
" def connect(self):\n",
|
| 403 |
-
" self.ws = ws_client.connect(self.url, open_timeout=30)\n",
|
| 404 |
-
" return self\n",
|
| 405 |
-
"\n",
|
| 406 |
-
" def _send_recv(self, msg):\n",
|
| 407 |
-
" self.ws.send(json.dumps(msg))\n",
|
| 408 |
-
" return json.loads(self.ws.recv(timeout=30))\n",
|
| 409 |
-
"\n",
|
| 410 |
-
" def reset(self, seed=None):\n",
|
| 411 |
-
" data = {\"seed\": seed} if seed else {}\n",
|
| 412 |
-
" resp = self._send_recv({\"type\": \"reset\", \"data\": data})\n",
|
| 413 |
-
" d = resp[\"data\"]\n",
|
| 414 |
-
" obs = d.get(\"observation\", d)\n",
|
| 415 |
-
" return {\"observation\": obs, \"reward\": d.get(\"reward\", 0), \"done\": d.get(\"done\", False)}\n",
|
| 416 |
-
"\n",
|
| 417 |
-
" def step(self, action_str):\n",
|
| 418 |
-
" resp = self._send_recv({\"type\": \"step\", \"data\": {\"action\": action_str, \"metadata\": {}}})\n",
|
| 419 |
-
" d = resp[\"data\"]\n",
|
| 420 |
-
" obs = d.get(\"observation\", d)\n",
|
| 421 |
-
" return {\"observation\": obs, \"reward\": d.get(\"reward\", 0), \"done\": d.get(\"done\", False)}\n",
|
| 422 |
-
"\n",
|
| 423 |
-
" def close(self):\n",
|
| 424 |
-
" if self.ws:\n",
|
| 425 |
-
" self.ws.close()\n",
|
| 426 |
-
"\n",
|
| 427 |
-
"# Quick test\n",
|
| 428 |
-
"client = TetrisClient().connect()\n",
|
| 429 |
-
"r = client.reset(seed=42)\n",
|
| 430 |
-
"print(f\"Connected! Piece: {r['observation']['current_piece']}\")\n",
|
| 431 |
-
"r = client.step(\"drop\")\n",
|
| 432 |
-
"print(f\"Step OK. Reward: {r['reward']}, Done: {r['done']}\")\n",
|
| 433 |
-
"client.close()\n",
|
| 434 |
-
"print(\"Environment connection verified!\")"
|
| 435 |
-
]
|
| 436 |
},
|
| 437 |
{
|
| 438 |
"cell_type": "code",
|
| 439 |
-
"execution_count":
|
| 440 |
"metadata": {
|
| 441 |
"colab": {
|
| 442 |
"base_uri": "https://localhost:8080/"
|
|
@@ -444,66 +169,12 @@
|
|
| 444 |
"id": "3O3yawZBFG8M",
|
| 445 |
"outputId": "7d8ed74b-4c46-4857-c1b7-9bb99848a93f"
|
| 446 |
},
|
| 447 |
-
"outputs": [
|
| 448 |
-
{
|
| 449 |
-
"output_type": "stream",
|
| 450 |
-
"name": "stdout",
|
| 451 |
-
"text": [
|
| 452 |
-
"Generated 400 prompts\n"
|
| 453 |
-
]
|
| 454 |
-
}
|
| 455 |
-
],
|
| 456 |
-
"source": [
|
| 457 |
-
"# Cell 4: Generate training prompts — single-char actions, 100 fixed\n",
|
| 458 |
-
"import random\n",
|
| 459 |
-
"\n",
|
| 460 |
-
"!wget -q -O game_engine.py https://raw.githubusercontent.com/OutOfMystic/tetris-openenv/main/src/tetris_env/server/game_engine.py\n",
|
| 461 |
-
"\n",
|
| 462 |
-
"from game_engine import TetrisEnv\n",
|
| 463 |
-
"\n",
|
| 464 |
-
"# Action encoding: 1 char = 1 action\n",
|
| 465 |
-
"ACTION_MAP = {'L': 'left', 'R': 'right', 'C': 'rotate_cw', 'W': 'rotate_ccw', 'D': 'drop', 'S': 'down'}\n",
|
| 466 |
-
"ACTION_CHARS = list(ACTION_MAP.keys())\n",
|
| 467 |
-
"\n",
|
| 468 |
-
"SYSTEM_PROMPT = \"\"\"You are a Tetris AI. Output exactly 100 actions as single letters separated by spaces.\n",
|
| 469 |
-
"L=left R=right C=rotate_cw W=rotate_ccw D=drop S=down\n",
|
| 470 |
-
"Example: L L C D R R D L D S D L C D R D ...\n",
|
| 471 |
-
"Strategy: fill complete rows. Multiple lines at once = bonus.\n",
|
| 472 |
-
"Output ONLY the 100 letters, nothing else.\"\"\"\n",
|
| 473 |
-
"\n",
|
| 474 |
-
"def make_prompt(result):\n",
|
| 475 |
-
" return f\"\"\"Board:\n",
|
| 476 |
-
"{result['board']}\n",
|
| 477 |
-
"\n",
|
| 478 |
-
"Piece: {result['current_piece']} Next: {result['next_piece']}\n",
|
| 479 |
-
"Score: {result['score']} Lines: {result['total_lines']} Height: {result['max_height']} Holes: {result['holes']}\n",
|
| 480 |
-
"\n",
|
| 481 |
-
"Your 100 actions:\"\"\"\n",
|
| 482 |
-
"\n",
|
| 483 |
-
"def generate_training_prompts(n_prompts=400):\n",
|
| 484 |
-
" prompts = []\n",
|
| 485 |
-
" for i in range(n_prompts):\n",
|
| 486 |
-
" env = TetrisEnv(seed=i)\n",
|
| 487 |
-
" result = env.reset(seed=i)\n",
|
| 488 |
-
" rng = random.Random(i)\n",
|
| 489 |
-
" moves = rng.randint(0, 4)\n",
|
| 490 |
-
" direction = rng.choice([\"left\", \"right\"])\n",
|
| 491 |
-
" for _ in range(moves):\n",
|
| 492 |
-
" env.step(direction)\n",
|
| 493 |
-
" result = env._make_result(0)\n",
|
| 494 |
-
" prompts.append({\n",
|
| 495 |
-
" \"prompt\": [{\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n",
|
| 496 |
-
" {\"role\": \"user\", \"content\": make_prompt(result)}],\n",
|
| 497 |
-
" })\n",
|
| 498 |
-
" print(f\"Generated {len(prompts)} prompts\")\n",
|
| 499 |
-
" return prompts\n",
|
| 500 |
-
"\n",
|
| 501 |
-
"train_prompts = generate_training_prompts(400)\n"
|
| 502 |
-
]
|
| 503 |
},
|
| 504 |
{
|
| 505 |
"cell_type": "code",
|
| 506 |
-
"execution_count":
|
| 507 |
"metadata": {
|
| 508 |
"colab": {
|
| 509 |
"base_uri": "https://localhost:8080/"
|
|
@@ -511,61 +182,19 @@
|
|
| 511 |
"id": "JdcKswz5FG8M",
|
| 512 |
"outputId": "b0533b78-b10a-4d09-bea1-1b18d8a58c54"
|
| 513 |
},
|
| 514 |
-
"outputs": [
|
| 515 |
-
{
|
| 516 |
-
"output_type": "stream",
|
| 517 |
-
"name": "stdout",
|
| 518 |
-
"text": [
|
| 519 |
-
"Dataset size: 400\n",
|
| 520 |
-
"Example prompt (user message):\n",
|
| 521 |
-
"Board:\n",
|
| 522 |
-
"+----------+\n",
|
| 523 |
-
"|..........|\n",
|
| 524 |
-
"|..........|\n",
|
| 525 |
-
"|..........|\n",
|
| 526 |
-
"|........@.|\n",
|
| 527 |
-
"|........@.|\n",
|
| 528 |
-
"|.......@@.|\n",
|
| 529 |
-
"|..........|\n",
|
| 530 |
-
"|..........|\n",
|
| 531 |
-
"|..........|\n",
|
| 532 |
-
"|..........|\n",
|
| 533 |
-
"|..........|\n",
|
| 534 |
-
"|..........|\n",
|
| 535 |
-
"|..........|\n",
|
| 536 |
-
"|..........|\n",
|
| 537 |
-
"|..........|\n",
|
| 538 |
-
"|..........|\n",
|
| 539 |
-
"|..........|\n",
|
| 540 |
-
"|..........|\n",
|
| 541 |
-
"|..........|\n",
|
| 542 |
-
"|..........|\n",
|
| 543 |
-
"+----------+\n",
|
| 544 |
-
"\n",
|
| 545 |
-
"Piece:\n"
|
| 546 |
-
]
|
| 547 |
-
}
|
| 548 |
-
],
|
| 549 |
-
"source": [
|
| 550 |
-
"# Cell 5: Create HF Dataset from prompts\n",
|
| 551 |
-
"from datasets import Dataset\n",
|
| 552 |
-
"\n",
|
| 553 |
-
"dataset = Dataset.from_list(train_prompts)\n",
|
| 554 |
-
"print(f\"Dataset size: {len(dataset)}\")\n",
|
| 555 |
-
"print(f\"Example prompt (user message):\")\n",
|
| 556 |
-
"print(dataset[0]['prompt'][1]['content'][:300])"
|
| 557 |
-
]
|
| 558 |
},
|
| 559 |
{
|
| 560 |
"cell_type": "code",
|
| 561 |
-
"source": "# Cell
|
| 562 |
"metadata": {},
|
| 563 |
"execution_count": null,
|
| 564 |
"outputs": []
|
| 565 |
},
|
| 566 |
{
|
| 567 |
"cell_type": "code",
|
| 568 |
-
"execution_count":
|
| 569 |
"metadata": {
|
| 570 |
"colab": {
|
| 571 |
"base_uri": "https://localhost:8080/"
|
|
@@ -573,70 +202,8 @@
|
|
| 573 |
"id": "1SRpxwy-FG8M",
|
| 574 |
"outputId": "30c168c0-faf0-4c26-fd74-62d3ed3ab13b"
|
| 575 |
},
|
| 576 |
-
"outputs": [
|
| 577 |
-
|
| 578 |
-
"output_type": "stream",
|
| 579 |
-
"name": "stdout",
|
| 580 |
-
"text": [
|
| 581 |
-
"Test rewards: [-157.5, -138.5, -94.0]\n"
|
| 582 |
-
]
|
| 583 |
-
}
|
| 584 |
-
],
|
| 585 |
-
"source": [
|
| 586 |
-
"# Cell 6: Reward function — parse single chars, exactly 100 actions\n",
|
| 587 |
-
"\n",
|
| 588 |
-
"NUM_ACTIONS = 100\n",
|
| 589 |
-
"\n",
|
| 590 |
-
"def tetris_reward_func(completions, **kwargs):\n",
|
| 591 |
-
" rewards = []\n",
|
| 592 |
-
" for i, completion in enumerate(completions):\n",
|
| 593 |
-
" text = completion[0]['content'].strip().upper() if isinstance(completion, list) else str(completion).strip().upper()\n",
|
| 594 |
-
"\n",
|
| 595 |
-
" # Parse single-char actions\n",
|
| 596 |
-
" chars = [ch for ch in text.split() if ch in ACTION_MAP]\n",
|
| 597 |
-
" # Also try without spaces (e.g. \"LLCDRRД\")\n",
|
| 598 |
-
" if len(chars) < 5:\n",
|
| 599 |
-
" chars = [ch for ch in text if ch in ACTION_MAP]\n",
|
| 600 |
-
"\n",
|
| 601 |
-
" # Pad to 100 or truncate to 100\n",
|
| 602 |
-
" if len(chars) < NUM_ACTIONS:\n",
|
| 603 |
-
" chars += ['D'] * (NUM_ACTIONS - len(chars)) # pad with drop\n",
|
| 604 |
-
" chars = chars[:NUM_ACTIONS]\n",
|
| 605 |
-
"\n",
|
| 606 |
-
" # Play on engine\n",
|
| 607 |
-
" env = TetrisEnv(seed=i)\n",
|
| 608 |
-
" env.reset(seed=i)\n",
|
| 609 |
-
"\n",
|
| 610 |
-
" total_lines = 0\n",
|
| 611 |
-
" steps_played = 0\n",
|
| 612 |
-
" game_over = False\n",
|
| 613 |
-
"\n",
|
| 614 |
-
" for ch in chars:\n",
|
| 615 |
-
" result = env.step(ACTION_MAP[ch])\n",
|
| 616 |
-
" steps_played += 1\n",
|
| 617 |
-
" total_lines = result['total_lines']\n",
|
| 618 |
-
" if result['done']:\n",
|
| 619 |
-
" game_over = True\n",
|
| 620 |
-
" break\n",
|
| 621 |
-
"\n",
|
| 622 |
-
" reward = 0.0\n",
|
| 623 |
-
" reward += total_lines * 200.0\n",
|
| 624 |
-
" reward += steps_played * 0.5\n",
|
| 625 |
-
" reward -= result['holes'] * 2.0\n",
|
| 626 |
-
" reward -= result['max_height'] * 1.0\n",
|
| 627 |
-
" if game_over:\n",
|
| 628 |
-
" reward -= 50.0\n",
|
| 629 |
-
"\n",
|
| 630 |
-
" rewards.append(reward)\n",
|
| 631 |
-
"\n",
|
| 632 |
-
" return rewards\n",
|
| 633 |
-
"\n",
|
| 634 |
-
"# Test\n",
|
| 635 |
-
"test = [[{\"content\": \"L L C D R R D L D S D \" * 6}],\n",
|
| 636 |
-
" [{\"content\": \"D D D D D D D D D D \" * 6}],\n",
|
| 637 |
-
" [{\"content\": \"garbage text no actions\"}]]\n",
|
| 638 |
-
"print(f\"Test rewards: {tetris_reward_func(test)}\")\n"
|
| 639 |
-
]
|
| 640 |
},
|
| 641 |
{
|
| 642 |
"cell_type": "code",
|
|
@@ -649,350 +216,15 @@
|
|
| 649 |
"id": "UKvvm6gPFG8M",
|
| 650 |
"outputId": "413432d5-f511-40d5-eb43-0874012e593d"
|
| 651 |
},
|
| 652 |
-
"outputs": [
|
| 653 |
-
{
|
| 654 |
-
"output_type": "stream",
|
| 655 |
-
"name": "stderr",
|
| 656 |
-
"text": [
|
| 657 |
-
"The tokenizer has new PAD/BOS/EOS tokens that differ from the model config and generation config. The model config and generation config were aligned accordingly, being updated with the tokenizer's values. Updated tokens: {'bos_token_id': None, 'pad_token_id': 151643}.\n"
|
| 658 |
-
]
|
| 659 |
-
},
|
| 660 |
-
{
|
| 661 |
-
"output_type": "stream",
|
| 662 |
-
"name": "stdout",
|
| 663 |
-
"text": [
|
| 664 |
-
"Starting GRPO training...\n"
|
| 665 |
-
]
|
| 666 |
-
},
|
| 667 |
-
{
|
| 668 |
-
"output_type": "stream",
|
| 669 |
-
"name": "stderr",
|
| 670 |
-
"text": [
|
| 671 |
-
"Passing `generation_config` together with generation-related arguments=({'disable_compile'}) is deprecated and will be removed in future versions. Please pass either a `generation_config` object OR all generation parameters explicitly, but not both.\n"
|
| 672 |
-
]
|
| 673 |
-
},
|
| 674 |
-
{
|
| 675 |
-
"output_type": "display_data",
|
| 676 |
-
"data": {
|
| 677 |
-
"text/plain": [
|
| 678 |
-
"<IPython.core.display.HTML object>"
|
| 679 |
-
],
|
| 680 |
-
"text/html": [
|
| 681 |
-
"\n",
|
| 682 |
-
" <div>\n",
|
| 683 |
-
" \n",
|
| 684 |
-
" <progress value='404' max='1200' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
|
| 685 |
-
" [ 404/1200 1:56:25 < 3:50:31, 0.06 it/s, Epoch 1.01/3]\n",
|
| 686 |
-
" </div>\n",
|
| 687 |
-
" <table border=\"1\" class=\"dataframe\">\n",
|
| 688 |
-
" <thead>\n",
|
| 689 |
-
" <tr style=\"text-align: left;\">\n",
|
| 690 |
-
" <th>Step</th>\n",
|
| 691 |
-
" <th>Training Loss</th>\n",
|
| 692 |
-
" </tr>\n",
|
| 693 |
-
" </thead>\n",
|
| 694 |
-
" <tbody>\n",
|
| 695 |
-
" <tr>\n",
|
| 696 |
-
" <td>10</td>\n",
|
| 697 |
-
" <td>-0.034254</td>\n",
|
| 698 |
-
" </tr>\n",
|
| 699 |
-
" <tr>\n",
|
| 700 |
-
" <td>20</td>\n",
|
| 701 |
-
" <td>-0.039996</td>\n",
|
| 702 |
-
" </tr>\n",
|
| 703 |
-
" <tr>\n",
|
| 704 |
-
" <td>30</td>\n",
|
| 705 |
-
" <td>-0.005777</td>\n",
|
| 706 |
-
" </tr>\n",
|
| 707 |
-
" <tr>\n",
|
| 708 |
-
" <td>40</td>\n",
|
| 709 |
-
" <td>-0.003115</td>\n",
|
| 710 |
-
" </tr>\n",
|
| 711 |
-
" <tr>\n",
|
| 712 |
-
" <td>50</td>\n",
|
| 713 |
-
" <td>-0.006602</td>\n",
|
| 714 |
-
" </tr>\n",
|
| 715 |
-
" <tr>\n",
|
| 716 |
-
" <td>60</td>\n",
|
| 717 |
-
" <td>-0.034982</td>\n",
|
| 718 |
-
" </tr>\n",
|
| 719 |
-
" <tr>\n",
|
| 720 |
-
" <td>70</td>\n",
|
| 721 |
-
" <td>-0.017585</td>\n",
|
| 722 |
-
" </tr>\n",
|
| 723 |
-
" <tr>\n",
|
| 724 |
-
" <td>80</td>\n",
|
| 725 |
-
" <td>-0.069048</td>\n",
|
| 726 |
-
" </tr>\n",
|
| 727 |
-
" <tr>\n",
|
| 728 |
-
" <td>90</td>\n",
|
| 729 |
-
" <td>-0.050287</td>\n",
|
| 730 |
-
" </tr>\n",
|
| 731 |
-
" <tr>\n",
|
| 732 |
-
" <td>100</td>\n",
|
| 733 |
-
" <td>0.014232</td>\n",
|
| 734 |
-
" </tr>\n",
|
| 735 |
-
" <tr>\n",
|
| 736 |
-
" <td>110</td>\n",
|
| 737 |
-
" <td>0.000296</td>\n",
|
| 738 |
-
" </tr>\n",
|
| 739 |
-
" <tr>\n",
|
| 740 |
-
" <td>120</td>\n",
|
| 741 |
-
" <td>-0.000809</td>\n",
|
| 742 |
-
" </tr>\n",
|
| 743 |
-
" <tr>\n",
|
| 744 |
-
" <td>130</td>\n",
|
| 745 |
-
" <td>0.006139</td>\n",
|
| 746 |
-
" </tr>\n",
|
| 747 |
-
" <tr>\n",
|
| 748 |
-
" <td>140</td>\n",
|
| 749 |
-
" <td>0.002477</td>\n",
|
| 750 |
-
" </tr>\n",
|
| 751 |
-
" <tr>\n",
|
| 752 |
-
" <td>150</td>\n",
|
| 753 |
-
" <td>-0.012867</td>\n",
|
| 754 |
-
" </tr>\n",
|
| 755 |
-
" <tr>\n",
|
| 756 |
-
" <td>160</td>\n",
|
| 757 |
-
" <td>0.003067</td>\n",
|
| 758 |
-
" </tr>\n",
|
| 759 |
-
" <tr>\n",
|
| 760 |
-
" <td>170</td>\n",
|
| 761 |
-
" <td>-0.006496</td>\n",
|
| 762 |
-
" </tr>\n",
|
| 763 |
-
" <tr>\n",
|
| 764 |
-
" <td>180</td>\n",
|
| 765 |
-
" <td>0.006063</td>\n",
|
| 766 |
-
" </tr>\n",
|
| 767 |
-
" <tr>\n",
|
| 768 |
-
" <td>190</td>\n",
|
| 769 |
-
" <td>0.003406</td>\n",
|
| 770 |
-
" </tr>\n",
|
| 771 |
-
" <tr>\n",
|
| 772 |
-
" <td>200</td>\n",
|
| 773 |
-
" <td>-0.022040</td>\n",
|
| 774 |
-
" </tr>\n",
|
| 775 |
-
" <tr>\n",
|
| 776 |
-
" <td>210</td>\n",
|
| 777 |
-
" <td>-0.025758</td>\n",
|
| 778 |
-
" </tr>\n",
|
| 779 |
-
" <tr>\n",
|
| 780 |
-
" <td>220</td>\n",
|
| 781 |
-
" <td>0.000000</td>\n",
|
| 782 |
-
" </tr>\n",
|
| 783 |
-
" <tr>\n",
|
| 784 |
-
" <td>230</td>\n",
|
| 785 |
-
" <td>-0.012708</td>\n",
|
| 786 |
-
" </tr>\n",
|
| 787 |
-
" <tr>\n",
|
| 788 |
-
" <td>240</td>\n",
|
| 789 |
-
" <td>-0.000000</td>\n",
|
| 790 |
-
" </tr>\n",
|
| 791 |
-
" <tr>\n",
|
| 792 |
-
" <td>250</td>\n",
|
| 793 |
-
" <td>0.001564</td>\n",
|
| 794 |
-
" </tr>\n",
|
| 795 |
-
" <tr>\n",
|
| 796 |
-
" <td>260</td>\n",
|
| 797 |
-
" <td>-0.000000</td>\n",
|
| 798 |
-
" </tr>\n",
|
| 799 |
-
" <tr>\n",
|
| 800 |
-
" <td>270</td>\n",
|
| 801 |
-
" <td>0.000000</td>\n",
|
| 802 |
-
" </tr>\n",
|
| 803 |
-
" <tr>\n",
|
| 804 |
-
" <td>280</td>\n",
|
| 805 |
-
" <td>0.000000</td>\n",
|
| 806 |
-
" </tr>\n",
|
| 807 |
-
" <tr>\n",
|
| 808 |
-
" <td>290</td>\n",
|
| 809 |
-
" <td>0.000000</td>\n",
|
| 810 |
-
" </tr>\n",
|
| 811 |
-
" <tr>\n",
|
| 812 |
-
" <td>300</td>\n",
|
| 813 |
-
" <td>-0.000000</td>\n",
|
| 814 |
-
" </tr>\n",
|
| 815 |
-
" <tr>\n",
|
| 816 |
-
" <td>310</td>\n",
|
| 817 |
-
" <td>-0.000000</td>\n",
|
| 818 |
-
" </tr>\n",
|
| 819 |
-
" <tr>\n",
|
| 820 |
-
" <td>320</td>\n",
|
| 821 |
-
" <td>0.000000</td>\n",
|
| 822 |
-
" </tr>\n",
|
| 823 |
-
" <tr>\n",
|
| 824 |
-
" <td>330</td>\n",
|
| 825 |
-
" <td>-0.001915</td>\n",
|
| 826 |
-
" </tr>\n",
|
| 827 |
-
" <tr>\n",
|
| 828 |
-
" <td>340</td>\n",
|
| 829 |
-
" <td>-0.001833</td>\n",
|
| 830 |
-
" </tr>\n",
|
| 831 |
-
" <tr>\n",
|
| 832 |
-
" <td>350</td>\n",
|
| 833 |
-
" <td>0.000000</td>\n",
|
| 834 |
-
" </tr>\n",
|
| 835 |
-
" <tr>\n",
|
| 836 |
-
" <td>360</td>\n",
|
| 837 |
-
" <td>0.004288</td>\n",
|
| 838 |
-
" </tr>\n",
|
| 839 |
-
" <tr>\n",
|
| 840 |
-
" <td>370</td>\n",
|
| 841 |
-
" <td>-0.000000</td>\n",
|
| 842 |
-
" </tr>\n",
|
| 843 |
-
" <tr>\n",
|
| 844 |
-
" <td>380</td>\n",
|
| 845 |
-
" <td>0.003926</td>\n",
|
| 846 |
-
" </tr>\n",
|
| 847 |
-
" <tr>\n",
|
| 848 |
-
" <td>390</td>\n",
|
| 849 |
-
" <td>0.000000</td>\n",
|
| 850 |
-
" </tr>\n",
|
| 851 |
-
" <tr>\n",
|
| 852 |
-
" <td>400</td>\n",
|
| 853 |
-
" <td>0.000000</td>\n",
|
| 854 |
-
" </tr>\n",
|
| 855 |
-
" </tbody>\n",
|
| 856 |
-
"</table><p>"
|
| 857 |
-
]
|
| 858 |
-
},
|
| 859 |
-
"metadata": {}
|
| 860 |
-
},
|
| 861 |
-
{
|
| 862 |
-
"output_type": "stream",
|
| 863 |
-
"name": "stdout",
|
| 864 |
-
"text": [
|
| 865 |
-
"[Step 10] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.725, completions/max_length=220.000, completions/max_terminated_length=150.300, completions/mean_length=192.387, completions/mean_terminated_length=118.600, completions/min_length=88.300, completions/min_terminated_length=88.300, entropy=0.050, frac_reward_zero_std=0.000, grad_norm=0.105, num_tokens=31799.000, reward=-92.294, reward_std=55.018, rewards/tetris_reward_func/mean=-92.294, rewards/tetris_reward_func/std=55.018, step_time=17.325\n",
|
| 866 |
-
"[Step 20] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.762, completions/max_length=220.000, completions/max_terminated_length=109.700, completions/mean_length=194.350, completions/mean_terminated_length=90.833, completions/min_length=115.200, completions/min_terminated_length=71.200, entropy=0.049, frac_reward_zero_std=0.000, grad_norm=0.154, num_tokens=63787.000, reward=-87.744, reward_std=64.287, rewards/tetris_reward_func/mean=-87.744, rewards/tetris_reward_func/std=64.287, step_time=17.077\n",
|
| 867 |
-
"[Step 30] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.688, completions/max_length=220.000, completions/max_terminated_length=132.100, completions/mean_length=185.338, completions/mean_terminated_length=113.600, completions/min_length=96.500, completions/min_terminated_length=96.500, entropy=0.051, frac_reward_zero_std=0.000, grad_norm=0.127, num_tokens=94966.000, reward=-84.419, reward_std=60.849, rewards/tetris_reward_func/mean=-84.419, rewards/tetris_reward_func/std=60.849, step_time=17.085\n",
|
| 868 |
-
"[Step 40] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.725, completions/max_length=220.000, completions/max_terminated_length=114.200, completions/mean_length=189.113, completions/mean_terminated_length=100.702, completions/min_length=107.200, completions/min_terminated_length=85.200, entropy=0.049, frac_reward_zero_std=0.000, grad_norm=0.059, num_tokens=126439.000, reward=-82.194, reward_std=65.590, rewards/tetris_reward_func/mean=-82.194, rewards/tetris_reward_func/std=65.590, step_time=17.078\n",
|
| 869 |
-
"[Step 50] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.738, completions/max_length=220.000, completions/max_terminated_length=128.500, completions/mean_length=193.863, completions/mean_terminated_length=111.117, completions/min_length=120.200, completions/min_terminated_length=98.200, entropy=0.041, frac_reward_zero_std=0.000, grad_norm=0.061, num_tokens=158364.000, reward=-72.206, reward_std=68.607, rewards/tetris_reward_func/mean=-72.206, rewards/tetris_reward_func/std=68.607, step_time=17.067\n",
|
| 870 |
-
"[Step 60] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.800, completions/max_length=220.000, completions/max_terminated_length=133.100, completions/mean_length=200.500, completions/mean_terminated_length=109.633, completions/min_length=111.000, completions/min_terminated_length=89.000, entropy=0.038, frac_reward_zero_std=0.000, grad_norm=0.059, num_tokens=190708.000, reward=-66.744, reward_std=67.078, rewards/tetris_reward_func/mean=-66.744, rewards/tetris_reward_func/std=67.078, step_time=17.116\n",
|
| 871 |
-
"[Step 70] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.800, completions/max_length=220.000, completions/max_terminated_length=121.900, completions/mean_length=200.662, completions/mean_terminated_length=111.100, completions/min_length=119.600, completions/min_terminated_length=97.600, entropy=0.039, frac_reward_zero_std=0.000, grad_norm=0.107, num_tokens=223145.000, reward=-46.081, reward_std=68.570, rewards/tetris_reward_func/mean=-46.081, rewards/tetris_reward_func/std=68.570, step_time=17.263\n",
|
| 872 |
-
"[Step 80] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.738, completions/max_length=220.000, completions/max_terminated_length=127.900, completions/mean_length=193.125, completions/mean_terminated_length=111.063, completions/min_length=96.800, completions/min_terminated_length=96.800, entropy=0.052, frac_reward_zero_std=0.000, grad_norm=0.153, num_tokens=254939.000, reward=-19.050, reward_std=55.485, rewards/tetris_reward_func/mean=-19.050, rewards/tetris_reward_func/std=55.485, step_time=17.245\n",
|
| 873 |
-
"[Step 90] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.750, completions/max_length=220.000, completions/max_terminated_length=119.900, completions/mean_length=197.775, completions/mean_terminated_length=103.608, completions/min_length=128.900, completions/min_terminated_length=84.900, entropy=0.052, frac_reward_zero_std=0.000, grad_norm=0.117, num_tokens=287121.000, reward=6.112, reward_std=42.999, rewards/tetris_reward_func/mean=6.112, rewards/tetris_reward_func/std=42.999, step_time=17.200\n",
|
| 874 |
-
"[Step 100] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.900, completions/max_length=220.000, completions/max_terminated_length=61.200, completions/mean_length=209.688, completions/mean_terminated_length=59.300, completions/min_length=167.400, completions/min_terminated_length=57.400, entropy=0.042, frac_reward_zero_std=0.000, grad_norm=0.099, num_tokens=320240.000, reward=6.544, reward_std=43.744, rewards/tetris_reward_func/mean=6.544, rewards/tetris_reward_func/std=43.744, step_time=17.200\n",
|
| 875 |
-
"[Step 110] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.988, completions/max_length=220.000, completions/max_terminated_length=16.400, completions/mean_length=219.300, completions/mean_terminated_length=16.400, completions/min_length=214.400, completions/min_terminated_length=16.400, entropy=0.035, frac_reward_zero_std=0.000, grad_norm=0.117, num_tokens=354208.000, reward=14.625, reward_std=26.601, rewards/tetris_reward_func/mean=14.625, rewards/tetris_reward_func/std=26.601, step_time=17.167\n",
|
| 876 |
-
"[Step 120] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.938, completions/max_length=220.000, completions/max_terminated_length=60.600, completions/mean_length=215.675, completions/mean_terminated_length=57.950, completions/min_length=187.300, completions/min_terminated_length=55.300, entropy=0.035, frac_reward_zero_std=0.000, grad_norm=0.119, num_tokens=387822.000, reward=16.738, reward_std=15.117, rewards/tetris_reward_func/mean=16.738, rewards/tetris_reward_func/std=15.117, step_time=17.196\n",
|
| 877 |
-
"[Step 130] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.950, completions/max_length=220.000, completions/max_terminated_length=61.300, completions/mean_length=216.662, completions/mean_terminated_length=61.300, completions/min_length=193.300, completions/min_terminated_length=61.300, entropy=0.035, frac_reward_zero_std=0.000, grad_norm=0.103, num_tokens=421579.000, reward=16.794, reward_std=25.586, rewards/tetris_reward_func/mean=16.794, rewards/tetris_reward_func/std=25.586, step_time=17.231\n",
|
| 878 |
-
"[Step 140] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.925, completions/max_length=220.000, completions/max_terminated_length=82.500, completions/mean_length=215.450, completions/mean_terminated_length=80.400, completions/min_length=188.300, completions/min_terminated_length=78.300, entropy=0.041, frac_reward_zero_std=0.000, grad_norm=0.142, num_tokens=455215.000, reward=24.444, reward_std=27.435, rewards/tetris_reward_func/mean=24.444, rewards/tetris_reward_func/std=27.435, step_time=17.233\n"
|
| 879 |
-
]
|
| 880 |
-
},
|
| 881 |
-
{
|
| 882 |
-
"output_type": "stream",
|
| 883 |
-
"name": "stderr",
|
| 884 |
-
"text": [
|
| 885 |
-
"Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n",
|
| 886 |
-
"WARNING:huggingface_hub.utils._http:Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n"
|
| 887 |
-
]
|
| 888 |
-
},
|
| 889 |
-
{
|
| 890 |
-
"output_type": "stream",
|
| 891 |
-
"name": "stdout",
|
| 892 |
-
"text": [
|
| 893 |
-
"[Step 150] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.963, completions/max_length=220.000, completions/max_terminated_length=23.100, completions/mean_length=215.700, completions/mean_terminated_length=20.800, completions/min_length=194.500, completions/min_terminated_length=18.500, entropy=0.053, frac_reward_zero_std=0.000, grad_norm=0.098, num_tokens=488775.000, reward=26.969, reward_std=42.222, rewards/tetris_reward_func/mean=26.969, rewards/tetris_reward_func/std=42.222, step_time=17.195\n",
|
| 894 |
-
"[Step 160] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.938, completions/max_length=220.000, completions/max_terminated_length=70.000, completions/mean_length=215.000, completions/mean_terminated_length=70.000, completions/min_length=180.000, completions/min_terminated_length=70.000, entropy=0.061, frac_reward_zero_std=0.000, grad_norm=0.141, num_tokens=522343.000, reward=23.725, reward_std=18.791, rewards/tetris_reward_func/mean=23.725, rewards/tetris_reward_func/std=18.791, step_time=17.401\n",
|
| 895 |
-
"[Step 170] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.988, completions/max_length=220.000, completions/max_terminated_length=13.200, completions/mean_length=218.900, completions/mean_terminated_length=13.200, completions/min_length=211.200, completions/min_terminated_length=13.200, entropy=0.083, frac_reward_zero_std=0.000, grad_norm=0.217, num_tokens=556311.000, reward=24.812, reward_std=26.831, rewards/tetris_reward_func/mean=24.812, rewards/tetris_reward_func/std=26.831, step_time=17.467\n",
|
| 896 |
-
"[Step 180] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.988, completions/max_length=220.000, completions/max_terminated_length=14.300, completions/mean_length=219.037, completions/mean_terminated_length=14.300, completions/min_length=212.300, completions/min_terminated_length=14.300, entropy=0.093, frac_reward_zero_std=0.000, grad_norm=0.196, num_tokens=590178.000, reward=27.462, reward_std=19.135, rewards/tetris_reward_func/mean=27.462, rewards/tetris_reward_func/std=19.135, step_time=17.405\n",
|
| 897 |
-
"[Step 190] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.975, completions/max_length=220.000, completions/max_terminated_length=29.900, completions/mean_length=218.238, completions/mean_terminated_length=29.900, completions/min_length=205.900, completions/min_terminated_length=29.900, entropy=0.119, frac_reward_zero_std=0.000, grad_norm=0.220, num_tokens=623981.000, reward=13.000, reward_std=26.462, rewards/tetris_reward_func/mean=13.000, rewards/tetris_reward_func/std=26.462, step_time=17.372\n",
|
| 898 |
-
"[Step 200] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.938, completions/max_length=220.000, completions/max_terminated_length=49.800, completions/mean_length=213.275, completions/mean_terminated_length=47.650, completions/min_length=177.500, completions/min_terminated_length=45.500, entropy=0.186, frac_reward_zero_std=0.000, grad_norm=0.273, num_tokens=657443.000, reward=13.613, reward_std=22.587, rewards/tetris_reward_func/mean=13.613, rewards/tetris_reward_func/std=22.587, step_time=17.294\n",
|
| 899 |
-
"[Step 210] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.863, completions/max_length=220.000, completions/max_terminated_length=98.300, completions/mean_length=208.775, completions/mean_terminated_length=92.817, completions/min_length=154.200, completions/min_terminated_length=88.200, entropy=0.255, frac_reward_zero_std=0.000, grad_norm=0.229, num_tokens=690505.000, reward=18.550, reward_std=19.927, rewards/tetris_reward_func/mean=18.550, rewards/tetris_reward_func/std=19.927, step_time=17.190\n",
|
| 900 |
-
"[Step 220] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.281, frac_reward_zero_std=0.000, grad_norm=0.288, num_tokens=724465.000, reward=19.150, reward_std=20.560, rewards/tetris_reward_func/mean=19.150, rewards/tetris_reward_func/std=20.560, step_time=17.162\n",
|
| 901 |
-
"[Step 230] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.975, completions/max_length=220.000, completions/max_terminated_length=25.300, completions/mean_length=217.662, completions/mean_terminated_length=25.300, completions/min_length=201.300, completions/min_terminated_length=25.300, entropy=0.201, frac_reward_zero_std=0.000, grad_norm=0.329, num_tokens=758206.000, reward=18.900, reward_std=18.913, rewards/tetris_reward_func/mean=18.900, rewards/tetris_reward_func/std=18.913, step_time=17.190\n",
|
| 902 |
-
"[Step 240] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.198, frac_reward_zero_std=0.000, grad_norm=0.214, num_tokens=792254.000, reward=19.625, reward_std=16.034, rewards/tetris_reward_func/mean=19.625, rewards/tetris_reward_func/std=16.034, step_time=17.204\n",
|
| 903 |
-
"[Step 250] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.975, completions/max_length=220.000, completions/max_terminated_length=35.000, completions/mean_length=218.875, completions/mean_terminated_length=35.000, completions/min_length=211.000, completions/min_terminated_length=35.000, entropy=0.192, frac_reward_zero_std=0.000, grad_norm=0.198, num_tokens=826172.000, reward=25.400, reward_std=11.692, rewards/tetris_reward_func/mean=25.400, rewards/tetris_reward_func/std=11.692, step_time=17.102\n",
|
| 904 |
-
"[Step 260] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.173, frac_reward_zero_std=0.000, grad_norm=0.223, num_tokens=860124.000, reward=25.200, reward_std=15.597, rewards/tetris_reward_func/mean=25.200, rewards/tetris_reward_func/std=15.597, step_time=17.231\n",
|
| 905 |
-
"[Step 270] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.221, frac_reward_zero_std=0.000, grad_norm=0.288, num_tokens=894140.000, reward=25.312, reward_std=12.701, rewards/tetris_reward_func/mean=25.312, rewards/tetris_reward_func/std=12.701, step_time=17.220\n",
|
| 906 |
-
"[Step 280] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.243, frac_reward_zero_std=0.000, grad_norm=0.274, num_tokens=928092.000, reward=26.238, reward_std=12.358, rewards/tetris_reward_func/mean=26.238, rewards/tetris_reward_func/std=12.358, step_time=17.183\n",
|
| 907 |
-
"[Step 290] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.151, frac_reward_zero_std=0.000, grad_norm=0.191, num_tokens=962060.000, reward=26.725, reward_std=13.212, rewards/tetris_reward_func/mean=26.725, rewards/tetris_reward_func/std=13.212, step_time=17.353\n",
|
| 908 |
-
"[Step 300] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.135, frac_reward_zero_std=0.000, grad_norm=0.218, num_tokens=996084.000, reward=27.775, reward_std=11.482, rewards/tetris_reward_func/mean=27.775, rewards/tetris_reward_func/std=11.482, step_time=17.287\n",
|
| 909 |
-
"[Step 310] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.134, frac_reward_zero_std=0.000, grad_norm=0.223, num_tokens=1030036.000, reward=25.488, reward_std=12.503, rewards/tetris_reward_func/mean=25.488, rewards/tetris_reward_func/std=12.503, step_time=17.345\n",
|
| 910 |
-
"[Step 320] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.148, frac_reward_zero_std=0.000, grad_norm=0.251, num_tokens=1064028.000, reward=23.150, reward_std=15.532, rewards/tetris_reward_func/mean=23.150, rewards/tetris_reward_func/std=15.532, step_time=17.364\n",
|
| 911 |
-
"[Step 330] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.988, completions/max_length=220.000, completions/max_terminated_length=18.400, completions/mean_length=219.550, completions/mean_terminated_length=18.400, completions/min_length=216.400, completions/min_terminated_length=18.400, entropy=0.179, frac_reward_zero_std=0.000, grad_norm=0.244, num_tokens=1097984.000, reward=24.875, reward_std=14.287, rewards/tetris_reward_func/mean=24.875, rewards/tetris_reward_func/std=14.287, step_time=17.403\n",
|
| 912 |
-
"[Step 340] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.963, completions/max_length=220.000, completions/max_terminated_length=40.900, completions/mean_length=219.150, completions/mean_terminated_length=40.350, completions/min_length=215.800, completions/min_terminated_length=39.800, entropy=0.239, frac_reward_zero_std=0.000, grad_norm=0.319, num_tokens=1131876.000, reward=26.175, reward_std=12.656, rewards/tetris_reward_func/mean=26.175, rewards/tetris_reward_func/std=12.656, step_time=17.309\n",
|
| 913 |
-
"[Step 350] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.257, frac_reward_zero_std=0.000, grad_norm=0.404, num_tokens=1165884.000, reward=24.625, reward_std=12.939, rewards/tetris_reward_func/mean=24.625, rewards/tetris_reward_func/std=12.939, step_time=17.213\n",
|
| 914 |
-
"[Step 360] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.975, completions/max_length=220.000, completions/max_terminated_length=31.200, completions/mean_length=218.400, completions/mean_terminated_length=31.200, completions/min_length=207.200, completions/min_terminated_length=31.200, entropy=0.283, frac_reward_zero_std=0.000, grad_norm=0.297, num_tokens=1199772.000, reward=26.212, reward_std=13.559, rewards/tetris_reward_func/mean=26.212, rewards/tetris_reward_func/std=13.559, step_time=17.201\n",
|
| 915 |
-
"[Step 370] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.232, frac_reward_zero_std=0.000, grad_norm=0.318, num_tokens=1233708.000, reward=28.762, reward_std=9.307, rewards/tetris_reward_func/mean=28.762, rewards/tetris_reward_func/std=9.307, step_time=17.198\n",
|
| 916 |
-
"[Step 380] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=0.988, completions/max_length=220.000, completions/max_terminated_length=16.400, completions/mean_length=219.300, completions/mean_terminated_length=16.400, completions/min_length=214.400, completions/min_terminated_length=16.400, entropy=0.198, frac_reward_zero_std=0.000, grad_norm=0.325, num_tokens=1267652.000, reward=31.312, reward_std=9.031, rewards/tetris_reward_func/mean=31.312, rewards/tetris_reward_func/std=9.031, step_time=17.213\n",
|
| 917 |
-
"[Step 390] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.141, frac_reward_zero_std=0.000, grad_norm=0.231, num_tokens=1301596.000, reward=28.175, reward_std=10.578, rewards/tetris_reward_func/mean=28.175, rewards/tetris_reward_func/std=10.578, step_time=17.176\n",
|
| 918 |
-
"[Step 400] clip_ratio/high_max=0.000, clip_ratio/high_mean=0.000, clip_ratio/low_mean=0.000, clip_ratio/low_min=0.000, clip_ratio/region_mean=0.000, completions/clipped_ratio=1.000, completions/max_length=220.000, completions/max_terminated_length=0.000, completions/mean_length=220.000, completions/mean_terminated_length=0.000, completions/min_length=220.000, completions/min_terminated_length=0.000, entropy=0.122, frac_reward_zero_std=0.000, grad_norm=0.358, num_tokens=1335572.000, reward=28.887, reward_std=10.549, rewards/tetris_reward_func/mean=28.887, rewards/tetris_reward_func/std=10.549, step_time=17.246\n"
|
| 919 |
-
]
|
| 920 |
-
}
|
| 921 |
-
],
|
| 922 |
-
"source": [
|
| 923 |
-
"# Cell 7: Configure and run GRPO training (A100, with reward logging)\n",
|
| 924 |
-
"from trl import GRPOConfig, GRPOTrainer\n",
|
| 925 |
-
"from transformers import TrainerCallback\n",
|
| 926 |
-
"\n",
|
| 927 |
-
"class RewardLogger(TrainerCallback):\n",
|
| 928 |
-
" def on_log(self, args, state, control, logs=None, **kwargs):\n",
|
| 929 |
-
" if not logs:\n",
|
| 930 |
-
" return\n",
|
| 931 |
-
" step = state.global_step\n",
|
| 932 |
-
" parts = []\n",
|
| 933 |
-
" for k in sorted(logs):\n",
|
| 934 |
-
" if k != 'loss' and k != 'learning_rate' and k != 'epoch':\n",
|
| 935 |
-
" parts.append(f\"{k}={logs[k]:.3f}\")\n",
|
| 936 |
-
" if parts:\n",
|
| 937 |
-
" print(f\"[Step {step}] {', '.join(parts)}\")\n",
|
| 938 |
-
"\n",
|
| 939 |
-
"training_args = GRPOConfig(\n",
|
| 940 |
-
" output_dir=\"tetris-agent-grpo\",\n",
|
| 941 |
-
" num_train_epochs=3,\n",
|
| 942 |
-
" per_device_train_batch_size=4,\n",
|
| 943 |
-
" gradient_accumulation_steps=2,\n",
|
| 944 |
-
" num_generations=8,\n",
|
| 945 |
-
" max_completion_length=220,\n",
|
| 946 |
-
" learning_rate=5e-6,\n",
|
| 947 |
-
" logging_steps=10, # log every 10 steps (less spam)\n",
|
| 948 |
-
" save_steps=50,\n",
|
| 949 |
-
" bf16=True,\n",
|
| 950 |
-
" report_to=\"none\",\n",
|
| 951 |
-
")\n",
|
| 952 |
-
"\n",
|
| 953 |
-
"trainer = GRPOTrainer(\n",
|
| 954 |
-
" model=model,\n",
|
| 955 |
-
" processing_class=tokenizer,\n",
|
| 956 |
-
" reward_funcs=[tetris_reward_func],\n",
|
| 957 |
-
" args=training_args,\n",
|
| 958 |
-
" train_dataset=dataset,\n",
|
| 959 |
-
" callbacks=[RewardLogger()],\n",
|
| 960 |
-
")\n",
|
| 961 |
-
"\n",
|
| 962 |
-
"print(\"Starting GRPO training...\")\n",
|
| 963 |
-
"trainer.train()\n",
|
| 964 |
-
"print(\"Training complete!\")\n"
|
| 965 |
-
]
|
| 966 |
},
|
| 967 |
{
|
| 968 |
"cell_type": "code",
|
| 969 |
-
"source": "# Cell
|
| 970 |
"metadata": {},
|
| 971 |
"execution_count": null,
|
| 972 |
"outputs": []
|
| 973 |
-
},
|
| 974 |
-
{
|
| 975 |
-
"cell_type": "code",
|
| 976 |
-
"execution_count": null,
|
| 977 |
-
"metadata": {
|
| 978 |
-
"id": "HUXSqr3nFG8N"
|
| 979 |
-
},
|
| 980 |
-
"outputs": [],
|
| 981 |
-
"source": "# Cell 9: Demo — TRAINED model plays Tetris (after training)\n# Compare with untrained model from Cell 5b!\n\nprint(\"Playing 3 games with TRAINED model and comparing to untrained baseline...\\n\")\n\ntrained_rewards = []\nfor seed in [42, 123, 7]:\n r = play_one_game(model, tokenizer, seed=seed, label=f\"TRAINED model\")\n trained_rewards.append(r)\n print()\n\navg_trained = sum(trained_rewards) / len(trained_rewards)\n\nprint(\"=\" * 60)\nprint(f\"UNTRAINED model reward (seed=42): {untrained_reward:+.1f}\")\nprint(f\"TRAINED model avg reward (3 games): {avg_trained:+.1f}\")\nprint(f\"Improvement: {avg_trained - untrained_reward:+.1f}\")\nprint(\"=\" * 60)\n\nif avg_trained > untrained_reward:\n print(\"Training improved the model! GRPO is working.\")\nelse:\n print(\"Model needs more training epochs or tuning.\")\n"
|
| 982 |
-
},
|
| 983 |
-
{
|
| 984 |
-
"cell_type": "code",
|
| 985 |
-
"execution_count": null,
|
| 986 |
-
"metadata": {
|
| 987 |
-
"id": "BnOW5pcKFG8N"
|
| 988 |
-
},
|
| 989 |
-
"outputs": [],
|
| 990 |
-
"source": [
|
| 991 |
-
"# Cell 10: Push trained model to HF Hub\n",
|
| 992 |
-
"model.push_to_hub(\"VortexedSquirrel/tetris-agent-grpo\")\n",
|
| 993 |
-
"tokenizer.push_to_hub(\"VortexedSquirrel/tetris-agent-grpo\")\n",
|
| 994 |
-
"print(\"Model pushed to https://huggingface.co/VortexedSquirrel/tetris-agent-grpo\")"
|
| 995 |
-
]
|
| 996 |
}
|
| 997 |
],
|
| 998 |
"metadata": {
|
|
|
|
| 5 |
"metadata": {
|
| 6 |
"id": "jYiMCaOSFG8J"
|
| 7 |
},
|
| 8 |
+
"source": "# Tetris OpenEnv — Per-Piece GRPO Training\n\nTrain an LLM to play Tetris: model sees the board before **every piece**, outputs actions until piece locks, learns via GRPO-style policy gradient.\n\n**Environment**: Local Tetris engine (game_engine.py from OpenEnv)\n**Model**: Qwen2.5-3B-Instruct + LoRA (r=16)\n**Training**: Custom REINFORCE/GRPO loop — 8 games/iteration, 100 iterations\n**Runtime**: L4 GPU (Colab)"
|
| 9 |
},
|
| 10 |
{
|
| 11 |
"cell_type": "code",
|
| 12 |
+
"execution_count": null,
|
| 13 |
"metadata": {
|
| 14 |
"colab": {
|
| 15 |
"base_uri": "https://localhost:8080/"
|
|
|
|
| 17 |
"id": "PHNUG6nYFG8L",
|
| 18 |
"outputId": "e913c80a-f0ec-4231-a35c-f01106a333a1"
|
| 19 |
},
|
| 20 |
+
"outputs": [],
|
| 21 |
+
"source": "# Cell 1: Install dependencies\n!pip install peft accelerate -q"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
},
|
| 23 |
{
|
| 24 |
"cell_type": "code",
|
| 25 |
+
"execution_count": null,
|
| 26 |
"metadata": {
|
| 27 |
"colab": {
|
| 28 |
"base_uri": "https://localhost:8080/",
|
|
|
|
| 143 |
"id": "4CEG1_JMFG8L",
|
| 144 |
"outputId": "fe327915-556f-409f-c75c-fc2f74a7e5f6"
|
| 145 |
},
|
| 146 |
+
"outputs": [],
|
| 147 |
+
"source": "# Cell 2: Load Qwen2.5-3B-Instruct + LoRA\nimport torch\nfrom transformers import AutoModelForCausalLM, AutoTokenizer\nfrom peft import LoraConfig, get_peft_model\n\nmodel_name = \"Qwen/Qwen2.5-3B-Instruct\"\n\ntokenizer = AutoTokenizer.from_pretrained(model_name)\nif tokenizer.pad_token is None:\n tokenizer.pad_token = tokenizer.eos_token\n\nmodel = AutoModelForCausalLM.from_pretrained(\n model_name,\n torch_dtype=torch.bfloat16,\n device_map=\"auto\",\n)\n\nlora_config = LoraConfig(\n r=16,\n lora_alpha=16,\n lora_dropout=0,\n target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",\n \"gate_proj\", \"up_proj\", \"down_proj\"],\n task_type=\"CAUSAL_LM\",\n)\n\nmodel = get_peft_model(model, lora_config)\nmodel.print_trainable_parameters()"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
},
|
| 149 |
{
|
| 150 |
"cell_type": "code",
|
| 151 |
+
"execution_count": null,
|
| 152 |
"metadata": {
|
| 153 |
"colab": {
|
| 154 |
"base_uri": "https://localhost:8080/"
|
|
|
|
| 156 |
"id": "D3HDc8_3FG8L",
|
| 157 |
"outputId": "b28882c2-1497-4d3b-98b8-4b4cb03cbb0b"
|
| 158 |
},
|
| 159 |
+
"outputs": [],
|
| 160 |
+
"source": "# Cell 3: Game engine + constants + prompt builder\nimport random\nimport torch.nn.functional as F\n\n# Download game engine from repo\n!wget -q -O game_engine.py https://raw.githubusercontent.com/OutOfMystic/tetris-openenv/main/src/tetris_env/server/game_engine.py\nfrom game_engine import TetrisEnv\n\n# === Constants ===\nMAX_ACTIONS_PER_PIECE = 20 # max tokens model can output per piece\nMAX_STEPS_PER_GAME = 200 # max total actions per game\nGAMES_PER_ITER = 8 # games per training iteration (same seed)\nNUM_ITERATIONS = 100 # total training iterations\nTEMPERATURE = 0.7\n\n# Training reward modifiers (on top of engine per-step rewards)\nLR_PENALTY = -0.1 # per L/R move\nPIECE_PLACED_BONUS = 1.0 # per piece successfully placed\nNO_PLACE_PENALTY = -10.0 # if 20 tokens exhausted without placing\nLINE_CLEAR_BONUS = 100.0 # per line cleared (+100/+200/+300/+400)\n\n# Action mapping\nACTION_CHARS = ['L', 'R', 'C', 'W', 'D', 'S']\nACTION_TO_ENGINE = {\n 'L': 'left', 'R': 'right', 'C': 'rotate_cw',\n 'W': 'rotate_ccw', 'D': 'drop', 'S': 'down'\n}\n\n# Pre-compute token IDs for action characters\nACTION_TOKEN_IDS = []\nfor ch in ACTION_CHARS:\n ids = tokenizer.encode(ch, add_special_tokens=False)\n ACTION_TOKEN_IDS.append(ids[0])\n print(f\" '{ch}' -> token_id {ids[0]}\")\nACTION_TOKEN_IDS = torch.tensor(ACTION_TOKEN_IDS, device=model.device)\n\n# Token ID -> action char lookup\nTOKEN_TO_CHAR = {ACTION_TOKEN_IDS[i].item(): ACTION_CHARS[i] for i in range(6)}\n\nSYSTEM_PROMPT = \"\"\"You are a Tetris AI. You see the board and current piece.\nOutput actions as single letters: L=left R=right C=rotate_cw W=rotate_ccw D=drop S=down\nPlace the piece to fill complete rows. Drop when positioned.\"\"\"\n\ndef build_prompt(result):\n return f\"\"\"Board:\n{result['board']}\n\nPiece: {result['current_piece']} Next: {result['next_piece']}\nScore: {result['score']} Lines: {result['total_lines']} Height: {result['max_height']} Holes: {result['holes']}\n\nYour actions:\"\"\"\n\nprint(\"\\nGame engine loaded. Action tokens mapped.\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
},
|
| 162 |
{
|
| 163 |
"cell_type": "code",
|
| 164 |
+
"execution_count": null,
|
| 165 |
"metadata": {
|
| 166 |
"colab": {
|
| 167 |
"base_uri": "https://localhost:8080/"
|
|
|
|
| 169 |
"id": "3O3yawZBFG8M",
|
| 170 |
"outputId": "7d8ed74b-4c46-4857-c1b7-9bb99848a93f"
|
| 171 |
},
|
| 172 |
+
"outputs": [],
|
| 173 |
+
"source": "# Cell 4: Core training functions — play_one_game + train_one_iteration\n\ndef play_one_game(model, tokenizer, seed, temperature=TEMPERATURE):\n \"\"\"\n Play a full Tetris game. Model sees board before each piece,\n generates up to 20 action tokens per piece via autoregressive sampling.\n Returns reward + piece data for gradient computation.\n \"\"\"\n env = TetrisEnv(seed=seed)\n env.reset(seed=seed)\n\n # Initial random offset (same as old prompt generation)\n rng = random.Random(seed)\n moves = rng.randint(0, 4)\n direction = rng.choice([\"left\", \"right\"])\n for _ in range(moves):\n if env.done:\n break\n env.step(direction)\n\n total_reward = 0.0\n total_steps = 0\n pieces_data = []\n\n while not env.done and total_steps < MAX_STEPS_PER_GAME:\n current_piece_name = env.current_piece_name\n lines_before = env.total_lines\n\n # Build fresh prompt for this piece (no history)\n result = env._make_result(0)\n messages = [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": build_prompt(result)},\n ]\n prompt_ids = tokenizer.apply_chat_template(\n messages, return_tensors=\"pt\", add_generation_prompt=True\n ).to(model.device)\n\n # Autoregressive generation with KV cache\n input_ids = prompt_ids\n action_ids_list = []\n piece_placed = False\n past_kv = None\n\n with torch.no_grad():\n for _ in range(MAX_ACTIONS_PER_PIECE):\n if past_kv is None:\n out = model(input_ids, use_cache=True)\n past_kv = out.past_key_values\n else:\n out = model(input_ids[:, -1:], past_key_values=past_kv, use_cache=True)\n past_kv = out.past_key_values\n\n logits = out.logits[:, -1, :]\n\n # Mask to only 6 action tokens\n masked = torch.full_like(logits, float('-inf'))\n masked[0, ACTION_TOKEN_IDS] = logits[0, ACTION_TOKEN_IDS]\n probs = F.softmax(masked / temperature, dim=-1)\n\n token_id = torch.multinomial(probs, 1).item()\n action_ids_list.append(token_id)\n\n # Execute in engine\n action_char = TOKEN_TO_CHAR[token_id]\n action_name = ACTION_TO_ENGINE[action_char]\n step_result = env.step(action_name)\n\n total_reward += step_result['reward']\n total_steps += 1\n\n if action_name in ('left', 'right'):\n total_reward += LR_PENALTY\n\n # Piece placed? (new piece spawned)\n if env.current_piece_name != current_piece_name:\n piece_placed = True\n total_reward += PIECE_PLACED_BONUS\n lines_cleared = env.total_lines - lines_before\n if lines_cleared > 0:\n total_reward += lines_cleared * LINE_CLEAR_BONUS\n break\n\n if env.done:\n break\n\n # Append token for next autoregressive step\n input_ids = torch.cat([\n input_ids,\n torch.tensor([[token_id]], device=model.device)\n ], dim=-1)\n\n # Force drop if piece not placed in 20 tokens\n if not piece_placed and not env.done:\n env.step('drop')\n total_reward += NO_PLACE_PENALTY\n total_steps += 1\n lines_cleared = env.total_lines - lines_before\n if lines_cleared > 0:\n total_reward += lines_cleared * LINE_CLEAR_BONUS\n\n # Store for gradient computation\n if action_ids_list:\n pieces_data.append({\n 'prompt_ids': prompt_ids.cpu(),\n 'action_ids': torch.tensor(action_ids_list, dtype=torch.long),\n })\n\n return {\n 'reward': total_reward,\n 'pieces': pieces_data,\n 'total_steps': total_steps,\n 'total_lines': env.total_lines,\n 'pieces_placed': len(pieces_data),\n }\n\n\ndef train_one_iteration(model, optimizer, seed, temperature=TEMPERATURE):\n \"\"\"\n One GRPO iteration:\n 1. Rollout: play 8 games (same seed) without grad\n 2. Compute GRPO advantages from game rewards\n 3. Update: recompute log_probs with grad, policy gradient backward\n \"\"\"\n # Phase 1: Rollout\n games = []\n for _ in range(GAMES_PER_ITER):\n game = play_one_game(model, tokenizer, seed, temperature)\n games.append(game)\n\n rewards = torch.tensor([g['reward'] for g in games], dtype=torch.float32)\n mean_r = rewards.mean().item()\n std_r = rewards.std().item()\n\n # Phase 2: Advantages\n if std_r < 1e-8:\n return {'mean_reward': mean_r, 'std_reward': 0.0, 'loss': 0.0,\n 'avg_steps': sum(g['total_steps'] for g in games) / GAMES_PER_ITER,\n 'avg_lines': sum(g['total_lines'] for g in games) / GAMES_PER_ITER,\n 'avg_pieces': sum(g['pieces_placed'] for g in games) / GAMES_PER_ITER}\n\n advantages = ((rewards - rewards.mean()) / (rewards.std() + 1e-8)).tolist()\n\n # Phase 3: Update — recompute log_probs with grad, backward per piece\n optimizer.zero_grad()\n total_pieces = max(1, sum(len(g['pieces']) for g in games))\n total_loss = 0.0\n\n for game_idx, game in enumerate(games):\n adv = advantages[game_idx]\n for piece in game['pieces']:\n prompt = piece['prompt_ids'].to(model.device)\n actions = piece['action_ids'].to(model.device)\n if len(actions) == 0:\n continue\n\n # Teacher-forced forward pass: prompt + action tokens\n full_input = torch.cat([prompt.squeeze(0), actions]).unsqueeze(0)\n logits = model(full_input).logits\n\n # Log_probs at positions where actions were generated\n P = prompt.shape[-1]\n action_logits = logits[0, P-1 : P-1+len(actions), :]\n\n # Mask to 6 action tokens, apply temperature\n masked = torch.full_like(action_logits, float('-inf'))\n masked[:, ACTION_TOKEN_IDS] = action_logits[:, ACTION_TOKEN_IDS]\n log_probs = F.log_softmax(masked / temperature, dim=-1)\n\n selected = log_probs.gather(1, actions.unsqueeze(1)).squeeze(1)\n piece_loss = -(selected.sum() * adv) / total_pieces\n\n piece_loss.backward()\n total_loss += piece_loss.item()\n\n torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n optimizer.step()\n\n return {\n 'mean_reward': mean_r,\n 'std_reward': std_r,\n 'loss': total_loss,\n 'avg_steps': sum(g['total_steps'] for g in games) / GAMES_PER_ITER,\n 'avg_lines': sum(g['total_lines'] for g in games) / GAMES_PER_ITER,\n 'avg_pieces': sum(g['pieces_placed'] for g in games) / GAMES_PER_ITER,\n }\n\n# Smoke test\nprint(\"Smoke test: playing 1 game...\")\ntest_game = play_one_game(model, tokenizer, seed=0)\nprint(f\"Reward: {test_game['reward']:.1f}, Steps: {test_game['total_steps']}, \"\n f\"Pieces: {test_game['pieces_placed']}, Lines: {test_game['total_lines']}\")\nprint(\"Training functions ready.\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
},
|
| 175 |
{
|
| 176 |
"cell_type": "code",
|
| 177 |
+
"execution_count": null,
|
| 178 |
"metadata": {
|
| 179 |
"colab": {
|
| 180 |
"base_uri": "https://localhost:8080/"
|
|
|
|
| 182 |
"id": "JdcKswz5FG8M",
|
| 183 |
"outputId": "b0533b78-b10a-4d09-bea1-1b18d8a58c54"
|
| 184 |
},
|
| 185 |
+
"outputs": [],
|
| 186 |
+
"source": "# Cell 5: Demo — UNTRAINED model plays one game\nprint(\"=== UNTRAINED MODEL ===\\n\")\n\ngame = play_one_game(model, tokenizer, seed=42)\n\nprint(f\"Total steps: {game['total_steps']}/{MAX_STEPS_PER_GAME}\")\nprint(f\"Pieces placed: {game['pieces_placed']}\")\nprint(f\"Lines cleared: {game['total_lines']}\")\nprint(f\"Game reward: {game['reward']:+.1f}\")\n\n# Show final board by replaying\nenv_demo = TetrisEnv(seed=42)\nenv_demo.reset(seed=42)\nrng = random.Random(42)\nm = rng.randint(0, 4)\nd = rng.choice([\"left\", \"right\"])\nfor _ in range(m):\n if not env_demo.done:\n env_demo.step(d)\nfor piece in game['pieces']:\n for tid in piece['action_ids'].tolist():\n if not env_demo.done:\n env_demo.step(ACTION_TO_ENGINE[TOKEN_TO_CHAR[tid]])\n # Force drop for pieces that weren't placed\n if not env_demo.done and env_demo.current_piece_name == env_demo.current_piece_name:\n pass # engine handles lock internally via gravity\n\nprint(f\"\\nFinal board:\")\nprint(env_demo.board_to_text())\n\nuntrained_reward = game['reward']\nprint(f\"\\nUntrained reward: {untrained_reward:+.1f}\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
},
|
| 188 |
{
|
| 189 |
"cell_type": "code",
|
| 190 |
+
"source": "# Cell 6: Training loop — 100 iterations of per-piece GRPO\noptimizer = torch.optim.AdamW(\n [p for p in model.parameters() if p.requires_grad],\n lr=5e-6,\n weight_decay=0.01,\n)\n\nhistory = []\n\nprint(\"Starting per-piece GRPO training...\")\nprint(f\"Config: {GAMES_PER_ITER} games/iter, max {MAX_STEPS_PER_GAME} steps/game, \"\n f\"max {MAX_ACTIONS_PER_PIECE} tokens/piece, {NUM_ITERATIONS} iterations\\n\")\n\nfor iteration in range(NUM_ITERATIONS):\n stats = train_one_iteration(model, optimizer, seed=iteration)\n history.append(stats)\n\n if iteration % 5 == 0 or iteration == NUM_ITERATIONS - 1:\n print(f\"[Iter {iteration:3d}] \"\n f\"reward={stats['mean_reward']:+8.1f} \"\n f\"std={stats['std_reward']:6.1f} \"\n f\"loss={stats['loss']:7.3f} \"\n f\"steps={stats['avg_steps']:5.1f} \"\n f\"lines={stats['avg_lines']:4.1f} \"\n f\"pieces={stats['avg_pieces']:4.1f}\")\n\nprint(\"\\nTraining complete!\")",
|
| 191 |
"metadata": {},
|
| 192 |
"execution_count": null,
|
| 193 |
"outputs": []
|
| 194 |
},
|
| 195 |
{
|
| 196 |
"cell_type": "code",
|
| 197 |
+
"execution_count": null,
|
| 198 |
"metadata": {
|
| 199 |
"colab": {
|
| 200 |
"base_uri": "https://localhost:8080/"
|
|
|
|
| 202 |
"id": "1SRpxwy-FG8M",
|
| 203 |
"outputId": "30c168c0-faf0-4c26-fd74-62d3ed3ab13b"
|
| 204 |
},
|
| 205 |
+
"outputs": [],
|
| 206 |
+
"source": "# Cell 7: Plot reward curve\nimport matplotlib.pyplot as plt\n\nfig, axes = plt.subplots(1, 3, figsize=(18, 5))\n\niters = range(len(history))\n\naxes[0].plot(iters, [h['mean_reward'] for h in history])\naxes[0].set_title('Mean Reward per Iteration')\naxes[0].set_xlabel('Iteration')\naxes[0].set_ylabel('Reward')\n\naxes[1].plot(iters, [h['avg_lines'] for h in history])\naxes[1].set_title('Avg Lines Cleared')\naxes[1].set_xlabel('Iteration')\n\naxes[2].plot(iters, [h['loss'] for h in history])\naxes[2].set_title('Policy Gradient Loss')\naxes[2].set_xlabel('Iteration')\n\nplt.tight_layout()\nplt.savefig('reward_curve.png', dpi=150)\nplt.show()\nprint(\"Reward curve saved to reward_curve.png\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
},
|
| 208 |
{
|
| 209 |
"cell_type": "code",
|
|
|
|
| 216 |
"id": "UKvvm6gPFG8M",
|
| 217 |
"outputId": "413432d5-f511-40d5-eb43-0874012e593d"
|
| 218 |
},
|
| 219 |
+
"outputs": [],
|
| 220 |
+
"source": "# Cell 8: Demo — TRAINED model plays 3 games\nprint(\"=== TRAINED MODEL ===\\n\")\n\ntrained_rewards = []\nfor seed in [42, 123, 7]:\n game = play_one_game(model, tokenizer, seed=seed)\n print(f\"Seed {seed}: reward={game['reward']:+.1f}, \"\n f\"steps={game['total_steps']}, lines={game['total_lines']}, \"\n f\"pieces={game['pieces_placed']}\")\n trained_rewards.append(game['reward'])\n\navg_trained = sum(trained_rewards) / len(trained_rewards)\nprint(f\"\\n{'='*50}\")\nprint(f\"UNTRAINED reward (seed=42): {untrained_reward:+.1f}\")\nprint(f\"TRAINED avg reward (3 games): {avg_trained:+.1f}\")\nprint(f\"Improvement: {avg_trained - untrained_reward:+.1f}\")\nprint('='*50)\n\nif avg_trained > untrained_reward:\n print(\"Training improved the model!\")\nelse:\n print(\"Model needs more training or tuning.\")"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
},
|
| 222 |
{
|
| 223 |
"cell_type": "code",
|
| 224 |
+
"source": "# Cell 9: Push trained model to HF Hub\nmodel.push_to_hub(\"VortexedSquirrel/tetris-agent-grpo\")\ntokenizer.push_to_hub(\"VortexedSquirrel/tetris-agent-grpo\")\nprint(\"Model pushed to https://huggingface.co/VortexedSquirrel/tetris-agent-grpo\")",
|
| 225 |
"metadata": {},
|
| 226 |
"execution_count": null,
|
| 227 |
"outputs": []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
}
|
| 229 |
],
|
| 230 |
"metadata": {
|