pulseml 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulseml
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A live ML training debugger - GUI or CLI, any backend (NumPy, PyTorch, TensorFlow, CuPy, JAX).
5
5
  Author-email: Yash Patel <codeyash09@gmail.com>
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pulseml"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "A live ML training debugger - GUI or CLI, any backend (NumPy, PyTorch, TensorFlow, CuPy, JAX)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -3541,6 +3541,24 @@ def _install_cli_excepthook(cli):
3541
3541
  previous_hook = sys.excepthook
3542
3542
 
3543
3543
  def _hook(exc_type, exc_value, exc_tb):
3544
+ # Disarm the global line/call tracer immediately. Once the script
3545
+ # has crashed, nothing below should still be traced -- and if we
3546
+ # leave `sys.settrace` pointed at `cli_tracer`, it stays the
3547
+ # process-wide trace function into interpreter shutdown, where it
3548
+ # gets invoked on unrelated __del__ calls (e.g. litellm's
3549
+ # AsyncHTTPHandler) after module globals have already been cleared
3550
+ # to None. That produces a second, uncatchable "Exception ignored
3551
+ # in ..." failure from *inside* the tracer, separate from (and
3552
+ # after) whatever we report here.
3553
+ sys.settrace(None)
3554
+ try:
3555
+ frame = exc_tb.tb_frame if exc_tb else None
3556
+ while frame is not None:
3557
+ frame.f_trace = None
3558
+ frame = frame.f_back
3559
+ except Exception:
3560
+ pass
3561
+
3544
3562
  previous_hook(exc_type, exc_value, exc_tb)
3545
3563
  if issubclass(exc_type, KeyboardInterrupt):
3546
3564
  return
@@ -3634,14 +3652,29 @@ def _start_cli_tracker(
3634
3652
  cli.extra_files = dict(extra_files or {})
3635
3653
  cli.pending_startup_error = startup_error
3636
3654
  cli.print_banner()
3637
- _install_cli_excepthook(cli)
3655
+ # Agent/provider setup used to be deferred until cli_tracer saw its
3656
+ # first 'line' event with a trackable/resolved local in scope -- fine
3657
+ # for a training loop, but it meant any crash before that point (e.g.
3658
+ # failing during data loading, before the loop even starts) left
3659
+ # cli.agent_provider unset, so the crash hook below had no agent to
3660
+ # hand the traceback to and could only print "no AI agent is
3661
+ # configured". discover_variables() already falls back to the
3662
+ # statically-discovered `cli.discovered` names (populated from AST
3663
+ # parsing at auto_track()-call time, before any user code runs) when
3664
+ # there are no live locals yet, so setup doesn't actually need to wait
3665
+ # for a live frame -- run it eagerly, before the excepthook can matter.
3666
+ cli.interactive_setup()
3638
3667
 
3639
3668
  if startup_error:
3640
3669
  print("[Pulse] The function passed to auto_track() raised an exception during its dry run:")
3641
3670
  print(startup_error)
3642
- print("[Pulse] Set up an AI agent below and Pulse will offer to diagnose/fix it before continuing.\n")
3671
+ if cli.agent_provider:
3672
+ print("[Pulse] Pulse will offer to diagnose/fix it (via the agent set up above) before continuing.\n")
3673
+ else:
3674
+ print("[Pulse] No agent configured (see summary above) -- run /agent to set one up before continuing.\n")
3675
+
3676
+ _install_cli_excepthook(cli)
3643
3677
 
3644
- setup_done = {"value": False}
3645
3678
  last_logged = {"t": 0.0}
3646
3679
 
3647
3680
  # ------------------------------------------------------------------
@@ -3672,7 +3705,7 @@ def _start_cli_tracker(
3672
3705
  # next window. Between windows, the training loop runs at native,
3673
3706
  # untraced speed.
3674
3707
  _CAPTURE_SPAN = min(0.05, throttle_interval / 4 if throttle_interval > 0 else 0.05)
3675
- window = {"open": True, "closes_at": 0.0} # start open so setup can run immediately
3708
+ window = {"open": True, "closes_at": 0.0} # start open so the first snapshot can run immediately
3676
3709
 
3677
3710
  def _close_window():
3678
3711
  window["open"] = False
@@ -3694,6 +3727,11 @@ def _start_cli_tracker(
3694
3727
  if window["open"]:
3695
3728
  _close_window()
3696
3729
 
3730
+ # Setup now runs eagerly, before tracing even starts (see above), so
3731
+ # the ticker can start right away too -- no need to wait for the
3732
+ # tracer to see a live frame first.
3733
+ threading.Thread(target=_ticker, daemon=True).start()
3734
+
3697
3735
  def cli_tracer(frame, event, arg):
3698
3736
  filename = frame.f_code.co_filename
3699
3737
  if _is_library_frame(filename):
@@ -3740,21 +3778,6 @@ def _start_cli_tracker(
3740
3778
  ):
3741
3779
  cli.var_states[name] = "track"
3742
3780
 
3743
- if not setup_done["value"]:
3744
- has_resolved_shape = any(shape is not None for shape in cli.discovered.values())
3745
- has_trackable_local = any(
3746
- not n.startswith("__") and is_trackable(v) for n, v in local_vars.items()
3747
- )
3748
- if has_resolved_shape or has_trackable_local:
3749
- cli.watch_locals = local_vars
3750
- cli.interactive_setup()
3751
- setup_done["value"] = True
3752
- # Setup is interactive/blocking; once it's done, start the
3753
- # background ticker that opens/closes future windows. (Kept
3754
- # off until now so setup itself isn't racing a window close.)
3755
- threading.Thread(target=_ticker, daemon=True).start()
3756
- return cli_tracer
3757
-
3758
3781
  cli.watch_locals = local_vars
3759
3782
 
3760
3783
  now = time.time()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulseml
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A live ML training debugger - GUI or CLI, any backend (NumPy, PyTorch, TensorFlow, CuPy, JAX).
5
5
  Author-email: Yash Patel <codeyash09@gmail.com>
6
6
  License: Proprietary
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes