JavaProjectRepo/异步推理并保存结果.md
2026-03-20 19:00:16 +08:00

3.9 KiB
Raw Blame History

异步推理并保存结果 (SimInferService 流程解析)

在仿真计算完成后,系统需要将每一步的计算结果发送给 Python 推理服务,以预测关键指标(如 Keff。这个过程由 SimInferServiceDeviceInferService 协同完成。

1. 推理数据不是一次性全部发送

核心结论: 并不是把所有结果封装为一个大 Request 调用一次推理接口,而是进行了精细的多级分组后,分别调用推理接口

因为不同的设备、不同的材料、以及用户为设备配置的不同算法,都对应着不同的模型文件Pipeline/Model。一次推理请求只能加载一个模型文件,所以必须将必须使用同一模型的数据分在同一批次发送。

2. 数据转换与分组流程

整个流程分为两部分:在 SimInferService 中进行一级分组,然后在 DeviceInferService 中进行二级和三级分组。

2.1 第一级分组:按设备类型 (DeviceType) 分组

位置: SimInferService.asyncInferAndSave

  1. 展平时间轴 (Flatten Timeline):仿真引擎返回的 SimContext 是按时间步Step组织的。代码首先遍历每个时间步将散落的属性如液位、浓度unitId 聚合。
  2. 注入元数据:通过 SimUnit,将静态属性(如设备固有的直径、高度)以及新加入的 materialType(如 U, Pu合并到设备的属性集合中生成 DeviceStepInfo 对象。
  3. 一级分组:将所有时间步的 DeviceStepInfo 根据 设备类型 (deviceType) 放入 Map<String, List<DeviceStepInfo>> groupedDevices 中。
    • 例如:所有的 CylindricalTank 数据在一组AnnularTank 在另一组。

2.2 第二级分组:按算法类型 (AlgorithmType) 分组

位置: DeviceInferService.processDeviceInference

  1. 读取配置:获取全局配置的算法(如 GPR以及针对特定设备独立配置的算法deviceAlgoConfig)。
  2. 二级分组:在某一个设备类型(如 CylindricalTank的数据列表中进一步按 算法类型 (algoType) 分组。
    • 例如CylindricalTank 组被拆分为 -> 使用 GPR 算法的批次、使用 MLP 算法的批次。

2.3 第三级分组:按材料类型 (MaterialType) 分组

位置: DeviceInferService.processDeviceInference

  1. 三级分组:在特定的算法批次中,进一步按 材料类型 (materialType) 分组。
    • 例如:使用 GPR 的 CylindricalTank 批次被拆分为 -> 材料为 U 的批次、材料为 Pu 的批次。

3. 发起推理请求

经过上述三级分组后,我们得到了一个高度同质化的数据批次。对于每一个这样的最终批次:

  1. 查询模型:调用 algorithmModelService.getCurrentModelPath(algoType, deviceType, materialType)。因为这三个维度相同,所以它们一定使用同一个物理模型文件(.pkl)。
  2. 构建 Request:将这批数据(包含所有相关设备在所有时间步的状态)组装成一个 InferRequest
  3. 发起调用:向 Python 服务发送 HTTP POST 请求 /v1/infer
  4. 入库保存Python 返回每个设备在每个时间步的预测结果(如 keffJava 端解析这些结果并批量保存到 scenario_result 表中。

4. 异常与状态管理

  • 整个过程是加上了 @Async 异步执行的,不会阻塞前端获取基础仿真数据。
  • 如果某一个批次因为找不到模型(例如上述终端日志中 GPR + CylindricalTank + U 的模型未配置),代码会记录错误并标记 hasAnyError = true,然后跳过该批次(continue)。
  • 兜底机制:如果所有的批次都失败或找不到模型(!hasAnySuccess && hasAnyError),会抛出 RuntimeException。这会被外层 SimInferService 捕获,并将该情景的状态更新为 "3" (失败)