Logo Limitar ancho de banda

Descargar modelos LLM en local es algo que se hace de vez en cuando, pero cuando te bajas un modelo masivo de 122B parámetros como Sehyo/Qwen3.5-122B-A10B-NVFP4, la descarga copa toda la conexión y deja al resto de la casa sin red. El CLI de Hugging Face (huggingface-cli o hf) no tiene un flag --limit-rate, así que toca buscar alternativas. En este apunte explico dos formas de limitar el ancho de banda en Linux usando Docker (mi preferida) o Wondershaper a nivel de host.


El problema

Tengo una conexión de fibra de 1 Gbps en casa. Cuando lanzo una descarga masiva desde Hugging Face, el CLI intenta usar todo el ancho de banda disponible. Eso significa que mientras se descarga un modelo de más de 70GB, nadie más en casa puede navegar o trabajar con normalidad.

Lo lógico sería que huggingface-cli tuviese un flag tipo --limit-rate como tiene curl, así que la solución pasa por limitar el ancho de banda a nivel de red, fuera de la herramienta.

Método 1: Docker (recomendado)

La idea es levantar un contenedor Docker para la descarga y limitar su tráfico de red con tc (Traffic Control de Linux). De esta forma la conexión del host queda intacta y el resto de dispositivos no se ven afectados.

Por qué tc desde el host y no desde dentro del contenedor

El primer impulso es ejecutar tc dentro del propio contenedor, aplicando un Token Bucket Filter (TBF) en su eth0. El problema es que tc en modo root qdisc solo limita el tráfico de salida (egress). Las descargas son tráfico de entrada (ingress), así que el límite no les afecta. Probé también un ingress policer dentro del contenedor, pero TCP compensa los paquetes descartados y la limitación real es mínima.

La solución que funciona es aplicar tc desde el host en la interfaz veth que Docker crea para el contenedor. Desde la perspectiva del host, el tráfico que envía hacia el contenedor es egress, y ahí sí funciona el TBF perfectamente.

El script hf-download-limited.sh

He creado un script que automatiza todo el proceso:

hf-download-limited.sh
#!/usr/bin/env bash
# hf-download-limited.sh
#
# Descarga un modelo de Hugging Face con ancho de banda limitado.
# Usa Docker + tc aplicado desde el host en la interfaz veth del contenedor.
#
# Requiere: docker, sudo (para tc), iproute2 en el host

set -euo pipefail

CONTAINER="hf-dl-$$"
LATENCY="400ms"

# ─── Ayuda ────────────────────────────────────────────────────
usage() {
    cat <<'EOF'
Uso: hf-download-limited.sh -m MODEL -d DIR [-b BANDWIDTH]

Descarga un modelo de Hugging Face limitando el ancho de banda
mediante Docker + tc (Traffic Control).

Argumentos obligatorios:
  -m, --model MODEL       Modelo de Hugging Face (ej: usuario/modelo)
  -d, --dir   DIR         Directorio local donde guardar el modelo

Argumentos opcionales:
  -b, --bandwidth MBIT    Ancho de banda máximo en Mbps (default: 600)
  -t, --token TOKEN       Token de Hugging Face (o usa env HF_TOKEN)
  -h, --help              Muestra esta ayuda

Ejemplo:
  ./hf-download-limited.sh \
    -m "Sehyo/Qwen3.5-122B-A10B-NVFP4" \
    -d "/home/luis/tmp/Qwen3.5-122B-A10B-NVFP4" \
    -b 600
EOF
    exit "${1:-0}"
}

# ─── Parseo de argumentos ────────────────────────────────────
MODEL=""
LOCAL_DIR=""
RATE_MBIT=600

while [[ $# -gt 0 ]]; do
    case "$1" in
        -m|--model)     MODEL="$2";    shift 2 ;;
        -d|--dir)       LOCAL_DIR="$2"; shift 2 ;;
        -b|--bandwidth) RATE_MBIT="$2"; shift 2 ;;
        -t|--token)     HF_TOKEN="$2"; shift 2 ;;
        -h|--help)      usage 0 ;;
        *)              echo "Error: argumento desconocido '$1'"; usage 1 ;;
    esac
done

if [[ -z "$MODEL" || -z "$LOCAL_DIR" ]]; then
    echo "Error: -m MODEL y -d DIR son obligatorios."
    echo ""
    usage 1
fi

# ─── Calcular parámetros de tc ────────────────────────────────
# burst = 2% del rate, mínimo 1 mbit (suficiente para kernels con HZ=250..1000)
RATE="${RATE_MBIT}mbit"
BURST_MBIT=$(( RATE_MBIT / 50 ))
if (( BURST_MBIT < 1 )); then
    BURST_MBIT=1
fi
BURST="${BURST_MBIT}mbit"

# ─── Limpieza al salir (Ctrl+C, error, etc.) ─────────────────
cleanup() {
    echo ""
    echo "--- Limpiando ---"
    docker stop "$CONTAINER" 2>/dev/null || true
    echo "Contenedor eliminado."
}
trap cleanup EXIT

# 1. Lanzar contenedor en segundo plano
echo "--- Iniciando contenedor ---"
HF_TOKEN_ARGS=()
if [[ -n "${HF_TOKEN:-}" ]]; then
    HF_TOKEN_ARGS=(-e "HF_TOKEN=$HF_TOKEN")
fi

docker run -d --rm --name "$CONTAINER" \
    -v "$(dirname "$LOCAL_DIR"):$(dirname "$LOCAL_DIR")" \
    -e DEBIAN_FRONTEND=noninteractive \
    "${HF_TOKEN_ARGS[@]}" \
    python:3.10-slim \
    sleep infinity > /dev/null

# 2. Instalar huggingface_hub dentro del contenedor
echo "--- Instalando huggingface_hub ---"
docker exec "$CONTAINER" pip install -qqq -U huggingface_hub

# 3. Encontrar la interfaz veth en el host
IFLINK=$(docker exec "$CONTAINER" cat /sys/class/net/eth0/iflink)
VETH=$(ip -o link | awk -v idx="$IFLINK" '$1 == idx":" {print $2}' | sed 's/@.*//')

if [[ -z "$VETH" ]]; then
    VETH=$(ip -o link | grep "^${IFLINK}: " | awk '{print $2}' | sed 's/@.*//' | sed 's/://')
fi

if [[ -z "$VETH" ]]; then
    echo "ERROR: No se encontró la interfaz veth para iflink=$IFLINK"
    exit 1
fi

# 4. Aplicar tc (Token Bucket Filter) en la veth del host
echo "--- Aplicando tc en $VETH: rate=$RATE burst=$BURST ---"
sudo tc qdisc add dev "$VETH" root tbf \
    rate "$RATE" burst "$BURST" latency "$LATENCY"

# 5. Lanzar la descarga (-it para barra de progreso)
echo ""
echo "=== Descargando $MODEL ==="
echo "=== Destino: $LOCAL_DIR ==="
echo "=== Límite: ${RATE_MBIT} Mbps ==="
echo ""
docker exec -it -e "PYTHONWARNINGS=ignore::UserWarning" "${HF_TOKEN_ARGS[@]}" "$CONTAINER" \
    hf download "$MODEL" --local-dir "$LOCAL_DIR"

echo ""
echo "=== Descarga completada ==="

Ejecútalo pasándole el modelo y el directorio de destino:

chmod +x hf-download-limited.sh
./hf-download-limited.sh \
    -m "Sehyo/Qwen3.5-122B-A10B-NVFP4" \
    -d "/home/luis/tmp/Qwen3.5-122B-A10B-NVFP4" \
    -b 600

El flag -b permite ajustar el límite de ancho de banda en Mbps (por defecto 600). Si tienes un token de Hugging Face puedes pasarlo con -t para obtener descargas más rápidas y rate limits más altos.

Qué hace el script paso a paso

  1. Parsea los argumentos (-m modelo, -d directorio, -b bandwidth, -t token) y calcula automáticamente el burst del Token Bucket Filter a partir del bandwidth.
  2. Lanza un contenedor con sleep infinity en segundo plano e instala huggingface_hub dentro.
  3. Busca la interfaz veth en el host. Cada contenedor Docker tiene una interfaz eth0 interna que está conectada a una veth en el host. El script lee el iflink del contenedor y busca la interfaz correspondiente con ip -o link.
  4. Aplica tc desde el host en esa veth. Un Token Bucket Filter (TBF) limita el tráfico que el host envía al contenedor, es decir, las descargas.
  5. Ejecuta hf download dentro del contenedor con -it para ver la barra de progreso.
  6. Limpia al terminar. Un trap cleanup EXIT se encarga de parar el contenedor si la descarga termina, falla, o haces Ctrl+C.

La salida es limpia:

--- Iniciando contenedor ---
--- Instalando huggingface_hub ---
--- Aplicando tc en vethe4845c6: rate=600mbit burst=12mbit ---

=== Descargando Sehyo/Qwen3.5-122B-A10B-NVFP4 ===
=== Destino: /home/luis/tmp/Qwen3.5-122B-A10B-NVFP4 ===
=== Límite: 600 Mbps ===

Fetching 15 files:  47%|████▋     | 7/15 [02:30<03:00, ...]

Cuando la descarga termina, el contenedor desaparece y con él todas las restricciones de red. El sistema queda limpio sin necesidad de hacer nada más.

Limitar ancho de banda en las descargas de Huggingface
Limitar ancho de banda descargas de HF.

Método 2: Wondershaper en el host

Si prefieres no usar Docker, puedes limitar el ancho de banda directamente en la interfaz de red del host.

Ojo con el Wondershaper de apt

No uses el paquete wondershaper del repositorio de Ubuntu, está completamente obsoleto y usa módulos del kernel deprecados (cbq), lo que provoca errores RTNETLINK. Usa el fork moderno de GitHub.

Instalar el fork moderno

El fork mantenido de magnific0/wondershaper usa el módulo htb (Hierarchical Token Bucket), que es el estándar actual.

git clone https://github.com/magnific0/wondershaper.git
cd wondershaper

Aplicar el límite

Primero identifico mi interfaz de red:

ip route | grep default

En mi caso es enP7s7. El script usa Kilobits por segundo (Kbps). Para limitar la descarga a unos 75 MB/s (600.000 Kbps) y la subida a 40 MB/s (320.000 Kbps):

sudo ./wondershaper -a enP7s7 -d 600000 -u 320000

Descargar el modelo

Con la red del host limitada, lanzo la descarga:

hf download Sehyo/Qwen3.5-122B-A10B-NVFP4 \
    --local-dir /mnt/baul/models/Qwen3.5-122B-A10B-NVFP

Quitar el límite

No te olvides de este paso

Una vez termine la descarga es crucial eliminar las restricciones para que la conexión vuelva a funcionar a velocidad completa.
sudo ./wondershaper -c -a enP7s7

Conclusión

Si tienes Docker disponible, el método 1 es claramente mejor: limita solo el tráfico del contenedor, no necesitas tocar la red del host y se limpia solo al terminar. El método 2 con Wondershaper es una buena alternativa si no puedes o no quieres usar Docker, pero ten en cuenta que afecta a toda la máquina y tienes que acordarte de quitar el límite cuando termines.

Enlaces interesantes