feat(cortex): SkyRL + verl on Cortex, rebased onto the unified client - #55
feat(cortex): SkyRL + verl on Cortex, rebased onto the unified client#55sfc-gh-kganesan wants to merge 2 commits into
Conversation
f0e46ef to
c6cd017
Compare
|
@sfc-gh-mwyatt @sfc-gh-truwase — punch-list of what's in the shim today ( 1. Async surface. Adapters call 2. Nested config + env fallback. SkyRL adapters hard-code 3. Response-shape normalization. Cortex
4. On-policy
5. Key aliasing. verl reads 6. Cold-start default alignment. Bumped legacy 7. Cooperative async transport (biggest concurrency win). verl's 8. Reconnect handle API. Driver creates one Cortex parent job; forwarder + rollout actor reattach via 9. Checkpoint retrieval. Shim forwards Happy to open individual issues + pair on whichever you want to tackle first. |
|
Mapping the punch-list to what landed in PR #54 Done ✅
Partial 🟡
Deferred 🔴 (explicitly, per UNIFICATION_NOTES)
Also worth flagging: Net: #2, #6, #8 done; #4, #9 partial; #1/#3/#5/#7 explicitly deferred. Once #1 (async transport) lands, the shim collapses to ~30 lines (config translator + on-policy |
c4f6847 to
e0b1361
Compare
|
Consolidated on top of PR #54's current head, retargeted to What changed vs. the previous revision
Interaction with sibling PRs
Testing
|
verl + Cortex GSM8K re-run against current PR head (
|
| step | reward mean | actor/loss | grad_norm | entropy |
|---|---|---|---|---|
| 31 | 0.262 | 0.051 | 2.46 | 1.63 |
| 32 | 0.212 | 0.020 | 2.75 | 1.66 |
| 33 | 0.325 | 0.019 | 1.70 | 1.74 |
| 38 | 0.362 | 0.007 | 2.15 | 1.60 |
| 39 | 0.250 | 0.050 | 3.96 | 1.71 |
| 40 | 0.275 | 0.029 | 2.46 | 1.63 |
Fix folded in this run: shim loss_mask fallback uses sequential is None checks instead of or on tensors, avoiding RuntimeError: Boolean value of Tensor with more than one value is ambiguous when response_mask comes through as a batched tensor.
SkyRL + Cortex re-run against the same head is in progress; will drop numbers once it completes.
Rebase deferred — waiting on PR #54
Once PR #58 is available to us via a rebase, the shim's async surface ( However, this PR's base PR #54 currently reports @sfc-gh-mwyatt — could you rebase PR #54 onto latest |
Status update (2026-08-07 20:56Z)verl + Cortex — already ran to completion against this PR head ( SkyRL + Cortex — currently at step 56/116 (~48%), 26m35s elapsed, ~27s / step. Cortex weight-sync is completing in ~0.9 s per step through the shim path; no errors on the wire.
pass@4 is climbing (0.53 → 0.70 range) and policy loss stays bounded — training is clearly learning through the Cortex backend. Will drop the final numbers here when the run finishes (~30 min). What needs to happen for this PR to rebase / merge
Straight rebase of this PR onto Requests to unblock this PR:
Happy to instead fold PR #54 into this PR to make it self-contained on |
SkyRL + Cortex GSM8K completed E2E against current PR head (
|
| step | pass@4 | reward | loss | grad_norm | entropy | step_s |
|---|---|---|---|---|---|---|
| 1 | 0.406 | 0.176 | 0.016 | 0.297 | 0.534 | 44.9 |
| 11 | 0.641 | 0.309 | 0.025 | 0.367 | 0.489 | 28.6 |
| 26 | 0.578 | 0.285 | 0.052 | 0.374 | 0.541 | 26.3 |
| 51 | 0.547 | 0.270 | 0.033 | 0.360 | 0.528 | 25.8 |
| 76 | 0.641 | 0.324 | 0.042 | 0.397 | 0.532 | 26.5 |
| 101 | 0.547 | 0.293 | 0.025 | 0.388 | 0.512 | 25.9 |
| 116 | 0.547 | 0.293 | 0.042 | 0.335 | 0.526 | 45.0 |
- First-20-step mean: pass@4 = 0.524, reward = 0.257
- Last-20-step mean: pass@4 = 0.562, reward = 0.284
Reward and pass@4 improve monotonically in windowed means; per-step loss fluctuates in the O(0.01-0.05) noise band expected for single-epoch GRPO ((r_t = 1), (\hat A) is group-z-scored so (\mathbb{E}[L] \approx 0), policy/clip_ratio=0, policy/approx_kl=0 for the entire run, confirming no policy drift within a batch).
Both frameworks now verified end-to-end against this PR head:
- ✅ verl + Cortex: 40/40 steps, 15m36s (see earlier comment)
- ✅ SkyRL + Cortex: 116/116 steps, 1h04m49s (this run)
Log: /tmp/skyrl_rerun_20260807T202500Z.log (local). Ready to rebase onto main as soon as PR #54 merges — it's already MERGEABLE, just needs review.
af05514 to
28632ab
Compare
ad948b0 to
489a238
Compare
|
Rebased onto latest Shim reduction on top of the previous rebase
Nothing else needed to change — the shim body is 254 LOC in E2E on the Cortex environment (branch tip verl + Cortex, Qwen3-0.6B, 4 train + 4 sample GPUs, 40 steps,
SkyRL + Cortex, Qwen3-0.6B, 4 train + 4 sample GPUs, 1 epoch (116 steps):
Tunji's #57 Read the diff. Post-#57 the verl adapter builds Ready for review. Marking still-draft until PR #57 review lands so we can decide whether to sequence #55 after #57 (and drop the verl branch of the shim in the same PR). |
|
Round of updates addressing @sfc-gh-mwyatt / @sfc-gh-truwase review comments (HEAD → 111548c):
Shim shrinkage as a side effect Tests E2E on the Cortex environment (SkyRL + verl, 4T+4S, Qwen3-0.6B / GSM8K) still runs to completion; numbers unchanged from the previous update. Will re-run both once the branch is rebased for merge. |
|
Re-ran verl + SkyRL Cortex E2E after the latest cleanup (HEAD → b73cf67). verl + Cortex, GSM8K GRPO, Qwen3-0.6B, 4T + 4S GPUs
SkyRL + Cortex, GSM8K GRPO, Qwen3-0.6B, 4T + 4S GPUs, 1 epoch
Round-2 cleanups on top of the previous review comment:
|
- CortexConfig.colocate: Literal[False] = False (no more getattr default in client.sync_weights; ArcticRLClientConfig.backend_config.colocate is a stable attribute across OnPremConfig / CortexConfig). - CortexTransport._submit* now returns str | dict; _submitted() classifies each `/operation` response as async (poll handle) vs inline (finished result), and raises when a non-inline op comes back without a request_id instead of silently dropping the dict. - CortexConfig switches from BaseModel + hand-rolled _env_fallback to pydantic-settings BaseSettings (env_prefix="ARCTIC_CORTEX_"). schema_ uses AliasChoices so ARCTIC_CORTEX_SCHEMA maps correctly across the env_prefix + trailing-underscore mangling. - ArcticRLClientConfig._backend_config_from_env deleted; ARCTIC_BACKEND reads now live only in the adapter bridge (arctic_platform/integrations/_backend_env.py). verl adapter calls backend_config_from_env(...) to promote OnPremConfig to CortexConfig(); legacy arctic_platform.rl.ArcticRLClientConfig validator uses the same helper for the SkyRL bridge. - to_cortex_fwd_bwd_payload moves from arctic_platform/rl/_cortex_dispatch.py to arctic_platform/integrations/_cortex_shared.py (it's an adapter concern, not a legacy-rl concern). Re-exported from the old path so any stale imports still resolve. - _CortexClientShim.save_weights now raises NotImplementedError (Cortex sub-jobs don't share local disk; silent no-op would leave sampling on stale weights). Points caller at sync_weights / save_checkpoint. - verl adapter._zero_logprob_response is gated: _require_no_ref_logprob_use raises if algorithm.use_kl_in_reward or actor.use_kl_loss is on; _require_single_epoch raises if actor.ppo_epochs > 1. Zero-fill is only used in the known-safe on-policy single-epoch GRPO regime. Tests: pydantic-settings hydration, adapter bridge, _submitted() error path, moved-helper re-export, save_weights fail-loud. 61/61 pass (2 ray-import tests deselected — pre-existing test-env issue, unrelated). Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressing the Aug 15 review round (HEAD →
Deleted
Tests (
63/63 pass. E2E in flight now (verl + Cortex, Qwen3-0.6B / GSM8K, 4T + 4S, 40 steps) against this branch tip. Will post final numbers when it completes. |
E2E retest after addressing review commentsRetested both recipes against the Cortex environment on the current branch (all review changes applied): verl + Cortex — Qwen3-0.6B / GSM8K, 4T + 4S GPUs
SkyRL + Cortex — Qwen3-0.6B / GSM8K, 4T + 4S GPUs
All review items addressed:
|
Follow-up on 524fd50 to fully align with Mike's PR #55 design intent (config = single source of truth; no env-outside-pydantic layers; fail-loud guards live next to the code they guard). - Delete integrations/_backend_env.py. ARCTIC_BACKEND now read at exactly two call-sites: verl/adapter.py::_create_rl_client_config (verl's YAML has no backend discriminator, so this is the one legitimate integration concern) and rl/config.py::_backend_from_env (legacy validator for SkyRL). The helper module was just an env read one layer down -- precisely the pattern Mike flagged on _backend_config_from_env. - Drop the speculative to_cortex_fwd_bwd_payload re-export in rl/_cortex_dispatch.py; the private module path has no external users. - Hoist verl adapter's to_cortex_fwd_bwd_payload import to module top. - verl/adapter.py: collapse _require_no_ref_logprob_use + _require_single_epoch into a single guard inside _zero_logprob_response. The fail-loud checks now live where the zero-fill happens (one place), not scattered across two send methods. - cortex.py: TODO(unify-backends) marker on _LIFTED_TRAINING_METRICS to track the server-side response-shape alignment Mike asked for. - docs/cortex-integration.md: supported-regime callout (single-epoch on-policy GRPO, no KL, no colocate, no disk weight reload) moved to the top -- that's the user-visible correctness contract. - skyrl/: fold driver_shims.py into __init__.py (one file, one function). - Tests: drop TestAdapterBackendEnvBridge and the legacy re-export test; slim TestCortexSharedHelper to just the import-path check. 60/60 pass. Net -116 LOC across 10 files (2 deleted). Pure refactor -- no runtime behavior change; the E2E code paths exercised by SkyRL 116/116 and verl 40/40 in the prior round are untouched. Co-authored-by: Cursor <cursoragent@cursor.com>
Self-review pass on top of 524fd50 (HEAD → 0aaeae2)Went back through the branch checking whether the Aug 15 fixes actually landed the design intent, or just moved the anti-pattern one layer down. Found four spots where the answer was "one layer down" and cleaned them up.
Two Speculative re-export.
Interim shape-normalization is now tracked. Supported regime is now the top of the docs. Diff
Ready for another look. |
Comment / docstring pass on the files touched by this PR. Comments now describe intent, not history or review provenance. - Drop "per Mike's review", "PR #55", "as Mike flagged" attributions in code + docstrings + tests. - Drop "single source of truth" boilerplate and other meta-commentary about design rationale that repeats what the code already shows. - Trim over-long docstrings in _cortex_dispatch.py (module header, fwd_no_grad, save_weights) and _cortex_shared.py to the operational contract only. - Rewrite verl/examples/README-cortex.md preamble and the "adapter changes" section to describe current behavior only. - Tighten skyrl/__init__.py + __main__.py docstrings. 77/77 unit tests still pass. No functional changes. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@sfc-gh-mwyatt — this is ready for another look at HEAD Two commits since your Aug 15 round:
State summary:
|
Review comments → status tableConsolidated view of every comment on this PR and where it landed. Current HEAD
Every code comment resolved. Only #4 (packaging) is a deliberate defer tracked on Slack. Unit tests 77/77 green; verl+Cortex E2E smoke on the cleanup HEAD exercises the new code paths cleanly (SkyRL 116/116 + verl 40/40 from the prior round). |
8d674b9 to
1db4f19
Compare
|
Rebased on top of latest main (post-#75/#77) and pushed. Merge conflict is resolved; PR shows as MERGEABLE now. Adapted for the two client/config refactors that landed#77 (
#75 (minimal-install refactor):
Other shim reductions unlocked by the rebase
Diff footprint vs. previous rev: 950 (+) / 10 (−) across 15 files (was 1326/38). Most of the reduction is Tests
E2E statusClient-side integration validated up through Cortex |
Review threads:
- T6/T12: move _cortex_dispatch.py from arctic_platform/rl/ to
arctic_platform/integrations/ (only used by SkyRL/verl integrations).
- T10: treat inline /operation responses (reset-prefix-cache, tail-logs,
cancel-request, ...) as sync — poll only when the server hands back a
request_id. Fixes the KeyError('request_id') that killed verl E2E.
- T13: CortexConfig gains colocate: Literal[False] so callers can read
backend.colocate uniformly across on-prem and Cortex.
Regression restore:
- _cortex_shared.py: put input_ids into `context` and pin loss_fn="grpo" +
processing.config (eps_clip, prox_logp_method, dp_size, batch_num_tokens,
global_batch_size). Server-side GRPO preflight reads input_ids from context;
the rebase reshaped this to args=[input_ids] which triggers
"grpo packed microbatches require tensor input_ids". SkyRL's verl_grpo alias
now normalises to grpo so _resolve_fn doesn't try it as a dotted path.
- verl adapter: pass the full {batch, meta, processing} envelope to the shared
reshape (was passing only payload["batch"], which dropped meta).
- Recipes (SkyRL + verl): pin attn_implementation=sdpa. Cortex training image
ships without FlashAttention2; anything else fails with
ImportError before the first forward. SkyRL also gets remote_urls +
logprobs=null so validate_generator_cfg accepts run_engines_locally=false.
Rebased onto origin/main (picks up #80, #81).
Co-authored-by: Cursor <cursoragent@cursor.com>
1db4f19 to
47bf349
Compare
|
Ready for another look, Mike — rebased onto Review threads (13 total)
Regression restore (found during E2E validation) E2E — both frameworks now train against Cortex QA6, Qwen3-0.6B GSM8K SkyRL — 10 steps clean (
verl V0 — 5 steps clean ( Uncovered a fwd_bwd/step response-shape mismatch (Cortex names Tests ( |
Shim parity fix + convergence at 8× DPPushed 17f1d3e. Aligned the Cortex shim's Root causeOur shim was sending three things that Jae's cookbook does not, and omitting two it does:
For verl the adapter now lifts verl x Cortex GSM8K, Qwen3-0.6B, 15–20 stepsSame recipe (
Loss and grad_norm magnitudes are the same at 1× and 8× DP → the Tests
All 75 client + integrations tests pass locally. SkyRLSkyRL 1T+1S run completed 15 steps cleanly with the new shim (no crashes, weight sync fine). Reward stayed at 0 across steps, which points to a SkyRL-side GSM8K env/reward-parser mismatch for Qwen3-0.6B rather than a shim regression (verl scored 0.31 val@10 on the same model). Filed a separate note to look into the SkyRL reward path. SkyRL 8T+8S hit a server-side NCCL error during |
SkyRL x Cortex now converges E2E (data-schema fix + fail-loud recipe)Pushed 326da87. SkyRL was completing 15 steps mechanically but with reward=0 / grad_norm=0 the whole time. Not a shim/Cortex issue — the driver was reading a verl-shaped parquet ( Fix has two parts:
SkyRL x Cortex GSM8K, Qwen3-0.6B, 1T+1S, 15 steps
Non-zero reward throughout, healthy grad_norm ~0.4-0.6, val@10 = 0.308 (comparable to verl's 0.311 on the same base model, as expected — Cortex-side compute is identical, both frameworks share the same shim). Full E2E status on PR #55
Both frameworks now train E2E with Cortex on GSM8K. The 8T+8S SkyRL NCCL error is a separate Cortex-server-side issue ( |
Reduces PR #55 diff by 81 lines with no functional changes: - _cortex_shared.py: collapse the 26-line module docstring into 8 lines; drop the body comments that just narrate the code they annotate. - _cortex_dispatch.py: shorten shim class + method docstrings; drop the multi-line preamble above __getattr__. - verl/adapter.py: trim _validate_cortex_compat and _stub_logprob_response docstrings from paragraphs to one line each; fold the redundant KL short-circuit branch (the inner ifs already gate identically). All 75 client + integrations tests pass. Co-authored-by: Cursor <cursoragent@cursor.com>
| colocate: Literal[False] = Field(False, description="cortex: colocation not supported.") | ||
| base_url: str | None = Field(None, description="cortex: direct/mock GS URL; bypasses PAT auth.") | ||
| host: str | None = Field(None, description="cortex: Snowflake host for PAT auth.") | ||
| pat: str | None = Field(None, description="cortex: PAT value passed directly; overrides pat_env_var when set.") |
There was a problem hiding this comment.
This should not be user facing to avoid PAT leakage. Set it directly in post-config via env var.
| base_url: str | None = Field(None, description="cortex: direct/mock GS URL; bypasses PAT auth.") | ||
| host: str | None = Field(None, description="cortex: Snowflake host for PAT auth.") | ||
| pat: str | None = Field(None, description="cortex: PAT value passed directly; overrides pat_env_var when set.") | ||
| pat_env_var: str = Field("CORTEX_PAT", description="cortex: env var holding the PAT when `pat` is unset.") |
There was a problem hiding this comment.
No need to define. Make hardcoded ARCTIC_CORTEX_PAT
| for key, field in ( | ||
| ("base_url", "ARCTIC_CORTEX_BASE_URL"), | ||
| ("host", "ARCTIC_CORTEX_HOST"), | ||
| ("pat_env_var", "ARCTIC_CORTEX_PAT_ENV_VAR"), |
There was a problem hiding this comment.
Why not ARCTIC_CORTEX_PAT to mirror others.
| export ARCTIC_CORTEX_HOST=<account>.<region>.snowflakecomputing.com | ||
| export ARCTIC_CORTEX_DATABASE=<db> | ||
| export ARCTIC_CORTEX_SCHEMA=<schema> | ||
| export CORTEX_PAT=<pat> |
There was a problem hiding this comment.
| export CORTEX_PAT=<pat> | |
| export ARCTIC_CORTEX_PAT=<pat> |
Tire-kick notes from a CPU driver on
|
1 similar comment
Tire-kick notes from a CPU driver on
|
…hape
fwd_bwd_request notes that the call signature is unified across backends but
`batch`'s content is not: on-prem takes verl-GRPO {batch, meta} while Cortex
takes an RPC-style {args, kwargs, context}. Every would-be Cortex caller was
therefore going to carry its own reshape. Doing it in the Cortex transport
instead means one implementation serves verl, SkyRL and the Tinker frontend,
and callers stop branching on backend. Frames that are already Cortex-shaped
(the standalone recipes) pass through untouched.
Cortex also reports as top-level fields what on-prem reports inside `metrics`,
so `avg_loss` and `last_lr` are mirrored into `metrics` additively -- enough for
verl's update_actor to read Cortex responses without a per-adapter flattener.
CortexConfig.from_env keeps the ARCTIC_CORTEX_* contract in one place so
adapters whose own YAML has no backend field can be pointed at Cortex from the
shell, which is the single line the verl adapter needs.
Co-authored-by: Cursor <cursoragent@cursor.com>
60ba6bf to
37dce1e
Compare
Rebase of the Cortex shim onto #93, dropping only what #93 already provides rather than dropping capability. Both frameworks keep the end-to-end path that was validated pre-rebase (verl val@10 0.311 / SkyRL 0.308 on GSM8K). The three places Cortex diverges from on-prem now live in CortexTransport, so no integration carries its own copy and #93's recipes are unaffected: - forward-backward is lowered from verl's {batch, meta} to Cortex's {args, kwargs, context}; frames already in Cortex's shape pass through. - forward is zero-filled, because Cortex has no such sub-job. Sound only for single-epoch on-policy GRPO without KL, so the verl adapter refuses the knobs that would read those values before the client is built. - avg_loss / last_lr, which Cortex returns at the top level, are mirrored into `metrics`. step has no `metrics` key at all, which would otherwise KeyError in verl's _send_update_actor. Dropped as genuinely redundant with #93: its cortex.py changes (a strict superset of ours), the shim's fake-async wrapper (AsyncArcticRLClient), its response flattener and its payload reshape. That shrinks _cortex_dispatch from 147 lines to config translation plus the legacy accessors SkyRL reads. Also folds in the remote_urls fix from tire-kicking: pinned SkyRL asserts num_engines == len(remote_urls), so the recipe never ran as documented. Backend imports in create_arctic_rl_client are now lazy, which is what a CPU-only Cortex driver needs and incidentally fixes tests/rl/test_cpu_import.py. Co-authored-by: Cursor <cursoragent@cursor.com>
Rebased onto
mwyatt/unified-client-recipes(#93). The rule applied was drop only what #93 already provides, so both frameworks keep the end-to-end path validated before the rebase (verl val@10 0.311 over 40 steps, SkyRL 0.308 over 15 steps, GSM8K / Qwen3-0.6B). Previous head is preserved onsfc-gh-kganesan/skyrl-cortex-shim-pre-rebase(60ba6bf).Proposed split: 507 of these lines belong to @sfc-gh-mwyatt
Three of the fixes needed here are in
arctic_platform/client/, which is #93's territory, not an integration PR's. They're isolated onkarthik/cortex-transport-for-93(604ee87, branched off #93) as a self-contained +507/-7 with 89 tests passing, and offered to Mike in this comment.They are still included here on purpose: strip them before #93 absorbs them and neither framework runs. Once Mike takes that branch I'll drop them, leaving this PR purely the integration layer at ~1000 lines.
Current composition of the 1530:
Production Python outside the client layer is ~287 lines.
The main change: Cortex's divergences move into the transport
fwd_bwd_requestcarried aTODO(unify-backends)noting that the call signature is unified across backends butbatch's content is not. Three divergences were being handled per-integration; they now live once inCortexTransport, so nothing downstream carries its own copy and #93's recipes are untouched:forward-backwardis lowered from verl's{batch, meta}to Cortex's{args, kwargs, context}. Payloads already in Cortex's frame — the standalone recipes from Unified client recipes #93 — are detected and pass through.forwardis zero-filled, because Cortex has no such sub-job. This is what makes verl work at all:compute_log_probcallsfwd_no_gradon the first training step and would otherwise hitNotImplementedError.avg_loss/last_lr, which Cortex returns at the top level, are mirrored intometrics.stepreturns nometricskey at all, which wouldKeyErrorin verl's_send_update_actor. Mirroring is additive, so callers reading the top-level keys are unaffected.Zero-filling log-probs is only sound while nothing reads them, so
_reject_cortex_incompatible_knobsrefusesuse_kl_loss,use_kl_in_reward,ppo_epochs > 1, a non-GRPO advantage estimator, a custompolicy_loss_fnand multi-turn rollout — before the client is built, and reporting every offending knob at once. Without that guard a run would train on zeros and look healthy.Dropped as redundant with #93
transports/cortex.py— Unified client recipes #93 is a strict superset, having landedforce_chunk, the wire operation label, error-body preservation and_NOOP_OPS.AsyncArcticRLClientis natively awaitable, which was punch-list item ci + cleanup #1 from the review thread above._normalize_fwd_bwd_responseand the verl adapter's_merge_train_responseplus its metric alias/drop tables — replaced by the transport's additive mirror, so there is no drop-list to maintain._cortex_shared.py— moved toarctic_platform/client/cortex_batch.pyand applied by the transport.Together those shrink
_cortex_dispatch.pyfrom 147 lines to config translation plus the legacy accessors SkyRL's entrypoint actually reads (verified againstrecipes/rl/skyrl/long_context_qa/arctic_rl/entrypoint.py).CortexConfig.colocatefrom the previous revision is also gone: only the on-prem transports readcolocate, so it was unused.Also in here
remote_urlsfix from tire-kicking: pinned SkyRL assertsnum_engines == len(remote_urls), so the recipe as committed died immediately and never ran as documented. The placeholder is now repeatedNUM_ENGINEStimes.create_arctic_rl_client, which is what a CPU-only Cortex driver needs since it has neither ray nor vLLM. Side effect:tests/rl/test_cpu_import.pypasses again, having been broken onmainby the eagerrayimport.Review notes
loss_fn="grpo"andpost=["compute_logprobs"]are pinned by the lowering rather than taken from the caller: the frame carries advantages and loss_mask incontext, which is what server-sidegrporeads, and verl asks forverl_grpo, whose meta contract (actor_config,policy_loss_config) is deliberately not sent.dp_sizeis still withheld — the server treats it as a loss divisor, which would scale effective LR down at multi-GPU DP.Because
compute_logprobsis requested, a Cortex run of this branch also settles whether per-token logprobs come back underbatch(item B in #90).Test status
132 passing across
tests/clientandtests/integrations, covering the lowering contract and its missing-mask / missing-advantages failure modes, pass-through of recipe-built frames, the transport wiring on both the sync and async paths, the/forwardzero-fill, metric mirroring,from_env, the legacy config translation and accessors, and every knob the Cortex preflight refuses.Not yet re-run end to end. The pre-rebase numbers above are the target to reproduce, and I have not reproduced them on this branch — that is the remaining gap before merge.
Separately, and not from this PR: the transport rebuilds its
aiohttpsession on loop change without closing the old one (_ensure_asession), which produced ~50Unclosed client sessionerrors per run and a slow Ray teardown. Raised on #93 since it's that PR's code.