58 lines
4.4 KiB
JavaScript
58 lines
4.4 KiB
JavaScript
|
|
import { readFileSync } from 'node:fs'
|
||
|
|
import { resolve } from 'node:path'
|
||
|
|
import { fileURLToPath } from 'node:url'
|
||
|
|
import ts from 'typescript'
|
||
|
|
|
||
|
|
const root = resolve(fileURLToPath(new URL('..', import.meta.url)))
|
||
|
|
const source = readFileSync(resolve(root, 'src/views/fine-tune/FineTuneCreateView.vue'), 'utf8')
|
||
|
|
const modelDialogSource = readFileSync(resolve(root, 'src/components/ModelSelectDialog.vue'), 'utf8')
|
||
|
|
const formModelSource = readFileSync(resolve(root, 'src/views/fine-tune/fineTuneFormModel.ts'), 'utf8')
|
||
|
|
|
||
|
|
function assert(condition, message) {
|
||
|
|
if (!condition) {
|
||
|
|
throw new Error(message)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
assert(source.includes(':rows="4"'), '任务描述 textarea should be taller than the original 2 rows')
|
||
|
|
assert(source.includes('gpu-index'), 'GPU cards should use a restrained index label')
|
||
|
|
assert(source.includes('GPU-{{ idx }}'), 'GPU cards should show the GPU number in a compact format')
|
||
|
|
assert(source.includes('gpu-usage-bar'), 'GPU cards should visualize usage with an enterprise-style progress bar')
|
||
|
|
assert(source.includes('is-busy'), 'GPU cards should have a distinct busy state for usage over 80%')
|
||
|
|
assert(source.includes('gpu.gpu_percent > 80'), 'GPU busy state should be driven by usage over 80%')
|
||
|
|
assert(source.includes('modelDialogVisible'), 'Model selection should open a dialog instead of a plain select')
|
||
|
|
assert(!modelDialogSource.includes('width="78vw"'), 'Model dialog should not use the previous oversized 78vw width')
|
||
|
|
assert(modelDialogSource.includes('width="860px"'), 'Model dialog should use a compact enterprise modal width')
|
||
|
|
assert(modelDialogSource.includes('model-series-list'), 'Model dialog should include a model series list column')
|
||
|
|
assert(modelDialogSource.includes('model-version-list'), 'Model dialog should include a snapshot/version list column')
|
||
|
|
assert(modelDialogSource.includes('handleConfirm'), 'Model dialog should confirm the selected model before updating the form')
|
||
|
|
assert(formModelSource.includes('auto_merge: false'), 'Auto merge should default to disabled')
|
||
|
|
assert(source.includes('v-if="form.train_type === \'SFT\'"'), 'Merge model settings should only be visible for SFT')
|
||
|
|
assert(source.includes('content-position="left">合并模型'), 'SFT form should include a merge model section below data configuration')
|
||
|
|
assert(source.includes('v-model="form.auto_merge"'), 'Merge model section should provide an auto merge selector')
|
||
|
|
assert(source.includes('label="自动合并权重并保存"'), 'Auto merge selector should use a clear visible label')
|
||
|
|
assert(formModelSource.includes("auto_merge: form.train_type === 'SFT' && form.auto_merge"), 'Auto merge should be normalized by the shared payload builder')
|
||
|
|
assert(source.includes('const payload = buildFineTunePayload(form, selectedGpus.value)'), 'Create and start should share one normalized payload')
|
||
|
|
assert(source.includes('startFineTune({ ...payload, task_id: taskId })'), 'Start request should reuse the normalized payload')
|
||
|
|
assert(source.includes('Object.assign(form, DEFAULT_TRAINING_PARAMS)'), 'Reset should reuse the canonical defaults')
|
||
|
|
assert(!source.includes('const taskData = {'), 'The duplicated create payload should be removed')
|
||
|
|
assert(!source.includes('const createRes: any'), 'Create response should use the API return type')
|
||
|
|
assert(!source.includes('(check as any).exists'), 'Name-check response should use its API return type')
|
||
|
|
assert(source.includes('任务名校验失败'), 'Name-check failures should be visible and block submission')
|
||
|
|
|
||
|
|
const runnableSource = ts.transpileModule(
|
||
|
|
formModelSource.replace("import type { FineTuneStartPayload, FineTuneTask } from '@/types'", ''),
|
||
|
|
{ compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 } },
|
||
|
|
).outputText
|
||
|
|
const model = await import(`data:text/javascript;base64,${Buffer.from(runnableSource).toString('base64')}`)
|
||
|
|
const defaults = model.createDefaultFineTuneForm()
|
||
|
|
const customized = { ...defaults, train_type: 'DPO', train_method: 'full', auto_merge: true, quantization_bit: 4 }
|
||
|
|
const payload = model.buildFineTunePayload(customized, [0, 2])
|
||
|
|
assert(payload.auto_merge === false, 'Non-SFT tasks must never enable auto merge')
|
||
|
|
assert(payload.quantization_bit === 0, 'Full fine-tuning must never send QLoRA quantization')
|
||
|
|
assert(payload.gpus.join(',') === '0,2', 'Selected GPUs should be preserved in the shared payload')
|
||
|
|
const resetDefaults = { ...model.DEFAULT_TRAINING_PARAMS }
|
||
|
|
assert(resetDefaults.lora_alpha === defaults.lora_alpha, 'Reset and initial defaults must share LoRA values')
|
||
|
|
|
||
|
|
console.log('fine-tune create UI regression checks passed')
|