Skip to content

fix(serving): surface failed model init as UNHEALTHY instead of hanging (#146 Bug 2) - #148

Merged
drunkcoding merged 2 commits into
devfrom
fix/surface-failed-init-unhealthy
Aug 13, 2026
Merged

fix(serving): surface failed model init as UNHEALTHY instead of hanging (#146 Bug 2)#148
drunkcoding merged 2 commits into
devfrom
fix/surface-failed-init-unhealthy

Conversation

@drunkcoding

Copy link
Copy Markdown
Contributor

Summary

Fixes #146 Bug 2: a failed async model init left /health stuck at
{"status":"starting"} forever instead of flipping to unhealthy, so hard
init failures (bad checkpoint, unresolved config, etc.) looked like indefinite
hangs.

_initialize_model() runs as an un-awaited asyncio task and wrapped its
heavy loads in a bare try/finally with no except. Any exception was
therefore stored on the task and never surfaced — set_healthy() was skipped
and the health state never left STARTING.

Change

  • _initialize_model: wrap the init body in except Exception → log the
    full traceback (_logger.exception) and _health_state.set_unhealthy(reason).
    /health now returns 503 {"status":"unhealthy","reason": "..."} on init
    failure. CancelledError (a BaseException) still propagates, so shutdown
    cancellation is unaffected.
  • Regression test (test_initialize_model_failure_sets_unhealthy): drives
    the real _initialize_model with a forced tokenizer-load failure and asserts
    health flips to unhealthy with a reason (and engine is None).
  • Stale test double: _FakeRuntimeEngine lacked the speculative_draft
    kwarg that _initialize_model has passed since the DFlash integration. On
    dev this raised a latent TypeError inside _initialize_model; before this
    change it propagated (test errored), and the new except turned it into an
    assertion failure. The mock is brought in line with the real
    ContinuousBatchingEngine signature.

Verification

# forced init failure now yields:
health: {'status': 'unhealthy', 'reason': "model initialization failed: ..."}  (is_healthy: False)
  • tests/python/unit460 passed, 1 skipped (the one remaining failure,
    test_examples_smoke, is an environment-only MKL_THREADING_LAYER conflict
    on import torch in a subprocess — passes with MKL_THREADING_LAYER=GNU, and
    is unrelated to this change).
  • tests/python/serving140 passed, 1 skipped.
  • ruff format / ruff check clean; LSP clean.

Relationship to #147

Independent of #147 (the multi-GPU device-placement hang, Bug 1). Both target
dev and reference #146; this PR is the health-surfacing robustness fix that
also explains why Bug 1 manifested as a silent hang rather than an error.

Refs: #146

_initialize_model() runs as an un-awaited asyncio task with a bare
try/finally, so any load failure (bad checkpoint, config, etc.) was
stored on the task and never surfaced: /health stayed
{"status":"starting"} forever instead of flipping to "unhealthy", making
hard init failures look like indefinite hangs (issue #146 Bug 2).

Wrap the init body in `except Exception`: log the traceback and
set_unhealthy(reason) so /health returns 503 {"status":"unhealthy",
"reason": ...} and the failure is visible. CancelledError still
propagates, so shutdown cancellation is unaffected.

Also brings the stale _FakeRuntimeEngine test double in line with the
real ContinuousBatchingEngine signature: it lacked the speculative_draft
kwarg that _initialize_model has passed since the DFlash integration, so
the new except turned its latent TypeError from a collection-time error
into an assertion failure.

Adds a regression test asserting a failed init flips health to
unhealthy with a reason.

Refs: #146
…to_experts

transformers 5.15 removed GlmMoeDsaMoE.route_tokens_to_experts, which
glm_moe_dsa.py and test_glm_routing.py rely on. CI installs the latest
5.x (requirements pin transformers>=5.3.0,<6), so unit-tests (3.10)/(3.12)
fail on dev (and every open PR) with
"AttributeError: type object 'GlmMoeDsaMoE' has no attribute
route_tokens_to_experts".

Skip the GLM routing module when the method is absent (mirrors the
existing importorskip guard), and resolve it via getattr so the block
raises a clear, actionable error at routing time instead of a cryptic
AttributeError at construction.

Refs: #146
drunkcoding pushed a commit that referenced this pull request Aug 11, 2026
_initialize_model passes speculative_draft to ContinuousBatchingEngine
(since the DFlash integration), but the watchdog test's _FakeRuntimeEngine
mock never accepted it. On transformers 5.15, once the earlier GLM
failure stops masking it (pytest runs fail-fast), test_watchdog_integration
fails with "unexpected keyword argument 'speculative_draft'". Match the
real ContinuousBatchingEngine signature (same fix already in #148).

Refs: #146
drunkcoding pushed a commit that referenced this pull request Aug 11, 2026
_initialize_model passes speculative_draft to ContinuousBatchingEngine
(since the DFlash integration), but the watchdog test's _FakeRuntimeEngine
mock never accepted it. On transformers 5.15, once the earlier GLM
failure stops masking it (pytest runs fail-fast), test_watchdog_integration
fails with "unexpected keyword argument 'speculative_draft'". Match the
real ContinuousBatchingEngine signature (same fix already in #148).

Refs: #146
@drunkcoding
drunkcoding merged commit bc093b5 into dev Aug 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant