starforge-cli 0.1.6__tar.gz → 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/PKG-INFO +2 -2
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/pyproject.toml +2 -2
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/__init__.py +1 -1
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/cli.py +2 -2
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/common.py +16 -20
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/exp.py +10 -13
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/init.py +9 -9
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/jobs.py +12 -4
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/plugin.py +0 -1
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/recipe.py +1 -1
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/sweep.py +1 -1
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/new_experiment.py +5 -5
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/recipe_lock.py +1 -1
- starforge_cli-0.1.6/starforge_cli/project.py → starforge_cli-0.1.7/starforge_cli/repo.py +28 -24
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/experiment-template/README.md +1 -1
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/spec_builder.py +8 -11
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/.gitignore +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/README.md +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/api_client.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/auth.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/catalog.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/cli_ui.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/client_device.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/__init__.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/admin.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/bench.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/dataset.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/login.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/serve.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/commands/submit.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/config_resolve.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/data_prep.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/packing.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/plugins_lock.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/agent-run.py.tmpl +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/custom-framework/train.sh +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/experiment-template/.gitkeep +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/experiment-template/config.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/common/README.md +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/common/__init__.py +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/README.md +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/README.md +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/distillation_math.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/grpo_lora.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/grpo_math_1B.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/grpo_megatron.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/grpo_noncolocated.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/grpo_sliding_puzzle.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/ppo_math_1B.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/rm.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/sft.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/models/README.md +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/models/qwen3.5-4b.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/models/qwen3.5-9b.yaml +0 -0
- {starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/gitignore +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: starforge-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: StarForge(星锻)命令行客户端:pip 安装后用 sf init 创建微调项目,再提交训练。
|
|
5
5
|
Project-URL: Homepage, https://github.com/wccdev/starforge
|
|
6
6
|
Project-URL: Documentation, https://github.com/wccdev/starforge
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Requires-Dist: datasets>=2.18
|
|
20
20
|
Requires-Dist: pyyaml>=6
|
|
21
|
-
Requires-Dist: starforge-core==0.
|
|
21
|
+
Requires-Dist: starforge-core==0.2.0
|
|
22
22
|
Requires-Dist: typer>=0.12
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: fastapi>=0.110; extra == 'dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "starforge-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.7"
|
|
4
4
|
description = "StarForge(星锻)命令行客户端:pip 安装后用 sf init 创建微调项目,再提交训练。"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -17,7 +17,7 @@ classifiers = [
|
|
|
17
17
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
18
18
|
]
|
|
19
19
|
dependencies = [
|
|
20
|
-
"starforge-core==0.
|
|
20
|
+
"starforge-core==0.2.0",
|
|
21
21
|
"typer>=0.12",
|
|
22
22
|
"datasets>=2.18",
|
|
23
23
|
"pyyaml>=6",
|
|
@@ -36,12 +36,12 @@ app = typer.Typer(
|
|
|
36
36
|
# ----------------------------- 项目与身份 -----------------------------
|
|
37
37
|
from starforge_cli.commands import init as init_cmd # noqa: E402
|
|
38
38
|
|
|
39
|
-
app.command(help="创建 StarForge
|
|
39
|
+
app.command(help="创建 StarForge 仓库(experiments/ + 官方基底 + common 骨架)")(init_cmd.init)
|
|
40
40
|
app.command(help="登录 StarForge")(login.login)
|
|
41
41
|
app.command(help="登出")(login.logout)
|
|
42
42
|
|
|
43
43
|
# ----------------------------- 实验资产(纯本地)-----------------------------
|
|
44
|
-
app.command(help="
|
|
44
|
+
app.command(help="列出仓库里的实验目录")(exp.ls)
|
|
45
45
|
app.command(help="新建实验(--from fork 现成实验;--method 来自 SDK recipe catalog)")(exp.new)
|
|
46
46
|
app.command(name="methods", help="列出可用的后训练方法与它们的超参")(exp.methods)
|
|
47
47
|
app.command(help="校验实验 config(提交前本地检查)")(exp.validate)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
-
from enum import Enum
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import Optional
|
|
8
7
|
|
|
@@ -12,24 +11,24 @@ from starforge_cli import cli_ui
|
|
|
12
11
|
|
|
13
12
|
#: StarForge 项目标记文件:`sf init` 生成,项目发现的唯一依据。
|
|
14
13
|
#: CLI 已 pip 化分发(starforge-cli),不再假设自己被 clone 在项目仓库里。
|
|
15
|
-
|
|
14
|
+
REPO_MARKER = "starforge.yaml"
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
def project_root() -> Path:
|
|
19
18
|
"""从 cwd 向上定位含 starforge.yaml 的项目根(类比 git 的仓库发现)。
|
|
20
19
|
|
|
21
20
|
找不到即失败并给出可执行指引——所有需要项目上下文的命令都经此收口;
|
|
22
|
-
login / status 等全局命令不触碰它。
|
|
21
|
+
login / status 等全局命令不触碰它。SF_REPO_ROOT 环境变量可显式覆盖
|
|
23
22
|
(CI / 脚本场景免 cd)。
|
|
24
23
|
"""
|
|
25
|
-
if env := os.environ.get("
|
|
24
|
+
if env := os.environ.get("SF_REPO_ROOT"):
|
|
26
25
|
p = Path(env).resolve()
|
|
27
|
-
if (p /
|
|
26
|
+
if (p / REPO_MARKER).is_file():
|
|
28
27
|
return p
|
|
29
|
-
cli_ui.fail(f"
|
|
28
|
+
cli_ui.fail(f"SF_REPO_ROOT={env} 不是 StarForge 仓库(缺 {REPO_MARKER})")
|
|
30
29
|
cur = Path.cwd().resolve()
|
|
31
30
|
for cand in (cur, *cur.parents):
|
|
32
|
-
if (cand /
|
|
31
|
+
if (cand / REPO_MARKER).is_file():
|
|
33
32
|
return cand
|
|
34
33
|
cli_ui.fail(
|
|
35
34
|
"当前目录不在 StarForge 项目内",
|
|
@@ -56,22 +55,21 @@ def _root() -> Path:
|
|
|
56
55
|
|
|
57
56
|
def project_name() -> str:
|
|
58
57
|
"""当前项目的 starforge.yaml name。提交到 console 的 project 只走这里。"""
|
|
59
|
-
from starforge_cli.
|
|
58
|
+
from starforge_cli.repo import RepoError, load_project_name
|
|
60
59
|
|
|
61
60
|
try:
|
|
62
61
|
return load_project_name(_root())
|
|
63
|
-
except
|
|
62
|
+
except RepoError as exc:
|
|
64
63
|
cli_ui.fail(str(exc))
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
projects = "projects"
|
|
66
|
+
#: 实验目录的唯一存放位置(历史上还支持 projects/,已与 experiments/ 合并)。
|
|
67
|
+
EXPERIMENTS_DIR = "experiments"
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
def resolve_exp(name: str) -> str:
|
|
73
|
-
"""把实验名解析为相对仓库根的路径,接受 'experiments/x' / '
|
|
74
|
-
cands = [name] if "/" in name else [f"
|
|
71
|
+
"""把实验名解析为相对仓库根的路径,接受 'experiments/x' / 'x'。"""
|
|
72
|
+
cands = [name] if "/" in name else [f"{EXPERIMENTS_DIR}/{name}"]
|
|
75
73
|
for c in cands:
|
|
76
74
|
if (_root() / c).is_dir():
|
|
77
75
|
return c
|
|
@@ -79,12 +77,10 @@ def resolve_exp(name: str) -> str:
|
|
|
79
77
|
|
|
80
78
|
|
|
81
79
|
def list_exps() -> list[str]:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
out += [p.name for p in base.iterdir() if p.is_dir()]
|
|
87
|
-
return sorted(set(out))
|
|
80
|
+
base = _root() / EXPERIMENTS_DIR
|
|
81
|
+
if not base.is_dir():
|
|
82
|
+
return []
|
|
83
|
+
return sorted({p.name for p in base.iterdir() if p.is_dir()})
|
|
88
84
|
|
|
89
85
|
|
|
90
86
|
def list_profiles() -> list[str]:
|
|
@@ -11,15 +11,15 @@ from starforge_cli.new_experiment import NewExperimentError, create_experiment
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def ls() -> None:
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
"""列出仓库里的实验目录。"""
|
|
15
|
+
base = common.ROOT / common.EXPERIMENTS_DIR
|
|
16
|
+
if not base.is_dir():
|
|
17
|
+
typer.echo(f"[{common.EXPERIMENTS_DIR}] (0)")
|
|
18
|
+
return
|
|
19
|
+
exps = sorted(p.name for p in base.iterdir() if p.is_dir())
|
|
20
|
+
typer.echo(f"\n[{common.EXPERIMENTS_DIR}] ({len(exps)})")
|
|
21
|
+
for e in exps:
|
|
22
|
+
typer.echo(f" - {e}")
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def new(
|
|
@@ -37,9 +37,6 @@ def new(
|
|
|
37
37
|
"--framework-version",
|
|
38
38
|
help="精确框架版本;必须是 recipe catalog 已发布版本",
|
|
39
39
|
),
|
|
40
|
-
kind: common.Kind = typer.Option(
|
|
41
|
-
common.Kind.experiments, "--kind", help="experiments 或 projects"
|
|
42
|
-
),
|
|
43
40
|
) -> None:
|
|
44
41
|
"""新建实验(--from fork 现成实验;--method 来自 SDK recipe catalog)。"""
|
|
45
42
|
if from_exp and method != "nemo-rl/grpo":
|
|
@@ -52,7 +49,7 @@ def new(
|
|
|
52
49
|
try:
|
|
53
50
|
create_experiment(
|
|
54
51
|
common.ROOT,
|
|
55
|
-
|
|
52
|
+
common.EXPERIMENTS_DIR,
|
|
56
53
|
name,
|
|
57
54
|
src=src,
|
|
58
55
|
method=method,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""`sf init`:交互式创建 StarForge
|
|
1
|
+
"""`sf init`:交互式创建 StarForge 仓库。"""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
@@ -7,25 +7,25 @@ from typing import Optional
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
9
|
from starforge_cli import cli_ui
|
|
10
|
-
from starforge_cli.
|
|
10
|
+
from starforge_cli.repo import InitError, init_repo
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def init(
|
|
14
14
|
directory: Optional[str] = typer.Argument(
|
|
15
|
-
None, help="
|
|
15
|
+
None, help="仓库目录(省略则交互询问;. 表示当前目录)"
|
|
16
16
|
),
|
|
17
|
-
name: Optional[str] = typer.Option(None, "--name", help="
|
|
17
|
+
name: Optional[str] = typer.Option(None, "--name", help="项目名,写入 starforge.yaml(默认目录名)"),
|
|
18
18
|
no_git: bool = typer.Option(False, "--no-git", help="不初始化 git 仓库"),
|
|
19
19
|
yes: bool = typer.Option(False, "--yes", "-y", help="非交互:全部取默认值"),
|
|
20
20
|
) -> None:
|
|
21
|
-
"""创建 StarForge
|
|
21
|
+
"""创建 StarForge 仓库:experiments/ + configs/(官方基底)+ common/ 骨架。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
仓库是独立的 git 仓库——实验配置与共享代码归你和团队所有,
|
|
24
24
|
与 CLI 工具的版本解耦(升级 CLI 只需 pip install -U starforge-cli)。
|
|
25
25
|
"""
|
|
26
26
|
target = (directory or "").strip()
|
|
27
27
|
if not target and not yes:
|
|
28
|
-
target = typer.prompt("
|
|
28
|
+
target = typer.prompt("仓库目录", default="my-starforge-lab")
|
|
29
29
|
if not target:
|
|
30
30
|
target = "my-starforge-lab"
|
|
31
31
|
dest = Path(target).expanduser()
|
|
@@ -39,11 +39,11 @@ def init(
|
|
|
39
39
|
use_git = typer.confirm("初始化 git 仓库?", default=True)
|
|
40
40
|
|
|
41
41
|
try:
|
|
42
|
-
root =
|
|
42
|
+
root = init_repo(dest if target != "." else Path.cwd(), name=project_name, git=use_git)
|
|
43
43
|
except InitError as e:
|
|
44
44
|
cli_ui.fail(str(e))
|
|
45
45
|
|
|
46
|
-
typer.secho(f"✓ StarForge
|
|
46
|
+
typer.secho(f"✓ StarForge 仓库已创建:{root}", fg=typer.colors.GREEN)
|
|
47
47
|
typer.echo("下一步:")
|
|
48
48
|
if target not in (".",):
|
|
49
49
|
typer.echo(f" cd {target}")
|
|
@@ -160,8 +160,13 @@ def job_stop(
|
|
|
160
160
|
job_id: str = typer.Argument(..., help="作业 ID"),
|
|
161
161
|
) -> None:
|
|
162
162
|
gate()
|
|
163
|
-
api_client.job_control_via_server("stop", job_id)
|
|
164
|
-
|
|
163
|
+
res = api_client.job_control_via_server("stop", job_id)
|
|
164
|
+
# 服务端用 stopped 区分「真停了」与「本来就是终态」——一律打印「已停止」会让人
|
|
165
|
+
# 以为卡放出来了。老服务端不返回这个字段,缺失时按真停了处理。
|
|
166
|
+
if res.get("stopped", True):
|
|
167
|
+
typer.secho("✓ 已停止作业", fg=typer.colors.GREEN)
|
|
168
|
+
else:
|
|
169
|
+
typer.secho(f"· 作业已是终态({res.get('status') or '未知'}),无需停止", fg=typer.colors.YELLOW)
|
|
165
170
|
|
|
166
171
|
|
|
167
172
|
@job_app.command("pause", help="暂停作业(保留 checkpoint,可继续)")
|
|
@@ -187,8 +192,11 @@ def job_delete(
|
|
|
187
192
|
job_id: str = typer.Argument(..., help="作业 ID"),
|
|
188
193
|
) -> None:
|
|
189
194
|
gate()
|
|
190
|
-
api_client.job_control_via_server("delete", job_id)
|
|
191
|
-
|
|
195
|
+
res = api_client.job_control_via_server("delete", job_id)
|
|
196
|
+
if res.get("removed_db", True):
|
|
197
|
+
typer.secho("✓ 已删除记录", fg=typer.colors.GREEN)
|
|
198
|
+
else:
|
|
199
|
+
typer.secho("· 台账记录未变(可能已被删除)", fg=typer.colors.YELLOW)
|
|
192
200
|
|
|
193
201
|
|
|
194
202
|
@job_app.command("cancel-all", help="停止我所有运行中 / 排队中的作业")
|
|
@@ -54,7 +54,7 @@ def recipe_status(
|
|
|
54
54
|
exp: Optional[str] = typer.Argument(
|
|
55
55
|
None, autocompletion=common.complete_exp, help="实验名或路径;省略则需 --all"
|
|
56
56
|
),
|
|
57
|
-
all_exps: bool = typer.Option(False, "--all", help="扫描 experiments/
|
|
57
|
+
all_exps: bool = typer.Option(False, "--all", help="扫描 experiments/ 与 smoke/"),
|
|
58
58
|
server: bool = typer.Option(False, "--server", help="再与 Console catalog 握手对照"),
|
|
59
59
|
) -> None:
|
|
60
60
|
manager = RecipeLockManager()
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- **不造第二条提交路径**:每个变体就是一次普通 `sf submit`(同一份 spec 构建、
|
|
5
5
|
校验、打包、配额准入、容量排队),服务端零特殊逻辑。sweep 只是「展开 + 循环 +
|
|
6
6
|
分组标识」。
|
|
7
|
-
- 分组复用现成机制:project 来自 starforge.yaml(web
|
|
7
|
+
- 分组复用现成机制:project 来自 starforge.yaml(web 项目页按项目聚合)+
|
|
8
8
|
client_meta.sweep_id / sweep_params(服务端落 jobs.sweep_id 索引列,
|
|
9
9
|
一键停止 / 徽标展示用)。
|
|
10
10
|
- 配额不足的变体自动入队(persist_and_enqueue),不会失败——批量提交天然
|
|
@@ -12,12 +12,12 @@ class NewExperimentError(Exception):
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _resolve_src_dir(repo_root: Path, src: str) -> Path:
|
|
15
|
-
for c in (src, f"experiments/{src}"
|
|
15
|
+
for c in (src, f"experiments/{src}"):
|
|
16
16
|
p = repo_root / c
|
|
17
17
|
if p.is_dir():
|
|
18
18
|
return p
|
|
19
19
|
raise NewExperimentError(
|
|
20
|
-
f"找不到来源实验: {src}(试过 {src} / experiments/{src}
|
|
20
|
+
f"找不到来源实验: {src}(试过 {src} / experiments/{src})"
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
|
|
@@ -135,7 +135,7 @@ def _create_from_template(
|
|
|
135
135
|
# get_recipe 的报错已列出可用值,并对无前缀裸名给出两段式提示。
|
|
136
136
|
raise NewExperimentError(f"--method 非法: {exc}") from exc
|
|
137
137
|
# 实验模板随 CLI 包分发(starforge_cli/scaffold/),不依赖项目内容。
|
|
138
|
-
from starforge_cli.
|
|
138
|
+
from starforge_cli.repo import experiment_template
|
|
139
139
|
|
|
140
140
|
template = experiment_template()
|
|
141
141
|
if not template.is_dir():
|
|
@@ -180,8 +180,8 @@ def create_experiment(
|
|
|
180
180
|
framework_version: str = "",
|
|
181
181
|
) -> None:
|
|
182
182
|
"""新建或 fork 实验;失败时抛 NewExperimentError。"""
|
|
183
|
-
if kind
|
|
184
|
-
raise NewExperimentError("
|
|
183
|
+
if kind != "experiments":
|
|
184
|
+
raise NewExperimentError("实验目录只能建在 experiments/ 下")
|
|
185
185
|
|
|
186
186
|
if src:
|
|
187
187
|
_fork_experiment(repo_root, kind, name, src)
|
|
@@ -20,7 +20,7 @@ LOCK_FILE = "recipe.lock.json"
|
|
|
20
20
|
LOCK_VERSION = "forge/recipe-lock/v3"
|
|
21
21
|
_LEGACY_LOCK_VERSION = "forge/recipe-lock/v2"
|
|
22
22
|
_SUPPORTED_LOCK_VERSIONS = (LOCK_VERSION, _LEGACY_LOCK_VERSION)
|
|
23
|
-
_EXPERIMENT_KINDS = ("experiments", "
|
|
23
|
+
_EXPERIMENT_KINDS = ("experiments", "smoke")
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class LockState(str, Enum):
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
"""StarForge
|
|
1
|
+
"""StarForge 仓库脚手架:`sf init` 的实现。
|
|
2
2
|
|
|
3
3
|
CLI 经 pip 分发(starforge-cli),用户不 clone 工具仓库;`sf init` 生成
|
|
4
|
-
|
|
4
|
+
自包含的仓库目录(自己的 git 仓库),布局与作业包白名单严格对应:
|
|
5
5
|
|
|
6
6
|
my-lab/
|
|
7
|
-
├── starforge.yaml
|
|
8
|
-
├── experiments/ 实验目录(sf new
|
|
7
|
+
├── starforge.yaml 仓库标记,并声明本仓库的项目名(项目发现的唯一依据)
|
|
8
|
+
├── experiments/ 实验目录(sf new 在此创建,一个目录 = 一次实验/作业)
|
|
9
9
|
├── configs/ 官方基底 + 模型片段(脚手架落地,用户可调可 pin)
|
|
10
|
-
├── common/
|
|
10
|
+
├── common/ 仓库共享代码(数据脚本 / 环境 / 奖励)
|
|
11
11
|
└── .gitignore
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
仓库与项目的关系:仓库是磁盘上的容器,项目是 starforge.yaml 声明的名字,提交后
|
|
14
|
+
在 console 侧按 @<用户名>/<项目名> 聚合出「项目」。项目名不要求全局唯一——用户名
|
|
15
|
+
编入分组键,不同用户的同名项目互不干扰。
|
|
16
|
+
|
|
17
|
+
平台 bootstrap/runner 不落地到仓库,也不由 CLI 上传;console 在准入后注入
|
|
14
18
|
内容寻址 Job Capsule,用户代码不能覆盖平台入口。
|
|
15
19
|
"""
|
|
16
20
|
from __future__ import annotations
|
|
@@ -24,31 +28,31 @@ from pathlib import Path
|
|
|
24
28
|
|
|
25
29
|
import yaml
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
REPO_MARKER = "starforge.yaml"
|
|
28
32
|
# 与 console 分组键一致:会进 URL / 路径,只允许安全字符。
|
|
29
33
|
_PROJECT_NAME_RE = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
class
|
|
36
|
+
class RepoError(RuntimeError):
|
|
33
37
|
pass
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
def load_project_name(root: Path) -> str:
|
|
37
|
-
"""读 starforge.yaml 的 name
|
|
38
|
-
path = root /
|
|
41
|
+
"""读 starforge.yaml 的 name(本仓库的项目名)。提交到 console 的 project 只来自这里。"""
|
|
42
|
+
path = root / REPO_MARKER
|
|
39
43
|
if not path.is_file():
|
|
40
|
-
raise
|
|
44
|
+
raise RepoError(f"{root} 不是 StarForge 仓库(缺 {REPO_MARKER})")
|
|
41
45
|
try:
|
|
42
46
|
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
43
47
|
except yaml.YAMLError as exc:
|
|
44
|
-
raise
|
|
48
|
+
raise RepoError(f"{path} 无法解析:{exc}") from exc
|
|
45
49
|
if not isinstance(data, dict):
|
|
46
|
-
raise
|
|
50
|
+
raise RepoError(f"{path} 根节点必须是对象")
|
|
47
51
|
name = str(data.get("name") or "").strip()
|
|
48
52
|
if not name:
|
|
49
|
-
raise
|
|
53
|
+
raise RepoError(f"{path} 缺少 name")
|
|
50
54
|
if name in (".", "..") or not _PROJECT_NAME_RE.match(name):
|
|
51
|
-
raise
|
|
55
|
+
raise RepoError(f"非法项目名 {name!r}(仅允许字母数字与 . _ -)")
|
|
52
56
|
return name
|
|
53
57
|
|
|
54
58
|
|
|
@@ -66,14 +70,14 @@ class InitError(RuntimeError):
|
|
|
66
70
|
pass
|
|
67
71
|
|
|
68
72
|
|
|
69
|
-
def
|
|
70
|
-
"""在 dest 生成 StarForge
|
|
73
|
+
def init_repo(dest: Path, *, name: str = "", git: bool = True) -> Path:
|
|
74
|
+
"""在 dest 生成 StarForge 仓库;目录已是仓库或非空冲突时报错。"""
|
|
71
75
|
dest = dest.resolve()
|
|
72
|
-
if (dest /
|
|
73
|
-
raise InitError(f"{dest} 已经是 StarForge
|
|
76
|
+
if (dest / REPO_MARKER).is_file():
|
|
77
|
+
raise InitError(f"{dest} 已经是 StarForge 仓库")
|
|
74
78
|
if dest.exists() and any(dest.iterdir()):
|
|
75
79
|
# 允许在非空目录初始化(如已有 README 的空仓库),但拒绝覆盖关键路径
|
|
76
|
-
for clash in ("experiments", "configs", "common",
|
|
80
|
+
for clash in ("experiments", "configs", "common", REPO_MARKER):
|
|
77
81
|
if (dest / clash).exists():
|
|
78
82
|
raise InitError(f"{dest} 下已存在 {clash},拒绝覆盖;换个目录或清理后重试")
|
|
79
83
|
dest.mkdir(parents=True, exist_ok=True)
|
|
@@ -93,8 +97,8 @@ def init_project(dest: Path, *, name: str = "", git: bool = True) -> Path:
|
|
|
93
97
|
cli_version = _pkg_version("starforge-cli")
|
|
94
98
|
except Exception: # noqa: BLE001 — 源码运行(未安装分发元数据)
|
|
95
99
|
cli_version = "dev"
|
|
96
|
-
(dest /
|
|
97
|
-
"# StarForge
|
|
100
|
+
(dest / REPO_MARKER).write_text(
|
|
101
|
+
"# StarForge 仓库标记:`sf` 命令以此发现仓库根,勿删除。\n"
|
|
98
102
|
f'apiVersion: forge/project/v1\n'
|
|
99
103
|
f'name: {project_name}\n'
|
|
100
104
|
f'created_by: starforge-cli {cli_version}\n',
|
|
@@ -104,7 +108,7 @@ def init_project(dest: Path, *, name: str = "", git: bool = True) -> Path:
|
|
|
104
108
|
if not readme.exists():
|
|
105
109
|
readme.write_text(
|
|
106
110
|
f"# {project_name}\n\n"
|
|
107
|
-
"StarForge
|
|
111
|
+
"StarForge(星锻)微调仓库。常用命令:\n\n"
|
|
108
112
|
"```bash\n"
|
|
109
113
|
"sf new my-exp --method nemo-rl/grpo # 新建实验\n"
|
|
110
114
|
"sf validate my-exp # 本地校验\n"
|
|
@@ -112,7 +116,7 @@ def init_project(dest: Path, *, name: str = "", git: bool = True) -> Path:
|
|
|
112
116
|
"sf job logs # 跟随日志\n"
|
|
113
117
|
"```\n\n"
|
|
114
118
|
"目录说明:`experiments/` 实验本体;`configs/` 官方基底与模型片段;\n"
|
|
115
|
-
"`common/`
|
|
119
|
+
"`common/` 仓库共享代码(数据脚本 / Agent 环境 / 奖励函数)。\n",
|
|
116
120
|
encoding="utf-8",
|
|
117
121
|
)
|
|
118
122
|
|
|
@@ -157,7 +157,7 @@ def materialize_pools(
|
|
|
157
157
|
roles = [f"{role}:{pool_name}"] if role else []
|
|
158
158
|
return name, pools, roles
|
|
159
159
|
|
|
160
|
-
missing = [raw for raw, (role, _, _) in zip(exprs, parsed) if not role]
|
|
160
|
+
missing = [raw for raw, (role, _, _) in zip(exprs, parsed, strict=False) if not role]
|
|
161
161
|
if missing:
|
|
162
162
|
raise SpecError(
|
|
163
163
|
f"多个 --profile 时每条都要 role= 前缀(角色到池的映射),缺前缀:{missing[0]!r}"
|
|
@@ -231,16 +231,13 @@ def build_spec(
|
|
|
231
231
|
raise SpecError(f"方法 {r.id} 使用 platform observability,不允许 --observability-url")
|
|
232
232
|
if r.framework == "custom" and not selected_image:
|
|
233
233
|
raise SpecError("custom recipe 要求显式提供 --image")
|
|
234
|
-
if r.framework in {"verl", "trl"} and operation == "train":
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
if not value.strip()
|
|
243
|
-
]
|
|
234
|
+
if r.framework in {"verl", "trl", "openrlhf"} and operation == "train":
|
|
235
|
+
required = [("--model", base_model), ("--train-data", train_data)]
|
|
236
|
+
# openrlhf:RL 的 eval.dataset 依赖 remote reward(上游硬约束),验证集是
|
|
237
|
+
# 可选项,约束由服务端 adapter 在编译期裁决;verl/trl 仍然三项必填。
|
|
238
|
+
if r.framework != "openrlhf":
|
|
239
|
+
required.append(("--validation-data", validation_data))
|
|
240
|
+
missing = [flag for flag, value in required if not value.strip()]
|
|
244
241
|
if missing:
|
|
245
242
|
raise SpecError(f"{r.framework} recipe 要求显式提供:{' '.join(missing)}")
|
|
246
243
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/custom-framework/train.sh
RENAMED
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/experiment-template/.gitkeep
RENAMED
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/experiment-template/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/common/__init__.py
RENAMED
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/README.md
RENAMED
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/rm.yaml
RENAMED
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/base/sft.yaml
RENAMED
|
File without changes
|
{starforge_cli-0.1.6 → starforge_cli-0.1.7}/starforge_cli/scaffold/project/configs/models/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|