On this page

Design

This page is a training-system diagnostic. It does not implement Drop7 moves. The harness asks whether the workstation's integrated AMD GPU can train the small board networks used by policy and value experiments, whether its answers agree with a higher-precision reference, and which software paths are safe to use.

Implementation

A board-shaped workload

The test network receives a stack of board planes, passes them through residual blocks, and ends in a column policy head plus a scalar value head. This gives the benchmark the convolution, normalization and optimizer work that a Drop7 learner would perform instead of timing an unrelated image model.

bench.pyA policy and value network for a seven-column board
Pythonlines 300–348
class ResidualBlock(nn.Module):
    def __init__(self, ch: int, norm: str = "group") -> None:
        super().__init__()
        self.conv1 = nn.Conv2d(ch, ch, 3, padding=1, bias=False)
        self.bn1 = make_norm(norm, ch)
        self.conv2 = nn.Conv2d(ch, ch, 3, padding=1, bias=False)
        self.bn2 = make_norm(norm, ch)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        y = F.relu(self.bn1(self.conv1(x)), inplace=True)
        y = self.bn2(self.conv2(y))
        return F.relu(x + y, inplace=True)


class Drop7Net(nn.Module):
    """Small residual CNN with a 7-way column policy head and a scalar head.

    Input:  (N, 12, 7, 7)
    Output: (N, 7) column logits, (N,) scalar regression (e.g. lifetime score).
    """

    def __init__(self, channels: int = 128, blocks: int = 6,
                 in_channels: int = IN_CHANNELS, norm: str = "group") -> None:
        super().__init__()
        self.norm_kind = norm
        self.stem = nn.Sequential(
            nn.Conv2d(in_channels, channels, 3, padding=1, bias=False),
            make_norm(norm, channels),
            nn.ReLU(inplace=True),
        )
        self.blocks = nn.Sequential(
            *[ResidualBlock(channels, norm) for _ in range(blocks)])
        # Policy head: 7 column logits.
        self.policy = nn.Sequential(
            nn.Conv2d(channels, 32, 1, bias=False),
            make_norm(norm, 32), nn.ReLU(inplace=True),
            nn.Flatten(), nn.Linear(32 * BOARD * BOARD, N_COLUMNS),
        )
        # Scalar head: one regression output.
        self.value = nn.Sequential(
            nn.Conv2d(channels, 32, 1, bias=False),
            make_norm(norm, 32), nn.ReLU(inplace=True),
            nn.Flatten(), nn.Linear(32 * BOARD * BOARD, 128),
            nn.ReLU(inplace=True), nn.Linear(128, 1),
        )

    def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
        h = self.blocks(self.stem(x))
        return self.policy(h), self.value(h).squeeze(-1)
The harness measures a residual trunk with the two outputs a Drop7 learner needs: column logits and one scalar estimate.

A normalization path chosen by evidence

Batch normalization failed to compile in training mode on the installed GPU stack. Preloading a second vendor library made that operation start and later introduced unstable device contexts. The retained safe path uses group normalization, which stays inside the framework's native kernels.

bench.pySelect the supported normalization kernel
Pythonlines 277–297
def make_norm(kind: str, ch: int) -> nn.Module:
    """Norm layer factory.

    'batch' is the textbook choice but is BROKEN FOR TRAINING on gfx1151 with
    the MIOpen shipped inside the torch ROCm wheel: MIOpen selects a GFX9-only
    solver for the batch-norm training kernel and fails to assemble it
    ("v_add_f32 ... row_bcast:31" is not a valid gfx1151 operand), so every
    training step raises miopenStatusUnknownError. Eval mode is unaffected.

    'group' avoids MIOpen entirely -- GroupNorm is a native PyTorch kernel --
    and is the default here. It is also the better choice on the merits for a
    self-play/RL setting, where BatchNorm's running statistics drift between
    the acting and learning distributions and couple samples within a batch.
    """
    if kind == "batch":
        return nn.BatchNorm2d(ch)
    if kind == "group":
        return nn.GroupNorm(min(8, ch), ch)
    if kind == "none":
        return nn.Identity()
    raise ValueError(f"unknown norm kind {kind!r}")
The factory keeps the broken batch-normalization path available for diagnosis and makes group normalization the working default.

Correctness before throughput

The harness probes the runtime, compares operations with a double-precision CPU reference, checks forward and backward passes, and runs a training soak before measuring speed. That ordering exposed silent CPU numerical defects that a GPU-versus-CPU comparison would otherwise have blamed on the GPU.

bench.pyRecord correctness gates and known warnings separately
Pythonlines 451–469
# --------------------------------------------------------------------------
# 2. Correctness
# --------------------------------------------------------------------------

def correctness(dev: torch.device, tol_matmul: float = 5e-2) -> dict[str, Any]:
    """A GPU that imports but computes garbage is a real failure mode on
    partially supported targets. These checks compare against CPU."""
    section("2. NUMERICAL CORRECTNESS (GPU vs CPU)")
    res: dict[str, Any] = {"passed": True, "checks": []}

    def record(name: str, ok: bool, detail: str, gate: bool = True) -> None:
        """gate=False: a known, documented limitation. Still reported loudly,
        but it does not fail the run, because the supported configuration
        works around it (see make_norm)."""
        res["checks"].append({"name": name, "ok": bool(ok),
                              "detail": detail, "gate": gate})
        if gate:
            res["passed"] = res["passed"] and bool(ok)
        else:
A known unsupported kernel remains visible as a warning, while failures on the supported path fail the run.

Uses

The GPU is useful for batched neural training. Exact game transitions and chain cascades remain on the CPU. The activation script supplies the runtime-library workaround, and the benchmark writes environment, correctness, throughput and telemetry data for a later machine-scoped record.

Verification

Three retained findings cover runtime enablement, a multithreaded matrix-multiply race and nondeterministic CPU convolution: GPU enablement, matrix-multiply race and convolution nondeterminism. They establish a working training path on this machine and record the unsafe paths beside it.

Technical recordWhat the GPU work measured and what it left outside scope
  • The correctness stage compares GPU operations with a double-precision CPU calculation.
  • The training stage measures forward, backward and optimizer work on the board-shaped network.
  • The probe records runtime versions, visible device properties, system load and the shared memory pool.
  • No game was played, no seed lease was opened and no policy-strength claim was made.

Limits

Every throughput run in the retained finding used a contended host, so none is an idle-machine baseline. The working environment depends on a system runtime library and session-level device access. The CPU numerical defects were isolated to the tested builds and shapes; their upstream cause remains unresolved.

Agent contextEnvironment setup, safe defaults and the remeasurement boundary

Read approaches/lifetime-objective/gpu/activate.sh before running the harness. Keep group normalization as the default and pin the affected CPU matrix library to the recorded safe thread count. Begin with bench.py --probe and bench.py --correctness before any throughput stage.

A publishable timing result needs a fresh registered run, an exclusive resource lease, the retained machine profile and repeated idle-host measurements. Do not reuse the exploratory throughput figures as a clean baseline, and do not describe this package as a game engine.