feat: 模型推理异步加载与对话链路修复,同步基线

模型推理全异步化改造:
- 计算节点 InferenceSession 改为后台线程异步加载模型,load 立即返回,
  加载期间事件循环保持响应(/inference/status 与 /health 不阻塞)
- 后端模型加载改为异步派发 + 轮询对账器(reconcile_inference_loads),
  任务状态由 starting 自动推进到 ready/error,解决多节点启动超时
  (timeout of 120000ms exceeded)
- 推理删除/卸载改为任务感知 + 短超时,删除先删记录再 best-effort 卸载,
  不再被不可达节点阻塞;同节点新模型替换旧任务标记失效
- 流式对话透传 task_id/node_id 路由到真正加载模型的算力节点,
  useStreamChat 解析 SSE 错误帧以干净文案展示
- 对话历史按任务 id 本地持久化,退出重进可恢复;移除页脚提示文本
- 新增后端推理异步加载与计算节点异步状态机单元测试

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wuyongtao
2026-08-04 16:59:34 +08:00
parent 250e060271
commit 0271942ba5
21 changed files with 1272 additions and 245 deletions

View File

@@ -18,9 +18,12 @@ const trainedModels = ref<TrainedModel[]>([])
const currentModel = computed(() => trainedModels.value.find((m) => m.name === modelName.value))
const form = reactive({
trained_model_id: '',
model_name: modelName.value,
train_method: method.value,
base_model_path: '',
adapter_path: '',
compute_node_id: '',
})
async function loadModel() {
@@ -28,23 +31,30 @@ async function loadModel() {
const res = await getTrainedModels()
trainedModels.value = res?.models || []
const target = trainedModels.value.find((m) => m.name === modelName.value)
form.trained_model_id = target?.id == null ? '' : String(target.id)
form.base_model_path = target?.base_model_path || ''
form.adapter_path = target?.artifact_dir || target?.adapter_path || target?.merged_path || ''
form.compute_node_id = target?.compute_node_id || ''
} catch {
// ignore
}
}
async function handleMerge() {
if (!form.model_name || !form.base_model_path) {
if (!form.model_name || !form.base_model_path || !form.adapter_path) {
ElMessage.warning('缺少模型信息')
return
}
merging.value = true
try {
await mergeModel({
trained_model_id: form.trained_model_id || form.model_name,
model_name: form.model_name,
train_method: form.train_method,
base_model_path: form.base_model_path,
adapter_path: form.adapter_path,
compute_node_id: form.compute_node_id,
output_model_name: `${form.model_name}-merged`,
})
ElMessage.success('合并成功')
router.push('/model-manage')
@@ -82,6 +92,9 @@ onMounted(loadModel)
<el-form-item label="基座模型路径">
<el-input v-model="form.base_model_path" placeholder="基座模型路径" />
</el-form-item>
<el-form-item label="Adapter 路径">
<el-input v-model="form.adapter_path" placeholder="LoRA Adapter 权重目录" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="merging" @click="handleMerge">