From b9651695d6d8f58484f339646d7a0409eef5f5ba Mon Sep 17 00:00:00 2001 From: Asher Feldman <59994+asher@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:18:55 -0700 Subject: [PATCH 1/3] fix: log the silent swallows that hide stuck state and lost accounting --- gmlx/assistant/serve.py | 6 +++++- gmlx/serve/governor.py | 13 +++++++++---- gmlx/serve/lifecycle.py | 5 +++-- gmlx/serve/patches/apc.py | 16 ++++++++++++++-- gmlx/serve/patches/routes.py | 27 ++++++++++++++------------- gmlx/serve/residency.py | 9 ++++++--- 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/gmlx/assistant/serve.py b/gmlx/assistant/serve.py index 5bfa126a..274cb4d3 100644 --- a/gmlx/assistant/serve.py +++ b/gmlx/assistant/serve.py @@ -45,6 +45,7 @@ import importlib import inspect import json +import logging import os import sys import threading @@ -54,6 +55,8 @@ from .brain import AssistantBrain, ToolRegistry from gmlx.talk.client import TalkClientError, stream_chat +_log = logging.getLogger(__name__) + _ASSISTANT_FLAG = "_kq_gguf_assistant_serve" _MAX_CONCURRENT_TURNS = 4 # immediate 429 above this, per process _DEFAULT_MAX_TOKENS = 4096 @@ -100,7 +103,8 @@ def close_memories(self) -> None: try: m.close() except Exception: - pass + _log.warning("assistant memory store close failed; queued " + "facts may be lost", exc_info=True) def try_acquire(self) -> bool: with self._lock: diff --git a/gmlx/serve/governor.py b/gmlx/serve/governor.py index e7b1a007..12fb10ed 100644 --- a/gmlx/serve/governor.py +++ b/gmlx/serve/governor.py @@ -452,13 +452,15 @@ def _disarm_throttle(gen, st: _GovState) -> None: try: mx.set_memory_limit(st.saved_mem_limit) except Exception: - pass + _log.warning("[governor] memory limit restore failed; the " + "throttle limit stays", exc_info=True) st.saved_mem_limit = None if st.saved_cache_limit is not None: try: mx.set_cache_limit(st.saved_cache_limit) except Exception: - pass + _log.warning("[governor] cache limit restore failed; the " + "throttle limit stays", exc_info=True) st.saved_cache_limit = None @@ -501,7 +503,8 @@ def _restore_demand_rungs(gen, st: _GovState) -> None: set_governor_width_clamp(0) except Exception: - pass + _log.warning("[governor] speculative width clamp release failed", + exc_info=True) st.width_clamped = False st.rung = 0 st.rung_rate_before = None @@ -524,7 +527,9 @@ def _registered_bytes() -> float: try: total += float(bytes_fn() or 0) except Exception: - pass + _log.warning("[governor] registered cache %r bytes() failed; " + "dropping registrant", name, exc_info=True) + _REG.pop(name, None) return total diff --git a/gmlx/serve/lifecycle.py b/gmlx/serve/lifecycle.py index e46aa663..4328a3f1 100644 --- a/gmlx/serve/lifecycle.py +++ b/gmlx/serve/lifecycle.py @@ -1322,8 +1322,9 @@ def service_uninstall(host: str, port) -> int: settings = _mb.load_menubar_settings() settings["autostart"] = None _mb.save_menubar_settings(settings) - except Exception: - pass + except Exception as exc: + print(f" note: could not clear the menu-bar autostart record " + f"({exc})", file=sys.stderr) print(f"uninstalled launchd agent {MENUBAR_AGENT_LABEL} " "(menu bar login item; a running server is left up - " "`gmlx stop` for that)") diff --git a/gmlx/serve/patches/apc.py b/gmlx/serve/patches/apc.py index 7b239d3c..089f3e39 100644 --- a/gmlx/serve/patches/apc.py +++ b/gmlx/serve/patches/apc.py @@ -5,8 +5,20 @@ from __future__ import annotations import importlib +import logging import weakref +_log = logging.getLogger(__name__) +_CAPTURE_WARNED: set[str] = set() + + +def _warn_capture_once(site: str) -> None: + if site in _CAPTURE_WARNED: + return + _CAPTURE_WARNED.add(site) + _log.warning("retirement %s capture failed; next-turn retirement keys " + "are off for such requests", site, exc_info=True) + def install_apc_lone_harvest() -> None: """Teach mlx-vlm's APC block harvest to read a lone request's plain ``KVCache``. @@ -118,7 +130,7 @@ def apply_chat_template(processor, config, prompt, *a, **kw): or kw.get("video")), }) except Exception: - pass + _warn_capture_once("render") return out apply_chat_template._kq_retire_capture = True @@ -160,7 +172,7 @@ def preprocess(text, _ref=ref): retire_key.register_ids(prompt, row, preprocess) except Exception: - pass + _warn_capture_once("ids") return raw _preprocess_request._kq_retire_capture = True diff --git a/gmlx/serve/patches/routes.py b/gmlx/serve/patches/routes.py index 5160bcaa..035c592c 100644 --- a/gmlx/serve/patches/routes.py +++ b/gmlx/serve/patches/routes.py @@ -276,14 +276,14 @@ def snapshot(): k: st[k] for k in ("budget_bytes", "resident_bytes") if k in st} except Exception: - pass + _log.debug("metrics snapshot: residency unavailable", exc_info=True) # gmlx wires APC per residency entry. Any resident manager # counts as enabled. try: if pool.apc_managers() and isinstance(base.get("apc"), dict): base["apc"]["enabled"] = True except Exception: - pass + _log.debug("metrics snapshot: apc unavailable", exc_info=True) try: import mlx.core as mx @@ -311,25 +311,25 @@ def snapshot(): int(getattr(f, "_lookups", 0)) for f in arenas) base["memory"] = mem except Exception: - pass + _log.debug("metrics snapshot: memory unavailable", exc_info=True) try: from ..admit_gate import admit_stats base["admission"] = admit_stats() except Exception: - pass + _log.debug("metrics snapshot: admission unavailable", exc_info=True) try: from gmlx.cache.fresh_gate import fresh_stats base["freshness"] = fresh_stats() except Exception: - pass + _log.debug("metrics snapshot: freshness unavailable", exc_info=True) try: from ..governor import governor_stats base["governor"] = governor_stats() except Exception: - pass + _log.debug("metrics snapshot: governor unavailable", exc_info=True) try: from ..capacity import get_table @@ -337,31 +337,31 @@ def snapshot(): if cap is not None: base["capacity"] = cap except Exception: - pass + _log.debug("metrics snapshot: capacity unavailable", exc_info=True) try: from ..queue_cap import queue_cap_stats base["queue"] = queue_cap_stats() except Exception: - pass + _log.debug("metrics snapshot: queue unavailable", exc_info=True) try: from ..queue_cap import concurrency_stats base["concurrency"] = concurrency_stats() except Exception: - pass + _log.debug("metrics snapshot: concurrency unavailable", exc_info=True) try: from ..estimate import rates_view base["rates"] = rates_view() except Exception: - pass + _log.debug("metrics snapshot: rates unavailable", exc_info=True) try: from ..live_requests import live_requests_view base["requests"] = live_requests_view() except Exception: - pass + _log.debug("metrics snapshot: requests unavailable", exc_info=True) return base snapshot.__dict__[_PATCH_FLAG] = True @@ -1004,8 +1004,9 @@ def _run(): continue try: _warm_and_release(mid) - except Exception: - pass + except Exception as exc: + print(f"[server] preload: {mid} failed, loads lazily on " + f"first request ({type(exc).__name__}: {exc})") thread = threading.Thread(target=_run, name="gmlx-preload-warm", daemon=True) thread.start() diff --git a/gmlx/serve/residency.py b/gmlx/serve/residency.py index 6dcf5de0..11b0bcbe 100644 --- a/gmlx/serve/residency.py +++ b/gmlx/serve/residency.py @@ -1126,7 +1126,8 @@ def _teardown(self, entry: _Entry): try: close() except Exception: - pass + _log.warning("teardown: %s close failed", + type(owner).__name__, exc_info=True) # A larger-than-RAM model leaves a page-cache remnant that taxes # whoever faults next (gmlx.stream.pagecache). Process exit sweeps it for # CLI runs; a long-lived server sweeps at eviction, before the next @@ -1161,7 +1162,8 @@ def _teardown(self, entry: _Entry): kq.residency_erase(a) kq.residency_commit() except Exception: - pass + _log.warning("teardown: residency erase failed; the wired " + "accounting may be stale", exc_info=True) m._kq_resident_arrays = None entry.model_cache = {} entry.response_generator = None @@ -1242,7 +1244,8 @@ def _streaming_footprint(model_path, file_bytes: int, env=None) -> int: if box.ring_fits: ring = int(model.ring_bytes) except Exception: - pass + _log.warning("streaming footprint: plan for %s failed; arena and " + "ring priced at zero", model_path, exc_info=True) return every + max(0, arena or 0) + ring From d4c286233023147c2415494c2ae52027f3a1be4f Mon Sep 17 00:00:00 2001 From: Asher Feldman <59994+asher@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:18:55 -0700 Subject: [PATCH 2/3] chore: S110 ratchet, a reason on every silent swallow --- CONTRIBUTING.md | 5 +++++ bench/serve-bench.py | 8 +++---- gmlx/_exitfix.py | 2 +- gmlx/assistant/brain.py | 2 +- gmlx/cache/apc_pooling.py | 2 +- gmlx/cache/kvarn_apc.py | 4 ++-- gmlx/cache/snapshot.py | 12 +++++----- gmlx/commands/completion.py | 2 +- gmlx/commands/menubar.py | 34 ++++++++++++++--------------- gmlx/eval_guard.py | 6 ++--- gmlx/gen/media_spans.py | 2 +- gmlx/gen/thinking_budget.py | 4 ++-- gmlx/load/discovery.py | 4 ++-- gmlx/load/remote.py | 4 ++-- gmlx/models/deepseek_v4/model.py | 2 +- gmlx/serve/bridge_vlm.py | 12 +++++----- gmlx/serve/decode_batch.py | 2 +- gmlx/serve/estimate.py | 22 +++++++++---------- gmlx/serve/governor.py | 4 ++-- gmlx/serve/lifecycle.py | 4 ++-- gmlx/serve/live_requests.py | 14 ++++++------ gmlx/serve/patches/chat_behavior.py | 4 ++-- gmlx/serve/patches/completions.py | 8 ++++--- gmlx/serve/patches/observability.py | 10 ++++----- gmlx/serve/patches/request_flow.py | 12 +++++----- gmlx/serve/patches/routes.py | 6 ++--- gmlx/serve/queue_cap.py | 8 +++---- gmlx/serve/residency.py | 18 +++++++-------- gmlx/serve/tts.py | 2 +- gmlx/spec/engine.py | 8 +++---- gmlx/spec/speculative.py | 16 +++++++------- gmlx/stream/budget.py | 2 +- gmlx/stream/decode_feeder.py | 10 ++++----- gmlx/stream/installs.py | 2 +- gmlx/stream/pin_weights.py | 4 ++-- gmlx/stream/prefetch.py | 2 +- gmlx/stream/prefill_feeder.py | 4 ++-- gmlx/talk/hotkey.py | 4 ++-- gmlx/tui/chat.py | 2 +- pyproject.toml | 10 +++++++-- 40 files changed, 147 insertions(+), 136 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17356803..37508c43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,6 +62,11 @@ positive comes from upstream typing (a stub that omits the attribute, a scalar union, a field added to an upstream dataclass), with a comment saying which; keep the count under twenty repo-wide. +ruff's S110 rule flags a `try`/`except Exception: pass`. Either log the +failure, or say where the error goes on the except line: +`except Exception: # noqa: S110 - `. A bare marker without a +reason is not accepted in review. Tests are exempt. + ## Things to know before you patch - The serving stack is stock mlx-vlm with late-bound patches over its diff --git a/bench/serve-bench.py b/bench/serve-bench.py index 3828dec1..114faadd 100755 --- a/bench/serve-bench.py +++ b/bench/serve-bench.py @@ -836,7 +836,7 @@ def wait_ready(runtime, proc, timeout): if r.status_code == 200: data = (r.json() or {}).get("data") or [] return data[0]["id"] if data else "model" - except Exception: + except Exception: # noqa: S110 - not ready yet; the poll loop retries pass time.sleep(1.0) raise TimeoutError(f"{runtime} server not ready within {timeout}s") @@ -972,7 +972,7 @@ def die_temp(): parts = out.split() # "DIE 62.4 MAX 66.1 (n=42)" if len(parts) >= 4 and parts[0] == "DIE": return float(parts[1]), float(parts[3]) - except Exception: + except Exception: # noqa: S110 - no die-temp probe -> None pass return None @@ -991,7 +991,7 @@ def thermal_snapshot(): for line in out.splitlines(): if "CPU_Speed_Limit" in line: return line.strip() - except Exception: + except Exception: # noqa: S110 - no thermal readout -> None pass return None @@ -2055,7 +2055,7 @@ def main(): ["git", "-C", os.path.dirname(args.ds4_bin), "rev-parse", "--short", "HEAD"], capture_output=True, text=True, timeout=5).stdout.strip() or None - except Exception: + except Exception: # noqa: S110 - unknown ds4 commit -> None pass def _meta(partial): diff --git a/gmlx/_exitfix.py b/gmlx/_exitfix.py index 8d57aab8..a25f0ed2 100644 --- a/gmlx/_exitfix.py +++ b/gmlx/_exitfix.py @@ -93,7 +93,7 @@ def _guard() -> None: try: sys.stdout.flush() sys.stderr.flush() - except Exception: + except Exception: # noqa: S110 - stdio may already be closed at exit pass os._exit(_code) diff --git a/gmlx/assistant/brain.py b/gmlx/assistant/brain.py index 83395447..cb75fb47 100644 --- a/gmlx/assistant/brain.py +++ b/gmlx/assistant/brain.py @@ -277,7 +277,7 @@ def turn(self, user_text: str) -> Iterator[BrainEvent]: if self.memory is not None and completed: try: self.memory.remember(user_text, "".join(spoken)) - except Exception: # noqa: BLE001 - best-effort + except Exception: # noqa: BLE001, S110 - best-effort pass if completed: if timings: diff --git a/gmlx/cache/apc_pooling.py b/gmlx/cache/apc_pooling.py index e6658d9e..de5075cb 100644 --- a/gmlx/cache/apc_pooling.py +++ b/gmlx/cache/apc_pooling.py @@ -401,7 +401,7 @@ def model_has_pools(model) -> bool: break try: model._kq_has_pools = found - except Exception: + except Exception: # noqa: S110 - memo stamp; recomputed when the model forbids ad-hoc attrs pass return found diff --git a/gmlx/cache/kvarn_apc.py b/gmlx/cache/kvarn_apc.py index 39454ad0..edb8328d 100644 --- a/gmlx/cache/kvarn_apc.py +++ b/gmlx/cache/kvarn_apc.py @@ -56,7 +56,7 @@ def stamp_model(model) -> None: if obj is not None: try: setattr(obj, _MODE_STAMP, True) - except Exception: + except Exception: # noqa: S110 - stamp only; an unsettable object reads as unstamped pass @@ -93,7 +93,7 @@ def kvarn_model_converts(model) -> bool: val = False try: setattr(model, _CONVERTS_ATTR, val) - except Exception: + except Exception: # noqa: S110 - memo stamp; recomputed when the model forbids ad-hoc attrs pass return val diff --git a/gmlx/cache/snapshot.py b/gmlx/cache/snapshot.py index 5dc4754b..f74b3bc7 100644 --- a/gmlx/cache/snapshot.py +++ b/gmlx/cache/snapshot.py @@ -1575,13 +1575,13 @@ def _once(): # release must not leak the other block set out of the manager. try: _ckpt_decline(manager, "exception") - except Exception: - pass # decline is telemetry only + except Exception: # noqa: S110 - decline is telemetry only + pass for blks in (main_blocks, bounded_blocks): try: manager.release(blks) - except Exception: - pass # best-effort release on the failure path + except Exception: # noqa: S110 - best-effort release on the failure path + pass _log.warning("APC ckpt store failed; continuing", exc_info=True) return 0 @@ -2165,8 +2165,8 @@ def _ckpt_disk_lookup(manager, ids, *, extra_hash, min_prefix_tokens, for blks in (blocks, wblocks): try: manager.release(blks) - except Exception: - pass # best-effort release on the failure path + except Exception: # noqa: S110 - best-effort release on the failure path + pass _log.warning("APC ckpt disk lookup failed; continuing", exc_info=True) return None, 0 diff --git a/gmlx/commands/completion.py b/gmlx/commands/completion.py index 4db18d73..c6fe8037 100644 --- a/gmlx/commands/completion.py +++ b/gmlx/commands/completion.py @@ -344,7 +344,7 @@ def cmd_complete(argv: list[str]) -> int: try: for line in _complete(list(argv)): print(line) - except Exception: # noqa: BLE001 - never let completion fail loudly + except Exception: # noqa: BLE001, S110 - never let completion fail loudly pass return 0 diff --git a/gmlx/commands/menubar.py b/gmlx/commands/menubar.py index 12251a70..4b7d5e63 100644 --- a/gmlx/commands/menubar.py +++ b/gmlx/commands/menubar.py @@ -97,8 +97,8 @@ def ptt_modifier_from_config(run: dict | None) -> str: "push_to_talk_modifier", "globe") if mod in PUSH_TO_TALK_MODIFIERS: return mod - except Exception: - pass # unreadable config -> default modifier + except Exception: # noqa: S110 - unreadable config -> default modifier + pass return "globe" @@ -160,8 +160,8 @@ def _existing_default_config() -> str | None: for p in default_config_paths(): if p.exists(): return str(p) - except Exception: - pass # probe only; no readable default config -> no path + except Exception: # noqa: S110 - probe only; no readable default config -> no path + pass return None @@ -275,8 +275,8 @@ def _autostart_server_once() -> None: lifecycle.launch_detached(argv, host=host, port=port, config_abspath=auto.get("config_abspath"), api_key_set=bool(auto.get("api_key_set"))) - except Exception: - pass # best-effort autostart replay; the menu stays usable without it + except Exception: # noqa: S110 - best-effort autostart replay; the menu stays usable without it + pass def build_menu_model(snapshot: dict, run: dict | None, @@ -774,8 +774,8 @@ def work(): _post_json(_server_root(self.url) + "/unload", {"model": model_id}, api_key=self._resolve_key(), timeout=8.0) - except Exception: - pass # fire-and-forget; the next poll shows the real state + except Exception: # noqa: S110 - fire-and-forget; the next poll shows the real state + pass self._spawn(work) def _reload(self) -> None: @@ -783,8 +783,8 @@ def work(): try: _post_json(_server_root(self.url) + "/v1/reload", {}, api_key=self._resolve_key(), timeout=15.0) - except Exception: - pass # fire-and-forget; the next poll shows the real state + except Exception: # noqa: S110 - fire-and-forget; the next poll shows the real state + pass self._spawn(work) def _copy_url(self) -> None: @@ -939,9 +939,8 @@ def _voice_notification(self, msg: str) -> None: def post(): try: self._rumps.notification("gmlx voice", None, msg) - except Exception: - pass # notification center unavailable (bare interpreter, - # no Info.plist) - the transcript line still has it + except Exception: # noqa: S110 - no notification center (bare interpreter); the transcript line still has it + pass try: from PyObjCTools import AppHelper AppHelper.callAfter(post) @@ -1086,8 +1085,8 @@ def _repreflight_hotkey(self) -> None: if hotkey.preflight(): self._hotkey_error = None self._arm_hotkey_async() - except Exception: - pass # advisory probe; the next tick retries + except Exception: # noqa: S110 - advisory probe; the next tick retries + pass def _set_hotkey(self, choice: str) -> None: """Menu callback (main run-loop thread): switch the hotkey on or @@ -1212,9 +1211,8 @@ def _post_down_notification(self, snap: dict, msg = down_message(snap.get("url") or self.url, run, pid_dead) try: self._rumps.notification("gmlx", None, msg) - except Exception: - pass # notification center unavailable (bare interpreter, no - # Info.plist) - the glyph flip still shows the state + except Exception: # noqa: S110 - no notification center (bare interpreter); the glyph flip still shows the state + pass def _make_unload(self, model_id: str): return lambda _sender: self._unload(model_id) diff --git a/gmlx/eval_guard.py b/gmlx/eval_guard.py index b63d7ed8..488093bc 100644 --- a/gmlx/eval_guard.py +++ b/gmlx/eval_guard.py @@ -125,12 +125,12 @@ def _harvest() -> None: register_stream(mx.default_stream(mx.Device(mx.cpu))) try: register_stream(mx.default_stream(mx.Device(mx.gpu))) - except Exception: - pass # no Metal device (CI) + except Exception: # noqa: S110 - no Metal device (CI) + pass try: from mlx_lm.generate import generation_stream register_stream(generation_stream) - except Exception: + except Exception: # noqa: S110 - optional: mlx_lm's generation stream is harvested when present pass diff --git a/gmlx/gen/media_spans.py b/gmlx/gen/media_spans.py index c689ddd0..4bb04b10 100644 --- a/gmlx/gen/media_spans.py +++ b/gmlx/gen/media_spans.py @@ -116,7 +116,7 @@ def _absolute_offset(batch) -> int: if callable(real): try: return int(real(0)) - except Exception: + except Exception: # noqa: S110 - odd batch without the row helper; the column count is the offset pass return int(getattr(batch, "_processed_prompt_columns", 0)) diff --git a/gmlx/gen/thinking_budget.py b/gmlx/gen/thinking_budget.py index a2c98c24..f4ad80e1 100644 --- a/gmlx/gen/thinking_budget.py +++ b/gmlx/gen/thinking_budget.py @@ -435,7 +435,7 @@ def think_tokenizer_for(processor): wrapped = tok try: processor._gmlx_think_tokenizer = wrapped - except Exception: # noqa: BLE001 - unsettable processor: skip the cache + except Exception: # noqa: BLE001, S110 - unsettable processor: skip the cache pass return wrapped @@ -754,7 +754,7 @@ def _quiet_kernel_status() -> None: attrs = termios.tcgetattr(fd) attrs[3] |= nokerninfo termios.tcsetattr(fd, termios.TCSANOW, attrs) - except Exception: # noqa: BLE001 - cosmetic; never block generation + except Exception: # noqa: BLE001, S110 - cosmetic; never block generation pass diff --git a/gmlx/load/discovery.py b/gmlx/load/discovery.py index 98daee67..b003922f 100644 --- a/gmlx/load/discovery.py +++ b/gmlx/load/discovery.py @@ -304,8 +304,8 @@ def _save_header_cache(cache: dict) -> None: with os.fdopen(fd, "w") as f: json.dump(cache, f) os.replace(tmp, p) - except Exception: - pass # best-effort cache write; discovery just re-scans next time + except Exception: # noqa: S110 - best-effort cache write; discovery re-scans next time + pass # Model-card sampling embedded in the GGUF header (llama.cpp writes diff --git a/gmlx/load/remote.py b/gmlx/load/remote.py index 017c6206..853d95bd 100644 --- a/gmlx/load/remote.py +++ b/gmlx/load/remote.py @@ -242,8 +242,8 @@ def _auth_headers(url: str) -> dict: try: from huggingface_hub import get_token token = get_token() - except Exception: - pass # no stored hub token -> anonymous request + except Exception: # noqa: S110 - no stored hub token -> anonymous request + pass if token: return {"Authorization": f"Bearer {token}"} return {} diff --git a/gmlx/models/deepseek_v4/model.py b/gmlx/models/deepseek_v4/model.py index c082463a..70e1dc27 100644 --- a/gmlx/models/deepseek_v4/model.py +++ b/gmlx/models/deepseek_v4/model.py @@ -669,7 +669,7 @@ def fire(fn): guard.eval(*(out if isinstance(out, (tuple, list)) else (out,)), site="dsa-warm-probe", owner="scratch") n += 1 - except Exception: # noqa: BLE001 - warm only, probes stay live + except Exception: # noqa: BLE001, S110 - warm only, probes stay live pass B, H, L, D, P = 1, 64, 64, 128, 1024 diff --git a/gmlx/serve/bridge_vlm.py b/gmlx/serve/bridge_vlm.py index d591b364..ba85fe49 100644 --- a/gmlx/serve/bridge_vlm.py +++ b/gmlx/serve/bridge_vlm.py @@ -200,8 +200,8 @@ def _build_detokenizer(backend): return _mlxvlm_tok.SPMStreamingDetokenizer(backend, trim_space=False) if _mlxvlm_tok._is_bpe_decoder(decoder): return _mlxvlm_tok.BPEStreamingDetokenizer(backend) - except Exception: - pass # unprobeable tokenizer json -> naive detokenizer + except Exception: # noqa: S110 - unprobeable tokenizer json -> naive detokenizer + pass return naive(backend) @@ -343,7 +343,7 @@ def _ensure_text_embedding_probe(model, raw_model) -> None: try: if lm._token_embedding() is not None: return # stock probe already reaches it - except Exception: # noqa: BLE001 - exotic wrapper + except Exception: # noqa: BLE001, S110 - exotic wrapper pass emb = _find_token_embedding(raw_model) if emb is None: @@ -798,8 +798,8 @@ def _apply_draft_block_size_override(result) -> None: cfg.runtime_block_size = n else: cfg.block_size = n - except Exception: - pass # frozen/odd config object -> keep the drafter's own default + except Exception: # noqa: S110 - frozen/odd config object -> keep the drafter's own default + pass def _log_drafter_source(gguf_path: str, drafter, draft_gguf_path: str | None) -> None: @@ -855,7 +855,7 @@ def _degrade_failed_mtp(model_path: str, error: str) -> None: import mlx.core as mx mx.clear_cache() - except Exception: + except Exception: # noqa: S110 - cache release is advisory after a failed load pass diff --git a/gmlx/serve/decode_batch.py b/gmlx/serve/decode_batch.py index 80fd8955..e31217b1 100644 --- a/gmlx/serve/decode_batch.py +++ b/gmlx/serve/decode_batch.py @@ -47,6 +47,6 @@ def decode_batch() -> int: fw = frontier_width() if fw: return min(DEFAULT_DECODE_BATCH, fw) - except Exception: + except Exception: # noqa: S110 - capacity table unreadable -> default width pass return DEFAULT_DECODE_BATCH diff --git a/gmlx/serve/estimate.py b/gmlx/serve/estimate.py index 21571604..cbc7e9c6 100644 --- a/gmlx/serve/estimate.py +++ b/gmlx/serve/estimate.py @@ -76,7 +76,7 @@ def rates_view() -> dict: out["decode_streams"] = len(rows) out["decode_tok_s"] = round(sum(float(r.get("decode_tok_s") or 0) for r in rows), 1) - except Exception: + except Exception: # noqa: S110 - advisory rate fields stay at their defaults pass try: runtime = importlib.import_module("mlx_vlm.server.runtime").runtime @@ -88,7 +88,7 @@ def rates_view() -> dict: secs = float(getattr(metrics, "_decode_time_total_s", 0.0) or 0.0) if gen > 0 and secs > 0: out["decode_tok_s_lifetime"] = round(gen / secs, 1) - except Exception: + except Exception: # noqa: S110 - advisory rate fields stay None pass return out @@ -150,7 +150,7 @@ def capacity_plan(width: int, depth: int) -> dict: ids = getattr(serving, "_PATH_TO_IDS", {}).get(str(t.get("path"))) or [] if ids: out["model"] = ids[0] - except Exception: + except Exception: # noqa: S110 - model id is cosmetic; the path basename stands pass except Exception: _log.debug("capacity plan: table read failed", exc_info=True) @@ -245,7 +245,7 @@ def _warm_tokens(manager, ids: list, extra_hash: int, model=None) -> tuple: model, int(manager.block_size))) if n > best: best, tier = int(n), "ckpt" - except Exception: + except Exception: # noqa: S110 - ckpt peek is advisory; the block tier still answers pass try: blocks, n = manager.lookup_prefix(ids, extra_hash=extra_hash) @@ -255,7 +255,7 @@ def _warm_tokens(manager, ids: list, extra_hash: int, model=None) -> tuple: finally: if blocks: manager.release(blocks) - except Exception: + except Exception: # noqa: S110 - block peek is advisory pass # Exact-tier entries carry the manager's wire salt (kvarn widths); # the block and ckpt tiers key on the caller's hash. @@ -266,7 +266,7 @@ def _warm_tokens(manager, ids: list, extra_hash: int, model=None) -> tuple: n = int(hit[1]) if isinstance(hit, (tuple, list)) else int(hit) if n > best: best, tier = n, "exact" - except Exception: + except Exception: # noqa: S110 - exact peek is advisory pass n = _exact_peek(manager, ids, exact_hash) if n > best: @@ -312,7 +312,7 @@ def _queue_wait_s(metrics, waiting: int) -> float: secs = float(getattr(metrics, "_decode_time_total_s", 0.0) or 0.0) if done > 0 and toks > 0 and gen > 0 and secs > 0: return waiting * (toks / done) / (gen / secs) - except Exception: + except Exception: # noqa: S110 - metrics unreadable -> flat 5 s per waiting request pass return 5.0 * waiting @@ -354,7 +354,7 @@ def estimate_request(body: dict, *, tenant_id=None) -> tuple: conc = concurrency_stats() out["waiting"], out["in_flight"] = conc.get("waiting"), conc.get("in_flight") out["decode_batch"] = conc.get("decode_batch") - except Exception: + except Exception: # noqa: S110 - advisory fields stay None pass pkg = importlib.import_module("mlx_vlm.server") @@ -383,7 +383,7 @@ def estimate_request(body: dict, *, tenant_id=None) -> tuple: if tok is not None: try: serving.reset_active_spec(tok) - except Exception: + except Exception: # noqa: S110 - token from another context; nothing to reset pass @@ -467,7 +467,7 @@ def _estimate_bound(body, out, t0, path, pkg, rg, model, processor, config, if trained: out["context_limit"], out["context_limit_source"] = int(trained), "trained" out["context_ok"] = bool(tokens + max(0, pinned) <= int(trained)) - except Exception: + except Exception: # noqa: S110 - context verdict stays None pass # warm prefix @@ -517,7 +517,7 @@ def _estimate_bound(body, out, t0, path, pkg, rg, model, processor, config, suffix = max(0, tokens - int(out["warm_tokens"] or 0)) eta = _queue_wait_s(metrics, out.get("waiting") or 0) + suffix / pre out["est_ttft_s"] = round(eta, 2) - except Exception: + except Exception: # noqa: S110 - ETA stays None pass out["estimate_ms"] = round((time.perf_counter() - t0) * 1000, 1) return 200, out diff --git a/gmlx/serve/governor.py b/gmlx/serve/governor.py index 12fb10ed..e386dd89 100644 --- a/gmlx/serve/governor.py +++ b/gmlx/serve/governor.py @@ -624,7 +624,7 @@ def _enter(gen, st: _GovState, band: int, ws: float, margin: float) -> None: if band >= YELLOW and up: try: mx.reset_peak_memory() - except Exception: + except Exception: # noqa: S110 - peak reset is advisory pass st.rung_peak = 0.0 st.rung_tick = st.tick_no @@ -879,7 +879,7 @@ def _governor_tick(gen) -> None: if st.rung_rate_before is None and st.tick_no - st.rung_tick >= 1: try: st.rung_peak = float(mx.get_peak_memory()) - except Exception: + except Exception: # noqa: S110 - peak read is advisory; the rung measures without it pass _measure_rung(gen, st) return diff --git a/gmlx/serve/lifecycle.py b/gmlx/serve/lifecycle.py index 4328a3f1..102797a4 100644 --- a/gmlx/serve/lifecycle.py +++ b/gmlx/serve/lifecycle.py @@ -587,7 +587,7 @@ def _warn_missing_models(host, port, api_key, config_abspath) -> None: f"model{plural_s(len(configured))} " f"available - see `gmlx logs` for what was skipped", file=sys.stderr) - except Exception: + except Exception: # noqa: S110 - advisory note only pass @@ -1368,7 +1368,7 @@ def service_status(host: str, port) -> int: try: import gmlx.commands.menubar as _mb auto = _mb.load_menubar_settings().get("autostart") - except Exception: + except Exception: # noqa: S110 - autostart record unreadable -> reported off pass state = "loaded" if mb_loaded else "installed but not loaded" extra = (f"; server autostart on ({auto.get('host')}:{auto.get('port')})" diff --git a/gmlx/serve/live_requests.py b/gmlx/serve/live_requests.py index 1e3d7635..3b85e28f 100644 --- a/gmlx/serve/live_requests.py +++ b/gmlx/serve/live_requests.py @@ -180,7 +180,7 @@ def _memo_tiers(rg, batch_gen, active) -> None: _TIER[(key, uid)] = tier if warm: _WARM[(key, uid)] = warm - except Exception: + except Exception: # noqa: S110 - memo only; rows fall back to the ledger pass @@ -277,7 +277,7 @@ def build_rows(rg, batch_gen, active, now=None) -> list: max_tokens=getattr(args, "max_tokens", None), warm=None, tier=None, position=position)) position += 1 - except Exception: + except Exception: # noqa: S110 - one unreadable section must not hide the others pass # engine-side: unadmitted prompts @@ -289,7 +289,7 @@ def build_rows(rg, batch_gen, active, now=None) -> list: max_tokens=m, warm=None, tier=None, position=position)) position += 1 - except Exception: + except Exception: # noqa: S110 - one unreadable section must not hide the others pass live_uids = set() @@ -318,7 +318,7 @@ def build_rows(rg, batch_gen, active, now=None) -> list: generated=0, max_tokens=getattr(led, "max_tokens", None), warm=warm, tier=_TIER.get((key, uid)))) - except Exception: + except Exception: # noqa: S110 - one unreadable section must not hide the others pass # decode batch @@ -348,7 +348,7 @@ def build_rows(rg, batch_gen, active, now=None) -> list: rows.append(_row(uid, info, model=model, state="decode", now=now, prompt_tokens=None, generated=ntok, max_tokens=None, warm=None, tier=None)) - except Exception: + except Exception: # noqa: S110 - one unreadable section must not hide the others pass # speculative engine: rows tracked from the loop's logging hooks @@ -356,7 +356,7 @@ def build_rows(rg, batch_gen, active, now=None) -> list: table = _SPEC.get(key) if table and table["rows"]: rows.extend(_spec_rows(rg, table, now, model)) - except Exception: + except Exception: # noqa: S110 - one unreadable section must not hide the others pass return rows @@ -470,7 +470,7 @@ def publish(rg, batch_gen, active, *, force: bool = False) -> None: from .queue_cap import note_engine note_engine(rg, batch_gen) - except Exception: + except Exception: # noqa: S110 - engine census is advisory pass with _LOCK: snap = _SNAPS.get(key) diff --git a/gmlx/serve/patches/chat_behavior.py b/gmlx/serve/patches/chat_behavior.py index 2e3b7c59..4271eeef 100644 --- a/gmlx/serve/patches/chat_behavior.py +++ b/gmlx/serve/patches/chat_behavior.py @@ -669,7 +669,7 @@ async def _stop_filter_sse(body, stops: list): if aclose is not None: try: await aclose() - except Exception: + except Exception: # noqa: S110 - upstream body may already be closed pass @@ -821,7 +821,7 @@ async def _timings_sse(body, cell): if aclose is not None: try: await aclose() - except Exception: + except Exception: # noqa: S110 - upstream body may already be closed pass diff --git a/gmlx/serve/patches/completions.py b/gmlx/serve/patches/completions.py index e2d6b250..270118bf 100644 --- a/gmlx/serve/patches/completions.py +++ b/gmlx/serve/patches/completions.py @@ -244,7 +244,7 @@ def _run(): finally: try: token_iter.close() - except Exception: + except Exception: # noqa: S110 - iterator already finished pass if stops and not hit_stop: text += scanner.flush() @@ -364,8 +364,10 @@ def _pump(): _put(e) finally: try: - token_iter.close() # normal end or disconnect: cancels - except Exception: # the in-flight batch generation + # normal end or disconnect: cancels the in-flight + # batch generation + token_iter.close() + except Exception: # noqa: S110 - iterator already finished pass threading.Thread(target=_pump, name="gmlx-completions-pump", diff --git a/gmlx/serve/patches/observability.py b/gmlx/serve/patches/observability.py index 4170295c..ec95c8d8 100644 --- a/gmlx/serve/patches/observability.py +++ b/gmlx/serve/patches/observability.py @@ -110,7 +110,7 @@ def n(value, fmt: str) -> str: import mlx.core as mx parts.append(f"active={mx.get_active_memory() / 1e9:.1f}G") parts.append(f"cache={mx.get_cache_memory() / 1e9:.1f}G") - except Exception: + except Exception: # noqa: S110 - memory readout is cosmetic pass return "[req] " + " ".join(parts) @@ -135,16 +135,16 @@ def record_success(self, envelope): orig_success(self, envelope) try: print(_format_timing_line(envelope), flush=True) - except Exception: - pass # a logging hiccup must never disturb the metrics store + except Exception: # noqa: S110 - a logging hiccup must never disturb the metrics store + pass def record_failure(self, *, endpoint, model, stream, error): orig_failure(self, endpoint=endpoint, model=model, stream=stream, error=error) try: ts = time.strftime("%Y-%m-%d %H:%M:%S") print(f"[req] {ts} {endpoint} {model} failed {error}", flush=True) - except Exception: - pass # a logging hiccup must never disturb the metrics store + except Exception: # noqa: S110 - a logging hiccup must never disturb the metrics store + pass record_success.__dict__[_REQUEST_LOG_FLAG] = True store.record_success = record_success diff --git a/gmlx/serve/patches/request_flow.py b/gmlx/serve/patches/request_flow.py index 5a2f1339..8e4cbb09 100644 --- a/gmlx/serve/patches/request_flow.py +++ b/gmlx/serve/patches/request_flow.py @@ -72,7 +72,7 @@ def _load_deferred_response(model_id, exc): ready, _reason, r = readiness() if not ready and r: retry = max(int(r), int(_RETRY_MIN_S)) - except Exception: + except Exception: # noqa: S110 - readiness unreadable -> the floor Retry-After pass return JSONResponse(status_code=503, content={ "error": {"message": str(exc), "type": "model_load_deferred", @@ -122,8 +122,8 @@ async def endpoint(*args, **kwargs): # while a load was being refused. return await asyncio.to_thread( _load_deferred_response, model_id, exc) - except Exception: - pass # stock handler re-resolves + surfaces errors + except Exception: # noqa: S110 - stock handler re-resolves + surfaces errors + pass return await original(*args, **kwargs) return endpoint @@ -244,7 +244,7 @@ async def _pump(): if aclose is not None: try: await aclose() - except Exception: + except Exception: # noqa: S110 - upstream body may already be closed pass @@ -356,8 +356,8 @@ async def endpoint(*args, **kwargs): if m is not None and not str(m).strip(): try: arg.model = serving._default_model_id() - except Exception: - pass # the resolver raises its typed error below + except Exception: # noqa: S110 - the resolver raises its typed error below + pass break profile = await _extract_request_profile( list(args) + list(kwargs.values())) diff --git a/gmlx/serve/patches/routes.py b/gmlx/serve/patches/routes.py index 035c592c..0558891d 100644 --- a/gmlx/serve/patches/routes.py +++ b/gmlx/serve/patches/routes.py @@ -453,7 +453,7 @@ def _log_aux_request(endpoint: str, model: str | None, started: float, mark = "" if status == "ok" else f" {status}" tail = f"{extra} total={elapsed:.2f}s" if extra else f"total={elapsed:.2f}s" print(f"[req] {ts} {endpoint} {model or '?'}{mark} {tail}", flush=True) - except Exception: + except Exception: # noqa: S110 - a logging hiccup must never disturb the response pass @@ -909,7 +909,7 @@ def _spawn_keep_warm(model_id: str): def _run(): try: _warm_and_release(model_id) - except Exception: + except Exception: # noqa: S110 - a load failure surfaces on the first real request pass thread = threading.Thread(target=_run, name="gmlx-keep-warm", daemon=True) @@ -943,7 +943,7 @@ def _warm_context_lengths() -> None: try: for rm in serving.resolved_models().values(): _capacity.trained_context_length(rm.path) - except Exception: + except Exception: # noqa: S110 - warm only; the request path scans on demand pass diff --git a/gmlx/serve/queue_cap.py b/gmlx/serve/queue_cap.py index 07683d06..395d0be4 100644 --- a/gmlx/serve/queue_cap.py +++ b/gmlx/serve/queue_cap.py @@ -116,7 +116,7 @@ def queue_cap_stats() -> dict: out["waiting"] = depth out["eta_s"] = (_retry_after_s(getattr(runtime, "metrics", None), depth) if depth > 0 else 0) - except Exception: + except Exception: # noqa: S110 - live fields stay None pass return out @@ -137,11 +137,11 @@ def concurrency_stats() -> dict: # preload); older pool stats without it fall back to busy. out["in_flight"] = sum(int(e.get("in_flight", e.get("busy")) or 0) for e in pool.stats().get("resident", [])) - except Exception: + except Exception: # noqa: S110 - in_flight stays None pass try: out["waiting"] = _waiting_depth_all() - except Exception: + except Exception: # noqa: S110 - waiting stays None pass return out @@ -177,7 +177,7 @@ def _waiting_depth(rg) -> int: if callable(qsize): try: depth += max(0, int(qsize())) - except Exception: + except Exception: # noqa: S110 - racy census; magnitude only pass reg = _ENGINES.get(id(rg)) if reg is not None and reg[0]() is rg: diff --git a/gmlx/serve/residency.py b/gmlx/serve/residency.py index 11b0bcbe..a08b8568 100644 --- a/gmlx/serve/residency.py +++ b/gmlx/serve/residency.py @@ -238,8 +238,8 @@ def release(self): def __del__(self): try: self.release() - except Exception: - pass # GC-time cleanup must never raise + except Exception: # noqa: S110 - GC-time cleanup must never raise + pass class _ReleasingTokenIterator: @@ -276,8 +276,8 @@ def __getattr__(self, name): def __del__(self): try: self._hold.release() - except Exception: - pass # GC-time cleanup must never raise + except Exception: # noqa: S110 - GC-time cleanup must never raise + pass class _GenerationGuard: @@ -695,8 +695,8 @@ def flush_all(self) -> int: try: _close() n += 1 - except Exception: - pass # best-effort sweep; count only the successful closes + except Exception: # noqa: S110 - best-effort sweep; count only the successful closes + pass return n def stats(self) -> dict: @@ -1135,7 +1135,7 @@ def _teardown(self, entry: _Entry): try: from gmlx.stream.pagecache import release_streaming_for release_streaming_for(entry.model_path) - except Exception: + except Exception: # noqa: S110 - page-cache sweep is advisory pass # Every eviction and reap funnels through here: drop this entry's # untracked-weights attributions so an evicted model stops taxing @@ -1186,7 +1186,7 @@ def _collect_failed_build() -> None: import mlx.core as mx (getattr(mx, "clear_cache", None) or mx.metal.clear_cache)() - except Exception: + except Exception: # noqa: S110 - cache release is advisory pass @@ -1207,7 +1207,7 @@ def _stamp_boot_kv_costs(rg, gguf_path: str) -> None: for target in wrapper_chain(model): try: object.__setattr__(target, "_kq_boot_kv_costs", costs) - except Exception: # noqa: BLE001 + except Exception: # noqa: BLE001, S110 - stamp only; an unsettable module reads as unstamped pass diff --git a/gmlx/serve/tts.py b/gmlx/serve/tts.py index 82792a63..ed94f621 100644 --- a/gmlx/serve/tts.py +++ b/gmlx/serve/tts.py @@ -170,7 +170,7 @@ def _configure_espeak() -> None: # possibly-too-long one. Import it now so the settings below are final. try: import misaki.espeak # noqa: F401 - except Exception: + except Exception: # noqa: S110 - the import runs espeak setup that raises past ImportError; the paths are set again below pass EspeakWrapper.set_library(lib) data = _espeak_data_path(espeakng_loader.get_data_path()) diff --git a/gmlx/spec/engine.py b/gmlx/spec/engine.py index 873cd7a2..e19737e3 100644 --- a/gmlx/spec/engine.py +++ b/gmlx/spec/engine.py @@ -232,7 +232,7 @@ def _resolve_l1(model): kinds = [] try: kinds = sorted({type(c).__name__ for c in lm.make_cache()}) - except Exception: + except Exception: # noqa: S110 - unprobeable stack; the warning below says so pass _log.warning( "APC OFF for this model: no tier serves its cache stack " @@ -240,7 +240,7 @@ def _resolve_l1(model): ", ".join(kinds) or "unprobeable") try: model._kq_apc_mode = mode - except Exception: + except Exception: # noqa: S110 - memo stamp; recomputed when the model forbids ad-hoc attrs pass if mode is None: return None, None @@ -276,7 +276,7 @@ def _ckpt_active(model, mode, block_size: int = 16) -> bool: sum(1 for t in tags if t.startswith("kvarn"))) try: model._kq_apc_ckpt = flag - except Exception: + except Exception: # noqa: S110 - memo stamp; recomputed when the model forbids ad-hoc attrs pass return bool(flag) @@ -300,7 +300,7 @@ def _ckpt_layout_for(model, block_size: int = 16): "model", exc_info=True) try: model._kq_apc_ckpt_layout = tags - except Exception: + except Exception: # noqa: S110 - memo stamp; recomputed when the model forbids ad-hoc attrs pass return tags or None diff --git a/gmlx/spec/speculative.py b/gmlx/spec/speculative.py index 21ef3aea..671ad1cd 100644 --- a/gmlx/spec/speculative.py +++ b/gmlx/spec/speculative.py @@ -83,7 +83,7 @@ def _round_log_close(): if fh is not None: try: fh.close() - except Exception: + except Exception: # noqa: S110 - atexit; the handle may be gone pass @@ -1433,8 +1433,8 @@ def _sidecar_post_prefill(drafter, sidecar_ctx: dict | None) -> None: try: drafter._kq_head_covered = False drafter._kq_head_request = None - except Exception: - pass # slotted/frozen drafter forbids ad-hoc attrs + except Exception: # noqa: S110 - slotted/frozen drafter forbids ad-hoc attrs + pass if sidecar_ctx is None or _SIDECAR_DISABLED: return if not getattr(drafter, "supports_kv_sidecar", False): @@ -1513,7 +1513,7 @@ def _pop_drafter_warm(prompt_cache: list) -> list | None: if warm is not None: try: prompt_cache[0]._kq_apc_drafter_warm = None - except Exception: + except AttributeError: pass # slotted/frozen cache forbids ad-hoc attrs return warm @@ -1529,7 +1529,7 @@ def _pop_seed_stream(prompt_cache: list) -> dict | None: if ctx is not None: try: prompt_cache[0]._kq_seed_stream = None - except Exception: + except AttributeError: pass # slotted/frozen cache forbids ad-hoc attrs return ctx @@ -1568,7 +1568,7 @@ def _pop_retire_ctx(prompt_cache: list) -> dict | None: if retire is not None: try: prompt_cache[0]._kq_apc_retire = None - except Exception: + except AttributeError: pass # slotted/frozen cache forbids ad-hoc attrs return retire @@ -2207,8 +2207,8 @@ def _owned_decode_rounds_batch( try: drafter._kq_head_covered = False drafter._kq_head_request = None - except Exception: - pass # slotted/frozen drafter forbids ad-hoc attrs + except Exception: # noqa: S110 - slotted/frozen drafter forbids ad-hoc attrs + pass def _reset_armed(n: int) -> None: # B=1-only drafters raise on a left_padding list; fall back bare. try: diff --git a/gmlx/stream/budget.py b/gmlx/stream/budget.py index 05d23db0..d3fdbb0e 100644 --- a/gmlx/stream/budget.py +++ b/gmlx/stream/budget.py @@ -273,7 +273,7 @@ def _decode_arena_bytes( ram = None try: ram = int(mx.device_info()["memory_size"]) - except Exception: + except Exception: # noqa: S110 - no device info (CPU-only build); the RAM fraction lever is skipped pass frac = os.environ.get("GMLX_DECODE_ARENA_RAM_FRAC", "") if frac and ram: diff --git a/gmlx/stream/decode_feeder.py b/gmlx/stream/decode_feeder.py index 7f6a42c3..7a2efabc 100644 --- a/gmlx/stream/decode_feeder.py +++ b/gmlx/stream/decode_feeder.py @@ -219,7 +219,7 @@ def _run(self) -> None: if self._on_start is not None: try: self._on_start() - except Exception: + except Exception: # noqa: S110 - start hook is advisory pass while True: item = self._q.get() @@ -717,7 +717,7 @@ def _shrink_now(self) -> int: import mlx.core as mx mx.synchronize() # no in-flight gather may reference a layer - except Exception: + except Exception: # noqa: S110 - no Metal device: nothing in flight pass freed = 0 for li in list(self._layers): @@ -1507,7 +1507,7 @@ def _regrow_headroom_ok(self) -> bool: import mlx.core as mx ram = int(mx.device_info()["memory_size"]) - except Exception: + except Exception: # noqa: S110 - no device info; the floor prices from avail pass if avail < need + _ram_floor_bytes(ram or avail) + kernel_floor_bytes(): return False @@ -1534,7 +1534,7 @@ def _clear_mlx_cache(self) -> None: import mlx.core as mx (getattr(mx, "clear_cache", None) or mx.metal.clear_cache)() - except Exception: + except Exception: # noqa: S110 - cache release is advisory pass def _resize_layer(self, li: int, new_s: int) -> None: @@ -1718,7 +1718,7 @@ def _print_wedges(self) -> None: def __del__(self): try: self.close() - except BaseException: # noqa: BLE001 - incl. ^C during interpreter exit + except BaseException: # noqa: BLE001, S110 - incl. ^C during interpreter exit pass diff --git a/gmlx/stream/installs.py b/gmlx/stream/installs.py index 83b8269a..fdafbbf1 100644 --- a/gmlx/stream/installs.py +++ b/gmlx/stream/installs.py @@ -155,7 +155,7 @@ def release(model) -> None: continue # keep the attr so the helper stays reachable try: object.__setattr__(owner, attr, None) - except Exception: + except Exception: # noqa: S110 - owner forbids the attr clear; the helper is closed already pass # A model with an un-closed helper keeps its wired-byte charge; the # arena prune already keeps open feeders via _open. diff --git a/gmlx/stream/pin_weights.py b/gmlx/stream/pin_weights.py index 72363ccd..841f47c6 100644 --- a/gmlx/stream/pin_weights.py +++ b/gmlx/stream/pin_weights.py @@ -253,8 +253,8 @@ def close(self) -> None: def __del__(self): try: self.close() - except Exception: - pass # GC-time cleanup must never raise + except Exception: # noqa: S110 - GC-time cleanup must never raise + pass def maybe_pin_weights( diff --git a/gmlx/stream/prefetch.py b/gmlx/stream/prefetch.py index 7d499f87..4e5df8f1 100644 --- a/gmlx/stream/prefetch.py +++ b/gmlx/stream/prefetch.py @@ -317,7 +317,7 @@ def close(self) -> None: def __del__(self): try: self.close() - except Exception: + except Exception: # noqa: S110 - GC-time cleanup must never raise pass diff --git a/gmlx/stream/prefill_feeder.py b/gmlx/stream/prefill_feeder.py index 5ca9824c..20db6c54 100644 --- a/gmlx/stream/prefill_feeder.py +++ b/gmlx/stream/prefill_feeder.py @@ -333,8 +333,8 @@ def close(self) -> None: def __del__(self): try: self.close() - except Exception: - pass # GC-time cleanup must never raise + except Exception: # noqa: S110 - GC-time cleanup must never raise + pass def ring_bytes(offsets) -> int: diff --git a/gmlx/talk/hotkey.py b/gmlx/talk/hotkey.py index 45e623f4..7c499138 100644 --- a/gmlx/talk/hotkey.py +++ b/gmlx/talk/hotkey.py @@ -185,5 +185,5 @@ def _handle_space(self, is_down: bool, flags: int) -> bool: def _fire(self) -> None: try: self.on_fire() - except Exception: - pass # a failing callback must not kill the event tap + except Exception: # noqa: S110 - a failing callback must not kill the event tap + pass diff --git a/gmlx/tui/chat.py b/gmlx/tui/chat.py index 05a75a0c..1ec1fe4f 100644 --- a/gmlx/tui/chat.py +++ b/gmlx/tui/chat.py @@ -977,7 +977,7 @@ def _build_model_info(args, config, drafter, vlm_mtp: bool) -> dict: size_bytes=sum(os.stat(s).st_size for s in pf.shards), n_shards=len(pf.shards), ) - except Exception: + except Exception: # noqa: S110 - info panel; the fields stay absent pass info["model_type"] = _model_type(config) if args.mmproj: diff --git a/pyproject.toml b/pyproject.toml index 4025c823..37b36dd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -270,9 +270,15 @@ backlog = [ # Vendored third-party code is linted upstream, not here. extend-exclude = ["gmlx/_vendor"] +[tool.ruff.lint] +# S110: a silent try-except-pass needs a reason on its except line +# (`# noqa: S110 - `), or a log line instead. +extend-select = ["S110"] + [tool.ruff.lint.per-file-ignores] -# Lambda assignment is idiomatic for the tests' one-line monkeypatch stubs. -"tests/**" = ["E731"] +# Lambda assignment is idiomatic for the tests' one-line monkeypatch stubs; +# a silent swallow in a test stub documents nothing. +"tests/**" = ["E731", "S110"] # Verbatim upstream copies (source-equality-tested); a noqa comment would # break the byte fidelity the equality tests certify. "gmlx/models/qwen35/verify_linear.py" = ["E741"] From 766c970ac0c733c46bc31b590c327f72a60b1ae5 Mon Sep 17 00:00:00 2001 From: Asher Feldman <59994+asher@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:18:56 -0700 Subject: [PATCH 3/3] test: fake prompt batch carries _inputs_embeds, FakeStore closes --- tests/assistant/test_assistant_serve.py | 3 +++ tests/spec/test_full_prompt_prefill.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/assistant/test_assistant_serve.py b/tests/assistant/test_assistant_serve.py index c938f038..027c707a 100644 --- a/tests/assistant/test_assistant_serve.py +++ b/tests/assistant/test_assistant_serve.py @@ -491,6 +491,9 @@ class FakeStore: def __init__(self, **kw): created.append(kw) + def close(self): + pass + import gmlx.assistant.memory as tm monkeypatch.setattr(tm, "MemoryStore", FakeStore) monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path)) diff --git a/tests/spec/test_full_prompt_prefill.py b/tests/spec/test_full_prompt_prefill.py index 30d67ab7..48a9e302 100644 --- a/tests/spec/test_full_prompt_prefill.py +++ b/tests/spec/test_full_prompt_prefill.py @@ -404,10 +404,13 @@ def test_prefill_step_env_override(monkeypatch): monkeypatch.setattr(mtp_prefill, "_mtp_prefill_init", lambda s: None) def fake_batch(): + # _inputs_embeds: the span-aware wrapper (media_spans) reads it + # when a serve test has layered it over prompt_step. return types.SimpleNamespace( draft_kind="mtp", prefill_step_size=None, needs_processing=lambda: False, + _inputs_embeds=None, ) monkeypatch.setenv("PREFILL_STEP_SIZE", "97")