जब मैंने पहली बार किसी local LLM run का time track करने की कोशिश की, मैं उसे खो बैठा।

शनिवार सुबह मैंने एक 7B model का 3 घंटे वाला Q4 quantization शुरू किया। Toggl में timer चालू किया। उठकर चला गया। दोपहर के खाने पर लौटा तो एक सोया हुआ Mac, एक अधूरा job, और Toggl का timer जो ख़ुशी-ख़ुशी «ml-experiments» project पर 3 घंटे 14 मिनट गिन चुका था।

Data झूठ था। काम हुआ ही नहीं। Mac 20 मिनट बाद सो गया; quantization रुक गई; उन 3 घंटों के लिए मेरा CPU usage graph एक सीधी सपाट लकीर थी। पर Toggl को कोई फ़र्क़ नहीं पड़ा — मैंने उसे बता दिया था कि मैं काम कर रहा हूँ, तो उसने मुझे काम करता हुआ गिन लिया।

Local AI/ML काम का time track करने में यही structural problem है। तुम्हारा computer जो चीज़ इस्तेमाल कर रही है, वह तुम नहीं हो। तुम्हारा time tracker जो माप रहा है वह है तुम क्या कर रहे हो, पर तुम असल में जानना चाहते हो model आगे बढ़ रहा है या नहीं। ये अलग-अलग signals हैं।

अब तक मैं Apple Silicon पर locally इतने fine-tunes, evaluations और long-context generations चला चुका हूँ कि workflow कुछ patterns में जम गया है। इसे सही तरीक़े से track करने के बारे में मैंने यही सीखा — और Timex वह क्या capture करता है जो दूसरे tools चूक जाते हैं।

Mac को «model चल रहा है» असल में कैसा दिखता है

जब Ollama या LM Studio या MLX या llama.cpp काम कर रहा होता है, तुम्हारे Mac का foreground app वही रहता है जिस पर तुमने आख़िरी बार click किया था। आमतौर पर वह वह terminal होता है जहाँ से तुमने job launch किया, या LM Studio की chat window, या localhost:11434 पर ताकता browser tab। Job ख़ुद एक background process है। OS की नज़र में, तुम अब भी terminal पर हो, भले ही तुम एक घंटा पहले उठकर चले गए हो।

अगर फिर तुम ढक्कन बंद कर दो या दूर हट जाओ, दो चीज़ें होती हैं:

  1. macOS तुम्हें idle मान लेता है (90 सेकंड से ज़्यादा कोई input नहीं)।
  2. macOS system को संभावित रूप से सोने लायक मानता है — power settings पर और ढक्कन बंद है या नहीं इस पर निर्भर करते हुए।

पहली चीज़ तुम्हारे time tracker को गिनना बंद करा देती है (सही — तुम काम नहीं कर रहे)। दूसरी चीज़ तुम्हारे model को चलना बंद करा देती है (ग़लत — वह तो काम कर रहा था)।

दूसरी चीज़ का हल वही है जिसके बारे में मैंने Ollama को clamshell mode में चलाओ में लिखा था। एक power assertion पकड़े रखो, Mac जागता रहता है, model generate करता रहता है। Timex इसे lid-down mode toggle में बाँध देता है ताकि तुम्हें caffeinate flag याद रखने की ज़रूरत न पड़े।

पहली चीज़ का हल इस post का विषय है।

ML काम के लिए तुम असल में क्या मापना चाहते हो

किसी LLM/ML run के लिए तीन अलग-अलग durations हो सकते हैं जिनकी तुम्हें परवाह हो:

  1. Wall-clock time — run कब शुरू हुआ, कब ख़त्म हुआ, कुल कितना बीता।
  2. Compute time — model कब सक्रिय रूप से काम कर रहा था (GPU/CPU > 0), तुम्हारा इंतज़ार करते हुए या अटके हुए नहीं।
  3. तुम्हारा attention time — तुम कब असल में run को देख/देखरेख कर रहे थे।

Quantization job के लिए तुम्हें ज़्यादातर #1 और #2 की परवाह होती है (यह efficient था या नहीं, Mac पूरे समय जागता रहा या नहीं)। Interactive eval session के लिए तुम्हें ज़्यादातर #3 की परवाह होती है (इस experiment ने तुम्हारा कितना समय खाया)। किसी model को production में रखना है या नहीं यह तय करने के लिए, तुम्हें तीनों की परवाह होती है।

ज़्यादातर general-purpose time trackers इन्हें घालमेल कर देते हैं। Toggl तुम्हें बताएगा कि तुमने «ml-experiments» पर 3 घंटे बिताए अगर तुम timer चालू करके चले जाओ। RescueTime बताएगा कि तुम computer पर थे ही नहीं। दोनों technically सही हैं और अलग-अलग बेकार हैं।

तुम्हें चाहिए एक record कि run कब शुरू हुआ, कब ख़त्म हुआ, और उस window के दौरान तुम क्या कर रहे थे। Timex तीनों को एक ही log में capture कर लेता है।

Timex किसी local LLM run को कैसे देखता है

यह रही पिछले हफ़्ते चलाई एक Q4 quantization के data की असली शक्ल। Run में wall-clock 2 घंटे 41 मिनट लगे। उसमें से 22 मिनट मैं keyboard पर था।

-- 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;

Rows की मोटी-मोटी शक्ल (पढ़ने की सहूलियत के लिए हर 15 मिनट पर sampling):

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)

कुछ चीज़ें ग़ौर करने लायक।

Window title पूरे समय वही string रहता है (llama.cpp quantize-stats ...)। यह अहम है — इसका मतलब है कि मैं बाद में SQLite file को grep करके हर quantization run को window title से ढूँढ सकता हूँ। Terminal title bar में foreground process का नाम सँभालकर रखता है; यही वह सुराग़ था जो मुझे चाहिए था।

Display sleep window (10:30 से 11:30) के दौरान rows ग़ायब हो जाती हैं। यह सही है। जब कुछ record ही नहीं हो रहा तो sampler को activity का दावा नहीं करना चाहिए। Strip में यह gap ईमानदार है — यह दिखाता है कि Mac सो रहा था — और यही gap वह चीज़ भी है जिसे मैं बाद में जाँचना चाहूँगा (क्या वह clamshell-mode का fail था, या मैं उसे चालू करना भूल गया?)।

बीच में आई lock screen rows (loginwindow) diagnostic सोना हैं। वे मुझे बताती हैं कि screen 09:59 पर lock हुई, जब मैं कमरे से निकला। अगर मुझे चिंता होती कि run सच में चल रहा था या नहीं जब मैं दूर था, तो crash rows या किसी और app की rows का न होना तसल्ली देता है।

«क्या इस बार lid-down mode ने काम किया?»

अपने ML runs के बारे में यह सवाल मैं सबसे ज़्यादा पूछता हूँ। क्या Mac पूरे समय जागता रहा? या clamshell किसी मोड़ पर fail हो गया?

एक SQL query जवाब देती है:

-- 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;

अगर lid-down mode ने एकदम सही काम किया, तो यह query शून्य rows लौटाती है। अगर Mac किसी भी मोड़ पर सो गया, तो हर sleep window के लिए शुरू होने का समय और अवधि के साथ एक row मिलती है।

ऊपर वाले run के लिए जवाब था: एक gap, 53 मिनट। Mac 10:30 से 11:23 तक सोया। Lid-down mode चालू नहीं था। मैं उसे toggle करना भूल गया था। Quantization एक घंटे रुकी रही, तब जागी जब मैं लौटा और mouse हिलाया, और फिर चल पड़ी।

पूरे run में जितना लगना चाहिए था उससे 1 घंटा 53 मिनट ज़्यादा लगे। यह उस किस्म का सवाल है जो तुम बिना ऐसे tracker के नहीं पूछ सकते जो ईमानदार gaps record करता हो।

Terminal छोड़े बिना runs को annotate करना

«यह run किसलिए था» का सबसे आसान mechanism है terminal की window title। ज़्यादातर terminals तुम्हें ANSI escape से title set करने देते हैं:

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

यह command के चलने भर के लिए window title को «quant-7b-q4 — eval-set-3» कर देता है। Timex हर sample row में उस title को capture करता है। बाद में, तुम eval-set-3 को grep करके उस experiment की ठीक-ठीक window निकाल सकते हो।

यह तरकीब आम तौर पर लागू होती है:

# 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

अब उस window के अंदर की हर sample row window title में mlx-finetune-llama3-r1-lr2e5 के रूप में दिखती है, और तुम हफ़्तों बाद SQLite से ठीक-ठीक start/end times और idle pattern निकाल सकते हो। यह कल्पना में आ सकने वाला सबसे सस्ता experiment tracking का रूप है, और यह बिना किसी cloud service के ज़िंदा रहता है।

एक असली ML हफ़्ता

पिछले हफ़्ते का mlx-* और llama-* titled काम data में ऐसा दिखता था:

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);

Output:

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

Eval (आख़िरी row) wall-clock 87 मिनट और active 71 मिनट था — वह एक interactive scoring session थी जहाँ मैं outputs को real time में देख रहा था। दो quantization jobs मेरे 18–22 मिनट के attention में 2.5–3 घंटे wall-clock के थे — लंबे बिना-निगरानी वाले runs।

वह ratio मायने रखता है। Active-बनाम-wall वाला column मुझे बताता है कौन-से experiments ने मेरा समय खाया और कौन-से ने मेरी बिजली। इनसे अलग-अलग फ़ैसले निकलते हैं।

किसी «असली» ML experiment tracker के मुक़ाबले तुम क्या छोड़ते हो

ML workflow के लिए Timex क्या है और क्या नहीं, यह रहा।

एक dedicated experiment tracker — Weights & Biases, MLflow, Aimmodel को track करता है। Loss curves, hyperparameters, metrics, artifacts। इनमें से कुछ भी Timex में नहीं रहता। अगर तुम्हें वह चाहिए, तो W&B साथ में चलाओ; दोनों एक-दूसरे के पूरक हैं।

Timex जो track करता है वह है run का इंसानी पहलू। तुमने इसे कब शुरू किया। तुम कब उठकर गए। यह कब ख़त्म हुआ। इसके दौरान तुम क्या कर रहे थे। Mac जागता रहा या नहीं। यह चलते वक़्त तुम Twitter देखने गए या नहीं। इनमें से कुछ भी W&B में नहीं रहता।

अगर तुम अपने Mac पर serious ML करते हो और «इंसानी पहलू» का कोई data log नहीं कर रहे — ज़्यादातर लोग नहीं करते — तो Timex शुरू करने का सबसे सस्ता तरीक़ा है। Data एक SQLite file में है जिसे तुम पहले से समझते हो। Queries छोटी हैं। Graph वह strip है जो Today view में दिखता है।

और practical पहलू पर: lid-down mode वह feature है जो laptop पर रातभर के runs को मुमकिन बनाता है। अगर तुम कभी 30% हो चुकी training पर जागे हो जो 100% हो जानी चाहिए थी, तो तुम पहले से जानते हो क्यों।

अपना अगला रातभर का job Timex के clamshell mode को चालू करके चलाओ। सुबह SQLite log तुम्हें बता देगा कि वह चला या नहीं।