La primera vez que intenté rastrear el tiempo de una ejecución local de LLM, lo perdí.

Un sábado por la mañana lancé una cuantización Q4 de 3 horas de un modelo de 7B. Arranqué el temporizador en Toggl. Me fui. Volví a la hora de comer a un Mac dormido, un trabajo sin terminar y un temporizador de Toggl que había marcado alegremente 3 horas y 14 minutos contra el proyecto «ml-experiments».

Los datos eran mentira. El trabajo no ocurrió. El Mac se durmió a los 20 minutos; la cuantización se detuvo; mi gráfica de uso de CPU durante esas 3 horas era una línea plana. Pero a Toggl le dio igual — le había dicho que estaba trabajando, así que me contó como trabajando.

Ese es el problema estructural de rastrear el tiempo del trabajo local de IA/ML. Lo que usa tu ordenador no eres tú. Lo que tu rastreador de tiempo mide es lo que tú estás haciendo, pero lo que en realidad quieres saber es si el modelo está avanzando. Esas son señales distintas.

A estas alturas he pasado suficiente tiempo ejecutando fine-tunes, evaluaciones y generaciones de contexto largo en local sobre Apple Silicon como para que el flujo de trabajo se haya asentado en unos cuantos patrones. Esto es lo que aprendí sobre rastrearlo correctamente — y lo que Timex captura que otras herramientas pasan por alto.

Cómo se ve realmente «el modelo está corriendo» para un Mac

Cuando Ollama, LM Studio, MLX o llama.cpp están trabajando, la app en primer plano de tu Mac sigue siendo aquello en lo que hiciste clic por última vez. Normalmente es el terminal desde el que lanzaste el trabajo, o la ventana de chat de LM Studio, o la pestaña del navegador apuntando a localhost:11434. El trabajo en sí es un proceso en segundo plano. En lo que respecta al SO, sigues en el terminal, aunque te hayas ido hace una hora.

Si entonces cierras la tapa o te alejas, pasan dos cosas:

  1. macOS te trata como inactivo (sin entradas durante más de 90 segundos).
  2. macOS trata el sistema como potencialmente dormible, según la configuración de energía y si la tapa está cerrada.

Lo primero hace que tu rastreador de tiempo deje de contar (correctamente — no estás trabajando). Lo segundo hace que tu modelo deje de correr (incorrectamente — sí estaba trabajando).

El arreglo para lo segundo es de lo que escribí en Ejecuta Ollama en modo clamshell. Mantén una power assertion, el Mac se queda despierto, el modelo sigue generando. Timex empaqueta esto en el interruptor de modo de tapa cerrada para que no tengas que acordarte del flag caffeinate.

El arreglo para lo primero es el tema de este post.

Qué quieres medir realmente en el trabajo de ML

Hay tres duraciones distintas que podrían importarte en una ejecución de LLM/ML:

  1. Tiempo de reloj — cuándo empezó la ejecución, cuándo terminó, total transcurrido.
  2. Tiempo de cómputo — cuándo el modelo estaba haciendo trabajo de forma activa (GPU/CPU > 0), sin esperarte a ti ni atascado.
  3. Tu tiempo de atención — cuándo estabas de verdad mirando/supervisando la ejecución.

Para un trabajo de cuantización, te importan sobre todo el #1 y el #2 (si fue eficiente, si el Mac se mantuvo despierto todo el rato). Para una sesión interactiva de evaluación, te importa sobre todo el #3 (cuánto de tu tiempo consumió este experimento). Para decidir si seguir usando un modelo en producción, te importan los tres.

La mayoría de los rastreadores de tiempo de propósito general los mezclan. Toggl te dirá que pasaste 3 horas en «ml-experiments» si arrancas un temporizador y te vas. RescueTime te dirá que no estabas en el ordenador. Ambos son técnicamente correctos e individualmente inútiles.

Lo que quieres es un registro de cuándo empezó la ejecución, cuándo terminó y qué estabas haciendo durante esa ventana. Timex captura los tres en un solo log.

Cómo ve Timex una ejecución local de LLM

Esta es la forma real de los datos de una cuantización Q4 que ejecuté la semana pasada. La ejecución duró 2 horas 41 minutos de reloj. Estuve al teclado 22 minutos de ellos.

-- The 3-hour window of the quantization run
SELECT
  ts,
  app_name,
  window_title,
  is_idle
FROM activity_samples
WHERE ts BETWEEN strftime('%s', '2026-05-31 09:14:00')
             AND strftime('%s', '2026-05-31 11:55:00')
ORDER BY ts;

La forma aproximada de las filas (muestreo cada 15 minutos para que se lea mejor):

09:14   Ghostty   llama.cpp quantize-stats ...   0  (active, just started)
09:29   Ghostty   llama.cpp quantize-stats ...   0  (active, watching output)
09:44   Ghostty   llama.cpp quantize-stats ...   1  (idle, walked away)
09:59   loginwindow   —                          1  (locked screen)
10:14   loginwindow   —                          1  (still locked)
... [no rows from 10:30 to 11:30 — display slept, sampler paused]
11:32   Ghostty   llama.cpp quantize-stats ...   0  (returned, woke up Mac)
11:47   Ghostty   llama.cpp quantize-stats ...   0  (watching final phases)
11:55   Ghostty   llama.cpp quantize-done        0  (job finished)

Unas cuantas cosas a notar.

El título de la ventana es la misma cadena todo el tiempo (llama.cpp quantize-stats ...). Eso es importante — significa que luego puedo hacer grep al archivo SQLite y encontrar cada ejecución de cuantización por el título de ventana. El terminal conserva el nombre del proceso en primer plano en la barra de título; esa es la miga de pan que necesitaba.

Las filas desaparecen durante la ventana de sueño de la pantalla (de 10:30 a 11:30). Eso es correcto. El muestreador no debería afirmar actividad cuando no se está registrando nada. El hueco en la franja es honesto — muestra que el Mac estaba dormido — y el hueco también es lo que quiero investigar después (¿fue un fallo del modo de tapa cerrada, o se me olvidó activarlo?).

Las filas de pantalla bloqueada intermedias (loginwindow) son oro diagnóstico. Me dicen que la pantalla se bloqueó a las 09:59, que es cuando salí de la habitación. Si me hubiera preocupado por si la ejecución estaba realmente corriendo mientras no estaba, la ausencia de filas de crash o de filas de otra app tranquiliza.

«¿Funcionó el modo de tapa cerrada esta vez?»

Esta es la pregunta que me hago más a menudo sobre mis propias ejecuciones de ML. ¿Se mantuvo el Mac despierto todo el rato? ¿O falló el clamshell en algún momento?

Una consulta SQL da la respuesta:

-- For a known run window, find any gap > 5 minutes (= sleep event)
WITH sample_gaps AS (
  SELECT
    ts AS t,
    ts - LAG(ts) OVER (ORDER BY ts) AS gap_seconds
  FROM activity_samples
  WHERE ts BETWEEN strftime('%s', '2026-05-31 09:14:00')
               AND strftime('%s', '2026-05-31 11:55:00')
)
SELECT datetime(t, 'unixepoch', 'localtime') AS gap_started, gap_seconds
FROM sample_gaps
WHERE gap_seconds > 300
ORDER BY t;

Si el modo de tapa cerrada funcionó a la perfección, esta consulta devuelve cero filas. Si el Mac se durmió en algún momento, obtienes una fila por cada ventana de sueño con la hora de inicio y la duración.

Para la ejecución de arriba, la respuesta fue: un hueco, 53 minutos. El Mac durmió de 10:30 a 11:23. El modo de tapa cerrada no estaba activado. Se me había olvidado activarlo. La cuantización se pausó una hora, se despertó cuando volví y moví el ratón, y se reanudó.

La ejecución total tardó 1 hora y 53 minutos más de lo que debería. Este es el tipo de pregunta que no puedes hacer sin un rastreador que registre huecos honestos.

Anotar ejecuciones sin salir del terminal

El mecanismo más simple de «para qué era esta ejecución» es el título de la ventana del terminal. La mayoría de terminales te dejan poner el título con un escape ANSI:

echo -ne "\033]0;quant-7b-q4 — eval-set-3\007"
ollama run mistral:7b-instruct-q4 < eval-set-3.jsonl > out.jsonl

Eso pone el título de la ventana en «quant-7b-q4 — eval-set-3» mientras dura el comando. Timex captura ese título en cada fila de muestreo. Más tarde, puedes hacer grep de eval-set-3 y sacar la ventana exacta de ese experimento.

El truco se generaliza:

# Wrap any long-running ML command with a title tag
run_titled() {
  local title="$1"
  shift
  echo -ne "\033]0;${title}\007"
  "$@"
  echo -ne "\033]0;${SHELL##*/}\007"
}

run_titled "mlx-finetune-llama3-r1-lr2e5" python finetune.py --lr 2e-5

Ahora cada fila de muestreo dentro de esa ventana aparece como mlx-finetune-llama3-r1-lr2e5 en el título de la ventana, y puedes sacar las horas exactas de inicio/fin y el patrón de inactividad de SQLite semanas después. Es la forma más barata de seguimiento de experimentos imaginable, y sobrevive sin ningún servicio en la nube.

Una semana real de ML

Así se veía en los datos el trabajo de la semana pasada titulado mlx-* y llama-*:

SELECT
  window_title,
  date(MIN(ts), 'unixepoch', 'localtime') AS started,
  ROUND((MAX(ts) - MIN(ts)) / 60.0, 1) AS wall_minutes,
  ROUND(SUM(CASE WHEN is_idle = 0 THEN 1 ELSE 0 END) / 60.0, 1) AS active_minutes
FROM activity_samples
WHERE window_title LIKE 'mlx-%' OR window_title LIKE 'llama-%'
GROUP BY window_title
ORDER BY MIN(ts);

Salida:

mlx-finetune-llama3-r1-lr2e5         2026-05-26   189.2  wall   34.1  active
llama-cpp-quant-7b-q4-eval3          2026-05-31   161.8  wall   22.4  active
llama-cpp-quant-7b-q8-eval3          2026-06-01   178.5  wall   18.9  active
mlx-eval-mtbench-r1-vs-base          2026-06-02    87.0  wall   71.2  active

La evaluación (última fila) fueron 87 minutos de reloj y 71 minutos activos — fue una sesión interactiva de puntuación en la que miraba las salidas en tiempo real. Los dos trabajos de cuantización fueron 18–22 minutos de mi atención por 2,5–3 horas de reloj — ejecuciones largas sin supervisión.

Esa proporción importa. La columna de activo-frente-a-reloj me dice qué experimentos costaron mi tiempo y cuáles costaron mi electricidad. De ahí salen decisiones distintas.

A qué renuncias frente a un rastreador de experimentos de ML «de verdad»

Esto es lo que Timex es y no es para el flujo de trabajo de ML.

Un rastreador de experimentos dedicado — Weights & Biases, MLflow, Aim — rastrea el modelo. Curvas de pérdida, hiperparámetros, métricas, artefactos. Nada de eso vive en Timex. Si lo necesitas, ejecuta W&B en paralelo; los dos son complementarios.

Lo que Timex rastrea es el lado humano de la ejecución. Cuándo la empezaste. Cuándo te fuiste. Cuándo terminó. Qué estabas haciendo durante ella. Si el Mac se mantuvo despierto. Si fuiste a mirar Twitter mientras corría. Nada de eso vive en W&B.

Si haces ML en serio en tu Mac y no estás registrando ninguno de los datos del «lado humano» — la mayoría de la gente no lo hace — Timex es la forma más barata de empezar. Los datos están en un archivo SQLite que ya entiendes. Las consultas son cortas. La gráfica es la franja en la vista de Hoy.

Y en el lado práctico: el modo de tapa cerrada es la función que hace viables las ejecuciones nocturnas en un portátil. Si alguna vez te has despertado a un entrenamiento al 30% que debería haber estado al 100%, ya sabes por qué.

Lanza tu próximo trabajo nocturno con el modo clamshell de Timex activado. El log de SQLite te dirá por la mañana si funcionó.