拆分 TrainingTaskOverview 组件与 trainingLogModel 状态模型,TrainingLogView 大幅瘦身;Mock 新增按文件路由的训练日志内容与更真实的 GPU 进程占用数据,adapter 类型收敛为 AxiosAdapter,配套新增 mock 内容回归脚本。
78 lines
3.2 KiB
JavaScript
78 lines
3.2 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { fileURLToPath } from 'node:url'
|
|
import path from 'node:path'
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
const mockDataPath = path.resolve(scriptDir, '../src/mock/data.ts')
|
|
const mockAdapterPath = path.resolve(scriptDir, '../src/mock/adapter.ts')
|
|
const modelPath = path.resolve(scriptDir, '../src/views/system/training-log/trainingLogModel.ts')
|
|
const [mockData, mockAdapter, modelSource] = await Promise.all([
|
|
readFile(mockDataPath, 'utf8'),
|
|
readFile(mockAdapterPath, 'utf8'),
|
|
readFile(modelPath, 'utf8'),
|
|
])
|
|
|
|
function objectFromMarker(source, marker) {
|
|
const markerIndex = source.indexOf(marker)
|
|
assert.notEqual(markerIndex, -1, `未找到 Mock 数据标记:${marker}`)
|
|
const openBrace = source.lastIndexOf('{', markerIndex)
|
|
assert.notEqual(openBrace, -1, `Mock 数据缺少对象起点:${marker}`)
|
|
|
|
let depth = 0
|
|
for (let index = openBrace; index < source.length; index += 1) {
|
|
if (source[index] === '{') depth += 1
|
|
if (source[index] === '}') depth -= 1
|
|
if (depth === 0) return source.slice(openBrace, index + 1)
|
|
}
|
|
|
|
assert.fail(`Mock 数据缺少对象终点:${marker}`)
|
|
}
|
|
|
|
const taskNames = [
|
|
'finance-sft-001',
|
|
'legal-sft-002',
|
|
'medical-cpt-001',
|
|
'service-dpo-001',
|
|
'finance-sft-002',
|
|
'general-sft-001',
|
|
]
|
|
const commonTrainingFields = [
|
|
'output_model_name',
|
|
'batch_size',
|
|
'learning_rate',
|
|
'n_epochs',
|
|
'save_steps',
|
|
'lr_scheduler_type',
|
|
'max_length',
|
|
'warmup_ratio',
|
|
'weight_decay',
|
|
]
|
|
|
|
for (const taskName of taskNames) {
|
|
const task = objectFromMarker(mockData, `name: '${taskName}'`)
|
|
for (const field of commonTrainingFields) {
|
|
assert.match(task, new RegExp(`\\b${field}\\s*:`), `${taskName} 缺少训练参数:${field}`)
|
|
}
|
|
|
|
if (/train_method:\s*'lora'/.test(task)) {
|
|
for (const field of ['lora_rank', 'lora_alpha', 'lora_dropout']) {
|
|
assert.match(task, new RegExp(`\\b${field}\\s*:`), `${taskName} 缺少 LoRA 参数:${field}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
const medicalTask = objectFromMarker(mockData, "name: 'medical-cpt-001'")
|
|
assert.match(medicalTask, /\bprocess_id\s*:/, '运行中的医疗训练任务缺少进程 ID')
|
|
assert.match(mockData, /medical-cpt-001_pid28741\.log/, '医疗训练任务缺少 PID 对应的日志文件')
|
|
assert.match(mockData, /export const mockTrainingLogContents/, '训练日志没有按文件提供独立 Mock 内容')
|
|
assert.match(mockData, /Num examples\s*=\s*9,800/, '医疗训练日志缺少样本规模信息')
|
|
assert.match(mockData, /Total optimization steps\s*=\s*921/, '医疗训练日志缺少真实训练步数信息')
|
|
assert.match(mockData, /\\'epoch\\':\s*1\.92/, '医疗训练日志的 Epoch 未与 64% 进度对齐')
|
|
assert.match(mockData, /\\'loss\\':\s*1\.146/, '医疗训练日志缺少当前 Loss 指标')
|
|
assert.match(mockAdapter, /const file = String\(params\.file/, '训练日志接口没有读取请求中的日志文件名')
|
|
assert.match(mockAdapter, /mockTrainingLogContents\[file\]/, '训练日志接口没有按请求文件返回对应内容')
|
|
assert.match(modelSource, /epoch:\s*number\[\]/, '训练指标模型没有保留逐步 Epoch')
|
|
|
|
console.log('训练日志 Mock 数据回归检查通过')
|