fix: 推理/评测结果同步、数据集统计与算力节点管理增强

后端:
- 抽取 fetch_eval_result_content 复用函数,model_eval_detail 直接应用评测任务结果
- health 接口移除数据库依赖,返回静态指标
- 数据集: count_dataset_records JSON 感知计数; 文件统计改为从 dataset_files 聚合重算; 在线编辑记录 size/record_count/version_no; 上传同步批处理
- 算力节点: 调度支持 requested GPU 子集校验与容量计算; 新增 delete_compute_node(含活动任务保护)及 DELETE 接口; 连接池 connect_timeout
- 评测任务落库 basic_metrics/score/completed_time, failed/stopped 记录 error

评测引擎:
- _load_dataset 支持 JSON/JSONL 文件
- 新增 exact match 与文本相似度指标, 余弦相似度去掉 2 样本限制

前端:
- 算力节点列表「维护」改为「删除」(带确认弹窗), compute.ts 新增 deleteComputeNode
- 数据集上传超时调整为 120s; FineTuneTask 增加 compute_node_id; GpuInfo 状态增加 reserved
This commit is contained in:
wuyongtao
2026-08-03 15:49:21 +08:00
parent cc08b164d0
commit 5cc306eb0a
15 changed files with 402 additions and 112 deletions

View File

@@ -1,7 +1,6 @@
from fastapi import APIRouter
from app.core.logging import get_logger
from app.db.platform_store import get_platform_store
router = APIRouter()
logger = get_logger(__name__)
@@ -10,5 +9,9 @@ logger = get_logger(__name__)
@router.get("/health")
async def health_check() -> dict[str, object]:
logger.info("health check requested")
return {"code": 0, "message": "ok", "data": get_platform_store().health_metrics()}
return {
"code": 0,
"message": "ok",
"data": {"cpu_percent": 0.0, "memory_percent": 0.0, "disk_percent": 0.0},
}

View File

@@ -15,7 +15,7 @@ from app.core.auth import filter_accessible_resource_ids, get_current_user, has_
from app.core.config import get_settings
from app.db.platform_store import get_platform_store
from app.modules.compute_gateway.client import ComputeNodeClient
from app.modules.compute_gateway.sync import poll_compute_jobs_once
from app.modules.compute_gateway.sync import fetch_eval_result_content, poll_compute_jobs_once
router = APIRouter()
@@ -915,6 +915,7 @@ async def upload_dataset_files(
) -> dict[str, Any]:
created: list[dict[str, Any]] = []
compute_sync: list[dict[str, Any]] = []
pending_sync: list[tuple[str, str, bytes]] = []
store = get_platform_store()
try:
store.dataset(dataset_id)
@@ -926,16 +927,18 @@ async def upload_dataset_files(
content = raw.decode("utf-8", errors="replace")
created_file = store.add_dataset_file(conn, dataset_id, file.filename or "upload.jsonl", content)
created.append(created_file)
if sync_to_compute:
compute_sync.extend(
await _sync_dataset_file_to_compute_nodes(
store,
dataset_id,
created_file["id"],
created_file["name"],
raw,
)
pending_sync.append((created_file["id"], created_file["name"], raw))
if sync_to_compute:
for file_id, file_name, raw in pending_sync:
compute_sync.extend(
await _sync_dataset_file_to_compute_nodes(
store,
dataset_id,
file_id,
file_name,
raw,
)
)
return ok({"files": created, "compute_sync": compute_sync})
@@ -1273,8 +1276,7 @@ async def model_eval_detail(task_id: str, current_user: dict = Depends(get_curre
try:
store = get_platform_store()
task = store.eval_task(task_id)
# If the eval job completed on a compute node, try to load results
if task.get("result_artifact_path"):
if task.get("compute_job_id") and task.get("compute_node_id") and task.get("status") in {"queued", "running", "completed"}:
node = next(
(n for n in store.compute_nodes() if n["id"] == task.get("compute_node_id")),
None,
@@ -1283,11 +1285,10 @@ async def model_eval_detail(task_id: str, current_user: dict = Depends(get_curre
try:
client = ComputeNodeClient(node["api_base_url"])
job = await client.get_job(task["compute_job_id"])
artifacts = job.get("artifacts") or []
for art in artifacts:
if art.get("name") == "eval_results.json":
task["_result_artifact"] = art
break
result_content = None
if job.get("status") == "completed" and not task.get("samples"):
result_content = await fetch_eval_result_content(client, node, job)
task = store.apply_eval_job_result(task_id, job, result_content)
except Exception:
pass
except KeyError:
@@ -1790,6 +1791,16 @@ async def update_compute_node(node_id: str, payload: dict[str, Any] = Body(...))
raise fail(400, str(exc))
@router.delete("/compute/nodes/{node_id}")
async def delete_compute_node(node_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().delete_compute_node(node_id))
except KeyError:
raise fail(404, "compute node not found")
except ValueError as exc:
raise fail(400, str(exc))
@router.post("/compute/nodes/{node_id}/test-connection")
async def test_compute_node(node_id: str) -> dict[str, Any]:
store = get_platform_store()