feat(finetune): clarify model flags; add price estimate & actual cost

- Rename for clarity: finetune --model → --base-model (create/price/
  capability/list); deploy create --model → --model-name, --name →
  --display-name
- Add `finetune price` (console domain) for pre-training cost estimate
  (sft/dpo/cpt)
- Add actual training cost (fee.ts) enriched into finetune get/watch
  from catalog price × reported usage
This commit is contained in:
故璃
2026-08-06 15:08:02 +08:00
parent a7245c0f62
commit f30fff9065
24 changed files with 707 additions and 256 deletions
+3 -3
View File
@@ -130,10 +130,10 @@ bl auth login --console
# Fine-tune & deploy — a one-shot train-to-serve workflow
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
bl finetune watch --job-id ft-xxx --output json # Non-blocking probe (running/succeeded return 0; failed/canceled report an error)
bl finetune capability --model qwen3-8b # Which training types a model supports
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
bl finetune capability --base-model qwen3-8b # Which training types a model supports
bl deploy text create --model-name qwen3-8b --display-name my-svc --plan mu # Deploy the trained model as an endpoint
# Browse models / apps / free-tier quota / usage statistics / workspaces
bl model list # Browse model families and pricing
+3 -3
View File
@@ -128,10 +128,10 @@ bl auth login --console
# 微调与部署 — 从训练到服务的一站式流程
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
bl finetune watch --job-id ft-xxx --output json # 非阻塞探测(运行中/成功返回 0;失败/取消报错)
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
bl finetune capability --base-model qwen3-8b # 查询模型支持哪些训练方式
bl deploy text create --model-name qwen3-8b --display-name my-svc --plan mu # 把训练好的模型部署为推理服务
# 浏览模型 / 应用 / 免费额度 / 用量统计 / 业务空间
bl model list # 浏览模型系列与价格信息
+3 -3
View File
@@ -130,10 +130,10 @@ bl auth login --console
# Fine-tune & deploy — a one-shot train-to-serve workflow
bl dataset upload --file ./train.jsonl # Upload a .jsonl dataset (validated first)
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # Local paths auto-upload
bl finetune watch --job-id ft-xxx --output json # Non-blocking probe (running/succeeded return 0; failed/canceled report an error)
bl finetune capability --model qwen3-8b # Which training types a model supports
bl deploy text create --model qwen3-8b --name my-svc --plan mu # Deploy the trained model as an endpoint
bl finetune capability --base-model qwen3-8b # Which training types a model supports
bl deploy text create --model-name qwen3-8b --display-name my-svc --plan mu # Deploy the trained model as an endpoint
# Browse models / apps / free-tier quota / usage statistics / workspaces
bl model list # Browse model families and pricing
+3 -3
View File
@@ -128,10 +128,10 @@ bl auth login --console
# 微调与部署 — 从训练到服务的一站式流程
bl dataset upload --file ./train.jsonl # 上传 .jsonl 数据集(先校验)
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --training-type sft-lora # 本地路径自动上传
bl finetune watch --job-id ft-xxx --output json # 非阻塞探测(运行中/成功返回 0;失败/取消报错)
bl finetune capability --model qwen3-8b # 查询模型支持哪些训练方式
bl deploy text create --model qwen3-8b --name my-svc --plan mu # 把训练好的模型部署为推理服务
bl finetune capability --base-model qwen3-8b # 查询模型支持哪些训练方式
bl deploy text create --model-name qwen3-8b --display-name my-svc --plan mu # 把训练好的模型部署为推理服务
# 浏览模型 / 应用 / 免费额度 / 用量统计 / 业务空间
bl model list # 浏览模型系列与价格信息
+2
View File
@@ -71,6 +71,7 @@ import {
finetuneExport,
finetuneWatch,
finetuneCapability,
finetunePrice,
deployTextCreate,
deployAudioCreate,
deployImageCreate,
@@ -191,6 +192,7 @@ export const commands: Record<string, AnyCommand> = {
"finetune export": finetuneExport,
"finetune watch": finetuneWatch,
"finetune capability": finetuneCapability,
"finetune price": finetunePrice,
"deploy text create": deployTextCreate,
"deploy audio create": deployAudioCreate,
"deploy image create": deployImageCreate,
+22 -25
View File
@@ -13,13 +13,13 @@ import {
import { emitResult, emitBare } from "bailian-cli-runtime";
const CREATE_FLAGS = {
model: {
modelName: {
type: "string",
valueHint: "<name>",
description: "Model name (catalog model or fine-tuned output) (required)",
valueHint: "<model_name>",
description: "Model to deploy — fine-tuned output name or catalog model (required)",
required: true,
},
name: {
displayName: {
type: "string",
valueHint: "<display_name>",
description: "Console display name for the deployment (required)",
@@ -63,7 +63,7 @@ const CREATE_FLAGS = {
} satisfies FlagsDef;
const CREATE_USAGE =
"--model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]";
"--model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]";
const CREATE_NOTES = [
"Plan defaults to `lora` (Token-billed) for text/image and `mu` (model-unit-",
@@ -77,14 +77,11 @@ const CREATE_NOTES = [
"Use `bl deploy models --source base` to inspect available templates.",
"After creation, status starts at PENDING and transitions to RUNNING.",
"Invoke the deployed model with: bl text chat --model <deployed_model>",
"WARNING: --model is overloaded across commands and refers to DIFFERENT",
"values. `bl deploy <modality> create --model` takes the exported model_name",
"(e.g. `qwen3-8b-ft-...`), but the create response also returns a",
"`deployed_model` field (the deployment instance id, e.g.",
"`qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use",
"the `deployed_model` from the create response — NOT the `model_name` you",
"passed to `deploy <modality> create`. Do not reuse the value across the two",
"commands.",
"NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).",
"The create response also returns a `deployed_model` field — the deployment",
"instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference",
"(`bl text chat --model <deployed_model>`) and lifecycle commands",
"(`deploy get/scale/pause/resume/delete --deployed-model <id>`).",
];
/**
@@ -118,8 +115,8 @@ async function runCreate(
ctx: CommandContext<typeof CREATE_FLAGS>,
): Promise<void> {
const { identity, settings, flags } = ctx;
const model = flags.model as string;
const name = flags.name as string;
const model = flags.modelName as string;
const name = flags.displayName as string;
const plan = (flags.plan as string | undefined) || defaultDeployPlan(modality);
// Plan-specific behaviour is owned by core `plans.ts`. The strategy resolves
@@ -165,10 +162,10 @@ export const deployTextCreate = defineCommand({
usageArgs: CREATE_USAGE,
flags: CREATE_FLAGS,
exampleArgs: [
"--model my-qwen-sft --name my-sft-test",
"--model qwen3.6-flash-2026-04-16 --name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000",
"--model qwen3-8b --name my-qwen3-mu --plan mu",
"--model qwen3-8b --name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2",
"--model-name my-qwen-sft --display-name my-sft-test",
"--model-name qwen3.6-flash-2026-04-16 --display-name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000",
"--model-name qwen3-8b --display-name my-qwen3-mu --plan mu",
"--model-name qwen3-8b --display-name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2",
],
notes: CREATE_NOTES,
validate: (flags) => validateCreate("text", flags),
@@ -182,9 +179,9 @@ export const deployAudioCreate = defineCommand({
usageArgs: CREATE_USAGE,
flags: CREATE_FLAGS,
exampleArgs: [
"--model my-cosyvoice-ft --name my-tts",
"--model my-cosyvoice-ft --name my-tts --deploy-spec dps-xxxx --capacity 1",
"--model my-cosyvoice-ft --name my-tts --dry-run",
"--model-name my-cosyvoice-ft --display-name my-tts",
"--model-name my-cosyvoice-ft --display-name my-tts --deploy-spec dps-xxxx --capacity 1",
"--model-name my-cosyvoice-ft --display-name my-tts --dry-run",
],
notes: CREATE_NOTES,
validate: (flags) => validateCreate("audio", flags),
@@ -198,9 +195,9 @@ export const deployImageCreate = defineCommand({
usageArgs: CREATE_USAGE,
flags: CREATE_FLAGS,
exampleArgs: [
"--model my-wan-ft --name my-wan",
"--model my-wan-ft --name my-wan-mu --plan mu",
"--model my-wan-ft --name my-wan --dry-run",
"--model-name my-wan-ft --display-name my-wan",
"--model-name my-wan-ft --display-name my-wan-mu --plan mu",
"--model-name my-wan-ft --display-name my-wan --dry-run",
],
notes: CREATE_NOTES,
validate: (flags) => validateCreate("image", flags),
@@ -43,7 +43,7 @@ async function fetchAllFoundationModels(settings: Settings): Promise<ModelCapabi
}
const CAPABILITY_FLAGS = {
model: {
baseModel: {
type: "string",
valueHint: "<m>",
description: "List training types supported by this base model.",
@@ -59,29 +59,30 @@ export default defineCommand({
description:
"Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it)",
auth: "none",
usageArgs: "--model <m> | --training-type <t>",
usageArgs: "--base-model <m> | --training-type <t>",
flags: CAPABILITY_FLAGS,
exampleArgs: [
"--model qwen3-8b",
"--base-model qwen3-8b",
"--training-type sft-lora",
"--training-type cpt --output json",
"--training-type sft --quiet",
],
notes: [
"Exactly one of --model / --training-type is required.",
"Exactly one of --base-model / --training-type is required.",
"Training-type values use the `<method>` / `<method>-lora` convention:",
"sft | sft-lora | dpo | dpo-lora | cpt. (cpt has no -lora variant server-side.)",
"Queries listFoundationModels, a public API — no console login needed.",
],
validate: (f) => {
if (f.model && f.trainingType)
return "--model and --training-type are mutually exclusive; pass one.";
if (!f.model && !f.trainingType) return "one of --model / --training-type is required.";
if (f.baseModel && f.trainingType)
return "--base-model and --training-type are mutually exclusive; pass one.";
if (!f.baseModel && !f.trainingType)
return "one of --base-model / --training-type is required.";
return undefined;
},
async run(ctx) {
const { settings, flags } = ctx;
const model = flags.model || undefined;
const model = flags.baseModel || undefined;
const trainingType = flags.trainingType || undefined;
if (settings.dryRun) {
@@ -19,7 +19,7 @@ export default defineCommand({
flags: CHECKPOINTS_FLAGS,
exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --output json"],
notes: [
"`model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model`.",
"`model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model-name`.",
"Checkpoints expire ~15 days after creation; `expire_time` shows the deadline. Export or deploy before expiry.",
],
async run(ctx) {
@@ -215,10 +215,10 @@ type CommandModality = "text" | "audio" | "image";
* output. Every modality's model consumes these.
*/
const COMMON_FLAGS = {
model: {
baseModel: {
type: "string",
valueHint: "<model>",
description: "Base model to fine-tune",
description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)",
required: true,
},
datasets: {
@@ -316,13 +316,13 @@ const IMAGE_FLAGS = {
} satisfies FlagsDef;
const TEXT_USAGE =
"--model <model> --datasets <id|path,...> [--validations <id|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft|sft-lora|dpo|dpo-lora|cpt>]";
"--base-model <model> --datasets <id|path,...> [--validations <id|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft|sft-lora|dpo|dpo-lora|cpt>]";
const AUDIO_USAGE =
"--model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>]";
"--base-model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>]";
const IMAGE_USAGE =
"--model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i|i2i>] [--learning-rate <str>]";
"--base-model <model> --datasets <id|path> [--validations <id|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i|i2i>] [--learning-rate <str>]";
const COMMON_NOTES = [
"Creating a job uploads any local datasets and consumes training quota.",
@@ -382,7 +382,7 @@ async function runCreate<F extends FlagsDef>(
): Promise<void> {
const { identity, settings } = ctx;
const flags = ctx.flags as Record<string, unknown>;
const model = flags.model as string;
const model = flags.baseModel as string;
const datasetsRaw = flags.datasets as string;
// CosyVoice audio fine-tuning accepts exactly one training file
@@ -636,14 +636,14 @@ export const finetuneTextCreate = defineCommand({
usageArgs: TEXT_USAGE,
flags: TEXT_FLAGS,
exampleArgs: [
"--model qwen3-8b --datasets file-xxx",
"--model qwen3-8b --datasets ./train.jsonl",
"--model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl",
"--model qwen3-8b --datasets file-aaa,./extra.jsonl",
"--model qwen3-8b --datasets ./train.jsonl --training-type sft",
'--model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4',
"--model qwen3-8b --datasets file-xxx --output json",
"--model qwen3-8b --datasets file-xxx --dry-run",
"--base-model qwen3-8b --datasets file-xxx",
"--base-model qwen3-8b --datasets ./train.jsonl",
"--base-model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl",
"--base-model qwen3-8b --datasets file-aaa,./extra.jsonl",
"--base-model qwen3-8b --datasets ./train.jsonl --training-type sft",
'--base-model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4',
"--base-model qwen3-8b --datasets file-xxx --output json",
"--base-model qwen3-8b --datasets file-xxx --dry-run",
],
notes: TEXT_NOTES,
run: (ctx) => runCreate("text", ctx),
@@ -656,11 +656,11 @@ export const finetuneAudioCreate = defineCommand({
usageArgs: AUDIO_USAGE,
flags: AUDIO_FLAGS,
exampleArgs: [
"--model cosyvoice-v3-flash --datasets ./audio.zip",
"--model cosyvoice-v3-flash --datasets file-xxx",
"--model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts",
"--model cosyvoice-v3-flash --datasets file-xxx --output json",
"--model cosyvoice-v3-flash --datasets ./audio.zip --dry-run",
"--base-model cosyvoice-v3-flash --datasets ./audio.zip",
"--base-model cosyvoice-v3-flash --datasets file-xxx",
"--base-model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts",
"--base-model cosyvoice-v3-flash --datasets file-xxx --output json",
"--base-model cosyvoice-v3-flash --datasets ./audio.zip --dry-run",
],
notes: AUDIO_NOTES,
run: (ctx) => runCreate("audio", ctx),
@@ -673,12 +673,12 @@ export const finetuneImageCreate = defineCommand({
usageArgs: IMAGE_USAGE,
flags: IMAGE_FLAGS,
exampleArgs: [
"--model wan2.7-image-pro --datasets ./images.zip",
"--model wan2.7-image-pro --datasets file-xxx",
"--model wan2.7-image-pro --datasets file-xxx --generation-type i2i",
"--model wan2.7-image-pro --datasets ./images.zip --model-name my-wan",
"--model wan2.7-image-pro --datasets file-xxx --output json",
"--model wan2.7-image-pro --datasets ./images.zip --dry-run",
"--base-model wan2.7-image-pro --datasets ./images.zip",
"--base-model wan2.7-image-pro --datasets file-xxx",
"--base-model wan2.7-image-pro --datasets file-xxx --generation-type i2i",
"--base-model wan2.7-image-pro --datasets ./images.zip --model-name my-wan",
"--base-model wan2.7-image-pro --datasets file-xxx --output json",
"--base-model wan2.7-image-pro --datasets ./images.zip --dry-run",
],
notes: IMAGE_NOTES,
run: (ctx) => runCreate("image", ctx),
@@ -0,0 +1,100 @@
/**
* Best-effort actual training fee calculation using the model catalog's
* "ft" (fine-tune) price entry. Pure API-key domain — no console auth needed.
*
* The model catalog (`listFoundationModels` via public gateway) returns a
* `prices[]` array **only when `queryPrice: true` is passed** (the same flag
* `fetchModelDetail` uses). Combined with the job's `output.usage` (actual
* consumed tokens, present on SUCCEEDED / CANCELED), this gives the exact
* training cost without any console-domain login.
*/
import {
callConsoleGateway,
effectiveConsoleGatewayConfig,
unwrapResponse,
MODEL_LIST_API,
type Settings,
type ModelPriceInfo,
} from "bailian-cli-core";
export interface ActualFee {
cost: number;
unitPrice: number;
priceUnit: string;
}
/**
* Fetch the model's training price from the public catalog gateway.
* Uses the same anonymous gateway path as `fetchModelCapability` (no console
* token required), but adds `queryPrice: true` to include the prices array.
*/
async function fetchTrainingPrice(
settings: Settings,
model: string,
): Promise<ModelPriceInfo | null> {
const eff = effectiveConsoleGatewayConfig(settings);
const result = await callConsoleGateway(
{ region: eff.consoleRegion, site: eff.consoleSite, switchAgent: eff.consoleSwitchAgent },
settings.timeout,
{
api: MODEL_LIST_API,
data: {
input: {
pageNo: 1,
pageSize: 10,
group: true,
model,
queryPrice: true,
querySampleCode: false,
queryGroupByModel: true,
queryQuota: false,
queryQpmInfo: false,
queryApplyStatus: false,
queryPermissions: false,
queryActivationStatus: false,
},
},
},
);
const responseData = unwrapResponse(result as Record<string, unknown>);
const list = (responseData.list as Record<string, unknown>[]) ?? [];
// The response is grouped; find the exact model in items.
for (const group of list) {
const items = (group.items as Record<string, unknown>[]) ?? [];
for (const item of items) {
if (item.model === model) {
const prices = (item.prices as ModelPriceInfo[]) ?? [];
return prices.find((entry) => entry.type === "ft") ?? null;
}
}
// Flat response fallback (no items nesting).
if (group.model === model) {
const prices = (group.prices as ModelPriceInfo[]) ?? [];
return prices.find((entry) => entry.type === "ft") ?? null;
}
}
return null;
}
/**
* Compute the actual training fee from the model catalog.
* Returns null when the price is unavailable (network error, model not in
* catalog, or no "ft" entry in the prices array). Never throws.
*/
export async function computeActualFee(
settings: Settings,
model: string,
usageTokens: number,
): Promise<ActualFee | null> {
try {
const ftEntry = await fetchTrainingPrice(settings, model);
const unitPrice = Number(ftEntry?.price);
if (!Number.isFinite(unitPrice) || unitPrice <= 0) return null;
const priceUnit = ftEntry?.priceUnit ?? "每百万tokens";
// price is yuan per million tokens.
const cost = (usageTokens / 1_000_000) * unitPrice;
return { cost: Number(cost.toFixed(4)), unitPrice, priceUnit };
} catch {
return null;
}
}
+15 -2
View File
@@ -1,5 +1,6 @@
import { defineCommand, getFineTune, type FlagsDef } from "bailian-cli-core";
import { emitResult } from "bailian-cli-runtime";
import { computeActualFee } from "./fee.ts";
const GET_FLAGS = {
jobId: {
@@ -44,7 +45,9 @@ export default defineCommand({
if (hyperParameters?.max_length !== undefined)
hyperParts.push(`max_length=${hyperParameters.max_length}`);
const item = {
const usageTokens = typeof job.usage === "number" ? job.usage : undefined;
const item: Record<string, unknown> = {
job_id: job.job_id ?? jobId,
base_model: job.model ?? "",
status: job.status ?? "",
@@ -56,10 +59,20 @@ export default defineCommand({
model_name: job.model_name ?? "",
created_at: job.create_time ?? job.gmt_create ?? "",
updated_at: job.end_time ?? job.gmt_modified ?? "",
usage: typeof job.usage === "number" ? String(job.usage) : "",
usage_tokens: usageTokens ?? "",
charge_type: typeof job.charge_type === "string" ? job.charge_type : "",
};
// Actual fee: only when the platform reports a concrete token count
// (SUCCEEDED / CANCELED). Best-effort — silently omitted on lookup failure.
if (usageTokens !== undefined && usageTokens > 0 && job.model) {
const fee = await computeActualFee(settings, job.model, usageTokens);
if (fee) {
item.training_cost = fee.cost;
item.cost_basis = `${fee.unitPrice} 元/${fee.priceUnit}`;
}
}
emitResult({ ...item, request_id: response.request_id }, "json");
},
});
@@ -13,26 +13,35 @@ const LIST_FLAGS = {
valueHint: "<s>",
description: "Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED)",
},
baseModel: {
type: "string",
valueHint: "<model>",
description: "Filter by base model ID (server-side)",
},
} satisfies FlagsDef;
export default defineCommand({
description: "List fine-tune jobs",
auth: "apiKey",
usageArgs: "[--page <n>] [--page-size <n>] [--status <s>]",
usageArgs: "[--page <n>] [--page-size <n>] [--status <s>] [--base-model <model>]",
flags: LIST_FLAGS,
exampleArgs: ["", "--status RUNNING", "--page-size 20 --output json"],
exampleArgs: ["", "--status RUNNING", "--base-model qwen3-8b", "--page-size 20"],
async run(ctx) {
const { settings, flags } = ctx;
const pageNo = flags.page;
const pageSize = flags.pageSize;
const status = flags.status || undefined;
const model = flags.baseModel || undefined;
if (settings.dryRun) {
emitResult({ action: "finetune.list", page: pageNo, page_size: pageSize, status }, "json");
emitResult(
{ action: "finetune.list", page: pageNo, page_size: pageSize, status, model },
"json",
);
return;
}
const response = await listFineTunes(ctx.client, { pageNo, pageSize, status });
const response = await listFineTunes(ctx.client, { pageNo, pageSize, status, model });
const payload = response.output ?? response.data;
const jobs = payload?.jobs ?? [];
const total = payload?.total;
@@ -0,0 +1,139 @@
import {
defineCommand,
fetchTrainingModelPrice,
estimateSftDpoTokens,
estimateCptTokens,
BailianError,
ExitCode,
type FlagsDef,
} from "bailian-cli-core";
import { emitResult } from "bailian-cli-runtime";
const PRICE_FLAGS = {
baseModel: {
type: "string",
valueHint: "<model>",
description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)",
required: true,
},
datasets: {
type: "string",
valueHint: "<ids>",
description: "Training dataset file IDs, comma-separated (required)",
required: true,
},
trainingType: {
type: "string",
valueHint: "<type>",
description: "Training type: sft | dpo | cpt (default: sft)",
},
nEpochs: {
type: "number",
valueHint: "<n>",
description: "Number of training epochs (default: 3)",
},
} satisfies FlagsDef;
const SUPPORTED_TRAINING_TYPES = ["sft", "dpo", "cpt"];
// Fixed hyper-parameters used for estimation. Only n_epochs materially affects
// the estimate; the rest are held at representative defaults (not exposed as
// flags to keep the command surface minimal).
const ESTIMATE_BATCH_SIZE = 16;
const ESTIMATE_MAX_LENGTH = 8192;
const DEFAULT_N_EPOCHS = 3;
export default defineCommand({
description: "Estimate the training cost for a fine-tune job (token billing)",
auth: "console",
usageArgs: "--base-model <model> --datasets <ids> [--training-type <type>] [--n-epochs <n>]",
flags: PRICE_FLAGS,
exampleArgs: [
"--base-model qwen3-8b --datasets file-ft-xxx",
"--base-model qwen3-8b --datasets file-ft-xxx,file-ft-yyy --n-epochs 2",
"--base-model qwen3-8b --datasets file-ft-xxx --training-type cpt",
],
notes: [
"Estimate only — the server computes token usage from the datasets; final cost is subject to the bill.",
"Covers token billing for sft / dpo / cpt. Training-unit (MTU) billing is not supported by this command.",
"Hyper-parameters other than --n-epochs are fixed at representative defaults for estimation.",
],
async run(ctx) {
const { settings, flags } = ctx;
const model = flags.baseModel;
const datasetIds = flags.datasets
.split(",")
.map((datasetId) => datasetId.trim())
.filter(Boolean);
const trainingType = (flags.trainingType ?? "sft").toLowerCase();
const nEpochs = flags.nEpochs ?? DEFAULT_N_EPOCHS;
if (!SUPPORTED_TRAINING_TYPES.includes(trainingType)) {
throw new BailianError(
`Unsupported training type "${trainingType}". Supported: ${SUPPORTED_TRAINING_TYPES.join(", ")}.`,
ExitCode.USAGE,
);
}
if (datasetIds.length === 0) {
throw new BailianError("--datasets must contain at least one file ID.", ExitCode.USAGE);
}
if (settings.dryRun) {
emitResult(
{ action: "finetune.price", model, datasets: datasetIds, trainingType, nEpochs },
"json",
);
return;
}
// Unit price (yuan per 千Token).
const priceInfo = await fetchTrainingModelPrice(ctx.client, model);
const unitPrice = Number(priceInfo.price);
if (!Number.isFinite(unitPrice)) {
throw new BailianError(
`No training price found for model "${model}".`,
ExitCode.GENERAL,
undefined,
{ rawResponse: JSON.stringify(priceInfo) },
);
}
// Per-epoch token estimate (min/max range).
const estimate =
trainingType === "cpt"
? await estimateCptTokens(ctx.client, model, datasetIds.join(","), nEpochs)
: await estimateSftDpoTokens(ctx.client, datasetIds, {
nEpochs,
batchSize: ESTIMATE_BATCH_SIZE,
maxLength: ESTIMATE_MAX_LENGTH,
});
const minPerEpoch = estimate.estimatedDatasetConsumedTokensMinPerEpoch ?? 0;
const maxPerEpoch = estimate.estimatedDatasetConsumedTokensMaxPerEpoch ?? 0;
const mixedMinPerEpoch = estimate.estimatedMixedConsumedTokensMinPerEpoch ?? 0;
const mixedMaxPerEpoch = estimate.estimatedMixedConsumedTokensMaxPerEpoch ?? 0;
const minTokens = (minPerEpoch + mixedMinPerEpoch) * nEpochs;
const maxTokens = (maxPerEpoch + mixedMaxPerEpoch) * nEpochs;
// price is yuan per 1000 tokens.
const minFee = (minTokens / 1000) * unitPrice;
const maxFee = (maxTokens / 1000) * unitPrice;
emitResult(
{
model,
training_type: trainingType,
n_epochs: nEpochs,
unit_price: unitPrice,
price_unit: priceInfo.priceUnit ?? "千Token",
estimated_tokens: { min: minTokens, max: maxTokens },
estimated_fee_yuan: {
min: Number(minFee.toFixed(4)),
max: Number(maxFee.toFixed(4)),
},
disclaimer: "Server-side estimate; final cost is subject to the bill.",
},
"json",
);
},
});
@@ -6,6 +6,7 @@ import {
type FlagsDef,
} from "bailian-cli-core";
import { emitResult, emitBare } from "bailian-cli-runtime";
import { computeActualFee } from "./fee.ts";
const DEFAULT_INTERVAL_SEC = 10;
const MIN_INTERVAL_SEC = 1;
@@ -131,10 +132,23 @@ export default defineCommand({
// Just the status word — ideal for `status=$(... finetune watch ... --quiet)`.
emitBare(status || "UNKNOWN");
} else {
emitResult(
{ job_id: jobId, status: status || "UNKNOWN", terminal, request_id: response.request_id },
"json",
);
const output: Record<string, unknown> = {
job_id: jobId,
status: status || "UNKNOWN",
terminal,
request_id: response.request_id,
};
// Enrich terminal output with actual fee when usage is reported.
const usageTokens = typeof job?.usage === "number" ? job.usage : undefined;
if (terminal && usageTokens && usageTokens > 0 && job?.model) {
output.usage_tokens = usageTokens;
const fee = await computeActualFee(settings, job.model as string, usageTokens);
if (fee) {
output.training_cost = fee.cost;
output.cost_basis = `${fee.unitPrice} 元/${fee.priceUnit}`;
}
}
emitResult(output, "json");
}
if (terminal && status !== "SUCCEEDED") {
@@ -171,7 +185,18 @@ export default defineCommand({
if (settings.quiet) {
emitBare(status || "UNKNOWN");
} else {
emitResult(response, "json");
// Enrich the raw response with actual fee when usage is available.
const usageTokens = typeof job?.usage === "number" ? job.usage : undefined;
const enriched: Record<string, unknown> = { ...response };
if (usageTokens && usageTokens > 0 && job?.model) {
const fee = await computeActualFee(settings, job.model as string, usageTokens);
if (fee) {
enriched.training_cost = fee.cost;
enriched.usage_tokens = usageTokens;
enriched.cost_basis = `${fee.unitPrice} 元/${fee.priceUnit}`;
}
}
emitResult(enriched, "json");
}
if (status !== "SUCCEEDED") {
throw new BailianError(
+1
View File
@@ -76,6 +76,7 @@ export { default as finetuneCheckpoints } from "./commands/finetune/checkpoints.
export { default as finetuneExport } from "./commands/finetune/export.ts";
export { default as finetuneWatch } from "./commands/finetune/watch.ts";
export { default as finetuneCapability } from "./commands/finetune/capability.ts";
export { default as finetunePrice } from "./commands/finetune/price.ts";
export {
deployTextCreate,
deployAudioCreate,
@@ -30,7 +30,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--model|--name/i);
expect(stderr).toMatch(/--model-name|--display-name/i);
});
test("deploy create --dry-run 构造 lora 部署请求体", async () => {
@@ -38,9 +38,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
"deploy",
"text",
"create",
"--model",
"--model-name",
"qwen-plus-2025-12-01",
"--name",
"--display-name",
"my-qwen-plus",
"--dry-run",
"--output",
@@ -68,9 +68,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
"deploy",
"text",
"create",
"--model",
"--model-name",
"qwen3-8b",
"--name",
"--display-name",
"my-qwen3-mu",
"--plan",
"mu",
@@ -102,9 +102,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
"deploy",
"audio",
"create",
"--model",
"--model-name",
"my-cosyvoice-ft",
"--name",
"--display-name",
"my-tts",
"--dry-run",
"--output",
@@ -31,7 +31,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--model|--datasets/i);
expect(stderr).toMatch(/--base-model|--datasets/i);
});
test("finetune create --dry-run 构造 SFT 默认请求体", async () => {
@@ -39,7 +39,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
"file-aaa,file-bbb",
@@ -73,7 +73,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
"file-aaa",
@@ -135,7 +135,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
"file-aaa",
@@ -157,7 +157,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
"file-aaa",
@@ -176,7 +176,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
`${localPath},file-bbb`,
@@ -207,7 +207,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
" , ",
@@ -228,7 +228,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
localPath,
@@ -250,7 +250,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
localPath,
@@ -272,7 +272,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
["cancel", ["--job-id", "ft-xxx"]],
["delete", ["--job-id", "ft-xxx"]],
["watch", ["--job-id", "ft-xxx"]],
["capability", ["--model", "qwen3-8b"]],
["capability", ["--base-model", "qwen3-8b"]],
])("finetune %s --dry-run 发出结构化动作", async (sub, extra) => {
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
"finetune",
@@ -292,7 +292,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"text",
"create",
"--model",
"--base-model",
"qwen3-8b",
"--datasets",
" file-a , ,file-b ",
@@ -314,7 +314,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"audio",
"create",
"--model",
"--base-model",
"cosyvoice-v3-flash",
"--datasets",
"file-audio",
@@ -343,7 +343,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--model|--datasets/i);
expect(stderr).toMatch(/--base-model|--datasets/i);
expect(stderr).not.toMatch(/--training-type|--n-epochs|--batch-size|--max-length/);
});
@@ -352,7 +352,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
"finetune",
"image",
"create",
"--model",
"--base-model",
"wan2.7-image-pro",
"--datasets",
"file-image",
+3
View File
@@ -43,6 +43,8 @@ export interface ListFineTunesParams {
pageNo?: number;
pageSize?: number;
status?: string;
/** Filter by base model ID (server-side). */
model?: string;
signal?: AbortSignal;
}
@@ -55,6 +57,7 @@ export async function listFineTunes(
if (params.pageNo !== undefined) qs.set("page_no", String(params.pageNo));
if (params.pageSize !== undefined) qs.set("page_size", String(params.pageSize));
if (params.status) qs.set("status", params.status);
if (params.model) qs.set("model", params.model);
const base = finetuneJobsPath();
const path = qs.toString() ? `${base}?${qs.toString()}` : base;
return client.requestJson<ListFineTunesResponse>({
+1
View File
@@ -3,3 +3,4 @@ export * from "./api.ts";
export * from "./capability.ts";
export * from "./preflight.ts";
export * from "./profiles/index.ts";
export * from "./price.ts";
+121
View File
@@ -0,0 +1,121 @@
/**
* Training price estimation via the **console gateway**.
*
* These are console-domain APIs (`zeldaEasy.broadscope-platform.*`); commands
* using them must declare `auth: "console"`. Two different argument wrappers
* exist: `getModelPrice` takes a top-level `query`, while the token-estimation
* APIs take a top-level `input`.
*/
import type { Client } from "../client/client.ts";
import { unwrapResponse } from "../console/models.ts";
// ---------------------------------------------------------------------------
// API names
// ---------------------------------------------------------------------------
export const TRAINING_MODEL_PRICE_API = "zeldaEasy.broadscope-platform.modelCenter.getModelPrice";
export const CALC_DATASETS_TOKENS_API =
"zeldaEasy.broadscope-platform.modelInstance.calculateDatasetsTotalTokens";
export const ESTIMATE_FINETUNE_TOKENS_API =
"zeldaEasy.broadscope-platform.modelInstance.estimateFinetuneTokens";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface TrainingModelPrice {
price?: string;
priceUnit?: string;
modelId?: string;
[key: string]: unknown;
}
export interface TokenEstimate {
estimatedDatasetConsumedTokensMinPerEpoch?: number;
estimatedDatasetConsumedTokensMaxPerEpoch?: number;
estimatedMixedConsumedTokensMinPerEpoch?: number;
estimatedMixedConsumedTokensMaxPerEpoch?: number;
[key: string]: unknown;
}
// ---------------------------------------------------------------------------
// API wrappers
// ---------------------------------------------------------------------------
/**
* Training unit price for a model. `price` is denominated in `priceUnit`
* (typically "千Token" — yuan per 1000 tokens).
*/
export async function fetchTrainingModelPrice(
client: Client,
modelId: string,
): Promise<TrainingModelPrice> {
const raw = await client.console<Record<string, unknown>>(TRAINING_MODEL_PRICE_API, {
query: { type: 0, modelId },
});
return unwrapResponse(raw) as TrainingModelPrice;
}
/**
* Estimate training tokens for SFT / DPO jobs.
* Returns a per-epoch min/max range; multiply by `n_epochs` for the total.
*/
export async function estimateSftDpoTokens(
client: Client,
datasetIds: string[],
hyperParams: { nEpochs: number; batchSize: number; maxLength: number },
): Promise<TokenEstimate> {
const raw = await client.console<Record<string, unknown>>(CALC_DATASETS_TOKENS_API, {
input: { trainDatasetIds: datasetIds, hyperParams },
});
return unwrapResponse(raw) as TokenEstimate;
}
/**
* Estimate training tokens for CPT jobs.
*
* The console API requires `hyperParams` as a **JSON string** with a full
* `userDefinedObj` payload (captured from the console frontend), plus several
* top-level fields (`algorithmType`, `bizType`, `priority`, …). Only
* `n_epochs` / `max_length` materially affect the estimate; the remaining
* hyper-parameters are fixed defaults.
*/
export async function estimateCptTokens(
client: Client,
model: string,
datasetIdsCsv: string,
nEpochs: number,
): Promise<TokenEstimate> {
const userDefinedObj = {
batch_size: 16,
eval_steps: 50,
learning_rate: "7e-6",
lr_scheduler_type: "linear",
max_length: 8192,
n_epochs: nEpochs,
split: 0.9,
save_total_limit: "3",
resume_from_checkpoint: false,
save_strategy: "epoch",
};
const hyperParams = JSON.stringify({
useDefault: false,
userDefinedObj,
useQwenMixedStrategy: false,
});
const raw = await client.console<Record<string, unknown>>(ESTIMATE_FINETUNE_TOKENS_API, {
input: {
trainingType: "cpt",
instanceName: `${model}_cli_estimate`,
algorithmType: 100,
bizType: 100,
trainDatasetIds: datasetIdsCsv,
hyperParams,
bailianTrainModel: model,
validationDatasetIds: "",
jobName: `${model}_cli_estimate`,
priority: "L0",
},
});
return unwrapResponse(raw) as TokenEstimate;
}
+5 -5
View File
@@ -23,14 +23,14 @@ description: >-
```
1. Validate data bl dataset validate --file train.jsonl [--schema chatml|dpo|cpt|tts|image]
2. Upload data bl dataset upload --file train.jsonl # returns a file-id
3. Create job bl finetune text|audio|image create --model <base> --datasets <file-id|path>
3. Create job bl finetune text|audio|image create --base-model <base> --datasets <file-id|path>
4. Watch progress bl finetune watch --job-id ft-xxx # or get / logs
5. Pick artifact bl finetune checkpoints --job-id ft-xxx
6. Export model bl finetune export --job-id ft-xxx --checkpoint ckpt-N --model-name my-model
7. Deploy service bl deploy text|audio|image create --model my-model --name my-svc
7. Deploy service bl deploy text|audio|image create --model-name my-model --display-name my-svc
```
- Unsure which training methods a base model supports → `bl finetune capability --model <base>` or `--training-type sft|sft-lora|dpo|cpt`.
- Unsure which training methods a base model supports → `bl finetune capability --base-model <base>` or `--training-type sft|sft-lora|dpo|cpt`.
- Text `--training-type` values: `sft` / `sft-lora` / `dpo` / `dpo-lora` / `cpt`. Audio bases include `cosyvoice-v3-flash`; image bases include `wan2.7-image-pro`.
- Deployment plans: audio defaults to `--plan mu`; text/image default to `lora`.
- Preview write operations (create / delete / cancel / scale) with `--dry-run` first, and confirm with the user before deleting a job or dataset.
@@ -55,10 +55,10 @@ Flags, usage, and examples: see [`reference/`](reference/index.md) or `bl <comma
```bash
bl dataset validate --file train.jsonl
bl dataset upload --file train.jsonl
bl finetune text create --model qwen3-8b --training-type sft-lora --datasets file-xxx
bl finetune text create --base-model qwen3-8b --training-type sft-lora --datasets file-xxx
bl finetune watch --job-id ft-xxx
bl finetune export --job-id ft-xxx --checkpoint ckpt-3 --model-name my-qwen-sft
bl deploy text create --model my-qwen-sft --name my-svc
bl deploy text create --model-name my-qwen-sft --display-name my-svc
```
## Common hand-offs
+79 -88
View File
@@ -25,27 +25,27 @@ Index: [index.md](index.md)
### `bl deploy audio create`
| Field | Value |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `deploy audio create` |
| **Description** | Create an audio (TTS) model deployment |
| **Usage** | `bl deploy audio create --model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
| Field | Value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Name** | `deploy audio create` |
| **Description** | Create an audio (TTS) model deployment |
| **Usage** | `bl deploy audio create --model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
#### Flags
| Flag | Type | Required | Description |
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `--model <name>` | string | yes | Model name (catalog model or fine-tuned output) (required) |
| `--name <display_name>` | string | yes | Console display name for the deployment (required) |
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
| Flag | Type | Required | Description |
| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `--model-name <model_name>` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) |
| `--display-name <display_name>` | string | yes | Console display name for the deployment (required) |
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
@@ -60,27 +60,24 @@ Index: [index.md](index.md)
- Use `bl deploy models --source base` to inspect available templates.
- After creation, status starts at PENDING and transitions to RUNNING.
- Invoke the deployed model with: bl text chat --model <deployed_model>
- WARNING: --model is overloaded across commands and refers to DIFFERENT
- values. `bl deploy <modality> create --model` takes the exported model_name
- (e.g. `qwen3-8b-ft-...`), but the create response also returns a
- `deployed_model` field (the deployment instance id, e.g.
- `qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use
- the `deployed_model` from the create response — NOT the `model_name` you
- passed to `deploy <modality> create`. Do not reuse the value across the two
- commands.
- NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).
- The create response also returns a `deployed_model` field — the deployment
- instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference
- (`bl text chat --model <deployed_model>`) and lifecycle commands
- (`deploy get/scale/pause/resume/delete --deployed-model <id>`).
#### Examples
```bash
bl deploy audio create --model my-cosyvoice-ft --name my-tts
bl deploy audio create --model-name my-cosyvoice-ft --display-name my-tts
```
```bash
bl deploy audio create --model my-cosyvoice-ft --name my-tts --deploy-spec dps-xxxx --capacity 1
bl deploy audio create --model-name my-cosyvoice-ft --display-name my-tts --deploy-spec dps-xxxx --capacity 1
```
```bash
bl deploy audio create --model my-cosyvoice-ft --name my-tts --dry-run
bl deploy audio create --model-name my-cosyvoice-ft --display-name my-tts --dry-run
```
### `bl deploy delete`
@@ -138,27 +135,27 @@ bl deploy get --deployed-model qwen-plus-2025-12-01-b6d61c71 --output json
### `bl deploy image create`
| Field | Value |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `deploy image create` |
| **Description** | Create an image generation model deployment |
| **Usage** | `bl deploy image create --model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
| Field | Value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Name** | `deploy image create` |
| **Description** | Create an image generation model deployment |
| **Usage** | `bl deploy image create --model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
#### Flags
| Flag | Type | Required | Description |
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `--model <name>` | string | yes | Model name (catalog model or fine-tuned output) (required) |
| `--name <display_name>` | string | yes | Console display name for the deployment (required) |
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
| Flag | Type | Required | Description |
| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `--model-name <model_name>` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) |
| `--display-name <display_name>` | string | yes | Console display name for the deployment (required) |
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
@@ -173,27 +170,24 @@ bl deploy get --deployed-model qwen-plus-2025-12-01-b6d61c71 --output json
- Use `bl deploy models --source base` to inspect available templates.
- After creation, status starts at PENDING and transitions to RUNNING.
- Invoke the deployed model with: bl text chat --model <deployed_model>
- WARNING: --model is overloaded across commands and refers to DIFFERENT
- values. `bl deploy <modality> create --model` takes the exported model_name
- (e.g. `qwen3-8b-ft-...`), but the create response also returns a
- `deployed_model` field (the deployment instance id, e.g.
- `qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use
- the `deployed_model` from the create response — NOT the `model_name` you
- passed to `deploy <modality> create`. Do not reuse the value across the two
- commands.
- NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).
- The create response also returns a `deployed_model` field — the deployment
- instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference
- (`bl text chat --model <deployed_model>`) and lifecycle commands
- (`deploy get/scale/pause/resume/delete --deployed-model <id>`).
#### Examples
```bash
bl deploy image create --model my-wan-ft --name my-wan
bl deploy image create --model-name my-wan-ft --display-name my-wan
```
```bash
bl deploy image create --model my-wan-ft --name my-wan-mu --plan mu
bl deploy image create --model-name my-wan-ft --display-name my-wan-mu --plan mu
```
```bash
bl deploy image create --model my-wan-ft --name my-wan --dry-run
bl deploy image create --model-name my-wan-ft --display-name my-wan --dry-run
```
### `bl deploy list`
@@ -372,27 +366,27 @@ bl deploy scale --deployed-model dep-... --capacity 2
### `bl deploy text create`
| Field | Value |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `deploy text create` |
| **Description** | Create a text model deployment |
| **Usage** | `bl deploy text create --model <model_name> --name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
| Field | Value |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `deploy text create` |
| **Description** | Create a text model deployment |
| **Usage** | `bl deploy text create --model-name <model_name> --display-name <display_name> [--plan <plan>] [--deploy-spec <id>] [--capacity <n>] [--billing-method <m>] [--input-tpm <n>] [--output-tpm <n>] [--thinking-output-tpm <n>]` |
#### Flags
| Flag | Type | Required | Description |
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `--model <name>` | string | yes | Model name (catalog model or fine-tuned output) (required) |
| `--name <display_name>` | string | yes | Console display name for the deployment (required) |
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
| Flag | Type | Required | Description |
| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `--model-name <model_name>` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) |
| `--display-name <display_name>` | string | yes | Console display name for the deployment (required) |
| `--plan <plan>` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu |
| `--deploy-spec <id>` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) |
| `--capacity <n>` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) |
| `--billing-method <m>` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) |
| `--input-tpm <n>` | number | no | PTU max input tokens/min (required for plan=ptu) |
| `--output-tpm <n>` | number | no | PTU max output tokens/min (required for plan=ptu) |
| `--thinking-output-tpm <n>` | number | no | PTU max thinking-output tokens/min (optional, some models) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
@@ -407,31 +401,28 @@ bl deploy scale --deployed-model dep-... --capacity 2
- Use `bl deploy models --source base` to inspect available templates.
- After creation, status starts at PENDING and transitions to RUNNING.
- Invoke the deployed model with: bl text chat --model <deployed_model>
- WARNING: --model is overloaded across commands and refers to DIFFERENT
- values. `bl deploy <modality> create --model` takes the exported model_name
- (e.g. `qwen3-8b-ft-...`), but the create response also returns a
- `deployed_model` field (the deployment instance id, e.g.
- `qwen3-8b-5ecb5f068d79`). The inference call `bl text chat --model` must use
- the `deployed_model` from the create response — NOT the `model_name` you
- passed to `deploy <modality> create`. Do not reuse the value across the two
- commands.
- NOTE: --model-name is the model being deployed (e.g. `qwen3-8b-ft-...`).
- The create response also returns a `deployed_model` field — the deployment
- instance id (e.g. `qwen3-8b-5ecb5f068d79`). Use that id for inference
- (`bl text chat --model <deployed_model>`) and lifecycle commands
- (`deploy get/scale/pause/resume/delete --deployed-model <id>`).
#### Examples
```bash
bl deploy text create --model my-qwen-sft --name my-sft-test
bl deploy text create --model-name my-qwen-sft --display-name my-sft-test
```
```bash
bl deploy text create --model qwen3.6-flash-2026-04-16 --name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000
bl deploy text create --model-name qwen3.6-flash-2026-04-16 --display-name my-flash --plan ptu --input-tpm 10000 --output-tpm 1000
```
```bash
bl deploy text create --model qwen3-8b --name my-qwen3-mu --plan mu
bl deploy text create --model-name qwen3-8b --display-name my-qwen3-mu --plan mu
```
```bash
bl deploy text create --model qwen3-8b --name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2
bl deploy text create --model-name qwen3-8b --display-name my-qwen3 --plan mu --deploy-spec MU1 --capacity 2
```
### `bl deploy update`
+102 -55
View File
@@ -19,6 +19,7 @@ Index: [index.md](index.md)
| `bl finetune image create` | Create an image generation model fine-tune job (sft-lora) |
| `bl finetune list` | List fine-tune jobs |
| `bl finetune logs` | Fetch training logs for a fine-tune job |
| `bl finetune price` | Estimate the training cost for a fine-tune job (token billing) |
| `bl finetune text create` | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) |
| `bl finetune watch` | Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal. |
@@ -26,17 +27,17 @@ Index: [index.md](index.md)
### `bl finetune audio create`
| Field | Value |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `finetune audio create` |
| **Description** | Create an audio TTS model fine-tune job (sft-lora) |
| **Usage** | `bl finetune audio create --model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>]` |
| Field | Value |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `finetune audio create` |
| **Description** | Create an audio TTS model fine-tune job (sft-lora) |
| **Usage** | `bl finetune audio create --base-model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>]` |
#### Flags
| Flag | Type | Required | Description |
| ---------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--model <model>` | string | yes | Base model to fine-tune |
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
| `--model-name <name>` | string | no | Output model name (after training) |
@@ -58,23 +59,23 @@ Index: [index.md](index.md)
#### Examples
```bash
bl finetune audio create --model cosyvoice-v3-flash --datasets ./audio.zip
bl finetune audio create --base-model cosyvoice-v3-flash --datasets ./audio.zip
```
```bash
bl finetune audio create --model cosyvoice-v3-flash --datasets file-xxx
bl finetune audio create --base-model cosyvoice-v3-flash --datasets file-xxx
```
```bash
bl finetune audio create --model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts
bl finetune audio create --base-model cosyvoice-v3-flash --datasets ./audio.zip --model-name my-tts
```
```bash
bl finetune audio create --model cosyvoice-v3-flash --datasets file-xxx --output json
bl finetune audio create --base-model cosyvoice-v3-flash --datasets file-xxx --output json
```
```bash
bl finetune audio create --model cosyvoice-v3-flash --datasets ./audio.zip --dry-run
bl finetune audio create --base-model cosyvoice-v3-flash --datasets ./audio.zip --dry-run
```
### `bl finetune cancel`
@@ -114,18 +115,18 @@ bl finetune cancel --job-id ft-xxx --dry-run
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `finetune capability` |
| **Description** | Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it) |
| **Usage** | `bl finetune capability --model <m> \| --training-type <t>` |
| **Usage** | `bl finetune capability --base-model <m> \| --training-type <t>` |
#### Flags
| Flag | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------------------------------------------------------- |
| `--model <m>` | string | no | List training types supported by this base model. |
| `--base-model <m>` | string | no | List training types supported by this base model. |
| `--training-type <t>` | string | no | List models supporting this training type: sft \| sft-lora \| dpo \| dpo-lora \| cpt. |
#### Notes
- Exactly one of --model / --training-type is required.
- Exactly one of --base-model / --training-type is required.
- Training-type values use the `<method>` / `<method>-lora` convention:
- sft | sft-lora | dpo | dpo-lora | cpt. (cpt has no -lora variant server-side.)
- Queries listFoundationModels, a public API — no console login needed.
@@ -133,7 +134,7 @@ bl finetune cancel --job-id ft-xxx --dry-run
#### Examples
```bash
bl finetune capability --model qwen3-8b
bl finetune capability --base-model qwen3-8b
```
```bash
@@ -166,7 +167,7 @@ bl finetune capability --training-type sft --quiet
#### Notes
- `model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model`.
- `model_name` (shown for SUCCEEDED checkpoints) is the direct input for `deploy create --model-name`.
- Checkpoints expire ~15 days after creation; `expire_time` shows the deadline. Export or deploy before expiry.
#### Examples
@@ -268,17 +269,17 @@ bl finetune get --job-id ft-xxx --output json
### `bl finetune image create`
| Field | Value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Name** | `finetune image create` |
| **Description** | Create an image generation model fine-tune job (sft-lora) |
| **Usage** | `bl finetune image create --model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i\|i2i>] [--learning-rate <str>]` |
| Field | Value |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `finetune image create` |
| **Description** | Create an image generation model fine-tune job (sft-lora) |
| **Usage** | `bl finetune image create --base-model <model> --datasets <id\|path> [--validations <id\|path>] [--model-name <name>] [--suffix <text>] [--generation-type <t2i\|i2i>] [--learning-rate <str>]` |
#### Flags
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--model <model>` | string | yes | Base model to fine-tune |
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
| `--model-name <name>` | string | no | Output model name (after training) |
@@ -304,46 +305,47 @@ bl finetune get --job-id ft-xxx --output json
#### Examples
```bash
bl finetune image create --model wan2.7-image-pro --datasets ./images.zip
bl finetune image create --base-model wan2.7-image-pro --datasets ./images.zip
```
```bash
bl finetune image create --model wan2.7-image-pro --datasets file-xxx
bl finetune image create --base-model wan2.7-image-pro --datasets file-xxx
```
```bash
bl finetune image create --model wan2.7-image-pro --datasets file-xxx --generation-type i2i
bl finetune image create --base-model wan2.7-image-pro --datasets file-xxx --generation-type i2i
```
```bash
bl finetune image create --model wan2.7-image-pro --datasets ./images.zip --model-name my-wan
bl finetune image create --base-model wan2.7-image-pro --datasets ./images.zip --model-name my-wan
```
```bash
bl finetune image create --model wan2.7-image-pro --datasets file-xxx --output json
bl finetune image create --base-model wan2.7-image-pro --datasets file-xxx --output json
```
```bash
bl finetune image create --model wan2.7-image-pro --datasets ./images.zip --dry-run
bl finetune image create --base-model wan2.7-image-pro --datasets ./images.zip --dry-run
```
### `bl finetune list`
| Field | Value |
| --------------- | ---------------------------------------------------------------- |
| **Name** | `finetune list` |
| **Description** | List fine-tune jobs |
| **Usage** | `bl finetune list [--page <n>] [--page-size <n>] [--status <s>]` |
| Field | Value |
| --------------- | --------------------------------------------------------------------------------------- |
| **Name** | `finetune list` |
| **Description** | List fine-tune jobs |
| **Usage** | `bl finetune list [--page <n>] [--page-size <n>] [--status <s>] [--base-model <model>]` |
#### Flags
| Flag | Type | Required | Description |
| ------------------ | ------ | -------- | -------------------------------------------------------------------- |
| `--page <n>` | number | no | Page number (default: 1) |
| `--page-size <n>` | number | no | Results per page (default: 10, max 100) |
| `--status <s>` | string | no | Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
| Flag | Type | Required | Description |
| ---------------------- | ------ | -------- | -------------------------------------------------------------------- |
| `--page <n>` | number | no | Page number (default: 1) |
| `--page-size <n>` | number | no | Results per page (default: 10, max 100) |
| `--status <s>` | string | no | Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED) |
| `--base-model <model>` | string | no | Filter by base model ID (server-side) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Examples
@@ -356,7 +358,11 @@ bl finetune list --status RUNNING
```
```bash
bl finetune list --page-size 20 --output json
bl finetune list --base-model qwen3-8b
```
```bash
bl finetune list --page-size 20
```
### `bl finetune logs`
@@ -405,19 +411,60 @@ bl finetune logs --job-id ft-xxx --tail 20
bl finetune logs --job-id ft-xxx --search checkpoint --tail 5
```
### `bl finetune price`
| Field | Value |
| --------------- | --------------------------------------------------------------------------------------------------- |
| **Name** | `finetune price` |
| **Description** | Estimate the training cost for a fine-tune job (token billing) |
| **Usage** | `bl finetune price --base-model <model> --datasets <ids> [--training-type <type>] [--n-epochs <n>]` |
#### Flags
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------ |
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
| `--datasets <ids>` | string | yes | Training dataset file IDs, comma-separated (required) |
| `--training-type <type>` | string | no | Training type: sft \| dpo \| cpt (default: sft) |
| `--n-epochs <n>` | number | no | Number of training epochs (default: 3) |
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
#### Notes
- Estimate only — the server computes token usage from the datasets; final cost is subject to the bill.
- Covers token billing for sft / dpo / cpt. Training-unit (MTU) billing is not supported by this command.
- Hyper-parameters other than --n-epochs are fixed at representative defaults for estimation.
#### Examples
```bash
bl finetune price --base-model qwen3-8b --datasets file-ft-xxx
```
```bash
bl finetune price --base-model qwen3-8b --datasets file-ft-xxx,file-ft-yyy --n-epochs 2
```
```bash
bl finetune price --base-model qwen3-8b --datasets file-ft-xxx --training-type cpt
```
### `bl finetune text create`
| Field | Value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | `finetune text create` |
| **Description** | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) |
| **Usage** | `bl finetune text create --model <model> --datasets <id\|path,...> [--validations <id\|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft\|sft-lora\|dpo\|dpo-lora\|cpt>]` |
| Field | Value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Name** | `finetune text create` |
| **Description** | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) |
| **Usage** | `bl finetune text create --base-model <model> --datasets <id\|path,...> [--validations <id\|path,...>] [--model-name <name>] [--suffix <text>] [--n-epochs <n>] [--batch-size <n>] [--learning-rate <str>] [--max-length <n>] [--training-type <sft\|sft-lora\|dpo\|dpo-lora\|cpt>]` |
#### Flags
| Flag | Type | Required | Description |
| ---------------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--model <model>` | string | yes | Base model to fine-tune |
| `--base-model <model>` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) |
| `--datasets <ids\|paths>` | string | yes | Comma-separated dataset file IDs or local paths (.jsonl for text, .zip for audio/image). Local paths are uploaded (validated) first, then their file-ids are used. |
| `--validations <ids\|paths>` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). |
| `--model-name <name>` | string | no | Output model name (after training) |
@@ -453,35 +500,35 @@ bl finetune logs --job-id ft-xxx --search checkpoint --tail 5
#### Examples
```bash
bl finetune text create --model qwen3-8b --datasets file-xxx
bl finetune text create --base-model qwen3-8b --datasets file-xxx
```
```bash
bl finetune text create --model qwen3-8b --datasets ./train.jsonl
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl
```
```bash
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --validations ./eval.jsonl
```
```bash
bl finetune text create --model qwen3-8b --datasets file-aaa,./extra.jsonl
bl finetune text create --base-model qwen3-8b --datasets file-aaa,./extra.jsonl
```
```bash
bl finetune text create --model qwen3-8b --datasets ./train.jsonl --training-type sft
bl finetune text create --base-model qwen3-8b --datasets ./train.jsonl --training-type sft
```
```bash
bl finetune text create --model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4
bl finetune text create --base-model qwen3-8b --datasets file-xxx --learning-rate "1.6e-5" --n-epochs 4
```
```bash
bl finetune text create --model qwen3-8b --datasets file-xxx --output json
bl finetune text create --base-model qwen3-8b --datasets file-xxx --output json
```
```bash
bl finetune text create --model qwen3-8b --datasets file-xxx --dry-run
bl finetune text create --base-model qwen3-8b --datasets file-xxx --dry-run
```
### `bl finetune watch`
+6 -5
View File
@@ -37,16 +37,17 @@ Use this index for the skill-scoped quick index and global flags.
| `bl finetune image create` | Create an image generation model fine-tune job (sft-lora) | [finetune.md](finetune.md) |
| `bl finetune list` | List fine-tune jobs | [finetune.md](finetune.md) |
| `bl finetune logs` | Fetch training logs for a fine-tune job | [finetune.md](finetune.md) |
| `bl finetune price` | Estimate the training cost for a fine-tune job (token billing) | [finetune.md](finetune.md) |
| `bl finetune text create` | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) | [finetune.md](finetune.md) |
| `bl finetune watch` | Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal. | [finetune.md](finetune.md) |
## By group
| Group | Commands | Reference |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |
| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `pause`, `resume`, `scale`, `text create`, `update` | [deploy.md](deploy.md) |
| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `text create`, `watch` | [finetune.md](finetune.md) |
| Group | Commands | Reference |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |
| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `pause`, `resume`, `scale`, `text create`, `update` | [deploy.md](deploy.md) |
| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `price`, `text create`, `watch` | [finetune.md](finetune.md) |
## Global flags