Repository navigation
Conversation
# ⭐ Feature ## Share the inference control plane - Add the CPU-only gateway, common manager, lifecycle coordinator, placement planner, routing schema, and compatibility client for Rollout, GenRM, and Teacher. - Validate split, defer, decoupled placement and reject same-phase co-residency before startup. - Coordinate drain, offload, onload, deferred Teacher publication, and dynamic Rollout updates without changing role workloads. --- # ✅ Tests ## Cover RFC redai-studio#71 contracts and GPU workflows - Add CPU tests for routing, discovery, placement, lifecycle rollback, PG ownership, static-weight protection, legacy clients, and deferred publication. - Add opt-in PP/PD topology and three-role GPU integration tests and validation launchers. - Record passing split and decoupled multi-node training evidence in the bilingual validation guide. --- # 📝 Documentation ## Document the acceptance evidence - Add English and Chinese API and validation guides with reproducible commands and acceptance mapping. - Keep the scope limited to the explicit RFC redai-studio#71 acceptance checks.
# 🐛 Bug Fix ## Keep weight readiness test doubles compatible - Add the rollout readiness RPC to the handshake manager stub. - Preserve failed synchronization behavior while allowing successful resume. --- # 🎨 Style ## Apply required CI formatting - Apply the repository's ruff and docformatter output to touched Python files.
# 🐛 Bug Fix ## Preserve legacy engine discovery and startup fixtures - Keep `get_engines_info()` compatible with managers created without the unified inference manager. - Update the startup cleanup fixture for the fully async weight-update arguments and metrics state. --- # 📝 Documentation ## Keep the public inference API focused - Remove internal acceptance reports and experiment details from the public API docs. - Move shared inference links into the related lifecycle and placement sections. --- # 🔩 Chore ## Remove private validation wrappers - Delete one-off cluster scripts containing site-specific paths and runtime settings.
关联 redai-studio#351。官方验收标准五项全部达成(证据矩阵见 PR 描述)。 单 commit 汇总全部开发提交;完整历史在 feat/genrm-elastic-scaling-history 分支(证据文件中的 hash 引用指向该分支)。 扩缩 API 与状态机(relax/utils/genrm_scale_registry.py,纯标准库): - POST /genrm/scale_out|scale_in,num_replicas 为目标绝对总数 - 同键幂等重放(verbatim NOOP replay);按模型互斥,未决清理同样阻塞 - 逐操作状态查询与 reconcile 端点 Manager 生命周期(relax/distributed/ray/genrm.py、multi_engine_manager.py): - 每副本独占 placement group,创建即登记 ownership,所有失败出口 (就绪超时、InfoActor 创建/探测/kill、abort)落到确认 REMOVED 或 pending cleanup,扩缩互斥保持到 reconcile 确认 - 健康检查通过后才发布路由;超时后迟到的结果不再发布 - 缩容最新优先选 victim,初始引擎受保护;drain 完成才销毁 - PG 释放以 Ray 确认 REMOVED 为准(remove_placement_group() 返回不算) - 物理完成栅栏:终态仅在 lifecycle 线程停止后计入 - multi_engine_manager +55 行:REMOVED 确认轮询与 pending 清理重试, 落在 GenRM/Teacher 共享骨架(Rollout 侧零改动,Task 3 融合点) Autoscaler 服务隔离(relax/utils/autoscaler/): - per-service runtime:独立采集器/决策引擎/策略/历史 - ServiceScalingPolicy 支持 GenRM 独立阈值,rollout 配置向后兼容 - /conditions、/scale_history、/metrics_history 支持 service= 过滤 - 指标逐字段有效性校验;unknown_engines 冻结 scale-in(健康路径与 旧实现一致,异常路径更保守,见 PR 描述"需维护者确认的共享语义变更") 可观测性: - /metrics 上报 Manager 实时容量 - TUI monitor --service genrm 视图与无头 SVG 截图 测试与 GPU 证据(demos/task4_genrm/EVIDENCE.md,全部 hash pin 可复算): - 96 个任务相关 CPU 测试(含 PG ownership 故障注入,修复前验证为红) - 手动 1→2→1:4,163 请求 0 失败,贪心前缀跨阶段一致 - Autoscaler 全周期:3,202 请求 0 失败;冻结阈值轮 r2 12/14(A7/B3 定性为验收口径缺陷:Ray 2.58 保留 REMOVED 墓碑)→ r3 以 v2 预注册 口径(非终态 PG 计数,阈值逐字冻结)14/14 全绿 - 奖励一致性:greedy 跨引擎逐输入一致;官方采样 4% 不稳定率定性为 逐引擎种子配置属性 - 故障注入:607.3s fail-closed 停等后 reconcile 清理确认;SIGKILL victim 后在飞请求全部重试到初始引擎 - 最小训练回路(dapo-genrm):step 训练、权重同步 200 OK ×9、judge 调用真实发生(judge_response 落盘) - 训练连续性(官方验收第 5 项):真实配方一次运行内 scale_out→ACTIVE (55s)→scale_in→COMPLETED(1s 排空),双窗口内训练事件无 >120s 停滞、弹性引擎真实服务奖励、8/8 rollouts 跑完、零错误 - B2 冒烟与连续性配方 scripts/training/genrm/run-qwen3-0.6B-4xgpu-*.sh
…le closed
Two review findings on the scale-in drain contract:
1. Reconcile no longer falls through when the manager progress query
fails. Previously a timeout/error returned {} and reconcile proceeded
with victim=None, so the manager retired its recorded victim on the
caller's (missing) drain proof alone, terminating requests still in
flight inside the single Gateway. Reconcile now fails closed with 503
until the victim is positively identified and proven at zero
in-flight (regression tests: progress-query failure and unknown
victim both refuse; identified drained victim still reconciles).
2. Drain confirmation is bound to the victim the watcher observed. A
multi-victim scale-in reuses the request id and swaps the drain event
per victim, so a delayed duplicate confirm for the previous victim
could release the next one without its in-flight count ever reaching
zero. confirm_scale_drained now takes the observed victim address and
rank and ignores stale identities under the scale lock; the watcher
passes them (regression tests: stale confirm between victims and
after retirement both ignored; legacy no-arg confirm still works).
…tors Two review findings on resource and control-plane safety: 1. _remove_owned_pg previously used membership in _pending_pg_cleanup to skip resubmitting remove_placement_group(). When the first submission itself raised (before Ray accepted the deletion), the rank entered _pending_pg_cleanup, every later retry only polled the table, and the PG stayed CREATED forever -- GPU leak plus a permanently held model scale mutex. Deletion-submitted state is now tracked separately (_pg_remove_submitted): a failed submission is retried, an accepted one is only polled for REMOVED confirmation (regression tests: first submit failure resubmits then confirms REMOVED; accepted submission is never resubmitted). 2. AutoscalerService._rebuild_service_runtimes constructed a fresh MetricsCollector for a service target added via PATCH /config but never started it -- without an HTTP session the new target collected nothing and never autoscaled; a removed target kept its session alive. The rebuild now records collectors to start/stop and the async PATCH path starts/stops them before publishing the new runtime set; start() consumes the pending list (regression tests: PATCH adds a started collector, PATCH removing the target stops it).
…apshots, bounded history Review findings on the scale control plane: - num_replicas is now a strict int: JSON true / "2" / 2.0 are rejected 422 at the HTTP boundary instead of being coerced by pydantic before the registry's isinstance checks (boundary test covers all three). - Scale submission fails closed when the authoritative capacity cannot be queried: the mutating path uses strict capacity lookups and returns 503 instead of submitting against a degraded snapshot where ready impersonates current; read-only /engines keeps its degraded view. - A watcher crash no longer zero-fills capacity: the terminal snapshot falls back to the registry's last known values (previous terminal snapshot, else the submit-time observation, now exposed in status) instead of inventing current=0/ready=0, and an entirely empty progress payload keeps cleanup_required set (fail closed) while a non-empty legacy snapshot without the key keeps its old meaning. - The registry is a long-running control plane: clean terminal operations are bounded to max_history (oldest evicted, counted in history_truncated), idempotency records whose clean outcome aged out of the replay window expire to fresh-request semantics, and dirty terminals / live operations are never evicted.
Review finding: the GenRM server args derived random_seed from the engine rank, so the elastic replica sampled differently from the initial one and the two replicas disagreed on borderline inputs under the official sampling config (measured 2/50 verdict flips in the reward consistency run). GenRM is a frozen scoring service: every replica must sample identically for an identical request, so the seed is now the base args.seed on every rank. The rollout builder keeps its rank-derived seed (sampling diversity is wanted there, not here).
Review findings on the autoscaler control plane: - Per-service evaluation now runs concurrently (asyncio.gather with per-service exception isolation): a stalled GenRM metrics endpoint can no longer delay the rollout service's evaluation cadence (regression test parks the GenRM evaluation and asserts rollout completes). - A legacy PATCH of rollout_service_url is no longer shadowed by the startup service_targets entry: the two spellings are kept in sync in both directions, so the patched URL actually takes effect (tests for both directions). - /status reports per-service last_scale_action / last_scale_time and the monitor's service view overlays them, so --service genrm shows GenRM's own scale history instead of the rollout runtime's. - Metrics aggregation is per-field: an engine whose scrape is missing only throughput still contributes its observed token_usage and queue depth to scale-out signals, while remaining an unknown engine for the conservative scale-in gate (cross-layer regression test). - Together with the collector hot start/stop from the previous commit, PATCHed service targets are fully live at runtime.
Review finding: the continuity verdict's single in-window check used the scale-out COMPLETION time as its lower bound, so the whole scale-out process was excluded and one train event between the two operations satisfied the assertion even when the 1 s scale-in window contained nothing. The verdict now asserts progress separately before the operations, between them, and after scale-in -- the honest, observable windows -- and the run-6 evidence is re-checked against them by the final-head rerun (the 1 s drain window is shorter than one training iteration; progress during it is not an observable claim).
…ction, autoscaler r4/r5, B2 smoke
…ertions, 8/8 rollouts
- registry: strictly bounded idempotency and history (fixed-window TTL + max_idempotency_records hard cap; NOOP and dangling records evictable; live/dirty-terminal protected with a provable per-model ceiling) - autoscaler: PATCH /config refuses (409) to remove a service target that still owns non-terminal or cleanup_required scale operations - manager: confirm_scale_drained requires the victim identity (no legacy unbound form); mismatched identity never releases the drain fence - watcher: a crash merges the freshest observed progress counts into the FAILED finish instead of falling back to submit-time capacity - docs: autoscaler cadence wording narrowed to per-cycle concurrency
Raw artifacts (event timelines, scale histories, charts, TUI screenshots, superseded rounds, per-request logs) move to the evidence branch (complete tree backfilled and pinned at c56c821); the PR keeps the final machine verdicts and the frozen preregistration documents only. Engine host fields in the retained summaries are normalized to node-0, matching the documented convention (raw values stay on the evidence branch).
…iant seeding Engine A serves N stochastic judge requests alone, elastic engine B boots afterwards with the same server seed, then a fixed probe set is attributed per engine: per-input verdict sets must agree despite diverged RNG states. Ready to run on the acceptance GPUs; flips, if any, are recorded in full for the request-level sampling_seed follow-up protocol.
…le-ins A scale-in that physically completed underneath a watcher which lost its progress RPCs until deadline left the registry FAILED + cleanup_required with the manager reporting COMPLETED / physical_done / no victim; the reconcile then refused forever. Victim-less snapshots now release only on that positive completion proof (COMPLETED + physical_done); FAILED and live-lifecycle snapshots keep the fail-closed 503.
The single-cycle gather barrier let one stalled service (30 s HTTP waits in GenRM discovery / pending-request polls) starve every later cycle for all services. Each service now owns an evaluation task with its own interval timing; the supervisor reconciles the worker set across config PATCHes. Registry stress gains the bot's TTL-advance dangling-key repro.
…a PATCH A worker keyed only by service name kept evaluating the pre-PATCH ServiceRuntime (stale state/collector) forever. The supervisor now keys on runtime object identity. Also: the divergence driver's load_rows skips on usable rows (the capped-list slice returned fewer rows than requested), and EVIDENCE.md pins the correct full evidence-branch SHA.
Adversarial finding (e2e_sampling_divergence, Qwen3-0.6B, temp 0.1): with server-level args.seed seeding alone, two replicas whose request histories diverged by 300 stochastic requests flipped verdicts on 2/50 fixed inputs -- each engine's RNG stream had advanced differently, so the same scoring request drew different verdicts depending on which replica served it. Product semantics change: a stochastic scoring request (temperature > 0) now derives an int32 sampling seed from SHA256(judge model path, exact model-facing input_ids, effective sampling config) and passes it to SGLang's per-request deterministic sampler. Identical scoring requests produce identical verdicts on every replica, independent of RNG-consumption history. Greedy requests are unchanged (already deterministic); an explicit caller-provided sampling_seed is respected.
… effect The request-level sampling_seed contract (1dbb9e3) was silently inert: SGLang drops the per-request seed tensor unless the server runs with enable_deterministic_inference, so sampling still drew from each engine's global RNG stream -- which advances with every request served. The adversarial rerun (sampling_divergence_20260925_r2) still flipped 2/50 under a replica-invariant server seed, exactly matching the RNG-history-divergence mechanism. GenRM engines now boot with enable_deterministic_inference=True: the per-request seed drives SGLang's deterministic sampler (pytorch sampling backend, radix cache disabled; tp_size=1 keeps NCCL unconstrained).
…1/B2/continuity reruns Adversarial divergence r3 (deterministic sampling activated): 0/50 flips across 300-request diverged RNG histories. B1 r3: official sampling instability 0/50, judge 97%/97%. B2 r3: training + weight sync x9 + graceful shutdown on the seed-contract head. Continuity r5: 12/12 three-window assertions, 8/8 rollouts.
…rs over gracefully Re-review findings: (1) between a scale POST leaving and its acceptance response landing, the remove-target guard saw an empty pending list, so a PATCH could delete the target and strand an operation the server had accepted; _execute_scale_out/in now register a SUBMITTING placeholder before the POST (non-terminal -> blocks removal; NOOP/CONFLICT/rejection remove it again). (2) cancelling a rebuilt worker mid-POST lost that response; workers now hand over gracefully -- the old worker finishes its current evaluation (in-flight POSTs included, updating the placeholder in the state object shared across the rebuild) and exits at the next iteration boundary, re-reading the services mapping each round. Also: stochastic requests carrying min_p are rejected with 422 (the deterministic sampling path required for replica-invariant verdicts does not implement min-p on the pinned SGLang), and HTTPException is no longer retried by the engine retry loop.
Four verdict pins (divergence r3, reward r3, B2 r3, continuity r5) were stale: they were taken before the node-0 normalization pass renamed host fields in the retained summaries, so a verifier following the manifest computed different hashes than the table listed. All 14 entries are now recomputed from the HEAD blobs; the byte-identical r4/r5 continuity verdicts (same assertion matrix after normalization) get an explanatory note pointing at the distinguishing event timelines on the evidence branch.
… null temperature Re-review residuals: (1) when a PATCH repointed a service's URL while an operation's acceptance response was still in flight, the saved request_id was polled at the NEW address (URL built from current config) and 404'd -- scale submissions now record the service_url they were sent to and status/reconcile queries follow it (legacy entries fall back to the current config); the handover test's pause moved inside the real POST response window (same-address and changed-address variants covered). (2) a null temperature slipped through float(value or 0.0) as greedy, but the pinned SGLang normalizes null to 1.0 (stochastic) -- the request reached the engine and hit the unsupported seeded min-p path; temperature must now be an explicit number (422 otherwise), asserted to fire before any engine HTTP call. The redundant except-HTTPException branch in the retry loop is removed (validation precedes the loop).
Ambiguous POST ownership: a transport failure after server acceptance used to delete the SUBMITTING placeholder, orphaning an operation the registry had admitted (future 409s with no known request_id to reconcile). Submissions now carry an idempotency_key, retries are bounded and same-key, unrecoverable outcomes persist as SUBMIT_UNKNOWN (non-terminal: keeps blocking target removal) and _update_pending_requests recovers ownership via idempotent replay -- exactly one server-side operation per key, proven by the accepted-but-response-lost and true-connect-failure regressions. PATCH /config atomicity: field mutations applied before the removal guard could raise 409 left the config partially patched. The patch is now staged on a deep-copied candidate and committed only after every validation passes; a rejected PATCH leaves config, targets, runtimes, collectors, workers and pending operations byte-identical (regression asserts full non-mutation). Sampling-seed semantics documented explicitly: content-deterministic stochastic scoring (identical content -> identical verdicts on any replica and across repeated calls), with the alternative per-request-diversity contract noted as a deliberate future change.
Two P2 findings from the round-8 review of the ambiguous-submission hardening: P2-1 -- terminal replay is not cleanup proof. A same-key replay could return a terminal status (FAILED/PARTIAL) whose POST body carries no cleanup_required; the pending-queue check treated the missing flag as False and moved the operation to history while the server-side registry still held the per-model mutex (later scales 409 forever, reconcile ownership lost). Cleanup is now three-state (True / False / UNKNOWN): adoption from any POST reply (fresh, inline retry or background SUBMIT_UNKNOWN recovery) resets it to UNKNOWN, and only the authoritative status endpoint -- directly or after a reconcile -- may prove it clean. Missing or malformed flags stay UNKNOWN (unknown != clean, fail closed), and the decision gate plus the remove-target guard block on UNKNOWN exactly like on dirty. P2-2 -- recovered operations kept no cooldown accounting. Background recovery only wrote request_id/status, so last_scale_time/action stayed None (or stale) and the next evaluation could immediately scale again. All adoption paths now share _adopt_scale_operation, which records last_scale_action and last_scale_time at adoption time (conservative for recovery: the server's acceptance timestamp is unknown; a slightly late cooldown beats an invisible operation). Unified lifecycle: _dispatch_scale_post (submit / ambiguous outcome) -> _adopt_scale_operation (ownership + cooldown + cleanup UNKNOWN) -> _refresh_authoritative_status (status endpoint is the only cleanup authority) -> _maybe_reconcile (terminal dirty only) -> _finalize_scale_request (terminal AND cleanup proven False). A POST reply never triggers the history transition, and an adopted operation reads the authoritative status in the same cycle. Regressions (production GenRMScaleRegistry behind the HTTP-contract fake, real submit/recovery methods): replay PENDING -> ACTIVE finish; replay FAILED dirty -> status+reconcile then history with the mutex released; PARTIAL dirty retained while reconcile is unfinished (no premature removal, decisions frozen, server 409); replay terminal clean completes without reconcile; status-endpoint failure keeps cleanup UNKNOWN (retained, removal 409, decisions frozen) until a working cycle; scale_in parity; cooldown parity normal-vs-recovered for scale_out and scale_in; terminal UNKNOWN cleanup blocks decisions.
Evidence-provenance cleanup (review feedback):
- Remove all relative references ('final head' / 'final PR head' /
'this PR head' / 'final-head rerun') from EVIDENCE.md; every run now
names the commit that produced it: the seed-contract chain (score
consistency r3, adversarial divergence r3, training smoke r3, training
continuity r5) at 945741e; the round-1 fix batch (failure injection r2,
autoscaler prereg r4/r5, smoke r2) at their exact heads (e7224af /
8b0c6fe / fddb093); the preregistered v2 rounds at fa3ca97.
- State the pinning policy accordingly: the newest code exercised by any
recorded acceptance run is 945741e; earlier verdicts are pinned to
their own producing commits.
- Judge ground-truth correctness is now explicitly informational and
reported per retained run (95%/95% in _r2, 97%/97% in _r3); replica
agreement, not judge capability, is the acceptance criterion.
- Terminology: 'request-level sampling-seed contract' -> 'content-derived
sampling-seed contract' (same content reproduces the same seed across
replicas and repeated calls).
Artifact blobs are untouched; the SHA256 pin manifest still matches the
committed verdict files.
…-Blackwell Final-code Autoscaler acceptance reconfirmation (Criterion 4, frozen protocol) exposed a product regression introduced by the deterministic sampling activation (945741e): Reproduction (identical frozen driver, thresholds, load curve and machine, same day): - final code (deterministic ON): the engine wedged mid-load -- the scheduler stopped completing requests (served frozen at 843, 48 requests hung, /health no answer, GPU util 0%), the elastic replica served 0 requests, and scale-in never became possible; the run failed. - pre-deterministic code (8b0c6fe, r5's product code): 13/14 functional assertions passed, elastic replica served 461 requests in STEADY and automatic scale-in completed. Root cause: SGLang applies the generic flashinfer attention-backend default BEFORE its deterministic-inference handler. On pre-Blackwell GPUs the handler's own deterministic fallback (fa3) is therefore preempted, flashinfer is kept, and the radix cache is force-disabled (flashinfer is not in SGLang's radix-supported deterministic set). Without prefix reuse, a 48-way concurrent scoring load re-prefills every long shared prompt until the scheduler wedges. Fix: select the attention backend SGLang itself recommends for deterministic inference on the detected architecture (fa3 on pre-Blackwell, flashinfer on Blackwell+), so the radix cache stays available. The per-request sampling-seed contract is unchanged (deterministic mode stays on; the pytorch sampling backend still consumes per-request seeds). Per-instance --genrm-engine-config overrides keep the highest priority. Regressions: deterministic attention-backend selection (fa3 pre- Blackwell, flashinfer Blackwell+, probe failure defers to the SGLang default), engine-config override precedence, and the server-args pipeline carrying attention_backend alongside enable_deterministic_inference.
FINAL_EVIDENCE_SHA = 6b501a2 (raw artifacts: the final-code confirmation r3, the regression reproduction r2, the environment-invalid r1, the A/B discrimination run at 8b0c6fe, and the four machine-blocked score-consistency attempts). EVIDENCE.md: the final-code Autoscaler confirmation row (frozen protocol, 14/14 at 5c1e2e7, deterministic sampling under the radix-preserving fa3 attention backend); the regression and environment failure records in the failed-runs table; the pinning policy now names 5c1e2e7 as the newest code exercised by any recorded acceptance run; the determinism known-limit notes that fa3 keeps the radix cache available on pre-Blackwell GPUs; the SHA256 manifest gains the final verdict blob.
…module Suite-wide dependency-stub tests can replace sglang.srt.server_args while relax.backends.sglang.sglang_engine is already imported, leaving its module-level ServerArgs bound to a non-dataclass stub that _compute_genrm_server_args cannot iterate. The two new deterministic attention-backend tests now skip on what the engine module actually sees (2 skips in the stubbed context, 9 passed standalone); the full suite's failure set is byte-identical to the pre-change baseline.
Re-review P1: the three-state cleanup gate (UNKNOWN/False/True, missing
!= clean) treats every service status response without an explicit
cleanup_required as UNKNOWN, but that flag only existed in GenRM's
status contract. Rollout's ScaleOutStatusResponse and
ScaleInStatusResponse never declared it, so FastAPI's response_model
stripped the key from the wire and the shared autoscaler -- whose
default target is rollout -- read UNKNOWN for every rollout terminal
operation:
- terminal scale-out (ACTIVE/PARTIAL/FAILED/CANCELLED) and scale-in
(COMPLETED/FAILED) stayed pending_requests forever;
- the next scaling evaluation stayed frozen ("in progress or awaiting
cleanup") with no path to unblock;
- a removable rollout-contract target kept failing PATCH removal with
409 (stale unresolved pending);
- history never recorded the finished operation.
Rollout has no deferred-cleanup lifecycle: a FAILED scale-out rolls its
engines back before reporting and a COMPLETED scale-in reports the
engines removed, so terminal status is cleanup-complete by contract.
The fix therefore makes the rollout status models state that explicitly
(cleanup_required: bool = False) instead of leaving the flag absent.
GenRM's semantics are unchanged: a GenRM POST reply still proves
nothing, a missing/malformed flag still reads UNKNOWN, terminal-dirty
still requires status/reconcile, and finalization still requires an
authoritative cleanup_required is False.
Coverage:
- response-model serialization tests pin model_dump()["cleanup_required"]
is False for both scale-out and scale-in wire shapes (the response-
model tests now import the component through tests.utils._dep_stubs
so they actually run where transfer_queue is absent);
- shared-autoscaler regressions drive a real AutoscalerService against
a fake speaking the rollout HTTP contract through the actual Pydantic
response models: scale-out and scale-in terminal requests finalize
into history, the next decision is no longer blocked, and a finalized
target no longer 409s on removal;
- the fail-closed guard stays pinned: a rollout-contract response with
the flag dropped (pre-fix wire shape) keeps the request pending and
the decision frozen -- the fix lives in the contract, never in the
autoscaler defaulting missing to clean.
…ic probe declines Re-review P2: _compute_genrm_server_args pinned attention_backend unconditionally -- including the architecture probe's None -- so the sglang_* inheritance loop (which backfills only fields absent from kwargs) could never apply the user's global --sglang-attention-backend to a GenRM engine again. The docstring's "falls back to SGLang default" only held for SGLang's internal default; the relax-level global flag was silently disabled by 5c1e2e7. The deterministic backend is now pinned only when the user has not chosen one globally: - explicit --sglang-attention-backend wins over the probe (restores the pre-5c1e2e7 inheritance behaviour); - a failed probe defers to SGLang's default instead of pinning None over it (and over the user's global choice); - the arch-appropriate deterministic backend (fa3 on pre-Blackwell, flashinfer on Blackwell+, radix cache preserved) is still pinned when the user set nothing, keeping the final-code autoscaler acceptance behaviour on this machine; - per-instance --genrm-engine-config overrides still take priority over both (applied after the loop). Regressions: - an explicit global choice beats the probe result; - a failed probe pins nothing (key absent, SGLang default applies) while the user's global choice still flows through the inheritance loop.
The module-level transfer_queue attribute guard (added with the rollout contract stub support) places two imports after executable code; ruff's E402 fires on the pushed tree and would fail the Pre-commit workflow. Annotate both imports as guarded.
…tion PASS, r5 proxy incident, train-continuity reanalysis v2) Evidence-branch pointer update only; no product change. Raw artifacts live on the evidence branch @ d88d6c7 (r6/r4 verdict trees, the archived raw job log with sha256, and the reanalysis v2 script + parser tests).
…ons table Per review: the top of the file still pointed at evidence 6b501a2 and called 5c1e2e7 the newest acceptance code while the bottom appended 0481701 reconfirmations — an index that contradicted itself. The top is now the only current-conclusions table: one row per official criterion, each bound to the exact product SHA, the producing run and its raw inputs on evidence 66f1f78, with an explicit superseded-by column. Reading rules state the caliber of each claim (0/50 = these 50 inputs under this config; GenRM-path-only weight sync; criterion 5 is continuity, no performance claim absent a control run; content-deterministic semantics pending maintainer acceptance). A scope commitments table separates delivered / not-implemented / to-be-negotiated (cross-Gateway wording now 'not implemented this phase; interface and responsibility to be negotiated'). The per-run history below is marked as historical detail and keeps its own producing commits.
- docs/en/api/genrm.md + docs/zh/api/genrm.md: endpoints table, request/ status contract, NOOP/idempotency semantics, scale-out/in state chains with terminal sets, cleanup_required and capacity fields (current/ready/ occupied/pending_cleanup), strict-capacity 503 and fallback semantics - docs/public/openapi/genrm.json: regenerate via scripts/tools/ generate_openapi.py; 10 paths, 9 schemas, descriptions from code docstrings - scripts/tools/generate_openapi.py: write trailing newline so reruns are byte-stable Verified against relax/components/genrm.py, relax/utils/genrm_scale_registry.py and relax/distributed/ray/genrm.py: every documented route, state, terminal set, capacity formula and error code matches the implementation.
- docs/public/openapi/rollout.json: add cleanup_required (bool) to ScaleOutStatusResponse and ScaleInStatusResponse properties — manual patch in generator-faithful style (type + title, not in required), avoiding the unrelated /predict drift a full regeneration would bring - docs/en/api/rollout.md + docs/zh/api/rollout.md: new 'Scale status cleanup contract' section (bilingual semantic parity) documenting the three-state contract shared with the Autoscaler (true = cleanup still pending; false = authoritative complete; absent = unknown, missing != clean) and why rollout terminal states report false (scale-out engines serve or were rolled back; scale-in COMPLETED removed / FAILED rolled back) Docs-only; no product code touched.
# 📝 Documentation - Point the two evidence index links to the reviewed fdf288d snapshot. - Leave product code, historical verdicts and experiment versions unchanged. - Keep this commit local; the public PR head and its CI remain unchanged.
# 📝 Documentation - Withdraw whole-run zero-error and stall-bound claims using reanalysis v2.1. - Distinguish log-reported sample counts from file-level sample verification. - Keep original verdicts and producing versions; mark superseded interpretations. - Correct the long-request drain description and completed reconfirmation status. - Preserve product code, GPU results and public PR history.
Merge main at 7c4e537 while preserving nonblocking rollout manager binding and initial weight synchronization. Apply the upstream offline-mode guard so SFT, DPO, and RM continue to skip startup weight updates. Retain the English and Chinese unified inference navigation entries using the current main formatting. Extend startup regression coverage across online/offline loss modes and synchronous, fully asynchronous, and hybrid execution. Validation: selected CPU regressions passed (110 passed, 1 skipped, 2 deselected); all pre-commit hooks passed. Full Ray and multi-node GPU validation was not rerun in the local environment. Refs redai-studio#71
The NPU provider copies runtime arguments into the Bridge model config. The inference placement plan contains frozen dataclasses, which the NPU image's older Bridge sanitizer tries to rebuild with setattr during the initial weight export, raising FrozenInstanceError on the role field. Keep the placement plan on the runtime args and exclude it from the model config. Preserve the other NPU argument copies and existing provider overrides. Add GPU/NPU provider regressions that retain the original plan. Validation: the NPU regression fails before the fix; the provider and inference suites pass afterward (123 passed, 1 skipped because Ray is unavailable). All pre-commit hooks pass. Real NPU validation remains with the integration CI. Refs redai-studio#71
Merge the GenRM elasticity implementation with the unified inference service. Preserve legacy generation, sampling and attribution through the gateway, count raw and streaming requests during drain, and bind closed admission to engine identities so recovered engines may reuse endpoints. Use the same published engine states for discovery and legacy routing. Preserve split placement offsets, retain owned placement groups until Ray confirms removal, and recreate elastic placements during engine recovery. Exclude retired slots from recovery and keep failed cleanup retryable. Reject deferred, training-offload and multi-GPU elastic layouts before taking the lifecycle lock or allocating resources. Document the supported scope in both languages and regenerate the GenRM API schema. Validation: focused CPU suite 426 passed, 4 optional-dependency skips; latest lifecycle/gateway regression rerun 34 passed (overlaps that suite); additional contract tests 12 passed, 17 optional-dependency skips. All pre-commit hooks and the VitePress documentation build passed. Real SGLang and multi-node GPU execution were not rerun on this CPU host. Refs redai-studio#71, redai-studio#351, redai-studio#356, redai-studio#370
Contributor
|
Thanks for contributing to Relax, @Arazyu! 感谢你为 Relax 做出贡献! 📚 Contribution guide / 贡献指南Describe the problem, your changes, and how you validated them. Keep each PR focused and run 请说明问题、改动和验证方式,保持 PR 聚焦,并在提交前运行 🛠️ CI commands / CI 指令
Put one command on the first line of a new PR comment. Rerun/cancel require PR authorship or repository write access. 在新 PR 评论的首行写一条指令。PR 作者或有仓库写权限的贡献者可以重跑、取消 CI。 🔎 Merge requirements / 合入条件GitHub:📝 请转为 Ready for review
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Integrate the unified inference service from #356 with GenRM elastic scaling from #370. GenRM scaling now shares the inference gateway's routing and discovery rules, and gateway requests participate in the scale-in drain accounting.
This is a draft integration candidate against
maincontaining both implementations and their original commit histories:9eec00a186b4b92f32dc2aba9c23e673e2375b13.da4acbbab530f082f37c1633c811c704be5a044e.36ee4ba8edca29fcf6d1d563eaabc503842b5512.The diff therefore includes both source PRs, not just conflict-resolution edits. The original PRs remain open for their respective reviews. The integration changes are in the merge commit; the two parent revisions identify the source implementations.
Why
Relates to #71 and #351; integrates #356 and #370.
Both implementations modify the GenRM component, manager and shared engine lifecycle. A mechanical merge could expose unpublished or draining engines through canonical discovery, bypass legacy scoring behavior and in-flight accounting through the gateway, or lose placement-group ownership during cleanup. These paths need consistent behavior before the implementations can run together.
How
REMOVED, retain unfinished cleanup for reconciliation, and allocate a fresh PG when recovering a failed elastic engine. Preserve shared Teacher PG ownership until the last worker is released.--offload-rollout) and multi-GPU elastic engines before acquiring the training lifecycle lock or allocating resources. Dynamic state and cleanup checks remain inside lifecycle serialization.Elastic scaling retains #370's scope: single-GPU elastic engines and drain accounting within one Gateway. Consistent direct-client routing does not establish direct-client or cross-Gateway graceful drain. This integration does not add live elastic scaling for pools that participate in training offload.
Testing
Local environment: macOS arm64, Python 3.12.13, Ray 2.59.0, pytest 9.1.1, CPU only; Python commands ran through
uvin a temporary environment. No GPU allocation was used for this integration.pre-commit run --all-files --show-diff-on-failuremainatf268f427a31844f2d0d54ad7374acfa6edce23e9git merge-treeThe four main-suite skips concern an unavailable Rollout import. The additional skips concern unavailable SGLang, Rollout import dependencies or the TransferQueue sampler API. The full repository test suite was not run locally. Real SGLang execution and multi-node GPU training have not been revalidated on this integrated head; inherited evidence in the source PRs and
demos/task4_genrm/describes its original producing revisions and is not a new integration result. Remote CI results must be assessed separately.Test and build commands
Run the Python commands with the project's test dependencies installed in a
uvenvironment:TEST_PYTHONis the path to the test environment's Python executable.pre-commit run --all-filespassespytest tests/)Type of Change
Screenshots / Logs