Skip to content

fix(lcb-service): distinguish infra crashes from submission failures in grading children - #433

Open
liayan wants to merge 16 commits into
mlcommons:mainfrom
liayan:fix/lcb-service-fork-start-method
Open

fix(lcb-service): distinguish infra crashes from submission failures in grading children#433
liayan wants to merge 16 commits into
mlcommons:mainfrom
liayan:fix/lcb-service-fork-start-method

Conversation

@liayan

@liayan liayan commented Jul 29, 2026

Copy link
Copy Markdown
Member

What

Distinguishes judge-side (infra) failures from submission-side failures when
a LiveCodeBench grading child produces no result, instead of scoring
everything as a submission failure:

  • -6 GradingChildDied vs -8 SubmissionKilledChild: a shared started_flag,
    flipped in run_test right after judge setup (reliability_guard, suite
    parse) and before the submission's own code runs, tells the parent whether
    the child died during the judge's setup (infra) or while running the
    submission (submission's fault).
  • -7 SubmissionExit: sys.exit() in submitted code is a BaseException,
    so it previously fell through the child's except Exception and got
    misclassified as GradingChildDied. Caught separately now.
  • All-infra guard: -5 TestRunnerError and -6 GradingChildDied are both
    judge-side; a batch where every future is infra-attributed now raises
    instead of silently reporting a 0 score.
  • Malformed ground-truth JSON in the dataset is now a judge-side (-5)
    failure, parsed alongside the suite parse in run_test instead of being
    folded into the submission's -4 inside grade_call_based.
  • mp.Manager() is used in a with-block and the killed child is
    join()ed, so a killed grading child no longer lingers as a zombie or
    leaks a manager process. mp.Value uses lock=False (single
    writer/reader, no lock needed).

This PR does not touch the multiprocessing start method (fork/spawn) —
that's split out into #457 (pinning the base image back to Python 3.11) to
avoid conflating a perf-sensitive change with this one.

Type of change

  • Bug fix
  • New feature
  • Documentation update
  • Refactor/cleanup

Testing

  • pytest -m unit — full suite passes, no regressions
  • tests/unit/evaluation/test_lcb_serve.py (new): covers every attribution
    path — -1/-5/-6/-7/-8, the all-infra guard (including a mixed batch that
    must not trip it), both call-based and stdio, malformed ground truth
  • Verified on real hardware: B300 (x86_64) and GB300 (aarch64) — full
    tests/unit/evaluation/ suite (314 tests) passes on both

Checklist

  • Code follows project style
  • Tests added and passing
  • Existing grading semantics preserved (only affects the previously
    mis-attributed 0-result path)

@liayan
liayan requested a review from a team July 29, 2026 19:32
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

Copy link
Copy Markdown

MLCommons CLA bot All contributors have signed the MLCommons CLA ✍️ ✅

@codecov-commenter

codecov-commenter commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.82609% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@ea33275). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...nce_endpoint/evaluation/livecodebench/lcb_serve.py 97.43% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #433   +/-   ##
=======================================
  Coverage        ?   80.52%           
=======================================
  Files           ?      152           
  Lines           ?    20720           
  Branches        ?        0           
=======================================
  Hits            ?    16685           
  Misses          ?     4035           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch from e9aa1db to 4ff0df4 Compare July 29, 2026 19:42
@liayan

liayan commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Verified on the Python 3.14 lcb-service image (forkserver default): grading works with the fork pin; dead grading children classify as -6, and the all-infra-errors check raises, so the original failure is still caught; an all-timeout batch scores 0 without raising — that case would have raised on commit one fix.

@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch 2 times, most recently from 47115a5 to 9c6739e Compare August 4, 2026 14:26
@nvzhihanj
nvzhihanj requested a review from hvagadia August 4, 2026 18:33
@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch from 9c6739e to 3dfa3ca Compare August 4, 2026 23:33
@hvagadia
hvagadia force-pushed the fix/lcb-service-fork-start-method branch from 83a5f2f to 3dfa3ca Compare August 5, 2026 20:23
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated

@nv-alicheng nv-alicheng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Council — Multi-AI Code Review

Reviewed by: Claude + Code-Quality (×2: diff + import-neighborhood, per request) | Depth: quick + forced code-quality

codex was unavailable in this environment. See the summary comment for neighborhood findings on _server.py/run_lcb_tests.py and untouched-line items that can't be posted inline.

Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
@nv-alicheng

Copy link
Copy Markdown
Collaborator

Review Council — Multi-AI Code Review

Reviewed by: Claude + Code-Quality (run twice — diff scope + import-neighborhood, per request) | Depth: quick with code-quality forced on (normally skipped at quick depth)
(codex CLI unavailable in this environment.)

Tight, well-reasoned fix. One high-severity correctness gap survives it, plus code-quality/neighborhood items you asked for. Existing bot/human threads corroborated but not duplicated (see bottom).

🔴 Must Fix (high)

File Line Category Reviewer Summary
lcb_serve.py 178 data-integrity Claude os._exit() / segfault / OOM-kill in submitted code dies before the result-append → classified -6 GradingChildDied → counted as infra → an all-crash batch trips the RuntimeError and discards a true pass@1=0, the opposite of the PR's goal. Bypasses the new SystemExit→-7 fix (those never reach an except). Reserve -6 for judge-startup failures; classify submission-induced exits as a submission fault like -7.

🟡 Should Fix (medium)

File Line Category Reviewer Summary
lcb_serve.py 64 code-quality Both Magic error codes (-1/-2/-5/-6/-7) + prose-encoded infra invariant → IntEnum, derive _LCB_INFRA_ERROR_CODES from it. This is the data-model root of the high finding above.
lcb_serve.py 141 code-quality Quality _MP_CTX.Manager() per sample, never shutdown() → leaks manager server processes; nested fork-from-fork under the pool. Use with.
lcb_serve.py 71 code-quality Quality (nbhd) Lazy import numpy / from .run_lcb_tests import run_test inside the fork target — violates the repo no-lazy-imports rule; hoisting also warms the modules in the parent before fork. (untouched line — not inline)
lcb_serve.py 513 code-quality Quality (nbhd) evaluate_dataframe mutates the caller's DataFrame in place (df["extracted_code"] = ...fillna("")) — hidden side effect on a caller-owned object. Assign to a local. (untouched line)
_server.py 405 code-quality Quality (nbhd) Websocket handler passes the module-global lcb_serve (typed LCBServe | None) into EvaluationSession with no None-guard → opaque AttributeError later inside run_in_executor; /info already 503-guards. Also asyncio.get_event_loop() in a coroutine is deprecated → get_running_loop(). (other file — not in PR diff)
_server.py 32 code-quality Quality (nbhd) from lib.lcb_serve import LCBServelib. prefix doesn't match the module's real location (same dir as _server.py); an implicit, undocumented container-packaging contract that ModuleNotFoundErrors if run in-place. Prefer from .lcb_serve import LCBServe or document the lib packaging. (other file)

🔵 Consider (low)

File Line Category Reviewer Summary
lcb_serve.py 67 code-quality Quality Missing -> tuple[list, dict] on the 3 grading funcs (67/91/126); fork target at 91 uses untyped *args/**kwargs where Process(...) wires args → wrong wiring fails only at runtime as -6.
lcb_serve.py 161 code-quality Both DRY: the two no-result branches duplicate res/metadata shape; a if resp_buffer: return ... guard clause flattens the else-after-return.
lcb_serve.py 459 code-quality Quality (nbhd) assert self.df is not None as a public-method precondition — stripped under python -O; raise explicitly. (untouched line)
run_lcb_tests.py 491 code-quality Quality (nbhd) except ValueError as e: raise e loses the traceback (use bare raise) and the following in_outs = None is unreachable dead code — on the exact -5 TestRunnerError path the PR relies on. (other file)

Existing threads (corroborated, not duplicated)

  • lcb_serve.py:309 (hvagadia, high/concurrency) — forcing fork on the outer ProcessPoolExecutor, created from a uvicorn worker thread via run_in_executor, can inherit thread-held locks and deadlock. Still live and unaddressed — the neighborhood Manager-leak/nested-fork finding (141) sits in the same fork-safety area and reinforces it. Not re-filed.
  • lcb_serve.py:176 (hvagadia/liayan, SystemExit)already fixed by this PR (bc502f1, -7 SubmissionExit). The high finding above is the residual case (os._exit/signals) that fix cannot reach.

⚠️ Commit hygiene: 9 commits including 4 apparent fixups. Consider squashing before merge.

@nv-alicheng

Copy link
Copy Markdown
Collaborator

One thing to note - I think this is a bug on my part: I'd used py3.14 as the base container for the lcb_runner, but maybe this also should be studied:

From the lcb_runner repo, they use py3.11 (https://github.com/LiveCodeBench/LiveCodeBench)

uv venv --python 3.11
source .venv/bin/activate

uv pip install -e .

I have personally not checked but there might be some variance between python versions (i.e. version specific language features like walrus operator, same-line multi-context managers, etc.), and the code generated by the worker should be either version-agnostic or catered to a specific version.

This can get solved a little more easily if we just pin to a python 3.11 container for lcb-service.

@liayan

liayan commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

One thing to note - I think this is a bug on my part: I'd used py3.14 as the base container for the lcb_runner, but maybe this also should be studied:

From the lcb_runner repo, they use py3.11 (https://github.com/LiveCodeBench/LiveCodeBench)

uv venv --python 3.11
source .venv/bin/activate

uv pip install -e .

I have personally not checked but there might be some variance between python versions (i.e. version specific language features like walrus operator, same-line multi-context managers, etc.), and the code generated by the worker should be either version-agnostic or catered to a specific version.

This can get solved a little more easily if we just pin to a python 3.11 container for lcb-service.

3.14's forkserver default is what exposed this, but rolling back to 3.11 would just hide it — fork-from-thread, the -6/-8 attribution, and the SystemExit handling are all version-independent bugs that'd still exist, so I don't see much upside to reverting now.

@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch from 7f4d040 to 750f3aa Compare August 11, 2026 13:22
@liayan

liayan commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Kindly ping.

@arekay-nv

Copy link
Copy Markdown
Collaborator

3.14's forkserver default is what exposed this, but rolling back to 3.11 would just hide it — fork-from-thread, the -6/-8 attribution, and the SystemExit handling are all version-independent bugs that'd still exist, so I don't see much upside to reverting now.

Lets also revert to 3.11 to keep it consistent with LCB specification. We can do that separately.

@arekay-nv arekay-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Council — Multi-AI Code Review

Reviewers: Codex (gpt-5.6-sol) + Claude (concurrency + accuracy lenses) + Grok 4.5 (Cursor). Event: comment only (no approve/reject). Findings verified across models; a couple of single-model claims were dropped after checking (e.g. an "open Manager() makes the fork worker multi-threaded" claim — empirically the worker stays single-threaded, so the spawn-pool / fork-child split is sound).

This PR has iterated well: the earlier SystemExit-7, outer-pool fork→spawn, Manager-leak→with+join, return-types, and DRY threads are all resolved — not re-raised here.

Posted inline (still open)

Line Sev Finding
117 high started_flag flips before judge-side setup → a judge-setup crash is mislabeled -8 and escapes the all-infra guard → silent pass@1=0 (mirror of the 83db6ed fix; boundary one frame too high).
213 high The regression test the PR body claims does not exist anywhere in tests/.
400 med All-infra guard is narrow: -8/-1 excluded, a single non-infra result disables it, and LCB's -4 "Error during testing" (run_lcb_tests.py:521/541) carries no "error" key so it's never classified/logged.
371 med warningerror floods ERROR with routine timeouts/submission failures, drowning genuine -5/-6.

Lower priority (not posted inline)

  • Lazy imports in execute_code_single (numpy, run_lcb_tests) — hoisting to module scope also shrinks the line-117 window (two birds).
  • PR description drift: the body says fork is used for the "outer process pool," but the code uses spawn there (fork only for the grading child) — please update.
  • Document the invariant that the fork child is safe only while the pool worker is single-threaded, so a future top-level import that spawns a thread doesn't silently reintroduce a fork+lock deadlock.
  • started_flag's Value can be lock=False (parent reads only post-join); the per-sample Manager could be a Pipe/SimpleQueue — hardening only.

Filed separately

  • A pre-existing accuracy bug found during this review — an empty test suite scores every submission as PASS (all([]) == True) — is tracked in #443 (out of scope for this PR's diff).

Orthogonal (existing thread)

The Python-3.11 discussion: pinning would only mask the (real, version-independent) MP bugs, so keep these fixes — but grading-interpreter parity with upstream LCB is a separate result-validity concern worth its own tracking, not a substitute.

Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py
@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch 3 times, most recently from 89dbccb to f866eb7 Compare August 12, 2026 19:25
Comment thread tests/unit/evaluation/test_lcb_serve.py
@arekay-nv

Copy link
Copy Markdown
Collaborator

@liayan can you add more details on how to reproduce the failures. I have tried to run LCB with py3.11 and py3.14 base images on x86/linux and unable to see any failures without the PR. Can you share the steps to reproduce the caused grading children to die at startup and left evaluation at 0/N.

@liayan

liayan commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

@liayan can you add more details on how to reproduce the failures. I have tried to run LCB with py3.11 and py3.14 base images on x86/linux and unable to see any failures without the PR. Can you share the steps to reproduce the caused grading children to die at startup and left evaluation at 0/N.

Good catch — I originally hit this on vera-rubin with a new lcb-service image on 3.14, but also confirmed it reproduces on GB200/GB300. Just haven't checked on x86 yet. Just reproduced it again

Steps:

  1. Get any aarch64 Linux host with Python 3.14 (default forkserver start method) — I used an enroot/pyxis container on a GB300 NVL72 node, but the actual lcb-service image isn't required; a stock python:3.14-slim reproduces it too.
  2. Run any workload shaped like ProcessPoolExecutor → mp.Manager() → mp.Process(), all default context — no LCB code, dataset, or server needed. I used a trivial driver with 8 pool workers and 32 dummy "grading" samples.
  3. Watch it stall with zero output. ps -eo pid,ppid,stat,cmd on the node while it's stuck:
274448  274444 S  /app/venv/bin/python -c from multiprocessing.forkserver import main; main(10, 11, ...)
274455  274445 S  /app/venv/bin/python -c from multiprocessing.forkserver import main; main(8, 9, ...)
274463  274455 Sl /app/venv/bin/python -c from multiprocessing.forkserver import main; main(8, 9, ...)
274481  274458 Z  [python] <defunct>
274484  274459 Z  [python] <defunct>
274487  274461 Z  [python] <defunct>
274490  274462 Z  [python] <defunct>
274495  274457 Z  [python] <defunct>
274506  274455 Z  [python] <defunct>
274515  274456 Z  [python] <defunct>
274518  274460 Z  [python] <defunct>

Grading children dead as zombies under a nested tree of forkserver helper processes — same shape as the original 0/349-for-3.5h, one defunct child per pool worker.

@liayan

liayan commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

Interesting — same exact steps on x86_64 (same Python 3.14, same code): finishes in ~1s, every time, at every scale I tried (up to 349 samples / 176 workers). So this looks architecture-specific rather than Python-only-specific.

@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch from a9547a5 to 7058663 Compare August 17, 2026 16:22
liayan added 13 commits August 19, 2026 14:39
Python 3.14 changed the default multiprocessing start method on Linux
from fork to forkserver. The grading pipeline (pool workers forking a
per-problem mp.Process + mp.Manager) only works with fork: under
forkserver the grading children die at startup, every result comes back
as an error, and execute_code_single_suppressed_errors turns that into
all-failed tests, so the service sits at 0/N forever.

Pin the fork context for the executor, the per-problem Process and its
Manager. Also raise if every subprocess reported an execution error --
that means the judge is broken, not that all samples failed -- and log
those errors at error level instead of warning.

Seen on a python 3.14 lcb-service image: 0/349 after 3.5h, one defunct
child per pool worker. Same inputs with fork forced: done in 6 min.
The repo pins 3.12 so CI won't hit this, but shipped images have.
Timeouts were counted as execution errors, so a small batch where every
submission loops forever would trip the guard and raise instead of
scoring 0. Split the empty-buffer case in run_code_subprocess: child
still alive at the deadline -> timeout (-1, submission's fault), child
exited without reporting -> new GradingChildDied (-6, judge's fault).
The guard now only counts -5/-6, so the forkserver startup deaths still
raise and all-timeout batches score normally.

Also log the multiprocessing start method at service init; that would
have made the original 0/N a one-line diagnosis.
…failure

sys.exit() is a BaseException, not caught by the existing `except
Exception`. grade_call_based's method invocation has no SystemExit
guard (unlike the stdio path's call_method, which already does),
so a call-based submission calling sys.exit() killed the grading
child before it filled resp_buffer and got misclassified as -6
GradingChildDied - an infra error that can trip the all-errors guard
even for a single-sample batch. Give it its own code (-7) instead,
kept out of _LCB_INFRA_ERROR_CODES.
evaluate() runs on an executor thread (the server dispatches it via
run_in_executor), so the per-request ProcessPoolExecutor was forking an
already-multithreaded process - a known deadlock risk: only the forking
thread survives in the child, locks held by other threads stay locked
forever. Switch the pool to spawn: fork+exec inherits no locks, so it is
safe to start from a thread, and everything submitted to the pool is
picklable, so it is a drop-in.

Tried forkserver first, but its helper hangs at pool shutdown in the
lcb-service container (Python 3.14.5) and leaks semaphores. Probed all
three start methods in the deployment image: fork and spawn tear down
cleanly, forkserver hangs indefinitely.

The inner grading child keeps fork: grading relies on fork semantics, and
forking from a freshly exec'd single-threaded pool worker is fine. The
startup log now prints both start methods.
A submission can kill its own grading child in ways no except block sees
(os._exit(), a native segfault, an OOM kill). That landed in the -6
GradingChildDied bucket and counted as an infrastructure error, so a batch
where every submission crashed its interpreter tripped the all-infra-errors
guard and aborted instead of reporting a legitimate 0 score.

The child now sets a shared started flag right before grading begins, so an
empty resp_buffer can be attributed: died before the flag means a judge
startup failure - still -6, still counted by the guard; died after means
the submission killed the interpreter - new -8 SubmissionKilledChild,
scored as a normal failed sample. Exit codes cannot make this distinction
because os._exit() lets the submission pick any code.
…process

Return the reported result early, hoist the shared all-failed res out of
the attribution branches, and keep only the metadata construction per
branch. No behavior change.
Each graded sample created a Manager (its own server process) and relied on
the GC finalizer to shut it down. Make the lifecycle explicit with a
with-block so the process count under load is bounded deterministically,
capture the child's started/exitcode state before the scope closes, and
reap a killed grading child with join() instead of leaving a zombie in the
pool worker.
execute_code_single_suppressed_errors is the fork target, but took fully
untyped variadics, so a miswired argument only failed at runtime inside the
grading child (surfacing as a spurious child-death error). Give it named
parameters, and declare the tuple[list, dict] return type on all three
grading helpers so the res/metadata unpacking is checked.
Should've gone out with the earlier attribution-fix commits -- had this
written already, just missed staging it at the time. Covers timeout (-1),
sys.exit (-7), os._exit (-8), and judge-side deaths (-6, both before
run_test and during its own setup), plus the all-infra guard: an
os._exit()-only batch scores 0 without tripping it, a judge-startup-death
batch does.
started_flag flipped True at wrapper entry, before run_test's own setup
(reliability_guard, suite parse) ran -- a death in that window got
misattributed as -8 SubmissionKilledChild instead of -6 GradingChildDied,
so a real judge bug could sneak past the all-infra guard as a silent 0.

Moved the flag into run_test itself, set right before dispatch to
grade_call_based/grade_stdio -- the actual first line of the submission's
own code. Hoisted the numpy/run_lcb_tests imports to module scope while
in there too (same window, and it'd been flagged as a lazy import anyway);
costs the grading child nothing since it forks from an already-warm pool
worker.
…e logging

Two gaps in the all-infra guard/logging: the outer except in
grade_call_based/grade_stdio's callers returns -4 with no "error" key, so a
bad submission that fails to compile or define the expected function never
gets logged (the classification gate keys on "error" in metadata) -- give it
one. Left -4 out of _LCB_INFRA_ERROR_CODES on purpose: it's reached whenever
the submission's own code fails to compile, which is the common case and is
plainly not the judge's fault, not some rare harness bug worth aborting a
whole run over.

Also split logger.error into error (infra codes) vs warning (everything
else) -- it was firing for every timeout/sys.exit/os._exit/bad-code sample,
which drowns the real -5/-6 signal in ops on any batch with a few slow or
broken submissions.
…low-up PR

Reviewer flagged a perf concern with pinning the pool to spawn; dropping the
explicit context here and following up with a separate PR + perf numbers.
Everything else (SystemExit, started_flag attribution, Manager with-block,
type annotations, logging levels) stays as-is.

Verified in a python:3.11-slim container (< 3.14, defaults to fork) — all
11 regression tests pass.
@liayan
liayan force-pushed the fix/lcb-service-fork-start-method branch from fdf2882 to c14c3c2 Compare August 19, 2026 18:39
@github-actions github-actions Bot added the size/normal PR Review Policy: <=500 non-test lines & <=20 files label Aug 19, 2026
@liayan liayan changed the title fix(lcb-service): use the fork start method explicitly fix(lcb-service): distinguish infra crashes from submission failures in grading children Aug 19, 2026
@liayan

liayan commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

Discussed offline with @arekay-nv — we're moving the explicit spawn/fork context pinning to a separate follow-up PR with more performance data, to avoid a perf regression on the spawn side. Everything else through c14c3c2 (SystemExit handling, started_flag attribution, Manager with-block, type annotations, logging levels) stays as-is here.

Verified in a python:3.11-slim container (Python < 3.14, defaults to fork) — all 11 regression tests pass.

Also opened #457 to pin the base image back to Python 3.11, mirroring LiveCodeBench's own supported version. Could you take a look and merge that one too — it's still blocking lcb-service on aarch64 without the new spawn soluton/fork solution.

CI's full test run already forces the process-wide multiprocessing default
to spawn (endpoint_client/worker.py sets it at import time), breaking these
tests' fork-only monkeypatch assumptions. Force fork locally in the tests
that need it instead of relying on the ambient default.
@liayan

liayan commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Discussed offline with @arekay-nv — we're moving the explicit spawn/fork context pinning to a separate follow-up PR with more performance data, to avoid a perf regression on the spawn side. Everything else through c14c3c2 (SystemExit handling, started_flag attribution, Manager with-block, type annotations, logging levels) stays as-is here.

Verified in a python:3.11-slim container (Python < 3.14, defaults to fork) — all 11 regression tests pass.

Also opened #457 to pin the base image back to Python 3.11, mirroring LiveCodeBench's own supported version. Could you take a look and merge that one too — it's still blocking lcb-service on aarch64 without the new spawn soluton/fork solution.

Hey @arekay-nv, could you give this another review and approve it if everything looks good? I’d also appreciate your help merging it, since I don’t have write permissions. I opened #457 as well for consistency.

@arekay-nv arekay-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review council — follow-up pass (round 2)

Ran an independent multi-model council (Codex gpt-5.6-sol @ xhigh + two Claude review lenses) over the current HEAD (7038582). Codex found no actionable regressions. The attribution core verified correct: the started_flag boundary is placed right (after reliability_guard() / suite-parse, before grade_*), the three no-result branches are mutually exclusive and each reachable, SystemExit-7 handling is correct, and the with mp.Manager() switch fixes a pre-existing per-sample Manager-process leak.

New findings posted inline. My earlier round-1 comments (incl. the all-infra guard narrowness at line 403) still stand and are not re-raised here.

Sev Where Issue
medium run_lcb_tests.py:530 Malformed ground-truth outputs → -4 (submission) while malformed suite → -5 (infra): split attribution, dataset faults can silently report 0
medium lcb_serve.py:362 -5 path and the guard's mixed-batch == boundary untested
low run_lcb_tests.py:558 stdio / grade_stdio branch untested
low lcb_serve.py:164 mp.Value default lock=True — latent deadlock + per-sample semaphore; use lock=False
low test_lcb_serve.py:124 Docstrings narrate dev-history / reference an unmerged "follow-up PR" (AGENTS.md)

Additional low-severity, not posted inline:

  • lcb_serve.py:383all([]) is True, so an empty/degenerate suite (LCBTestLoader(strict=False) returns inputs: []) scores a free pass; guard with bool(res) and all(...). Pre-existing.
  • lcb_serve.py:360future.result() is unguarded; a BrokenProcessPool, or a raise inside run_code_subprocess (malformed-suite json.loads, Manager spawn failure), bypasses the guard with an opaque crash. Pre-existing.
  • Fork-only fault injection-6/-8 attribution is exercised only under forced fork; the forkserver/spawn method that caused the production incident isn't directly tested. Worth confirming the fork-pinned tests pass on Python 3.14 / macOS (fork-after-threads).
  • Docs — the new -5..-8 taxonomy and the refuse-to-report-0 RuntimeError aren't documented in livecodebench/README.md or docs/evaluation/DESIGN.md.

🤖 Generated by an AI review council (Codex + Claude); posted for the author's consideration.

Comment thread src/inference_endpoint/evaluation/livecodebench/run_lcb_tests.py
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py
Comment thread src/inference_endpoint/evaluation/livecodebench/run_lcb_tests.py
Comment thread src/inference_endpoint/evaluation/livecodebench/lcb_serve.py Outdated
Comment thread tests/unit/evaluation/test_lcb_serve.py Outdated

@arekay-nv arekay-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thanks for putting this together and addressing the review comments.
There are just a final few ones - please address them before merging.

arekay-nv and others added 2 commits August 22, 2026 09:42
…e flag

Malformed suite JSON already landed as -5 TestRunnerError, but malformed
ground-truth outputs were parsed one level down inside grade_call_based
and landed as -4 (submission's fault) instead. Move that parsing next to
the suite parse in run_test so both dataset problems get the same -5
attribution.

Also: drop the lock on started_flag (single writer, single reader after
join, no contention to guard against), and cover the -5 path, the stdio
branch, and a mixed infra/non-infra batch, none of which had a test.

Signed-off-by: Liang Yan <lyan@coreweave.com>
@liayan

liayan commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

All follow-up items from the new round review are addressed in a442550 — see replies on each thread. Ran the full tests/unit/evaluation/ suite on both B300 and GB300 hardware; no regression.

@arekay-nv the new commit landed but seems still need another reviewer approval per requrement, could you help merge it directly or should I ping other reviewers here.

@arekay-nv
arekay-nv requested a review from leopck August 24, 2026 19:29
@arekay-nv

Copy link
Copy Markdown
Collaborator

@liayan sure will do - can you update the PR description as well - it still seems to reference the fork vs spawn changes.

@liayan

liayan commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

@liayan sure will do - can you update the PR description as well - it still seems to reference the fork vs spawn changes.

Done.
Please also give #457 a review when convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/normal PR Review Policy: <=500 non-test lines & <=20 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants