iTheresaApocalypse commited on
Commit
3da4c7b
·
verified ·
1 Parent(s): a683690

Default: standard build_system_prompt (reasoning=True) + <think> prefill, any game frame

Browse files
README.md CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  # GameScaling 推理模板
2
 
3
  用 GameScaling checkpoint 玩游戏、或把它当 baseline 所需的全部**推理侧**代码:系统提示词
@@ -6,7 +12,7 @@ OmniGameArena、GameWorld)的接入。不含任何训练代码。
6
 
7
  ```
8
  gamescaling/ 核心包,只依赖 requests + Pillow
9
- prompt.py 系统提示词。默认是 TRAINING_SYSTEM_PROMPT(不写游戏名)
10
  policy.py 多轮客户端:历史窗口、图像编码、请求体
11
  action.py 动作格式与反解(200 ms chunk -> 6 x 33 ms 键鼠)
12
  keymaps.py 训练词表 <-> 浏览器/Playwright 键名
@@ -15,8 +21,8 @@ scripts/serve_vllm.sh 起 OpenAI 兼容服务
15
  tests/test_core.py 离线单测(不需要 GPU)
16
  ```
17
 
18
- 默认推理是一帧游戏画面:`TRAINING_SYSTEM_PROMPT` 里没有游戏名,user 文本固定为
19
- `current game screen`。checkpoint 与起服务目录:
20
 
21
  | 目录 | 规模 |
22
  |---|---|
@@ -48,10 +54,17 @@ bash scripts/serve_vllm.sh /path/to/checkpoint 8000 # 单卡即可跑 0.8B-
48
 
49
  ```python
50
  from PIL import Image
51
- from gamescaling import TRAINING_SYSTEM_PROMPT, GameScalingPolicy, PolicyConfig
52
-
53
- # 默认:系统提示词不写游戏名,输入就是一帧游戏画面。画面按原尺寸送入,不预填 <think>。
54
- policy = GameScalingPolicy(TRAINING_SYSTEM_PROMPT, PolicyConfig())
 
 
 
 
 
 
 
55
  policy.reset() # 每局开始调用
56
  decision = policy.act(Image.open("frame.png")) # 一次调用 = 一个 200 ms 决策
57
  a = decision.action
@@ -64,10 +77,11 @@ a.parsed # False = 无法解析,按 no-op 执行
64
  不重新按下);鼠标位移 `X/1000*屏宽`、`Y/1000*屏高` 在 200 ms 内分摊或一次性下发均可,
65
  三个集成各自的做法见下文。
66
 
67
- 要写上游戏名和规则时,用 `build_system_prompt(游戏名, rules=..., key_bindings=...)`,客户端仍用
68
- `PolicyConfig()`。论文三张表用 `PolicyConfig.preset("omnigamearena" | "vizdoom" | "gameworld")`。
69
- `preset("omnigamearena")` 会把画面缩到 854×480,并预填 `<think>`,此时系统提示词要
70
- `reasoning=True`。
 
71
 
72
  ## 3. 系统提示词规范
73
 
@@ -225,8 +239,8 @@ python run_suite.py --suite <your_suite.yaml> # 批量:suite 里 mode
225
  时加 `mouse_aim=True`;`reasoning` 与 `think_prefill` 一致。
226
  2. 每 200 ms(或暂停时钟下每次决策)截一帧,调用 `policy.act(frame)`。
227
  3. 按 §4 执行 `Action`:6 个 33 ms 子步的按住键 + 鼠标位移 + 滚轮。
228
- 4. 新游戏沿用默认客户端(原尺寸画面、不预填 `<think>`、`TRAINING_SYSTEM_PROMPT` 或
229
- 你自己的 `build_system_prompt`)。要对齐论文某一张表时,再改用
230
  `PolicyConfig.preset("omnigamearena" | "vizdoom" | "gameworld")`。
231
 
232
  ## 7. 与论文评测代码的差异
 
1
+ ---
2
+ tags:
3
+ - gamescaling
4
+ - qwen3_5
5
+ ---
6
+
7
  # GameScaling 推理模板
8
 
9
  用 GameScaling checkpoint 玩游戏、或把它当 baseline 所需的全部**推理侧**代码:系统提示词
 
12
 
13
  ```
14
  gamescaling/ 核心包,只依赖 requests + Pillow
15
+ prompt.py 系统提示词(标准 build_system_prompt + GameWorld 版式)
16
  policy.py 多轮客户端:历史窗口、图像编码、请求体
17
  action.py 动作格式与反解(200 ms chunk -> 6 x 33 ms 键鼠)
18
  keymaps.py 训练词表 <-> 浏览器/Playwright 键名
 
21
  tests/test_core.py 离线单测(不需要 GPU)
22
  ```
23
 
24
+ 默认推理:系统提示词用 `build_system_prompt` 标准格式,每轮 user 是 `current game screen` 加一帧
25
+ 游戏画面,assistant 预填 `<think>`。checkpoint 目录:
26
 
27
  | 目录 | 规模 |
28
  |---|---|
 
54
 
55
  ```python
56
  from PIL import Image
57
+ from gamescaling import GameScalingPolicy, PolicyConfig, build_system_prompt
58
+
59
+ # 任意游戏:标准系统提示词(游戏名 + 规则 + 训练词表键位 + think/action 输出格式)
60
+ system = build_system_prompt(
61
+ "Doom Battle-1",
62
+ rules="Kill as many monsters as possible; collect health and ammo.",
63
+ key_bindings={"W": "Move forward", "S": "Move backward", "A": "Strafe left",
64
+ "D": "Strafe right", "LB": "Fire"},
65
+ mouse_aim=True, # 鼠标控视角的游戏
66
+ ) # reasoning 默认 True,与默认的 <think> 预填一致
67
+ policy = GameScalingPolicy(system, PolicyConfig()) # 预填 <think>,画面按原尺寸送入
68
  policy.reset() # 每局开始调用
69
  decision = policy.act(Image.open("frame.png")) # 一次调用 = 一个 200 ms 决策
70
  a = decision.action
 
77
  不重新按下);鼠标位移 `X/1000*屏宽`、`Y/1000*屏高` 在 200 ms 内分摊或一次性下发均可,
78
  三个集成各自的做法见下文。
79
 
80
+ 默认口径不绑定某个 benchmark:任意游戏画面 + `build_system_prompt` 标准格式 + 预填 `<think>`。
81
+ `TRAINING_SYSTEM_PROMPT` 是预训练的通用提示词,只有 action-only 输出格式,和 `<think>` 预填
82
+ 不匹配,只用于消融。复现论文三张表时再改用
83
+ `PolicyConfig.preset("omnigamearena" | "vizdoom" | "gameworld")`:前两档把画面缩到 854×480;
84
+ `gameworld` 不预填 `<think>`,提示词要用 `build_gameworld_system_prompt(..., reasoning=False)`。
85
 
86
  ## 3. 系统提示词规范
87
 
 
239
  时加 `mouse_aim=True`;`reasoning` 与 `think_prefill` 一致。
240
  2. 每 200 ms(或暂停时钟下每次决策)截一帧,调用 `policy.act(frame)`。
241
  3. 按 §4 执行 `Action`:6 个 33 ms 子步的按住键 + 鼠标位移 + 滚轮。
242
+ 4. 新游戏沿用默认客户端(原尺寸画面、预填 `<think>`、`build_system_prompt` 标准格式)。
243
+ 要对齐论文某一张表时,再改用
244
  `PolicyConfig.preset("omnigamearena" | "vizdoom" | "gameworld")`。
245
 
246
  ## 7. 与论文评测代码的差异
gamescaling/__pycache__/policy.cpython-310.pyc CHANGED
Binary files a/gamescaling/__pycache__/policy.cpython-310.pyc and b/gamescaling/__pycache__/policy.cpython-310.pyc differ
 
gamescaling/__pycache__/prompt.cpython-310.pyc CHANGED
Binary files a/gamescaling/__pycache__/prompt.cpython-310.pyc and b/gamescaling/__pycache__/prompt.cpython-310.pyc differ
 
gamescaling/policy.py CHANGED
@@ -60,10 +60,9 @@ class PolicyConfig:
60
  image_format: str = "jpeg" # jpeg | png
61
  jpeg_quality: int = 90
62
  resample: str = "bilinear"
63
- # Prefill "<think>" so the model reasons freely and closes it itself.
64
- # Off by default: the generic prompt asks for an action string. Turn it on
65
- # only together with a system prompt built with reasoning=True.
66
- think_prefill: bool = False
67
  temperature: float = 1.0
68
  max_tokens: int = 2048
69
  stop: list[str] = field(default_factory=lambda: [ACTION_END])
@@ -76,14 +75,14 @@ class PolicyConfig:
76
  def preset(cls, benchmark: str, **overrides: Any) -> "PolicyConfig":
77
  """Paper-benchmark clients. The default constructor is not one of these.
78
 
79
- Default ``PolicyConfig()`` sends the given game frame unchanged and does
80
- not prefill ``<think>``. Use a preset only when reproducing a paper table.
81
  """
82
  presets = {
83
- "omnigamearena": cls(image_size=(854, 480), think_prefill=True),
84
  # Free-form reasoning without a stop string: a model that quotes the
85
  # format inside <think> would otherwise be cut off mid-thought.
86
- "vizdoom": cls(image_size=(854, 480), think_prefill=True, stop=[]),
87
  "gameworld": cls(
88
  image_size=None, image_height=480, image_format="png", resample="lanczos",
89
  think_prefill=False, max_tokens=4096, stop=[ACTION_END],
 
60
  image_format: str = "jpeg" # jpeg | png
61
  jpeg_quality: int = 90
62
  resample: str = "bilinear"
63
+ # Prefill "<think>" so the model reasons freely and closes it itself. The
64
+ # system prompt must then be built with reasoning=True.
65
+ think_prefill: bool = True
 
66
  temperature: float = 1.0
67
  max_tokens: int = 2048
68
  stop: list[str] = field(default_factory=lambda: [ACTION_END])
 
75
  def preset(cls, benchmark: str, **overrides: Any) -> "PolicyConfig":
76
  """Paper-benchmark clients. The default constructor is not one of these.
77
 
78
+ Default ``PolicyConfig()`` prefills ``<think>`` and sends the given game
79
+ frame unchanged. Use a preset only when reproducing a paper table.
80
  """
81
  presets = {
82
+ "omnigamearena": cls(image_size=(854, 480)),
83
  # Free-form reasoning without a stop string: a model that quotes the
84
  # format inside <think> would otherwise be cut off mid-thought.
85
+ "vizdoom": cls(image_size=(854, 480), stop=[]),
86
  "gameworld": cls(
87
  image_size=None, image_height=480, image_format="png", resample="lanczos",
88
  think_prefill=False, max_tokens=4096, stop=[ACTION_END],
gamescaling/prompt.py CHANGED
@@ -71,8 +71,9 @@ TAIL = (
71
  MOUSE_AIM_LINE = "- Mouse X Y -> aim / rotate the camera (X>0 right, Y>0 down)"
72
  _MOUSE_BUTTON_ALIASES = {"LMB": "LB", "RMB": "RB", "MMB": "MB"}
73
 
74
- # Default inference prompt: no game name, no rules. The user turn is one game
75
- # frame labeled "current game screen". Named games use `build_system_prompt`.
 
76
  TRAINING_SYSTEM_PROMPT = '''You are an experienced PC game player, proficient in keyboard and mouse operations. Based on the current game screen and the corresponding historical trajectory, plan the next 200ms of actions, consisting of 6 steps. Each step is spaced 33ms apart. Every step lasts 33ms from its start time until the next step begins.
77
  **Output Format**
78
  <|action_start|>X Y Z ; k1 k2 k3 ; k4 k5 ; k6 ; k7 ; k8 ; k9 k10<|action_end|>
@@ -102,9 +103,9 @@ def build_system_prompt(
102
  rules: str | None = None,
103
  key_bindings: Iterable[tuple[str, str]] | dict[str, str] | None = None,
104
  mouse_aim: bool = True,
105
- reasoning: bool = False,
106
  ) -> str:
107
- """Key-list layout for a named game (ViZDoom, OmniGameArena, or a new game).
108
 
109
  ``key_bindings`` maps training tokens to what they do, e.g.
110
  ``{"W": "Move forward", "LB": "Fire"}``; ``LMB``/``RMB``/``MMB`` are accepted
 
71
  MOUSE_AIM_LINE = "- Mouse X Y -> aim / rotate the camera (X>0 right, Y>0 down)"
72
  _MOUSE_BUTTON_ALIASES = {"LMB": "LB", "RMB": "RB", "MMB": "MB"}
73
 
74
+ # The generic prompt of the pre-training mixture: no game information at all,
75
+ # action-only Output Format. Kept for ablations; it does not match a <think>
76
+ # prefill. Inference uses `build_system_prompt`.
77
  TRAINING_SYSTEM_PROMPT = '''You are an experienced PC game player, proficient in keyboard and mouse operations. Based on the current game screen and the corresponding historical trajectory, plan the next 200ms of actions, consisting of 6 steps. Each step is spaced 33ms apart. Every step lasts 33ms from its start time until the next step begins.
78
  **Output Format**
79
  <|action_start|>X Y Z ; k1 k2 k3 ; k4 k5 ; k6 ; k7 ; k8 ; k9 k10<|action_end|>
 
103
  rules: str | None = None,
104
  key_bindings: Iterable[tuple[str, str]] | dict[str, str] | None = None,
105
  mouse_aim: bool = True,
106
+ reasoning: bool = True,
107
  ) -> str:
108
+ """Standard key-list layout for any game (ViZDoom, OmniGameArena, or a new game).
109
 
110
  ``key_bindings`` maps training tokens to what they do, e.g.
111
  ``{"W": "Move forward", "LB": "Fire"}``; ``LMB``/``RMB``/``MMB`` are accepted
tests/test_core.py CHANGED
@@ -61,17 +61,15 @@ def test_lenient_recovers_skeleton():
61
 
62
 
63
  def test_finalize_restores_prefill_and_stop():
64
- cfg = PolicyConfig(think_prefill=True)
65
  out = finalize("\n\n</think>\n\n<|action_start|>0 0 0 ; W ; ; ; ; ; ", "stop", cfg)
66
  assert out.startswith("<think>") and out.endswith("<|action_end|>")
67
  assert parse_action(out).steps[0] == ["W"]
68
 
69
 
70
  def test_request_shape():
71
- plain = build_request([{"role": "user", "content": "current game screen"}], PolicyConfig())
72
- assert plain["messages"][-1]["role"] == "user"
73
- assert "continue_final_message" not in plain
74
- cfg = PolicyConfig(model="m", think_prefill=True)
75
  body = build_request([{"role": "system", "content": "s"}], cfg)
76
  assert body["messages"][-1] == {"role": "assistant", "content": "<think>"}
77
  assert body["continue_final_message"] and not body["add_generation_prompt"]
@@ -89,8 +87,7 @@ def test_image_encoding():
89
 
90
 
91
  def test_prompt_sections_in_order():
92
- p = build_system_prompt("Doom", rules="Kill monsters.", key_bindings={"W": "Forward", "LMB": "Fire"},
93
- reasoning=True)
94
  order = ["currently playing Doom", "**Game Rules**", "**Common Keys in Doom**", "- LB -> Fire",
95
  "**Action only (usual)**", "**Explanation**", "**Key Naming Rules**"]
96
  idx = [p.index(s) for s in order]
 
61
 
62
 
63
  def test_finalize_restores_prefill_and_stop():
64
+ cfg = PolicyConfig()
65
  out = finalize("\n\n</think>\n\n<|action_start|>0 0 0 ; W ; ; ; ; ; ", "stop", cfg)
66
  assert out.startswith("<think>") and out.endswith("<|action_end|>")
67
  assert parse_action(out).steps[0] == ["W"]
68
 
69
 
70
  def test_request_shape():
71
+ cfg = PolicyConfig(model="m")
72
+ assert cfg.image_size is None
 
 
73
  body = build_request([{"role": "system", "content": "s"}], cfg)
74
  assert body["messages"][-1] == {"role": "assistant", "content": "<think>"}
75
  assert body["continue_final_message"] and not body["add_generation_prompt"]
 
87
 
88
 
89
  def test_prompt_sections_in_order():
90
+ p = build_system_prompt("Doom", rules="Kill monsters.", key_bindings={"W": "Forward", "LMB": "Fire"})
 
91
  order = ["currently playing Doom", "**Game Rules**", "**Common Keys in Doom**", "- LB -> Fire",
92
  "**Action only (usual)**", "**Explanation**", "**Key Naming Rules**"]
93
  idx = [p.index(s) for s in order]