DEV Community

Cover image for La misma molécula, otra GPU, otro puesto: cómo una tarjeta barata cambió un ranking sin dar ningún error
김민식/학생
김민식/학생

Posted on

La misma molécula, otra GPU, otro puesto: cómo una tarjeta barata cambió un ranking sin dar ningún error

TL;DR

Al mover la puntuación del Open Discovery Challenge (ODC) a trabajos de Hugging Face, quisimos elegir la tarjeta gráfica más barata que diera el mismo resultado. Re-puntuamos las mismas moléculas en tres tarjetas, con la escritura del ranking bloqueada. Dos tarjetas (L40S y A100) reprodujeron los valores registrados hasta el tercer decimal. La más barata (L4) no dio ningún error, pero puntuó más bajo: 87,329 en lugar de 88,106 en la temporada 2 y 80,504 en lugar de 81,427 en la temporada 4. Si insertamos esos valores en los rankings públicos de hoy, la molécula que va primera en la temporada 2 caería al puesto 13, y la del puesto 29 en la temporada 4 caería al 66. Y el ahorro habría sido de unos 0,007 dólares por envío en la temporada 2 y de 0,013 en la 4. La lección: cuando cambias el hardware de un evaluador, el chequeo que importa no es "¿arranca?", sino "¿reproduce lo ya puntuado?". Al final hay una compuerta de 20 líneas en Python que lo automatiza.

Una aclaración de entrada: VIDRAFT organiza el ODC, y esta cuenta es de VIDRAFT. Es un incidente nuestro, contado con los números que medimos. Las puntuaciones del ODC son predicciones computacionales, no mediciones de laboratorio.

¿Qué pasó exactamente al cambiar de tarjeta?

El ODC puntúa moléculas (SMILES) con un verificador automático que combina seis ejes: actividad, unión, selectividad, ADMET, novedad y síntesis. Los pesos son públicos y los lees en /api/seasons. En la temporada 2 (tuberculosis) son 20, 25, 25, 15, 10 y 5 puntos.

Para dejar de depender de una sola máquina, probamos tres tarjetas de Hugging Face. Tomamos dos casos que ya estaban puntuados en el ranking (uno de la temporada 2 y otro de la temporada 4), los volvimos a puntuar en cada tarjeta y comparamos con el valor registrado. La escritura al ranking estaba bloqueada durante la prueba, así que ninguna de estas mediciones tocó el ranking real.

Tarjeta Memoria (nominal) USD/hora Temporada 2: puntaje Temporada 4: puntaje
Valor registrado en el ranking n/d n/d 88,106 81,427
1x Nvidia L4 24 GB 0,80 87,329 80,504
1x Nvidia L40S 48 GB 1,80 88,106 81,427
Nvidia A100 large 80 GB 2,50 88,106 81,427

Los precios y las memorias son los de la documentación de hardware de Hugging Face a fecha de hoy. L40S y A100 coinciden con el registro en todos los dígitos. La L4 se desvía 0,777 puntos en un caso y 0,923 en el otro.

Puntaje de la misma molécula por tarjeta

Una nota sobre el gráfico: el eje vertical empieza en 78, no en 0, para que se vea una diferencia de menos de un punto. Léelo con los números de la tabla, no con la altura de las barras.

¿Por qué una tarjeta más pequeña no falló, pero sí cambió el resultado?

Esta es la parte que nos preocupa más. Con poca memoria de vídeo esperas un error de memoria agotada: el trabajo muere, lo ves en el registro, cambias de tarjeta. Aquí no pasó. El trabajo terminó, escribió un puntaje con formato válido y ese puntaje era distinto.

Lo que sí sabemos, medido eje por eje en la L4:

  • El eje de actividad dio el mismo valor en las tres tarjetas (17,561 de 20 puntos). El modelo se cargó bien y los datos de entrada eran los mismos.
  • La unión y la selectividad no llegaron al tope en la L4 (24,725 frente a 25,0), y los ejes que dependen de ellas bajaron proporcionalmente: ADMET, novedad y síntesis.
  • Es decir, lo que cambió fue el cálculo estructural (el acoplamiento molecular), no la carga del modelo ni la lectura de datos.

Lo que no sabemos: la causa interna. Sospechamos que una configuración con menos memoria toma un camino numérico distinto sin avisar, pero es una hipótesis, no un hallazgo. No hemos aislado qué operación cambia. Por eso la decisión que tomamos no depende de la causa: depende de que la tarjeta no reproduce el registro.

El riesgo práctico es este. Un fallo ruidoso se corrige. Uno silencioso se acumula: cada envío puntuado en la tarjeta equivocada queda guardado como si fuera comparable con los demás.

¿Cuánto habría movido eso el ranking?

Pregunta justa: ¿0,78 puntos es mucho? Depende de lo apretado que esté el ranking. Lo medimos contra los datos públicos de hoy (7 de noviembre de 2026), leyendo /api/leaderboard de cada temporada. Este es el código:

import json, urllib.request

BASE = "https://final-bench-open-discovery-challenge.hf.space"

def puntajes(temporada: int) -> list[float]:
    req = urllib.request.Request(f"{BASE}/api/leaderboard?season={temporada}",
                                 headers={"User-Agent": "Mozilla/5.0"})
    entradas = json.load(urllib.request.urlopen(req, timeout=90))["entries"]
    return [e["total"] for e in entradas if e.get("total") is not None]

def puesto(total: float, todos: list[float]) -> int:
    return sum(1 for x in todos if x > total) + 1

# (temporada, valor registrado, valor medido en la tarjeta barata)
for temporada, registrado, barato in [(2, 88.106, 87.329), (4, 81.427, 80.504)]:
    todos = puntajes(temporada)
    print(f"T{temporada}: {len(todos)} filas | registrado {registrado} -> puesto {puesto(registrado, todos)}"
          f" | con la tarjeta barata {barato} -> puesto {puesto(barato, todos)}")
Enter fullscreen mode Exit fullscreen mode

Salida de la ejecución de hoy:

T2: 7942 filas | registrado 88.106 -> puesto 1 | con la tarjeta barata 87.329 -> puesto 13
T4: 4853 filas | registrado 81.427 -> puesto 29 | con la tarjeta barata 80.504 -> puesto 66
Enter fullscreen mode Exit fullscreen mode
Temporada Filas del ranking Valor registrado Puesto Con la tarjeta barata Puesto
2 (tuberculosis) 7.942 88,106 1 87,329 13
4 (analgesia no opioide) 4.853 81,427 29 80,504 66

En la temporada 2, entre 87,329 y 88,106 hay otras 12 entradas. En la temporada 4 hay 37. Dicho de otra forma: un desplazamiento menor a un punto, en un ranking con miles de filas, es la diferencia entre el primer puesto y el puesto trece. Y si un ranking reparte un premio (la temporada 2 ofrece 2.000 dólares), el puesto depende de la tarjeta que corrió el trabajo. Una puntuación comparable tiene que ser independiente de la máquina que la calculó.

Para que no haya malentendidos: este cálculo es hipotético. Insertamos un valor medido en la tarjeta barata dentro del ranking actual para ver dónde caería. Ningún envío real fue puntuado en la L4 ni cambió de puesto.

¿Cuánto ahorraba la tarjeta barata?

Aquí viene el dato que cierra el argumento. La L4 cuesta 0,80 dólares la hora frente a 1,80 de la L40S, pero tarda más en cada envío. Medimos segundos por envío y lo convertimos a dólares (segundos por precio por hora, dividido entre 3.600):

Tarjeta Temporada 2: segundos por envío USD por envío Temporada 4: segundos por envío USD por envío
L4 724,9 0,161 207,1 0,046
L40S 336,2 0,168 117,3 0,059
A100 large 326,1 0,227 134,5 0,093

En la temporada 2 la L4 es 2,2 veces más lenta que la L40S, y por eso su ventaja de precio por hora casi desaparece: 0,161 frente a 0,168 dólares por envío, una diferencia de 0,007. La A100 es un 35 % más cara por envío que la L40S sin ganar velocidad (en la temporada 4 incluso es más lenta). Las pruebas usaron tres repeticiones de acoplamiento en la temporada 2 y una en la temporada 4, así que no compares los segundos entre temporadas.

Costo por envío puntuado en cada tarjeta

Si hubiéramos elegido por precio por hora, habríamos escogido la L4 y ahorrado entre 0,007 y 0,013 dólares por envío a cambio de resultados que no coinciden con el registro. La decisión que tomamos fue fijar la L40S para puntuar y vetar la L4 para esa tarea.

¿Cómo compruebas una tarjeta antes de aprobarla?

El método es corto y sirve para cualquier evaluador, no solo para el nuestro:

  1. Elige casos ya puntuados cuyo valor registrado conozcas. Usa casos fijos, no "el primero de la cola". Nuestro primer intento tomaba el primer pendiente y falló por completo (IndexError) cuando la cola quedó vacía; además, cada tarjeta recibía una molécula distinta y la comparación no valía nada.
  2. Vuelve a puntuarlos en la tarjeta candidata con la escritura al ranking bloqueada.
  3. Compara con el registro hasta el decimal que te importe. "Parecido" no es una respuesta.
  4. Guarda la tabla completa (tarjeta, caso, valor registrado, valor medido, diferencia). Escribir solo "pasó" no sirve a quien revise después.
  5. Aprueba la tarjeta solo si todos los casos coinciden, y que el lanzador rechace cualquier tarjeta que no esté en la lista aprobada antes de arrancar el trabajo.

Esta es la compuerta con nuestros valores reales:

# Compuerta: una tarjeta entra en la lista aprobada solo si re-puntúa casos
# ya puntuados y reproduce el valor registrado hasta el tercer decimal.
TOLERANCIA = 0.0005  # medio milésimo: igualdad al tercer decimal

def compuerta(tarjeta: str, pares: list[tuple[float, float]]) -> bool:
    ok = True
    for registrado, medido in pares:
        dif = abs(registrado - medido)
        pasa = dif < TOLERANCIA
        print(f"{tarjeta:11s} registrado={registrado:7.3f} medido={medido:7.3f} dif={dif:6.3f} {'PASA' if pasa else 'FALLA'}")
        ok &= pasa
    return ok

mediciones = {
    "l4x1":       [(88.106, 87.329), (81.427, 80.504)],
    "l40sx1":     [(88.106, 88.106), (81.427, 81.427)],
    "a100-large": [(88.106, 88.106), (81.427, 81.427)],
}
aprobadas = {t for t, p in mediciones.items() if compuerta(t, p)}
print("Tarjetas aprobadas:", sorted(aprobadas))
Enter fullscreen mode Exit fullscreen mode
l4x1        registrado= 88.106 medido= 87.329 dif= 0.777 FALLA
l4x1        registrado= 81.427 medido= 80.504 dif= 0.923 FALLA
l40sx1      registrado= 88.106 medido= 88.106 dif= 0.000 PASA
l40sx1      registrado= 81.427 medido= 81.427 dif= 0.000 PASA
a100-large  registrado= 88.106 medido= 88.106 dif= 0.000 PASA
a100-large  registrado= 81.427 medido= 81.427 dif= 0.000 PASA
Tarjetas aprobadas: ['a100-large', 'l40sx1']
Enter fullscreen mode Exit fullscreen mode

En producción esta lista no se edita a mano: una tarjeta nueva entra solo con un cambio que enlaza la tabla de reproducción que la respalda. Cuesta unas decenas de centavos por tarjeta, mucho menos que descubrir semanas después que parte del ranking se calculó con otra vara.

¿Qué no demuestra este experimento?

Conviene ser exactos con los límites:

  • Son pocos casos. Dos moléculas (una por temporada) y tres tarjetas. Es suficiente para vetar la L4, no para afirmar que L40S y A100 son idénticas en todo el espacio de moléculas.
  • El tope oculta dispersión. En esos dos casos la unión y la selectividad estaban en su valor máximo con L40S y A100. Cuando un eje llega al tope, dos tarjetas pueden dar el mismo número aunque su ruido interno sea distinto. La L4 se delató justo porque no alcanzaba el tope. Un chequeo más fuerte necesita también casos que queden por debajo del tope; es trabajo pendiente nuestro.
  • No conocemos la causa. Sabemos qué ejes se movieron, no qué operación lo provoca.
  • Es un resultado de este verificador. No afirmamos nada sobre la L4 en otras cargas de trabajo.
  • El cambio de puesto es hipotético, como explicamos arriba.

Preguntas frecuentes

¿Es la L4 una mala tarjeta? No. Es una tarjeta correcta para muchas cargas. Lo que medimos es que, para este verificador y con esta configuración, no reproduce los valores ya registrados. La conclusión vale para esta combinación, no para la tarjeta en general.

¿Por qué no dio un error de memoria? No lo sabemos con certeza. Lo observable es que el trabajo terminó con un puntaje de formato válido y el eje de actividad idéntico, mientras los ejes estructurales bajaron. Por eso la única defensa fiable es comparar números con un registro previo.

¿Cuántos decimales hay que exigir? Los que necesite tu decisión. Aquí exigimos el tercero porque los rankings se separan por décimas, y L40S y A100 lo cumplieron. Si tu métrica tiene ruido legítimo (muestreo aleatorio, por ejemplo), fija primero la semilla o mide el ruido propio y exige una banda en lugar de igualdad.

¿Esto afecta a los envíos ya puntuados? No. La prueba se hizo con la escritura bloqueada, así que ninguna medición de la L4 llegó al ranking y los valores registrados no cambiaron. Lo que evitamos fue empezar a puntuar envíos nuevos en la L4, que habría mezclado dos varas en el mismo ranking.

¿Sirve para otros evaluadores, como los de benchmarks de LLM? El principio sí: cambia el hardware o la versión de una librería numérica, vuelve a evaluar casos antiguos y compara con el registro antes de aprobar el cambio. Los detalles dependen de cada evaluador.

¿Dónde puedo ver los límites y pesos del reto? En el endpoint público /api/seasons del Space del ODC. Los leí en vivo para este artículo.

Further reading

Top comments (0)