bios-sdk 0.1.0-dev.1
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/README.md +292 -0
- package/dist/client.d.ts +57 -0
- package/dist/client.js +181 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +80 -0
- package/dist/resources/datasets.d.ts +180 -0
- package/dist/resources/datasets.js +358 -0
- package/dist/resources/gpu-priorities.d.ts +14 -0
- package/dist/resources/gpu-priorities.js +54 -0
- package/dist/resources/gpu.d.ts +60 -0
- package/dist/resources/gpu.js +101 -0
- package/dist/resources/inference.d.ts +82 -0
- package/dist/resources/inference.js +529 -0
- package/dist/resources/models.d.ts +75 -0
- package/dist/resources/models.js +115 -0
- package/dist/resources/training.d.ts +146 -0
- package/dist/resources/training.js +399 -0
- package/dist/resources/wallet.d.ts +44 -0
- package/dist/resources/wallet.js +52 -0
- package/dist/types.d.ts +1466 -0
- package/dist/types.js +4 -0
- package/package.json +49 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access the BIOS model catalog -- search HuggingFace models, fetch
|
|
3
|
+
* training-relevant configuration, and check adapter compatibility.
|
|
4
|
+
*/
|
|
5
|
+
export class Models {
|
|
6
|
+
_http;
|
|
7
|
+
/** @internal */
|
|
8
|
+
constructor(_http) {
|
|
9
|
+
this._http = _http;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Search the HuggingFace model catalog for fine-tunable models.
|
|
13
|
+
*
|
|
14
|
+
* Results are filtered to exclude quantized derivatives (GGUF, GPTQ, AWQ)
|
|
15
|
+
* and adapters by default -- pass `kind: "adapter"` to find LoRA adapters.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const res = await client.models.search({ query: 'llama', type: 'llm', limit: 10 });
|
|
20
|
+
* for (const m of res.models) {
|
|
21
|
+
* console.log(`${m.id} -- ${m.totalParams}B params`);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
async search(params = {}) {
|
|
26
|
+
const q = new URLSearchParams();
|
|
27
|
+
if (params.query)
|
|
28
|
+
q.set('q', params.query);
|
|
29
|
+
if (params.type)
|
|
30
|
+
q.set('type', params.type);
|
|
31
|
+
if (params.offset !== undefined)
|
|
32
|
+
q.set('offset', String(params.offset));
|
|
33
|
+
if (params.limit !== undefined)
|
|
34
|
+
q.set('limit', String(params.limit));
|
|
35
|
+
if (params.minParams !== undefined)
|
|
36
|
+
q.set('min_params', String(params.minParams));
|
|
37
|
+
if (params.maxParams !== undefined)
|
|
38
|
+
q.set('max_params', String(params.maxParams));
|
|
39
|
+
if (params.sort)
|
|
40
|
+
q.set('sort', params.sort);
|
|
41
|
+
if (params.author)
|
|
42
|
+
q.set('author', params.author);
|
|
43
|
+
if (params.visibility)
|
|
44
|
+
q.set('visibility', params.visibility);
|
|
45
|
+
if (params.kind)
|
|
46
|
+
q.set('kind', params.kind);
|
|
47
|
+
if (params.integrationId)
|
|
48
|
+
q.set('integration_id', params.integrationId);
|
|
49
|
+
return this._http.fetchGet(`/api/public/model-search?${q}`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fetch the training configuration for a specific model from HuggingFace.
|
|
53
|
+
*
|
|
54
|
+
* Returns parameter counts, architecture type, and whether the model
|
|
55
|
+
* uses Mixture-of-Experts -- information needed to choose the right
|
|
56
|
+
* GPU and adapter configuration.
|
|
57
|
+
*
|
|
58
|
+
* @param modelId - Full HuggingFace model ID (e.g. "meta-llama/Llama-3.1-8B").
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const config = await client.models.getConfig('meta-llama/Llama-3.1-8B');
|
|
63
|
+
* console.log(`${config.totalParams}B params, MoE: ${config.isMoE}`);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
async getConfig(modelId) {
|
|
67
|
+
return this._http.fetchGet(`/api/public/model-config?id=${encodeURIComponent(modelId)}`);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check which adapters are compatible with a given architecture,
|
|
71
|
+
* training method, and (optionally) RLHF algorithm.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const compat = await client.models.getAdapterCompatibility({
|
|
76
|
+
* modelType: 'llama',
|
|
77
|
+
* trainingMethod: 'rlhf',
|
|
78
|
+
* rlhfAlgorithm: 'dpo',
|
|
79
|
+
* });
|
|
80
|
+
* const usable = compat.adapters.filter(a => a.compatible);
|
|
81
|
+
* console.log(`${usable.length} compatible adapters`);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
async getAdapterCompatibility(params = {}) {
|
|
85
|
+
const q = new URLSearchParams();
|
|
86
|
+
if (params.architecture)
|
|
87
|
+
q.set('architecture', params.architecture);
|
|
88
|
+
if (params.modelType)
|
|
89
|
+
q.set('model_type', params.modelType);
|
|
90
|
+
if (params.trainingMethod)
|
|
91
|
+
q.set('training_method', params.trainingMethod);
|
|
92
|
+
if (params.rlhfAlgorithm)
|
|
93
|
+
q.set('rlhf_algorithm', params.rlhfAlgorithm);
|
|
94
|
+
return this._http.fetchGet(`/api/public/adapter-compatibility?${q}`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* List the architectures the platform supports for a given scope. This is
|
|
98
|
+
* the authoritative registry the platform gates on:
|
|
99
|
+
* - `inference` — HF architecture classes that can be served (deployed).
|
|
100
|
+
* - `training` — architecture keys the fine-tuning wizard/gate accepts.
|
|
101
|
+
*
|
|
102
|
+
* A `count` of 0 means the registry is empty and nothing is explicitly
|
|
103
|
+
* restricted (every architecture the engine supports is allowed).
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* const { architectures } = await client.models.getSupportedArchitectures({ scope: 'inference' });
|
|
107
|
+
* const servable = architectures.filter(a => a.enabled).map(a => a.architecture);
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
async getSupportedArchitectures(params = {}) {
|
|
111
|
+
const q = new URLSearchParams();
|
|
112
|
+
q.set('scope', params.scope ?? 'inference');
|
|
113
|
+
return this._http.fetchGet(`/api/public/serving-architectures?${q}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { HttpClient } from '../client.js';
|
|
2
|
+
import type { TrainingCreateParams, TrainingListParams, TrainingJob, TrainingListResponse, TrainingMetrics, TrainingCheckpoint, TrainingLogs, TrainingStopResponse, TrainingResumeResponse, TrainingPreflightResponse, TrainingCapabilities } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create, monitor, and manage fine-tuning training jobs.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Training {
|
|
7
|
+
private readonly _http;
|
|
8
|
+
/** @internal */
|
|
9
|
+
constructor(_http: HttpClient);
|
|
10
|
+
/** Return the authoritative BIOS image, method, adapter, and hyperparameter contract. */
|
|
11
|
+
capabilities(): Promise<TrainingCapabilities>;
|
|
12
|
+
/**
|
|
13
|
+
* Create a new training job.
|
|
14
|
+
*
|
|
15
|
+
* Provisions GPU resources, downloads the model and dataset, and begins
|
|
16
|
+
* training. The job starts in "pending" status and progresses through
|
|
17
|
+
* provisioning, downloading, and running states.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const job = await client.training.create({
|
|
22
|
+
* model: 'meta-llama/Llama-3.1-8B-Instruct',
|
|
23
|
+
* datasetId: 'ds_abc123',
|
|
24
|
+
* method: 'sft',
|
|
25
|
+
* adapter: 'lora',
|
|
26
|
+
* epochs: 3,
|
|
27
|
+
* learningRate: 2e-4,
|
|
28
|
+
* loraRank: 16,
|
|
29
|
+
* loraAlpha: 32,
|
|
30
|
+
* gpuType: 'A100_80GB',
|
|
31
|
+
* gpuCount: 1,
|
|
32
|
+
* });
|
|
33
|
+
* console.log(`Job ${job.id} created -- status: ${job.status}`);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
create(params: TrainingCreateParams): Promise<TrainingJob>;
|
|
37
|
+
/** Validate and canonicalize a training request without creating or billing a job. */
|
|
38
|
+
preflight(params: TrainingCreateParams): Promise<TrainingPreflightResponse>;
|
|
39
|
+
/** Return one server-driven page with pagination metadata. */
|
|
40
|
+
listPage(params?: TrainingListParams): Promise<TrainingListResponse>;
|
|
41
|
+
/**
|
|
42
|
+
* List training jobs, optionally filtered by workspace or status.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const jobs = await client.training.list({ status: 'running' });
|
|
47
|
+
* for (const job of jobs) {
|
|
48
|
+
* console.log(`${job.id} -- ${job.model} -- ${job.status}`);
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
list(params?: TrainingListParams): Promise<TrainingJob[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Get detailed information about a training job.
|
|
55
|
+
*
|
|
56
|
+
* @param id - Training job ID.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const job = await client.training.get('job_abc123');
|
|
61
|
+
* console.log(`Status: ${job.status}, Progress: ${job.progress}%`);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
get(id: string): Promise<TrainingJob>;
|
|
65
|
+
/**
|
|
66
|
+
* Get training metrics (loss curves, learning rate, throughput) for a job.
|
|
67
|
+
*
|
|
68
|
+
* @param id - Training job ID.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const metrics = await client.training.getMetrics('job_abc123');
|
|
73
|
+
* console.log(`Current loss: ${metrics.metrics.at(-1)?.loss}`);
|
|
74
|
+
* console.log(`${metrics.metrics.length} metric points`);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
getMetrics(id: string): Promise<TrainingMetrics>;
|
|
78
|
+
/**
|
|
79
|
+
* List checkpoints saved during training.
|
|
80
|
+
*
|
|
81
|
+
* @param id - Training job ID.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const checkpoints = await client.training.getCheckpoints('job_abc123');
|
|
86
|
+
* for (const cp of checkpoints) {
|
|
87
|
+
* console.log(`${cp.name}: ${cp.size_bytes} bytes`);
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
getCheckpoints(id: string): Promise<TrainingCheckpoint[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Get training logs for a job.
|
|
94
|
+
*
|
|
95
|
+
* @param id - Training job ID.
|
|
96
|
+
* @param tail - Maximum newest entries to return (server clamps to 1-1000).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const logs = await client.training.getLogs('job_abc123');
|
|
101
|
+
* for (const entry of logs.logs) {
|
|
102
|
+
* console.log(entry.level, entry.message);
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
getLogs(id: string, tail?: number): Promise<TrainingLogs>;
|
|
107
|
+
/**
|
|
108
|
+
* Stop a running training job.
|
|
109
|
+
*
|
|
110
|
+
* By default, intermediate data (checkpoints, logs) is preserved.
|
|
111
|
+
* Pass `keepData: false` to clean up all job artifacts.
|
|
112
|
+
*
|
|
113
|
+
* @param id - Training job ID.
|
|
114
|
+
* @param keepData - Whether to keep checkpoints and logs. Defaults to true.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* await client.training.stop('job_abc123');
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
stop(id: string, keepData?: boolean): Promise<TrainingStopResponse>;
|
|
122
|
+
/**
|
|
123
|
+
* Resume a stopped training job from its last checkpoint.
|
|
124
|
+
*
|
|
125
|
+
* @param id - Training job ID.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* await client.training.resume('job_abc123');
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
resume(id: string, idempotencyKey?: string): Promise<TrainingResumeResponse>;
|
|
133
|
+
/**
|
|
134
|
+
* Delete a specific checkpoint from a training job.
|
|
135
|
+
*
|
|
136
|
+
* @param jobId - Training job ID.
|
|
137
|
+
* @param checkpointId - Checkpoint ID to delete.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* await client.training.deleteCheckpoint('job_abc123', 'cp_xyz789');
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
getEvals(id: string): Promise<Record<string, unknown>>;
|
|
145
|
+
deleteCheckpoint(jobId: string, checkpointId: string): Promise<void>;
|
|
146
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { normalizeGPUPlacement } from './gpu-priorities.js';
|
|
2
|
+
const TRAINING_IDEMPOTENCY_KEY = /^[A-Za-z0-9._:-]{8,128}$/;
|
|
3
|
+
function trainingIdempotencyKey(explicit) {
|
|
4
|
+
const key = explicit?.trim() || globalThis.crypto.randomUUID();
|
|
5
|
+
if (!TRAINING_IDEMPOTENCY_KEY.test(key)) {
|
|
6
|
+
throw new Error("BiOS: idempotencyKey must be 8-128 characters using letters, numbers, '.', '_', ':', or '-'");
|
|
7
|
+
}
|
|
8
|
+
return key;
|
|
9
|
+
}
|
|
10
|
+
function buildTrainingRequest(params) {
|
|
11
|
+
let trainingMethod = params.method;
|
|
12
|
+
if (trainingMethod === 'vlm')
|
|
13
|
+
trainingMethod = 'sft';
|
|
14
|
+
const datasetIds = (params.datasetIds || []).filter((id) => id.trim() !== '');
|
|
15
|
+
if (datasetIds.length === 0 && params.datasetId?.trim())
|
|
16
|
+
datasetIds.push(params.datasetId);
|
|
17
|
+
if (datasetIds.length === 0) {
|
|
18
|
+
throw new Error('BiOS: datasetId or datasetIds must contain at least one dataset');
|
|
19
|
+
}
|
|
20
|
+
const body = {
|
|
21
|
+
model_id: params.model,
|
|
22
|
+
dataset_ids: datasetIds,
|
|
23
|
+
training_method: trainingMethod,
|
|
24
|
+
train_type: params.adapter,
|
|
25
|
+
};
|
|
26
|
+
if (params.modelRevision !== undefined)
|
|
27
|
+
body.model_revision = params.modelRevision;
|
|
28
|
+
if (params.rlhfAlgorithm !== undefined)
|
|
29
|
+
body.rlhf_type = params.rlhfAlgorithm;
|
|
30
|
+
const queueEnabled = params.queueIfUnavailable ?? false;
|
|
31
|
+
const placement = normalizeGPUPlacement(params.gpuPriorities, queueEnabled, params.gpuType, params.gpuCount, 'queueIfUnavailable');
|
|
32
|
+
body.queue_if_unavailable = queueEnabled;
|
|
33
|
+
if (params.queueDeadline !== undefined) {
|
|
34
|
+
const deadline = params.queueDeadline instanceof Date
|
|
35
|
+
? params.queueDeadline
|
|
36
|
+
: new Date(params.queueDeadline);
|
|
37
|
+
if (Number.isNaN(deadline.getTime())) {
|
|
38
|
+
throw new Error('BiOS: queueDeadline must be a valid RFC 3339 timestamp');
|
|
39
|
+
}
|
|
40
|
+
body.queue_deadline = deadline.toISOString();
|
|
41
|
+
}
|
|
42
|
+
if (params.maxPriceHourCents !== undefined) {
|
|
43
|
+
if (!Number.isInteger(params.maxPriceHourCents) || params.maxPriceHourCents < 0) {
|
|
44
|
+
throw new Error('BiOS: maxPriceHourCents must be a non-negative integer');
|
|
45
|
+
}
|
|
46
|
+
body.max_price_hour_cents = params.maxPriceHourCents;
|
|
47
|
+
}
|
|
48
|
+
if (placement.gpuType !== undefined)
|
|
49
|
+
body.gpu_type = placement.gpuType;
|
|
50
|
+
if (placement.gpuCount !== undefined)
|
|
51
|
+
body.gpu_count = placement.gpuCount;
|
|
52
|
+
if (placement.priorities !== undefined)
|
|
53
|
+
body.gpu_priorities = placement.priorities;
|
|
54
|
+
if (params.name !== undefined)
|
|
55
|
+
body.name = params.name;
|
|
56
|
+
if (params.workspaceId !== undefined)
|
|
57
|
+
body.workspace_id = params.workspaceId;
|
|
58
|
+
if (params.storageGb !== undefined)
|
|
59
|
+
body.storage_gb = params.storageGb;
|
|
60
|
+
if (params.numCheckpoints !== undefined)
|
|
61
|
+
body.num_checkpoints = params.numCheckpoints;
|
|
62
|
+
if (params.modelParamsB !== undefined)
|
|
63
|
+
body.model_params_b = params.modelParamsB;
|
|
64
|
+
if (params.modelActiveParamsB !== undefined)
|
|
65
|
+
body.model_active_params_b = params.modelActiveParamsB;
|
|
66
|
+
if (params.integrationId !== undefined)
|
|
67
|
+
body.integration_id = params.integrationId;
|
|
68
|
+
if (params.networkVolumeId !== undefined)
|
|
69
|
+
body.network_volume_id = params.networkVolumeId;
|
|
70
|
+
if (params.cacheDataset !== undefined)
|
|
71
|
+
body.cache_dataset = params.cacheDataset;
|
|
72
|
+
if (params.datasetSampleLimits !== undefined)
|
|
73
|
+
body.dataset_sample_limits = params.datasetSampleLimits;
|
|
74
|
+
if (params.datasetMixing !== undefined)
|
|
75
|
+
body.dataset_mixing = params.datasetMixing;
|
|
76
|
+
if (params.mixing !== undefined)
|
|
77
|
+
body.mixing = params.mixing;
|
|
78
|
+
const config = {};
|
|
79
|
+
if (params.epochs !== undefined)
|
|
80
|
+
config.num_train_epochs = params.epochs;
|
|
81
|
+
if (params.batchSize !== undefined)
|
|
82
|
+
config.per_device_train_batch_size = params.batchSize;
|
|
83
|
+
if (params.gradientAccumulation !== undefined)
|
|
84
|
+
config.gradient_accumulation_steps = params.gradientAccumulation;
|
|
85
|
+
if (params.learningRate !== undefined)
|
|
86
|
+
config.learning_rate = params.learningRate;
|
|
87
|
+
if (params.lrScheduler !== undefined)
|
|
88
|
+
config.lr_scheduler_type = params.lrScheduler;
|
|
89
|
+
if (params.warmupRatio !== undefined)
|
|
90
|
+
config.warmup_ratio = params.warmupRatio;
|
|
91
|
+
if (params.warmupSteps !== undefined)
|
|
92
|
+
config.warmup_steps = params.warmupSteps;
|
|
93
|
+
if (params.weightDecay !== undefined)
|
|
94
|
+
config.weight_decay = params.weightDecay;
|
|
95
|
+
if (params.maxGradNorm !== undefined)
|
|
96
|
+
config.max_grad_norm = params.maxGradNorm;
|
|
97
|
+
if (params.maxSeqLength !== undefined)
|
|
98
|
+
config.max_length = params.maxSeqLength;
|
|
99
|
+
if (params.gradientCheckpointing !== undefined)
|
|
100
|
+
config.gradient_checkpointing = params.gradientCheckpointing;
|
|
101
|
+
if (params.mixedPrecision !== undefined) {
|
|
102
|
+
config.torch_dtype = params.mixedPrecision === 'bf16'
|
|
103
|
+
? 'bfloat16'
|
|
104
|
+
: params.mixedPrecision === 'fp16' ? 'float16' : 'float32';
|
|
105
|
+
}
|
|
106
|
+
if (params.seed !== undefined)
|
|
107
|
+
config.seed = params.seed;
|
|
108
|
+
if (params.loraRank !== undefined)
|
|
109
|
+
config.lora_rank = params.loraRank;
|
|
110
|
+
if (params.loraAlpha !== undefined)
|
|
111
|
+
config.lora_alpha = params.loraAlpha;
|
|
112
|
+
if (params.loraDropout !== undefined)
|
|
113
|
+
config.lora_dropout = params.loraDropout;
|
|
114
|
+
if (params.loraTargetModules !== undefined)
|
|
115
|
+
config.target_modules = params.loraTargetModules;
|
|
116
|
+
if (params.quantizationBit !== undefined)
|
|
117
|
+
config.quant_bits = params.quantizationBit;
|
|
118
|
+
if (params.deepspeed !== undefined)
|
|
119
|
+
config.deepspeed = params.deepspeed;
|
|
120
|
+
if (params.saveSteps !== undefined)
|
|
121
|
+
config.save_steps = params.saveSteps;
|
|
122
|
+
if (params.saveEpochs !== undefined) {
|
|
123
|
+
if (params.saveEpochs !== 1) {
|
|
124
|
+
throw new Error('BiOS: saveEpochs only supports 1; use extraConfig.save_strategy for explicit checkpoint policy');
|
|
125
|
+
}
|
|
126
|
+
config.save_strategy = 'epoch';
|
|
127
|
+
}
|
|
128
|
+
if (params.maxCheckpoints !== undefined)
|
|
129
|
+
config.save_total_limit = params.maxCheckpoints;
|
|
130
|
+
if (params.evalSteps !== undefined)
|
|
131
|
+
config.eval_steps = params.evalSteps;
|
|
132
|
+
if (params.evalSplit !== undefined) {
|
|
133
|
+
const ratio = typeof params.evalSplit === 'string' ? Number(params.evalSplit) : params.evalSplit;
|
|
134
|
+
if (!Number.isFinite(ratio))
|
|
135
|
+
throw new Error('BiOS: evalSplit must be a numeric ratio');
|
|
136
|
+
config.split_dataset_ratio = ratio;
|
|
137
|
+
}
|
|
138
|
+
if (params.extraConfig)
|
|
139
|
+
Object.assign(config, params.extraConfig);
|
|
140
|
+
if (Object.keys(config).length > 0)
|
|
141
|
+
body.config = config;
|
|
142
|
+
return { body, datasetIds, trainingMethod };
|
|
143
|
+
}
|
|
144
|
+
function normalizeJob(raw, fallback = {}) {
|
|
145
|
+
const job = { ...fallback, ...raw };
|
|
146
|
+
const id = String(job.id || job.job_id || '');
|
|
147
|
+
if (!id)
|
|
148
|
+
throw new Error('BiOS: training response did not include id or job_id');
|
|
149
|
+
job.id = id;
|
|
150
|
+
if (!job.job_id && raw.job_id)
|
|
151
|
+
job.job_id = String(raw.job_id);
|
|
152
|
+
if (!job.model_id && job.model)
|
|
153
|
+
job.model_id = job.model;
|
|
154
|
+
if (!job.training_method && job.method)
|
|
155
|
+
job.training_method = job.method;
|
|
156
|
+
if (!job.train_type && job.adapter)
|
|
157
|
+
job.train_type = job.adapter;
|
|
158
|
+
if (!job.rlhf_type && job.rlhf_algorithm)
|
|
159
|
+
job.rlhf_type = job.rlhf_algorithm;
|
|
160
|
+
if (!job.dataset_ids && job.dataset_id)
|
|
161
|
+
job.dataset_ids = [job.dataset_id];
|
|
162
|
+
// Compatibility aliases let existing automations migrate without losing the
|
|
163
|
+
// canonical REST field names returned by the current API.
|
|
164
|
+
if (!job.model && job.model_id)
|
|
165
|
+
job.model = job.model_id;
|
|
166
|
+
if (!job.method && job.training_method)
|
|
167
|
+
job.method = job.training_method;
|
|
168
|
+
if (!job.adapter && job.train_type)
|
|
169
|
+
job.adapter = job.train_type;
|
|
170
|
+
if (!job.rlhf_algorithm && job.rlhf_type)
|
|
171
|
+
job.rlhf_algorithm = job.rlhf_type;
|
|
172
|
+
if (!job.dataset_id && job.dataset_ids?.length)
|
|
173
|
+
job.dataset_id = job.dataset_ids[0];
|
|
174
|
+
if (!job.error && job.error_message)
|
|
175
|
+
job.error = job.error_message;
|
|
176
|
+
if (!job.status)
|
|
177
|
+
job.status = 'pending';
|
|
178
|
+
return job;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Create, monitor, and manage fine-tuning training jobs.
|
|
182
|
+
*/
|
|
183
|
+
export class Training {
|
|
184
|
+
_http;
|
|
185
|
+
/** @internal */
|
|
186
|
+
constructor(_http) {
|
|
187
|
+
this._http = _http;
|
|
188
|
+
}
|
|
189
|
+
/** Return the authoritative BIOS image, method, adapter, and hyperparameter contract. */
|
|
190
|
+
async capabilities() {
|
|
191
|
+
return this._http.fetchGet('/api/training/capabilities');
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Create a new training job.
|
|
195
|
+
*
|
|
196
|
+
* Provisions GPU resources, downloads the model and dataset, and begins
|
|
197
|
+
* training. The job starts in "pending" status and progresses through
|
|
198
|
+
* provisioning, downloading, and running states.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* const job = await client.training.create({
|
|
203
|
+
* model: 'meta-llama/Llama-3.1-8B-Instruct',
|
|
204
|
+
* datasetId: 'ds_abc123',
|
|
205
|
+
* method: 'sft',
|
|
206
|
+
* adapter: 'lora',
|
|
207
|
+
* epochs: 3,
|
|
208
|
+
* learningRate: 2e-4,
|
|
209
|
+
* loraRank: 16,
|
|
210
|
+
* loraAlpha: 32,
|
|
211
|
+
* gpuType: 'A100_80GB',
|
|
212
|
+
* gpuCount: 1,
|
|
213
|
+
* });
|
|
214
|
+
* console.log(`Job ${job.id} created -- status: ${job.status}`);
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
async create(params) {
|
|
218
|
+
const { body, datasetIds, trainingMethod } = buildTrainingRequest(params);
|
|
219
|
+
const result = await this._http.fetchPost('/api/training/jobs', body, {
|
|
220
|
+
'Idempotency-Key': trainingIdempotencyKey(params.idempotencyKey),
|
|
221
|
+
});
|
|
222
|
+
return normalizeJob(result, {
|
|
223
|
+
model_id: params.model,
|
|
224
|
+
model_revision: params.modelRevision,
|
|
225
|
+
training_method: trainingMethod,
|
|
226
|
+
train_type: params.adapter,
|
|
227
|
+
rlhf_type: params.rlhfAlgorithm,
|
|
228
|
+
dataset_ids: datasetIds,
|
|
229
|
+
gpu_type: params.gpuType,
|
|
230
|
+
gpu_count: params.gpuCount,
|
|
231
|
+
status: 'pending',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/** Validate and canonicalize a training request without creating or billing a job. */
|
|
235
|
+
async preflight(params) {
|
|
236
|
+
const { body } = buildTrainingRequest(params);
|
|
237
|
+
return this._http.fetchPost('/api/training/preflight', body);
|
|
238
|
+
}
|
|
239
|
+
/** Return one server-driven page with pagination metadata. */
|
|
240
|
+
async listPage(params = {}) {
|
|
241
|
+
const q = new URLSearchParams();
|
|
242
|
+
if (params.workspaceId)
|
|
243
|
+
q.set('workspace_id', params.workspaceId);
|
|
244
|
+
if (params.status)
|
|
245
|
+
q.set('status', params.status);
|
|
246
|
+
if (params.limit !== undefined)
|
|
247
|
+
q.set('limit', String(params.limit));
|
|
248
|
+
if (params.offset !== undefined)
|
|
249
|
+
q.set('offset', String(params.offset));
|
|
250
|
+
const qs = q.toString();
|
|
251
|
+
const raw = await this._http.fetchGet(`/api/training/jobs${qs ? `?${qs}` : ''}`);
|
|
252
|
+
const envelope = Array.isArray(raw)
|
|
253
|
+
? { jobs: raw.map((job) => normalizeJob(job)), total: raw.length, limit: raw.length, offset: 0 }
|
|
254
|
+
: {
|
|
255
|
+
jobs: (raw.jobs || []).map((job) => normalizeJob(job)),
|
|
256
|
+
total: Number(raw.total || 0),
|
|
257
|
+
limit: Number(raw.limit || params.limit || 50),
|
|
258
|
+
offset: Number(raw.offset || 0),
|
|
259
|
+
};
|
|
260
|
+
return envelope;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* List training jobs, optionally filtered by workspace or status.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* const jobs = await client.training.list({ status: 'running' });
|
|
268
|
+
* for (const job of jobs) {
|
|
269
|
+
* console.log(`${job.id} -- ${job.model} -- ${job.status}`);
|
|
270
|
+
* }
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
async list(params = {}) {
|
|
274
|
+
return (await this.listPage(params)).jobs;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get detailed information about a training job.
|
|
278
|
+
*
|
|
279
|
+
* @param id - Training job ID.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```ts
|
|
283
|
+
* const job = await client.training.get('job_abc123');
|
|
284
|
+
* console.log(`Status: ${job.status}, Progress: ${job.progress}%`);
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
async get(id) {
|
|
288
|
+
const result = await this._http.fetchGet(`/api/training/jobs/${encodeURIComponent(id)}`);
|
|
289
|
+
return normalizeJob(result);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get training metrics (loss curves, learning rate, throughput) for a job.
|
|
293
|
+
*
|
|
294
|
+
* @param id - Training job ID.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* const metrics = await client.training.getMetrics('job_abc123');
|
|
299
|
+
* console.log(`Current loss: ${metrics.metrics.at(-1)?.loss}`);
|
|
300
|
+
* console.log(`${metrics.metrics.length} metric points`);
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
async getMetrics(id) {
|
|
304
|
+
const result = await this._http.fetchGet(`/api/training/jobs/${encodeURIComponent(id)}/metrics`);
|
|
305
|
+
const metrics = Array.isArray(result.metrics)
|
|
306
|
+
? result.metrics
|
|
307
|
+
: Array.isArray(result.steps) ? result.steps : [];
|
|
308
|
+
return { ...result, metrics, graph_configs: result.graph_configs || [], steps: metrics };
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* List checkpoints saved during training.
|
|
312
|
+
*
|
|
313
|
+
* @param id - Training job ID.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* const checkpoints = await client.training.getCheckpoints('job_abc123');
|
|
318
|
+
* for (const cp of checkpoints) {
|
|
319
|
+
* console.log(`${cp.name}: ${cp.size_bytes} bytes`);
|
|
320
|
+
* }
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
async getCheckpoints(id) {
|
|
324
|
+
const result = await this._http.fetchGet(`/api/training/jobs/${encodeURIComponent(id)}/checkpoints`);
|
|
325
|
+
return Array.isArray(result) ? result : result.checkpoints || [];
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get training logs for a job.
|
|
329
|
+
*
|
|
330
|
+
* @param id - Training job ID.
|
|
331
|
+
* @param tail - Maximum newest entries to return (server clamps to 1-1000).
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* const logs = await client.training.getLogs('job_abc123');
|
|
336
|
+
* for (const entry of logs.logs) {
|
|
337
|
+
* console.log(entry.level, entry.message);
|
|
338
|
+
* }
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
async getLogs(id, tail = 200) {
|
|
342
|
+
const result = await this._http.fetchGet(`/api/training/jobs/${encodeURIComponent(id)}/logs?tail=${encodeURIComponent(String(tail))}`);
|
|
343
|
+
const logs = (result.logs || []).map((entry) => typeof entry === 'string'
|
|
344
|
+
? { level: 'INFO', message: entry, timestamp: '' }
|
|
345
|
+
: entry);
|
|
346
|
+
return { logs, lines: logs.map((entry) => entry.message) };
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Stop a running training job.
|
|
350
|
+
*
|
|
351
|
+
* By default, intermediate data (checkpoints, logs) is preserved.
|
|
352
|
+
* Pass `keepData: false` to clean up all job artifacts.
|
|
353
|
+
*
|
|
354
|
+
* @param id - Training job ID.
|
|
355
|
+
* @param keepData - Whether to keep checkpoints and logs. Defaults to true.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```ts
|
|
359
|
+
* await client.training.stop('job_abc123');
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
async stop(id, keepData = true) {
|
|
363
|
+
return this._http.fetchPost(`/api/training/jobs/${encodeURIComponent(id)}/stop`, { keep_data: keepData });
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Resume a stopped training job from its last checkpoint.
|
|
367
|
+
*
|
|
368
|
+
* @param id - Training job ID.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* await client.training.resume('job_abc123');
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
async resume(id, idempotencyKey) {
|
|
376
|
+
const result = await this._http.fetchPost(`/api/training/jobs/${encodeURIComponent(id)}/resume`, undefined, { 'Idempotency-Key': trainingIdempotencyKey(idempotencyKey) });
|
|
377
|
+
const resumedId = result.id || result.job_id;
|
|
378
|
+
if (!resumedId)
|
|
379
|
+
throw new Error('BiOS: resume response did not include job_id');
|
|
380
|
+
return { ...result, id: resumedId };
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Delete a specific checkpoint from a training job.
|
|
384
|
+
*
|
|
385
|
+
* @param jobId - Training job ID.
|
|
386
|
+
* @param checkpointId - Checkpoint ID to delete.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* await client.training.deleteCheckpoint('job_abc123', 'cp_xyz789');
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
async getEvals(id) {
|
|
394
|
+
return this._http.fetchGet(`/api/training/jobs/${encodeURIComponent(id)}/evals`);
|
|
395
|
+
}
|
|
396
|
+
async deleteCheckpoint(jobId, checkpointId) {
|
|
397
|
+
await this._http.fetchDelete(`/api/training/jobs/${encodeURIComponent(jobId)}/checkpoints/${encodeURIComponent(checkpointId)}`);
|
|
398
|
+
}
|
|
399
|
+
}
|