Compare commits
25 Commits
a2e5bf4e10
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 35e4bb5558 | |||
| 10319ce0a6 | |||
| 835f192689 | |||
| fcc5d7a437 | |||
| d46ccc04d7 | |||
| 0ab0fdc43e | |||
| f2c54dd0dc | |||
| ad750f0f06 | |||
| 7521b323f4 | |||
| 5fc93b64b6 | |||
| cd14e6e3aa | |||
| 0447dd80ed | |||
| 5f4640ea60 | |||
| b85f920f40 | |||
| e4851b1153 | |||
| b73aef5cf8 | |||
| 72c8f6a047 | |||
| c7a49ea4ea | |||
| fbef92af55 | |||
| 154db0ac27 | |||
| 9835170cd7 | |||
| fd51f21ba6 | |||
| 776622e0a2 | |||
|
|
4d2d7a9ad1 | ||
|
|
532fbdbee4 |
@@ -1,7 +1,10 @@
|
||||
services:
|
||||
kamco-changedetection-api:
|
||||
image: kamco-train-app:latest
|
||||
container_name: kamco-train-api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-dev
|
||||
image: kamco-cd-training-api:${IMAGE_TAG:-latest}
|
||||
container_name: kamco-cd-training-api
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
@@ -9,13 +12,13 @@ services:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
ports:
|
||||
- "7200:8080"
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=dev
|
||||
- TZ=Asia/Seoul
|
||||
- cors.allowed-origins=*
|
||||
- cors.allowed-origins[0]=*
|
||||
volumes:
|
||||
- /data/training:/data/training
|
||||
- /backup/data/training:/backup/data/training
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- kamco-cds
|
||||
|
||||
@@ -37,7 +37,8 @@ public class GpuDmonReader {
|
||||
int avg =
|
||||
deque.isEmpty()
|
||||
? 0
|
||||
: (int) Math.round(deque.stream().mapToInt(Integer::intValue).average().orElse(0));
|
||||
: (int)
|
||||
Math.round(deque.stream().mapToInt(Integer::intValue).average().orElse(0));
|
||||
result.put(gpu, avg);
|
||||
}
|
||||
});
|
||||
@@ -98,8 +99,7 @@ public class GpuDmonReader {
|
||||
|
||||
Process process = pb.start();
|
||||
// 프로세스 실행 후 stdout 읽기
|
||||
try (BufferedReader br =
|
||||
new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
|
||||
String line;
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
@Slf4j
|
||||
public final class HeaderUtil {
|
||||
|
||||
private HeaderUtil() {}
|
||||
@@ -20,4 +25,20 @@ public final class HeaderUtil {
|
||||
public static String getRequired(HttpServletRequest request, String headerName) {
|
||||
return get(request, headerName);
|
||||
}
|
||||
|
||||
public static boolean isEnglishRequest() {
|
||||
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
if (!(attrs instanceof ServletRequestAttributes servletAttrs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String acceptLanguage = servletAttrs.getRequest().getHeader("Accept-Language");
|
||||
|
||||
if (acceptLanguage == null || acceptLanguage.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return acceptLanguage.toLowerCase().startsWith("en");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.training.common.utils.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -31,10 +32,11 @@ public class Enums {
|
||||
// enum -> CodeDto list
|
||||
public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) {
|
||||
Object[] enums = enumClass.getEnumConstants();
|
||||
boolean english = HeaderUtil.isEnglishRequest();
|
||||
|
||||
return Arrays.stream(enums)
|
||||
.map(e -> (EnumType) e)
|
||||
.map(e -> new CodeDto(e.getId(), e.getText()))
|
||||
.map(e -> new CodeDto(e.getId(), english ? e.getId() : e.getText()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -293,6 +293,8 @@ public class DatasetApiController {
|
||||
DatasetService.validateTrainValTestDirs(req.getFilePath());
|
||||
// 파일 개수 검증
|
||||
DatasetService.validateDirFileCount(req.getFilePath());
|
||||
// 폴더명(uid)으로 등록한 건이 있는지 체크
|
||||
datasetService.validateExistsUidChk(req.getFilePath());
|
||||
|
||||
datasetAsyncService.insertDeliveriesDatasetAsync(req);
|
||||
return ApiResponseDto.createOK("ok");
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kamco.cd.training.common.enums.LearnDataRegister;
|
||||
import com.kamco.cd.training.common.enums.LearnDataType;
|
||||
import com.kamco.cd.training.common.enums.ModelType;
|
||||
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||
import com.kamco.cd.training.common.utils.enums.Enums;
|
||||
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -90,7 +91,8 @@ public class DatasetDto {
|
||||
|
||||
public String getStatus(String status) {
|
||||
LearnDataRegister type = Enums.fromId(LearnDataRegister.class, status);
|
||||
return type == null ? null : type.getText();
|
||||
boolean english = HeaderUtil.isEnglishRequest();
|
||||
return type == null ? null : (english ? type.getId() : type.getText());
|
||||
}
|
||||
|
||||
public String getYear() {
|
||||
@@ -99,7 +101,8 @@ public class DatasetDto {
|
||||
|
||||
public String getDataTypeName() {
|
||||
LearnDataType type = Enums.fromId(LearnDataType.class, this.dataType);
|
||||
return type == null ? null : type.getText();
|
||||
boolean english = HeaderUtil.isEnglishRequest();
|
||||
return type == null ? null : (english ? type.getId() : type.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +318,7 @@ public class DatasetDto {
|
||||
|
||||
public String getDataTypeName(String groupTitleCd) {
|
||||
LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd);
|
||||
return type == null ? null : type.getText();
|
||||
return type == null ? null : (HeaderUtil.isEnglishRequest() ? type.getId() : type.getText());
|
||||
}
|
||||
|
||||
public String getYear() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.training.common.enums.DetectionClassification;
|
||||
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -131,7 +132,10 @@ public class DatasetObjDto {
|
||||
private String classCd;
|
||||
|
||||
public String getClassName() {
|
||||
return DetectionClassification.fromString(classCd).getDesc();
|
||||
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? DetectionClassification.fromString(classCd).getId()
|
||||
: DetectionClassification.fromString(classCd).getDesc();
|
||||
// fromString 메서드를 사용하여 안전하게 변환 (미정의 값은 ETC로 처리)
|
||||
// return DetectionClassification.valueOf(classCd.toUpperCase()).getDesc();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import com.kamco.cd.training.common.enums.LearnDataRegister;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.AddDeliveriesReq;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetMngRegDto;
|
||||
import com.kamco.cd.training.postgres.core.DatasetCoreService;
|
||||
import java.util.UUID;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@@ -45,7 +49,11 @@ public class DatasetAsyncService {
|
||||
try {
|
||||
|
||||
// ===== 1. UID 생성 =====
|
||||
String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
|
||||
// String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
|
||||
|
||||
// 26-05-08: UID를 생성하지 않고 folder name 을 uid 로 저장하기 -> req.getFilePath()
|
||||
Path selectedPath = Paths.get(req.getFilePath());
|
||||
String uid = selectedPath.getFileName().toString();
|
||||
log.info("{} 생성된 UID: {}", LOG_PREFIX, uid);
|
||||
|
||||
// ===== 2. 마스터 데이터 생성 =====
|
||||
@@ -71,6 +79,7 @@ public class DatasetAsyncService {
|
||||
datasetMngRegDto.setTitle(title);
|
||||
datasetMngRegDto.setMemo(req.getMemo());
|
||||
datasetMngRegDto.setDatasetPath(req.getFilePath());
|
||||
datasetMngRegDto.setTotalSize(getDirectorySize(req.getFilePath())); // 선택한 폴더 안의 모든 파일 용량 합계
|
||||
|
||||
// 마스터 저장
|
||||
datasetUid = datasetCoreService.insertDatasetMngData(datasetMngRegDto);
|
||||
@@ -121,4 +130,24 @@ public class DatasetAsyncService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Long getDirectorySize(String filePath) {
|
||||
Path selectedPath = Paths.get(filePath);
|
||||
|
||||
try (Stream<Path> paths = Files.walk(selectedPath)) {
|
||||
return paths
|
||||
.filter(Files::isRegularFile)
|
||||
.mapToLong(
|
||||
path -> {
|
||||
try {
|
||||
return Files.size(path);
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ public class DatasetService {
|
||||
.memo(addReq.getMemo())
|
||||
.roundNo(stage)
|
||||
.totalSize(addReq.getFileSize())
|
||||
.datasetPath(addReq.getFilePath())
|
||||
.datasetPath(addReq.getFilePath() + uid)
|
||||
.build();
|
||||
|
||||
datasetUid = datasetCoreService.insertDatasetMngData(mngRegDto); // tb_dataset 에 insert
|
||||
@@ -676,4 +676,18 @@ public class DatasetService {
|
||||
total,
|
||||
System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
public void validateExistsUidChk(String filePath) {
|
||||
Path selectedPath = Paths.get(filePath);
|
||||
String uid = selectedPath.getFileName().toString();
|
||||
|
||||
// 같은 uid 로 등록한 파일이 있는지 확인
|
||||
Long existsCnt = datasetCoreService.findDatasetByUidExistsCnt(uid);
|
||||
if (existsCnt > 0) {
|
||||
throw new CustomApiException(
|
||||
ApiResponseCode.DUPLICATE_DATA.getId(),
|
||||
HttpStatus.CONFLICT,
|
||||
"이미 등록된 회차 데이터 파일입니다. 확인 부탁드립니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,9 +277,7 @@ public class FileManagerDto {
|
||||
@Schema(description = "디스크 사용률 (%)", example = "50.5")
|
||||
private Double usagePercentage;
|
||||
|
||||
|
||||
@JsonFormatDttm
|
||||
private ZonedDateTime lastModifiedDate;
|
||||
@JsonFormatDttm private ZonedDateTime lastModifiedDate;
|
||||
|
||||
public ZonedDateTime getLastModifiedDate() {
|
||||
return ZonedDateTime.now();
|
||||
|
||||
@@ -627,8 +627,7 @@ public class FileManagerService {
|
||||
duPb.redirectErrorStream(true);
|
||||
Process duProcess = duPb.start();
|
||||
try (java.io.BufferedReader br =
|
||||
new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(duProcess.getInputStream()))) {
|
||||
new java.io.BufferedReader(new java.io.InputStreamReader(duProcess.getInputStream()))) {
|
||||
String line = br.readLine();
|
||||
if (line != null) {
|
||||
// du -sb 출력 형식: "12345678\t/path/to/dir"
|
||||
|
||||
@@ -236,4 +236,41 @@ public class ModelTrainMngApiController {
|
||||
public ApiResponseDto<MonitorDto> getSystem() throws IOException {
|
||||
return ApiResponseDto.ok(systemMonitorService.get());
|
||||
}
|
||||
|
||||
@Operation(summary = "모델학습 1단계/2단계 실행중인 것 id 정보", description = "모델학습 1단계/2단계 실행중인 것 id 정보")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "검색 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/ing-model")
|
||||
public ApiResponseDto<ModelTrainMngDto.InProgressModel> findInprogressModel() {
|
||||
return ApiResponseDto.ok(modelTrainMngService.findInprogressModel());
|
||||
}
|
||||
|
||||
@Operation(summary = "모델학습 진행율 퍼센트", description = "모델학습 진행율 퍼센트")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "검색 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Long.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/progress-percent/{uuid}")
|
||||
public ApiResponseDto<ModelTrainMngDto.ProgressPercent> findTrainProgressPercent(
|
||||
@PathVariable UUID uuid) {
|
||||
return ApiResponseDto.ok(modelTrainMngService.findTrainProgressPercent(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.kamco.cd.training.common.enums.LearnDataType;
|
||||
import com.kamco.cd.training.common.enums.TrainStatusType;
|
||||
import com.kamco.cd.training.common.enums.TrainType;
|
||||
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||
import com.kamco.cd.training.common.utils.enums.Enums;
|
||||
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectTransferDataSet;
|
||||
@@ -32,6 +33,8 @@ public class ModelTrainDetailDto {
|
||||
private String modelNo;
|
||||
private String modelVer;
|
||||
@JsonFormatDttm private ZonedDateTime step1StrtDttm;
|
||||
@JsonFormatDttm private ZonedDateTime step1EndDttm;
|
||||
@JsonFormatDttm private ZonedDateTime step2StrtDttm;
|
||||
@JsonFormatDttm private ZonedDateTime step2EndDttm;
|
||||
private String statusCd;
|
||||
private String trainType;
|
||||
@@ -40,7 +43,9 @@ public class ModelTrainDetailDto {
|
||||
public String getStatusName() {
|
||||
if (this.statusCd == null || this.statusCd.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.statusCd).getText(); // 또는 getName()
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.statusCd).getId()
|
||||
: TrainStatusType.valueOf(this.statusCd).getText(); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.statusCd; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -49,18 +54,16 @@ public class ModelTrainDetailDto {
|
||||
public String getTrainTypeName() {
|
||||
if (this.trainType == null || this.trainType.isBlank()) return null;
|
||||
try {
|
||||
return TrainType.valueOf(this.trainType).getText(); // 또는 getName()
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? TrainType.valueOf(this.trainType).getId()
|
||||
: TrainType.valueOf(this.trainType).getText(); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.trainType; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
}
|
||||
|
||||
private String formatDuration(ZonedDateTime start, ZonedDateTime end) {
|
||||
if (end == null) {
|
||||
end = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
if (start == null) {
|
||||
if (start == null || end == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -70,11 +73,42 @@ public class ModelTrainDetailDto {
|
||||
long minutes = (totalSeconds % 3600) / 60;
|
||||
long seconds = totalSeconds % 60;
|
||||
|
||||
if (HeaderUtil.isEnglishRequest()) {
|
||||
return String.format("%dh %dm %ds", hours, minutes, seconds);
|
||||
} else {
|
||||
return String.format("%d시간 %d분 %d초", hours, minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStepAllDuration() {
|
||||
return formatDuration(this.step1StrtDttm, this.step2EndDttm);
|
||||
if (this.step2EndDttm != null) {
|
||||
// step1 + step2 실제 소요시간 합산
|
||||
long step1Seconds = 0;
|
||||
long step2Seconds = 0;
|
||||
|
||||
if (this.step1StrtDttm != null && this.step1EndDttm != null) {
|
||||
step1Seconds =
|
||||
Math.abs(Duration.between(this.step1StrtDttm, this.step1EndDttm).getSeconds());
|
||||
}
|
||||
if (this.step2StrtDttm != null && this.step2EndDttm != null) {
|
||||
step2Seconds =
|
||||
Math.abs(Duration.between(this.step2StrtDttm, this.step2EndDttm).getSeconds());
|
||||
}
|
||||
|
||||
long totalSeconds = step1Seconds + step2Seconds;
|
||||
long hours = totalSeconds / 3600;
|
||||
long minutes = (totalSeconds % 3600) / 60;
|
||||
long seconds = totalSeconds % 60;
|
||||
|
||||
if (HeaderUtil.isEnglishRequest()) {
|
||||
return String.format("%dh %dm %ds", hours, minutes, seconds);
|
||||
} else {
|
||||
return String.format("%d시간 %d분 %d초", hours, minutes, seconds);
|
||||
}
|
||||
} else {
|
||||
// step2 없으면 step1만
|
||||
return formatDuration(this.step1StrtDttm, this.step1EndDttm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +208,7 @@ public class ModelTrainDetailDto {
|
||||
|
||||
public String getDataTypeName(String groupTitleCd) {
|
||||
LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd);
|
||||
return type == null ? null : type.getText();
|
||||
return type == null ? null : (HeaderUtil.isEnglishRequest() ? type.getId() : type.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.kamco.cd.training.model.dto;
|
||||
import com.kamco.cd.training.common.dto.HyperParam;
|
||||
import com.kamco.cd.training.common.enums.TrainStatusType;
|
||||
import com.kamco.cd.training.common.enums.TrainType;
|
||||
import com.kamco.cd.training.common.utils.HeaderUtil;
|
||||
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -44,16 +45,20 @@ public class ModelTrainMngDto {
|
||||
private String requestPath;
|
||||
|
||||
private String packingState;
|
||||
private ZonedDateTime packingStrtDttm;
|
||||
private ZonedDateTime packingEndDttm;
|
||||
@JsonFormatDttm private ZonedDateTime packingStrtDttm;
|
||||
@JsonFormatDttm private ZonedDateTime packingEndDttm;
|
||||
|
||||
private Long beforeModelId;
|
||||
private Integer bestEpoch;
|
||||
|
||||
private UUID beforeModelUuid;
|
||||
|
||||
public String getStatusName() {
|
||||
if (this.statusCd == null || this.statusCd.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.statusCd).getText(); // 또는 getName()
|
||||
return (HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.statusCd).getId()
|
||||
: TrainStatusType.valueOf(this.statusCd).getText()); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.statusCd; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -62,7 +67,9 @@ public class ModelTrainMngDto {
|
||||
public String getStep1StatusName() {
|
||||
if (this.step1Status == null || this.step1Status.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.step1Status).getText(); // 또는 getName()
|
||||
return (HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.step1Status).getId()
|
||||
: TrainStatusType.valueOf(this.step1Status).getText()); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.step1Status; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -71,7 +78,9 @@ public class ModelTrainMngDto {
|
||||
public String getStep2StatusName() {
|
||||
if (this.step2Status == null || this.step2Status.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.step2Status).getText(); // 또는 getName()
|
||||
return (HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.step2Status).getId()
|
||||
: TrainStatusType.valueOf(this.step2Status).getText()); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.step2Status; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -80,7 +89,9 @@ public class ModelTrainMngDto {
|
||||
public String getTrainTypeName() {
|
||||
if (this.trainType == null || this.trainType.isBlank()) return null;
|
||||
try {
|
||||
return TrainType.valueOf(this.trainType).getText(); // 또는 getName()
|
||||
return (HeaderUtil.isEnglishRequest()
|
||||
? TrainType.valueOf(this.trainType).getId()
|
||||
: TrainType.valueOf(this.trainType).getText()); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.trainType; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -97,8 +108,12 @@ public class ModelTrainMngDto {
|
||||
long minutes = (totalSeconds % 3600) / 60;
|
||||
long seconds = totalSeconds % 60;
|
||||
|
||||
if (HeaderUtil.isEnglishRequest()) {
|
||||
return String.format("%dh %dm %ds", hours, minutes, seconds);
|
||||
} else {
|
||||
return String.format("%d시간 %d분 %d초", hours, minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStep1Duration() {
|
||||
return formatDuration(this.step1StrtDttm, this.step1EndDttm);
|
||||
@@ -260,7 +275,9 @@ public class ModelTrainMngDto {
|
||||
public String getStatusName() {
|
||||
if (this.statusCd == null || this.statusCd.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.statusCd).getText(); // 또는 getName()
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.statusCd).getId()
|
||||
: TrainStatusType.valueOf(this.statusCd).getText(); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.statusCd; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -269,7 +286,9 @@ public class ModelTrainMngDto {
|
||||
public String getStep1StatusName() {
|
||||
if (this.step1Status == null || this.step1Status.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.step1Status).getText(); // 또는 getName()
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.step1Status).getId()
|
||||
: TrainStatusType.valueOf(this.step1Status).getText(); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.step1Status; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -278,7 +297,9 @@ public class ModelTrainMngDto {
|
||||
public String getStep2StatusName() {
|
||||
if (this.step2Status == null || this.step2Status.isBlank()) return null;
|
||||
try {
|
||||
return TrainStatusType.valueOf(this.step2Status).getText(); // 또는 getName()
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? TrainStatusType.valueOf(this.step2Status).getId()
|
||||
: TrainStatusType.valueOf(this.step2Status).getText(); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.step2Status; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -287,7 +308,9 @@ public class ModelTrainMngDto {
|
||||
public String getTrainTypeName() {
|
||||
if (this.trainType == null || this.trainType.isBlank()) return null;
|
||||
try {
|
||||
return TrainType.valueOf(this.trainType).getText(); // 또는 getName()
|
||||
return HeaderUtil.isEnglishRequest()
|
||||
? TrainType.valueOf(this.trainType).getId()
|
||||
: TrainType.valueOf(this.trainType).getText(); // 또는 getName()
|
||||
} catch (IllegalArgumentException e) {
|
||||
return this.trainType; // 매핑 못하면 코드 그대로 반환(원하면 null 처리)
|
||||
}
|
||||
@@ -304,8 +327,12 @@ public class ModelTrainMngDto {
|
||||
long minutes = (totalSeconds % 3600) / 60;
|
||||
long seconds = totalSeconds % 60;
|
||||
|
||||
if (HeaderUtil.isEnglishRequest()) {
|
||||
return String.format("%dh %dm %ds", hours, minutes, seconds);
|
||||
} else {
|
||||
return String.format("%d시간 %d분 %d초", hours, minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStep1Duration() {
|
||||
return formatDuration(this.step1StrtDttm, this.step1EndDttm);
|
||||
@@ -353,4 +380,26 @@ public class ModelTrainMngDto {
|
||||
// 삭제 될 파일
|
||||
private List<String> deleteTargets;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public static class InProgressModel {
|
||||
|
||||
private String modelNo;
|
||||
private UUID uuid;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public static class ProgressPercent {
|
||||
|
||||
private Long modelId;
|
||||
private String jobType;
|
||||
private String statusCd;
|
||||
private Integer totalEpoch;
|
||||
private Integer currentEpoch;
|
||||
private Double percent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,4 +350,12 @@ public class ModelTrainMngService {
|
||||
public Long findModelStep1InProgressCnt() {
|
||||
return modelTrainMngCoreService.findModelStep1InProgressCnt();
|
||||
}
|
||||
|
||||
public ModelTrainMngDto.InProgressModel findInprogressModel() {
|
||||
return modelTrainMngCoreService.findInprogressModel();
|
||||
}
|
||||
|
||||
public ModelTrainMngDto.ProgressPercent findTrainProgressPercent(UUID uuid) {
|
||||
return modelTrainMngCoreService.findTrainProgressPercent(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,15 @@ public class ModelTrainMngCoreService {
|
||||
entity.setTrainType(addReq.getTrainType()); // 일반, 전이
|
||||
entity.setBeforeModelId(addReq.getBeforeModelId());
|
||||
|
||||
// 전이학습일 때 beforeModelUuid 추가하기
|
||||
if (addReq.getBeforeModelId() != null) {
|
||||
ModelMasterEntity beforeModel =
|
||||
modelMngRepository
|
||||
.findById(addReq.getBeforeModelId())
|
||||
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
|
||||
entity.setBeforeModelUuid(beforeModel.getUuid());
|
||||
}
|
||||
|
||||
entity.setStatusCd(TrainStatusType.READY.getId());
|
||||
entity.setStep1State(TrainStatusType.READY.getId());
|
||||
|
||||
@@ -713,4 +722,17 @@ public class ModelTrainMngCoreService {
|
||||
entity.setTmpFileStatus("FAIL");
|
||||
entity.setTmpFileErrMessage(message);
|
||||
}
|
||||
|
||||
public ModelTrainMngDto.InProgressModel findInprogressModel() {
|
||||
return modelMngRepository.findInprogressModel();
|
||||
}
|
||||
|
||||
public ModelTrainMngDto.ProgressPercent findTrainProgressPercent(UUID uuid) {
|
||||
ModelMasterEntity entity =
|
||||
modelMngRepository
|
||||
.findByUuid(uuid)
|
||||
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
|
||||
|
||||
return modelMngRepository.findTrainProgressPercent(entity.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,9 @@ public class ModelMasterEntity {
|
||||
@Column(name = "tmp_file_err_message", columnDefinition = "TEXT")
|
||||
private String tmpFileErrMessage;
|
||||
|
||||
@Column(name = "before_model_uuid")
|
||||
private UUID beforeModelUuid;
|
||||
|
||||
public ModelTrainMngDto.Basic toDto() {
|
||||
return new ModelTrainMngDto.Basic(
|
||||
this.id,
|
||||
@@ -157,6 +160,7 @@ public class ModelMasterEntity {
|
||||
this.packingStrtDttm,
|
||||
this.packingEndDttm,
|
||||
this.beforeModelId,
|
||||
this.bestEpoch);
|
||||
this.bestEpoch,
|
||||
this.beforeModelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ public class ModelDetailRepositoryImpl implements ModelDetailRepositoryCustom {
|
||||
modelMasterEntity.modelNo,
|
||||
modelMasterEntity.modelVer,
|
||||
modelMasterEntity.step1StrtDttm,
|
||||
modelMasterEntity.step1EndDttm,
|
||||
modelMasterEntity.step2StrtDttm,
|
||||
modelMasterEntity.step2EndDttm,
|
||||
modelMasterEntity.statusCd,
|
||||
modelMasterEntity.trainType,
|
||||
|
||||
@@ -47,4 +47,13 @@ public interface ModelMngRepositoryCustom {
|
||||
* @return 모델 목록
|
||||
*/
|
||||
List<ModelMasterEntity> findByHyperParamId(Long hyperParamId);
|
||||
|
||||
/**
|
||||
* 학습 진행중인 모델 type, uuid 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ModelTrainMngDto.InProgressModel findInprogressModel();
|
||||
|
||||
ModelTrainMngDto.ProgressPercent findTrainProgressPercent(Long id);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QModelConfigEntity.modelConfigEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QModelHyperParamEntity.modelHyperParamEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QModelMasterEntity.modelMasterEntity;
|
||||
import static com.kamco.cd.training.postgres.entity.QModelTrainJobEntity.modelTrainJobEntity;
|
||||
|
||||
import com.kamco.cd.training.common.enums.TrainStatusType;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
|
||||
@@ -14,7 +15,9 @@ import com.kamco.cd.training.train.dto.TrainRunRequest;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.CaseBuilder;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -143,6 +146,8 @@ public class ModelMngRepositoryImpl implements ModelMngRepositoryCustom {
|
||||
|
||||
@Override
|
||||
public TrainRunRequest findTrainRunRequest(Long modelId) {
|
||||
QModelMasterEntity beforeModel = new QModelMasterEntity("beforeModel");
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
@@ -190,12 +195,17 @@ public class ModelMngRepositoryImpl implements ModelMngRepositoryCustom {
|
||||
Expressions.nullExpression(Integer.class),
|
||||
Expressions.nullExpression(String.class),
|
||||
modelHyperParamEntity.uuid,
|
||||
modelMasterEntity.reqTmpYn))
|
||||
modelMasterEntity.reqTmpYn,
|
||||
modelMasterEntity.trainType,
|
||||
beforeModel.uuid,
|
||||
beforeModel.bestEpoch))
|
||||
.from(modelMasterEntity)
|
||||
.leftJoin(modelHyperParamEntity)
|
||||
.on(modelHyperParamEntity.id.eq(modelMasterEntity.hyperParamId))
|
||||
.leftJoin(modelConfigEntity)
|
||||
.on(modelConfigEntity.model.id.eq(modelMasterEntity.id))
|
||||
.leftJoin(beforeModel)
|
||||
.on(modelMasterEntity.beforeModelId.eq(beforeModel.id))
|
||||
.where(modelMasterEntity.id.eq(modelId))
|
||||
.fetchOne();
|
||||
}
|
||||
@@ -231,4 +241,58 @@ public class ModelMngRepositoryImpl implements ModelMngRepositoryCustom {
|
||||
.orderBy(modelMasterEntity.createdDttm.desc())
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelTrainMngDto.InProgressModel findInprogressModel() {
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
ModelTrainMngDto.InProgressModel.class,
|
||||
modelMasterEntity.modelNo,
|
||||
modelMasterEntity.uuid))
|
||||
.from(modelMasterEntity)
|
||||
.where(
|
||||
modelMasterEntity
|
||||
.step1State
|
||||
.eq(TrainStatusType.IN_PROGRESS.getId())
|
||||
.or(modelMasterEntity.step2State.eq(TrainStatusType.IN_PROGRESS.getId())))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelTrainMngDto.ProgressPercent findTrainProgressPercent(Long id) {
|
||||
NumberExpression<Integer> currentEpoch =
|
||||
new CaseBuilder()
|
||||
.when(
|
||||
modelTrainJobEntity
|
||||
.jobType
|
||||
.eq("TEST")
|
||||
.and(modelTrainJobEntity.statusCd.eq("SUCCESS")))
|
||||
.then(1)
|
||||
.otherwise(modelTrainJobEntity.currentEpoch.coalesce(0));
|
||||
|
||||
NumberExpression<Integer> totalEpoch = modelTrainJobEntity.totalEpoch.coalesce(1);
|
||||
|
||||
// per 계산
|
||||
NumberExpression<Double> per =
|
||||
currentEpoch
|
||||
.castToNum(Double.class)
|
||||
.divide(totalEpoch.castToNum(Double.class))
|
||||
.multiply(100);
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
ModelTrainMngDto.ProgressPercent.class,
|
||||
modelTrainJobEntity.modelId,
|
||||
modelTrainJobEntity.jobType,
|
||||
modelTrainJobEntity.statusCd,
|
||||
totalEpoch.as("totalEpoch"),
|
||||
currentEpoch.as("currentEpoch"),
|
||||
per.as("per")))
|
||||
.from(modelTrainJobEntity)
|
||||
.where(modelTrainJobEntity.modelId.eq(id))
|
||||
.orderBy(modelTrainJobEntity.attemptNo.desc())
|
||||
.fetchFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,12 @@ public class TrainRunRequest {
|
||||
|
||||
private Boolean reqTmpYn; // tmp 심볼릭 링크를 쓰는지 아닌지 여부
|
||||
|
||||
private String trainType; // 일반 : GENERAL, 전이 : TRANSFER
|
||||
|
||||
private UUID beforeModelUUID;
|
||||
private Integer
|
||||
beforeBestEpoch; // 전이학습인 경우, before 모델 베스트 pth 경로를 보내야 해서 before 모델의 bestEpoch 을 조회하기
|
||||
|
||||
public String getOutputFolder() {
|
||||
return String.valueOf(this.outputFolder);
|
||||
}
|
||||
|
||||
@@ -379,9 +379,30 @@ public class DockerTrainService {
|
||||
addArg(c, "--saturation-range", req.getSaturationRange());
|
||||
addArg(c, "--hue-delta", req.getHueDelta());
|
||||
|
||||
// 재시작을 위한 파라미터
|
||||
if (req.getResumeFrom() != null && !req.getResumeFrom().isBlank()) {
|
||||
c.add("--resume");
|
||||
addArg(c, "--load-from", req.getResumeFrom());
|
||||
} else if ("TRANSFER".equals(req.getTrainType())
|
||||
&& req.getBeforeModelUUID() != null
|
||||
&& req.getBeforeBestEpoch() != null) {
|
||||
// 재시작이 아닌데 전이학습인 경우, 전이학습을 위한 파라미터
|
||||
String finalLoadFromPath;
|
||||
String bestModelFileName = "best_changed_fscore_epoch_" + req.getBeforeBestEpoch() + ".pth";
|
||||
String epochFileName = "epoch_" + req.getBeforeBestEpoch() + ".pth";
|
||||
|
||||
// 이전 모델 response 경로
|
||||
Path beforeModelDir = Path.of(responseDir).resolve(req.getBeforeModelUUID().toString());
|
||||
// 이전 모델 response 의 베스트 fscore pth 모델 파일 경로
|
||||
Path bestFilePath = beforeModelDir.resolve(bestModelFileName);
|
||||
|
||||
// 베스트 fscore 파일이 있으면 그 경로를 사용, 없으면 epoch_00.pth 파일 경로 사용
|
||||
if (Files.exists(bestFilePath)) {
|
||||
finalLoadFromPath = bestFilePath.toString();
|
||||
} else {
|
||||
finalLoadFromPath = beforeModelDir.resolve(epochFileName).toString();
|
||||
}
|
||||
addArg(c, "--load-from", finalLoadFromPath);
|
||||
}
|
||||
|
||||
addArg(c, "--save-interval", 1);
|
||||
@@ -498,6 +519,10 @@ public class DockerTrainService {
|
||||
c.add("docker");
|
||||
c.add("run");
|
||||
c.add("--rm");
|
||||
|
||||
c.add("--user"); // root 권한 문제를 해결하기 위해 임시 추가
|
||||
c.add("0:0");
|
||||
|
||||
c.add("--gpus");
|
||||
c.add("all");
|
||||
|
||||
@@ -515,7 +540,8 @@ public class DockerTrainService {
|
||||
}
|
||||
|
||||
c.add("-v");
|
||||
c.add(responseDir + ":/checkpoints");
|
||||
// c.add(responseDir + ":/checkpoints");
|
||||
c.add(responseDir + "/" + req.getOutputFolder() + ":/checkpoints");
|
||||
|
||||
c.add("kamco-cd-train:latest");
|
||||
|
||||
@@ -523,7 +549,7 @@ public class DockerTrainService {
|
||||
c.add("/workspace/change-detection-code/run_evaluation_pipeline.py");
|
||||
|
||||
addArg(c, "--dataset-folder", req.getDatasetFolder());
|
||||
addArg(c, "--output-folder", req.getOutputFolder());
|
||||
// addArg(c, "--output-folder", req.getOutputFolder());
|
||||
|
||||
c.add("--epoch");
|
||||
c.add(modelFile);
|
||||
|
||||
@@ -50,16 +50,17 @@ swagger:
|
||||
|
||||
|
||||
file:
|
||||
dataset-dir: /data/training/request/
|
||||
base_path: /backup/data/training
|
||||
dataset-dir: ${file.base_path}/request/
|
||||
dataset-tmp-dir: ${file.dataset-dir}tmp/
|
||||
|
||||
pt-path: /data/training/response/v6-cls-checkpoints/
|
||||
pt-path: ${file.base_path}/response/v6-cls-checkpoints/
|
||||
pt-FileName: yolov8_6th-6m.pt
|
||||
|
||||
train:
|
||||
docker:
|
||||
image: kamco-cd-train:latest
|
||||
base_path: /data/training
|
||||
base_path: /backup/data/training
|
||||
request_dir: ${train.docker.base_path}/request
|
||||
response_dir: ${train.docker.base_path}/response
|
||||
symbolic_link_dir: ${train.docker.base_path}/tmp
|
||||
|
||||
@@ -37,8 +37,8 @@ spring:
|
||||
max-file-size: 10GB
|
||||
max-request-size: 10GB
|
||||
|
||||
transaction:
|
||||
default-timeout: 300 # 5분 트랜잭션 타임아웃
|
||||
#transaction:
|
||||
# default-timeout: 300 # 5분 트랜잭션 타임아웃
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
Reference in New Issue
Block a user