Skip to content

Commit f66889c

Browse files
committed
feat: update model deploy
1 parent 068ec0b commit f66889c

14 files changed

Lines changed: 1061 additions & 572 deletions

File tree

packages/cli/src/commands.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ import {
5252
datasetGet,
5353
datasetDelete,
5454
datasetValidate,
55-
finetuneCreate,
55+
finetuneTextCreate,
56+
finetuneAudioCreate,
57+
finetuneImageCreate,
5658
finetuneList,
5759
finetuneGet,
5860
finetuneCancel,
@@ -62,7 +64,9 @@ import {
6264
finetuneExport,
6365
finetuneWatch,
6466
finetuneCapability,
65-
deployCreate,
67+
deployTextCreate,
68+
deployAudioCreate,
69+
deployImageCreate,
6670
deployList,
6771
deployGet,
6872
deployModels,
@@ -133,7 +137,9 @@ export const commands: Record<string, AnyCommand> = {
133137
"dataset get": datasetGet,
134138
"dataset delete": datasetDelete,
135139
"dataset validate": datasetValidate,
136-
"finetune create": finetuneCreate,
140+
"finetune text create": finetuneTextCreate,
141+
"finetune audio create": finetuneAudioCreate,
142+
"finetune image create": finetuneImageCreate,
137143
"finetune list": finetuneList,
138144
"finetune get": finetuneGet,
139145
"finetune cancel": finetuneCancel,
@@ -143,7 +149,9 @@ export const commands: Record<string, AnyCommand> = {
143149
"finetune export": finetuneExport,
144150
"finetune watch": finetuneWatch,
145151
"finetune capability": finetuneCapability,
146-
"deploy create": deployCreate,
152+
"deploy text create": deployTextCreate,
153+
"deploy audio create": deployAudioCreate,
154+
"deploy image create": deployImageCreate,
147155
"deploy list": deployList,
148156
"deploy get": deployGet,
149157
"deploy models": deployModels,

packages/cli/tests/e2e/deploy.e2e.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
2222
});
2323

2424
test("deploy create --help 正常退出并展示必填项", async () => {
25-
const { stderr, exitCode } = await runCli(["deploy", "create", "--help"]);
25+
const { stderr, exitCode } = await runCli(["deploy", "text", "create", "--help"]);
2626
expect(exitCode, stderr).toBe(0);
2727
expect(stderr).toMatch(/--model|--name/i);
2828
});
2929

3030
test("deploy create --dry-run 构造 lora 部署请求体", async () => {
3131
const { stdout, stderr, exitCode } = await runCli([
3232
"deploy",
33+
"text",
3334
"create",
3435
"--model",
3536
"qwen-plus-2025-12-01",
@@ -59,6 +60,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
5960
test("deploy create --plan mu --deploy-spec --dry-run 透传 deploy_spec", async () => {
6061
const { stdout, stderr, exitCode } = await runCli([
6162
"deploy",
63+
"text",
6264
"create",
6365
"--model",
6466
"qwen3-8b",
@@ -85,6 +87,28 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
8587
expect(data.body.capacity).toBe(2);
8688
});
8789

90+
test("deploy audio create --dry-run 构造 lora 部署请求体", async () => {
91+
// deploy create does not inspect modality; the split only changes the path.
92+
// The audio subcommand shares the full flag surface and run logic.
93+
const { stdout, stderr, exitCode } = await runCli([
94+
"deploy",
95+
"audio",
96+
"create",
97+
"--model",
98+
"my-cosyvoice-ft",
99+
"--name",
100+
"my-tts",
101+
"--dry-run",
102+
"--output",
103+
"json",
104+
]);
105+
expect(exitCode, stderr).toBe(0);
106+
const data = parseStdoutJson<{ action: string; body: { plan: string; name: string } }>(stdout);
107+
expect(data.action).toBe("deploy.create");
108+
expect(data.body.plan).toBe("lora");
109+
expect(data.body.name).toBe("my-tts");
110+
});
111+
88112
test("deploy scale --dry-run 转发 capacity", async () => {
89113
const { stdout, stderr, exitCode } = await runCli([
90114
"deploy",

packages/cli/tests/e2e/finetune.e2e.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
2323
});
2424

2525
test("finetune create --help 正常退出并展示必填项", async () => {
26-
const { stderr, exitCode } = await runCli(["finetune", "create", "--help"]);
26+
const { stderr, exitCode } = await runCli(["finetune", "text", "create", "--help"]);
2727
expect(exitCode, stderr).toBe(0);
2828
expect(stderr).toMatch(/--model|--datasets/i);
2929
});
3030

3131
test("finetune create --dry-run 构造 SFT 默认请求体", async () => {
3232
const { stdout, stderr, exitCode } = await runCli([
3333
"finetune",
34+
"text",
3435
"create",
3536
"--model",
3637
"qwen3-8b",
@@ -64,6 +65,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
6465
test("finetune create --dry-run 转发训练类型与超参", async () => {
6566
const { stdout, stderr, exitCode } = await runCli([
6667
"finetune",
68+
"text",
6769
"create",
6870
"--model",
6971
"qwen3-8b",
@@ -125,6 +127,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
125127
async (cliType, serverType) => {
126128
const { stdout, stderr, exitCode } = await runCli([
127129
"finetune",
130+
"text",
128131
"create",
129132
"--model",
130133
"qwen3-8b",
@@ -146,6 +149,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
146149
test("finetune create --training-type 拒绝不支持的训练类型值", async () => {
147150
const { stdout, stderr, exitCode } = await runCli([
148151
"finetune",
152+
"text",
149153
"create",
150154
"--model",
151155
"qwen3-8b",
@@ -164,6 +168,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
164168
const localPath = join(cliPackageRoot, "tests", "e2e", ".dataset-valid.jsonl");
165169
const { stdout, stderr, exitCode } = await runCli([
166170
"finetune",
171+
"text",
167172
"create",
168173
"--model",
169174
"qwen3-8b",
@@ -194,6 +199,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
194199
test("finetune create --datasets 为空时拒绝", async () => {
195200
const { stdout, stderr, exitCode } = await runCli([
196201
"finetune",
202+
"text",
197203
"create",
198204
"--model",
199205
"qwen3-8b",
@@ -214,6 +220,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
214220
const localPath = join(cliPackageRoot, "tests", "e2e", ".dataset-valid.jsonl");
215221
const { stdout, stderr, exitCode } = await runCli([
216222
"finetune",
223+
"text",
217224
"create",
218225
"--model",
219226
"qwen3-8b",
@@ -235,6 +242,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
235242
const localPath = join(cliPackageRoot, "tests", "e2e", ".dataset-valid.jsonl");
236243
const { stdout, stderr, exitCode } = await runCli([
237244
"finetune",
245+
"text",
238246
"create",
239247
"--model",
240248
"qwen3-8b",
@@ -276,6 +284,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
276284
test("finetune create --dry-run 解析多 datasets 中的空白", async () => {
277285
const { stdout, stderr, exitCode } = await runCli([
278286
"finetune",
287+
"text",
279288
"create",
280289
"--model",
281290
"qwen3-8b",
@@ -291,6 +300,60 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
291300
}>(stdout);
292301
expect(data.body.training_file_ids).toEqual(["file-a", "file-b"]);
293302
});
303+
304+
test("finetune audio create --dry-run 用 sft-lora 默认 + audio 超参", async () => {
305+
// Audio has no --training-type flag; the command fixes modality=audio and
306+
// defaults to sft-lora (efficient_sft) with the fixed CosyVoice hyper-params.
307+
const { stdout, stderr, exitCode } = await runCli([
308+
"finetune",
309+
"audio",
310+
"create",
311+
"--model",
312+
"cosyvoice-v3-flash",
313+
"--datasets",
314+
"file-audio",
315+
"--dry-run",
316+
"--output",
317+
"json",
318+
]);
319+
expect(exitCode, stderr).toBe(0);
320+
const data = parseStdoutJson<{
321+
action: string;
322+
body: { training_type: string; hyper_parameters: Record<string, unknown> };
323+
}>(stdout);
324+
expect(data.action).toBe("finetune.create");
325+
expect(data.body.training_type).toBe("efficient_sft");
326+
// Audio TTS defaults are fixed (not the text n_epochs/batch_size surface).
327+
expect(data.body.hyper_parameters.lm_max_epoch).toBeDefined();
328+
});
329+
330+
test("finetune audio create --help 不暴露文本超参 flag", async () => {
331+
// Audio models don't consume --training-type / --n-epochs / --batch-size /
332+
// --learning-rate / --max-length, so those flags are not offered.
333+
const { stderr, exitCode } = await runCli(["finetune", "audio", "create", "--help"]);
334+
expect(exitCode, stderr).toBe(0);
335+
expect(stderr).toMatch(/--model|--datasets/i);
336+
expect(stderr).not.toMatch(/--training-type|--n-epochs|--batch-size|--max-length/);
337+
});
338+
339+
test("finetune image create --dry-run 用 sft-lora 默认", async () => {
340+
const { stdout, stderr, exitCode } = await runCli([
341+
"finetune",
342+
"image",
343+
"create",
344+
"--model",
345+
"wan2.7-image-pro",
346+
"--datasets",
347+
"file-image",
348+
"--dry-run",
349+
"--output",
350+
"json",
351+
]);
352+
expect(exitCode, stderr).toBe(0);
353+
const data = parseStdoutJson<{ action: string; body: { training_type: string } }>(stdout);
354+
expect(data.action).toBe("finetune.create");
355+
expect(data.body.training_type).toBe("efficient_sft");
356+
});
294357
});
295358

296359
describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (DashScope)", () => {

0 commit comments

Comments
 (0)