大家好,我是超哥。今天教大家部署 Laya 本地决策 AI 引擎,Laya 是一个几百MB的开源决策模型,速度比 Jev 快 7–11 倍,它主要负责分类、判断、评分以及智能路由,可以直接在本地运行,实现毫秒级 AI 决策,不需要依赖云端 API。接下来我会带大家完成 Laya 的本地部署和运行测试。下面开始教程。

一、安装python环境
下载并安装python
下载地址:https://www.python.org/downloads/release/python-3120/
直达下载:https://www.python.org/ftp/python/3.12.0/python-3.12.0-amd64.exe
验证python是否安装成功,cmd里执行下面代码:
python --version
二、安装虚拟环境
1. 创建模型目录
在D盘创建目录:LayaAI,创建后路径 D:\LayaAI
2. 创建虚拟空间
cmd进入 D:\LayaAI,执行下面命令,创建虚拟空间:
python -m venv laya-env
输入下面命令,激活虚拟空间:
laya-env\Scripts\activate

3. 升级 pip
升级 pip 命令:
python -m pip install --upgrade pip
4. 安装 CUDA 版 PyTorch
安装命令:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
检测命令:
python -c "import torch; print('PyTorch:', torch.__version__); print('CUDA:', torch.cuda.is_available()); print('GPU:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'None')"
检测需要出现类似下面文字(一个人一个样):
CUDA: True
CUDA版本: 12.8
GPU: NVIDIA GeForce RTX 4080 SUPER
三、安装 Laya
Github 项目地址:https://github.com/NandhaKishorM/laya
1. 安装 Laya 命令
pip install laya
2. 检测 Laya 是否安装成功
python -c "import laya; print('Laya OK'); print('Version:', laya.__version__)"
3. 测试 Laya
电脑 D:\LayaAI 目录下创建 test.py 文件,复制下面的内容进去:
from laya import Router
print("正在加载 Laya...")
router = Router(preload=True)
state = {
"subject": "Payment failed",
"body": "I tried to pay for my order twice, but both payments failed."
}
questions = {
"category": {
"type": "choice",
"instructions": "Which team should handle this request?",
"criteria": {
"billing": "payments, invoices, refunds",
"technical": "bugs, errors, integrations",
"sales": "pricing, demos, purchases",
"other": "none of the above"
}
}
}
print("正在进行决策...")
result = router.predict(state, questions)
print("\n===== Laya 决策结果 =====")
print(result)
执行下面的命令
python test.py
四、贪吃蛇测试 Laya
1. 安装 Pygame
pip install pygame
2. 测试 Pygame 是否安装成功
python -c "import pygame; print(pygame.version.ver)"
3. 贪吃蛇 Laya 测试 Demo
电脑 D:\LayaAI 目录下创建 snake.py 文件,复制下面的内容进去:
import time
import random
from collections import deque
import pygame
import torch
from laya import Router
# ============================================================
# Laya Snake - Windows / CUDA
#
# Default mode:
# Laya + Safety Shield
#
# Set PURE_LAYA = True if you want to deliberately observe
# unfiltered Laya decisions (it can crash into walls).
# ============================================================
PURE_LAYA = False
BOARD_W = 20
BOARD_H = 14
CELL = 36
GAME_W = BOARD_W * CELL
GAME_H = BOARD_H * CELL
SIDE_W = 340
WIDTH = GAME_W + SIDE_W
HEIGHT = GAME_H
FPS = 60
MOVE_INTERVAL = 0.12
# ------------------------- colors ----------------------------
BG = (7, 13, 18)
BOARD = (5, 11, 16)
GRID = (19, 31, 40)
PANEL = (13, 22, 29)
TEXT = (235, 241, 245)
MUTED = (140, 157, 170)
GREEN = (32, 192, 87)
GREEN_HEAD = (50, 224, 111)
YELLOW = (255, 177, 0)
RED = (235, 75, 81)
BLUE = (105, 170, 240)
BAR_BG = (35, 49, 59)
DIRECTIONS = {
"UP": (0, -1),
"DOWN": (0, 1),
"LEFT": (-1, 0),
"RIGHT": (1, 0),
}
OPPOSITE = {
"UP": "DOWN",
"DOWN": "UP",
"LEFT": "RIGHT",
"RIGHT": "LEFT",
}
def inside(x, y):
return 0 <= x < BOARD_W and 0 <= y < BOARD_H
def next_pos(pos, direction):
dx, dy = DIRECTIONS[direction]
return [pos[0] + dx, pos[1] + dy]
def manhattan(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
# ============================================================
# Board analysis / planner
# ============================================================
def legal_moves(game):
"""
Hard Snake movement rules.
This keeps the v2 goal-seeking behavior, but makes self-collision
an absolute execution rule:
- no wall collision
- no collision with the snake body
- no 180-degree reversal
- the tail is only considered free on a non-eating move
"""
result = []
for direction in DIRECTIONS:
if direction == OPPOSITE[game.direction]:
continue
p = next_pos(game.snake[0], direction)
# Rule 1: wall collision = illegal
if not inside(p[0], p[1]):
continue
# Rule 2: body collision = illegal.
# The current tail leaves the board cell only when this move
# does NOT eat food.
occupied = set(map(tuple, game.snake))
if game.food != p and game.snake:
occupied.discard(tuple(game.snake[-1]))
if tuple(p) in occupied:
continue
# Final simulation check. This catches any mismatch between the
# rule calculation and the actual SnakeGame.move() implementation.
fake = SnakeGame.from_existing(game)
if not fake.move(direction) or fake.game_over:
continue
result.append(direction)
return result
def reachable_area(game, start, extra_blocked=None):
blocked = set(map(tuple, game.snake))
if extra_blocked:
blocked.update(extra_blocked)
# The tail can move on the next ordinary step.
if game.snake:
blocked.discard(tuple(game.snake[-1]))
start_t = tuple(start)
if start_t in blocked or not inside(*start):
return 0
q = deque([start_t])
seen = {start_t}
while q:
x, y = q.popleft()
for dx, dy in DIRECTIONS.values():
nx, ny = x + dx, y + dy
if not inside(nx, ny):
continue
p = (nx, ny)
if p in seen or p in blocked:
continue
seen.add(p)
q.append(p)
return len(seen)
def shortest_path_length(game, target):
"""BFS distance from snake head to target through current free cells."""
blocked = set(map(tuple, game.snake))
if game.snake:
blocked.discard(tuple(game.snake[-1]))
start = tuple(game.snake[0])
target = tuple(target)
if start == target:
return 0
q = deque([(start, 0)])
seen = {start}
while q:
(x, y), dist = q.popleft()
for dx, dy in DIRECTIONS.values():
nx, ny = x + dx, y + dy
if not inside(nx, ny):
continue
p = (nx, ny)
if p in seen or p in blocked:
continue
if p == target:
return dist + 1
seen.add(p)
q.append((p, dist + 1))
return None
def planner_summary(game):
"""Compact structured information supplied to Laya."""
legal = legal_moves(game)
candidates = {}
for direction in DIRECTIONS:
p = next_pos(game.snake[0], direction)
if direction not in legal:
candidates[direction] = {
"legal": False,
"distance_to_food": None,
"reachable_area": 0,
}
continue
fake = SnakeGame.from_existing(game)
fake.move(direction)
area = reachable_area(fake, fake.snake[0])
distance = shortest_path_length(
fake,
game.food
) if game.food else None
candidates[direction] = {
"legal": True,
"distance_to_food": distance,
"reachable_area": area,
}
return legal, candidates
# ============================================================
# Snake
# ============================================================
class SnakeGame:
def __init__(self):
self.reset()
@classmethod
def from_existing(cls, other):
obj = cls.__new__(cls)
obj.snake = [p[:] for p in other.snake]
obj.direction = other.direction
obj.food = other.food[:] if other.food else None
obj.score = other.score
obj.steps = other.steps
obj.game_over = other.game_over
return obj
def reset(self):
cx = BOARD_W // 2
cy = BOARD_H // 2
self.snake = [
[cx, cy],
[cx - 1, cy],
[cx - 2, cy],
[cx - 3, cy],
]
self.direction = "RIGHT"
self.food = self.spawn_food()
self.score = 0
self.steps = 0
self.game_over = False
def spawn_food(self):
free = [
[x, y]
for y in range(BOARD_H)
for x in range(BOARD_W)
if [x, y] not in self.snake
]
return random.choice(free) if free else None
def move(self, direction):
if self.game_over:
return False
if direction == OPPOSITE[self.direction]:
direction = self.direction
dx, dy = DIRECTIONS[direction]
head = self.snake[0]
new_head = [head[0] + dx, head[1] + dy]
# Collision check. The tail is allowed to move away on
# a non-eating step.
occupied = set(map(tuple, self.snake))
if self.food != new_head and self.snake:
occupied.discard(tuple(self.snake[-1]))
if not inside(*new_head) or tuple(new_head) in occupied:
self.game_over = True
return False
self.direction = direction
self.snake.insert(0, new_head)
self.steps += 1
if self.food and new_head == self.food:
self.score += 1
self.food = self.spawn_food()
else:
self.snake.pop()
return True
# ============================================================
# Laya controller
# ============================================================
class LayaController:
def __init__(self):
print("=" * 60)
print("Loading Laya...")
print("=" * 60)
self.router = Router(
preload=True,
device="cuda"
)
print("Laya loaded successfully.")
if torch.cuda.is_available():
print("GPU:", torch.cuda.get_device_name(0))
self.probabilities = {
d: 0.25 for d in DIRECTIONS
}
self.choice = "RIGHT"
self.latency_ms = 0.0
self.raw_choice = "RIGHT"
self.shield_intervention = False
self.dead_end_risk = None
self.food_reachable = None
def predict(self, game):
legal, candidates = planner_summary(game)
# Build a compact state. The planner information is
# deliberately supplied to Laya; this is similar in spirit
# to the public Snake demo, which does not feed a raw image
# to the checkpoint.
state = {
"game": {
"board": [BOARD_W, BOARD_H],
"score": game.score,
"steps": game.steps,
"direction": game.direction,
"snake_head": game.snake[0],
"snake_length": len(game.snake),
"food": game.food,
},
"planner": {
"legal_moves": legal,
"candidates": candidates,
"food_distance": (
shortest_path_length(game, game.food)
if game.food else None
),
"reachable_area": reachable_area(
game, game.snake[0]
),
},
}
questions = {
"next_move": {
"type": "choice",
"instructions": (
"Choose the best next move for the snake. "
"Use the supplied planner information. "
"Never choose a move marked legal=false. "
"Prefer a move that approaches the food while "
"preserving a large reachable area."
),
"criteria": {
"UP": "Move one cell upward.",
"DOWN": "Move one cell downward.",
"LEFT": "Move one cell left.",
"RIGHT": "Move one cell right.",
},
},
"dead_end_risk": {
"type": "noul",
"instructions": (
"Does the current position have a serious "
"dead-end risk according to the supplied "
"reachable-area and candidate information?"
),
},
"food_reachable": {
"type": "noul",
"instructions": (
"Is the food currently reachable according "
"to the supplied planner information?"
),
},
}
if torch.cuda.is_available():
torch.cuda.synchronize()
start = time.perf_counter()
result = self.router.predict(
state,
questions
)
if torch.cuda.is_available():
torch.cuda.synchronize()
self.latency_ms = (
time.perf_counter() - start
) * 1000.0
# ---------------- parse choice ----------------
try:
answer = result["answers"]["next_move"]
raw_choice = answer.get("choice")
raw_probs = answer.get("probabilities", {})
if raw_choice in DIRECTIONS:
self.raw_choice = raw_choice
probs = {}
for d in DIRECTIONS:
try:
probs[d] = max(
0.0,
float(raw_probs.get(d, 0.0))
)
except Exception:
probs[d] = 0.0
total = sum(probs.values())
if total > 0:
probs = {
d: v / total
for d, v in probs.items()
}
self.probabilities = probs
except Exception as exc:
print("Choice parsing error:", exc)
print(result)
# ---------------- parse auxiliary answers ----------------
try:
self.dead_end_risk = float(
result["answers"]["dead_end_risk"]["noul"]
)
except Exception:
self.dead_end_risk = None
try:
self.food_reachable = float(
result["answers"]["food_reachable"]["noul"]
)
except Exception:
self.food_reachable = None
# ---------------- goal-seeking safety shield ----------------
#
# The previous version only blocked immediate collisions.
# That is NOT enough: Laya can legally choose LEFT forever
# while the food is below/right of the snake.
#
# Here Laya still provides the direction probabilities, but
# the execution layer requires the move to make measurable
# progress toward the current food whenever a safe progress
# move exists. Among equally good progress moves, Laya decides.
#
# This is much closer to the public Laya Snake demo's idea:
# planner features + Laya decision + execution safety layer.
self.shield_intervention = False
safe = list(legal)
if PURE_LAYA:
selected = self.raw_choice
if selected == OPPOSITE[game.direction]:
selected = game.direction
elif not safe:
selected = game.direction
else:
current_distance = shortest_path_length(game, game.food)
scored = []
for d in safe:
fake = SnakeGame.from_existing(game)
moved_ok = fake.move(d)
# A candidate that actually causes GAME OVER is never
# allowed into the goal-seeking candidate pool.
if not moved_ok or fake.game_over:
continue
new_distance = (
shortest_path_length(fake, game.food)
if game.food else None
)
area = reachable_area(fake, fake.snake[0])
# Primary objective: get closer to food.
# Secondary objective: keep the snake in open space.
if current_distance is not None and new_distance is not None:
progress = current_distance - new_distance
else:
progress = 0
scored.append(
{
"direction": d,
"distance": new_distance,
"progress": progress,
"area": area,
"prob": self.probabilities.get(d, 0.0),
}
)
# If there is at least one safe move that reduces the
# shortest-path distance, discard moves that do not.
progress_moves = [
item for item in scored
if item["progress"] > 0
]
if progress_moves:
candidates = progress_moves
else:
# No direct progress is available. Preserve space,
# then let Laya break ties.
max_area = max(item["area"] for item in scored)
candidates = [
item for item in scored
if item["area"] >= max_area * 0.90
]
# Laya remains the tie-breaker / preference signal.
# A small area term prevents obviously cramped choices.
selected_item = max(
candidates,
key=lambda item: (
item["prob"] * 100.0
+ item["area"] * 0.02
)
)
selected = selected_item["direction"]
if selected != self.raw_choice:
self.shield_intervention = True
# FINAL HARD RULE:
# Never execute a move that the actual game engine says is a
# wall/body collision. This does not change v2's goal-seeking
# behavior; it only prevents an illegal move from reaching game.move().
if not PURE_LAYA:
verified = SnakeGame.from_existing(game)
if not verified.move(selected) or verified.game_over:
safe_fallbacks = []
for d in legal:
test = SnakeGame.from_existing(game)
if test.move(d) and not test.game_over:
safe_fallbacks.append(d)
if safe_fallbacks:
selected = max(
safe_fallbacks,
key=lambda d: self.probabilities.get(d, 0.0)
)
self.shield_intervention = True
else:
# No legal move exists: the current position is
# genuinely terminal.
selected = game.direction
self.shield_intervention = True
self.choice = selected
return selected
# ============================================================
# GUI
# ============================================================
def text(screen, font, value, x, y, color=TEXT):
surf = font.render(str(value), True, color)
screen.blit(surf, (x, y))
def draw_bar(screen, x, y, width, height, value):
pygame.draw.rect(
screen,
BAR_BG,
(x, y, width, height)
)
pygame.draw.rect(
screen,
GREEN,
(
x,
y,
int(width * max(0.0, min(1.0, value))),
height
)
)
def draw(screen, game, controller, fonts, paused):
small = fonts["small"]
normal = fonts["normal"]
big = fonts["big"]
screen.fill(BG)
# Board
pygame.draw.rect(
screen,
BOARD,
(0, 0, GAME_W, GAME_H)
)
for x in range(BOARD_W + 1):
px = x * CELL
pygame.draw.line(
screen, GRID,
(px, 0),
(px, GAME_H)
)
for y in range(BOARD_H + 1):
py = y * CELL
pygame.draw.line(
screen, GRID,
(0, py),
(GAME_W, py)
)
# Food
if game.food:
fx, fy = game.food
pygame.draw.circle(
screen,
YELLOW,
(
fx * CELL + CELL // 2,
fy * CELL + CELL // 2
),
CELL // 4
)
# Snake
for i, (x, y) in enumerate(game.snake):
rect = pygame.Rect(
x * CELL + 2,
y * CELL + 2,
CELL - 4,
CELL - 4
)
pygame.draw.rect(
screen,
GREEN_HEAD if i == 0 else GREEN,
rect,
border_radius=5
)
# Side
pygame.draw.rect(
screen,
PANEL,
(GAME_W, 0, SIDE_W, HEIGHT)
)
x = GAME_W + 24
y = 20
text(screen, big, "LAYA × SNAKE", x, y)
y += 42
if game.game_over:
text(screen, normal, "GAME OVER", x, y, RED)
elif paused:
text(screen, normal, "PAUSED", x, y, YELLOW)
else:
text(screen, small, "LIVE DECISION - XGDN.Com", x, y, MUTED)
y += 44
text(screen, normal, "NEXT MOVE", x, y)
y += 34
for d in ("UP", "DOWN", "LEFT", "RIGHT"):
p = controller.probabilities.get(d, 0.0)
text(screen, small, d, x, y)
draw_bar(
screen,
x + 70,
y + 4,
150,
14,
p
)
text(
screen,
small,
f"{p * 100:.0f}%",
x + 230,
y
)
y += 36
y += 8
text(screen, small, "SELECTED", x, y, MUTED)
y += 23
text(
screen,
big,
controller.choice,
x,
y,
GREEN_HEAD
)
y += 48
text(screen, small, "RAW LAYA", x, y, MUTED)
text(
screen,
small,
controller.raw_choice,
x + 110,
y
)
y += 27
text(screen, small, "GOAL + RULE SHIELD", x, y, MUTED)
shield_text = (
"INTERVENED"
if controller.shield_intervention
else "PASS"
)
shield_color = (
YELLOW
if controller.shield_intervention
else GREEN
)
text(
screen,
small,
shield_text,
x + 110,
y,
shield_color
)
y += 39
# Metrics
metrics = [
("SCORE", game.score),
("STEPS", game.steps),
("LATENCY", f"{controller.latency_ms:.1f} ms"),
(
"DECISIONS/S",
f"{1000 / controller.latency_ms:.0f}"
if controller.latency_ms > 0 else "—"
),
]
for label, value in metrics:
text(screen, small, label, x, y, MUTED)
text(screen, normal, value, x + 125, y - 4)
y += 31
y += 9
text(screen, small, "MODEL", x, y, MUTED)
y += 22
text(screen, small, "Laya multilingual + goal planner", x, y)
y += 35
if controller.dead_end_risk is not None:
text(
screen,
small,
"DEAD-END RISK",
x,
y,
MUTED
)
text(
screen,
small,
f"{controller.dead_end_risk * 100:.1f}%",
x + 125,
y
)
y += 27
if controller.food_reachable is not None:
text(
screen,
small,
"FOOD REACHABLE",
x,
y,
MUTED
)
text(
screen,
small,
f"{controller.food_reachable * 100:.1f}%",
x + 125,
y
)
y += 32
text(
screen,
small,
"SPACE Pause / Resume",
x,
y,
MUTED
)
y += 21
text(
screen,
small,
"R Restart",
x,
y,
MUTED
)
y += 21
text(
screen,
small,
"ESC Quit",
x,
y,
MUTED
)
# ============================================================
# Main
# ============================================================
def main():
pygame.init()
pygame.display.set_caption(
"Laya Snake - RTX 4080 Super"
)
screen = pygame.display.set_mode(
(WIDTH, HEIGHT)
)
clock = pygame.time.Clock()
fonts = {
"small": pygame.font.SysFont(
"Consolas", 15
),
"normal": pygame.font.SysFont(
"Consolas", 19, bold=True
),
"big": pygame.font.SysFont(
"Consolas", 27, bold=True
),
}
controller = LayaController()
game = SnakeGame()
running = True
paused = False
last_move = time.perf_counter()
while running:
now = time.perf_counter()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
paused = not paused
elif event.key == pygame.K_r:
game.reset()
paused = False
last_move = time.perf_counter()
if (
not paused
and not game.game_over
and now - last_move >= MOVE_INTERVAL
):
last_move = now
direction = controller.predict(game)
game.move(direction)
draw(
screen,
game,
controller,
fonts,
paused
)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()