GPU and batching
An infrastructure package that made PyTorch work on this machine's integrated AMD GPU, checked that it computes correct answers, and measured how much faster it is than the CPU.
On this page
- Neural training only; the hardware plan keeps exact transitions and chain cascades on the CPU
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.
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)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.
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}")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.
# --------------------------------------------------------------------------
# 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: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.