Skip to content

Phase 8.2 WP1 (Java): decode-baseline workers, launcher and session report - #89

Merged
perNyfelt merged 6 commits into
mainfrom
phase8-2-wp1-java
Nov 8, 2026
Merged

perNyfelt merged 6 commits into
mainfrom
phase8-2-wp1-java

Conversation

@perNyfelt

Copy link
Copy Markdown
Member

Java side of the frozen section 13 contract (PR #87 is the Python reference implementation; this branch is one verifiable unit of plan section 3.2). Full record in req/plans/phase8-2-reference-findings.md section 14.

What lands

jmlx-models test sources (benchmark-only, never published API):

  • BaselineAdapters — the controlled and stock generation/step adapters, driven through the new GenerationRequest.pretokenized seam.
  • BaselineWorker — the process worker mirroring the frozen protocol: anchored job validation (manifest re-hash, repo anchoring, safetensors set pinning), claim/ready/report protocol, per-request priming (reference-pair primes nothing), wall-clock watchdog with grace backstop. Exit 0 ok / 1 failed / 3 timeout, same as the Python worker.
  • BaselineWorkerTestSupport + three test classes (5 / 18 / 9 tests), all added to the macOS CI required-suite list.

jmlx-benchmarks main sources:

  • BaselineBenchmark (spec validation, sequential fork in spec order, session report), BaselineJob (frozen jmlx-decode-baseline-job-1 file + fork commands), BaselineReportValidator (adapter-aware validation of jmlx-decode-baseline-1), BaselineStatistics (type-7 quantiles).
  • Opt-in :jmlx-benchmarks:benchmarkBaseline task only. benchmarkDecode, benchmarkBatchDecode and benchmarkTf32 are preserved; ordinary check gains no downloads or Python execution.

Java-specific deviations (findings §14.1)

  • (a) JDK 25 removed System.setenv: the launcher owns MLX_ENABLE_TF32 on both worker processes and the Java worker verifies the process environment equals the job precision_env before any native initialization, failing the run on mismatch.
  • (b) Files.move(ATOMIC_MOVE) is rename(2) and silently replaces: the no-replace report guarantee (Python gets it from os.link) is an explicit Files.exists(report.json) refusal under the worker's REPORT_LOCK.
  • (c) Identity/label mapping: identity.device from sysctl, identity.java {version, vendor, flags}; EOS/STOP_TOKEN→stop, MAX_TOKENS→length, CANCELLED→cancelled; Java stock unused_lookahead_forwards: 0; load_state: "materialized".

Session report decisions (findings §14.3)

  • Mode-scoped per_runtime[runtime]["<mode>/<case>/<len>"] and first_request[runtime][mode] type-7 quantiles — headline and fixed-continuation never pool.
  • Reference-pair samples run the observed (unprimed) regime and are excluded from per_runtime entirely — they belong to the WP3 pairs aggregation. (An early unmodeled run pooled one such sample into the 0.6b headline cell, n=21 vs 20; the fixture tests pin the rule.)
  • Outcomes: ok/failed/timeout (valid report), invalid-report (raw report embedded), dead, crashed, not-started; exclusion list in the summary. Launcher exit 0 = all ok, 1 = spec validation (nothing forked), 2 = any non-ok (session.json still written). A starter exception stops the session.

Evidence

  • 59 pure-Java launcher tests: 6 statistics (hand-computed type-7), 28 validator (every valid adapter shape + every structural rejection — including the four rules in §14.2 that the tests caught before the rules existed), 25 end-to-end over a hermetic mini repository with a fake starter simulating every protocol outcome. Runs on any platform, no native bootstrap.
  • :jmlx-benchmarks:check, :jmlx-models:check and root check green on this head.
  • Opt-in 0.6b session against the real frozen manifest (M5 Max, precision 1, findings §14.6): both workers ok in ~20 s; headline qwen3-0.6b/32 — 26 requests (1 first + 5 warmups + 20 measured), 128 emitted, load 0.042 s materialized, ttft {p10 0.00584, median 0.00697, p90 0.01367 s, n 20}, streaming ≈ 226.6/228.4/230.3 tok/s, first ttft 0.121 s; reference-pair ok/observed/1 measured/prime_s 0.0; workload sha e8c77301… matches the frozen manifest digest; native pins metal 0.32.4 / mlx-c a26f2d5.

…eport

Java side of the frozen section 13 contract (PR #87 is the Python
reference; this branch is one verifiable unit of plan section 3.2):

- jmlx-models test sources: BaselineAdapters (benchmark-only controlled
  and stock generation/step adapters, never published API), BaselineWorker
  (the process worker: anchored job validation, manifest re-hash,
  safetensors set pinning, claim/ready/report protocol, per-request
  priming, wall-clock watchdog with grace backstop) and
  BaselineWorkerTestSupport; driven through the new
  GenerationRequest.pretokenized seam
- jmlx-benchmarks: BaselineBenchmark (spec validation, sequential fork,
  session report), BaselineJob (frozen job file + fork commands),
  BaselineReportValidator (adapter-aware report validation) and
  BaselineStatistics (type-7 quantiles), exposed only through the opt-in
  :jmlx-benchmarks:benchmarkBaseline task; benchmarkDecode,
  benchmarkBatchDecode and benchmarkTf32 are preserved, and ordinary
  check gains no downloads or Python execution
- Java deviations from the Python worker (findings section 14.1): the
  launcher owns MLX_ENABLE_TF32 on both worker processes and the Java
  worker verifies it (JDK 25 removed System.setenv); the no-replace
  report write is an explicit existence refusal under REPORT_LOCK
  (Files.move ATOMIC_MOVE would replace); identity and finish-reason
  label mapping
- Session report (findings section 14.3): mode-scoped per_runtime and
  first_request type-7 quantiles; reference-pair (observed, unprimed)
  samples excluded from the headline quantiles entirely — they belong to
  the WP3 pairs aggregation; outcome stubs plus the exclusion list;
  launcher exit 0/1/2
- Evidence: 59 pure-Java launcher tests (hermetic mini repository plus a
  fake starter simulating every protocol outcome), 32 jmlx-models tests
  (the three suites added to the macOS CI required-suite list), and an
  opt-in 0.6b session against the frozen manifest (both workers ok;
  headline ttft median 0.006971979 s over 20 measured requests,
  findings section 14.6)
…ncher limits and worker hardening

- Summary cells are adapter- and mode-scoped on per_runtime and
  first_request (controlled/stock and headline/fixed-continuation never
  pool; Medium reproduced: the mixed 0.6b run's median TTFT 0.535 s sat
  between the two adapters' own medians)
- Launcher: run_policy validated without defaults, ok-report-vs-job
  cross-check (invalid-report with violations on mismatch), fork wait
  limit min(timeout_s + 90 s, cell_time_budget_s) with kill plus 10 s
  settle, session time budget records the remainder not-started, and the
  frozen-manifest digest pin refuses an edited checkout (backstop test
  included)
- Worker: per-run watchdog token (a stale watchdog drops every remaining
  action), the stock stop-set assertion unions the tokenizer's
  special-token eos, identity flags come from the JVM input arguments on
  both sides, and the stock drain note is corrected (the synchronous
  loop queues no lookahead, so no drain exists or is needed)
- Fixed-continuation cells gain step_s (per-step, measured requests)
- Tests: BaselineBenchmarkTest 39 e2e, BaselineWorkerProtocolTest 20
  (no new suite classes, the CI required-suite list is unchanged);
  findings section 13.4 item 16, section 14 amended, 14.7 added;
  jmlx-benchmarks README key-shape wording
…ering

- The stale-watchdog two-run test no longer relies on a sleep margin:
  run A is held on a one-shot requestHold latch (consumed by the held
  run's first request) and released only after run B's report is on
  disk; every A-vs-B ordering step is observed with bounded polls
  (worker-ready, latch consumption, watchdog fire) via two new read
  seams. A's post-loop failure handling can no longer race B's report
  write on a slow runner.
- resetRunState() now runs inside the REPORT_LOCK critical section,
  after the run-token increment, so a stale watchdog's grace action
  landing in that gap can no longer observe the fresh state under the
  old token and mark a healthy new run timeout.
- Stop-set assertion javadoc corrected: the stock path feeds the
  generation loop only the manifest frozen set; the tokenizer's eos is
  in the resolved set because the manifest derives the frozen set from
  the artifact's stop sources, tokenizer included. Same correction in
  the stop-set test comment.
- Session budget overrun documented in the launcher javadoc: the
  budget gates new forks, not in-flight workers.
- Findings: section 13.4 item 17 and new section 14.8; item 16(a)/
  16(b) and the section 14.7 Medium bullet corrected in place
  (fake-worker mini reproduction, not a 0.6b run; numbers unchanged).

No contract, schema or measurement-path change; no opt-in session
re-run needed.
…old on failed checks

- Findings item 17(a) mischaracterized the old race twice (corrected
  in place, and in §14.8/the test comment): A's resumed request woke
  BEFORE B wrote its report, and A's poisoned report was a failed
  report from its error handler (the resumed request throws on the
  closed scope, so the post-loop timeout path is never reached), not
  a timeout report — B's own report write was then refused and B
  returned failed.
- A failed check before the latch release left run A blocked on the
  latch for the rest of the test JVM: the cross-run test now releases
  the hold in a try/finally, run A is a daemon thread, and
  resetTestSeams releases any outstanding latch.

Findings: section 13.4 item 18 and new section 14.9. Test-only; no
contract, schema or measurement-path change.
…cription

Item 18(a) and section 14.9 said A's resumed request in the old race
"throws on the closed scope"; that is true only in the current test,
where B has already finished. In the old race B was still running, so
the scope was B's live one and only B's thread could use it (thread
confinement, as the round-1 test comment said). Corrected in place;
the conclusion (A's request throws, the post-loop timeout path is
never reached) is unchanged.

Findings: section 13.4 item 19 and new section 14.10. No code,
contract, schema or measurement-path change.
@perNyfelt
perNyfelt merged commit ce4e193 into main Nov 8, 2026
3 checks passed
@perNyfelt
perNyfelt deleted the phase8-2-wp1-java branch November 8, 2026 18:25
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