| import gradio as gr |
| import numpy as np |
| import torch |
| import json |
| from huggingface_hub import hf_hub_download |
| import torch.nn as nn |
|
|
|
|
|
|
| class BlokusTransformer(nn.Module): |
| def __init__(self, |
| d_max=20, |
| embed_dim=128, |
| num_heads=4, |
| mlp_dim=256, |
| num_layers=4, |
| dropout=0.1): |
| super().__init__() |
| |
| self.input_proj = nn.Linear(5, embed_dim) |
| self.pos_embed = nn.Parameter(torch.zeros(d_max * d_max, embed_dim)) |
| nn.init.trunc_normal_(self.pos_embed, std=0.02) |
|
|
| self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) |
| |
| encoder_layer = nn.TransformerEncoderLayer(d_model=embed_dim, |
| nhead=num_heads, |
| dim_feedforward=mlp_dim, |
| dropout=dropout, |
| batch_first=True) |
| self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) |
| self.policy_head = nn.Linear(embed_dim, 1) |
| self.value_head = nn.Linear(embed_dim, 4) |
| |
| def forward(self, x): |
| """ |
| board_tensor: shape [batch_size, d, input_dim] |
| each cell has input_dim=5 features (4 occupancy bits + 1 legal bit) |
| |
| Returns: |
| policy_logits: [batch_size, d, d] |
| value: [batch_size, 4] |
| """ |
| batch_size, d, _ = x.shape |
| |
| |
| x = self.input_proj(x) |
| |
| |
| pos_embed_slice = self.pos_embed[:d, :].unsqueeze(0) |
| x = x + pos_embed_slice |
|
|
| init_cls_tokens = self.cls_token.expand(batch_size, -1, -1) |
| x = torch.cat([init_cls_tokens, x], dim=1) |
| |
| x = self.transformer(x) |
| board_tokens = x[:, 1:, :] |
| cls_tokens = x[:, 0, :] |
|
|
| policy_logits = self.policy_head(board_tokens).view(batch_size, d) |
| value_logits = self.value_head(cls_tokens) |
| |
| return policy_logits, value_logits |
|
|
| |
| |
| model_path = hf_hub_download( |
| repo_id="aracape/Blokus-Transformer", |
| filename="20x20.pt" |
| ) |
|
|
| |
| config = { |
| "d_max": 20, |
| "embed_dim": 128, |
| "num_heads": 4, |
| "mlp_dim": 256, |
| "num_layers": 4, |
| "dropout": 0.1 |
| } |
| model = BlokusTransformer(**config) |
| model.load_state_dict(torch.load(model_path, weights_only=True, map_location='cpu')) |
| model.eval() |
|
|
| def predict(input_data): |
| """ |
| Process input and return prediction |
| """ |
| try: |
| |
| if isinstance(input_data, str): |
| input_data = json.loads(input_data) |
| |
| |
| input_np = np.array(input_data, dtype=np.float32) |
| if input_np.size == 20 * 20 * 5: |
| |
| input_tensor = torch.from_numpy(input_np.reshape(1, 20 * 20, 5)) |
| else: |
| return "Error: Input must be 20x20x5 = 2000 elements", "Error: Invalid input size" |
| |
| |
| with torch.no_grad(): |
| policies, values = model(input_tensor) |
|
|
| policy = policies.squeeze().tolist() |
| value = values.squeeze().tolist() |
| |
| return policy, value |
| |
| except Exception as e: |
| return f"Error: {str(e)}" |
|
|
| |
| iface = gr.Interface( |
| fn=predict, |
| inputs=gr.Textbox(label="Input (JSON format)", placeholder='{"data": [1, 2, 3, 4]}'), |
| outputs=gr.Textbox(label="Prediction"), |
| title="My PyTorch Model API" |
| ) |
|
|
| |
| iface.launch(share=True) |