Skip to content

issue #641 — Phase 2 matched-dose install-resistance curves#478

Open
superkaiba wants to merge 28 commits into
mainfrom
issue-641
Open

issue #641 — Phase 2 matched-dose install-resistance curves#478
superkaiba wants to merge 28 commits into
mainfrom
issue-641

Conversation

@superkaiba

Copy link
Copy Markdown
Owner

Closes task #641.

superkaiba and others added 17 commits June 14, 2026 12:33
… dose ladder

Vendor branch-only modules from issue-537@35d5e737 (NOT on main; #537 is
awaiting_promotion):
  - configs/condition/i537_em.yaml (EM Hydra condition)
  - src/.../experiments/i537_contexts.py (context registry, build_messages/prompt,
    NEGATIVE_CIDS, load_registry, registry_hash)
  - src/.../experiments/i537_judging.py (judge_request_for_row, parse_verdict_em,
    em_rates_from_verdicts with n_included/excluded_frac/p_mis_sweep, submit_judge_batch_raw)

Trainer edits (train_phase SFTConfig, plan #641 §4.1 Must-Fix #1 + dose-ladder enabler):
  - save_steps=getattr(training,"save_steps",500): thread save_steps so
    +training.save_strategy=steps +training.save_steps=25 emits the dose ladder
    (HF defaults 500 -> only 500+final at max_steps=560). Default 500 = no behaviour
    change for any non-dose-ladder run.
  - EPM_KEEP_ADAPTER_DIR=1 fence in _finalize_phase: the ladder checkpoints live INSIDE
    adapter_dir; the default reap would destroy them before per-checkpoint eval. The
    fence lets the #641 driver own per-checkpoint eval + reap. No-op for every other run.

Library-drift reconciliation (parent issue-537 -> main): no breaking signature
changes; alignment.py/batch_judge.py drift is additive (judge_dispatch cache_control,
batch routing), _chunk_requests/format_betley_judge_user_msg/parse_judge_json/
DEFAULT_JUDGE_MODEL all present + compatible on main. Edit targets verified against
the CURRENT (newer) trainer.py, not the parent's.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…yrun smokes

issue_641 package (genuinely-new code, plan §4.1):
  - data.py: build_em_mix (vendored published-corpus loader + the 5th `default`
    contrastive negative + the §4.5/§4.7 disjointness invariant by
    resolved-prompt-hash AND canonical-persona-id, run against the realized
    panels). widened_neutral_candidates HARD-excludes the police_officer ⟷
    neg_sp_police collision a raw-cid check would miss. load_em_pairs vendored
    in-package (no `scripts.` import dependency).
  - stats.py: hierarchical bootstrap (seeds -> probes -> completions, coherent
    subset only) with per-replicate saturating dose-curve refit; per-seed
    asymptotes REQUIRED; H5 asymptote-difference + classify_h5; Arm-B ΔL +
    classify_h1_h2 (equivalence read: H2 = ΔL<=-0.10 AND CI excludes 0; H1 =
    CI subset [-0.10,+0.10]; else AMBIGUOUS); Arm-A diagnostic regression.

scripts/issue641_dose_curves.py: unified dispatcher (PASS_UNIFIED §4.12).
  - phases base-propensity / run (build+train-ladder+eval) / aggregate / upload-raw.
  - Hydra train cmd: +training.max_steps/+training.save_steps (append, not in
    turner_em schema) + ++training.save_strategy=steps/++save_total_limit
    (force-override, ARE in turner_em.yaml) -- verified via `train.py --cfg job`.
  - poll_pipeline contract: [phase=...] lines + terminal [phase=done] + sentinel
    with _SENTINEL_REQUIRED_KEYS (verified via poll_pipeline._parse_sentinel).
  - §6.5 per-completion JSONL (aligned/coherent/binary_misaligned) + per-cell
    aggregate (n_included/excluded_frac/em_rate_at_threshold_{20,40}) + a
    raw_completions.json mirror per cell for upload_raw_completions_to_data_repo.
  - adapter-ladder HF persist (one checkpoint-<step> per ladder point) +
    raw-completions upload before [phase=done].
  - smoke phases: --smoke (full GPU, PASS_UNIFIED), --smoke-cpu (data-gen real +
    disjointness collision + bootstrap fixture, no GPU), --dispatcher-dryrun
    (phase seq + sentinel, no GPU), --stage-inputs, --verify-imports.

CPU smoke + dispatcher dry-run both exit 0; verify-imports + ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan §4.5/§4.9 — the matched neutral is picked AFTER P0. Add data.select_matched_neutral
(narrow pool closest base-propensity match; widen to widened_neutral_candidates,
which excludes the negative-panel collision personas, if no narrow match within
±0.10; below-floor is REPORTED not dropped — graceful degradation) + the
--phase select-neutral driver phase (reads base_propensity.json -> writes
matched_neutral.json). P0 default contexts now include the narrow neutral
candidate pool so the selector has their base propensities. phase_base_propensity
resolves non-registry PERSONAS-key candidates via neutral_source_ctx.

This makes the post-P0 neutral pick mechanical instead of a hand-pick (closes the
deferred-production-path step). CPU fixture smoke + ruff + verify-imports + workflow_lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e, H5 sign

Fixes the 5 code-review blockers (round-1 verdict upheld by the reconciler):

- B1/B2: promote _resolve_ctx to a module-level _resolve_source_ctx used by
  phase_run, _eval_checkpoint, and the realized-sources build. Arm-B
  matched-neutral PERSONAS keys (data_scientist, ...) no longer KeyError on
  bare registry[source], and the §4.7 disjointness assert is now TOTAL over
  the realized panel (never silently drops a persona-key source).
- B4: add a dedicated harmful_advice YES/NO compliance judge to i537_judging
  (HARMFUL_ADVICE_JUDGE_PROMPT_V1 + harmful_advice_rate_from_verdicts); P0
  dispatches the right judge+parser per tag (em -> Betley dual rubric,
  harmful_advice -> compliance) so the Arm-B H1 covariate measures
  harmful-advice compliance, not the broad EM rate (plan §4.6).
- B5: fix classify_h5 sign direction — H5b ceiling requires the CI entirely
  NEGATIVE (resistant below non-resistant) with gap >= 0.15 + flat slope; a
  positive gap returns H5_REVERSED, never H5b.

Smoke: CPU carve-out exercises the resolver via a PERSONAS key, the
disjointness collision through build_em_mix's production path, the H5 sign
on positive+negative fixtures, and the per-tag judge dispatch; new
tests/test_issue641_stats_and_judge.py pins classify_h5 + the judge (8 tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-pool reachability, bootstrap seed coherence

B1 (armb-matched-dose-not-resolved): phase_aggregate now resolves the Arm-B
matched dose from the Arm-A median install crossing (plan §4.5) via
stats.resolve_matched_dose — first ladder step whose pooled-median Arm-A install
(across 6 sources × 2 seeds) crosses 0.5; fallback step 375 when Arm-A absent /
no crossing; --matched-dose default None (cli-override distinguishable). The
chosen dose + matched_dose_source written to dose_curve_results.json.

B2 (armb-widened-pool-never-measured): the default base-propensity P0 contexts
now include widened_neutral_candidates() (approach (a)), so the §4.5 widened-pool
fallback in select_matched_neutral is reachable — widened keys are in
candidate_propensity. Factored _default_base_propensity_contexts() as the single
source of truth for main() + the reachability smoke.

B3 (bootstrap-seed-trajectory-incoherent): bootstrap_dose_curve and
bootstrap_class_asymptote_difference draw boot_seeds ONCE per replicate and
thread the SAME draw across every dose step (new _resample_step_for_seeds);
seeds are the replication unit (§6.3). Arm-B ΔL single-dose path
(bootstrap_armB_delta / _resample_records) intentionally unchanged.

Smokes (all exercise the production paths): B1 runs the REAL phase_aggregate
against fixture cells whose median crosses at step 250 (asserts
dose_curve_results.json matched_dose==250 / armA-median-crossing) + fallback +
cli-override; B2 asserts the widened candidate is selected over far-narrow ones +
default P0 contexts cover the widened pool; B3 uses a 2-seed opposing-curve
fixture + a per-replicate trace to assert a pure-seed replicate traces that
seed's own curve at every dose (the broken per-step draw gives late-gap -0.05 vs
0.85). 6 new unit tests in test_issue641_stats_and_judge.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er_ho ⟷ kindergarten_teacher)

The plan body / package docstring use the PERSONAS slug `kindergarten_teacher`
while the records gate keys on `ARM_B_TEACHER_CID` (= the #537 registry cid
`sp_teacher_ho`); both resolve to the same system prompt. If the operator typed
`--sources kindergarten_teacher,<neutral>` (matching the plan body), per-cell
records keyed under `kindergarten_teacher` while phase_aggregate's
`ARM_B_TEACHER_CID in records` gate looked for `sp_teacher_ho` and found nothing,
silently dropping the H1-vs-H2 headline (armB delta=None, no error).

Fix (approach a — defensive in BOTH directions): add `canonicalize_source` +
`TEACHER_ALIAS_MAP` in issue_641.data and apply it at every records-keying
boundary (P0 per_context, P3 per-completion source) AND on the aggregate read
side (_load_cell_records, _canonical_per_context), so the teacher's cells always
collapse onto the single canonical key the gate fires on regardless of which slug
is typed. Docstrings (package + dispatcher CLI) now name both aliases.

Closes concern armb-teacher-source-alias-mismatch (BLOCKER, round 3 reconciler).

Tests: + alias-roundtrip unit test, + phase_aggregate fires armB under BOTH slugs
(parametrized), + explicit failure-mode test (non-canonical-slug records satisfy
the gate after canonicalizing load). 20/20 unit tests pass (16 pre-existing + 4
new); the production-path CPU smoke gains a p4_teacher_alias phase asserting armB
fires under both slugs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nified router

Single --workload-cmd entrypoint driving the GPU pipeline phases in sequence
(base-propensity -> Arm A run -> select-neutral -> Arm B run); the matched
neutral is resolved at runtime from base_propensity/matched_neutral.json and
threaded into the Arm-B --sources arg. P4 aggregate runs off-pod on the VM
(plan §9/§10), not in this workload. TQDM_DISABLE=1 guards the #607 GCE
metadata-runner SIGPIPE trap; set -euo pipefail per the wrapper-pipefail rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lity

GCP startup script's EXIT trap powers off the VM on rc!=0, and the
eps-router service account has no logging.read IAM, so workload failures
were fully blind. Wrapping the dispatch with bash 'set -x' tracing +
tee-to-file + on-EXIT HF upload to superkaiba1/explore-persona-space-data:issue641_debug/
so we can post-mortem the next attempt.
vLLM 0.11.0 _run_engine line 1610: pbar.format_dict["elapsed"] returns 0
when TQDM_DISABLE=1 makes tqdm a no-op, causing division by zero in the
throughput calculation. Fix: pass use_tqdm=False to llm.generate() directly
(bypasses the buggy code path at the API level) and remove the
TQDM_DISABLE=1 env from the dispatch wrapper.

Caught via HF debug-log upload on GCP attempt #4 (2026-06-14).
… 96 completions JSONLs (P4 aggregate input)
…, regression label declip, coherence non-monotone disclosure + layout fix
@superkaiba
superkaiba marked this pull request as ready for review June 16, 2026 07:25
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
explore-persona-space-dashboard Ready Ready Preview, Comment Jun 19, 2026 2:22am

Request Review

… (identity-conflict-more-seeds follow-up)

Additive aggregate-phase change for the 8-seed Arm-B re-fold:
- --extra-records-roots flag + _load_cell_records(extra_roots=…) unions the
  parent run's dose_curves/ with the follow-up subdir so the hierarchical
  bootstrap pools the parent's 2 seeds + the 6 new seeds.
- assert_schedule_parity (round-1 BLOCK fix): fails loud if any pooled cell
  was not trained at max_steps=560 / linear, naming the offending cell. Runs
  in production aggregate only.
- _write_run_metadata persists per-cell run_metadata.json (max_steps,
  lr_scheduler_type, lr, warmup_steps, seed) at eval time; backfilled for the
  96 committed parent cells (provenance-grounded at 560/linear).
- Two production-path smokes: schedule-parity fail-loud branches + the
  extra-records re-fold seed union.

Training recipe, eval rig, contrastive negatives, EM mix, Betley judge, and
the hierarchical bootstrap are unchanged from the parent run.
…root-silent-drop CONCERNs

Two binding reconciler concerns from round-1 code-review (autonomous mode
does not defer CONCERNs):

stale-schedule-metadata-attestation:
- Persist training-schedule provenance at ADAPTER creation time
  (<adapter_dir>/adapter_run_metadata.json) in _train_dose_ladder, the
  moment the trainer subprocess succeeds.
- _assert_adapter_schedule_parity validates max_steps==560 / linear BEFORE
  the resume-skip on an existing adapter; raises ValueError (naming the dir
  + mismatched fields) on mismatch, missing sidecar, or malformed JSON.
  A stale 100-step adapter now fails LOUD upstream and never reaches
  _eval_checkpoint, where its cell sidecar would otherwise be relabeled 560
  and silently pass the pool-side parity assert.

extra-root-silent-drop:
- _validate_extra_roots asserts every explicit --extra-records-roots entry
  exists, has dose_curves/, and contributes >=1 completions__*.jsonl;
  called from BOTH _load_cell_records and assert_schedule_parity.
- _assert_armB_seed_set guards the production step-100 headline: the pooled
  Arm-B teacher seed set must include both parent seeds {42,1042} + >=5 new
  seeds (gated to !smoke + matched_dose==100). A silently-dropped extra root
  no longer yields a 6-seed aggregate that still bootstraps cleanly.

Tests + smokes:
- tests/test_issue641_concern_fixes.py (14 tests): adapter-provenance
  PASS/mismatch/missing/malformed; extra-root 4 bad shapes + happy path +
  both aggregate entry points; Arm-B seed-set positive/missing-parent/
  too-few-new. Each fails pre-fix (symbols absent), passes post-fix.
- run_smoke_cpu adds smoke_adapter_schedule_provenance +
  smoke_extra_root_validation, driving the REAL new functions CPU-side.

Training recipe, eval rig, contrastive negatives, EM mix, Betley judge, and
the hierarchical bootstrap are unchanged.
Arm-B-only re-run at parent-identical schedule (max_steps=560 + save_steps=25),
6 new seeds {1,7,123,2024,31337,98765}, eval ONLY step-100 via --ladder 100.
Routes writes to eval_results/issue_641/identity-conflict-more-seeds/.
EXIT-trap uploads debug log to HF data repo for post-mortem (matches parent
dispatch script's pattern; the GCE startup script powers off on rc!=0).
…loor)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
superkaiba and others added 2 commits June 18, 2026 17:40
…al JSONs

Per-cell EM judge records (completions__*.jsonl) + install-rate summaries
(em_rate__*.json) + run_metadata for the 2-source x 6-new-seed Arm-B
step-100 dose-curve cells. Pulled from the GCP instance eps-issue-641
(root-owned /workspace) via sudo tar over SSH; raw_completions.json
excluded (lives on the HF data repo). These are the inputs to the
off-VM P4 8-seed re-fold aggregate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8-seed re-fold of the kindergarten-teacher vs matched-neutral identity-conflict
pairing at step 100. teacher-neutral gap -0.07, 95% CI [-0.24, +0.11] (39%
narrower than the parent 2-seed [-0.26, +0.31]); still AMBIGUOUS. All numbers
re-extracted from committed eval JSONs via the issue_641 stats module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-extracts the identity-conflict-more-seeds pooled teacher-minus-neutral
gap (-0.067, CI [-0.24, +0.11], AMBIGUOUS), per-seed gaps + coherent
included counts (teacher 192 / neutral 166), 4/8 teacher-below-neutral,
and the 16-cell schedule-parity result — all off committed eval JSONs.
Schema mirrors the parent analysis/dose_curve_results.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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