From f30fff90654de3be3b36260cd29fdb30cf5556e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=95=85=E7=92=83?= Date: Thu, 6 Aug 2026 15:08:02 +0800 Subject: [PATCH] feat(finetune): clarify model flags; add price estimate & actual cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 6 +- README.zh.md | 6 +- packages/cli/README.md | 6 +- packages/cli/README.zh.md | 6 +- packages/cli/src/commands.ts | 2 + .../commands/src/commands/deploy/create.ts | 47 +++-- .../src/commands/finetune/capability.ts | 17 +- .../src/commands/finetune/checkpoints.ts | 2 +- .../commands/src/commands/finetune/create.ts | 50 +++--- .../commands/src/commands/finetune/fee.ts | 100 +++++++++++ .../commands/src/commands/finetune/get.ts | 17 +- .../commands/src/commands/finetune/list.ts | 17 +- .../commands/src/commands/finetune/price.ts | 139 +++++++++++++++ .../commands/src/commands/finetune/watch.ts | 35 +++- packages/commands/src/index.ts | 1 + .../commands/tests/e2e/deploy.e2e.test.ts | 14 +- .../commands/tests/e2e/finetune.e2e.test.ts | 28 +-- packages/core/src/finetune/api.ts | 3 + packages/core/src/finetune/index.ts | 1 + packages/core/src/finetune/price.ts | 121 +++++++++++++ skills/bailian-finetune/SKILL.md | 10 +- skills/bailian-finetune/reference/deploy.md | 167 +++++++++--------- skills/bailian-finetune/reference/finetune.md | 157 ++++++++++------ skills/bailian-finetune/reference/index.md | 11 +- 24 files changed, 707 insertions(+), 256 deletions(-) create mode 100644 packages/commands/src/commands/finetune/fee.ts create mode 100644 packages/commands/src/commands/finetune/price.ts create mode 100644 packages/core/src/finetune/price.ts diff --git a/README.md b/README.md index 1e869d1..719e94f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.zh.md b/README.zh.md index b5f2b41..df8a1de 100644 --- a/README.zh.md +++ b/README.zh.md @@ -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 # 浏览模型系列与价格信息 diff --git a/packages/cli/README.md b/packages/cli/README.md index 1e869d1..719e94f 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -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 diff --git a/packages/cli/README.zh.md b/packages/cli/README.zh.md index b5f2b41..df8a1de 100644 --- a/packages/cli/README.zh.md +++ b/packages/cli/README.zh.md @@ -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 # 浏览模型系列与价格信息 diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index bd18f4d..ebd8a62 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -71,6 +71,7 @@ import { finetuneExport, finetuneWatch, finetuneCapability, + finetunePrice, deployTextCreate, deployAudioCreate, deployImageCreate, @@ -191,6 +192,7 @@ export const commands: Record = { "finetune export": finetuneExport, "finetune watch": finetuneWatch, "finetune capability": finetuneCapability, + "finetune price": finetunePrice, "deploy text create": deployTextCreate, "deploy audio create": deployAudioCreate, "deploy image create": deployImageCreate, diff --git a/packages/commands/src/commands/deploy/create.ts b/packages/commands/src/commands/deploy/create.ts index 15f6b2a..de468fc 100644 --- a/packages/commands/src/commands/deploy/create.ts +++ b/packages/commands/src/commands/deploy/create.ts @@ -13,13 +13,13 @@ import { import { emitResult, emitBare } from "bailian-cli-runtime"; const CREATE_FLAGS = { - model: { + modelName: { type: "string", - valueHint: "", - description: "Model name (catalog model or fine-tuned output) (required)", + valueHint: "", + description: "Model to deploy — fine-tuned output name or catalog model (required)", required: true, }, - name: { + displayName: { type: "string", valueHint: "", description: "Console display name for the deployment (required)", @@ -63,7 +63,7 @@ const CREATE_FLAGS = { } satisfies FlagsDef; const CREATE_USAGE = - "--model --name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]"; + "--model-name --display-name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]"; 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 ", - "WARNING: --model is overloaded across commands and refers to DIFFERENT", - "values. `bl deploy 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 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 `) and lifecycle commands", + "(`deploy get/scale/pause/resume/delete --deployed-model `).", ]; /** @@ -118,8 +115,8 @@ async function runCreate( ctx: CommandContext, ): Promise { 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), diff --git a/packages/commands/src/commands/finetune/capability.ts b/packages/commands/src/commands/finetune/capability.ts index e9091a9..7ab81c7 100644 --- a/packages/commands/src/commands/finetune/capability.ts +++ b/packages/commands/src/commands/finetune/capability.ts @@ -43,7 +43,7 @@ async function fetchAllFoundationModels(settings: Settings): Promise", 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 | --training-type ", + usageArgs: "--base-model | --training-type ", 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 `` / `-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) { diff --git a/packages/commands/src/commands/finetune/checkpoints.ts b/packages/commands/src/commands/finetune/checkpoints.ts index 769cf72..190110d 100644 --- a/packages/commands/src/commands/finetune/checkpoints.ts +++ b/packages/commands/src/commands/finetune/checkpoints.ts @@ -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) { diff --git a/packages/commands/src/commands/finetune/create.ts b/packages/commands/src/commands/finetune/create.ts index f9bbaf0..d6496f6 100644 --- a/packages/commands/src/commands/finetune/create.ts +++ b/packages/commands/src/commands/finetune/create.ts @@ -215,10 +215,10 @@ type CommandModality = "text" | "audio" | "image"; * output. Every modality's model consumes these. */ const COMMON_FLAGS = { - model: { + baseModel: { type: "string", valueHint: "", - 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 --datasets [--validations ] [--model-name ] [--suffix ] [--n-epochs ] [--batch-size ] [--learning-rate ] [--max-length ] [--training-type ]"; + "--base-model --datasets [--validations ] [--model-name ] [--suffix ] [--n-epochs ] [--batch-size ] [--learning-rate ] [--max-length ] [--training-type ]"; const AUDIO_USAGE = - "--model --datasets [--validations ] [--model-name ] [--suffix ]"; + "--base-model --datasets [--validations ] [--model-name ] [--suffix ]"; const IMAGE_USAGE = - "--model --datasets [--validations ] [--model-name ] [--suffix ] [--generation-type ] [--learning-rate ]"; + "--base-model --datasets [--validations ] [--model-name ] [--suffix ] [--generation-type ] [--learning-rate ]"; const COMMON_NOTES = [ "Creating a job uploads any local datasets and consumes training quota.", @@ -382,7 +382,7 @@ async function runCreate( ): Promise { const { identity, settings } = ctx; const flags = ctx.flags as Record; - 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), diff --git a/packages/commands/src/commands/finetune/fee.ts b/packages/commands/src/commands/finetune/fee.ts new file mode 100644 index 0000000..7559524 --- /dev/null +++ b/packages/commands/src/commands/finetune/fee.ts @@ -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 { + 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); + const list = (responseData.list as Record[]) ?? []; + // The response is grouped; find the exact model in items. + for (const group of list) { + const items = (group.items as Record[]) ?? []; + 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 { + 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; + } +} diff --git a/packages/commands/src/commands/finetune/get.ts b/packages/commands/src/commands/finetune/get.ts index 05d96fb..0ec4d5a 100644 --- a/packages/commands/src/commands/finetune/get.ts +++ b/packages/commands/src/commands/finetune/get.ts @@ -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 = { 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"); }, }); diff --git a/packages/commands/src/commands/finetune/list.ts b/packages/commands/src/commands/finetune/list.ts index 529a5fd..d37fc5b 100644 --- a/packages/commands/src/commands/finetune/list.ts +++ b/packages/commands/src/commands/finetune/list.ts @@ -13,26 +13,35 @@ const LIST_FLAGS = { valueHint: "", description: "Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED)", }, + baseModel: { + type: "string", + valueHint: "", + description: "Filter by base model ID (server-side)", + }, } satisfies FlagsDef; export default defineCommand({ description: "List fine-tune jobs", auth: "apiKey", - usageArgs: "[--page ] [--page-size ] [--status ]", + usageArgs: "[--page ] [--page-size ] [--status ] [--base-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; diff --git a/packages/commands/src/commands/finetune/price.ts b/packages/commands/src/commands/finetune/price.ts new file mode 100644 index 0000000..32dd756 --- /dev/null +++ b/packages/commands/src/commands/finetune/price.ts @@ -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: "", + description: "Base model to fine-tune (e.g. qwen3-8b; not the output model name)", + required: true, + }, + datasets: { + type: "string", + valueHint: "", + description: "Training dataset file IDs, comma-separated (required)", + required: true, + }, + trainingType: { + type: "string", + valueHint: "", + description: "Training type: sft | dpo | cpt (default: sft)", + }, + nEpochs: { + type: "number", + valueHint: "", + 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 --datasets [--training-type ] [--n-epochs ]", + 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", + ); + }, +}); diff --git a/packages/commands/src/commands/finetune/watch.ts b/packages/commands/src/commands/finetune/watch.ts index 8baf738..eb802a5 100644 --- a/packages/commands/src/commands/finetune/watch.ts +++ b/packages/commands/src/commands/finetune/watch.ts @@ -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 = { + 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 = { ...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( diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 63a1316..848fcb7 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -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, diff --git a/packages/commands/tests/e2e/deploy.e2e.test.ts b/packages/commands/tests/e2e/deploy.e2e.test.ts index 6fb5ded..47d5b81 100644 --- a/packages/commands/tests/e2e/deploy.e2e.test.ts +++ b/packages/commands/tests/e2e/deploy.e2e.test.ts @@ -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", diff --git a/packages/commands/tests/e2e/finetune.e2e.test.ts b/packages/commands/tests/e2e/finetune.e2e.test.ts index 59e9374..a045b2e 100644 --- a/packages/commands/tests/e2e/finetune.e2e.test.ts +++ b/packages/commands/tests/e2e/finetune.e2e.test.ts @@ -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", diff --git a/packages/core/src/finetune/api.ts b/packages/core/src/finetune/api.ts index 1126c9d..757c044 100644 --- a/packages/core/src/finetune/api.ts +++ b/packages/core/src/finetune/api.ts @@ -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({ diff --git a/packages/core/src/finetune/index.ts b/packages/core/src/finetune/index.ts index e0b064b..3b82e58 100644 --- a/packages/core/src/finetune/index.ts +++ b/packages/core/src/finetune/index.ts @@ -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"; diff --git a/packages/core/src/finetune/price.ts b/packages/core/src/finetune/price.ts new file mode 100644 index 0000000..64651ae --- /dev/null +++ b/packages/core/src/finetune/price.ts @@ -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 { + const raw = await client.console>(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 { + const raw = await client.console>(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 { + 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>(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; +} diff --git a/skills/bailian-finetune/SKILL.md b/skills/bailian-finetune/SKILL.md index b647c8d..d218702 100644 --- a/skills/bailian-finetune/SKILL.md +++ b/skills/bailian-finetune/SKILL.md @@ -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 --datasets +3. Create job bl finetune text|audio|image create --base-model --datasets 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 ` or `--training-type sft|sft-lora|dpo|cpt`. +- Unsure which training methods a base model supports → `bl finetune capability --base-model ` 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 --name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]` | +| Field | Value | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Name** | `deploy audio create` | +| **Description** | Create an audio (TTS) model deployment | +| **Usage** | `bl deploy audio create --model-name --display-name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]` | #### Flags -| Flag | Type | Required | Description | -| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- | -| `--model ` | string | yes | Model name (catalog model or fine-tuned output) (required) | -| `--name ` | string | yes | Console display name for the deployment (required) | -| `--plan ` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu | -| `--deploy-spec ` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) | -| `--capacity ` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) | -| `--billing-method ` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) | -| `--input-tpm ` | number | no | PTU max input tokens/min (required for plan=ptu) | -| `--output-tpm ` | number | no | PTU max output tokens/min (required for plan=ptu) | -| `--thinking-output-tpm ` | number | no | PTU max thinking-output tokens/min (optional, some models) | -| `--api-key ` | string | no | API key | -| `--base-url ` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- | +| `--model-name ` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) | +| `--display-name ` | string | yes | Console display name for the deployment (required) | +| `--plan ` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu | +| `--deploy-spec ` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) | +| `--capacity ` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) | +| `--billing-method ` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) | +| `--input-tpm ` | number | no | PTU max input tokens/min (required for plan=ptu) | +| `--output-tpm ` | number | no | PTU max output tokens/min (required for plan=ptu) | +| `--thinking-output-tpm ` | number | no | PTU max thinking-output tokens/min (optional, some models) | +| `--api-key ` | string | no | API key | +| `--base-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 -- WARNING: --model is overloaded across commands and refers to DIFFERENT -- values. `bl deploy 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 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 `) and lifecycle commands +- (`deploy get/scale/pause/resume/delete --deployed-model `). #### 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 --name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]` | +| Field | Value | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Name** | `deploy image create` | +| **Description** | Create an image generation model deployment | +| **Usage** | `bl deploy image create --model-name --display-name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]` | #### Flags -| Flag | Type | Required | Description | -| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- | -| `--model ` | string | yes | Model name (catalog model or fine-tuned output) (required) | -| `--name ` | string | yes | Console display name for the deployment (required) | -| `--plan ` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu | -| `--deploy-spec ` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) | -| `--capacity ` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) | -| `--billing-method ` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) | -| `--input-tpm ` | number | no | PTU max input tokens/min (required for plan=ptu) | -| `--output-tpm ` | number | no | PTU max output tokens/min (required for plan=ptu) | -| `--thinking-output-tpm ` | number | no | PTU max thinking-output tokens/min (optional, some models) | -| `--api-key ` | string | no | API key | -| `--base-url ` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- | +| `--model-name ` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) | +| `--display-name ` | string | yes | Console display name for the deployment (required) | +| `--plan ` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu | +| `--deploy-spec ` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) | +| `--capacity ` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) | +| `--billing-method ` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) | +| `--input-tpm ` | number | no | PTU max input tokens/min (required for plan=ptu) | +| `--output-tpm ` | number | no | PTU max output tokens/min (required for plan=ptu) | +| `--thinking-output-tpm ` | number | no | PTU max thinking-output tokens/min (optional, some models) | +| `--api-key ` | string | no | API key | +| `--base-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 -- WARNING: --model is overloaded across commands and refers to DIFFERENT -- values. `bl deploy 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 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 `) and lifecycle commands +- (`deploy get/scale/pause/resume/delete --deployed-model `). #### 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 --name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]` | +| Field | Value | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `deploy text create` | +| **Description** | Create a text model deployment | +| **Usage** | `bl deploy text create --model-name --display-name [--plan ] [--deploy-spec ] [--capacity ] [--billing-method ] [--input-tpm ] [--output-tpm ] [--thinking-output-tpm ]` | #### Flags -| Flag | Type | Required | Description | -| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------- | -| `--model ` | string | yes | Model name (catalog model or fine-tuned output) (required) | -| `--name ` | string | yes | Console display name for the deployment (required) | -| `--plan ` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu | -| `--deploy-spec ` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) | -| `--capacity ` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) | -| `--billing-method ` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) | -| `--input-tpm ` | number | no | PTU max input tokens/min (required for plan=ptu) | -| `--output-tpm ` | number | no | PTU max output tokens/min (required for plan=ptu) | -| `--thinking-output-tpm ` | number | no | PTU max thinking-output tokens/min (optional, some models) | -| `--api-key ` | string | no | API key | -| `--base-url ` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------------------- | ------ | -------- | ------------------------------------------------------------------------------- | +| `--model-name ` | string | yes | Model to deploy — fine-tuned output name or catalog model (required) | +| `--display-name ` | string | yes | Console display name for the deployment (required) | +| `--plan ` | string | no | Billing plan: lora (default, Token-billed) \| ptu (Token-billed) \| mu | +| `--deploy-spec ` | string | no | Deploy spec (only used by plan=mu; auto-picked if omitted) | +| `--capacity ` | number | no | Resource units (plan=mu only; required by API; defaults to the template's unit) | +| `--billing-method ` | string | no | Billing method (plan=mu only; default "POST_PAY", the only supported value) | +| `--input-tpm ` | number | no | PTU max input tokens/min (required for plan=ptu) | +| `--output-tpm ` | number | no | PTU max output tokens/min (required for plan=ptu) | +| `--thinking-output-tpm ` | number | no | PTU max thinking-output tokens/min (optional, some models) | +| `--api-key ` | string | no | API key | +| `--base-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 -- WARNING: --model is overloaded across commands and refers to DIFFERENT -- values. `bl deploy 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 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 `) and lifecycle commands +- (`deploy get/scale/pause/resume/delete --deployed-model `). #### 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` diff --git a/skills/bailian-finetune/reference/finetune.md b/skills/bailian-finetune/reference/finetune.md index 0b05ca0..178adb2 100644 --- a/skills/bailian-finetune/reference/finetune.md +++ b/skills/bailian-finetune/reference/finetune.md @@ -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 --datasets [--validations ] [--model-name ] [--suffix ]` | +| Field | Value | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `finetune audio create` | +| **Description** | Create an audio TTS model fine-tune job (sft-lora) | +| **Usage** | `bl finetune audio create --base-model --datasets [--validations ] [--model-name ] [--suffix ]` | #### Flags | Flag | Type | Required | Description | | ---------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `--model ` | string | yes | Base model to fine-tune | +| `--base-model ` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) | | `--datasets ` | 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 ` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). | | `--model-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 \| --training-type ` | +| **Usage** | `bl finetune capability --base-model \| --training-type ` | #### Flags | Flag | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------------------------------------------------- | -| `--model ` | string | no | List training types supported by this base model. | +| `--base-model ` | string | no | List training types supported by this base model. | | `--training-type ` | 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 `` / `-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 --datasets [--validations ] [--model-name ] [--suffix ] [--generation-type ] [--learning-rate ]` | +| Field | Value | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `finetune image create` | +| **Description** | Create an image generation model fine-tune job (sft-lora) | +| **Usage** | `bl finetune image create --base-model --datasets [--validations ] [--model-name ] [--suffix ] [--generation-type ] [--learning-rate ]` | #### Flags | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--model ` | string | yes | Base model to fine-tune | +| `--base-model ` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) | | `--datasets ` | 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 ` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). | | `--model-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 ] [--page-size ] [--status ]` | +| Field | Value | +| --------------- | --------------------------------------------------------------------------------------- | +| **Name** | `finetune list` | +| **Description** | List fine-tune jobs | +| **Usage** | `bl finetune list [--page ] [--page-size ] [--status ] [--base-model ]` | #### Flags -| Flag | Type | Required | Description | -| ------------------ | ------ | -------- | -------------------------------------------------------------------- | -| `--page ` | number | no | Page number (default: 1) | -| `--page-size ` | number | no | Results per page (default: 10, max 100) | -| `--status ` | string | no | Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED) | -| `--api-key ` | string | no | API key | -| `--base-url ` | string | no | API base URL | +| Flag | Type | Required | Description | +| ---------------------- | ------ | -------- | -------------------------------------------------------------------- | +| `--page ` | number | no | Page number (default: 1) | +| `--page-size ` | number | no | Results per page (default: 10, max 100) | +| `--status ` | string | no | Filter by status (PENDING / RUNNING / SUCCEEDED / FAILED / CANCELED) | +| `--base-model ` | string | no | Filter by base model ID (server-side) | +| `--api-key ` | string | no | API key | +| `--base-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 --datasets [--training-type ] [--n-epochs ]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------------------------ | +| `--base-model ` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) | +| `--datasets ` | string | yes | Training dataset file IDs, comma-separated (required) | +| `--training-type ` | string | no | Training type: sft \| dpo \| cpt (default: sft) | +| `--n-epochs ` | number | no | Number of training epochs (default: 3) | +| `--console-region ` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) | +| `--console-site ` | string | no | Console site: domestic, international | +| `--console-switch-agent ` | number | no | Switch agent UID for delegated access | +| `--workspace-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 --datasets [--validations ] [--model-name ] [--suffix ] [--n-epochs ] [--batch-size ] [--learning-rate ] [--max-length ] [--training-type ]` | +| 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 --datasets [--validations ] [--model-name ] [--suffix ] [--n-epochs ] [--batch-size ] [--learning-rate ] [--max-length ] [--training-type ]` | #### Flags | Flag | Type | Required | Description | | ---------------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--model ` | string | yes | Base model to fine-tune | +| `--base-model ` | string | yes | Base model to fine-tune (e.g. qwen3-8b; not the output model name) | | `--datasets ` | 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 ` | string | no | Comma-separated validation dataset file IDs or local paths (auto-uploaded like --datasets). | | `--model-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` diff --git a/skills/bailian-finetune/reference/index.md b/skills/bailian-finetune/reference/index.md index 07cf63f..3d163eb 100644 --- a/skills/bailian-finetune/reference/index.md +++ b/skills/bailian-finetune/reference/index.md @@ -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