dsh-runnpu 0.1.0

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/compute-mode/preset/agent.cordis.yml +368 -0
  4. package/compute-mode/preset/preset.yml +3 -0
  5. package/compute-mode/preset/skills/runnpu/SKILL.md +233 -0
  6. package/compute-mode/preset/skills/runnpu/VENDOR.txt +11 -0
  7. package/compute-mode/preset/skills/runnpu/examples/01-dev-environment.sh +26 -0
  8. package/compute-mode/preset/skills/runnpu/examples/02-training-job.sh +26 -0
  9. package/compute-mode/preset/skills/runnpu/examples/03-inference-service.sh +24 -0
  10. package/compute-mode/preset/skills/runnpu/examples/04-fractional-vnpu.sh +14 -0
  11. package/compute-mode/preset/skills/runnpu/examples/05-troubleshoot.sh +17 -0
  12. package/compute-mode/preset/skills/runnpu/references/admin.md +60 -0
  13. package/compute-mode/preset/skills/runnpu/references/data-sources.md +35 -0
  14. package/compute-mode/preset/skills/runnpu/references/templates-assets.md +61 -0
  15. package/compute-mode/preset/skills/runnpu/references/training-runs.md +56 -0
  16. package/compute-mode/preset/skills/runnpu/references/troubleshooting.md +75 -0
  17. package/compute-mode/preset/skills/runnpu/references/workload-types.md +47 -0
  18. package/cordis.patch.yml +80 -0
  19. package/dist/chunk-JLZMOSQC.mjs +1 -0
  20. package/dist/chunk-JUBIWGKW.mjs +4 -0
  21. package/dist/chunk-JZXLCA2E.mjs +1 -0
  22. package/dist/chunk-KQOFBYGE.mjs +9 -0
  23. package/dist/chunk-XMZG2NP7.mjs +2 -0
  24. package/dist/chunk-XQPU3AJN.mjs +1 -0
  25. package/dist/chunk-YKASEMP3.mjs +1 -0
  26. package/dist/chunk-YPL4VUYF.mjs +4 -0
  27. package/dist/compute-node.mjs +1 -0
  28. package/dist/compute-tools.mjs +41 -0
  29. package/dist/connectors.mjs +3 -0
  30. package/dist/dsh-runnpu.mjs +43 -0
  31. package/dist/json.mjs +1 -0
  32. package/dist/pins.mjs +1 -0
  33. package/dist/series.mjs +1 -0
  34. package/dist/training-node.mjs +2 -0
  35. package/dist/training-tools.mjs +30 -0
  36. package/dist/ui-host.mjs +1 -0
  37. package/lib/client.js +189 -0
  38. package/package.json +78 -0
  39. package/training-mode/preset/agent.cordis.yml +343 -0
  40. package/training-mode/preset/preset.yml +3 -0
  41. package/training-mode/preset/skills/runnpu/SKILL.md +233 -0
  42. package/training-mode/preset/skills/runnpu/VENDOR.txt +11 -0
  43. package/training-mode/preset/skills/runnpu/examples/01-dev-environment.sh +26 -0
  44. package/training-mode/preset/skills/runnpu/examples/02-training-job.sh +26 -0
  45. package/training-mode/preset/skills/runnpu/examples/03-inference-service.sh +24 -0
  46. package/training-mode/preset/skills/runnpu/examples/04-fractional-vnpu.sh +14 -0
  47. package/training-mode/preset/skills/runnpu/examples/05-troubleshoot.sh +17 -0
  48. package/training-mode/preset/skills/runnpu/references/admin.md +60 -0
  49. package/training-mode/preset/skills/runnpu/references/data-sources.md +35 -0
  50. package/training-mode/preset/skills/runnpu/references/templates-assets.md +61 -0
  51. package/training-mode/preset/skills/runnpu/references/training-runs.md +56 -0
  52. package/training-mode/preset/skills/runnpu/references/troubleshooting.md +75 -0
  53. package/training-mode/preset/skills/runnpu/references/workload-types.md +47 -0
  54. package/training-mode/preset/skills/swanlab-skill/LICENSE +21 -0
  55. package/training-mode/preset/skills/swanlab-skill/SKILL.md +163 -0
  56. package/training-mode/preset/skills/swanlab-skill/VENDOR.txt +9 -0
  57. package/training-mode/preset/skills/swanlab-skill/references/ANALYSIS_GUIDE.md +83 -0
  58. package/training-mode/preset/skills/swanlab-skill/references/CLI_REFERENCE.md +510 -0
  59. package/training-mode/preset/skills/swanlab-skill/references/SDK_QUICKSTART.md +341 -0
  60. package/training-mode/preset/skills/swanlab-skill/references/SWANLAB_CONCEPTS.md +523 -0
  61. package/training-mode/preset/skills/swanlab-skill/scripts/plot_metrics.py +353 -0
  62. package/training-mode/preset/skills/swanlab-skill/scripts/runs_benchmark.py +550 -0
  63. package/training-mode/preset/skills/training-research/SKILL.md +33 -0
  64. package/training-mode/preset/skills/training-research/references/pipeline.md +45 -0
  65. package/training-mode/preset/skills/training-research/references/research.md +76 -0
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SwanLab metric line-chart plotter.
4
+
5
+ Fetch scalar metrics via the SwanLab OOP Api, then render line charts
6
+ with matplotlib. Each metric key gets its own subplot with trend line
7
+ and summary statistics (min / max / mean / std / last).
8
+
9
+ Usage:
10
+ # Basic (requires `swanlab login` first)
11
+ python plot_metrics.py username/project_name/run_id --keys loss,acc
12
+
13
+ # Custom sample size and output path
14
+ python plot_metrics.py username/project_name/run_id --keys loss --sample 500 -o loss_chart.png
15
+
16
+ # Self-hosted server
17
+ python plot_metrics.py username/project_name/run_id --keys loss --host https://your-swanserver
18
+
19
+ # Explicit API key (skip login)
20
+ python plot_metrics.py username/project_name/run_id --keys loss --api-key YOUR_KEY
21
+
22
+ # Plot from a pre-fetched JSON file (skip API calls entirely)
23
+ python plot_metrics.py --data metrics.json --keys loss,acc -o chart.png
24
+ """
25
+
26
+ import argparse
27
+ import json
28
+ import math
29
+ import sys
30
+ from typing import Any, Dict, List, Optional, Tuple
31
+
32
+ import matplotlib
33
+
34
+ matplotlib.use("Agg")
35
+ import matplotlib.pyplot as plt
36
+ import numpy as np
37
+ from swanlab.api import Api
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Data fetching
41
+ # ---------------------------------------------------------------------------
42
+
43
+
44
+ def fetch_metrics(
45
+ path: str,
46
+ keys: List[str],
47
+ sample: int = 1500,
48
+ api_key: Optional[str] = None,
49
+ host: Optional[str] = None,
50
+ ) -> Dict[str, Any]:
51
+ """Fetch scalar metric data via the SwanLab Api."""
52
+ api = Api(api_key=api_key, host=host)
53
+ experiment = api.run(path)
54
+ if not experiment.run_id:
55
+ raise ValueError(f"Failed to fetch experiment at path '{path}'. Please verify the path and credentials.")
56
+ return experiment.metrics(keys=keys, sample=sample, ignore_timestamp=True)
57
+
58
+
59
+ def fetch_summary(
60
+ path: str,
61
+ keys: List[str],
62
+ api_key: Optional[str] = None,
63
+ host: Optional[str] = None,
64
+ ) -> Dict[str, Any]:
65
+ """Fetch metric summary statistics via the SwanLab Api."""
66
+ api = Api(api_key=api_key, host=host)
67
+ experiment = api.run(path)
68
+ if not experiment.run_id:
69
+ raise ValueError(f"Failed to fetch experiment at path '{path}'. Please verify the path and credentials.")
70
+ return experiment.summary(keys=keys)
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Data extraction — pull (steps, values) pairs from the raw API dict
75
+ # ---------------------------------------------------------------------------
76
+
77
+
78
+ def _unwrap_envelope(data: Any) -> Any:
79
+ """Unwrap the ``{"ok": ..., "errmsg": ..., "data": ...}`` envelope written by CLI ``--save``."""
80
+ if (
81
+ isinstance(data, dict)
82
+ and "list" not in data
83
+ and "keys" not in data
84
+ and isinstance(data.get("ok"), bool)
85
+ and "data" in data
86
+ ):
87
+ return data.get("data") or {}
88
+ return data
89
+
90
+
91
+ def extract_series(metric_data: Dict[str, Any], key: str) -> Tuple[List[int], List[float]]:
92
+ """
93
+ Extract (steps, values) for a single metric key.
94
+
95
+ Handles three response shapes:
96
+ - Current SDK format (``Experiment.metrics()`` / ``Metrics.json()``)::
97
+ ``{"keys": [...], "list": [{"key": k, "metrics": [{"index": i, "data": v}, ...], ...}]}``
98
+ - CLI ``--save`` output — the same structure wrapped in an
99
+ ``{"ok": ..., "errmsg": ..., "data": ...}`` envelope
100
+ - Legacy format (pre-0.9.0)::
101
+ ``{key: [{"step": s, "value": v}, ...]}`` (flat list or nested under "data")
102
+
103
+ Data points use ``index``/``data`` field names in the current format and
104
+ ``step``/``value`` in the legacy format; both are accepted.
105
+ """
106
+ data = _unwrap_envelope(metric_data)
107
+ if not isinstance(data, dict):
108
+ return [], []
109
+
110
+ points: Any = None
111
+ entries = data.get("list")
112
+ if isinstance(entries, list):
113
+ # Current SDK format: locate the entry matching the requested key
114
+ for entry in entries:
115
+ if isinstance(entry, dict) and entry.get("key") == key:
116
+ points = entry.get("metrics") or []
117
+ break
118
+ elif key in data:
119
+ # Legacy format: flat list or nested under "data"
120
+ entry = data[key]
121
+ if isinstance(entry, dict) and "data" in entry:
122
+ points = entry["data"]
123
+ elif isinstance(entry, list):
124
+ points = entry
125
+
126
+ steps: List[int] = []
127
+ values: List[float] = []
128
+ for pt in points or []:
129
+ if not isinstance(pt, dict):
130
+ continue
131
+ step = pt.get("step")
132
+ if step is None:
133
+ step = pt.get("index")
134
+ value = pt.get("value")
135
+ if value is None:
136
+ value = pt.get("data")
137
+ if step is None or value is None:
138
+ continue
139
+ try:
140
+ steps.append(int(step))
141
+ values.append(float(value))
142
+ except (TypeError, ValueError):
143
+ continue
144
+ return steps, values
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Statistics helpers
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ def compute_stats(values: List[float]) -> Dict[str, float]:
153
+ """Compute basic descriptive statistics for a value series."""
154
+ if not values:
155
+ return {}
156
+ arr = np.array(values)
157
+ return {
158
+ "min": float(np.min(arr)),
159
+ "max": float(np.max(arr)),
160
+ "mean": float(np.mean(arr)),
161
+ "std": float(np.std(arr)),
162
+ "last": float(arr[-1]),
163
+ }
164
+
165
+
166
+ def format_stats_text(stats: Dict[str, float]) -> str:
167
+ """Format statistics dict into a compact annotation string."""
168
+ if not stats:
169
+ return "N/A"
170
+ parts = [
171
+ f"min={stats['min']:.6g}",
172
+ f"max={stats['max']:.6g}",
173
+ f"mean={stats['mean']:.6g}",
174
+ f"std={stats['std']:.6g}",
175
+ f"last={stats['last']:.6g}",
176
+ ]
177
+ return " ".join(parts)
178
+
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Plotting
182
+ # ---------------------------------------------------------------------------
183
+
184
+
185
+ def plot_line_chart(
186
+ metric_data: Dict[str, Any],
187
+ keys: List[str],
188
+ output: str = "metrics_chart.png",
189
+ title: Optional[str] = None,
190
+ dpi: int = 150,
191
+ ) -> None:
192
+ """
193
+ Render one subplot per metric key with a line chart and stats annotation.
194
+
195
+ :param metric_data: Raw dict returned by ``Api.metrics()``.
196
+ :param keys: Metric keys to plot.
197
+ :param output: Output image file path.
198
+ :param title: Overall chart title (auto-generated if None).
199
+ :param dpi: Image resolution.
200
+ """
201
+ n = len(keys)
202
+ if n == 0:
203
+ print("No keys to plot.", file=sys.stderr)
204
+ return
205
+
206
+ # Layout: at most 2 subplots per row
207
+ ncols = min(n, 2)
208
+ nrows = math.ceil(n / ncols)
209
+
210
+ fig, axes = plt.subplots(nrows, ncols, figsize=(7 * ncols, 4.5 * nrows), squeeze=False)
211
+ fig.suptitle(title or "SwanLab Metrics", fontsize=14, fontweight="bold", y=0.98)
212
+
213
+ for idx, key in enumerate(keys):
214
+ row, col = divmod(idx, ncols)
215
+ ax = axes[row][col]
216
+
217
+ steps, values = extract_series(metric_data, key)
218
+
219
+ if not steps:
220
+ ax.set_title(key, fontsize=11)
221
+ ax.text(
222
+ 0.5,
223
+ 0.5,
224
+ "No data",
225
+ ha="center",
226
+ va="center",
227
+ transform=ax.transAxes,
228
+ fontsize=12,
229
+ color="gray",
230
+ )
231
+ ax.set_xlabel("step")
232
+ ax.set_ylabel(key)
233
+ continue
234
+
235
+ # Line chart
236
+ ax.plot(steps, values, linewidth=1.0, alpha=0.85)
237
+
238
+ # Highlight the last data point
239
+ ax.scatter([steps[-1]], [values[-1]], s=20, zorder=5)
240
+
241
+ # Stats annotation box
242
+ stats = compute_stats(values)
243
+ stats_text = format_stats_text(stats)
244
+ ax.text(
245
+ 0.02,
246
+ 0.02,
247
+ stats_text,
248
+ transform=ax.transAxes,
249
+ fontsize=7,
250
+ verticalalignment="bottom",
251
+ bbox=dict(boxstyle="round,pad=0.3", facecolor="lightyellow", alpha=0.8),
252
+ )
253
+
254
+ ax.set_title(key, fontsize=11)
255
+ ax.set_xlabel("step")
256
+ ax.set_ylabel(key)
257
+ ax.grid(True, alpha=0.3)
258
+
259
+ # Hide unused subplots
260
+ for idx in range(n, nrows * ncols):
261
+ row, col = divmod(idx, ncols)
262
+ axes[row][col].set_visible(False)
263
+
264
+ plt.tight_layout(rect=(0, 0, 1, 0.95))
265
+ fig.savefig(output, dpi=dpi, bbox_inches="tight")
266
+ plt.close(fig)
267
+ print(f"Chart saved to: {output}")
268
+
269
+
270
+ # ---------------------------------------------------------------------------
271
+ # CLI entry point
272
+ # ---------------------------------------------------------------------------
273
+
274
+
275
+ def parse_args() -> argparse.Namespace:
276
+ parser = argparse.ArgumentParser(
277
+ description="Fetch SwanLab experiment metrics and plot line charts.",
278
+ formatter_class=argparse.RawDescriptionHelpFormatter,
279
+ epilog=__doc__,
280
+ )
281
+ parser.add_argument(
282
+ "path",
283
+ nargs="?",
284
+ default=None,
285
+ help="Experiment path: username/project_name/run_id (optional when --data is used)",
286
+ )
287
+ parser.add_argument(
288
+ "--data",
289
+ default=None,
290
+ help="Path to a JSON file with pre-fetched metric data (SDK output, CLI --save file, or legacy format). Skips API calls entirely.",
291
+ )
292
+ parser.add_argument(
293
+ "--keys",
294
+ "-k",
295
+ required=True,
296
+ help="Comma-separated metric keys, e.g. 'loss,acc'",
297
+ )
298
+ parser.add_argument("--sample", "-s", type=int, default=1500, help="Sample size (default: 1500)")
299
+ parser.add_argument(
300
+ "--output",
301
+ "-o",
302
+ default="metrics_chart.png",
303
+ help="Output image path (default: metrics_chart.png)",
304
+ )
305
+ parser.add_argument("--title", "-t", default=None, help="Chart title (default: auto)")
306
+ parser.add_argument("--dpi", type=int, default=150, help="Image DPI (default: 150)")
307
+ parser.add_argument("--api-key", default=None, help="SwanLab API key (or use swanlab login)")
308
+ parser.add_argument("--host", default=None, help="SwanLab API host URL (for self-hosted)")
309
+ return parser.parse_args()
310
+
311
+
312
+ def main() -> None:
313
+ args = parse_args()
314
+ keys = [k.strip() for k in args.keys.split(",") if k.strip()]
315
+
316
+ if not keys:
317
+ print("Error: --keys must contain at least one key.", file=sys.stderr)
318
+ sys.exit(1)
319
+
320
+ if not args.path and not args.data:
321
+ print(
322
+ "Error: provide either a PATH argument or --data <json_file>.",
323
+ file=sys.stderr,
324
+ )
325
+ sys.exit(1)
326
+
327
+ if args.data:
328
+ # Load metric data from a pre-fetched JSON file
329
+ with open(args.data, "r", encoding="utf-8") as f:
330
+ metric_data = json.load(f)
331
+ print(f"Loaded metric data from: {args.data}")
332
+ else:
333
+ print(f"Fetching metrics for: {args.path}")
334
+ print(f"Keys: {keys}, sample: {args.sample}")
335
+ metric_data = fetch_metrics(
336
+ path=args.path,
337
+ keys=keys,
338
+ sample=args.sample,
339
+ api_key=args.api_key,
340
+ host=args.host,
341
+ )
342
+
343
+ plot_line_chart(
344
+ metric_data=metric_data,
345
+ keys=keys,
346
+ output=args.output,
347
+ title=args.title or f"Metrics: {args.path}",
348
+ dpi=args.dpi,
349
+ )
350
+
351
+
352
+ if __name__ == "__main__":
353
+ main()