feat: 训练任务支持从最新 checkpoint 继续训练

- 后端: 新增 resume/checkpoints 端点,compute 客户端与适配器支持续训
- 计算节点: llama_factory 适配器断点续训支持及测试
- 前端: fine-tune API 封装与列表页续训/断点展示
This commit is contained in:
wuyongtao
2026-08-21 14:48:55 +08:00
parent 0233755859
commit 0b59c1ebee
6 changed files with 276 additions and 7 deletions

View File

@@ -49,6 +49,15 @@ export interface FineTuneGpuStatus {
error?: string
}
export interface FineTuneCheckpoint {
id: string
step: number
name: string
path: string
size_bytes?: number
create_time?: string
}
/** 训练任务列表 */
export const getFineTuneList = () => get<FineTuneTask[]>('/fine-tune')
@@ -89,6 +98,13 @@ export const updateFineTune = (id: string | number, data: Partial<FineTuneTask>)
/** 停止训练任务 */
export const stopFineTune = (id: string | number) => post(`/fine-tune/stop/${id}`)
/** 从最新 checkpoint 继续训练 */
export const resumeFineTune = (id: string | number) => post(`/fine-tune/${id}/resume`)
/** 获取训练断点 */
export const getFineTuneCheckpoints = (id: string | number) =>
get<FineTuneCheckpoint[]>(`/fine-tune/${id}/checkpoints`)
/** 删除训练任务 */
export const deleteFineTune = (id: string | number) => del(`/fine-tune/${id}`)

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import DataTablePage from '@/components/DataTablePage.vue'
import ModelStatusTag from '@/components/ModelStatusTag.vue'
import { usePolling } from '@/composables/usePolling'
@@ -10,6 +10,7 @@ import {
getFineTuneList,
deleteFineTune,
stopFineTune,
resumeFineTune,
getFineTuneProgress,
getFineTune,
} from '@/api/modules/fineTune'
@@ -87,9 +88,29 @@ async function handleDelete(row: any) {
}
async function handleStop(row: any) {
await ElMessageBox.confirm(
'停止后将保留已生成的 checkpoint可在资源可用时继续训练。是否停止当前任务',
'停止训练',
{ type: 'warning' },
)
await stopFineTune(row.id)
ElMessage.success('训练任务已停止')
loadData()
ElMessage.success('训练任务已停止,可继续训练')
await loadData()
}
async function handleResume(row: any) {
await ElMessageBox.confirm(
'系统将使用最新 checkpoint 继续训练,并重新检查基座模型、数据集、算力节点和 GPU 是否可用。是否继续?',
'继续训练',
{ type: 'info' },
)
try {
await resumeFineTune(row.id)
ElMessage.success('继续训练已提交')
await loadData()
} catch {
// 请求拦截器已展示后端返回的具体预检原因
}
}
function viewLog(row: any) {
@@ -217,7 +238,7 @@ onMounted(async () => {
<template #actions="{ row }">
<div class="action-buttons">
<el-button
v-if="row.status === 'running'"
v-if="['syncing', 'queued', 'running'].includes(row.status)"
type="warning"
link
size="small"
@@ -225,6 +246,15 @@ onMounted(async () => {
>
<i class="fa fa-stop-circle-o" style="margin-right: 4px" /> 停止
</el-button>
<el-button
v-if="row.status === 'stopped' || row.status === 'failed'"
type="success"
link
size="small"
@click="handleResume(row)"
>
<i class="fa fa-play-circle-o" style="margin-right: 4px" /> 继续
</el-button>
<el-button type="primary" link size="small" @click="viewLog(row)">
<i class="fa fa-file-text-o" style="margin-right: 4px" /> 日志
</el-button>