Image-Text-to-Text
Transformers
Safetensors
English
gamescaling
game-agent
embodied-agent
keyboard-mouse
continued-pretraining
qwen3_5
Instructions to use THUSI-Lab/GameScaling with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use THUSI-Lab/GameScaling with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="THUSI-Lab/GameScaling")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("THUSI-Lab/GameScaling", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use THUSI-Lab/GameScaling with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "THUSI-Lab/GameScaling" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "THUSI-Lab/GameScaling", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/THUSI-Lab/GameScaling
- SGLang
How to use THUSI-Lab/GameScaling with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "THUSI-Lab/GameScaling" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "THUSI-Lab/GameScaling", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "THUSI-Lab/GameScaling" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "THUSI-Lab/GameScaling", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use THUSI-Lab/GameScaling with Docker Model Runner:
docker model run hf.co/THUSI-Lab/GameScaling
Default: standard build_system_prompt (reasoning=True) + <think> prefill, any game frame
Browse files- README.md +27 -13
- gamescaling/__pycache__/policy.cpython-310.pyc +0 -0
- gamescaling/__pycache__/prompt.cpython-310.pyc +0 -0
- gamescaling/policy.py +7 -8
- gamescaling/prompt.py +5 -4
- tests/test_core.py +4 -7
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 系统提示词
|
| 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 |
-
默认推理
|
| 19 |
-
|
| 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
|
| 52 |
-
|
| 53 |
-
#
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 68 |
-
`
|
| 69 |
-
|
| 70 |
-
`
|
|
|
|
| 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. 新游戏沿用默认客户端(原尺寸画面、
|
| 229 |
-
|
| 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 |
-
#
|
| 65 |
-
|
| 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
|
| 80 |
-
|
| 81 |
"""
|
| 82 |
presets = {
|
| 83 |
-
"omnigamearena": cls(image_size=(854, 480)
|
| 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),
|
| 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 |
-
#
|
| 75 |
-
#
|
|
|
|
| 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 =
|
| 106 |
) -> str:
|
| 107 |
-
"""
|
| 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(
|
| 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 |
-
|
| 72 |
-
assert
|
| 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]
|