dsh-runnpu 0.1.0 → 0.2.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.
- package/dist/chunk-4CZL4IT4.mjs +4 -0
- package/dist/{chunk-XMZG2NP7.mjs → chunk-GJSGPQNW.mjs} +1 -1
- package/dist/chunk-KQBXZKCA.mjs +4 -0
- package/dist/chunk-MGSEPBNR.mjs +6 -0
- package/dist/{chunk-YPL4VUYF.mjs → chunk-ZA65VWZ4.mjs} +1 -1
- package/dist/chunk-ZVARRQYA.mjs +1 -0
- package/dist/compute-node.mjs +1 -1
- package/dist/compute-tools.mjs +20 -20
- package/dist/connectors.mjs +2 -2
- package/dist/pins.mjs +1 -1
- package/dist/training-node.mjs +2 -2
- package/dist/training-tools.mjs +16 -16
- package/lib/client.js +20 -20
- package/package.json +1 -1
- package/dist/chunk-JUBIWGKW.mjs +0 -4
- package/dist/chunk-KQOFBYGE.mjs +0 -9
package/dist/connectors.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{a as
|
|
1
|
+
import{a as v,b as L,c as I,d as N,e as O,f as C,g as P,h as B,i as W,j as K,l as $,m as j,n as R}from"./chunk-YKASEMP3.mjs";import{a as y,b,e as x,g,i as h,j as A,k as S,l as E}from"./chunk-4CZL4IT4.mjs";import{a as G}from"./chunk-JZXLCA2E.mjs";var H={};G(H,{SwanLabError:()=>p,api:()=>d,check:()=>_,extractJson:()=>U,findRunByName:()=>z,keysOf:()=>tn,metricSeries:()=>an,probe:()=>F,projectList:()=>X,runInfo:()=>Z,runKeys:()=>en,runList:()=>Y,runLogs:()=>sn,runMetrics:()=>rn,runSummary:()=>nn,runWebUrl:()=>on,selfHostedInfo:()=>Q,userInfo:()=>D,usernameOf:()=>V,version:()=>cn,workloadEnv:()=>k});import{execFile as T}from"node:child_process";var p=class extends Error{constructor(e,t,o){super(t),this.name="SwanLabError",this.code=e,this.details=o}};function d(n,e,t={}){let o=n.cli||"swanlab",u=["api",...e];return n.apiHost&&u.push("--host",n.apiHost),n.apiKey&&u.push("--api-key",n.apiKey),new Promise((i,r)=>{T(o,u,{signal:t.signal,timeout:t.timeoutMs??3e4,maxBuffer:32*1024*1024,env:{...process.env,NO_COLOR:"1"}},(s,a,c)=>{if(s){if(s.code==="ENOENT"){r(new p("cli_missing",`\`${o}\` \u4E0D\u5728 PATH \u4E0A\uFF1B\u7528 \`pip install -U swanlab\` \u5B89\u88C5`));return}let l=String(c||s.message).trim().slice(0,2e3);if(/No such command ['"]api['"]/i.test(l)){r(new p("cli_too_old","\u5F53\u524D swanlab CLI \u6CA1\u6709 `api` \u5B50\u547D\u4EE4\uFF08\u8BE5\u547D\u4EE4\u7EC4\u81EA SDK 0.9.0 \u8D77\u63D0\u4F9B\uFF09\u3002\u9762\u677F\u53D6\u6570\u4E0E\u5B9E\u9A8C\u67E5\u8BE2\u90FD\u4F9D\u8D56\u5B83\uFF1A\u8BF7 `pip install -U swanlab` \u5347\u7EA7\u5230 0.9.0 \u4EE5\u4E0A\u3002"));return}r(new p("cli_failed",`swanlab api ${e[0]??""} failed: ${l}`,{args:e[0]}));return}let f;try{f=U(a)}catch{r(new p("bad_output","swanlab api returned output that is not JSON",{head:a.slice(0,500)}));return}if(f&&typeof f=="object"&&f.ok===!1){r(new p("api_error",String(f.errmsg||"SwanLab API request failed").slice(0,2e3)));return}i(f&&typeof f=="object"&&f.ok===!0&&"data"in f?f.data:f)})})}function U(n){let e=String(n??"").trim();if(!e)return;try{return JSON.parse(e)}catch{}let t=e.split(`
|
|
2
2
|
`);for(let o=0;o<t.length;o+=1){let u=t[o].trimStart()[0];if(u!=="{"&&u!=="[")continue;let i=t.slice(o).join(`
|
|
3
|
-
`);try{return JSON.parse(i)}catch{}}throw new Error("no JSON document in output")}async function D(e
|
|
3
|
+
`);try{return JSON.parse(i)}catch{}}throw new Error("no JSON document in output")}async function D(n,e){return await d(n,["user","info"],e)}async function Q(n,e){return await d(n,["self-hosted","info"],e)}async function X(n,{workspace:e,all:t=!0}={},o){let u=["project","list"];return e&&u.push("--workspace",e),t&&u.push("--all"),await d(n,u,o)}async function Y(n,e,t){return await d(n,["run","list",e],t)}function V(n){for(let e of[n?.username,n?.name,n?.user?.username,n?.user?.name])if(typeof e=="string"&&e.trim()!=="")return e.trim()}function z(n,e){let t=typeof e=="string"?e.trim():"";if(t==="")return;let o=Array.isArray(n?.list)?n.list:Array.isArray(n)?n:[],u=[];for(let r of o){if(r?.name!==t)continue;let s=typeof r?.run_id=="string"?r.run_id.trim():"";if(s==="")continue;let a=Date.parse(r?.created_at??r?.createdAt??"");u.push({row:r,runId:s,at:Number.isFinite(a)?a:Number.NEGATIVE_INFINITY})}if(u.length===0)return;let i=u[0];for(let r of u.slice(1))r.at>i.at&&(i=r);return{runId:i.runId,state:typeof i.row?.state=="string"?i.row.state:void 0,ambiguous:u.length>1,matches:u.length}}async function Z(n,e,t){return await d(n,["run","info",e],t)}async function nn(n,e,t){return await d(n,["run","summary",e],t)}async function en(n,e,t){try{return{source:"series",data:await d(n,["run","series",e],t)}}catch(o){if(o.code==="cli_missing")throw o;return{source:"columns",data:await d(n,["run","columns",e],t)}}}function tn(n){let e=n?.source!==void 0&&n?.data!==void 0?n.data:n,t=Array.isArray(e?.keys)?e.keys:Array.isArray(e?.list)?e.list:Array.isArray(e)?e:[],o=[],u=new Set;for(let i of t){let r=typeof i=="string"?i:typeof i?.key=="string"?i.key:void 0;if(r===void 0)continue;let s=r.trim();s===""||u.has(s)||(u.add(s),o.push(s))}return o}async function rn(n,e,t,o){if(!t?.length)throw new p("no_keys","runMetrics needs an explicit key list");return await d(n,["run","metrics",e,"--keys",t.join(",")],o)}function an(n){let e={};for(let t of Array.isArray(n?.list)?n.list:[])typeof t?.key=="string"&&(e[t.key]=(Array.isArray(t.metrics)?t.metrics:[]).filter(o=>Number.isFinite(o?.timestamp??o?.index)).map(o=>[o.timestamp??o.index,typeof o?.data=="number"&&Number.isFinite(o.data)?o.data:null]));return e}function on(n,e){if(!n||!e)return;let t=String(e).split("/");if(t.length!==3||t.some(r=>r===""))return;let[o,u,i]=t;return`${n.replace(/\/+$/,"")}/@${o}/${u}/runs/${i}/chart`}async function sn(n,e,t){return await d(n,["run","logs",e],t)}function k(n,e={}){let t={};return n.apiKey&&(t.SWANLAB_API_KEY=n.apiKey),n.apiHost&&(t.SWANLAB_API_HOST=n.apiHost),n.webHost&&(t.SWANLAB_WEB_HOST=n.webHost),e.name&&(t.SWANLAB_NAME=e.name,t.SWANLAB_EXP_NAME=e.name,t.SWANLAB_EXPERIMENT_NAME=e.name),e.description&&(t.SWANLAB_DESCRIPTION=e.description),t}async function cn(n,e){try{let t=n.cli||"swanlab";return(await new Promise((u,i)=>{T(t,["--version"],{timeout:1e4,signal:e?.signal},(r,s)=>r?i(r):u(s))})).trim()}catch{return}}async function F(n,e){if(!n.apiHost)return{reachable:!1,reason:"SwanLab API \u5730\u5740\u672A\u914D\u7F6E"};let t=new AbortController,o=setTimeout(()=>t.abort(),e?.timeoutMs??8e3);try{return await fetch(n.apiHost,{method:"HEAD",signal:t.signal,redirect:"manual"}),{reachable:!0}}catch(u){return u?.name==="AbortError"?{reachable:!1,reason:`\u8FDE\u63A5 ${n.apiHost} \u8D85\u65F6`,code:"timeout"}:{reachable:!1,reason:`\u65E0\u6CD5\u8FDE\u901A ${n.apiHost}\uFF1A${u?.message??u}`,code:"unreachable"}}finally{clearTimeout(o)}}async function _(n,e){let t=await F(n,e);if(!t.reachable)return{...t,credentials:"unknown"};if(!n.apiKey)return{reachable:!0,credentials:"missing"};try{return await D(n,{...e,timeoutMs:15e3}),{reachable:!0,credentials:"valid"}}catch(o){return o.code==="cli_too_old"||o.code==="cli_missing"?{reachable:!0,credentials:"unverifiable",reason:o.message,code:o.code}:{reachable:!0,credentials:"invalid",reason:o.message,code:o.code}}}var M={runnpu:{server:"",tokenEnv:b.runnpuToken},swanlab:{apiHost:y.apiHost,webHost:y.webHost,apiKeyEnv:b.swanlabApiKey,cli:"swanlab"}};function un(n){return{runnpu:{...M.runnpu,...n?.runnpu??{}},swanlab:{...M.swanlab,...n?.swanlab??{}}}}function q({credentials:n=()=>{},config:e}={}){let t=un(e);return{runnpu:{api:R,Error:v,async connection(i){let r=await S(n(),t,i);return{server:r.runnpu.server,token:r.runnpu.token}},async describe(i){return await A(n(),t,i)},async health(i,r){let s=await this.connection(i);if(!s.server)return{state:"unconfigured",reason:"\u5C1A\u672A\u586B\u5199 RunNPU \u767B\u5F55\u5730\u5740"};try{let a=await I(s,r);if(!s.token)return{state:"unauthenticated",reason:"\u5DF2\u8FDE\u901A\u5E73\u53F0\uFF0C\u4F46\u5C1A\u672A\u767B\u5F55",platform:a};let c=await N(s,r);return{state:"connected",platform:a,identity:c}}catch(a){return{state:a.code==="unreachable"?"unreachable":"error",reason:a.message,code:a.code}}},async identity(i,r){try{let s=await this.connection(i);if(!s.server||!s.token)return;let a=(await N(s,r))?.user;if(!a)return;let c={...a.id===void 0?{}:{id:String(a.id)},...a.login===void 0?{}:{login:String(a.login)},...a.display_name===void 0?{}:{displayName:String(a.display_name)}};return Object.keys(c).length===0?void 0:c}catch{return}},async projectBySlug(i,r,s){let a=await this.connection(i);if(!a.server||!a.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55");let c=await B(a,s),f=Array.isArray(c?.items)?c.items:[],l=f.find(m=>m?.slug===r||m?.id===r);if(!l){let m=f.map(J=>J?.slug).filter(Boolean).join(" / ");throw new Error(`\u6CA1\u6709\u540D\u4E3A ${r} \u7684\u9879\u76EE${m?`\u3002\u53EF\u9009\uFF1A${m}`:"\uFF08\u9879\u76EE\u5217\u8868\u4E3A\u7A7A\uFF09"}`)}let w=await W(a,l.id,s);return{id:l.id,slug:l.slug??r,name:w?.name??l.name,quota:w?.quota??l.quota}},async setQuota(i,r,s,a){let c=await this.connection(i);if(!c.server||!c.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55");return await K(c,r,{quota:s},a)},async login({server:i,email:r,password:s,scope:a="global",cwd:c}){let{token:f,me:l}=await L(i,r,s),w=await h(n(),b.runnpuToken,f,{scope:a,cwd:c});return a==="project"&&c?await g(c,{runnpu:{server:i}}):await E({runnpu:{server:i}}),{stored:w,identity:l}},async act(i,r,s,a,c){let f=await this.connection(i);if(!f.server||!f.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55");if(r==="stop")return await O(f,s,a,c);if(r==="start")return await C(f,s,a,c);if(r==="delete")return await P(f,s,a,c);throw new Error(`\u4E0D\u652F\u6301\u7684\u64CD\u4F5C\uFF1A${r}`)},async query(i,r,s,a){let c=await this.connection(i);return await j(c,r,s,a)},queries:Object.fromEntries(Object.entries($).map(([i,r])=>[i,{id:i,label:r.label}]))},swanlab:{api:H,Error:p,async connection(i){let r=await S(n(),t,i);return{mode:r.swanlab.mode,apiHost:r.swanlab.apiHost,webHost:r.swanlab.webHost,apiKey:r.swanlab.apiKey,cli:t.swanlab.cli}},async describe(i){return(await A(n(),t,i)).swanlab},async health(i,r){let s=await this.connection(i);if(!s.apiHost)return{state:"unconfigured",reason:"\u5C1A\u672A\u914D\u7F6E SwanLab \u5730\u5740"};if(!s.apiKey)return{state:"unauthenticated",reason:"\u5C1A\u672A\u914D\u7F6E SwanLab API Key"};let a=await _(s,{signal:r}),c={mode:s.mode,apiHost:s.apiHost,webHost:s.webHost};return a.reachable?a.credentials==="invalid"?{state:"error",reason:a.reason,code:a.code,...c}:a.credentials==="unverifiable"?{state:"degraded",reason:a.reason,code:a.code,...c}:{state:"connected",...c}:{state:"unreachable",reason:a.reason,code:a.code,...c}},async configure({mode:i,apiHost:r,webHost:s,apiKey:a,scope:c="global",cwd:f}){let l=i==="cloud"?{mode:i,apiHost:y.apiHost,webHost:y.webHost}:{mode:"self-hosted",apiHost:r,webHost:s||r};if(l.mode==="self-hosted"&&!l.apiHost)throw new Error("\u79C1\u6709\u90E8\u7F72\u5FC5\u987B\u586B\u5199 API \u5730\u5740");if(c==="project"&&f){let w=(await x(f)).config?.swanlab;await g(f,{swanlab:{...w,...l}})}else await E({swanlab:l});return a&&await h(n(),b.swanlabApiKey,a,{scope:c,cwd:f}),l},async workloadEnv(i,r){return k(await this.connection(i),r)}}}}var wn="runnpu-connectors";function mn(n,e){let{runnpu:t,swanlab:o}=q({credentials:()=>n.get("credentials"),config:e});n.effect(()=>n.provide("runnpu",t),"runnpu-connectors: runnpu"),n.effect(()=>n.provide("swanlab",o),"runnpu-connectors: swanlab")}export{mn as apply,wn as name};
|
package/dist/pins.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as g,b as h,c as i,d as j,e as k,f as l,g as m,h as n,i as o,j as p,k as q}from"./chunk-
|
|
1
|
+
import{a as g,b as h,c as i,d as j,e as k,f as l,g as m,h as n,i as o,j as p,k as q}from"./chunk-GJSGPQNW.mjs";import{a,b,c,d,e,f}from"./chunk-JLZMOSQC.mjs";import"./chunk-4CZL4IT4.mjs";import"./chunk-JZXLCA2E.mjs";export{a as CHARTABLE_QUERIES,b as CHART_GROUPS,o as MAX_PINS,q as PinStore,n as RANGES,h as TABULAR_QUERIES,g as VIEW_KINDS,d as availableGroups,j as deriveView,c as groupsOf,m as isTimeView,f as lastOf,l as normalizeViewSpec,p as pinKey,i as rowsOf,e as toSeries,k as viewOf};
|
package/dist/training-node.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as
|
|
2
|
-
${
|
|
1
|
+
import{a as O,b as U}from"./chunk-KQBXZKCA.mjs";import{a as P,b as N,c as C,d as x,f as G,g as z,h as F,i as W}from"./chunk-MGSEPBNR.mjs";import{e as I,m as E,n as D,o as L,p as j}from"./chunk-4CZL4IT4.mjs";import"./chunk-JZXLCA2E.mjs";var ee={apiPath:O.training.apiPath,refreshMs:O.training.refreshMs},q=5,J=200,te=Object.freeze(["completed","failed","stopped","rejected"]);function T(e){return typeof e=="string"&&te.includes(e)}var ne=new Map([["submitting","submitting"],["queued","queued"],["pending","queued"],["admitted","queued"],["running","running"],["completed","completed"],["succeeded","completed"],["failed","failed"],["rejected","rejected"],["stopped","stopped"],["suspended","stopped"]]);function se(e){if(typeof e=="string")return ne.get(e.trim().toLowerCase())}var re=15*6e4;function ae(e,p,n=re){if(T(e?.status))return;let i=Date.parse(e?.startedAt??"");if(Number.isFinite(i)&&!(p-i<n))return"stopped"}function K(e){return!e?.swanlabRun&&e?.swanlabUpload==="online"&&typeof e?.swanlabRunName=="string"&&e.swanlabRunName!==""}function ie(e){return!e||typeof e.seq!="number"?!1:!T(e.status)||K(e)}var B=200;async function oe(e,p,n,i,u){if(!(!p.some(t=>!T(t.status)&&typeof t.workloadRef=="string")||!n)){u.workloads+=1;try{let t=await n.query(e,"workloads",{limit:B},i),l=Array.isArray(t?.items)?t.items:[];return{byRef:new Map(l.map(d=>[`${d?.project}/${d?.name}`,d])),complete:l.length<B}}catch{return}}}async function ue(e,p,n,i,u){if(!(!p.some(K)||!n))try{let m=(await I(e)).config?.swanlab?.project;if(typeof m!="string"||m.trim()==="")return;let t=await n.connection(e);if(!t?.apiHost||!t?.apiKey)return;let l=m.trim();if(!l.includes("/")){u.userInfo+=1;let d=n.api.usernameOf(await n.api.userInfo(t,{signal:i}));if(!d)return;l=`${d}/${l}`}return u.runList+=1,{path:l,data:await n.api.runList(t,l,{signal:i})}}catch{return}}async function ce(e,{runnpu:p,swanlab:n,now:i=Date.now(),signal:u}={}){let m={workloads:0,runList:0,userInfo:0},t=[];if(!e)return{changed:t,requests:m};let l=(await E(e)).filter(ie);if(l.length===0)return{changed:t,requests:m};let[d,b]=await Promise.all([oe(e,l,p,u,m),ue(e,l,n,u,m)]);for(let f of l){let h={};if(d&&!T(f.status)&&typeof f.workloadRef=="string"){let w=d.byRef.get(f.workloadRef),g=w?se(w.status):d.complete?ae(f,i):void 0;g&&g!==f.status&&(h.status=g)}if(b&&K(f)){let w=n.api.findRunByName(b.data,f.swanlabRunName);w&&(h.swanlabRun=`${b.path}/${w.runId}`)}let y=h.status??f.status;if(T(y)&&!f.endedAt&&(h.endedAt=new Date(i).toISOString()),Object.keys(h).length!==0)try{let w=await D(e,f.seq,h);w.changed.length>0&&t.push({seq:f.seq,fields:w.changed})}catch{}}return{changed:t,requests:m}}var de="\u5B9E\u9A8C\u91CC\u8FD8\u6CA1\u6709\u4EFB\u4F55\u5173\u952E\u6307\u6807\u7684\u6570\u636E \u2014\u2014 \u53EF\u80FD\u662F\u8BAD\u7EC3\u521A\u8D77\u6B65\uFF0C\u4E5F\u53EF\u80FD\u662F .train/metrics.json \u7684\u952E\u5168\u90FD\u6CA1\u547D\u4E2D\u3002\u82E5\u6301\u7EED\u5982\u6B64\uFF0C\u8BA9 Agent \u7528 `swanlab api run series` \u590D\u6838\u5B9E\u9645\u7684\u6307\u6807\u540D\u5E76\u91CD\u65B0 training_metrics_set\u3002";function fe(e,p){let n=[];for(let u of Array.isArray(e)?e:[])typeof u=="string"&&u.trim()!==""&&!n.includes(u)&&n.push(u);let i=n.filter(u=>{let m=p?.[u];return!Array.isArray(m)||!m.some(t=>typeof t?.[1]=="number")});return i.length===0?{missing:i}:i.length===n.length?{missing:i,hint:de}:{missing:i,missingKeys:i}}var le=5*6e4;async function pe({swanlab:e,conn:p,cwd:n,runPath:i,keys:u,stamp:m,memo:t,now:l=Date.now(),signal:d}){let b=`${i}
|
|
2
|
+
${m}`,f=t?.get(n);if(f?.token===b&&(f.settled||l-f.at<le))return f.unknown?u.filter(y=>f.unknown.includes(y)):void 0;let h=y=>{if(t)for(t.delete(n),t.set(n,{token:b,at:l,...y});t.size>J;)t.delete(t.keys().next().value)};try{let y=await e.api.runKeys(p,i,{signal:d});if(y?.source!=="series"){h({settled:!1});return}let w=new Set(e.api.keysOf(y));if(w.size===0){h({settled:!1});return}let g=u.filter(r=>!w.has(r));return h({settled:g.length===0,unknown:g}),g}catch{h({settled:!1});return}}function me(e){let p=Array.isArray(e?.keys)?e.keys.map(n=>n?.name).join(","):"";return`${e?.inferredAt??""}|${p}`}function X(e,p){let n={...ee,...p??{}},i=n.apiPath,u=U({refresh:()=>Promise.all([...d.keys()].map(async r=>{let a=r||void 0;await ce(a,{runnpu:e.runnpu,swanlab:e.swanlab}).catch(()=>{}),await y(a).catch(()=>{})})),intervalMs:n.refreshMs}),m=u.hub,t=new Map,l=new Map,d=new Map,b=r=>{let a=e[r];if(!a)throw Object.assign(new Error(`${r} \u8FDE\u63A5\u5668\u672A\u6302\u8F7D`),{code:"connector_missing",status:503});return a};function f(r){return r.find(a=>typeof a.swanlabRun=="string"&&a.swanlabRun.length>0)}async function h(r,a){if(!r)return{state:"no-workspace"};if(!await j(r))return{state:"not-training-project"};let[s,o]=await Promise.all([E(r),L(r)]),c=f(s),k=s.find(A=>A.decision==="promote"),R=s[0]?{seq:s[0].seq,total:s.length,hyperparams:s[0].hyperparams,template:s[0].template,status:s[0].status,startedAt:s[0].startedAt,previous:s[1]?.hyperparams,decision:s[0].decision,promotedSeq:k?.seq}:void 0;if(!o?.keys?.length)return{state:"no-metric-keys",version:R,hint:"\u5173\u952E\u6307\u6807\u5C1A\u672A\u4ECE\u8BAD\u7EC3\u4EE3\u7801\u4E2D\u5206\u6790\u51FA\u6765"};if(!c)return{state:"no-run",version:R,keys:o.keys.slice(0,q)};let _=o.keys.slice(0,q),S=b("swanlab"),M=await S.connection(r);if(!M.apiKey||!M.apiHost)return{state:"swanlab-unconfigured",version:R,keys:_,run:c.swanlabRun};try{let A=_.map(Z=>Z.name),Q=await S.api.runMetrics(M,c.swanlabRun,A,{signal:a}),v=S.api.metricSeries(Q),{missing:$,...V}=fe(A,v),H=$.length>0?await pe({swanlab:S,conn:M,cwd:r,runPath:c.swanlabRun,keys:$,stamp:me(o),memo:l,signal:a}):void 0;return{state:"ok",version:R,keys:_,run:c.swanlabRun,runUrl:S.api.runWebUrl(M.webHost,c.swanlabRun),metrics:v,...V,...H?.length?{unknownKeys:H}:{},at:Date.now()}}catch(A){return{state:"error",version:R,keys:_,run:c.swanlabRun,reason:A.message,code:A.code}}}async function y(r){let a=await h(r),s=r??"";for(t.delete(s),t.set(s,a);t.size>J;)t.delete(t.keys().next().value);return m.publish("strip",{cwd:r,strip:a}),a}let w=[],g=(r,a,s)=>{w.push({path:a,handler:s})};return g("exact",`${i}/state`,x({async GET(r,a,s){let o=s.searchParams.get("cwd")||void 0,c=e.runnpu,k=e.swanlab,[R,_,S,M,A]=await Promise.all([j(o),E(o),L(o),c?c.health(o):Promise.resolve({state:"unconfigured",reason:"\u8FDE\u63A5\u5668\u672A\u6302\u8F7D"}),k?k.health(o):Promise.resolve({state:"unconfigured",reason:"\u8FDE\u63A5\u5668\u672A\u6302\u8F7D"})]);P(a,200,{workspace:o,isTrainingProject:R,runnpu:M,swanlab:A,connection:c?await c.describe(o):void 0,metricKeys:S?.keys?.slice(0,q)??[],runs:_.slice(0,20),refreshMs:n.refreshMs})}})),g("exact",`${i}/swanlab`,x({async POST(r,a){let s=await C(r),o=s?.mode==="self-hosted"?"self-hosted":"cloud";if(o==="self-hosted"&&!s?.apiHost){N(a,422,"validation_failed","\u79C1\u6709\u90E8\u7F72\u5FC5\u987B\u586B\u5199 API \u5730\u5740");return}let c=b("swanlab"),k=await c.configure({mode:o,apiHost:s?.apiHost,webHost:s?.webHost,apiKey:s?.apiKey,scope:s?.scope==="project"?"project":"global",cwd:s?.cwd||void 0}),R=await c.health(s?.cwd||void 0);P(a,200,{saved:k,health:R})}})),g("exact",`${i}/metrics`,x({async GET(r,a,s){let o=s.searchParams.get("cwd")||void 0;P(a,200,await y(o))}})),g("exact",`${i}/events`,(r,a)=>{let o=new URL(r.url??"/","http://127.0.0.1").searchParams.get("cwd")||"";u.handler(r,a,{seed(c){d.set(o,(d.get(o)??0)+1);let k=t.get(o);k&&c("strip",{cwd:o||void 0,strip:k})},onClose(){let c=(d.get(o)??1)-1;c>0?d.set(o,c):d.delete(o)}})}),{routes:w,start:u.start,stop:u.stop}}var Y="runnpu-training-node",Se=["webServer"];function Me(e,p){G(e);let n=X(z(e,{runnpu:"runnpu",swanlab:"swanlab"}),p);F(e,n.routes,Y),W(e,n,Y)}export{Me as apply,Se as inject,Y as name};
|
package/dist/training-tools.mjs
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import{a as
|
|
2
|
-
`)}return`sha256:${
|
|
3
|
-
`).map(
|
|
4
|
-
`)}function
|
|
5
|
-
`)}function
|
|
1
|
+
import{a as O,b as C,c as Q}from"./chunk-ZVARRQYA.mjs";import{g as Y}from"./chunk-MGSEPBNR.mjs";import{a as N}from"./chunk-XQPU3AJN.mjs";import{d as _,f as H,m as z}from"./chunk-4CZL4IT4.mjs";import"./chunk-JZXLCA2E.mjs";import{defineTool as Xe}from"@deepseek-ai/dsh-tools";import{execFile as ve,spawn as je}from"node:child_process";import{mkdtemp as He,readFile as X,rm as ze,writeFile as L,readdir as Ke}from"node:fs/promises";import{tmpdir as Be}from"node:os";import{join as P}from"node:path";function _e(n,r,s=5){let e=(n??[]).map(p=>p?.[1]).filter(p=>typeof p=="number"&&Number.isFinite(p));if(e.length===0)return{points:0,flags:["no-data"]};let t=e[e.length-1],i=r==="higher"?Math.max(...e):Math.min(...e),o={points:e.length,final:t,best:i,flags:[]},a=e.slice(-Math.max(2,s));if(a.length>=2&&a[0]!==0){let p=(a[a.length-1]-a[0])/Math.abs(a[0])*100;o.tailPct=p,Math.abs(p)<1?o.flags.push("plateau"):(r==="higher"?p>0:p<0)?o.flags.push("improving"):o.flags.push("worsening")}return o}function Z(n,r,s={}){let e=s.tailK??5,t=n.map(o=>({label:o.label,perKey:Object.fromEntries(r.map(a=>[a.name,_e(o.series?.[a.name],I(a.direction),e)]))})),i=r.map(o=>{let a=I(o.direction),p=t.map(l=>({label:l.label,final:l.perKey[o.name].final})).filter(l=>typeof l.final=="number").sort((l,g)=>a==="higher"?g.final-l.final:l.final-g.final),d={key:o.name,direction:a};if(p.length===0)return d;let[c,u]=p;return d.winner=c.label,d.final=c.final,u&&u.final!==0&&(d.runnerUp=u.label,d.marginPct=Math.abs((c.final-u.final)/Math.abs(u.final)*100)),d});return{rows:t,winners:i}}function I(n){return n==="higher"?"higher":"lower"}import{createHash as ee}from"node:crypto";import{createReadStream as Oe}from"node:fs";import{copyFile as Ae,mkdir as te,readdir as Te,stat as K}from"node:fs/promises";import{dirname as De,join as A,resolve as ne}from"node:path";var S=class extends Error{constructor(r,s,e){super(s),this.name="UploadError",this.code=r,this.details=e}},re=32222,Le="/workspace/code",Me="/workspace/data",B=Object.freeze([Object.freeze({pattern:".train/secrets.json",why:"SwanLab / RunNPU \u51ED\u636E\uFF0C\u7EDD\u4E0D\u8FDB\u5BB9\u5668",group:"secret"}),Object.freeze({pattern:".train",why:"\u8BAD\u7EC3\u8D26\u672C\u662F\u672C\u5730\u7684\uFF0C\u5BB9\u5668\u91CC\u7528\u4E0D\u5230",group:"secret"})]),J=Object.freeze([Object.freeze({pattern:".git",why:"\u7248\u672C\u5E93\u5386\u53F2\uFF0C\u5BB9\u5668\u91CC\u8DD1\u8BAD\u7EC3\u7528\u4E0D\u5230",group:"noise"}),Object.freeze({pattern:"__pycache__",why:"\u5B57\u8282\u7801\u7F13\u5B58\uFF0C\u67B6\u6784\u76F8\u5173\u4E14\u4F1A\u88AB\u91CD\u65B0\u751F\u6210",group:"noise"}),Object.freeze({pattern:"*.pyc",why:"\u540C\u4E0A",group:"noise"}),Object.freeze({pattern:"node_modules",why:"\u524D\u7AEF\u4F9D\u8D56\uFF0C\u8BAD\u7EC3\u955C\u50CF\u91CC\u6CA1\u6709\u7528",group:"noise"}),Object.freeze({pattern:".DS_Store",why:"macOS \u76EE\u5F55\u5143\u6570\u636E",group:"noise"}),Object.freeze({pattern:"swanlog",why:"SwanLab \u672C\u5730\u65E5\u5FD7\u76EE\u5F55\uFF0C\u5BB9\u5668\u91CC\u4F1A\u91CD\u65B0\u751F\u6210",group:"noise"})]),ae=Object.freeze([Object.freeze({pattern:"*.ckpt",why:"\u6743\u91CD\u6587\u4EF6\uFF1A\u4EE3\u7801\u9053\u6BCF\u8F6E\u90FD\u540C\u6B65\uFF0C\u522B\u628A\u5B83\u5E26\u4E0A\uFF08\u7528 dataset:true \u5355\u72EC\u4F20\uFF09",group:"weight"}),Object.freeze({pattern:"*.safetensors",why:"\u540C\u4E0A",group:"weight"}),Object.freeze({pattern:"*.pt",why:"\u540C\u4E0A",group:"weight"}),Object.freeze({pattern:"*.pth",why:"\u540C\u4E0A",group:"weight"}),Object.freeze({pattern:"*.bin",why:"\u540C\u4E0A\uFF08HF \u6743\u91CD\u5206\u7247\u5E38\u7528\u8FD9\u4E2A\u540E\u7F00\uFF09",group:"weight"})]);function qe(n){if(typeof n!="string")throw new S("bad_exclude",`extraExcludes \u53EA\u63A5\u53D7\u5B57\u7B26\u4E32\uFF0C\u6536\u5230 ${JSON.stringify(n)}`);let r=n.trim();if(r==="")throw new S("bad_exclude","extraExcludes \u91CC\u6709\u4E00\u4E2A\u7A7A\u6A21\u5F0F");if(/[\0\n\r]/.test(r))throw new S("bad_exclude",`\u6392\u9664\u6A21\u5F0F\u4E0D\u80FD\u5305\u542B\u6362\u884C\u6216 NUL\uFF1A${JSON.stringify(n)}`);if(/^[!+]/.test(r)||/^-\s/.test(r))throw new S("bad_exclude",`\u6392\u9664\u6A21\u5F0F ${JSON.stringify(r)} \u770B\u8D77\u6765\u662F\u4E00\u6761\u300C\u91CD\u65B0\u5305\u542B / \u6E05\u7A7A\u89C4\u5219\u300D\u7684 filter \u8BED\u6CD5\u3002extraExcludes \u53EA\u80FD\u589E\u52A0\u6392\u9664\u9879\uFF0C\u4E0D\u80FD\u64A4\u9500\u5F3A\u5236\u6392\u9664\uFF08\u5C24\u5176\u662F .train/secrets.json\uFF09\u3002`);return r}function pe(n,r=[]){let e=(n==="data"?[...B,...J]:[...B,...J,...ae]).map(i=>({...i})),t=new Set(e.map(i=>i.pattern));for(let i of r??[]){let o=qe(i);t.has(o)||(t.add(o),e.push({pattern:o,why:"\u8C03\u7528\u65B9\u8FFD\u52A0",group:"extra"}))}return e}var se=new Map;function oe(n){let r="";for(let s of n)s==="*"?r+="[^/]*":s==="?"?r+="[^/]":r+=s.replace(/[.+^${}()|[\]\\]/g,"\\$&");return r}function Ce(n){let r=se.get(n);if(r)return r;let s=n.trim(),e=s.endsWith("/");e&&(s=s.slice(0,-1)),s.startsWith("/")&&(s=s.slice(1));let t=s.includes("/"),i={anchored:t,dirOnly:e,regex:new RegExp(t?`^${oe(s)}(?:/.*)?$`:`^${oe(s)}$`)};return se.set(n,i),i}function Ue(n,r,s=!1){let e=n.split("/");for(let t of r){let i=Ce(t);if(!(i.dirOnly&&!s)){if(i.anchored){if(i.regex.test(n))return t;continue}for(let o of e)if(i.regex.test(o))return t}}}async function ce(n,r,s={}){let e=s.maxFiles??2e5,t=[],i=new Map,o=[],a=0,p=0,d=new Map([...B,...J,...ae].map(l=>[l.pattern,l.why])),c=(l,g,h)=>{let y=i.get(l);y||(y={pattern:l,why:d.get(l)??"\u8C03\u7528\u65B9\u8FFD\u52A0",files:0,dirs:0,bytes:0},i.set(l,y)),g==="dir"?y.dirs+=1:(y.files+=1,y.bytes+=h)},u=[""];for(;u.length>0;){s.signal?.throwIfAborted();let l=u.shift(),g;try{g=await Te(A(n,l),{withFileTypes:!0})}catch(h){throw new S("scan_failed",`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55 ${A(n,l)}\uFF1A${h?.message??h}`)}g.sort((h,y)=>h.name<y.name?-1:h.name>y.name?1:0);for(let h of g){let y=l===""?h.name:`${l}/${h.name}`,j=h.isDirectory();if(!j&&!h.isFile()){a+=1,o.length<20&&o.push(y);continue}let f=Ue(y,r,j);if(f!==void 0){if(j)c(f,"dir",0);else{let b=0;try{b=(await K(A(n,y))).size}catch{}c(f,"file",b)}continue}if(j){u.push(y);continue}let $;try{$=await K(A(n,y))}catch{a+=1,o.length<20&&o.push(y);continue}if(t.push({path:y,size:$.size}),p+=$.size,t.length>e)throw new S("too_many_files",`\u5F85\u4E0A\u4F20\u6587\u4EF6\u8D85\u8FC7 ${e} \u4E2A\uFF0C\u5DF2\u4E2D\u6B62\u3002\u8BF7\u628A source \u7F29\u5C0F\u5230\u771F\u6B63\u8981\u4F20\u7684\u76EE\u5F55\uFF0C\u6216\u7528 extraExcludes \u6392\u6389\u6570\u636E/\u4EA7\u7269\u76EE\u5F55\uFF08\u6570\u636E\u96C6\u8BF7\u7528 dataset: true \u5355\u72EC\u8D70\u6570\u636E\u901A\u9053\uFF09\u3002`,{maxFiles:e})}}return t.sort((l,g)=>l.path<g.path?-1:l.path>g.path?1:0),{files:t,bytes:p,excluded:[...i.values()],skipped:o,skippedCount:a}}async function de(n,r,s){let e=[...r].sort((i,o)=>i.path<o.path?-1:i.path>o.path?1:0),t=ee("sha256");for(let i of e){s?.throwIfAborted();let o=ee("sha256");o.update(i.path,"utf8"),o.update(Buffer.from([0]));let a=Oe(A(n,i.path));for await(let p of a)o.update(p);t.update(o.digest("hex")),t.update(`
|
|
2
|
+
`)}return`sha256:${t.digest("hex")}`}async function le(n,r,s,e){await te(s,{recursive:!0});let t=new Set,i=0;for(let o of r){e?.throwIfAborted();let a=A(s,o.path),p=De(a);t.has(p)||(await te(p,{recursive:!0}),t.add(p)),await Ae(A(n,o.path),a),i+=o.size}return{files:r.length,bytes:i}}function Ie(n){let s=(Array.isArray(n?.items)?n.items:[]).filter(e=>typeof e?.internal_ip=="string"&&e.internal_ip!=="").sort((e,t)=>String(e.name)<String(t.name)?-1:String(e.name)>String(t.name)?1:0);return s.find(e=>e.status==="ready")??s[0]}function ue(n,r,s={}){let e=s.ref?`\u8D1F\u8F7D ${s.ref}`:"\u8FD9\u4E2A\u8D1F\u8F7D",t=n?.ssh;if(!t||t.enabled!==!0)throw new S("ssh_disabled",`${e} \u6CA1\u6709\u5F00\u542F SSH\uFF08\u5E73\u53F0\u62A5\u544A ssh.enabled = false\uFF09\u3002\u4E0A\u4F20\u53EA\u8D70 SSH \u901A\u9053\uFF1A\u8BF7\u5E26 \`--ssh\` \u91CD\u65B0\u521B\u5EFA\u8D1F\u8F7D\uFF0C\u6216\u6362\u4E00\u4E2A\u5DF2\u5F00 SSH \u7684 training-env / workspace\u3002\uFF08\u4E0D\u4F1A\u9000\u56DE \`runnpu workload exec\`\u2014\u2014\u90A3\u6761\u8DEF\u8981\u8FC7 K8s API Server\uFF0C\u5DE5\u7A0B\u6D41\u91CF\u4E0D\u8BE5\u5360\u7528\u63A7\u5236\u9762\u3002\uFF09`,{ref:s.ref});let i=typeof t.username=="string"&&t.username.trim()!==""?t.username.trim():void 0;if(i===void 0){let u=Array.isArray(n?.pods)?n.pods:[],l=u.filter(g=>g?.ready===!0);if(l.length===0){let g=u.length===0?"\u5F53\u524D\u6CA1\u6709\u4EFB\u4F55 Pod":`\u5F53\u524D ${u.length} \u4E2A Pod \u90FD\u4E0D\u662F Ready\uFF08${u.map(h=>`${h?.name??"?"}: ${h?.phase??"?"}`).join("\u3001")}\uFF09`;throw new S("ssh_pending",`${e} \u7684 SSH \u5DF2\u5F00\u542F\uFF0C\u4F46\u5E73\u53F0\u8FD8\u6CA1\u6709\u62A5\u544A\u767B\u5F55\u540D\uFF1Aoperator \u53EA\u5BF9 **Ready \u7684 Pod** \u4E0A\u62A5 status.ssh\uFF0C${g}\u3002\u8FD9\u662F\u300C\u7B49\u4E00\u7B49\u300D\uFF0C\u4E0D\u662F\u914D\u7F6E\u9519\u2014\u2014\u7B49\u8D1F\u8F7D\u8D77\u6765\u518D\u4F20\uFF1B\u5982\u679C\u4E00\u76F4\u8D77\u4E0D\u6765\uFF0C\u5148\u770B \`runnpu workload events\` \u6392\u67E5 Pod\u3002`,{pods:u.length,ready:l.length})}throw new S("ssh_username_missing",`${e} \u7684 SSH \u5DF2\u5F00\u542F\u3001Pod \u4E5F Ready\uFF0C\u4F46\u5E73\u53F0\u6CA1\u6709\u900F\u51FA ssh.username\u3002\u8FD9\u662F\u63A7\u5236\u53F0\u7248\u672C\u592A\u65E7\uFF08\u8BE5\u6295\u5F71 2026-08 \u624D\u8865\u9F50\uFF09\uFF0C\u9700\u8981\u91CD\u65B0\u90E8\u7F72 / \u5347\u7EA7\u63A7\u5236\u53F0\u3002`+(t.target?`**\u4E0D\u8981**\u62FF ssh.target\uFF08${t.target}\uFF09\u5F53\u7528\u6237\u540D\u2014\u2014\u90A3\u662F piper\u2192\u5BB9\u5668\u90A3\u4E00\u6BB5\u7684\u4E0A\u6E38\u5730\u5740\uFF0C\u7528\u5B83\u767B\u5F55\u4F1A\u88AB\u5224 no matching pipe\uFF0C\u8868\u73B0\u4E3A Permission denied\u3002`:""),{target:t.target})}let o=[],a=typeof t.host=="string"?t.host.trim():"",p={value:a,from:"projection"};if(a===""){let u=typeof s.sshHost=="string"?s.sshHost.trim():"";if(u!=="")a=u,p={value:a,from:"config"},o.push(`\u5165\u53E3\u4E3B\u673A\u6765\u81EA\u63D2\u4EF6\u914D\u7F6E sshHost=${a}\uFF08\u5E73\u53F0\u5C1A\u672A\u6295\u5F71 ssh.host\uFF09\u3002`);else{let l=Ie(r);if(!l)throw new S("no_ssh_host","\u5E73\u53F0\u6CA1\u6709\u7ED9\u51FA SSH \u5165\u53E3\u5730\u5740\uFF08ssh.host \u7F3A\u5931\u2014\u2014\u7EBF\u4E0A\u63A7\u5236\u53F0\u9700\u8981\u91CD\u65B0\u90E8\u7F72\u4E00\u6B21\u624D\u4F1A\u5E26\u4E0A\u8FD9\u4E2A\u5B57\u6BB5\uFF09\uFF0C\u8282\u70B9\u5217\u8868\u91CC\u4E5F\u6CA1\u6709\u53EF\u7528\u7684 internal_ip \u53EF\u4EE5\u515C\u5E95\u3002\u8BF7\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u663E\u5F0F\u8BBE\u7F6E sshHost\uFF08\u67D0\u53F0\u8282\u70B9 IP \u6216 SSHPiper \u7684 VIP\uFF09\u3002");a=String(l.internal_ip),p={value:a,from:"node",node:String(l.name??"")},o.push(`\u5165\u53E3\u4E3B\u673A\u53D6\u81EA\u8282\u70B9 ${l.name} \u7684 internal_ip=${a}\u2014\u2014\u5E73\u53F0\u8FD8\u6CA1\u6295\u5F71 ssh.host\u3002SSHPiper \u662F NodePort\uFF0C\u6BCF\u53F0\u8282\u70B9\u90FD\u901A\uFF0C\u6240\u4EE5\u8FD9\u662F\u53EF\u7528\u7684\u515C\u5E95\uFF1B\u63A7\u5236\u53F0\u91CD\u65B0\u90E8\u7F72\u540E\u4F1A\u81EA\u52A8\u6539\u7528\u5E73\u53F0\u7ED9\u7684\u503C\u3002`)}}let d=Number(t.port),c;return Number.isInteger(d)&&d>0&&d<65536?c={value:d,from:"projection"}:Number.isInteger(s.sshPort)&&Number(s.sshPort)>0&&Number(s.sshPort)<65536?(c={value:Number(s.sshPort),from:"config"},o.push(`\u5165\u53E3\u7AEF\u53E3\u6765\u81EA\u63D2\u4EF6\u914D\u7F6E sshPort=${c.value}\uFF08\u5E73\u53F0\u5C1A\u672A\u6295\u5F71 ssh.port\uFF09\u3002`)):(c={value:re,from:"default"},o.push(`\u5165\u53E3\u7AEF\u53E3\u7528\u9ED8\u8BA4\u503C ${re}\uFF08SSHPiper \u7684 NodePort\uFF09\u2014\u2014\u5E73\u53F0\u8FD8\u6CA1\u6295\u5F71 ssh.port\u3002\u5982\u679C\u90E8\u7F72\u65B9\u6539\u8FC7\u8FD9\u4E2A\u7AEF\u53E3\uFF0C\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u8BBE sshPort\u3002`)),{username:i,host:a,port:c.value,target:typeof t.target=="string"&&t.target!==""?t.target:void 0,derivation:{username:{value:i,from:"projection"},host:p,port:c,fallback:p.from!=="projection"||c.from!=="projection",notes:o}}}function U({hasRsync:n,hasTar:r,lane:s}){return n?s==="data"?{method:"rsync",reason:"\u8FDC\u7AEF\u6709 rsync\uFF1A\u589E\u91CF\u4F20\u8F93 + --partial \u65AD\u70B9\u7EED\u4F20\uFF0C\u5DF2\u5728\u8FDC\u7AEF\u7684\u540C\u540D\u540C\u5927\u5C0F\u6587\u4EF6\u4F1A\u81EA\u52A8\u8DF3\u8FC7\uFF1B\u4E0D\u5E26 --delete\uFF0C\u6570\u636E\u96C6\u53EA\u589E\u4E0D\u5220\u3002"}:{method:"rsync",reason:"\u8FDC\u7AEF\u6709 rsync\uFF1A\u589E\u91CF\u4F20\u8F93 + --delete\uFF0C\u672C\u5730\u5220\u6389\u7684\u6587\u4EF6\u5728\u5BB9\u5668\u91CC\u4E5F\u4F1A\u6D88\u5931\uFF0C\u4EE3\u7801\u9053\u6BCF\u8F6E\u540C\u6B65\u6700\u7701\u5E26\u5BBD\u3002"}:s==="data"?{method:"sftp",reason:"\u8FDC\u7AEF\u6CA1\u6709 rsync\uFF1A\u6570\u636E\u9053\u4E0D\u8D70 tar \u7BA1\u9053\uFF08tar \u6D41\u65E2\u4E0D\u80FD\u7EED\u4F20\u4E5F\u4E0D\u80FD\u8DF3\u8FC7\u5DF2\u6709\u6587\u4EF6\uFF09\uFF0C\u6539\u7528 helper \u5185\u5D4C\u7684 SFTP \u5B50\u7CFB\u7EDF\uFF0C\u9010\u6587\u4EF6 reput \u7EED\u4F20\u3002"}:r?{method:"tar",reason:"\u8FDC\u7AEF\u6CA1\u6709 rsync\u3001\u6709 tar\uFF1A\u8D70 SSH \u7684 exec \u901A\u9053\u505A tar \u7BA1\u9053\uFF0C\u4E00\u6B21\u6027\u5168\u91CF\u63A8\u9001\uFF08\u540C\u4E00\u6761 TCP\uFF0C\u4E0D\u8FC7 API Server\uFF09\u3002"}:{method:"sftp",reason:"\u8FDC\u7AEF\u65E2\u6CA1\u6709 rsync \u4E5F\u6CA1\u6709 tar\uFF08distroless \u955C\u50CF\u5E38\u89C1\uFF09\uFF1A\u7528 helper \u4E8C\u8FDB\u5236\u5185\u5D4C\u7684 SFTP \u5B50\u7CFB\u7EDF\uFF0C\u5B83\u4E0E\u955C\u50CF\u5185\u5BB9\u65E0\u5173\uFF0C\u662F\u552F\u4E00\u666E\u9002\u901A\u9053\u3002"}}function he(n){return[{probe:"rsync",...U({hasRsync:!0,hasTar:!0,lane:n})},{probe:"tar",...U({hasRsync:!1,hasTar:!0,lane:n})},{probe:"none",...U({hasRsync:!1,hasTar:!1,lane:n})}]}var G=Object.freeze(["-o","BatchMode=yes","-o","StrictHostKeyChecking=accept-new","-o","ConnectTimeout=10"]),fe="command -v rsync >/dev/null 2>&1 && echo rsync; command -v tar >/dev/null 2>&1 && echo tar; exit 0";function me(n){let r=String(n??"").split(`
|
|
3
|
+
`).map(s=>s.trim());return{hasRsync:r.includes("rsync"),hasTar:r.includes("tar")}}function W(n,r){return["-p",String(n.port),...G,`${n.username}@${n.host}`,r]}function ie(n){let r=String(n);if(r.includes("\0"))throw new S("bad_path","\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B NUL");return`'${r.replaceAll("'","'\\''")}'`}function D(n){let r=String(n??"");if(!r.startsWith("/"))throw new S("bad_dest",`\u8FDC\u7AEF\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6536\u5230 ${JSON.stringify(n)}`);if(!/^\/[A-Za-z0-9._@+:\-/]*$/.test(r))throw new S("bad_dest",`\u8FDC\u7AEF\u76EE\u5F55\u53EA\u63A5\u53D7\u5B57\u6BCD\u3001\u6570\u5B57\u548C . _ - + @ : / \u8FD9\u4E9B\u5B57\u7B26\uFF0C\u6536\u5230 ${JSON.stringify(n)}\u2014\u2014\u542B\u7A7A\u683C\u6216\u5F15\u53F7\u7684\u8DEF\u5F84\u4F1A\u540C\u65F6\u7A7F\u8FC7\u8FDC\u7AEF shell \u548C sftp \u7684\u5206\u8BCD\u5668\uFF0C\u4E0D\u503C\u5F97\u4E3A\u5B83\u5192\u9669\u3002`);let s=r.replace(/\/+$/,"")||"/";if(s.split("/").includes(".."))throw new S("bad_dest",`\u8FDC\u7AEF\u76EE\u5F55\u4E0D\u80FD\u5305\u542B ..\uFF1A${JSON.stringify(n)}`);return s}function ye({entry:n,source:r,dest:s,patterns:e,lane:t}){let i=D(s),o=t==="data"?["-a","--partial"]:["-a","-z","--delete"];for(let a of e)o.push("--exclude",a);return o.push("-e",`ssh -p ${n.port} ${G.join(" ")}`),o.push(`${r.replace(/\/+$/,"")}/`,`${n.username}@${n.host}:${i}/`),o}function we({entry:n,source:r,dest:s,listFile:e}){let t=D(s);return{local:{bin:"tar",argv:["-c","-f","-","-C",r,"--null","-T",e],env:{COPYFILE_DISABLE:"1"}},remote:{bin:"ssh",argv:W(n,`mkdir -p ${ie(t)} && tar -x --no-same-owner --no-same-permissions -f - -C ${ie(t)}`)}}}function T(n){let r=String(n);if(/[\0\n\r]/.test(r))throw new S("bad_path",`\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B\u6362\u884C\u6216 NUL\uFF1A${JSON.stringify(n)}`);return`"${r.replace(/(["\\])/g,"\\$1")}"`}function ge(n){let r=n.split("/").filter(Boolean),s=[],e="";for(let t of r)e+=`/${t}`,s.push(`-mkdir ${T(e)}`);return s}function be({dest:n,stagedPath:r,lane:s}){let t=D(n).split("/").slice(0,-1).join("/")||"/",i=s==="data"?"reput -r":"put -r";return[...ge(t),`${i} ${T(r)} ${T(t)}`,""].join(`
|
|
4
|
+
`)}function $e({dest:n,files:r,lane:s,localRoot:e}){let t=D(n),i=s==="data"?"reput":"put",o=[`lcd ${T(e)}`,...ge(t)],a=new Set;for(let p of r){let d=p.path.split("/"),c=t;for(let u of d.slice(0,-1))c+=`/${u}`,!a.has(c)&&(a.add(c),o.push(`-mkdir ${T(c)}`))}for(let p of r)o.push(`${i} ${T(p.path)} ${T(`${t}/${p.path}`)}`);return o.push(""),o.join(`
|
|
5
|
+
`)}function V({entry:n,batchFile:r}){return["-b",r,"-P",String(n.port),...G,`${n.username}@${n.host}`]}function Se(n){return n==="data"?Me:Le}async function ke(n,r){let s=r?ne(n,r):ne(n),e;try{e=await K(s)}catch{throw new S("no_source",`\u672C\u5730\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${s}`)}if(!e.isDirectory())throw new S("no_source",`source \u5FC5\u987B\u662F\u76EE\u5F55\uFF1A${s}`);return s}var xe=5,Pe=n=>`${String(n).padStart(4,"0")}.json`;function M(n){let r=n?.cwd;if(!r)throw new Error("\u8FD9\u4E2A\u5DE5\u5177\u9700\u8981\u4E00\u4E2A\u7ED1\u5B9A\u4E86\u5DE5\u4F5C\u76EE\u5F55\u7684\u4F1A\u8BDD\uFF08\u8BAD\u7EC3\u6A21\u5F0F\u5FC5\u987B\u5148\u9009\u597D\u8BAD\u7EC3\u5DE5\u7A0B\u76EE\u5F55\uFF09");return r}function Je(n,r,s={}){return new Promise((e,t)=>{ve(n,r,{env:{...process.env,...s.env??{},NO_COLOR:"1"},timeout:s.timeoutMs??15*6e4,maxBuffer:32*1024*1024,signal:s.signal},(i,o,a)=>{if(i){let p=String(a||"").trim().slice(0,4e3);t(Object.assign(new Error(p||`${n} \u6267\u884C\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${i.code??"\u672A\u77E5"}\uFF09`),{stderr:p}));return}e(o)})})}function F(n,r,s={}){return new Promise((e,t)=>{ve(n,r,{env:{...process.env,NO_COLOR:"1"},timeout:s.timeoutMs??15e3,maxBuffer:8*1024*1024,signal:s.signal},(i,o,a)=>{if(i){t(Re(n,i,a));return}e({stdout:String(o),stderr:String(a)})})})}function Re(n,r,s){let e=String(s||"").trim().slice(0,2e3);if(r?.code==="ENOENT")return new Error(`\u672C\u673A\u6CA1\u6709 ${n} \u547D\u4EE4\uFF1A\u4E0A\u4F20\u7528\u7684\u662F\u672C\u673A\u7684 ssh / sftp / rsync / tar \u5BA2\u6237\u7AEF\uFF0C\u8BF7\u5148\u628A ${n} \u88C5\u4E0A\u518D\u8BD5\u3002`);let t=r?.killed?"\u8D85\u65F6\u88AB\u7EC8\u6B62":typeof r?.code=="number"?`\u9000\u51FA\u7801 ${r.code}`:`${r?.code??"\u672A\u77E5\u9519\u8BEF"}`;return new Error(`${n} ${t}${e?`\uFF1A${e}`:""}`)}function Ge(n,r,s){return new Promise((e,t)=>{let i={...process.env,NO_COLOR:"1"},o=je(n.bin,n.argv,{env:{...i,...n.env??{}},stdio:["ignore","pipe","pipe"]}),a=je(r.bin,r.argv,{env:i,stdio:["pipe","pipe","pipe"]}),p=!1,d={[n.bin]:"",[r.bin]:""},c=y=>{p||(p=!0,clearTimeout(u),s.signal?.removeEventListener("abort",l),o.kill("SIGKILL"),a.kill("SIGKILL"),y?t(y):e(void 0))},u=setTimeout(()=>c(new Error(`tar \u7BA1\u9053\u5728 ${s.timeoutMs}ms \u5185\u6CA1\u6709\u5B8C\u6210\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u90E8\u5206\u6587\u4EF6\u53EF\u80FD\u5DF2\u7ECF\u843D\u5730\uFF09`)),s.timeoutMs),l=()=>c(new Error("\u4E0A\u4F20\u5DF2\u53D6\u6D88"));s.signal?.addEventListener("abort",l,{once:!0}),o.stderr.on("data",y=>{d[n.bin]=(d[n.bin]+y).slice(0,2e3)}),a.stderr.on("data",y=>{d[r.bin]=(d[r.bin]+y).slice(0,2e3)}),o.stdout.pipe(a.stdin),o.stdout.on("error",()=>{}),a.stdin.on("error",()=>{});let g=[[o,n],[a,r]];for(let[y,j]of g)y.on("error",f=>c(Re(j.bin,f,d[j.bin])));let h;o.on("close",y=>{h=y,y!==0&&c(new Error(`\u672C\u5730 ${n.bin} \u6253\u5305\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${y}\uFF09${d[n.bin]?`\uFF1A${d[n.bin].trim()}`:""}`))}),a.on("close",y=>{if(y!==0){c(new Error(`\u8FDC\u7AEF\u89E3\u5305\u5931\u8D25\uFF08${r.bin} \u9000\u51FA\u7801 ${y}\uFF09${d[r.bin]?`\uFF1A${d[r.bin].trim()}`:""}`));return}h===void 0?o.on("close",()=>c(void 0)):h===0&&c(void 0)})})}async function We(n){let r;try{r=await Ke(P(_(n),"runs"))}catch{return 1}let s=r.filter(e=>e.endsWith(".json")).map(e=>Number.parseInt(e,10)).filter(Number.isInteger);return s.length===0?1:Math.max(...s)+1}function Ve(n,r){return!(n.length===2&&/(^|\/)(ba)?sh$/.test(n[0])&&n[1]==="-c")||r.length!==1?{args:r,applied:!1,warning:'SwanLab API \u5730\u5740\u4ECE\u672C\u673A\u63A2\u6D4B\u4E0D\u901A\uFF0C\u4F46\u542F\u52A8\u547D\u4EE4\u4E0D\u662F `bash -c "<\u811A\u672C>"` \u7684\u5F62\u72B6\uFF0C\u65E0\u6CD5\u81EA\u52A8\u964D\u7EA7\u3002\u8BF7\u5728\u8BAD\u7EC3\u547D\u4EE4\u524D\u81EA\u884C\u52A0 `swanlab offline`\uFF0C\u5426\u5219\u8FD9\u6B21\u8BAD\u7EC3\u4E0D\u4F1A\u6709\u66F2\u7EBF\u3002'}:{args:[`swanlab offline >/dev/null 2>&1; ${r[0]}`],applied:!0,warning:void 0}}function Ne(n,r={}){let s=[];return s.push(O({name:"training_upload",description:'Upload this project into a workload over SSH. Use this INSTEAD of composing scp/rsync/sftp yourself for every code or dataset upload: the exclusion of .train/secrets.json is enforced here as a mechanism rather than remembered, the transport is chosen from what the remote image actually has (rsync, a tar pipe, or the SFTP subsystem built into the SSH helper, which works even on a distroless image), and the code lane returns a codeRev content hash that you then pass to training_submit so "which code ran" stays answerable. Two lanes: code (the default \u2014 small, re-synced every iteration) and dataset:true (large, uploaded once to a separate destination; never re-send a dataset alongside the code). Call it with dryRun:true first when you want to show the user the plan \u2014 which files, which exclusions, which SSH entry \u2014 before anything moves.',parameters:{type:"object",properties:{project:{type:"string",description:"RunNPU project slug that owns the target workload."},name:{type:"string",description:"Target workload name. It must already have SSH enabled and a Ready pod."},source:{type:"string",description:"Local directory to upload, absolute or relative to the workspace. Defaults to the workspace root."},dest:{type:"string",description:"Absolute remote directory. Defaults to /workspace/code, or /workspace/data when dataset is true."},dataset:{type:"boolean",description:"Switch to the DATA lane: a separate destination, resume-first transfer, no codeRev, and model-weight suffixes are NOT excluded (they are the payload). Use it for anything large and stable, and do not re-upload it with every code change."},dryRun:{type:"boolean",description:"Report the plan \u2014 SSH entry, candidate methods, file and exclusion counts \u2014 and transfer nothing."},extraExcludes:{type:"array",items:{type:"string"},description:"Additional exclude patterns (rsync flavour: a pattern without a slash matches any path segment). These only ADD; the forced rules, .train/secrets.json first among them, cannot be cancelled."},timeoutMs:{type:"integer",description:"Transfer deadline in milliseconds. Default 1800000 (30 minutes)."}},required:["project","name"]},result:{type:"object",additionalProperties:!1,properties:{workloadRef:{type:"string"},lane:{type:"string",enum:["code","data"]},dryRun:{type:"boolean"},source:{type:"string"},dest:{type:"string"},method:{type:"string",enum:["rsync","tar","sftp"]},methodReason:{type:"string"},methodCandidates:{type:"array",items:{type:"object",additionalProperties:!1,properties:{probe:{type:"string"},method:{type:"string"},reason:{type:"string"}},required:["probe","method","reason"]}},entry:{type:"object",additionalProperties:!1,properties:{username:{type:"string"},host:{type:"string"},port:{type:"integer"},fallback:{type:"boolean"},from:{type:"object",additionalProperties:!1,properties:{username:{type:"string"},host:{type:"string"},port:{type:"string"}},required:["username","host","port"]},notes:{type:"array",items:{type:"string"}}},required:["username","host","port","fallback","from","notes"]},entryError:{type:"object",additionalProperties:!1,properties:{code:{type:"string"},message:{type:"string"}},required:["code","message"]},files:{type:"integer"},bytes:{type:"integer"},excludePatterns:{type:"array",items:{type:"string"}},excluded:{type:"array",items:{type:"object",additionalProperties:!1,properties:{pattern:{type:"string"},why:{type:"string"},files:{type:"integer"},dirs:{type:"integer"},bytes:{type:"integer"}},required:["pattern","why","files","dirs","bytes"]}},skipped:{type:"array",items:{type:"string"}},skippedCount:{type:"integer"},codeRev:{type:"string"},durationMs:{type:"integer"},warnings:{type:"array",items:{type:"string"}}},required:["workloadRef","lane","dryRun","source","dest","files","bytes","excludePatterns","excluded","skipped","skippedCount","durationMs","warnings"]},content:(e,t)=>{let i=t.dryRun?`\u4E0A\u4F20\u8BA1\u5212\uFF08dryRun\uFF0C\u6CA1\u6709\u4F20\u4EFB\u4F55\u4E1C\u897F\uFF09\uFF1A${t.source} \u2192 ${t.workloadRef}:${t.dest}`:`\u5DF2\u4E0A\u4F20\u5230 ${t.workloadRef}:${t.dest}\uFF08${t.method}\uFF0C${Math.round(t.durationMs/100)/10}s\uFF09`,o=t.entry?`\u5165\u53E3\uFF1A${t.entry.username}@${t.entry.host}:${t.entry.port}\uFF08\u7528\u6237\u540D\u6765\u81EA${t.entry.from.username==="projection"?"\u5E73\u53F0\u6295\u5F71":t.entry.from.username}\uFF0C\u4E3B\u673A\u6765\u81EA${t.entry.from.host}\uFF0C\u7AEF\u53E3\u6765\u81EA${t.entry.from.port}\uFF09`:`\u5165\u53E3\u672A\u89E3\u6790\uFF1A${t.entryError?.message??"\u672A\u77E5\u539F\u56E0"}`,a=t.excluded.length===0?"\u5F3A\u5236\u6392\u9664\u6E05\u5355\u6CA1\u6709\u547D\u4E2D\u4EFB\u4F55\u6587\u4EF6":`\u5F3A\u5236\u6392\u9664\u547D\u4E2D\uFF1A${t.excluded.map(p=>`${p.pattern}\uFF08${p.files} \u6587\u4EF6 / ${p.dirs} \u76EE\u5F55\uFF09`).join("\u3001")}`;return[{type:"text",text:`${i}
|
|
6
6
|
${t.files} \u4E2A\u6587\u4EF6\u3001${t.bytes} \u5B57\u8282${t.skippedCount>0?`\uFF0C\u8DF3\u8FC7 ${t.skippedCount} \u4E2A\u975E\u666E\u901A\u6587\u4EF6\uFF08\u7B26\u53F7\u94FE\u63A5\u7B49\uFF09`:""}
|
|
7
|
-
${
|
|
8
|
-
${
|
|
7
|
+
${a}
|
|
8
|
+
${o}`+(t.codeRev?`
|
|
9
9
|
codeRev\uFF1A${t.codeRev}\uFF08\u63D0\u4EA4\u8BAD\u7EC3\u65F6\u628A\u5B83\u4F20\u7ED9 training_submit \u7684 codeRev \u53C2\u6570\uFF09`:"")+(t.methodCandidates?`
|
|
10
|
-
\u5B9E\u9645\u65B9\u6CD5\u8981\u7B49\u63A2\u6D4B\uFF1A${t.methodCandidates.map(
|
|
10
|
+
\u5B9E\u9645\u65B9\u6CD5\u8981\u7B49\u63A2\u6D4B\uFF1A${t.methodCandidates.map(p=>`${p.probe}\u2192${p.method}`).join("\u3001")}`:"")+(t.entry?.notes?.length?`
|
|
11
11
|
${t.entry.notes.join(`
|
|
12
12
|
`)}`:"")+(t.warnings.length>0?`
|
|
13
13
|
\u6CE8\u610F\uFF1A
|
|
14
14
|
- ${t.warnings.join(`
|
|
15
|
-
- `)}`:"")}]}
|
|
16
|
-
\u8FDC\u7AEF\u53EF\u80FD\u5DF2\u7ECF\u843D\u4E86\u4E00\u90E8\u5206\u6587\u4EF6\uFF0C\u8BF7\u4FEE\u597D\u540E\u91CD\u8DD1\u672C\u5DE5\u5177\uFF08rsync / sftp \u90FD\u662F\u5E42\u7B49\u7684\uFF09\uFF0C\u4E0D\u8981\u5F53\u6210\u529F\u5904\u7406\u3002`)}finally{await
|
|
15
|
+
- `)}`:"")}]},timeoutMs:35*6e4,async run(e,t){let i=Date.now(),o=M(t),a=`${e.project}/${e.name}`,p=e.dataset===!0?"data":"code",d=[],c=await ke(o,e.source),u=D(e.dest??Se(p));if(u==="/")throw new Error("\u62D2\u7EDD\u628A\u5DE5\u7A0B\u76F4\u63A5\u94FA\u5230\u5BB9\u5668\u6839\u76EE\u5F55 /\u3002\u8BF7\u7ED9\u4E00\u4E2A\u5177\u4F53\u7684\u76EE\u5F55\uFF0C\u6BD4\u5982 /workspace/code\u3002");let g=pe(p,e.extraExcludes??[]).map(m=>m.pattern),h=await ce(c,g,{signal:t.signal});if(h.files.length===0)throw new Error(`${c} \u91CC\u6CA1\u6709\u4EFB\u4F55\u53EF\u4E0A\u4F20\u7684\u6587\u4EF6\uFF08\u5168\u90E8\u88AB\u6392\u9664\uFF0C\u6216\u76EE\u5F55\u662F\u7A7A\u7684\uFF09\u3002\u5F53\u524D\u6392\u9664\u6E05\u5355\uFF1A${g.join("\u3001")}`);h.skippedCount>0&&d.push(`\u8DF3\u8FC7\u4E86 ${h.skippedCount} \u4E2A\u7B26\u53F7\u94FE\u63A5 / \u975E\u666E\u901A\u6587\u4EF6\uFF08\u4E0D\u8DDF\u968F\u7B26\u53F7\u94FE\u63A5\uFF0C\u907F\u514D\u628A\u5BB9\u5668\u5916\u7684\u4E1C\u897F\u4E00\u8D77\u4F20\u8FDB\u53BB\uFF09\uFF1A${h.skipped.join("\u3001")}${h.skippedCount>h.skipped.length?" \u2026":""}`);let y={workloadRef:a,lane:p,source:c,dest:u,files:h.files.length,bytes:h.bytes,excludePatterns:g,excluded:h.excluded,skipped:h.skipped,skippedCount:h.skippedCount},j={sshHost:r?.sshHost,sshPort:r?.sshPort,ref:a},f=async()=>{let m=C(n,"runnpu","RunNPU"),x=await m.connection(o);if(!x.server||!x.token)throw new S("not_logged_in","RunNPU \u5C1A\u672A\u767B\u5F55\uFF1A\u8BF7\u5728\u754C\u9762\u53F3\u680F\u6216\u8BBE\u7F6E\u91CC\u586B\u5199\u767B\u5F55\u5730\u5740\u4E0E\u8D26\u53F7\u5BC6\u7801");let R=await m.api.workload(x,e.project,e.name,t.signal),Ee=!(typeof R?.ssh?.host=="string"&&R.ssh.host.trim()!=="")&&!j.sshHost?await m.api.nodes(x,t.signal):void 0;return ue(R,Ee,j)},$=m=>({username:m.username,host:m.host,port:m.port,fallback:m.derivation.fallback,from:{username:m.derivation.username.from,host:m.derivation.host.from,port:m.derivation.port.from},notes:m.derivation.notes});if(e.dryRun===!0){let m,x;try{m=$(await f())}catch(R){x={code:R?.code??"entry_failed",message:String(R?.message??R)}}return N({...y,dryRun:!0,methodCandidates:he(p),entry:m,entryError:x,durationMs:Date.now()-i,warnings:d})}let b=await f();b.derivation.fallback&&d.push(`SSH \u5165\u53E3\u6709\u515C\u5E95\u6210\u5206\uFF1A${b.derivation.notes.join(" ")}`);let k={hasRsync:!1,hasTar:!1};try{let m=await F("ssh",W(b,fe),{timeoutMs:15e3,signal:t.signal});k=me(m.stdout)}catch(m){d.push(`\u8FDC\u7AEF\u80FD\u529B\u63A2\u6D4B\u6CA1\u8DD1\u6210\uFF08${String(m?.message??m).slice(0,200)}\uFF09\uFF0C\u6309\u300C\u65E2\u65E0 rsync \u4E5F\u65E0 tar\u300D\u5904\u7406\uFF0C\u8D70 SFTP\u3002`)}let v=U({...k,lane:p}),q=p==="code"?await de(c,h.files,t.signal):void 0,E=Number.isInteger(e.timeoutMs)&&e.timeoutMs>0?e.timeoutMs:30*6e4,w=await He(P(Be(),"dsh-upload-"));try{if(v.method==="rsync")await F("rsync",ye({entry:b,source:c,dest:u,patterns:g,lane:p}),{timeoutMs:E,signal:t.signal});else if(v.method==="tar"){let m=P(w,"members.list");await L(m,`${h.files.map(R=>R.path).join("\0")}\0`,"utf8");let x=we({entry:b,source:c,dest:u,listFile:m});await Ge(x.local,x.remote,{timeoutMs:E,signal:t.signal})}else if(p==="code"){let m=P(w,u.split("/").pop()??"code");await le(c,h.files,m,t.signal);let x=P(w,"batch.sftp");await L(x,be({dest:u,stagedPath:m,lane:p}),"utf8"),await F("sftp",V({entry:b,batchFile:x}),{timeoutMs:E,signal:t.signal})}else{let m=P(w,"batch.sftp");await L(m,$e({dest:u,files:h.files,lane:p,localRoot:c}),"utf8"),await F("sftp",V({entry:b,batchFile:m}),{timeoutMs:E,signal:t.signal})}}catch(m){throw new Error(`\u4E0A\u4F20\u5931\u8D25\uFF08\u65B9\u6CD5 ${v.method}\uFF0C\u76EE\u6807 ${a}:${u}\uFF09\uFF1A${String(m?.message??m).slice(0,2e3)}
|
|
16
|
+
\u8FDC\u7AEF\u53EF\u80FD\u5DF2\u7ECF\u843D\u4E86\u4E00\u90E8\u5206\u6587\u4EF6\uFF0C\u8BF7\u4FEE\u597D\u540E\u91CD\u8DD1\u672C\u5DE5\u5177\uFF08rsync / sftp \u90FD\u662F\u5E42\u7B49\u7684\uFF09\uFF0C\u4E0D\u8981\u5F53\u6210\u529F\u5904\u7406\u3002`)}finally{await ze(w,{recursive:!0,force:!0})}return N({...y,dryRun:!1,method:v.method,methodReason:v.reason,entry:$(b),codeRev:q,durationMs:Date.now()-i,warnings:d})}})),s.push(O({name:"training_submit",description:"Submit a training workload to RunNPU with SwanLab instrumentation wired up, and record the run in the project ledger. Use this INSTEAD of composing `runnpu workload create` yourself whenever the workload is a training run: it injects the SwanLab credentials as container environment variables without ever putting them in the conversation, checks whether the cluster can actually reach the SwanLab API (degrading to offline logging when it cannot, so you never promise a curve that will not arrive), allocates the run sequence number, and writes .train/runs/NNNN.json with the hyperparameters, template identity and code revision. Restate the parameters to the user and get confirmation before calling it \u2014 this creates real cluster workloads that consume quota.",parameters:{type:"object",properties:{project:{type:"string",description:"RunNPU project slug that owns the workload."},type:{type:"string",enum:["training-job","training-env"],description:"training-job runs to completion; training-env is the long-lived interactive box. Default training-job."},name:{type:"string",description:"Workload name. Defaults to train-NNNN matching the ledger sequence."},image:{type:"string",description:"Container image. Give this or fromTemplate."},fromTemplate:{type:"string",description:"Workload template name or id to expand (preferred \u2014 templates carry presets you would otherwise rediscover the hard way)."},xpu:{type:"integer",description:"Accelerator cards. Default 1; 0 means CPU only."},nodes:{type:"integer",description:"training-job only: 1 for single node, >= 2 for multi-node. No values in between."},pool:{type:"string",description:"Compute pool; omitted takes the project default."},command:{type:"array",items:{type:"string"},description:'Command, in order \u2014 e.g. ["bash", "-c"].'},args:{type:"array",items:{type:"string"},description:'Arguments \u2014 e.g. ["python train.py"].'},mounts:{type:"array",items:{type:"string"},description:'Raw --mount specs, e.g. "datavolume:/data,name=ds,ro". Passed through verbatim.'},env:{type:"array",items:{type:"string"},description:"Extra NON-SECRET environment variables as KEY=VALUE. Never put credentials here \u2014 they would enter the session log."},security:{type:"string",enum:["restricted","baseline"],description:"Pod security level. SSH is unrelated to it and to root: the helper is an injected binary that keeps the container's existing identity when PID 1 is not root, so SSH works under BOTH levels \u2014 never downgrade a workload to baseline just to get SSH. Under restricted a root IMAGE still needs runAsUid, which is a requirement of the security level itself, not of SSH."},runAsUid:{type:"integer",description:"Container UID; required under restricted for root images."},workdir:{type:"string",description:"Working directory inside the container."},workspacePool:{type:"string",description:"Managed work-disk storage pool."},workspaceGib:{type:"integer",description:"Managed work-disk size in GiB. Size it for code + dataset + checkpoints together."},backoffLimit:{type:"integer",description:"training-job retry budget."},activeDeadlineSeconds:{type:"integer",description:"training-job wall-clock deadline."},ttlAfterFinished:{type:"integer",description:"Seconds before a finished training-job is auto-deleted."},hyperparams:{type:"object",additionalProperties:!0,properties:{},description:"This run's hyperparameters. Recorded in the ledger and used for the version card diff \u2014 pass what you actually set."},template:{type:"object",additionalProperties:!1,properties:{id:{type:"string",description:"Template identity."},version:{type:"string",description:"Template version."}},required:["id"],description:"Solution-template identity and version behind this run."},adaptation:{type:"object",additionalProperties:!0,properties:{},description:"Parameters used to adapt the template to this project."},codeRev:{type:"string",description:'Git sha or content hash of the code that was uploaded, so "which code ran" stays answerable.'},note:{type:"string",description:"Why this run is configured this way \u2014 one line, kept in the ledger."},wait:{type:"boolean",description:"Block until running/failed/rejected (up to 10 minutes). Default false."}},required:["project"]},result:{type:"object",additionalProperties:!1,properties:{seq:{type:"integer"},workloadRef:{type:"string"},ledgerPath:{type:"string"},swanlab:{type:"object",additionalProperties:!1,properties:{upload:{type:"string",enum:["online","offline","unconfigured"]},runName:{type:"string"},injectedKeys:{type:"array",items:{type:"string"}},reason:{type:"string"}},required:["upload","injectedKeys"]},workload:{type:"object",additionalProperties:!0,properties:{}},warnings:{type:"array",items:{type:"string"}}},required:["seq","workloadRef","ledgerPath","swanlab","warnings"]},content:(e,t)=>[{type:"text",text:`\u7B2C ${t.seq} \u6B21\u8BAD\u7EC3\u5DF2\u63D0\u4EA4\uFF1A${t.workloadRef}
|
|
17
17
|
SwanLab\uFF1A${t.swanlab.upload==="online"?`\u5728\u7EBF\u4E0A\u62A5\uFF08\u5B9E\u9A8C\u540D ${t.swanlab.runName??"\u672A\u547D\u540D"}\uFF09`:t.swanlab.upload==="offline"?`\u79BB\u7EBF\u8BB0\u5F55\uFF0C\u7ED3\u675F\u540E\u9700 swanlab sync \u8865\u4F20\uFF08${t.swanlab.reason??"\u96C6\u7FA4\u65E0\u6CD5\u8FDE\u901A API"}\uFF09`:"\u672A\u914D\u7F6E\uFF0C\u672C\u6B21\u6CA1\u6709\u66F2\u7EBF"}
|
|
18
18
|
\u6CE8\u5165\u7684\u73AF\u5883\u53D8\u91CF\uFF1A${t.swanlab.injectedKeys.join(", ")||"\u65E0"}\uFF08\u503C\u4E0D\u51FA\u73B0\u5728\u5BF9\u8BDD\u4E2D\uFF09
|
|
19
19
|
\u8D26\u672C\uFF1A${t.ledgerPath}`+(t.warnings.length>0?`
|
|
20
20
|
\u6CE8\u610F\uFF1A
|
|
21
21
|
- ${t.warnings.join(`
|
|
22
|
-
- `)}`:"")}]
|
|
23
|
-
`,"utf8"),
|
|
24
|
-
`,"utf8"),
|
|
25
|
-
\u8D85\u8FC7 5 \u4E2A\u4E0A\u9650\uFF0C\u5DF2\u4E22\u5F03\uFF1A${t.dropped.join(", ")}`:"")+"\n\u8FD9\u4E9B\u952E\u662F\u4ECE\u4EE3\u7801\u91CC\u63A8\u65AD\u51FA\u6765\u7684\uFF1A\u5B9E\u9A8C\u4E00\u65E6\u6709\u6570\u636E\uFF0C\u7528 `swanlab api run series <user/project/run_id>` \u590D\u6838\u5B83\u4EEC\u662F\u5426\u771F\u7684\u5B58\u5728\uFF1B\u9762\u677F\u5BF9\u4E0D\u4E0A\u65F6\u4E5F\u4F1A\u5728\u6307\u6807\u5E26\u4E0A\u70B9\u540D\uFF0C\u5C4A\u65F6\u91CD\u65B0\u5206\u6790\u5E76\u518D\u8C03\u7528\u4E00\u6B21\u672C\u5DE5\u5177\u3002"}]
|
|
26
|
-
`,"utf8"),
|
|
22
|
+
- `)}`:"")}],timeoutMs:16*6e4,async run(e,t){let i=M(t),o=C(n,"runnpu","RunNPU"),a=C(n,"swanlab","SwanLab"),p=[],d=await o.connection(i);if(!d.server||!d.token)throw new Error("RunNPU \u5C1A\u672A\u767B\u5F55\uFF1A\u8BF7\u5728\u754C\u9762\u53F3\u680F\u6216\u8BBE\u7F6E\u91CC\u586B\u5199\u767B\u5F55\u5730\u5740\u4E0E\u8D26\u53F7\u5BC6\u7801");await H(i);let c=await We(i),u=e.name??`train-${String(c).padStart(4,"0")}`,l=await a.connection(i),g="unconfigured",h;if(!l.apiKey||!l.apiHost)h="SwanLab \u672A\u914D\u7F6E",p.push("SwanLab \u672A\u914D\u7F6E\uFF0C\u8FD9\u6B21\u8BAD\u7EC3\u4E0D\u4F1A\u6709\u66F2\u7EBF\u3002\u5148\u5728\u8BAD\u7EC3\u6A21\u5F0F\u7684\u914D\u7F6E\u533A\u586B\u597D API \u5730\u5740\u4E0E Key\u3002");else{let w=await a.api.check(l,{signal:t.signal});g=w.reachable?"online":"offline",w.reachable||(h=w.reason),w.credentials==="unverifiable"?p.push(`\u51ED\u636E\u65E0\u6CD5\u5728\u672C\u673A\u6821\u9A8C\uFF08${w.reason}\uFF09\u3002\u5730\u5740\u662F\u901A\u7684\uFF0C\u672C\u6B21\u4ECD\u6309\u5728\u7EBF\u4E0A\u62A5\u63D0\u4EA4\uFF1B\u5982\u679C\u8BAD\u7EC3\u8D77\u6765\u540E\u6CA1\u6709\u66F2\u7EBF\uFF0C\u5148\u6000\u7591\u8FD9\u4E2A\u3002`):w.credentials==="invalid"&&p.push(`SwanLab \u51ED\u636E\u6821\u9A8C\u5931\u8D25\uFF1A${w.reason}\u3002\u53EF\u80FD\u4E0D\u4F1A\u6709\u66F2\u7EBF\u3002`)}let y=[e.template?.id?`template=${e.template.id}${e.template.version?`@${e.template.version}`:""}`:void 0,e.codeRev?`code=${e.codeRev}`:void 0,e.hyperparams?`hp=${JSON.stringify(e.hyperparams)}`:void 0].filter(Boolean).join(" ").slice(0,900),j=g==="unconfigured"?{}:await a.workloadEnv(i,{name:u,description:y||void 0}),f=Array.isArray(e.command)?[...e.command]:[],$=Array.isArray(e.args)?[...e.args]:[];if(g==="offline"){let w=Ve(f,$);$=w.args,w.warning?p.push(w.warning):p.push("\u96C6\u7FA4\u65E0\u6CD5\u8FDE\u901A SwanLab API\uFF0C\u5DF2\u6539\u4E3A\u79BB\u7EBF\u8BB0\u5F55\uFF1A\u8BAD\u7EC3\u7ED3\u675F\u540E\u7528 `swanlab sync` \u8865\u4F20\u3002")}let b=["workload","create","--project",e.project,"--name",u,"--type",e.type??"training-job"],k=(w,m)=>{m!=null&&m!==""&&b.push(w,String(m))};k("--from-template",e.fromTemplate),k("--image",e.image),k("--pool",e.pool),k("--security",e.security),k("--workdir",e.workdir),k("--workspace-pool",e.workspacePool),e.xpu!==void 0&&k("--xpu",e.xpu),e.nodes!==void 0&&k("--nodes",e.nodes),e.runAsUid!==void 0&&k("--run-as-uid",e.runAsUid),e.workspaceGib!==void 0&&k("--workspace-gib",e.workspaceGib),e.backoffLimit!==void 0&&k("--backoff-limit",e.backoffLimit),e.activeDeadlineSeconds!==void 0&&k("--active-deadline-seconds",e.activeDeadlineSeconds),e.ttlAfterFinished!==void 0&&k("--ttl-after-finished",e.ttlAfterFinished);for(let w of f)b.push("--command",w);for(let w of $)b.push("--arg",w);for(let w of e.mounts??[])b.push("--mount",w);for(let w of e.env??[])b.push("--env",w);for(let[w,m]of Object.entries(j))b.push("--env",`${w}=${m}`);e.wait&&b.push("--wait"),b.push("-o","json");let v;try{let w=await Je("runnpu",b,{env:{RUNNPU_SERVER:d.server,RUNNPU_TOKEN:d.token},signal:t.signal,timeoutMs:e.wait?9e5:6e4});v=w.trim()?JSON.parse(w):void 0}catch(w){throw new Error(`\u63D0\u4EA4\u5931\u8D25\uFF1A${w.message}`)}let q={seq:c,startedAt:new Date().toISOString(),endedAt:null,template:e.template,adaptation:e.adaptation,hyperparams:e.hyperparams,codeRev:e.codeRev,workloadRef:`${e.project}/${u}`,swanlabRun:void 0,swanlabRunName:g==="unconfigured"?void 0:u,swanlabUpload:g,status:v?.status??"submitting",decision:void 0,note:e.note},E=P(_(i),"runs",Pe(c));return await L(E,`${JSON.stringify(q,void 0,2)}
|
|
23
|
+
`,"utf8"),N({seq:c,workloadRef:q.workloadRef,ledgerPath:E,swanlab:{upload:g,runName:q.swanlabRunName,injectedKeys:Object.keys(j),reason:h},workload:v,warnings:p})}})),s.push(O({name:"training_run_update",description:"Record what you and the user CONCLUDED about one run, into .train/runs/NNNN.json: the decision (promote, discard, iterate), the note that says why, and any correction to the recorded hyperparameters. That judgment is the part of the ledger nothing else can produce, and it is what makes the ledger worth reading three weeks later \u2014 so call this when a run has taught you something, not on a schedule. The mechanical half of the entry is NOT yours to maintain: a background sync copies the workload status, the end instant and the SwanLab run path (reverse-looked-up from the SWANLAB_NAME the submit injected) into the entry on its own, so after submitting you never have to come back to fill those in. The parameters for them exist only as a manual override for when that sync cannot work.",parameters:{type:"object",properties:{seq:{type:"integer",description:"Run sequence number, as returned by training_submit."},decision:{type:"string",enum:["promote","discard","next"],description:"The conclusion about this run. This, the note and the hyperparameter corrections are what this tool is for."},note:{type:"string",description:"What was learned from this run, and why the next one changes what it changes."},hyperparams:{type:"object",additionalProperties:!0,properties:{},description:"Corrections merged into the recorded hyperparameters \u2014 what actually ran, when it differed from what was submitted."},swanlabRun:{type:"string",description:'MANUAL OVERRIDE, only when the automatic sync cannot fill this in \u2014 no `swanlab.project` registered in .train/config.json, or the experiment is not on the first page of `run list`. Full SwanLab experiment path, "username/project/run_id". Normally the background sync writes it; do not write it routinely.'},status:{type:"string",enum:["submitting","queued","running","completed","failed","stopped","rejected"],description:"MANUAL OVERRIDE, only when the automatic sync is wrong or not running (it copies the workload status from RunNPU every refresh round). Correcting a run the platform lost track of is a legitimate use."},endedAt:{type:"string",description:"MANUAL OVERRIDE, only when the automatic sync did not record one. ISO-8601 end instant; the sync records the instant it NOTICED the run was over."}},required:["seq"]},result:{type:"object",additionalProperties:!1,properties:{seq:{type:"integer"},updated:{type:"array",items:{type:"string"}},run:{type:"object",additionalProperties:!0,properties:{}}},required:["seq","updated","run"]},content:(e,t)=>[{type:"text",text:`\u7B2C ${t.seq} \u6B21\u8BAD\u7EC3\u7684\u8D26\u672C\u5DF2\u66F4\u65B0\uFF1A${t.updated.join(", ")||"\u65E0\u53D8\u5316"}`}],async run(e,t){let i=M(t),o=P(_(i),"runs",Pe(e.seq)),a;try{a=JSON.parse(await X(o,"utf8"))}catch{throw new Error(`\u8D26\u672C\u91CC\u6CA1\u6709\u7B2C ${e.seq} \u6B21\u8BAD\u7EC3\uFF08${o}\uFF09`)}let p=[],d=(c,u)=>{u!==void 0&&(a[c]=u,p.push(c))};return d("swanlabRun",e.swanlabRun),d("status",e.status),d("decision",e.decision),d("endedAt",e.endedAt),d("note",e.note),e.hyperparams&&(a.hyperparams={...a.hyperparams??{},...e.hyperparams},p.push("hyperparams")),await L(o,`${JSON.stringify(a,void 0,2)}
|
|
24
|
+
`,"utf8"),N({seq:e.seq,updated:p,run:a})}})),s.push(O({name:"training_metrics_set",description:"Record the key metrics to watch for this project, into .train/metrics.json. Derive them by READING the training code \u2014 the swanlab.log() call sites, the printed log fields, the framework defaults \u2014 not by guessing and not by querying an experiment that may not exist yet. At most five: they render above the composer and the dashboard pulls only these keys. Give each one a direction (is lower or higher better, which cross-run comparison needs) and the source line you inferred it from, so the choice can be checked.",parameters:{type:"object",properties:{keys:{type:"array",description:"The metric keys to watch, most important first. At most five.",items:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:'Exact SwanLab metric key, e.g. "train/loss".'},direction:{type:"string",enum:["lower","higher"],description:"Whether lower or higher is better."},source:{type:"string",description:`Where this came from, e.g. "train.py:142 swanlab.log({'train/loss': ...})".`}},required:["name","direction","source"]}},inferredFrom:{type:"string",description:"Code revision the inference was made against."}},required:["keys"]},result:{type:"object",additionalProperties:!1,properties:{path:{type:"string"},keys:{type:"array",items:{type:"string"}},dropped:{type:"array",items:{type:"string"}}},required:["path","keys","dropped"]},content:(e,t)=>[{type:"text",text:`\u5173\u952E\u6307\u6807\u5DF2\u8BB0\u5F55\uFF08${t.keys.length} \u4E2A\uFF09\uFF1A${t.keys.join(", ")}`+(t.dropped.length>0?`
|
|
25
|
+
\u8D85\u8FC7 5 \u4E2A\u4E0A\u9650\uFF0C\u5DF2\u4E22\u5F03\uFF1A${t.dropped.join(", ")}`:"")+"\n\u8FD9\u4E9B\u952E\u662F\u4ECE\u4EE3\u7801\u91CC\u63A8\u65AD\u51FA\u6765\u7684\uFF1A\u5B9E\u9A8C\u4E00\u65E6\u6709\u6570\u636E\uFF0C\u7528 `swanlab api run series <user/project/run_id>` \u590D\u6838\u5B83\u4EEC\u662F\u5426\u771F\u7684\u5B58\u5728\uFF1B\u9762\u677F\u5BF9\u4E0D\u4E0A\u65F6\u4E5F\u4F1A\u5728\u6307\u6807\u5E26\u4E0A\u70B9\u540D\uFF0C\u5C4A\u65F6\u91CD\u65B0\u5206\u6790\u5E76\u518D\u8C03\u7528\u4E00\u6B21\u672C\u5DE5\u5177\u3002"}],async run(e,t){let i=M(t);await H(i);let o=e.keys.slice(0,xe),a=e.keys.slice(xe).map(d=>d.name),p=P(_(i),"metrics.json");return await L(p,`${JSON.stringify({keys:o,inferredAt:new Date().toISOString(),inferredFrom:e.inferredFrom},void 0,2)}
|
|
26
|
+
`,"utf8"),N({path:p,keys:o.map(d=>d.name),dropped:a})}})),s.push(O({name:"training_ledger",description:"Read this project's training ledger: every recorded run newest first, plus the key-metric list. Call it before proposing the next experiment so the proposal is grounded in what has already been tried and what was concluded about it, rather than in the current conversation alone.",parameters:{type:"object",properties:{limit:{type:"integer",description:"How many runs to return, newest first. Default 10."}}},result:{type:"object",additionalProperties:!1,properties:{workspace:{type:"string"},total:{type:"integer"},runs:{type:"array",items:{type:"object",additionalProperties:!0,properties:{}}},metricKeys:{type:"array",items:{type:"object",additionalProperties:!0,properties:{}}}},required:["workspace","total","runs","metricKeys"]},content:(e,t)=>[{type:"text",text:t.total===0?"\u8FD9\u4E2A\u9879\u76EE\u8FD8\u6CA1\u6709\u8BB0\u5F55\u8FC7\u8BAD\u7EC3\u8FD0\u884C\u3002":`\u5171 ${t.total} \u6B21\u8BAD\u7EC3\uFF0C\u6700\u8FD1\u4E00\u6B21\u662F\u7B2C ${t.runs[0]?.seq} \u6B21\uFF08${t.runs[0]?.status??"\u72B6\u6001\u672A\u77E5"}\uFF09\u3002\u5173\u952E\u6307\u6807\uFF1A${t.metricKeys.map(i=>i.name).join(", ")||"\u5C1A\u672A\u5206\u6790"}`}],async run(e,t){let i=M(t),o=await z(i),a=[];try{a=JSON.parse(await X(P(_(i),"metrics.json"),"utf8"))?.keys??[]}catch{a=[]}return N({workspace:i,total:o.length,runs:o.slice(0,e.limit??10),metricKeys:a})}})),s.push(O({name:"training_compare",description:"Deterministically compare runs of THIS project across the key metrics: per-run final / best value, tail-window trend (plateau / improving / worsening), and the winner per metric by its recorded direction. Use this INSTEAD of pulling each run's metrics yourself and comparing numbers in your head \u2014 these numbers decide who gets promoted, so they come from arithmetic, not from the model. Defaults to every ledger run that has a SwanLab run linked; interpretation (why the winner won, which knob to turn next) stays with you and the training-research skill's rule table. The result is a summary \u2014 do not restate the whole table back into the conversation.",parameters:{type:"object",properties:{seqs:{type:"array",items:{type:"integer"},description:"Ledger sequence numbers to compare. Omitted: every run with a linked SwanLab run (max 8, oldest first)."},extraRuns:{type:"array",items:{type:"string"},description:"Off-ledger SwanLab run paths (user/project/run_id) to include, e.g. a colleague's baseline."},keys:{type:"array",items:{type:"string"},description:"Metric keys to compare. Omitted: the keys in .train/metrics.json (their directions apply either way)."},tailK:{type:"integer",description:"Tail window (points) for the trend verdict. Default 5."}}},result:{type:"object",additionalProperties:!1,properties:{workspace:{type:"string"},keys:{type:"array",items:{type:"object",additionalProperties:!1,properties:{name:{type:"string"},direction:{type:"string",enum:["lower","higher"]}},required:["name","direction"]}},rows:{type:"array",items:{type:"object",additionalProperties:!0,properties:{}}},winners:{type:"array",items:{type:"object",additionalProperties:!0,properties:{}}},warnings:{type:"array",items:{type:"string"}}},required:["workspace","keys","rows","winners","warnings"]},content:(e,t)=>{let i=t.winners.filter(o=>o.winner).map(o=>{let a=typeof o.marginPct=="number"?`\uFF0C\u9886\u5148 ${o.runnerUp} ${o.marginPct.toFixed(1)}%`:"";return`${o.key}\uFF08${o.direction==="higher"?"\u8D8A\u9AD8\u8D8A\u597D":"\u8D8A\u4F4E\u8D8A\u597D"}\uFF09\uFF1A${o.winner} = ${o.final}${a}`});return[{type:"text",text:(i.length?`\u8D62\u5BB6\u5224\u5B9A\uFF1A
|
|
27
27
|
${i.join(`
|
|
28
28
|
`)}`:"\u6CA1\u6709\u4EFB\u4F55 run \u5728\u8FD9\u4E9B\u6307\u6807\u4E0A\u6709\u6570\u636E\u3002")+(t.warnings.length?`
|
|
29
29
|
\u6CE8\u610F\uFF1A${t.warnings.join("\uFF1B")}`:"")+`
|
|
30
|
-
\u5B8C\u6574\u9010 run \u7EDF\u8BA1\u5728\u7ED3\u6784\u5316\u7ED3\u679C\u91CC\uFF1B\u89E3\u91CA\u4E0E\u4E0B\u4E00\u6B65\u6309 training-research \u7684\u8BCA\u65AD\u89C4\u5219\u8868\u6765\uFF0C\u4E0D\u8981\u628A\u8868\u539F\u6837\u6284\u56DE\u5BF9\u8BDD\u3002`}]}
|
|
30
|
+
\u5B8C\u6574\u9010 run \u7EDF\u8BA1\u5728\u7ED3\u6784\u5316\u7ED3\u679C\u91CC\uFF1B\u89E3\u91CA\u4E0E\u4E0B\u4E00\u6B65\u6309 training-research \u7684\u8BCA\u65AD\u89C4\u5219\u8868\u6765\uFF0C\u4E0D\u8981\u628A\u8868\u539F\u6837\u6284\u56DE\u5BF9\u8BDD\u3002`}]},async run(e,t){let i=M(t),o=C(n,"swanlab","SwanLab"),a=await o.connection(i);if(!a.apiKey||!a.apiHost)throw new Error("SwanLab \u5C1A\u672A\u914D\u7F6E\uFF08\u7F3A apiKey/apiHost\uFF09\uFF1A\u5148\u5728\u8BBE\u7F6E\u91CC\u8FDE\u597D\uFF0C\u6216\u68C0\u67E5 .train/secrets.json");let p=[],d=await z(i),c;e.seqs?.length?c=e.seqs.map(f=>{let $=d.find(b=>b.seq===f);if(!$)throw new Error(`\u8D26\u672C\u91CC\u6CA1\u6709\u7B2C ${f} \u6B21\u8BAD\u7EC3`);return $.swanlabRun||p.push(`\u7B2C ${f} \u6B21\u6CA1\u6709\u5173\u8054 SwanLab run\uFF0C\u8DF3\u8FC7`),$}):(c=d.filter(f=>typeof f.swanlabRun=="string"&&f.swanlabRun.length>0),c.length>8&&(p.push(`\u8D26\u672C\u6709 ${c.length} \u4E2A\u53EF\u6BD4 run\uFF0C\u53EA\u53D6\u6700\u8FD1 8 \u4E2A`),c=c.slice(0,8)));let u=c.filter(f=>f.swanlabRun).reverse().map(f=>({label:`#${f.seq}`,runPath:f.swanlabRun}));for(let f of e.extraRuns??[])u.push({label:f,runPath:f});if(u.length===0)throw new Error("\u6CA1\u6709\u53EF\u6BD4\u7684 run\uFF1A\u8D26\u672C\u91CC\u8FD8\u6CA1\u6709\u5173\u8054\u4E86 SwanLab run \u7684\u8BAD\u7EC3");let l=[];try{l=JSON.parse(await X(P(_(i),"metrics.json"),"utf8"))?.keys??[]}catch{l=[]}if(e.keys?.length&&(l=e.keys.map(f=>{let $=l.find(b=>b.name===f);return $||p.push(`${f} \u4E0D\u5728 metrics.json \u91CC\uFF0Cdirection \u6309 lower \u5904\u7406`),{name:f,direction:$?.direction}})),l.length===0)throw new Error("\u6CA1\u6709\u53EF\u6BD4\u7684\u6307\u6807\uFF1A.train/metrics.json \u4E3A\u7A7A\u4E14\u672A\u663E\u5F0F\u7ED9 keys");let g=l.map(f=>f.name),h=[];for(let f of u)try{let $=await o.api.runMetrics(a,f.runPath,g,{signal:t.signal});h.push({label:f.label,series:o.api.metricSeries($)})}catch($){p.push(`${f.label}\uFF08${f.runPath}\uFF09\u62C9\u53D6\u5931\u8D25\uFF1A${$?.message??$}`),h.push({label:f.label,series:{}})}let{rows:y,winners:j}=Z(h,l,{tailK:e.tailK??5});return N({workspace:i,keys:l.map(f=>({name:f.name,direction:I(f.direction)})),rows:y,winners:j,warnings:p})}})),{tools:s,guards:[]}}var yt="runnpu-training-tools",wt=["tools"];function gt(n,r={}){let{tools:s}=Ne(Y(n,{runnpu:"runnpu",swanlab:"swanlab"}),r),e=t=>n.get(t);for(let t of s)n.tools?.register(Xe(Q(t,e)))}export{gt as apply,wt as inject,yt as name};
|