An MI300X Over MCP: What the Matrix Cores Execute, and What They Don't
This article provides a step by step inventory and measurement of a single AMD Instinct MI300X on an AMD Developer Cloud hosted GPU enabled system. A suite of Python MCP tools is built to simplify management of the droplet, and the same server is used to read the card's native numeric format support off the hardware. https://github.com/xbill9/amd-gputools The workstation writing this has no AMD GPU and never will. The hardware is one MI300X droplet billed at $1.99 an hour, and everything that reaches it goes through twelve tag-scoped MCP tools for inventory, power, reboot, hardware scan, GPU state and remote execution. Two of the readings those tools returned were wrong the first time, and both were corrected by parsing output instead of trusting a status. The format table at the end disagrees with AMD's published peaks in two places, and the disagreement is the result rather than a footnote to it. Prerequisites - An AMD Developer Cloud account with a GPU droplet already created. devcloud.amd.com is DigitalOcean underneath - same v2 API, same droplet ids - and the token comes from the My AMD Team account, not a personal DigitalOcean one. - The droplet tagged. Every lookup in this server is scoped by tag_name , so an untagged droplet is invisible to it and a tagged one belonging to someone else is not. - DIGITALOCEAN_ACCESS_TOKEN in the environment, or in a mode 0600.env , or in~/ocean.txt . The same order is used by the server and byssh-droplet.sh . - An SSH key on the droplet as root .BatchMode=yes throughout, so a wrong key fails rather than waiting on a password prompt. - Python 3 with httpx andpython-dotenv in the system interpreter. No virtualenv -.mcp.json launches the server with a barepython3 . The Box, In Detail Everything in these three tables was read off the machine by hardware_scan , rocminfo and lspci through the MCP server, on 2026-09-16. Host | CPU | INTEL(R) XEON(R) PLATINUM 8568Y+ | | Cores | 20 vCPU - 1 socket, 20 cores, 1 thread per core (no SMT) | | Cache | L1d 640 KiB (20 instances), L2 80 MiB (20 instances) | | NUMA | 1 node | | RAM | 236 GB usable, 138 GB free, 90 GB in buff/cache | | Disk | 720 GB, 595 G free | | OS | Debian 13 (trixie), kernel 6.12.94+deb13-amd64 | | Droplet | gpu-mi300x1-192gb-devcloud , region atl1 , id 601142018 | GPU | Device | AMD Instinct MI300X VF (virtual function), [1002:74b5] at 83:00.0 | | Target | gfx942 , ISA amdgcn-amd-amdhsa--gfx942:sramecc+:xnack- | | Compute | 304 CUs, 4 SIMDs per CU, wavefront 64, max 32 waves per CU | | Clock | 2100 MHz max | | Workgroup | max 1024 threads, LDS (GROUP segment) 64 KB | | Cacheline | 128 B | | Link | PCIe Gen5 x16 - LnkSta: Speed 32GT/s, Width x16 | | BAR | Region 0 is 256 G prefetchable, so the whole framebuffer is host-visible | | VBIOS | 113-M3000108-103 , SKU M3000108 , SMC firmware 00.85.129.03 | | UUID | GPU-2896ede4ddd2a8b6 | GPU memory - the numbers that decide what fits: | Pool | Total | Used | |---|---|---| | VRAM | 205,822,885,888 B = 191.69 GiB | 180,717,051,904 B = 168.31 GiB (87%†) | | VIS_VRAM (host-visible) | 205,822,885,888 B = 191.69 GiB | 180,717,051,904 B = 168.31 GiB | | GTT (system memory aperture) | 126,676,250,624 B = 117.98 GiB | 21,327,872 B = 0.02 GiB | † 87 is what rocm-smi reports; the division gives 87.8%. The tool truncates, and this article quotes the tool. Two things worth reading off that table. VIS_VRAM equals VRAM: this is a large-BAR configuration, the entire 191.69 GiB is CPU-mappable, and no part of the framebuffer is hidden behind the old 256 MB window. And rocminfo reports three GLOBAL pools - coarse grained, fine grained and extended fine grained - each at 200,998,912 KB, which is the same 191.69 GiB described three ways, not three separate allocations. The "VF" matters. This is a virtualized MI300X, not a bare card: firmware queries like ASD, PFP, MES and SOS answer "Not supported on the given system", and amd-smi cannot see partitioning. MEC (32948), RLC (65), SDMA (24), SMC and the RAS/XGMI TAs do report. The Control Plane: Twelve Tools, Tag-Scoped get_help is the tool that describes the others, and its real output is the shape of the whole server: 📡 amd-gputools - DigitalOcean control plane for this project's AMD MI300 droplets. Scoped to droplets tagged gemma; SSH as root; checkout at /opt/amd-gputools. - list_droplets - List every droplet tagged for this project, with state and address. - droplet_status - Show one tagged droplet in detail, by numeric id or by name. - start_droplet - Power on a tagged droplet. No-op if it is already active. - stop_droplet - Power off a tagged droplet. - reboot_droplet - Reboot a tagged droplet. - action_status - Poll a droplet action returned by start_droplet or stop_droplet. - ssh_command - Print the ssh command for a tagged droplet, with its current address. - run_on_droplet - Run one shell command on a tagged droplet over SSH and return its output. - gpu_status - Report the AMD GPUs on a tagged droplet, and say why if there are none. - hardware_scan - Inventory a droplet: host, GPU, firmware, ROCm packages and installed tools. - list_gpu_sizes - List DigitalOcean GPU droplet sizes and their prices, cheapest first. - get_help - List the tools this server exposes. No create or destroy tools, by design: both are dollar-per-hour decisions, so they stay a deliberate step in the DigitalOcean console. Powering a droplet off does not stop DigitalOcean billing it. Three design decisions are visible in that text. Every lookup is scoped by tag. The server resolves droplets through GET /v2/droplets?tag_name=gemma , so a tool cannot name, reboot or power off a droplet nobody tagged for it. The token in use is an account token with full reach; the tag is what keeps a reboot from landing on someone else's box. There is no create and no destroy. Not an oversight. Creating an MI300X starts a meter and destroying one throws away state, and neither is a thing a model should be one tool call away from. The console is a fine place for a decision that costs dollars per hour. Powering off does not stop the billing. This is the misconception the server exists to correct, so it is written into the tool output rather than a README. DigitalOcean reserves the resources of a stopped droplet and charges the full rate; only destroying it stops the meter. stop_droplet says so in its return value every time, and a test enforces that the word survives refactoring. Worked Example 1: Inventory list_droplets takes no arguments. What comes back is the table, already tallied: | Droplet | ID | Size | Status | Public IP | Region | | --- | --- | --- | --- | --- | --- | | debian-gpu-mi300x1-192gb-devcloud-atl1 | 601142018 | gpu-mi300x1-192gb-devcloud | active | 165.245.134.217 | atl1 | 📡 1 droplet(s) tagged gemma: 1 active. The last line is the point. The tool counts; the model quotes. One droplet is trivially countable by eye, but the same code path handles twenty, and a model asked to count twenty rows is a coin flip. Every tool in this server that returns rows also returns the tally - list_gpu_sizes sorts and names the cheapest, hardware_scan counts present and missing tools, gpu_status counts cards. droplet_status 601142018 drills in, and ends with the sentence that costs money: debian-gpu-mi300x1-192gb-devcloud-atl1 (601142018) - status: active - size: gpu-mi300x1-192gb-devcloud - 20 vCPU, 240 GB RAM, 720 GB disk - region: atl1 - image: debian-13-x64 - public IPv4: 165.245.134.217 - tags: gemma - created: 2026-09-16T18:09:45Z ✅ Reachable as root@165.245.134.217. Billing is running. Note what is not here: a hardcoded IP anywhere in the server. Every tool resolves the address per call. A rebuilt droplet gets a new one, and a cached address turns into an SSH timeout that reads like a dead GPU. Worked Example 2: One Round Trip For The Whole Box hardware_scan answers "what can this box actually run" in a single SSH round trip, instead of ten tool calls that each pay connection setup. It ships one shell script that prints >> -fenced sections, and the server parses them into a report. Abridged real output: ## GPU - /dev/kfd: present - amdgpu module: loaded - pci: 83:00.0 Processing accelerators [1200]: ... [Instinct MI300X VF] [1002:74b5] - gfx targets: gfx942 (1 GPU agent(s)) - device: AMD Instinct MI300X VF - vram: card0,205822885888,180717051904 ## Tools - present (9): rocm-smi, rocminfo, amd-smi, clinfo, docker, python3, pip3, git, tmux - missing (2): hipcc, podman - ROCM-SMI version: 2.2.0+unknown - ROCM-SMI-LIB version: 7.2.0 ## ROCm packages (8) libhsa-runtime64-1:amd64 6.1.2-3 librocm-smi64-1 6.1.2-1 rocm-smi 6.1.2-1 rocminfo 6.1.2-2 ## Python - torch 2.9.1+rocm6.4 hip 6.4.43484-123eb5128 avail True - ModuleNotFoundError: No module named 'vllm' That last pair of lines is the whole vLLM-on-ROCm situation in two lines: torch is installed and sees the GPU; vLLM is not installed and cannot be. There is no prebuilt vLLM wheel for ROCm - PyPI's are CUDA builds, AMD's manylinux index carries torch and triton but no vllm, and building from source needs hipcc plus rocblas , hipblaslt , miopen and rccl , of which Debian 13 ships exactly one (hipcc 5.7.1, and the scan shows even that is absent here). vLLM comes from a container on this box, and the scan is what proves it rather than asserts it. One parsing detail that cost a wrong number: rocminfo uses the key Name: for both the agent (Name: gfx942 ) and its ISA (Name: amdgcn-amd-amdhsa--gfx942:sramecc+:xnack- ). Matching on gfx alone counts one MI300X as two GPU agents. The scan only counts a bare gfx target. Worked Example 3: The Reboot That Is The Fix A freshly provisioned GPU droplet here does not work, and looks like a driver problem. lspci shows the card. /dev/dri/renderD128 exists. /dev/kfd does not, because amdgpu failed to bind the VF and unloaded itself. Nothing needs installing - Debian's in-tree amdgpu and the ROCm 6.1.2 userspace on the image are enough. reb
Comments
No comments yet. Start the discussion.