ROCm – DEV Community


NOTA: Este documento está basado en Ubuntu.

¿Todavía no sabes que es ROCm?

ROCm es la alternativa libre a CUDA. Es cierto que CUDA le lleva ventaja, pero, las cosas empiezan a cambiar, sobre todo debido a la deriva del mercado… Nvidia no puede “cubrir” la demanda mundial y ello a dado una ventana de oportunidad a que AMD pueda convencer.

ROCm son drivers libres para las GPU, aunque es evidente que al estar bajo el paraguas de AMD es sobretodo para AMDGPUs: https://rocm.docs.amd.com/en/latest/index.html



I. Diagnosticar tu AMDGPU

Lo primero es instalar las herramientas de diagnóstico necesarias:

sudo apt update && sudo apt install -y lshw
Enter fullscreen mode

Exit fullscreen mode

Y tras ello, ejecutamos el diagnóstico:

sudo lshw -c video 2>/dev/null | grep "configuration"
Enter fullscreen mode

Exit fullscreen mode

Para la validación del resultado debes buscar la cadena driver=amdgpu:

configuration: driver=amdgpu latency=0: El sistema gráfico está correctamente anclado al driver nativo.

configuration: driver=llvmpipe ... (O salida vacía / driver=unsigned): el sistema no está usando aceleración hardware.



Instalar/restaurar drivers nativos

A) Eliminar completamente instalaciones propietarias anteriores:

sudo amdgpu-install --uninstall 2>/dev/null
sudo apt purge -y "amdgpu-*" "rocm-*" "hsa-*"
Enter fullscreen mode

Exit fullscreen mode

B) Forzar la reinstalación del stack gráfico nativo de Ubuntu:

sudo apt update
sudo apt install --reinstall xserver-xorg-video-amdgpu xserver-xorg-core libgl1-mesa-dri
sudo apt install --reinstall linux-image-generic linux-headers-generic
Enter fullscreen mode

Exit fullscreen mode

C) Regenerar imagen de arranque y reiniciar:

sudo update-initramfs -u
sudo reboot
Enter fullscreen mode

Exit fullscreen mode



II. Configuración

Vamos a realizar la configuración para una iGPU Radeon, más exactamente configuramos un equipo con Ryzen AI 9 y Radeon 780M/880M.

sudo apt update && sudo apt install -y build-essential git curl cmake

UBUNTU_CODENAME=$(lsb_release -sc)
BASE_URL="https://repo.radeon.com/amdgpu-install/7.1.1/ubuntu/$UBUNTU_CODENAME/"

INSTALLER_FILE=$(curl -s "$BASE_URL" | grep -o 'amdgpu-install_[0-9.-]*_all.deb' | sort -V | tail -n 1)

if [ -z "$INSTALLER_FILE" ]; then
    echo "Error Crítico: No se localizó el paquete instalador en $BASE_URL"
    exit 1
fi

wget -c "$BASE_URL$INSTALLER_FILE"

sudo apt install -y "./$INSTALLER_FILE"

# Desplegando pila ROCm y HIP (User-space).
# El flag --no-dkms evita la recompilación del módulo del kernel, previniendo conflictos gráficos.
sudo amdgpu-install --usecase=rocm,hip --no-dkms -y

sudo usermod -aG render,video $USER

# Inyección de variable de entorno para compatibilidad con RDNA 3.5
if ! grep -q "HSA_OVERRIDE_GFX_VERSION=11.0.0" ~/.bashrc; then
    echo 'export HSA_OVERRIDE_GFX_VERSION=11.0.0' >> ~/.bashrc
fi
Enter fullscreen mode

Exit fullscreen mode



III. Comprobaciones y tests

Primero revisemos que tenemos la GPU:

lspci | grep -i amd
Enter fullscreen mode

Exit fullscreen mode

Deberías ver tu gráfica en el listado.

Si tienes memoria compartida (shared memory) deberás habilitar el máximo que necesites en tu BIOS (UMA Frame Buffer Size o Integrated Graphics Memory).



Preparar un contenedor para las comprobaciones.

Crea un Dockerfile:

FROM rocm/pytorch:rocm6.1_ubuntu22.04_py3.10_pytorch_2.1.2

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    rocm-smi \
    git \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

ENV HSA_OVERRIDE_GFX_VERSION=11.0.0

WORKDIR /app

COPY test_gpu.py .

CMD ["/bin/bash", "-c", "python3 test_gpu.py; bash"]
Enter fullscreen mode

Exit fullscreen mode

… y para facilitar el uso, también creamos un compose.yml:

services:
  rocm-strix:
    build: .
    image: rocm-strix:v1
    container_name: rocm_strix_container
    restart: unless-stopped

    # Fundamental para el acceso al hardware
    devices:
      - "/dev/kfd:/dev/kfd"
      - "/dev/dri:/dev/dri"

    # Permisos de seguridad relajados para acceso a memoria GPU
    security_opt:
      - seccomp:unconfined
    cap_add:
      - SYS_PTRACE

    # Grupos necesarios para acceder a video/render
    group_add:
      - video
      - render

    # Vital para PyTorch y sus DataLoaders (memoria compartida)
    ipc: host

    environment:
      # CRÍTICO: Engaña a la librería para usar kernels de RX 7900 en la APU que estoy testeando
      - HSA_OVERRIDE_GFX_VERSION=11.0.0
      - ROCM_PATH=/opt/rocm
      # Opcional: Define la zona horaria
      - TZ=Europe/Madrid 

    volumes:
      - ./data:/app/data
      # --- Acceso al Hardware ---
      - /sys/class/power_supply:/sys/class/power_supply:ro
      - /sys/class/drm:/sys/class/drm:ro
      - /sys/class/hwmon:/sys/class/hwmon:ro
Enter fullscreen mode

Exit fullscreen mode

Y por último un pequeño script, test_gpu.py:

import torch
import time
import psutil
import platform
import os
import subprocess
import json

def format_bytes(size):
    power = 2**10
    n = 0
    power_labels = 0 : 'B', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'
    while size > power:
        size /= power
        n += 1
    return f"size:.2f power_labels[n]"

def get_cpu_info():
    try:
        command = "cat /proc/cpuinfo | grep 'model name' | uniq | cut -d : -f 2"
        cpu_name = subprocess.check_output(command, shell=True).decode().strip()
    except:
        cpu_name = platform.processor()

    logical = psutil.cpu_count(logical=True)
    physical = psutil.cpu_count(logical=False)
    freq = psutil.cpu_freq()
    freq_str = f"freq.max:.0f Mhz" if freq else "Unknown"
    return cpu_name, physical, logical, freq_str

def get_battery_status():
    if not hasattr(psutil, "sensors_battery"): return "No soportado"
    try:
        battery = psutil.sensors_battery()
        if battery is None: return "No detectada"
        plugged = "CONECTADO" if battery.power_plugged else "BATERÍA"
        return f"battery.percent% (plugged)"
    except: return "Error leyendo batería"

def get_gpu_sensors_rocm():
    data = 
    try:
        # Intentamos obtener JSON. Si falla, manejamos la excepción.
        raw_json = subprocess.check_output("rocm-smi -a --json", shell=True).decode()
        smi_data = json.loads(raw_json)
        card = list(smi_data.keys())[0]
        gpu_data = smi_data[card]

        # --- LÓGICA DE NOTAS EXPLICATIVAS ---

        # 1. TEMPERATURAS
        edge = gpu_data.get("Temperature (Sensor edge) (C)", "N/A")
        junc = gpu_data.get("Temperature (Sensor junction) (C)", "N/A")
        if junc == "N/A": junc = "N/A (APU: Solo reporta sensor Edge)"
        data['temp'] = f"edge°C (Edge) / junc"

        # 2. CONSUMO (POWER)
        pwr = gpu_data.get("Average Graphics Package Power (W)", "N/A")
        if pwr == "N/A": pwr = "N/A (APU: Energía compartida CPU/GPU)"
        data['power'] = pwr

        # 3. RELOJES (CLOCKS)
        sclk = gpu_data.get("SCLK", "N/A")
        mclk = gpu_data.get("MCLK", "N/A")
        if sclk == "N/A" or mclk == "N/A":
            # Si Docker bloquea uno, bloquea los dos. Ponemos un mensaje global.
            data['clocks'] = "N/A / N/A (Docker: Ambos sensores (SCLK/MCLK) bloqueados por aislamiento)"
        else:
            data['clocks'] = f"sclk / mclk"

        # 4. VENTILADOR
        fan = gpu_data.get("Fan Speed (%)", "N/A")
        if fan == "N/A": fan = "N/A (Laptop: Controlado por BIOS/EC)"
        data['fan'] = fan

        # 5. CARGA Y MEMORIA
        data['usage'] = gpu_data.get("GPU use (%)", "N/A")
        data['vram_used'] = gpu_data.get("VRAM Total Used Memory (B)", 0)
        data['gtt_used'] = gpu_data.get("GTT Total Used Memory (B)", 0)

    except Exception as e:
        data['error'] = f"Error interpretando sensores: str(e)"
        # Valores por defecto seguros para que no rompa el print
        data['temp'] = "Error"
        data['power'] = "Error"
        data['clocks'] = "Error"
        data['fan'] = "Error"
        data['usage'] = "?"
        data['vram_used'] = 0
        data['gtt_used'] = 0

    return data

def benchmark_latency(iters=1000):
    start = torch.cuda.Event(enable_timing=True)
    end = torch.cuda.Event(enable_timing=True)
    for _ in range(100): torch.cuda.synchronize() # Warmup
    start.record()
    for _ in range(iters): torch.cuda.synchronize()
    end.record()
    torch.cuda.synchronize()
    return (start.elapsed_time(end) / iters) * 1000

def benchmark_bandwidth(size_mb=512):
    elements = size_mb * 1024 * 1024 // 4 
    t_cpu = torch.randn(elements, dtype=torch.float32)
    torch.cuda.synchronize()

    # H2D
    start = time.time()
    t_gpu = t_cpu.to('cuda')
    torch.cuda.synchronize()
    h2d = (size_mb / 1024) / (time.time() - start)

    # D2H
    start = time.time()
    _ = t_gpu.to('cpu')
    torch.cuda.synchronize()
    d2h = (size_mb / 1024) / (time.time() - start)
    return h2d, d2h

def benchmark_compute(n, dtype):
    a = torch.randn(n, n, device="cuda", dtype=dtype)
    b = torch.randn(n, n, device="cuda", dtype=dtype)
    torch.mm(a, b)
    torch.cuda.synchronize()
    start = time.time()
    torch.mm(a, b)
    torch.cuda.synchronize()
    ops = 2 * (n ** 3)
    return (ops / (time.time() - start)) / 1e12

# ================= REPORT =================
print("\n" + "="*60)
print("          ROCm ULTIMATE DIAGNOSTIC TOOL     ")
print("="*60)

# [1] HOST
cpu, _, _, _ = get_cpu_info()
vm = psutil.virtual_memory()
print(f"\n[1] HOST & ENERGÍA")
print(f"CPU:             cpu")
print(f"RAM Sistema:     format_bytes(vm.available) libres / format_bytes(vm.total) Total")
print(f"Estado Energía:  get_battery_status()")

# [2] GPU
print(f"\n[2] SENSORES GPU (ROCm SMI)")
if torch.cuda.is_available():
    sensors = get_gpu_sensors_rocm()
    d = 0
    props = torch.cuda.get_device_properties(d)
    print(f"Dispositivo:     torch.cuda.get_device_name(d)")
    print(f"VRAM Total:      format_bytes(props.total_memory)")
    print(f"-"*40)
    print(f"Temp:            sensors.get('temp', '?')")
    print(f"Consumo:         sensors.get('power', '?')")
    print(f"Relojes:         sensors.get('clocks', '?')")
    print(f"Ventilador:      sensors.get('fan', '?')")
    print(f"-"*40)
    print(f"Carga GPU:       sensors.get('usage', '?')%")
    print(f"Memoria Usada:   VRAM: format_bytes(float(sensors.get('vram_used', 0))) | GTT: format_bytes(float(sensors.get('gtt_used', 0)))")
else:
    print("FATAL: GPU NO DETECTADA")

# [3] BENCHMARKS
print(f"\n[3] PRUEBAS DE ESTRÉS & RENDIMIENTO")
print(f"Latencia Kernel: benchmark_latency():.2f µs")
try:
    h2d, d2h = benchmark_bandwidth(512)
    print(f"Ancho Banda:     Escritura: h2d:.2f GB/s | Lectura: d2h:.2f GB/s")
except: print("Error memoria")

print("\n--- Potencia de Cómputo ---")
for size in [4096, 8192]:
    tf32 = benchmark_compute(size, torch.float32)
    print(f"Matriz sizexsize: FP32: tf32:.2f TFLOPS", end="")
    try:
        tf16 = benchmark_compute(size, torch.float16)
        print(f" | FP16: tf16:.2f TFLOPS (Boost: tf16/tf32:.2fx)")
    except: print("")

print("\n" + "-"*60 + "\n")
Enter fullscreen mode

Exit fullscreen mode

Y ahora, para probarlo:

docker compose build
docker compose up
Enter fullscreen mode

Exit fullscreen mode

Si algo falla y necesitas recrear el contenedor, debes eliminarlo previamente con:

docker rm -f rocm_strix_container
Enter fullscreen mode

Exit fullscreen mode

Por último, puedes ver el estado de la GPU con:

rocm-smi
Enter fullscreen mode

Exit fullscreen mode

y del sistema con:

htop
Enter fullscreen mode

Exit fullscreen mode



Source link