feat(tuning): release V0.8.2 Agent 界面重构
This commit is contained in:
@@ -18,10 +18,12 @@ from .process import GpuLease, ResourceBusyError, terminate_process
|
||||
from .schema import (
|
||||
BASE_REWARD_CONFIGURATION,
|
||||
merge_proposal,
|
||||
validate_configuration_constraints,
|
||||
validate_constraints,
|
||||
validate_proposal,
|
||||
)
|
||||
from .scoring import DEFAULT_OBJECTIVE_WEIGHTS, score_evaluation, validate_objective_weights
|
||||
from .storage import TuningStorage, now_iso
|
||||
from .storage import StorageConflict, TuningStorage, now_iso
|
||||
from .study import OptunaStudies
|
||||
from .tensorboard import ingest_scalars
|
||||
|
||||
@@ -98,7 +100,7 @@ class TuningManager:
|
||||
)
|
||||
):
|
||||
raise TuningError("gpuIds 必须是非空非负整数数组")
|
||||
trial_count = self._integer(payload, "trialCount", 12, 4, 20)
|
||||
trial_count = self._integer(payload, "trialCount", 12, 1, 100)
|
||||
rung0 = self._integer(payload, "initialIterations", 300, 1, 1000000)
|
||||
rung1 = self._integer(payload, "middleIterations", 900, rung0, 1000000)
|
||||
rung2 = self._integer(payload, "finalIterations", 2000, rung1, 1000000)
|
||||
@@ -159,6 +161,15 @@ class TuningManager:
|
||||
session["trials"] = self.storage.list_trials(session_id)
|
||||
session["proposals"] = self.storage.list_proposals(session_id)
|
||||
session["audit"] = self.storage.audit_events(session_id)
|
||||
control = self.storage.get_control(session_id)
|
||||
current = next(
|
||||
(trial for trial in session["trials"] if trial["id"] == session["currentTrialId"]),
|
||||
None,
|
||||
)
|
||||
control["effectiveAfterCurrent"] = bool(
|
||||
current and current["state"] in {"training", "evaluating"}
|
||||
)
|
||||
session["control"] = control
|
||||
return session
|
||||
|
||||
def list(self) -> list[dict]:
|
||||
@@ -274,8 +285,8 @@ class TuningManager:
|
||||
policy = run_dir / "policy.onnx"
|
||||
if return_code != 0 or checkpoint is None or not policy.is_file():
|
||||
raise TuningError(f"训练失败(返回码 {return_code})或缺少 checkpoint/policy.onnx")
|
||||
self._wait_if_paused(session_id, self.cancel_events[session_id])
|
||||
|
||||
# 暂停只关闭后续 Trial 调度门;已经开始的 Trial 必须连同固定评估一起
|
||||
# 完成,避免把一次单步令牌错误地消耗在半个 Trial 上。
|
||||
eval_output = run_dir / "evaluation.json"
|
||||
eval_command = [
|
||||
self.python,
|
||||
@@ -332,6 +343,9 @@ class TuningManager:
|
||||
score=scored["score"],
|
||||
eligible=scored["eligible"],
|
||||
)
|
||||
best = self._best_highest_rung(session_id)
|
||||
if best is not None:
|
||||
self.storage.update_session(session_id, best_trial_id=best["id"])
|
||||
try:
|
||||
optuna_number = self.studies.record(
|
||||
session_id,
|
||||
@@ -363,6 +377,13 @@ class TuningManager:
|
||||
default=None,
|
||||
)
|
||||
|
||||
def _best_highest_rung(self, session_id: str) -> dict | None:
|
||||
for rung in (2, 1, 0):
|
||||
best = self._best(session_id, rung=rung)
|
||||
if best is not None:
|
||||
return best
|
||||
return None
|
||||
|
||||
def _proposal_context(self, session: dict) -> dict:
|
||||
trials = self.storage.list_trials(session["id"])[-12:]
|
||||
rejected_feedback = [
|
||||
@@ -374,6 +395,7 @@ class TuningManager:
|
||||
"task": session["config"]["taskId"],
|
||||
"objectiveWeights": session["objectiveWeights"],
|
||||
"allowlist": "服务端将验证固定 schema;最多四项修改",
|
||||
"parameterConstraints": self.storage.get_control(session["id"])["constraints"],
|
||||
"rejectedFeedback": rejected_feedback,
|
||||
"trials": [
|
||||
{
|
||||
@@ -414,6 +436,8 @@ class TuningManager:
|
||||
"model": "optuna-fallback",
|
||||
}
|
||||
source = "fallback"
|
||||
constraints = self.storage.get_control(session["id"])["constraints"]
|
||||
result["patch"] = validate_proposal(result["patch"], previous, constraints)
|
||||
proposal = self.storage.create_proposal(
|
||||
session["id"],
|
||||
base_trial_id,
|
||||
@@ -487,7 +511,10 @@ class TuningManager:
|
||||
if t["rung"] == 0 and t["state"] == "completed"
|
||||
]
|
||||
next_number = len({t["number"] for t in completed_rung0})
|
||||
best_score = max((t["score"] or 0.0 for t in completed_rung0), default=0.0)
|
||||
best_score = max(
|
||||
(t["score"] if t["score"] is not None else float("-inf") for t in completed_rung0),
|
||||
default=0.0,
|
||||
)
|
||||
no_improve = 0
|
||||
while (
|
||||
next_number < session["config"]["trialCount"]
|
||||
@@ -495,12 +522,19 @@ class TuningManager:
|
||||
):
|
||||
if cancel.is_set():
|
||||
raise TuningError("session 已取消")
|
||||
self._wait_if_paused(session_id, cancel)
|
||||
base = self._best(session_id, rung=0) or completed_rung0[0]
|
||||
self._wait_for_dispatch(session_id, cancel)
|
||||
control = self.storage.get_control(session_id)
|
||||
base = (
|
||||
self.storage.get_trial(control["activeBaseTrialId"])
|
||||
if control["activeBaseTrialId"]
|
||||
else self._best(session_id, rung=0) or completed_rung0[0]
|
||||
)
|
||||
proposal = self._request_proposal(
|
||||
session, base["rewardConfig"], base["id"], next_number
|
||||
)
|
||||
if session["mode"] == "approval":
|
||||
# 模式允许在 session 运行期间切换,因此每次决策都读取最新持久化值。
|
||||
current_mode = self.storage.get_session(session_id)["mode"]
|
||||
if current_mode == "approval":
|
||||
self.storage.update_session(
|
||||
session_id, state="awaiting_approval", message="等待批准 Agent 建议"
|
||||
)
|
||||
@@ -515,8 +549,9 @@ class TuningManager:
|
||||
else:
|
||||
self.storage.decide_proposal(proposal["id"], "approved", "自动模式")
|
||||
proposal = self.storage.get_proposal(proposal["id"])
|
||||
self._wait_if_paused(session_id, cancel)
|
||||
reward_config = merge_proposal(base["rewardConfig"], proposal["patch"])
|
||||
self._claim_dispatch(session_id, cancel)
|
||||
constraints = self.storage.get_control(session_id)["constraints"]
|
||||
reward_config = merge_proposal(base["rewardConfig"], proposal["patch"], constraints)
|
||||
trial = self.storage.create_trial(
|
||||
session_id,
|
||||
next_number,
|
||||
@@ -527,8 +562,16 @@ class TuningManager:
|
||||
f"trial-{next_number:03d}-rung-0",
|
||||
)
|
||||
result = self._execute_trial(session, trial)
|
||||
if result["eligible"] and (result["score"] or -999) > best_score + 0.01:
|
||||
best_score = result["score"]
|
||||
if control["activeBaseTrialId"]:
|
||||
self.storage.set_active_base(session_id, None)
|
||||
self._pause_after_step(session_id)
|
||||
result_score = result["score"]
|
||||
if (
|
||||
result["eligible"]
|
||||
and result_score is not None
|
||||
and result_score > best_score + 0.01
|
||||
):
|
||||
best_score = result_score
|
||||
no_improve = 0
|
||||
else:
|
||||
no_improve += 1
|
||||
@@ -537,13 +580,17 @@ class TuningManager:
|
||||
|
||||
# Promote top configurations; each new rung resumes its own previous checkpoint.
|
||||
for rung in (1, 2):
|
||||
self._wait_if_paused(session_id, cancel)
|
||||
previous = [
|
||||
t
|
||||
for t in self.storage.list_trials(session_id)
|
||||
if t["rung"] == rung - 1 and t["state"] == "completed" and t["eligible"]
|
||||
]
|
||||
previous.sort(key=lambda t: t["score"] or -999, reverse=True)
|
||||
previous.sort(
|
||||
key=lambda trial: (
|
||||
trial["score"] if trial["score"] is not None else float("-inf")
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
promoted_numbers = {
|
||||
trial["number"]
|
||||
for trial in self.storage.list_trials(session_id)
|
||||
@@ -554,6 +601,7 @@ class TuningManager:
|
||||
continue
|
||||
if cancel.is_set():
|
||||
raise TuningError("session 已取消")
|
||||
self._claim_dispatch(session_id, cancel)
|
||||
root = self._session_root(session_id)
|
||||
checkpoint = root / parent["checkpointPath"]
|
||||
trial = self.storage.create_trial(
|
||||
@@ -566,12 +614,9 @@ class TuningManager:
|
||||
f"trial-{parent['number']:03d}-rung-{rung}",
|
||||
)
|
||||
self._execute_trial(session, trial, checkpoint)
|
||||
self._pause_after_step(session_id)
|
||||
|
||||
best = (
|
||||
self._best(session_id, rung=2)
|
||||
or self._best(session_id, rung=1)
|
||||
or self._best(session_id, rung=0)
|
||||
)
|
||||
best = self._best_highest_rung(session_id)
|
||||
if best is None:
|
||||
raise TuningError("没有通过安全门槛的 trial")
|
||||
preset_name = f"{session['config']['runName']}-{session_id[:8]}"
|
||||
@@ -602,10 +647,42 @@ class TuningManager:
|
||||
self.workers.pop(session_id, None)
|
||||
self.processes.pop(session_id, None)
|
||||
|
||||
def _wait_if_paused(self, session_id: str, cancel: threading.Event) -> None:
|
||||
def _wait_for_dispatch(self, session_id: str, cancel: threading.Event) -> None:
|
||||
"""Wait at a scheduler boundary without consuming a one-shot token."""
|
||||
with self.condition:
|
||||
while self.storage.get_session(session_id)["state"] == "paused" and not cancel.is_set():
|
||||
while not cancel.is_set():
|
||||
session = self.storage.get_session(session_id)
|
||||
control = self.storage.get_control(session_id)
|
||||
if session["state"] == "paused":
|
||||
self.condition.wait(timeout=1.0)
|
||||
continue
|
||||
if control["runPolicy"] == "continuous" or control["dispatchTokens"] > 0:
|
||||
return
|
||||
self.storage.update_session(
|
||||
session_id,
|
||||
state="paused",
|
||||
message="单步 Trial 已完成;等待下一个调度令牌",
|
||||
)
|
||||
self.storage.audit(session_id, "step_gate_waiting", {})
|
||||
self.condition.wait(timeout=1.0)
|
||||
raise TuningError("session 已取消")
|
||||
|
||||
def _claim_dispatch(self, session_id: str, cancel: threading.Event) -> None:
|
||||
while not cancel.is_set():
|
||||
self._wait_for_dispatch(session_id, cancel)
|
||||
if self.storage.use_dispatch_token(session_id):
|
||||
return
|
||||
raise TuningError("session 已取消")
|
||||
|
||||
def _pause_after_step(self, session_id: str) -> None:
|
||||
control = self.storage.get_control(session_id)
|
||||
if control["runPolicy"] == "step" and control["dispatchTokens"] == 0:
|
||||
self.storage.update_session(
|
||||
session_id,
|
||||
state="paused",
|
||||
message="单步 Trial 已完成;后续调度已暂停",
|
||||
)
|
||||
self.storage.audit(session_id, "step_trial_completed", {})
|
||||
|
||||
def approve(self, session_id: str, proposal_id: str, payload: Any) -> dict:
|
||||
proposal = self.storage.get_proposal(proposal_id)
|
||||
@@ -613,11 +690,15 @@ class TuningManager:
|
||||
raise TuningError("proposal 不属于该 session")
|
||||
patch = proposal["patch"]
|
||||
feedback = None
|
||||
base = self.storage.get_trial(proposal["baseTrialId"])
|
||||
constraints = self.storage.get_control(session_id)["constraints"]
|
||||
if isinstance(payload, dict):
|
||||
feedback = payload.get("feedback")
|
||||
if "patch" in payload:
|
||||
base = self.storage.get_trial(proposal["baseTrialId"])
|
||||
patch = validate_proposal(payload["patch"], base["rewardConfig"])
|
||||
patch = payload["patch"]
|
||||
if feedback is not None and (not isinstance(feedback, str) or len(feedback) > 2000):
|
||||
raise TuningError("feedback 无效")
|
||||
patch = validate_proposal(patch, base["rewardConfig"], constraints)
|
||||
if not self.storage.decide_proposal(proposal_id, "approved", feedback, patch):
|
||||
raise TuningError("proposal 已处理")
|
||||
self.storage.audit(
|
||||
@@ -642,22 +723,213 @@ class TuningManager:
|
||||
self.condition.notify_all()
|
||||
return self.detail(session_id)
|
||||
|
||||
def set_mode(self, session_id: str, payload: Any) -> dict:
|
||||
if not isinstance(payload, dict) or payload.get("mode") not in ("automatic", "approval"):
|
||||
raise TuningError("mode 必须是 automatic 或 approval")
|
||||
mode = payload["mode"]
|
||||
with self.condition:
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] not in RUNNING_STATES | {"awaiting_approval", "paused"}:
|
||||
raise TuningError("当前状态不能切换运行模式")
|
||||
previous = session["mode"]
|
||||
if previous == mode:
|
||||
return self.detail(session_id)
|
||||
self.storage.update_session(session_id, mode=mode)
|
||||
approved_ids = []
|
||||
if mode == "automatic":
|
||||
constraints = self.storage.get_control(session_id)["constraints"]
|
||||
for proposal in self.storage.list_proposals(session_id):
|
||||
if proposal["state"] != "pending":
|
||||
continue
|
||||
base = self.storage.get_trial(proposal["baseTrialId"])
|
||||
try:
|
||||
validate_proposal(proposal["patch"], base["rewardConfig"], constraints)
|
||||
except Exception as error:
|
||||
self.storage.decide_proposal(
|
||||
proposal["id"], "rejected", f"参数护栏已变化:{error}"
|
||||
)
|
||||
continue
|
||||
if self.storage.decide_proposal(
|
||||
proposal["id"], "approved", "运行时切换为全自动模式"
|
||||
):
|
||||
approved_ids.append(proposal["id"])
|
||||
if session["state"] == "awaiting_approval":
|
||||
self.storage.update_session(
|
||||
session_id, state="running", message="已切换为全自动模式,继续调参"
|
||||
)
|
||||
self.storage.audit(
|
||||
session_id,
|
||||
"session_mode_changed",
|
||||
{"from": previous, "to": mode, "autoApprovedProposalIds": approved_ids},
|
||||
)
|
||||
self.condition.notify_all()
|
||||
return self.detail(session_id)
|
||||
|
||||
def set_constraints(self, session_id: str, payload: Any) -> dict:
|
||||
if not isinstance(payload, dict) or set(payload) != {"revision", "constraints"}:
|
||||
raise TuningError("参数护栏请求必须包含 revision 与 constraints")
|
||||
revision = payload["revision"]
|
||||
if isinstance(revision, bool) or not isinstance(revision, int) or revision < 0:
|
||||
raise TuningError("constraints revision 必须是非负整数")
|
||||
constraints = validate_constraints(payload["constraints"])
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] not in ACTIVE_SESSION_STATES:
|
||||
raise TuningError("终态 session 不能修改参数护栏")
|
||||
control = self.storage.get_control(session_id)
|
||||
base = None
|
||||
if control["activeBaseTrialId"]:
|
||||
base = self.storage.get_trial(control["activeBaseTrialId"])
|
||||
elif session["currentTrialId"]:
|
||||
base = self.storage.get_trial(session["currentTrialId"])
|
||||
else:
|
||||
base = self._best_highest_rung(session_id)
|
||||
if base is not None:
|
||||
validate_configuration_constraints(base["rewardConfig"], constraints)
|
||||
try:
|
||||
updated = self.storage.replace_constraints(session_id, revision, constraints)
|
||||
except StorageConflict as error:
|
||||
raise ResourceBusyError(str(error)) from error
|
||||
|
||||
rejected = []
|
||||
for proposal in self.storage.list_proposals(session_id):
|
||||
if proposal["state"] != "pending":
|
||||
continue
|
||||
proposal_base = self.storage.get_trial(proposal["baseTrialId"])
|
||||
try:
|
||||
validate_proposal(proposal["patch"], proposal_base["rewardConfig"], constraints)
|
||||
except Exception as error:
|
||||
message = f"参数护栏 revision {updated['constraintsRevision']}:{error}"
|
||||
if self.storage.decide_proposal(proposal["id"], "rejected", message):
|
||||
rejected.append(proposal["id"])
|
||||
self.storage.audit(
|
||||
session_id,
|
||||
"constraints_updated",
|
||||
{
|
||||
"revision": updated["constraintsRevision"],
|
||||
"paths": sorted(constraints),
|
||||
"rejectedProposalIds": rejected,
|
||||
},
|
||||
)
|
||||
if rejected:
|
||||
with self.condition:
|
||||
self.condition.notify_all()
|
||||
return self.detail(session_id)
|
||||
|
||||
def step(self, session_id: str, payload: Any) -> dict:
|
||||
if not isinstance(payload, dict) or payload.get("count", 1) != 1:
|
||||
raise TuningError("单步调度一次只能发放 1 个 Trial 令牌")
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] not in {"paused", "awaiting_approval"}:
|
||||
raise TuningError("请先暂停或等待 Proposal 审批,再执行单步 Trial")
|
||||
control = self.storage.get_control(session_id)
|
||||
if control["dispatchTokens"] > 0:
|
||||
raise TuningError("已有未消费的单步 Trial 令牌")
|
||||
try:
|
||||
self.storage.grant_dispatch_token(session_id)
|
||||
except StorageConflict as error:
|
||||
raise ResourceBusyError(str(error)) from error
|
||||
if session["state"] == "paused":
|
||||
has_pending = any(
|
||||
proposal["state"] == "pending"
|
||||
for proposal in self.storage.list_proposals(session_id)
|
||||
)
|
||||
self.storage.update_session(
|
||||
session_id,
|
||||
state="awaiting_approval" if has_pending else "running",
|
||||
message="单步令牌已就绪;请审批 Proposal"
|
||||
if has_pending
|
||||
else "已授权执行一个 Trial",
|
||||
)
|
||||
self.storage.audit(session_id, "step_token_granted", {"count": 1})
|
||||
with self.condition:
|
||||
self.condition.notify_all()
|
||||
return self.detail(session_id)
|
||||
|
||||
def rollback(self, session_id: str, payload: Any) -> dict:
|
||||
if not isinstance(payload, dict):
|
||||
raise TuningError("rollback 请求体必须是对象")
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] not in {"paused", "awaiting_approval"}:
|
||||
raise TuningError("回滚只能在安全暂停或等待审批时执行")
|
||||
target_id = payload.get("trialId")
|
||||
if payload.get("target") == "best":
|
||||
target_id = session["bestTrialId"] or (self._best_highest_rung(session_id) or {}).get(
|
||||
"id"
|
||||
)
|
||||
if not isinstance(target_id, str):
|
||||
raise TuningError("rollback 必须指定 trialId 或 target=best")
|
||||
target = self.storage.get_trial(target_id)
|
||||
if target["sessionId"] != session_id:
|
||||
raise TuningError("rollback Trial 不属于该 session")
|
||||
if target["state"] != "completed" or not target["eligible"]:
|
||||
raise TuningError("只能回滚到已完成且通过安全门槛的 Trial")
|
||||
checkpoint = payload.get("checkpoint", False)
|
||||
if not isinstance(checkpoint, bool):
|
||||
raise TuningError("checkpoint 必须是布尔值")
|
||||
if checkpoint:
|
||||
path_value = target.get("checkpointPath")
|
||||
if not path_value:
|
||||
raise TuningError("目标 Trial 没有可用 checkpoint")
|
||||
root = self._session_root(session_id)
|
||||
path = (root / path_value).resolve()
|
||||
if not path.is_relative_to(root) or not path.is_file():
|
||||
raise TuningError("目标 checkpoint 不存在或路径非法")
|
||||
|
||||
superseded = []
|
||||
for proposal in self.storage.list_proposals(session_id):
|
||||
if proposal["state"] == "pending" and self.storage.decide_proposal(
|
||||
proposal["id"], "rejected", f"由回滚到 Trial {target_id[:8]} 取代"
|
||||
):
|
||||
superseded.append(proposal["id"])
|
||||
self.storage.set_active_base(session_id, target_id)
|
||||
self.storage.reset_step_gate(session_id)
|
||||
self.storage.update_session(
|
||||
session_id,
|
||||
state="paused",
|
||||
message=f"已回滚到 Trial {target['number']} / Rung {target['rung']};等待单步或继续",
|
||||
)
|
||||
self.storage.audit(
|
||||
session_id,
|
||||
"rollback_selected",
|
||||
{
|
||||
"trialId": target_id,
|
||||
"checkpoint": checkpoint,
|
||||
"supersededProposalIds": superseded,
|
||||
},
|
||||
)
|
||||
with self.condition:
|
||||
self.condition.notify_all()
|
||||
return self.detail(session_id)
|
||||
|
||||
def pause(self, session_id: str) -> dict:
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] not in RUNNING_STATES | {"awaiting_approval"}:
|
||||
raise TuningError("当前状态不能暂停")
|
||||
# 以持久化调度门记录暂停意图,而不是只依赖 session.state。当前 Trial
|
||||
# 的训练/评估会继续完成,下一次 _claim_dispatch 必须等待显式继续或单步。
|
||||
self.storage.reset_step_gate(session_id)
|
||||
self.storage.update_session(
|
||||
session_id, state="paused", message="已暂停后续调度;当前子进程将完成"
|
||||
session_id, state="paused", message="已暂停后续调度;在途 Trial(如有)将完整结束"
|
||||
)
|
||||
return self.detail(session_id)
|
||||
|
||||
def resume(self, session_id: str) -> dict:
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] == "paused":
|
||||
self.storage.update_session(session_id, state="running", message="继续调参")
|
||||
self.storage.set_run_policy(session_id, "continuous")
|
||||
has_pending = any(
|
||||
proposal["state"] == "pending"
|
||||
for proposal in self.storage.list_proposals(session_id)
|
||||
)
|
||||
self.storage.update_session(
|
||||
session_id,
|
||||
state="awaiting_approval" if has_pending else "running",
|
||||
message="请审批待处理 Proposal" if has_pending else "连续调参已恢复",
|
||||
)
|
||||
with self.condition:
|
||||
self.condition.notify_all()
|
||||
elif session["state"] == "interrupted":
|
||||
self.storage.set_run_policy(session_id, "continuous")
|
||||
self.storage.update_session(session_id, state="queued", message="从最近完整结果恢复")
|
||||
self._start_worker(session_id, resume=True)
|
||||
else:
|
||||
@@ -665,7 +937,9 @@ class TuningManager:
|
||||
return self.detail(session_id)
|
||||
|
||||
def cancel(self, session_id: str) -> dict:
|
||||
self.storage.get_session(session_id)
|
||||
session = self.storage.get_session(session_id)
|
||||
if session["state"] not in ACTIVE_SESSION_STATES:
|
||||
return self.detail(session_id)
|
||||
self.storage.update_session(session_id, state="cancelled", message="正在取消")
|
||||
event = self.cancel_events.get(session_id)
|
||||
if event:
|
||||
@@ -678,12 +952,22 @@ class TuningManager:
|
||||
return self.detail(session_id)
|
||||
|
||||
def metrics(
|
||||
self, session_id: str, trial_id: str, tags: list[str] | None, max_points: int
|
||||
self,
|
||||
session_id: str,
|
||||
trial_id: str,
|
||||
tags: list[str] | None,
|
||||
max_points: int,
|
||||
after_step: int | None = None,
|
||||
) -> dict:
|
||||
trial = self.storage.get_trial(trial_id)
|
||||
if trial["sessionId"] != session_id:
|
||||
raise TuningError("trial 不属于该 session")
|
||||
return {"trialId": trial_id, "series": self.storage.metrics(trial_id, tags, max_points)}
|
||||
series = self.storage.metrics(trial_id, tags, max_points, after_step)
|
||||
next_step = max(
|
||||
(point["step"] for item in series for point in item["points"]),
|
||||
default=after_step,
|
||||
)
|
||||
return {"trialId": trial_id, "series": series, "nextStep": next_step}
|
||||
|
||||
def best_artifact(self, session_id: str) -> Path:
|
||||
session = self.storage.get_session(session_id)
|
||||
|
||||
Reference in New Issue
Block a user