Load models and encode inputs#

LczeroModel owns neural execution while LczeroEvaluator owns chess-aware preparation and output standardization. This notebook creates and reloads a tiny local PyTorch network so every cell runs offline. The same evaluator boundary accepts converted ONNX files and Hub models.

[1]:
from pathlib import Path
from tempfile import TemporaryDirectory

import chess
import torch
from torch import nn

from lczerolens import InputFormat, LczeroEvaluator, LczeroModel


class TinyNetwork(nn.Module):
    def forward(self, planes):
        batch = planes.shape[0]
        policy = torch.zeros((batch, 1858), device=planes.device)
        wdl = torch.tensor([0.4, 0.3, 0.3], device=planes.device).expand(batch, -1).clone()
        mlh = torch.full((batch,), 12.0, device=planes.device)
        return policy, wdl, mlh


with TemporaryDirectory() as directory:
    model_path = Path(directory) / "tiny-network.pt"
    torch.save(TinyNetwork(), model_path)
    model = LczeroModel.from_path(str(model_path), out_keys=["policy", "wdl", "mlh"])

model.heads, model.network is not None, model.network_checksum[:12]
[1]:
(('policy', 'wdl', 'mlh'), True, 'sha256:f861c')

For a real network, use LczeroModel.from_path("network.onnx") or install the hub extra and call LczeroModel.from_hf("organization/model"). Pin the Hub revision when provenance must identify immutable weights.

[2]:
boards = [chess.Board(), chess.Board()]
boards[1].push_uci("e2e4")
evaluator = LczeroEvaluator(model, input_format=InputFormat.CLASSICAL_112)
prepared = evaluator.prepare(boards)
{
    "batch_size": tuple(prepared.batch_size),
    "planes_shape": tuple(prepared["input", "planes"].shape),
    "legal_moves": prepared["input", "legal_mask"].sum(-1).tolist(),
    "device": str(prepared.device),
}
[2]:
{'batch_size': (2,),
 'planes_shape': (2, 112, 8, 8),
 'legal_moves': [20, 20],
 'device': 'cpu'}

The prepared TensorDict is the advanced execution boundary. External instrumentation can add nested keys between prepare() and finish(); lczerolens validates its own keys without discarding those additions. Device movement belongs to the model and TensorDict.

[3]:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
evaluator.model.to(device)
prepared = evaluator.prepare(boards)
prepared["demo", "position_id"] = torch.arange(len(boards), device=device).unsqueeze(-1)
evaluations = evaluator.finish(boards, evaluator.model(prepared))
assert ("demo", "position_id") in evaluations.tensors.keys(include_nested=True, leaves_only=True)
{
    "heads": evaluator.model.heads,
    "value_origin": evaluations[0].value.origin.value,
    "mlh": evaluations[0].mlh,
    "retained_demo_key": evaluations.tensors["demo", "position_id"].tolist(),
}
[3]:
{'heads': ('policy', 'wdl', 'mlh'),
 'value_origin': 'derived_from_wdl',
 'mlh': 12.0,
 'retained_demo_key': [[0], [1]]}