starforge-cli 0.1.6__py3-none-any.whl

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.
Files changed (55) hide show
  1. starforge_cli/__init__.py +3 -0
  2. starforge_cli/api_client.py +589 -0
  3. starforge_cli/auth.py +349 -0
  4. starforge_cli/catalog.py +124 -0
  5. starforge_cli/cli.py +74 -0
  6. starforge_cli/cli_ui.py +469 -0
  7. starforge_cli/client_device.py +104 -0
  8. starforge_cli/commands/__init__.py +1 -0
  9. starforge_cli/commands/admin.py +140 -0
  10. starforge_cli/commands/bench.py +94 -0
  11. starforge_cli/commands/common.py +178 -0
  12. starforge_cli/commands/dataset.py +150 -0
  13. starforge_cli/commands/exp.py +213 -0
  14. starforge_cli/commands/init.py +52 -0
  15. starforge_cli/commands/jobs.py +223 -0
  16. starforge_cli/commands/login.py +54 -0
  17. starforge_cli/commands/plugin.py +243 -0
  18. starforge_cli/commands/recipe.py +163 -0
  19. starforge_cli/commands/serve.py +79 -0
  20. starforge_cli/commands/submit.py +467 -0
  21. starforge_cli/commands/sweep.py +154 -0
  22. starforge_cli/config_resolve.py +17 -0
  23. starforge_cli/data_prep.py +60 -0
  24. starforge_cli/new_experiment.py +195 -0
  25. starforge_cli/packing.py +179 -0
  26. starforge_cli/plugins_lock.py +73 -0
  27. starforge_cli/project.py +130 -0
  28. starforge_cli/recipe_lock.py +453 -0
  29. starforge_cli/scaffold/agent-run.py.tmpl +146 -0
  30. starforge_cli/scaffold/custom-framework/train.sh +56 -0
  31. starforge_cli/scaffold/experiment-template/.gitkeep +0 -0
  32. starforge_cli/scaffold/experiment-template/README.md +36 -0
  33. starforge_cli/scaffold/experiment-template/config.yaml +44 -0
  34. starforge_cli/scaffold/project/common/README.md +12 -0
  35. starforge_cli/scaffold/project/common/__init__.py +0 -0
  36. starforge_cli/scaffold/project/configs/README.md +103 -0
  37. starforge_cli/scaffold/project/configs/base/README.md +24 -0
  38. starforge_cli/scaffold/project/configs/base/distillation_math.yaml +284 -0
  39. starforge_cli/scaffold/project/configs/base/grpo_lora.yaml +30 -0
  40. starforge_cli/scaffold/project/configs/base/grpo_math_1B.yaml +470 -0
  41. starforge_cli/scaffold/project/configs/base/grpo_megatron.yaml +43 -0
  42. starforge_cli/scaffold/project/configs/base/grpo_noncolocated.yaml +18 -0
  43. starforge_cli/scaffold/project/configs/base/grpo_sliding_puzzle.yaml +81 -0
  44. starforge_cli/scaffold/project/configs/base/ppo_math_1B.yaml +454 -0
  45. starforge_cli/scaffold/project/configs/base/rm.yaml +224 -0
  46. starforge_cli/scaffold/project/configs/base/sft.yaml +294 -0
  47. starforge_cli/scaffold/project/configs/models/README.md +16 -0
  48. starforge_cli/scaffold/project/configs/models/qwen3.5-4b.yaml +12 -0
  49. starforge_cli/scaffold/project/configs/models/qwen3.5-9b.yaml +10 -0
  50. starforge_cli/scaffold/project/gitignore +11 -0
  51. starforge_cli/spec_builder.py +372 -0
  52. starforge_cli-0.1.6.dist-info/METADATA +40 -0
  53. starforge_cli-0.1.6.dist-info/RECORD +55 -0
  54. starforge_cli-0.1.6.dist-info/WHEEL +4 -0
  55. starforge_cli-0.1.6.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,372 @@
1
+ """从 CLI 参数构建 JobSpec。
2
+
3
+ 客户端构建完整 JobSpec,并在**本地**按 recipe 声明预校验 —— 超参拼错、
4
+ 取值越界在敲回车那一刻就能看到,不必等上传完、排完队、跑起来才发现。
5
+
6
+ 本地校验用的是与服务端**同一份** recipe 目录(都来自 starforge-core),所以不会
7
+ 出现「本地说没问题、服务端说不行」。
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from pathlib import Path
13
+ from typing import Any, Optional
14
+
15
+ from starforge_core import __version__ as CORE_VERSION
16
+ from starforge_core.contract import (
17
+ ArtifactRef,
18
+ DataRef,
19
+ DataSpec,
20
+ FrameworkRef,
21
+ JobSpec,
22
+ JobSpecBody,
23
+ LifecycleSpec,
24
+ Metadata,
25
+ ModelSpec,
26
+ PluginUse,
27
+ Provenance,
28
+ RecipeRef,
29
+ ResourcePool,
30
+ ResourceSpec,
31
+ SourceSpec,
32
+ SpecError,
33
+ exp_basename,
34
+ )
35
+ from starforge_core.recipes import RuntimeArtifact, get_recipe
36
+
37
+
38
+ def parse_set(items: list[str]) -> dict[str, Any]:
39
+ """把 `--set k=v` 解析成超参字典,并按字面量推断类型。
40
+
41
+ 类型推断是必要的:recipe schema 会拒绝 "500"(字符串)作为 max_num_steps,
42
+ 而命令行给过来的一切都是字符串。推断规则刻意保守 —— 只认 bool/int/float,
43
+ 其余保持字符串,避免把 "1e-5" 之外的东西误判。
44
+ """
45
+ out: dict[str, Any] = {}
46
+ for raw in items or []:
47
+ if "=" not in raw:
48
+ raise SpecError(f"--set 需要 key=value 形式,收到 {raw!r}")
49
+ key, _, val = raw.partition("=")
50
+ key, val = key.strip(), val.strip()
51
+ if not key:
52
+ raise SpecError(f"--set 的 key 不能为空: {raw!r}")
53
+ out[key] = _literal(val)
54
+ return out
55
+
56
+
57
+ def _literal(v: str) -> Any:
58
+ low = v.lower()
59
+ if low in ("true", "yes", "on"):
60
+ return True
61
+ if low in ("false", "no", "off"):
62
+ return False
63
+ try:
64
+ return int(v)
65
+ except ValueError:
66
+ pass
67
+ try:
68
+ return float(v)
69
+ except ValueError:
70
+ return v
71
+
72
+
73
+ def parse_pool(raw: str) -> ResourcePool:
74
+ """解析内部资源池字符串 `name:series:nodes:gpus_per_node`。
75
+
76
+ 用户不再手写这个格式——CLI 把 `--profile 名称[:总卡数]` 经注册表物化成
77
+ 这种字符串后传进来(见 materialize_pools);保留字符串形态是为了
78
+ build_spec 的入参与测试稳定。
79
+ """
80
+ parts = [p.strip() for p in raw.split(":")]
81
+ if len(parts) != 4:
82
+ raise SpecError(
83
+ f"资源池需要 name:series:nodes:gpus_per_node 四段,收到 {raw!r}"
84
+ "(例:train:h200:1:4)"
85
+ )
86
+ name, series, nodes, per_node = parts
87
+ for label, v in (("nodes", nodes), ("gpus_per_node", per_node)):
88
+ if not v.isdigit() or int(v) <= 0:
89
+ raise SpecError(f"资源池的 {label} 应为正整数,收到 {v!r}")
90
+ return ResourcePool(name=name, series=series, nodes=int(nodes), gpus_per_node=int(per_node))
91
+
92
+
93
+ def parse_profile_expr(raw: str) -> tuple[str, str, Optional[int]]:
94
+ """解析统一资源参数 `[role=]名称[:总卡数]` → (role, profile_name, gpus)。
95
+
96
+ 例:h200 → ("", "h200", None);h200:4 → ("", "h200", 4);
97
+ rollout=h100:2 → ("rollout", "h100", 2)。
98
+ role 前缀是异构多池的扩展位:不同角色落在不同卡型/池上。
99
+ """
100
+ s = (raw or "").strip()
101
+ role, _, rest = s.partition("=") if "=" in s else ("", "", s)
102
+ role = role.strip()
103
+ name, sep, gpus_s = rest.partition(":")
104
+ name, gpus_s = name.strip(), gpus_s.strip()
105
+ if not name:
106
+ raise SpecError(f"--profile 需要 [role=]名称[:总卡数] 形式,收到 {raw!r}")
107
+ if not sep:
108
+ return role, name, None
109
+ if not gpus_s.isdigit() or int(gpus_s) <= 0:
110
+ raise SpecError(f"--profile 的总卡数应为正整数,收到 {raw!r}(例:h200:4)")
111
+ return role, name, int(gpus_s)
112
+
113
+
114
+ def _shape_for(name: str, gpus: Optional[int], entry: dict) -> tuple[int, int]:
115
+ """由 profile 默认形状与可选的总卡数覆盖推导 (nodes, gpus_per_node)。
116
+
117
+ 不超过单节点容量 → 单节点 N 卡;超过则必须是整节点的倍数——部分占用
118
+ 多个节点的形状没有对应的调度语义,宁可报错也不猜。
119
+ """
120
+ per_node = int(entry.get("gpus_per_node") or 1)
121
+ if gpus is None:
122
+ return int(entry.get("num_nodes") or 1), per_node
123
+ if gpus <= per_node:
124
+ return 1, gpus
125
+ if gpus % per_node == 0:
126
+ return gpus // per_node, per_node
127
+ raise SpecError(
128
+ f"profile「{name}」每节点 {per_node} 卡:要 {gpus} 卡须不超过 {per_node}"
129
+ f"(单节点)或为 {per_node} 的整数倍(整节点)"
130
+ )
131
+
132
+
133
+ def materialize_pools(
134
+ exprs: list[str], registry: dict[str, dict]
135
+ ) -> tuple[str, list[str], list[str]]:
136
+ """把 `--profile [role=]名称[:总卡数]` 物化成 (作业 profile, pools, roles)。
137
+
138
+ series 与默认形状一律取服务端注册表,用户没有途径写出「钉在 A 卡、
139
+ 账记 B 卡」的 spec。单条目 → 单池 "all"(角色由 build_spec 自动推断);
140
+ 多条目是异构多池扩展位:每条必须带 role= 前缀,池名即角色名。
141
+ """
142
+ if not exprs:
143
+ raise SpecError("至少需要一个 --profile(`sf status` 查看可用值)")
144
+ parsed = [parse_profile_expr(e) for e in exprs]
145
+
146
+ known = "、".join(sorted(registry)) or "(注册表为空)"
147
+ for _, name, _ in parsed:
148
+ if name not in registry:
149
+ raise SpecError(f"未知的 profile「{name}」;可用:{known}")
150
+
151
+ if len(parsed) == 1:
152
+ role, name, gpus = parsed[0]
153
+ entry = registry[name]
154
+ nodes, per_node = _shape_for(name, gpus, entry)
155
+ pool_name = role or "all"
156
+ pools = [f"{pool_name}:{entry.get('series') or name}:{nodes}:{per_node}"]
157
+ roles = [f"{role}:{pool_name}"] if role else []
158
+ return name, pools, roles
159
+
160
+ missing = [raw for raw, (role, _, _) in zip(exprs, parsed) if not role]
161
+ if missing:
162
+ raise SpecError(
163
+ f"多个 --profile 时每条都要 role= 前缀(角色到池的映射),缺前缀:{missing[0]!r}"
164
+ "(例:--profile train=h200:8 --profile rollout=h100:2)"
165
+ )
166
+ seen: set[str] = set()
167
+ pools, roles = [], []
168
+ for role, name, gpus in parsed:
169
+ if role in seen:
170
+ raise SpecError(f"角色 {role!r} 重复声明")
171
+ seen.add(role)
172
+ entry = registry[name]
173
+ nodes, per_node = _shape_for(name, gpus, entry)
174
+ pools.append(f"{role}:{entry.get('series') or name}:{nodes}:{per_node}")
175
+ roles.append(f"{role}:{role}")
176
+ # 作业级 env/overrides/pin 目前是单 profile 语义,取第一条的 profile。
177
+ return parsed[0][1], pools, roles
178
+
179
+
180
+ def build_spec(
181
+ exp_rel: str,
182
+ *,
183
+ recipe: str,
184
+ framework_version: str = "",
185
+ user: str = "",
186
+ project: str = "",
187
+ display_name: str = "",
188
+ sets: Optional[list[str]] = None,
189
+ pools: Optional[list[str]] = None,
190
+ roles: Optional[list[str]] = None,
191
+ init_from: str = "",
192
+ on_success: Optional[list[str]] = None,
193
+ image: str = "",
194
+ observability_url: str = "",
195
+ base_model: str = "",
196
+ train_data: str = "",
197
+ validation_data: str = "",
198
+ train_dataset: str = "",
199
+ validation_dataset: str = "",
200
+ operation: str = "train",
201
+ provenance: Optional[dict] = None,
202
+ validate: bool = True,
203
+ plugin_uses: Optional[list[dict]] = None,
204
+ ) -> JobSpec:
205
+ """构建并(默认)本地校验 JobSpec。
206
+
207
+ 校验失败抛 SpecError —— CLI 捕获后打印可读错误,不发起任何网络请求。
208
+ """
209
+ # get_recipe 的报错已列出可用值,并对无前缀裸名给出两段式提示,直接透传。
210
+ r = get_recipe(recipe)
211
+ selected_framework_version = framework_version.strip() or r.runtime.default_version
212
+ selected_runtime = r.runtime.resolve(selected_framework_version)
213
+ requested_image = image.strip()
214
+ if requested_image:
215
+ RuntimeArtifact.from_dict(
216
+ {"kind": "oci", "reference": requested_image},
217
+ field_path="spec.framework.image",
218
+ )
219
+ if r.framework == "custom":
220
+ selected_image = requested_image
221
+ else:
222
+ # 空 = 服务端用 catalog / runtime registry;有值 = 按作业覆盖(服务端再查 allowlist)
223
+ selected_image = requested_image
224
+
225
+ observability = r.adapter_options.get("observability")
226
+ if observability == "external" and not observability_url.strip():
227
+ raise SpecError(
228
+ f"方法 {r.id} 使用 external observability,必须显式提供 --observability-url"
229
+ )
230
+ if observability == "platform" and observability_url.strip():
231
+ raise SpecError(f"方法 {r.id} 使用 platform observability,不允许 --observability-url")
232
+ if r.framework == "custom" and not selected_image:
233
+ raise SpecError("custom recipe 要求显式提供 --image")
234
+ if r.framework in {"verl", "trl"} and operation == "train":
235
+ missing = [
236
+ flag
237
+ for flag, value in (
238
+ ("--model", base_model),
239
+ ("--train-data", train_data),
240
+ ("--validation-data", validation_data),
241
+ )
242
+ if not value.strip()
243
+ ]
244
+ if missing:
245
+ raise SpecError(f"{r.framework} recipe 要求显式提供:{' '.join(missing)}")
246
+
247
+ # 平台数据集引用(<owner>/<name>[@version]):格式在本地就校验,
248
+ # 存在性与可见性由服务端在提交准入时裁决。
249
+ for flag, ref in (("--train-dataset", train_dataset), ("--validation-dataset", validation_dataset)):
250
+ ds_id = ref.strip().partition("@")[0]
251
+ if ref.strip() and ("/" not in ds_id or len([s for s in ds_id.split("/") if s]) != 2):
252
+ raise SpecError(
253
+ f"{flag} 必须是 <owner>/<name>[@version](如 alice/gsm8k@v1),收到 {ref!r};"
254
+ "用 `sf dataset ls` 查看完整 ID"
255
+ )
256
+
257
+ hyperparams = parse_set(sets or [])
258
+ if validate:
259
+ # 与服务端同一份 recipe 声明:不会出现「本地说没问题、服务端说不行」。
260
+ hyperparams = r.validate_hyperparams(hyperparams, strict=True)
261
+
262
+ pool_objs = tuple(parse_pool(p) for p in (pools or []))
263
+ if not pool_objs:
264
+ # CLI 总会由 --profile 物化出资源池(materialize_pools),走到这里
265
+ # 只能是直接调用 build_spec 却漏传 pools。
266
+ raise SpecError("缺少资源池:请传 pools(CLI 由 --profile 名称[:总卡数] 自动物化)")
267
+ role_map: dict[str, str] = {}
268
+ for raw in roles or []:
269
+ if ":" not in raw:
270
+ raise SpecError(f"角色映射需要 role:pool 形式,收到 {raw!r}")
271
+ role, _, pool = raw.partition(":")
272
+ role_map[role.strip()] = pool.strip()
273
+
274
+ if pool_objs and not role_map:
275
+ # 只有一个池时可以合理推断:recipe 声明的角色全落在它上面。
276
+ if len(pool_objs) == 1:
277
+ role_map = {role: pool_objs[0].name for role in r.roles}
278
+ else:
279
+ raise SpecError(
280
+ f"声明了 {len(pool_objs)} 个资源池但缺少角色映射;"
281
+ f"多池请用 --profile role=名称[:总卡数] 声明。方法 {r.id} 的角色:{', '.join(r.roles)}"
282
+ )
283
+ if validate:
284
+ known = {p.name for p in pool_objs}
285
+ for role, pool in role_map.items():
286
+ if role not in r.roles:
287
+ raise SpecError(f"方法 {r.id} 没有角色 {role!r};可用:{', '.join(r.roles)}")
288
+ if pool not in known:
289
+ raise SpecError(f"角色 {role!r} 指向未定义的资源池 {pool!r}")
290
+
291
+ actions = tuple(a.strip() for a in (on_success or []) if a.strip())
292
+ if validate:
293
+ for a in actions:
294
+ if not r.supports(a):
295
+ raise SpecError(
296
+ f"方法 {r.id} 不支持训练后动作 {a!r};支持:{', '.join(r.lifecycle) or '(无)'}"
297
+ )
298
+
299
+ prov = provenance or {}
300
+ return JobSpec(
301
+ metadata=Metadata(
302
+ name=exp_basename(exp_rel),
303
+ project=project or "",
304
+ owner=user or "", # 服务端会权威覆写
305
+ display_name=display_name or "",
306
+ ),
307
+ spec=JobSpecBody(
308
+ recipe=RecipeRef(name=r.id, version=r.version, digest=r.digest, plugins=r.plugins),
309
+ source=SourceSpec(exp=exp_rel),
310
+ framework=FrameworkRef(
311
+ kind=r.framework,
312
+ version=selected_framework_version,
313
+ runtime_id=selected_runtime.runtime_id,
314
+ image=selected_image,
315
+ observability_url=observability_url.strip(),
316
+ ),
317
+ model=ModelSpec(
318
+ base=base_model.strip(),
319
+ init_from=ArtifactRef.parse(init_from) if init_from else None,
320
+ ),
321
+ # dataset 与 path 是两个正交维度:dataset 触发分发(拉到共享缓存、
322
+ # 注入 <NAME>_DATA_DIR 环境变量),path 是训练框架实际读的位置
323
+ # (nemo-rl 通常在 config 里用 ${oc.env:<NAME>_DATA_DIR} 引用,不需要 path)。
324
+ data=DataSpec(
325
+ train=(
326
+ DataRef(dataset=train_dataset.strip(), path=train_data.strip())
327
+ if (train_data.strip() or train_dataset.strip()) else None
328
+ ),
329
+ validation=(
330
+ DataRef(dataset=validation_dataset.strip(), path=validation_data.strip())
331
+ if (validation_data.strip() or validation_dataset.strip()) else None
332
+ ),
333
+ ),
334
+ resources=ResourceSpec(pools=pool_objs, roles=role_map),
335
+ hyperparams=hyperparams,
336
+ lifecycle=LifecycleSpec(on_success=actions),
337
+ # 平台托管插件包的精确引用(来自实验的 plugins.lock.json)。
338
+ # PluginUse.from_dict 顺带做格式校验:id 两段式、digest 必须 sha256:。
339
+ plugins=tuple(
340
+ PluginUse.from_dict(p, field_path=f"spec.plugins[{i}]")
341
+ for i, p in enumerate(plugin_uses or [])
342
+ ),
343
+ ),
344
+ provenance=Provenance(
345
+ core_version=CORE_VERSION,
346
+ git_commit=str(prov.get("git_commit") or ""),
347
+ git_dirty=bool(prov.get("git_dirty")),
348
+ config_sha=str(prov.get("config_sha") or ""),
349
+ ),
350
+ )
351
+
352
+
353
+ def infer_recipe(exp_path: Path) -> str:
354
+ """从实验目录读取显式 recipe:`recipe.lock.json` 的 recipe.name 是唯一事实源。
355
+
356
+ 锁文件本来就是实验必备(`sf new` 生成、提交前校验 digest),recipe 名与版本
357
+ 都在里面,不再要求单独的 method 标注文件;旧实验遗留的 `method` 文件作为
358
+ 兼容回退仍可读。读不到返回空串,由调用方明确报错;不按其他文件的存在性推断。
359
+ """
360
+ lock = exp_path / "recipe.lock.json"
361
+ if lock.is_file():
362
+ try:
363
+ payload = json.loads(lock.read_text(encoding="utf-8"))
364
+ except (OSError, json.JSONDecodeError):
365
+ payload = {}
366
+ name = str((payload.get("recipe") or {}).get("name") or "").strip()
367
+ if name:
368
+ return name
369
+ legacy = exp_path / "method"
370
+ if legacy.is_file():
371
+ return legacy.read_text(encoding="utf-8").strip()
372
+ return ""
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.5
2
+ Name: starforge-cli
3
+ Version: 0.1.6
4
+ Summary: StarForge(星锻)命令行客户端:pip 安装后用 sf init 创建微调项目,再提交训练。
5
+ Project-URL: Homepage, https://github.com/wccdev/starforge
6
+ Project-URL: Documentation, https://github.com/wccdev/starforge
7
+ Project-URL: Repository, https://github.com/wccdev/starforge
8
+ Keywords: grpo,llm,post-training,rlhf,sft,starforge
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: datasets>=2.18
20
+ Requires-Dist: pyyaml>=6
21
+ Requires-Dist: starforge-core==0.1.6
22
+ Requires-Dist: typer>=0.12
23
+ Provides-Extra: dev
24
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
25
+ Requires-Dist: httpx>=0.27; extra == 'dev'
26
+ Requires-Dist: pytest>=8; extra == 'dev'
27
+ Requires-Dist: pyyaml>=6; extra == 'dev'
28
+ Requires-Dist: ruff>=0.5; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # starforge-cli
32
+
33
+ StarForge(星锻)命令行客户端。源码在平台仓 `cli/`,以 PyPI 包 `starforge-cli` 发布,安装后提供 `sf` 命令。
34
+
35
+ ```bash
36
+ pip install starforge-cli
37
+ sf init my-lab --yes
38
+ ```
39
+
40
+ 官方示例项目见独立仓库 `starforge-tutorial`。本目录不是微调项目,不要在这里 `sf new`。
@@ -0,0 +1,55 @@
1
+ starforge_cli/__init__.py,sha256=bBl2cfgC1O5UwCs3_ba_T_aWkoV4VjViiYfALsE_hDE,129
2
+ starforge_cli/api_client.py,sha256=JL-fou_v8B9ftKpdgehivzLgnqSesftn-IrmwqTy-Dk,25829
3
+ starforge_cli/auth.py,sha256=jdK-1tcGY-xdIZr3FIgCLoUHlCOk6k6hbR3TfOcpy90,13008
4
+ starforge_cli/catalog.py,sha256=aDy3bh_zvPOz70f2goj6Qc-vR8taieYsdUh0kMt-r7s,5750
5
+ starforge_cli/cli.py,sha256=sSxC_zRq5ut-5eYsoAS5Ej27ipnvS1pkcdBPQI-_FHg,3235
6
+ starforge_cli/cli_ui.py,sha256=hHFQe7Ci82n6ZP0Etz4I6I84O_t5aOnb4_xfOXkwjyI,15951
7
+ starforge_cli/client_device.py,sha256=homzz_OOIztOJ8SwgJ1TetnG7Gsz6KiYyTV1RnJ8bKE,3742
8
+ starforge_cli/config_resolve.py,sha256=ln3plTCtp1a0oBgmbCl9DwJ90eAKu-4jD38_6nTfStg,743
9
+ starforge_cli/data_prep.py,sha256=QOo3iWg_UzZyfrH7QaJf1j0KLeY-vlutRHJUlM9hQf8,2157
10
+ starforge_cli/new_experiment.py,sha256=RPQmSSTvY8pi0MSMd-xUyHEOA9ZPTYjgZWtk9EJ_7hk,7305
11
+ starforge_cli/packing.py,sha256=UxqUCe1f3EponHUktrSYZeLTFpWAsUW1Slq0LCHOFrE,7560
12
+ starforge_cli/plugins_lock.py,sha256=tIjDYdu0jBVsCxltSsJTNeiSRtE80JTg7bBqaH_1gmc,2705
13
+ starforge_cli/project.py,sha256=9_DFMG05gQTon3eBnLVYzBkNEAl9B7T05mlF3Ef5SXU,5568
14
+ starforge_cli/recipe_lock.py,sha256=rLMXo-j0mBRzrgw-WQhhZ85l8z0FPCHYc9MZwBNzBr8,17335
15
+ starforge_cli/spec_builder.py,sha256=Qs_ADSvptBEUnbR2biHZIMxwkohGmdChTuGLd55IK5M,15475
16
+ starforge_cli/commands/__init__.py,sha256=xTREWFhmebvT1e--N_DZ3EpbNYhjH0kyuI75WpriOiA,85
17
+ starforge_cli/commands/admin.py,sha256=6VM22LsB4O8wWgeiPFSr-giIKrhZ1273aZdZWl1R03M,6641
18
+ starforge_cli/commands/bench.py,sha256=gqgUZL3wH4GhD6MqPlLMZDlv2oqGQVzjZLuA4uLcaOE,4138
19
+ starforge_cli/commands/common.py,sha256=1JW7VA1c4yf6_vwf2XWhvoqjD4dTjEBE8q2mbWPcnwA,6628
20
+ starforge_cli/commands/dataset.py,sha256=5v8GWoUXbavWkPwvjNOolVQHaWqeIPgVadWuFFiAiu8,6291
21
+ starforge_cli/commands/exp.py,sha256=WDTN91KP5zfTKCs6Hy8wXAFIxB49gUJOUI_k_4e388g,8437
22
+ starforge_cli/commands/init.py,sha256=vw-fCgj-ZfOrxMW-4Kml3dCcX6Pt6oK7gdK-ooVQWWc,2085
23
+ starforge_cli/commands/jobs.py,sha256=fubC0Mr8yRe1mm0DGhDxcKWKa9pKJq7YvmQEo0mTSkE,8886
24
+ starforge_cli/commands/login.py,sha256=CKx19kwyguTY-soWpJN-NmTGaELh2vZhSdU66NuitSY,2172
25
+ starforge_cli/commands/plugin.py,sha256=LOSomV-G3sOVn5LNi3fF9rSC7rSNTS0aOLTdVgMciJY,9732
26
+ starforge_cli/commands/recipe.py,sha256=48998L3lPAsC8NSGZS4gtsXTQCBxNeEhQACZe2kHfII,5444
27
+ starforge_cli/commands/serve.py,sha256=D0W9XWJImYkdCKyC0uN9SjZEWMkjAHz1R7kTeHyqnGs,3245
28
+ starforge_cli/commands/submit.py,sha256=9eyYwvlVg-6WvydEbzfarRq-OlkSP5uDnyjGI4uoO8M,21322
29
+ starforge_cli/commands/sweep.py,sha256=Od6A7Ve6FkIoo3C4v26Apw_UkP4swaxas_ws39Mlawo,6427
30
+ starforge_cli/scaffold/agent-run.py.tmpl,sha256=g0XUzej4p2-5ThcsR5BYpUEBdLjd1_L-zNxWoLeaLQ0,5865
31
+ starforge_cli/scaffold/custom-framework/train.sh,sha256=n2RLgEsk20hcTinaGbvWrXwc9y7xHzxIjlrTChF4j3c,3458
32
+ starforge_cli/scaffold/experiment-template/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ starforge_cli/scaffold/experiment-template/README.md,sha256=ht-Szx04Fmo70kPkDUmD2-X151fawN9HjFA-rMLUsA0,1184
34
+ starforge_cli/scaffold/experiment-template/config.yaml,sha256=x29cTrWm-VsRYPLqtE5O1BTwm4dEBBwJr5jpeIa4r8M,1663
35
+ starforge_cli/scaffold/project/gitignore,sha256=OF7Hn2W-Ffmw6Uvlblhq-7XoqHd0_JKVq7cYcLZf5rw,108
36
+ starforge_cli/scaffold/project/common/README.md,sha256=6mBNuYYq26A3ck0gqMPF3MEs0xCFdBfL5ZTiqlW0feY,690
37
+ starforge_cli/scaffold/project/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ starforge_cli/scaffold/project/configs/README.md,sha256=J8hLHgzpE1q1RN9fRqt_TAFGTKXvAKzhzY5h_apt2dc,5463
39
+ starforge_cli/scaffold/project/configs/base/README.md,sha256=-mFyhx-DEaKOJkyVHYMPIYsAR73Ky4nTi_wJxC2xKyU,1241
40
+ starforge_cli/scaffold/project/configs/base/distillation_math.yaml,sha256=Tj8TI2JjL9CVnEX8KoRlx7-MqbT7H_mTjItYpXWq5U8,10351
41
+ starforge_cli/scaffold/project/configs/base/grpo_lora.yaml,sha256=noVhbwE5HoXNZVdysasKLbw6IPitF8nQz6XmKwnyO6M,1206
42
+ starforge_cli/scaffold/project/configs/base/grpo_math_1B.yaml,sha256=UH68TavoA8TEb1mrUJSoxCXRipmpnVIhSIoU_9atoQk,20156
43
+ starforge_cli/scaffold/project/configs/base/grpo_megatron.yaml,sha256=gdD4OY42GPbiFxVU30HuFjsG75AeS052-bzOGhVqnXA,2398
44
+ starforge_cli/scaffold/project/configs/base/grpo_noncolocated.yaml,sha256=a5Qr6yAQ-9j8_SF5C0R4f3Rtj9aOHJlw6VQ5UEH9ip0,1035
45
+ starforge_cli/scaffold/project/configs/base/grpo_sliding_puzzle.yaml,sha256=GpL4IR4ponTrMA9_N41Q_uKbBYPBkUnvpJB-Urll5Ws,2463
46
+ starforge_cli/scaffold/project/configs/base/ppo_math_1B.yaml,sha256=zsVXO17SzPia1SBdRdrPe5JLrueOaf0kIPlV2r-M4GQ,12845
47
+ starforge_cli/scaffold/project/configs/base/rm.yaml,sha256=mqyi24MmOlMPqvkW_M7W5K3CTDapHE1wrE8Qd2DCVk8,8284
48
+ starforge_cli/scaffold/project/configs/base/sft.yaml,sha256=lr_bnzcNobzWwoOLYI9i-53K4MJ9RlxHt9zXywb6VoI,11162
49
+ starforge_cli/scaffold/project/configs/models/README.md,sha256=bXsDjKpwLcUScXe1xAqce4nv1S8lScTY7Tahu5Jt7gw,778
50
+ starforge_cli/scaffold/project/configs/models/qwen3.5-4b.yaml,sha256=FCDitwTu_xCeawB9_BFM3Y1N0uryq4zowdpR_hY6cxc,478
51
+ starforge_cli/scaffold/project/configs/models/qwen3.5-9b.yaml,sha256=-_o-zn4XlIQVR9dzw_a1g4nFWrvw0P_EY0vPzSEamw0,587
52
+ starforge_cli-0.1.6.dist-info/METADATA,sha256=GnMSEnDFpflGjq1ASKY95IEeQE8lkrmusP8L5aZugwY,1601
53
+ starforge_cli-0.1.6.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
54
+ starforge_cli-0.1.6.dist-info/entry_points.txt,sha256=cH1c_LcMPSnRb3UBTb05zk3TtmFtpvSsVy4ODkh9ctQ,45
55
+ starforge_cli-0.1.6.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sf = starforge_cli.cli:app