inference-job
into V20220110
@@ -19,8 +19,8 @@ type JobType string | |||
type ModelArtsJobStatus string | |||
const ( | |||
NPUResource = "NPU" | |||
GPUResource = "CPU/GPU" | |||
NPUResource = "NPU" | |||
GPUResource = "CPU/GPU" | |||
JobWaiting CloudbrainStatus = "WAITING" | |||
JobStopped CloudbrainStatus = "STOPPED" | |||
@@ -33,6 +33,7 @@ const ( | |||
JobTypeSnn4imagenet JobType = "SNN4IMAGENET" | |||
JobTypeBrainScore JobType = "BRAINSCORE" | |||
JobTypeTrain JobType = "TRAIN" | |||
JobTypeInference JobType = "INFERENCE" | |||
//notebook | |||
ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中 | |||
@@ -111,7 +112,7 @@ type Cloudbrain struct { | |||
ComputeResource string //计算资源,例如npu | |||
EngineID int64 //引擎id | |||
TrainUrl string //输出的obs路径 | |||
TrainUrl string //输出模型的obs路径 | |||
BranchName string //分支名称 | |||
Parameters string //传给modelarts的param参数 | |||
BootFile string //启动文件 | |||
@@ -125,6 +126,12 @@ type Cloudbrain struct { | |||
EngineName string //引擎名称 | |||
TotalVersionCount int //任务的所有版本数量,包括删除的 | |||
LabelName string //标签名称 | |||
ModelName string //模型名称 | |||
ModelVersion string //模型版本 | |||
CkptName string //权重文件名称 | |||
ResultUrl string //推理结果的obs路径 | |||
User *User `xorm:"-"` | |||
Repo *Repository `xorm:"-"` | |||
} | |||
@@ -210,7 +217,7 @@ type CloudbrainsOptions struct { | |||
JobType string | |||
VersionName string | |||
IsLatestVersion string | |||
JobTypeNot bool | |||
JobTypeNot bool | |||
} | |||
type TaskPod struct { | |||
@@ -644,6 +651,25 @@ type Config struct { | |||
Flavor Flavor `json:"flavor"` | |||
PoolID string `json:"pool_id"` | |||
} | |||
type CreateInferenceJobParams struct { | |||
JobName string `json:"job_name"` | |||
Description string `json:"job_desc"` | |||
InfConfig InfConfig `json:"config"` | |||
WorkspaceID string `json:"workspace_id"` | |||
} | |||
type InfConfig struct { | |||
WorkServerNum int `json:"worker_server_num"` | |||
AppUrl string `json:"app_url"` //训练作业的代码目录 | |||
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下 | |||
Parameter []Parameter `json:"parameter"` | |||
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL | |||
EngineID int64 `json:"engine_id"` | |||
LogUrl string `json:"log_url"` | |||
CreateVersion bool `json:"create_version"` | |||
Flavor Flavor `json:"flavor"` | |||
PoolID string `json:"pool_id"` | |||
} | |||
type CreateTrainJobVersionParams struct { | |||
Description string `json:"job_desc"` | |||
@@ -975,6 +1001,9 @@ func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) { | |||
cond = cond.And( | |||
builder.Eq{"Status": "COMPLETED"}, | |||
) | |||
cond = cond.And( | |||
builder.Eq{"job_type": "TRAIN"}, | |||
) | |||
cloudbrains := make([]*CloudbrainInfo, 0) | |||
if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC"). | |||
@@ -1208,3 +1237,20 @@ func GetCloudbrainTrainJobCountByUserID(userID int64) (int, error) { | |||
And("job_type = ? and user_id = ? and type = ?", JobTypeTrain, userID, TypeCloudBrainTwo).Count(new(Cloudbrain)) | |||
return int(count), err | |||
} | |||
func GetCloudbrainInferenceJobCountByUserID(userID int64) (int, error) { | |||
count, err := x.In("status", ModelArtsTrainJobInit, ModelArtsTrainJobImageCreating, ModelArtsTrainJobSubmitTrying, ModelArtsTrainJobWaiting, ModelArtsTrainJobRunning, ModelArtsTrainJobScaling, ModelArtsTrainJobCheckInit, ModelArtsTrainJobCheckRunning, ModelArtsTrainJobCheckRunningCompleted). | |||
And("job_type = ? and user_id = ? and type = ?", JobTypeInference, userID, TypeCloudBrainTwo).Count(new(Cloudbrain)) | |||
return int(count), err | |||
} | |||
func UpdateInferenceJob(job *Cloudbrain) error { | |||
return updateJobInferenceJob(x, job) | |||
} | |||
func updateJobInferenceJob(e Engine, job *Cloudbrain) error { | |||
var sess *xorm.Session | |||
sess = e.Where("job_id = ?", job.JobID) | |||
_, err := sess.Cols("status", "train_job_duration").Update(job) | |||
return err | |||
} |
@@ -45,6 +45,30 @@ type CreateModelArtsTrainJobForm struct { | |||
EngineName string `form:"engine_names" binding:"Required"` | |||
} | |||
type CreateModelArtsInferenceJobForm struct { | |||
JobName string `form:"job_name" binding:"Required"` | |||
Attachment string `form:"attachment" binding:"Required"` | |||
BootFile string `form:"boot_file" binding:"Required"` | |||
WorkServerNumber int `form:"work_server_number" binding:"Required"` | |||
EngineID int `form:"engine_id" binding:"Required"` | |||
PoolID string `form:"pool_id" binding:"Required"` | |||
Flavor string `form:"flavor" binding:"Required"` | |||
Params string `form:"run_para_list" binding:"Required"` | |||
Description string `form:"description"` | |||
IsSaveParam string `form:"is_save_para"` | |||
ParameterTemplateName string `form:"parameter_template_name"` | |||
PrameterDescription string `form:"parameter_description"` | |||
BranchName string `form:"branch_name" binding:"Required"` | |||
VersionName string `form:"version_name" binding:"Required"` | |||
FlavorName string `form:"flaver_names" binding:"Required"` | |||
EngineName string `form:"engine_names" binding:"Required"` | |||
LabelName string `form:"label_names" binding:"Required"` | |||
TrainUrl string `form:"train_url" binding:"Required"` | |||
ModelName string `form:"model_name" binding:"Required"` | |||
ModelVersion string `form:"model_version" binding:"Required"` | |||
CkptName string `form:"ckpt_name" binding:"Required"` | |||
} | |||
func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
return validate(errs, ctx.Data, f, ctx.Locale) | |||
} |
@@ -38,6 +38,7 @@ const ( | |||
// "]}" | |||
CodePath = "/code/" | |||
OutputPath = "/output/" | |||
ResultPath = "/result/" | |||
LogPath = "/log/" | |||
JobPath = "/job/" | |||
OrderDesc = "desc" //向下查询 | |||
@@ -45,6 +46,8 @@ const ( | |||
Lines = 500 | |||
TrainUrl = "train_url" | |||
DataUrl = "data_url" | |||
ResultUrl = "result_url" | |||
CkptUrl = "ckpt_url" | |||
PerPage = 10 | |||
IsLatestVersion = "1" | |||
NotLatestVersion = "0" | |||
@@ -113,6 +116,36 @@ type GenerateTrainJobVersionReq struct { | |||
TotalVersionCount int | |||
} | |||
type GenerateInferenceJobReq struct { | |||
JobName string | |||
Uuid string | |||
Description string | |||
CodeObsPath string | |||
BootFile string | |||
BootFileUrl string | |||
DataUrl string | |||
TrainUrl string | |||
FlavorCode string | |||
LogUrl string | |||
PoolID string | |||
WorkServerNumber int | |||
EngineID int64 | |||
Parameters []models.Parameter | |||
CommitID string | |||
Params string | |||
BranchName string | |||
FlavorName string | |||
EngineName string | |||
LabelName string | |||
IsLatestVersion string | |||
VersionCount int | |||
TotalVersionCount int | |||
ModelName string | |||
ModelVersion string | |||
CkptName string | |||
ResultUrl string | |||
} | |||
type VersionInfo struct { | |||
Version []struct { | |||
ID int `json:"id"` | |||
@@ -441,8 +474,82 @@ func TransTrainJobStatus(status int) string { | |||
} | |||
} | |||
func GetVersionOutputPathByTotalVersionCount(TotalVersionCount int) (VersionOutputPath string) { | |||
func GetOutputPathByCount(TotalVersionCount int) (VersionOutputPath string) { | |||
talVersionCountToString := fmt.Sprintf("%04d", TotalVersionCount) | |||
VersionOutputPath = "V" + talVersionCountToString | |||
return VersionOutputPath | |||
} | |||
func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (err error) { | |||
jobResult, err := createInferenceJob(models.CreateInferenceJobParams{ | |||
JobName: req.JobName, | |||
Description: req.Description, | |||
InfConfig: models.InfConfig{ | |||
WorkServerNum: req.WorkServerNumber, | |||
AppUrl: req.CodeObsPath, | |||
BootFileUrl: req.BootFileUrl, | |||
DataUrl: req.DataUrl, | |||
EngineID: req.EngineID, | |||
// TrainUrl: req.TrainUrl, | |||
LogUrl: req.LogUrl, | |||
PoolID: req.PoolID, | |||
CreateVersion: true, | |||
Flavor: models.Flavor{ | |||
Code: req.FlavorCode, | |||
}, | |||
Parameter: req.Parameters, | |||
}, | |||
}) | |||
if err != nil { | |||
log.Error("CreateJob failed: %v", err.Error()) | |||
return err | |||
} | |||
attach, err := models.GetAttachmentByUUID(req.Uuid) | |||
if err != nil { | |||
log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error()) | |||
return err | |||
} | |||
err = models.CreateCloudbrain(&models.Cloudbrain{ | |||
Status: TransTrainJobStatus(jobResult.Status), | |||
UserID: ctx.User.ID, | |||
RepoID: ctx.Repo.Repository.ID, | |||
JobID: strconv.FormatInt(jobResult.JobID, 10), | |||
JobName: req.JobName, | |||
JobType: string(models.JobTypeInference), | |||
Type: models.TypeCloudBrainTwo, | |||
VersionID: jobResult.VersionID, | |||
VersionName: jobResult.VersionName, | |||
Uuid: req.Uuid, | |||
DatasetName: attach.Name, | |||
CommitID: req.CommitID, | |||
EngineID: req.EngineID, | |||
TrainUrl: req.TrainUrl, | |||
BranchName: req.BranchName, | |||
Parameters: req.Params, | |||
BootFile: req.BootFile, | |||
DataUrl: req.DataUrl, | |||
LogUrl: req.LogUrl, | |||
FlavorCode: req.FlavorCode, | |||
Description: req.Description, | |||
WorkServerNumber: req.WorkServerNumber, | |||
FlavorName: req.FlavorName, | |||
EngineName: req.EngineName, | |||
LabelName: req.LabelName, | |||
IsLatestVersion: req.IsLatestVersion, | |||
VersionCount: req.VersionCount, | |||
TotalVersionCount: req.TotalVersionCount, | |||
ModelName: req.ModelName, | |||
ModelVersion: req.ModelVersion, | |||
CkptName: req.CkptName, | |||
ResultUrl: req.ResultUrl, | |||
}) | |||
if err != nil { | |||
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error()) | |||
return err | |||
} | |||
return nil | |||
} |
@@ -874,3 +874,59 @@ sendjob: | |||
return &result, nil | |||
} | |||
func createInferenceJob(createJobParams models.CreateInferenceJobParams) (*models.CreateTrainJobResult, error) { | |||
checkSetting() | |||
client := getRestyClient() | |||
var result models.CreateTrainJobResult | |||
retry := 0 | |||
sendjob: | |||
res, err := client.R(). | |||
SetHeader("Content-Type", "application/json"). | |||
SetAuthToken(TOKEN). | |||
SetBody(createJobParams). | |||
SetResult(&result). | |||
Post(HOST + "/v1/" + setting.ProjectID + urlTrainJob) | |||
if err != nil { | |||
return nil, fmt.Errorf("resty create inference-job: %s", err) | |||
} | |||
req, _ := json.Marshal(createJobParams) | |||
log.Info("%s", req) | |||
if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||
retry++ | |||
_ = getToken() | |||
goto sendjob | |||
} | |||
if res.StatusCode() != http.StatusOK { | |||
var temp models.ErrorResult | |||
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil { | |||
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error()) | |||
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error()) | |||
} | |||
log.Error("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
BootFileErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.BootFileUrl + "'." | |||
DataSetErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.DataUrl + "'." | |||
if temp.ErrorMsg == BootFileErrorMsg { | |||
log.Error("启动文件错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
return &result, fmt.Errorf("启动文件错误!") | |||
} | |||
if temp.ErrorMsg == DataSetErrorMsg { | |||
log.Error("数据集错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
return &result, fmt.Errorf("数据集错误!") | |||
} | |||
return &result, fmt.Errorf("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) | |||
} | |||
if !result.IsSuccess { | |||
log.Error("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||
return &result, fmt.Errorf("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||
} | |||
return &result, nil | |||
} |
@@ -376,10 +376,10 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er | |||
return fileInfos, nil | |||
} | |||
func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error) { | |||
func GetObsListObject(jobName, outPutPath, parentDir, versionName string) ([]FileInfo, error) { | |||
input := &obs.ListObjectsInput{} | |||
input.Bucket = setting.Bucket | |||
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName, parentDir), "/") | |||
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, outPutPath, versionName, parentDir), "/") | |||
strPrefix := strings.Split(input.Prefix, "/") | |||
output, err := ObsCli.ListObjects(input) | |||
fileInfos := make([]FileInfo, 0) | |||
@@ -401,7 +401,7 @@ func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error | |||
nextParentDir = parentDir + "/" + fileName | |||
} | |||
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath { | |||
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == outPutPath { | |||
continue | |||
} | |||
} else { | |||
@@ -832,6 +832,7 @@ modelarts.notebook=Debug Task | |||
modelarts.train_job=Train Task | |||
modelarts.train_job.new_debug= New Debug Task | |||
modelarts.train_job.new_train=New Train Task | |||
modelarts.train_job.new_infer=New Inference Task | |||
modelarts.train_job.config=Configuration information | |||
modelarts.train_job.new=New train Task | |||
modelarts.train_job.new_place=The description should not exceed 256 characters | |||
@@ -890,6 +891,11 @@ modelarts.train_job_para_admin=train_job_para_admin | |||
modelarts.train_job_para.edit=train_job_para.edit | |||
modelarts.train_job_para.connfirm=train_job_para.connfirm | |||
modelarts.infer_job = Inference Job | |||
modelarts.infer_job.model_version = Model/Version | |||
modelarts.infer_job.select_model = Select Model | |||
model.manage.import_new_model=Import New Model | |||
model.manage.create_error=Equal Name and Version has existed. | |||
model.manage.model_name = Model Name | |||
@@ -793,7 +793,7 @@ debug=调试 | |||
debug_again=再次调试 | |||
stop=停止 | |||
delete=删除 | |||
model_download=模型下载 | |||
model_download=结果下载 | |||
submit_image=提交镜像 | |||
download=模型下载 | |||
@@ -838,6 +838,7 @@ modelarts.notebook=调试任务 | |||
modelarts.train_job=训练任务 | |||
modelarts.train_job.new_debug=新建调试任务 | |||
modelarts.train_job.new_train=新建训练任务 | |||
modelarts.train_job.new_infer=新建推理任务 | |||
modelarts.train_job.config=配置信息 | |||
modelarts.train_job.new=新建训练任务 | |||
modelarts.train_job.new_place=描述字数不超过256个字符 | |||
@@ -900,6 +901,10 @@ modelarts.train_job_para_admin=任务参数管理 | |||
modelarts.train_job_para.edit=编辑 | |||
modelarts.train_job_para.connfirm=确定 | |||
modelarts.infer_job = 推理任务 | |||
modelarts.infer_job.model_version = 模型/版本 | |||
modelarts.infer_job.select_model = 选择模型 | |||
model.manage.import_new_model=导入新模型 | |||
model.manage.create_error=相同的名称和版本的模型已经存在。 | |||
model.manage.model_name = 模型名称 | |||
@@ -524,7 +524,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
Get(notify.GetThread). | |||
Patch(notify.ReadThread) | |||
}, reqToken()) | |||
operationReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, OperationRequired: true}) | |||
//Project board | |||
m.Group("/projectboard", func() { | |||
@@ -886,6 +886,15 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/model_list", repo.ModelList) | |||
}) | |||
}) | |||
m.Group("/inference-job", func() { | |||
m.Group("/:jobid", func() { | |||
m.Get("", repo.GetModelArtsInferenceJob) | |||
m.Get("/log", repo.TrainJobGetLog) | |||
m.Post("/del_version", repo.DelTrainJobVersion) | |||
m.Post("/stop_version", repo.StopTrainJobVersion) | |||
m.Get("/result_list", repo.ResultList) | |||
}) | |||
}) | |||
}, reqRepoReader(models.UnitTypeCloudBrain)) | |||
}, repoAssignment()) | |||
}) | |||
@@ -141,7 +141,6 @@ func TrainJobGetLog(ctx *context.APIContext) { | |||
var jobID = ctx.Params(":jobid") | |||
var versionName = ctx.Query("version_name") | |||
// var logFileName = ctx.Query("file_name") | |||
var baseLine = ctx.Query("base_line") | |||
var order = ctx.Query("order") | |||
var lines = ctx.Query("lines") | |||
@@ -307,7 +306,80 @@ func ModelList(ctx *context.APIContext) { | |||
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) | |||
return | |||
} | |||
models, err := storage.GetObsListObject(task.JobName, parentDir, versionName) | |||
models, err := storage.GetObsListObject(task.JobName, "output/", parentDir, versionName) | |||
if err != nil { | |||
log.Info("get TrainJobListModel failed:", err) | |||
ctx.ServerError("GetObsListObject:", err) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
"JobID": jobID, | |||
"VersionName": versionName, | |||
"StatusOK": 0, | |||
"Path": dirArray, | |||
"Dirs": models, | |||
"task": task, | |||
"PageIsCloudBrain": true, | |||
}) | |||
} | |||
func GetModelArtsInferenceJob(ctx *context.APIContext) { | |||
var ( | |||
err error | |||
) | |||
jobID := ctx.Params(":jobid") | |||
job, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.NotFound(err) | |||
return | |||
} | |||
result, err := modelarts.GetTrainJob(jobID, strconv.FormatInt(job.VersionID, 10)) | |||
if err != nil { | |||
ctx.NotFound(err) | |||
return | |||
} | |||
job.Status = modelarts.TransTrainJobStatus(result.IntStatus) | |||
job.Duration = result.Duration | |||
job.TrainJobDuration = result.TrainJobDuration | |||
if result.Duration != 0 { | |||
job.TrainJobDuration = addZero(result.Duration/3600000) + ":" + addZero(result.Duration%3600000/60000) + ":" + addZero(result.Duration%60000/1000) | |||
} else { | |||
job.TrainJobDuration = "00:00:00" | |||
} | |||
err = models.UpdateInferenceJob(job) | |||
if err != nil { | |||
log.Error("UpdateJob failed:", err) | |||
} | |||
ctx.JSON(http.StatusOK, map[string]interface{}{ | |||
"JobID": jobID, | |||
"JobStatus": job.Status, | |||
"JobDuration": job.TrainJobDuration, | |||
}) | |||
} | |||
func ResultList(ctx *context.APIContext) { | |||
var ( | |||
err error | |||
) | |||
var jobID = ctx.Params(":jobid") | |||
var versionName = ctx.Query("version_name") | |||
parentDir := ctx.Query("parentDir") | |||
dirArray := strings.Split(parentDir, "/") | |||
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName) | |||
if err != nil { | |||
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) | |||
return | |||
} | |||
models, err := storage.GetObsListObject(task.JobName, "result/", parentDir, versionName) | |||
if err != nil { | |||
log.Info("get TrainJobListModel failed:", err) | |||
ctx.ServerError("GetObsListObject:", err) | |||
@@ -474,6 +474,24 @@ func ShowOneVersionOtherModel(ctx *context.Context) { | |||
func ShowModelTemplate(ctx *context.Context) { | |||
ctx.Data["isModelManage"] = true | |||
repoId := ctx.Repo.Repository.ID | |||
Type := -1 | |||
_, count, _ := models.QueryModel(&models.AiModelQueryOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: 1, | |||
PageSize: 2, | |||
}, | |||
RepoID: repoId, | |||
Type: Type, | |||
New: MODEL_LATEST, | |||
}) | |||
ctx.Data["MODEL_COUNT"] = count | |||
_, trainCount, _ := models.QueryModelTrainJobList(repoId) | |||
log.Info("query train count=" + fmt.Sprint(trainCount)) | |||
ctx.Data["TRAIN_COUNT"] = trainCount | |||
ctx.HTML(200, tplModelManageIndex) | |||
} | |||
@@ -586,3 +604,52 @@ func ModifyModelInfo(ctx *context.Context) { | |||
} | |||
} | |||
func QueryModelListForPredict(ctx *context.Context) { | |||
repoId := ctx.Repo.Repository.ID | |||
modelResult, count, err := models.QueryModel(&models.AiModelQueryOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: -1, | |||
PageSize: -1, | |||
}, | |||
RepoID: repoId, | |||
Type: -1, | |||
New: -1, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Cloudbrain", err) | |||
return | |||
} | |||
log.Info("query return count=" + fmt.Sprint(count)) | |||
nameList := make([]string, 0) | |||
nameMap := make(map[string][]*models.AiModelManage) | |||
for _, model := range modelResult { | |||
if _, value := nameMap[model.Name]; !value { | |||
models := make([]*models.AiModelManage, 0) | |||
models = append(models, model) | |||
nameMap[model.Name] = models | |||
nameList = append(nameList, model.Name) | |||
} else { | |||
nameMap[model.Name] = append(nameMap[model.Name], model) | |||
} | |||
} | |||
mapInterface := make(map[string]interface{}) | |||
mapInterface["nameList"] = nameList | |||
mapInterface["nameMap"] = nameMap | |||
ctx.JSON(http.StatusOK, mapInterface) | |||
} | |||
func QueryModelFileForPredict(ctx *context.Context) { | |||
id := ctx.Query("ID") | |||
model, err := models.QueryModelById(id) | |||
if err != nil { | |||
log.Error("no such model!", err.Error()) | |||
ctx.ServerError("no such model:", err) | |||
return | |||
} | |||
prefix := model.Path[len(setting.Bucket)+1:] | |||
fileinfos, err := storage.GetAllObjectByBucketAndPrefix(setting.Bucket, prefix) | |||
ctx.JSON(http.StatusOK, fileinfos) | |||
} |
@@ -1,9 +1,11 @@ | |||
package repo | |||
import ( | |||
"archive/zip" | |||
"encoding/json" | |||
"errors" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"os" | |||
"path" | |||
@@ -37,6 +39,10 @@ const ( | |||
tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new" | |||
tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show" | |||
tplModelArtsTrainJobVersionNew base.TplName = "repo/modelarts/trainjob/version_new" | |||
tplModelArtsInferenceJobIndex base.TplName = "repo/modelarts/inferencejob/index" | |||
tplModelArtsInferenceJobNew base.TplName = "repo/modelarts/inferencejob/new" | |||
tplModelArtsInferenceJobShow base.TplName = "repo/modelarts/inferencejob/show" | |||
) | |||
func DebugJobIndex(ctx *context.Context) { | |||
@@ -49,8 +55,10 @@ func DebugJobIndex(ctx *context.Context) { | |||
page = 1 | |||
} | |||
debugType := modelarts.DebugType | |||
jobTypeNot := false | |||
if debugListType == models.GPUResource { | |||
debugType = models.TypeCloudBrainOne | |||
jobTypeNot = true | |||
} else if debugListType == models.NPUResource { | |||
debugType = models.TypeCloudBrainTwo | |||
} | |||
@@ -62,8 +70,8 @@ func DebugJobIndex(ctx *context.Context) { | |||
}, | |||
RepoID: repo.ID, | |||
Type: debugType, | |||
JobTypeNot: true, | |||
JobType: string(models.JobTypeTrain), | |||
JobTypeNot: jobTypeNot, | |||
JobType: string(models.JobTypeDebug), | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Get debugjob faild:", err) | |||
@@ -744,7 +752,7 @@ func versionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrai | |||
func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) { | |||
ctx.Data["PageIsTrainJob"] = true | |||
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(modelarts.TotalVersionCount) | |||
VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
description := form.Description | |||
@@ -789,18 +797,11 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) | |||
return | |||
} | |||
// attach, err := models.GetAttachmentByUUID(uuid) | |||
// if err != nil { | |||
// log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error()) | |||
// return | |||
// } | |||
//todo: del the codeLocalPath | |||
// _, err := ioutil.ReadDir(codeLocalPath) | |||
// if err == nil { | |||
// os.RemoveAll(codeLocalPath) | |||
// } | |||
os.RemoveAll(codeLocalPath) | |||
_, err = ioutil.ReadDir(codeLocalPath) | |||
if err == nil { | |||
os.RemoveAll(codeLocalPath) | |||
} | |||
gitRepo, _ := git.OpenRepository(repo.RepoPath()) | |||
commitID, _ := gitRepo.GetBranchCommitID(branch_name) | |||
@@ -968,7 +969,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ | |||
ctx.ServerError("GetCloudbrainByJobIDAndIsLatestVersion faild:", err) | |||
return | |||
} | |||
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(latestTask.TotalVersionCount + 1) | |||
VersionOutputPath := modelarts.GetOutputPathByCount(latestTask.TotalVersionCount + 1) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
@@ -1006,18 +1007,17 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ | |||
return | |||
} | |||
// attach, err := models.GetAttachmentByUUID(uuid) | |||
// if err != nil { | |||
// log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error()) | |||
// return | |||
// } | |||
//todo: del the codeLocalPath | |||
// _, err = ioutil.ReadDir(codeLocalPath) | |||
// if err == nil { | |||
// os.RemoveAll(codeLocalPath) | |||
// } | |||
os.RemoveAll(codeLocalPath) | |||
_, err = ioutil.ReadDir(codeLocalPath) | |||
if err == nil { | |||
os.RemoveAll(codeLocalPath) | |||
} else { | |||
log.Error("创建任务失败,原代码还未删除,请重试!: %s (%v)", repo.FullName(), err) | |||
versionErrorDataPrepare(ctx, form) | |||
ctx.RenderWithErr("创建任务失败,原代码还未删除,请重试!", tplModelArtsTrainJobVersionNew, &form) | |||
return | |||
} | |||
// os.RemoveAll(codeLocalPath) | |||
gitRepo, _ := git.OpenRepository(repo.RepoPath()) | |||
commitID, _ := gitRepo.GetBranchCommitID(branch_name) | |||
@@ -1255,6 +1255,37 @@ func paramCheckCreateTrainJob(form auth.CreateModelArtsTrainJobForm) error { | |||
return nil | |||
} | |||
func paramCheckCreateInferenceJob(form auth.CreateModelArtsInferenceJobForm) error { | |||
if !strings.HasSuffix(form.BootFile, ".py") { | |||
log.Error("the boot file(%s) must be a python file", form.BootFile) | |||
return errors.New("启动文件必须是python文件") | |||
} | |||
if form.WorkServerNumber > 25 || form.WorkServerNumber < 1 { | |||
log.Error("the WorkServerNumber(%d) must be in (1,25)", form.WorkServerNumber) | |||
return errors.New("计算节点数必须在1-25之间") | |||
} | |||
if form.ModelName == "" { | |||
log.Error("the ModelName(%d) must not be nil", form.ModelName) | |||
return errors.New("模型名称不能为空") | |||
} | |||
if form.ModelVersion == "" { | |||
log.Error("the ModelVersion(%d) must not be nil", form.ModelVersion) | |||
return errors.New("模型版本不能为空") | |||
} | |||
if form.CkptName == "" { | |||
log.Error("the CkptName(%d) must not be nil", form.CkptName) | |||
return errors.New("权重文件不能为空") | |||
} | |||
if form.BranchName == "" { | |||
log.Error("the Branch(%d) must not be nil", form.BranchName) | |||
return errors.New("分支名不能为空") | |||
} | |||
return nil | |||
} | |||
func TrainJobShow(ctx *context.Context) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
var jobID = ctx.Params(":jobid") | |||
@@ -1289,7 +1320,7 @@ func TrainJobShow(ctx *context.Context) { | |||
ctx.Data["canNewJob"] = canNewJob | |||
//将运行参数转化为epoch_size = 3, device_target = Ascend的格式 | |||
for i, task := range VersionListTasks { | |||
for i, _ := range VersionListTasks { | |||
var parameters models.Parameters | |||
@@ -1310,9 +1341,6 @@ func TrainJobShow(ctx *context.Context) { | |||
} else { | |||
VersionListTasks[i].Parameters = "" | |||
} | |||
VersionListTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain) | |||
VersionListTasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain) | |||
} | |||
pager := context.NewPagination(VersionListCount, setting.UI.IssuePagingNum, page, 5) | |||
@@ -1514,6 +1542,404 @@ func getConfigList(perPage, page int, sortBy, order, searchContent, configType s | |||
return list, nil | |||
} | |||
func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) { | |||
ctx.Data["PageIsTrainJob"] = true | |||
VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
description := form.Description | |||
workServerNumber := form.WorkServerNumber | |||
engineID := form.EngineID | |||
bootFile := form.BootFile | |||
flavorCode := form.Flavor | |||
params := form.Params | |||
poolID := form.PoolID | |||
repo := ctx.Repo.Repository | |||
codeLocalPath := setting.JobPath + jobName + modelarts.CodePath | |||
codeObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.CodePath | |||
resultObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.ResultPath + VersionOutputPath + "/" | |||
logObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.LogPath + VersionOutputPath + "/" | |||
dataPath := "/" + setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + uuid + "/" | |||
branch_name := form.BranchName | |||
FlavorName := form.FlavorName | |||
EngineName := form.EngineName | |||
LabelName := form.LabelName | |||
isLatestVersion := modelarts.IsLatestVersion | |||
VersionCount := modelarts.VersionCount | |||
trainUrl := form.TrainUrl | |||
modelName := form.ModelName | |||
modelVersion := form.ModelVersion | |||
ckptName := form.CkptName | |||
ckptUrl := form.TrainUrl + form.CkptName | |||
if err := paramCheckCreateInferenceJob(form); err != nil { | |||
log.Error("paramCheckCreateInferenceJob failed:(%v)", err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
count, err := models.GetCloudbrainInferenceJobCountByUserID(ctx.User.ID) | |||
if err != nil { | |||
log.Error("GetCloudbrainInferenceJobCountByUserID failed:%v", err, ctx.Data["MsgID"]) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("system error", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} else { | |||
if count >= 1 { | |||
log.Error("the user already has running or waiting inference task", ctx.Data["MsgID"]) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("you have already a running or waiting inference task, can not create more", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
} | |||
//todo: del the codeLocalPath | |||
_, err = ioutil.ReadDir(codeLocalPath) | |||
if err == nil { | |||
os.RemoveAll(codeLocalPath) | |||
} | |||
gitRepo, _ := git.OpenRepository(repo.RepoPath()) | |||
commitID, _ := gitRepo.GetBranchCommitID(branch_name) | |||
if err := git.Clone(repo.RepoPath(), codeLocalPath, git.CloneRepoOptions{ | |||
Branch: branch_name, | |||
}); err != nil { | |||
log.Error("创建任务失败,服务器超时!: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("创建任务失败,服务器超时!", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
//todo: upload code (send to file_server todo this work?) | |||
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.ResultPath + VersionOutputPath + "/"); err != nil { | |||
log.Error("Failed to obsMkdir_result: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("Failed to obsMkdir_result", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.LogPath + VersionOutputPath + "/"); err != nil { | |||
log.Error("Failed to obsMkdir_log: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("Failed to obsMkdir_log", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil { | |||
log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("Failed to uploadCodeToObs", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
//todo: del local code? | |||
var parameters models.Parameters | |||
param := make([]models.Parameter, 0) | |||
param = append(param, models.Parameter{ | |||
Label: modelarts.ResultUrl, | |||
Value: "s3:/" + resultObsPath, | |||
}, models.Parameter{ | |||
Label: modelarts.CkptUrl, | |||
Value: "s3:/" + ckptUrl, | |||
}) | |||
if len(params) != 0 { | |||
err := json.Unmarshal([]byte(params), ¶meters) | |||
if err != nil { | |||
log.Error("Failed to Unmarshal params: %s (%v)", params, err) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr("运行参数错误", tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
for _, parameter := range parameters.Parameter { | |||
if parameter.Label != modelarts.TrainUrl && parameter.Label != modelarts.DataUrl { | |||
param = append(param, models.Parameter{ | |||
Label: parameter.Label, | |||
Value: parameter.Value, | |||
}) | |||
} | |||
} | |||
} | |||
req := &modelarts.GenerateInferenceJobReq{ | |||
JobName: jobName, | |||
DataUrl: dataPath, | |||
Description: description, | |||
CodeObsPath: codeObsPath, | |||
BootFileUrl: codeObsPath + bootFile, | |||
BootFile: bootFile, | |||
TrainUrl: trainUrl, | |||
FlavorCode: flavorCode, | |||
WorkServerNumber: workServerNumber, | |||
EngineID: int64(engineID), | |||
LogUrl: logObsPath, | |||
PoolID: poolID, | |||
Uuid: uuid, | |||
Parameters: param, //modelarts训练时用到 | |||
CommitID: commitID, | |||
BranchName: branch_name, | |||
Params: form.Params, | |||
FlavorName: FlavorName, | |||
EngineName: EngineName, | |||
LabelName: LabelName, | |||
IsLatestVersion: isLatestVersion, | |||
VersionCount: VersionCount, | |||
TotalVersionCount: modelarts.TotalVersionCount, | |||
ModelName: modelName, | |||
ModelVersion: modelVersion, | |||
CkptName: ckptName, | |||
ResultUrl: resultObsPath, | |||
} | |||
//将params转换Parameters.Parameter,出错时返回给前端 | |||
// var Parameters modelarts.Parameters | |||
// if err := json.Unmarshal([]byte(params), &Parameters); err != nil { | |||
// ctx.ServerError("json.Unmarshal failed:", err) | |||
// return | |||
// } | |||
err = modelarts.GenerateInferenceJob(ctx, req) | |||
if err != nil { | |||
log.Error("GenerateTrainJob failed:%v", err.Error()) | |||
inferenceJobErrorNewDataPrepare(ctx, form) | |||
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form) | |||
return | |||
} | |||
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/inference-job") | |||
} | |||
func InferenceJobIndex(ctx *context.Context) { | |||
MustEnableModelArts(ctx) | |||
repo := ctx.Repo.Repository | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
PageSize: setting.UI.IssuePagingNum, | |||
}, | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
JobType: string(models.JobTypeInference), | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Cloudbrain", err) | |||
return | |||
} | |||
for i, task := range tasks { | |||
tasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain) | |||
tasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain) | |||
tasks[i].ComputeResource = models.NPUResource | |||
} | |||
repoId := ctx.Repo.Repository.ID | |||
Type := -1 | |||
_, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: 1, | |||
PageSize: 2, | |||
}, | |||
RepoID: repoId, | |||
Type: Type, | |||
New: MODEL_LATEST, | |||
}) | |||
ctx.Data["MODEL_COUNT"] = model_count | |||
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
ctx.Data["Page"] = pager | |||
ctx.Data["PageIsCloudBrain"] = true | |||
ctx.Data["Tasks"] = tasks | |||
ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx) | |||
ctx.Data["RepoIsEmpty"] = repo.IsEmpty | |||
ctx.HTML(200, tplModelArtsInferenceJobIndex) | |||
} | |||
func InferenceJobNew(ctx *context.Context) { | |||
err := inferenceJobNewDataPrepare(ctx) | |||
if err != nil { | |||
ctx.ServerError("get new inference-job info failed", err) | |||
return | |||
} | |||
ctx.HTML(200, tplModelArtsInferenceJobNew) | |||
} | |||
func inferenceJobNewDataPrepare(ctx *context.Context) error { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
t := time.Now() | |||
var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] | |||
ctx.Data["job_name"] = jobName | |||
attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID) | |||
if err != nil { | |||
ctx.ServerError("GetAllUserAttachments failed:", err) | |||
return err | |||
} | |||
ctx.Data["attachments"] = attachs | |||
var resourcePools modelarts.ResourcePool | |||
if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["resource_pools"] = resourcePools.Info | |||
var engines modelarts.Engine | |||
if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engines"] = engines.Info | |||
var versionInfos modelarts.VersionInfo | |||
if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engine_versions"] = versionInfos.Version | |||
var flavorInfos modelarts.Flavor | |||
if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["flavor_infos"] = flavorInfos.Info | |||
ctx.Data["params"] = "" | |||
ctx.Data["branchName"] = ctx.Repo.BranchName | |||
configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom) | |||
if err != nil { | |||
ctx.ServerError("getConfigList failed:", err) | |||
return err | |||
} | |||
ctx.Data["config_list"] = configList.ParaConfigs | |||
return nil | |||
} | |||
func inferenceJobErrorNewDataPrepare(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) error { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
t := time.Now() | |||
var jobName = "inference" + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] | |||
ctx.Data["job_name"] = jobName | |||
attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID) | |||
if err != nil { | |||
ctx.ServerError("GetAllUserAttachments failed:", err) | |||
return err | |||
} | |||
ctx.Data["attachments"] = attachs | |||
var resourcePools modelarts.ResourcePool | |||
if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["resource_pools"] = resourcePools.Info | |||
var engines modelarts.Engine | |||
if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engines"] = engines.Info | |||
var versionInfos modelarts.VersionInfo | |||
if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["engine_versions"] = versionInfos.Version | |||
var flavorInfos modelarts.Flavor | |||
if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["flavor_infos"] = flavorInfos.Info | |||
configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom) | |||
if err != nil { | |||
ctx.ServerError("getConfigList failed:", err) | |||
return err | |||
} | |||
var Parameters modelarts.Parameters | |||
if err = json.Unmarshal([]byte(form.Params), &Parameters); err != nil { | |||
ctx.ServerError("json.Unmarshal failed:", err) | |||
return err | |||
} | |||
ctx.Data["params"] = Parameters.Parameter | |||
ctx.Data["config_list"] = configList.ParaConfigs | |||
ctx.Data["bootFile"] = form.BootFile | |||
ctx.Data["uuid"] = form.Attachment | |||
ctx.Data["branch_name"] = form.BranchName | |||
ctx.Data["model_name"] = form.ModelName | |||
ctx.Data["model_version"] = form.ModelVersion | |||
ctx.Data["ckpt_name"] = form.CkptName | |||
return nil | |||
} | |||
func InferenceJobShow(ctx *context.Context) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
var jobID = ctx.Params(":jobid") | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
log.Error("GetInferenceTask(%s) failed:%v", jobID, err.Error()) | |||
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobShow, nil) | |||
return | |||
} | |||
//设置权限 | |||
canNewJob, err := canUserCreateTrainJobVersion(ctx, task.UserID) | |||
if err != nil { | |||
ctx.ServerError("canNewJob failed", err) | |||
return | |||
} | |||
ctx.Data["canNewJob"] = canNewJob | |||
//将运行参数转化为epoch_size = 3, device_target = Ascend的格式 | |||
var parameters models.Parameters | |||
err = json.Unmarshal([]byte(task.Parameters), ¶meters) | |||
if err != nil { | |||
log.Error("Failed to Unmarshal Parameters: %s (%v)", task.Parameters, err) | |||
trainJobNewDataPrepare(ctx) | |||
return | |||
} | |||
if len(parameters.Parameter) > 0 { | |||
paramTemp := "" | |||
for _, Parameter := range parameters.Parameter { | |||
param := Parameter.Label + " = " + Parameter.Value + "; " | |||
paramTemp = paramTemp + param | |||
} | |||
task.Parameters = paramTemp[:len(paramTemp)-2] | |||
} else { | |||
task.Parameters = "" | |||
} | |||
LabelName := strings.Split(strings.TrimSpace(task.LabelName), " ") | |||
ctx.Data["labelName"] = LabelName | |||
ctx.Data["jobID"] = jobID | |||
ctx.Data["jobName"] = task.JobName | |||
ctx.Data["task"] = task | |||
ctx.Data["userName"] = ctx.User.Name | |||
ctx.HTML(http.StatusOK, tplModelArtsInferenceJobShow) | |||
} | |||
func ModelDownload(ctx *context.Context) { | |||
var ( | |||
err error | |||
@@ -1542,6 +1968,31 @@ func ModelDownload(ctx *context.Context) { | |||
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) | |||
} | |||
func ResultDownload(ctx *context.Context) { | |||
var ( | |||
err error | |||
) | |||
var jobID = ctx.Params(":jobid") | |||
versionName := ctx.Query("version_name") | |||
parentDir := ctx.Query("parent_dir") | |||
fileName := ctx.Query("file_name") | |||
log.Info("DownloadResult start.") | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.Data["error"] = err.Error() | |||
} | |||
path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName, parentDir, fileName), "/") | |||
log.Info("Download path is:%s", path) | |||
url, err := storage.GetObsCreateSignedUrlByBucketAndKey(setting.Bucket, path) | |||
if err != nil { | |||
log.Error("GetObsCreateSignedUrl failed: %v", err.Error(), ctx.Data["msgID"]) | |||
ctx.ServerError("GetObsCreateSignedUrl", err) | |||
return | |||
} | |||
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) | |||
} | |||
func DeleteJobStorage(jobName string) error { | |||
//delete local | |||
localJobPath := setting.JobPath + jobName | |||
@@ -1559,3 +2010,69 @@ func DeleteJobStorage(jobName string) error { | |||
return nil | |||
} | |||
func DownloadMultiResultFile(ctx *context.Context) { | |||
var jobID = ctx.Params(":jobid") | |||
var versionName = ctx.Query("version_name") | |||
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName) | |||
if err != nil { | |||
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) | |||
return | |||
} | |||
// if !isCanDeleteOrDownload(ctx, task) { | |||
// ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) | |||
// return | |||
// } | |||
// path := Model_prefix + models.AttachmentRelativePath(id) + "/" | |||
path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName), "/") + "/" | |||
allFile, err := storage.GetAllObjectByBucketAndPrefix(setting.Bucket, path) | |||
if err == nil { | |||
//count++ | |||
// models.ModifyModelDownloadCount(id) | |||
returnFileName := task.JobName + ".zip" | |||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+returnFileName) | |||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream") | |||
w := zip.NewWriter(ctx.Resp) | |||
defer w.Close() | |||
for _, oneFile := range allFile { | |||
if oneFile.IsDir { | |||
log.Info("zip dir name:" + oneFile.FileName) | |||
} else { | |||
log.Info("zip file name:" + oneFile.FileName) | |||
fDest, err := w.Create(oneFile.FileName) | |||
if err != nil { | |||
log.Info("create zip entry error, download file failed: %s\n", err.Error()) | |||
ctx.ServerError("download file failed:", err) | |||
return | |||
} | |||
body, err := storage.ObsDownloadAFile(setting.Bucket, path+oneFile.FileName) | |||
if err != nil { | |||
log.Info("download file failed: %s\n", err.Error()) | |||
ctx.ServerError("download file failed:", err) | |||
return | |||
} else { | |||
defer body.Close() | |||
p := make([]byte, 1024) | |||
var readErr error | |||
var readCount int | |||
// 读取对象内容 | |||
for { | |||
readCount, readErr = body.Read(p) | |||
if readCount > 0 { | |||
fDest.Write(p[:readCount]) | |||
} | |||
if readErr != nil { | |||
break | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} else { | |||
log.Info("error,msg=" + err.Error()) | |||
ctx.ServerError("no file to download.", err) | |||
} | |||
} |
@@ -992,6 +992,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/show_model_child_api", repo.ShowOneVersionOtherModel) | |||
m.Get("/query_train_job", reqRepoCloudBrainReader, repo.QueryTrainJobList) | |||
m.Get("/query_train_job_version", reqRepoCloudBrainReader, repo.QueryTrainJobVersionList) | |||
m.Get("/query_model_for_predict", reqRepoCloudBrainReader, repo.QueryModelListForPredict) | |||
m.Get("/query_modelfile_for_predict", reqRepoCloudBrainReader, repo.QueryModelFileForPredict) | |||
m.Group("/:ID", func() { | |||
m.Get("", repo.ShowSingleModel) | |||
m.Get("/downloadsingle", repo.DownloadSingleModelFile) | |||
@@ -1030,6 +1032,17 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) | |||
}) | |||
m.Group("/inference-job", func() { | |||
m.Get("", reqRepoCloudBrainReader, repo.InferenceJobIndex) | |||
m.Group("/:jobid", func() { | |||
m.Get("", reqRepoCloudBrainReader, repo.InferenceJobShow) | |||
m.Get("/result_download", reqRepoCloudBrainReader, repo.ResultDownload) | |||
m.Get("/downloadall", repo.DownloadMultiResultFile) | |||
}) | |||
m.Get("/create", reqRepoCloudBrainWriter, repo.InferenceJobNew) | |||
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate) | |||
}) | |||
}, context.RepoRef()) | |||
m.Group("/blockchain", func() { | |||
@@ -217,6 +217,7 @@ | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="active item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
@@ -388,7 +389,7 @@ | |||
<div class="item" style="padding: 0 !important;"> | |||
<!-- 接收结果 --> | |||
<iframe src="" frameborder="0" name="iframeContent" style="display: none;"></iframe> | |||
{{if .CanDel}} | |||
{{if .CanDebug}} | |||
<a id="model-image-{{.JobID}}" class='imageBtn ui basic {{if ne .Status "RUNNING"}}disabled {{else}}blue {{end}}button'>{{$.i18n.Tr "repo.submit_image"}}</a> | |||
{{else}} | |||
<a class="imageBtn ui basic disabled button">{{$.i18n.Tr "repo.submit_image"}}</a> | |||
@@ -396,7 +397,7 @@ | |||
</div> | |||
<div class="item" style="padding: 0 !important;"> | |||
<!-- 模型下载 --> | |||
{{if .CanDel}} | |||
{{if .CanDebug}} | |||
<a class="ui basic blue button" href="{{$.RepoLink}}/cloudbrain/{{.JobID}}/models" target="_blank">{{$.i18n.Tr "repo.download"}}</a> | |||
{{else}} | |||
<a class="ui basic disabled button">{{$.i18n.Tr "repo.download"}}</a> | |||
@@ -488,6 +489,7 @@ | |||
<script> | |||
// 调试和评分新开窗口 | |||
console.log({{.Tasks}}) | |||
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||
let url={{.RepoLink}} | |||
let getParam=getQueryVariable('debugListType') | |||
@@ -0,0 +1,318 @@ | |||
<!-- 头部导航栏 --> | |||
{{template "base/head" .}} | |||
<style> | |||
.fontsize14{ | |||
font-size: 14px; | |||
} | |||
.padding0{ | |||
padding: 0 !important; | |||
} | |||
</style> | |||
<!-- 弹窗 --> | |||
<div id="mask"> | |||
<div id="loadingPage"> | |||
<div class="rect1"></div> | |||
<div class="rect2"></div> | |||
<div class="rect3"></div> | |||
<div class="rect4"></div> | |||
<div class="rect5"></div> | |||
</div> | |||
</div> | |||
<!-- 提示框 --> | |||
<div class="alert"></div> | |||
<div class="repository release dataset-list view"> | |||
{{template "repo/header" .}} | |||
<!-- 列表容器 --> | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
<div class="ui two column stackable grid "> | |||
<div class="column"> | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> | |||
<a class="active item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
{{if .Permission.CanWrite $.UnitTypeCloudBrain}} | |||
<a class="ui green button" href="{{.RepoLink}}/modelarts/inference-job/create">{{$.i18n.Tr "repo.modelarts.train_job.new_infer"}}</a> | |||
{{else}} | |||
<a class="ui disabled button" >{{$.i18n.Tr "repo.modelarts.train_job.new_infer"}}</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
{{if eq 0 (len .Tasks)}} | |||
<div class="ui placeholder segment bgtask-none"> | |||
<div class="ui icon header bgtask-header-pic"></div> | |||
<div class="bgtask-content-header">未创建过推理任务</div> | |||
<div class="bgtask-content"> | |||
{{if $.RepoIsEmpty}} | |||
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div> | |||
{{end}} | |||
{{if eq 0 $.MODEL_COUNT}} | |||
<div class="bgtask-content-txt">模型文件:您还没有模型文件,请先通过<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>产生并 <a href="{{.RepoLink}}/modelmanage/show_model">导出模型</a> ;</div> | |||
{{end}} | |||
<div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div> | |||
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||
</div> | |||
</div> | |||
{{else}} | |||
<!-- 中下列表展示区 --> | |||
<div class="ui grid"> | |||
<div class="row"> | |||
<div class="ui sixteen wide column"> | |||
<!-- 任务展示 --> | |||
<div class="dataset list"> | |||
<!-- 表头 --> | |||
<div class="ui grid stackable" style="background: #f0f0f0;;"> | |||
<div class="row"> | |||
<div class="three wide column padding0"> | |||
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span> | |||
</div> | |||
<div class="three wide column text center padding0"> | |||
<span style="margin:0 6px">{{$.i18n.Tr "repo.modelarts.infer_job.model_version"}}</span> | |||
</div> | |||
<div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.modelarts.status"}}</span> | |||
</div> | |||
<div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.modelarts.createtime"}}</span> | |||
</div> | |||
<div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_status_runtime"}}</span> | |||
</div> | |||
<!-- <div class="two wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.modelarts.computing_resources"}}</span> | |||
</div> --> | |||
<div class="one wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span> | |||
</div> | |||
<div class="three wide column text center padding0"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
{{range .Tasks}} | |||
<div class="ui grid stackable item"> | |||
<div class="row"> | |||
<!-- 任务名 --> | |||
<div class="three wide column padding0"> | |||
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;"> | |||
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span> | |||
</a> | |||
</div> | |||
<!-- 模型版本 --> | |||
<div class="three wide column text center padding0"> | |||
<a >{{.ModelName}} </a>/ <span style="font-size: 12px;">{{.ModelVersion}} </span> | |||
</div> | |||
<!-- 任务状态 --> | |||
<div class="two wide column text center padding0" > | |||
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}"> | |||
<span><i id="{{.JobID}}-icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="{{.JobID}}-text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span> | |||
</span> | |||
</div> | |||
<!-- 任务创建时间 --> | |||
<div class="two wide column text center padding0"> | |||
<span style="font-size: 12px;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span> | |||
</div> | |||
<!-- 任务运行时间 --> | |||
<div class="two wide column text center padding0"> | |||
<span style="font-size: 12px;" id="duration-{{.JobID}}">{{.TrainJobDuration}}</span> | |||
</div> | |||
<!-- 计算资源 --> | |||
<!-- <div class="two wide column text center padding0"> | |||
<span style="font-size: 12px;">{{.ComputeResource}}</span> | |||
</div> --> | |||
<!-- 创建者 --> | |||
<div class="one wide column text center padding0"> | |||
{{if .User.Name}} | |||
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a> | |||
{{else}} | |||
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a> | |||
{{end}} | |||
</div> | |||
<div class="three wide column text center padding0"> | |||
<!-- 停止任务 --> | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})"> | |||
{{$.i18n.Tr "repo.stop"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic disabled button"> | |||
{{$.i18n.Tr "repo.stop"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
<!-- 下载 --> | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanModify}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-download-{{.JobID}}" href="{{$.RepoLink}}/modelarts/inference-job/{{.JobID}}/downloadall?version_name={{.VersionName}}" class="ui basic blue button" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.model_download"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.model_download"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
<!-- 删除任务 --> | |||
<div class="ui compact buttons"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanDel}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this,{{.VersionName}},{{.JobID}})" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.delete"}} | |||
</a> | |||
{{else}} | |||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;"> | |||
{{$.i18n.Tr "repo.delete"}} | |||
</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} {{template "base/paginate" .}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 确认模态框 --> | |||
<div id="deletemodel"> | |||
<div class="ui basic modal"> | |||
<div class="ui icon header"> | |||
<i class="trash icon"></i> 删除任务 | |||
</div> | |||
<div class="content"> | |||
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p> | |||
</div> | |||
<div class="actions"> | |||
<div class="ui red basic inverted cancel button"> | |||
<i class="remove icon"></i> 取消操作 | |||
</div> | |||
<div class="ui green basic inverted ok button"> | |||
<i class="checkmark icon"></i> 确定操作 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
// 加载任务状态 | |||
var timeid = window.setInterval(loadJobStatus, 15000); | |||
$(document).ready(loadJobStatus); | |||
function loadJobStatus() { | |||
$(".job-status").each((index, job) => { | |||
const jobID = job.dataset.jobid | |||
const repoPath = job.dataset.repopath | |||
const versionname = job.dataset.version | |||
const status_text = $(`#${jobID}-text`).text() | |||
if(['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED'].includes(status_text)){ | |||
return | |||
} | |||
$.get(`/api/v1/repos/${repoPath}/modelarts/inference-job/${jobID}?version_name=${versionname}`, (data) => { | |||
const jobID = data.JobID | |||
const status = data.JobStatus | |||
const duration = data.JobDuration | |||
$('#duration-'+jobID).text(duration) | |||
if (status != job.textContent.trim()) { | |||
$('#' + jobID+'-icon').removeClass().addClass(status) | |||
$('#' + jobID+ '-text').text(status) | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
}); | |||
}; | |||
function deleteVersion(version_name,jobID){ | |||
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/del_version' | |||
$.post(url,{version_name:version_name},(data)=>{ | |||
if(data.StatusOK===0){ | |||
location.reload() | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function stopVersion(version_name,jobID){ | |||
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/stop_version' | |||
$.post(url,{version_name:version_name},(data)=>{ | |||
if(data.StatusOK===0){ | |||
$('#'+version_name+'-stop').removeClass('blue') | |||
$('#'+version_name+'-stop').addClass('disabled') | |||
refreshStatus(version_name,jobID) | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function refreshStatus(version_name,jobID){ | |||
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'?version_name='+version_name | |||
$.get(url,(data)=>{ | |||
$(`#${jobID}-icon`).attr("class",data.JobStatus) | |||
// detail status and duration | |||
$(`#${jobID}-text`).text(data.JobStatus) | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function assertDelete(obj,version_name,jobID) { | |||
if (obj.style.color == "rgb(204, 204, 204)") { | |||
return | |||
} else { | |||
// var delId = obj.parentNode.id | |||
flag = 1; | |||
$('.ui.basic.modal') | |||
.modal({ | |||
onDeny: function() { | |||
flag = false | |||
}, | |||
onApprove: function() { | |||
// document.getElementById(delId).submit() | |||
deleteVersion(version_name,jobID) | |||
flag = true | |||
}, | |||
onHidden: function() { | |||
if (flag == false) { | |||
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut(); | |||
} | |||
} | |||
}) | |||
.modal('show') | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,465 @@ | |||
{{template "base/head" .}} | |||
<style> | |||
.unite{ | |||
font-family: SourceHanSansSC-medium !important; | |||
color: rgba(16, 16, 16, 100) !important; | |||
} | |||
.title{ | |||
font-size: 16px !important; | |||
padding-left: 3rem !important; | |||
} | |||
.min_title{ | |||
font-size: 14px !important; | |||
padding-left: 6rem !important; | |||
margin-bottom: 2rem !important; | |||
} | |||
.width80{ | |||
width: 80.7% !important; | |||
} | |||
.width84{ | |||
width: 84% !important; | |||
margin-left: 5.1rem !important; | |||
} | |||
.width35{ | |||
width: 35.5% !important; | |||
} | |||
.nowrap { | |||
white-space: nowrap !important; | |||
} | |||
</style> | |||
<div id="mask"> | |||
<div id="loadingPage"> | |||
<div class="rect1"></div> | |||
<div class="rect2"></div> | |||
<div class="rect3"></div> | |||
<div class="rect4"></div> | |||
<div class="rect5"></div> | |||
</div> | |||
</div> | |||
<div class="repository"> | |||
{{template "repo/header" .}} | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
<h4 class="ui top attached header"> | |||
{{.i18n.Tr "repo.modelarts.train_job.new"}} | |||
</h4> | |||
<div class="ui attached segment"> | |||
<!-- equal width --> | |||
<form class="ui form" action="{{.Link}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<input type="hidden" name="action" value="update"> | |||
<input type="hidden" id="ai_engine_name" name="engine_names" value=""> | |||
<input type="hidden" id="ai_flaver_name" name="flaver_names" value=""> | |||
<input type="hidden" id="ai_model_version" name="model_version" value=""> | |||
<input type="hidden" id="ai_model_label" name="label_names" value=""> | |||
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label> | |||
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="36"> | |||
<span class="tooltips" style="display: block;">请输入只包含大小写字母、数字、_和-,最长36个字符。</span> | |||
</div> | |||
<div class="unite min_title inline field"> | |||
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}</label> | |||
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea> | |||
</div> | |||
<div class="ui divider"></div> | |||
<!-- 模型相关配置 --> | |||
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}:</h4> | |||
<div class="required unite inline min_title fields" style="width: 91.8%;"> | |||
<div class="required eight wide field"> | |||
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.modelarts.infer_job.select_model"}}</label> | |||
<div class="ui fluid search selection dropdown loading" id="select_model"> | |||
{{if $.ckpt_name}} | |||
<input type="hidden" name="model_name" value="{{$.model_name}}" required> | |||
<div class="text">{{$.model_name}}</div> | |||
{{else}} | |||
<input type="hidden" name="model_name" required> | |||
<div class="text"></div> | |||
{{end}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" id="model_name"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="three wide field"> | |||
<div class="ui fluid search selection dropdown" id="select_model_version"> | |||
{{if $.ckpt_name}} | |||
<input type="hidden" name="train_url" value="{{$.model_version}}" required> | |||
<div class="text">{{$.model_version}}</div> | |||
{{else}} | |||
<input type="hidden" name="train_url" required> | |||
<div class="text"></div> | |||
{{end}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" id="model_name_version"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="five wide field"> | |||
<div class="ui fluid search selection dropdown" id="select_model_checkpoint"> | |||
{{if $.ckpt_name}} | |||
<input type="hidden" name="ckpt_name" value="{{$.ckpt_name}}" required> | |||
<div class="text">{{$.ckpt_name}}</div> | |||
{{else}} | |||
<input type="hidden" name="ckpt_name" required> | |||
<div class="text"></div> | |||
{{end}} | |||
<i class="dropdown icon"></i> | |||
<div class="menu" id="model_checkpoint"> | |||
</div> | |||
</div> | |||
</div> | |||
<span > | |||
<i class="question circle icon" data-content="模型文件位置存储在环境变量ckpt_path中。" data-position="top center" data-variation="inverted mini"></i> | |||
</span> | |||
</div> | |||
<!-- AI引擎 --> | |||
<div class="required unite inline min_title fields" style="width: 90%;"> | |||
<div class="required eight wide field"> | |||
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}</label> | |||
<select class="ui fluid selection search dropdown" id="trainjob_engines"> | |||
{{range .engines}} | |||
<option value="{{.Value}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<div class="eight wide field" id="engine_name"> | |||
<select class="ui fluid selection dropdown nowrap" id="trainjob_engine_versions" name="engine_id" style="white-space: nowrap;"> | |||
{{range .engine_versions}} | |||
<option name="engine_id" value="{{.ID}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
</div> | |||
<!-- 代码分支 --> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.code_version"}}</label> | |||
<select class="ui dropdown width35" id="code_version" name="branch_name"> | |||
{{if .branch_name}} | |||
<option name="branch_name" value="{{.branch_name}}">{{.branch_name}}</option> | |||
{{range $k, $v :=.Branches}} | |||
{{ if ne $v $.branch_name }} | |||
<option name="branch_name" value="{{$v}}">{{$v}}</option> | |||
{{end}} | |||
{{end}} | |||
{{else}} | |||
<option name="branch_name" value="{{.branchName}}">{{.branchName}}</option> | |||
{{range $k, $v :=.Branches}} | |||
{{ if ne $v $.branchName }} | |||
<option name="branch_name" value="{{$v}}">{{$v}}</option> | |||
{{end}} | |||
{{end}} | |||
{{end}} | |||
</select> | |||
</div> | |||
<!-- 数据集 --> | |||
<div class="required unite min_title inline field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label> | |||
<select class="ui dropdown width35" id="trainjob_datasets" name="attachment" placeholder="选择数据集" required> | |||
{{if $.uuid}} | |||
<option name="attachment" value="{{$.uuid}}">{{$.datasetName}}</option> | |||
{{end}} | |||
{{range .attachments}} | |||
<option value="">选择数据集</option> | |||
<option name="attachment" value="{{.UUID}}">{{.Attachment.Name}}</option> | |||
{{end}} | |||
</select> | |||
<span> | |||
<i class="question circle icon" data-content="数据集位置存储在环境变量data_url中。" data-position="top center" data-variation="inverted mini"></i> | |||
</span> | |||
<span class="tooltips" style="display: block;">数据集位置存储在环境变量data_url中,推理输出路径存储在环境变量result_url中。</span> | |||
</div> | |||
<!-- 启动文件 --> | |||
<div class="inline unite min_title field required"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> | |||
{{if .bootFile}} | |||
<input style="width: 35.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="254" > | |||
{{else}} | |||
<input style="width: 35.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" > | |||
{{end}} | |||
<span > | |||
<i class="question circle icon" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="top center" data-variation="inverted mini"></i> | |||
</span> | |||
<a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a> | |||
</div> | |||
<!-- 运行参数 --> | |||
<div class="inline unite min_title field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label> | |||
<span id="add_run_para" style="margin-left: 0.5rem;cursor:pointer;color: rgba(3, 102, 214, 100);font-size: 14px;line-height: 26px;font-family: SourceHanSansSC-medium;"><i class="plus square outline icon"></i>{{.i18n.Tr "repo.modelarts.train_job.add_run_parameter"}}</span> | |||
<input id="store_run_para" type="hidden" name="run_para_list"> | |||
<div class="dynamic field" style="margin-top: 1rem;"> | |||
{{if ne 0 (len .params)}} | |||
{{range $k ,$v := .params}} | |||
<div class="two fields width84" id="para{{$k}}"> | |||
<div class="field"> | |||
<input type="text" name="shipping_first-name" value={{$v.Label}} required> | |||
</div> | |||
<div class="field"> | |||
<input type="text" name="shipping_last-name" value={{$v.Value}} required> | |||
</div> | |||
<span> | |||
<i class="trash icon"></i> | |||
</span> | |||
</div> | |||
{{end}} | |||
{{end}} | |||
</div> | |||
</div> | |||
<div class="required field " style="display: none;"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.resource_pool"}}</label> | |||
<select class="ui dropdown" id="trainjob_resource_pool" style='width:385px' name="pool_id"> | |||
{{range .resource_pools}} | |||
<option value="{{.ID}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<!-- 规格 --> | |||
<div class="required unite min_title inline field" id="flaver_name"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> | |||
<select class="ui dropdown width80" id="trainjob-flavor" name="flavor"> | |||
{{range .flavor_infos}} | |||
<option name="flavor" value="{{.Code}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<!-- 计算节点 --> | |||
<div class="inline required unite min_title field"> | |||
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> | |||
<div class="ui labeled input" style="width: 5%;"> | |||
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254" value="1" readonly> | |||
</div> | |||
</div> | |||
<!-- 表单操作 --> | |||
<div class="inline unite min_title field"> | |||
<button class="ui create_train_job green button"> | |||
{{.i18n.Tr "repo.cloudbrain.new"}} | |||
</button> | |||
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
</div> | |||
<!-- 模态框 --> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
const RepoLink = {{.RepoLink}} | |||
const url_href = window.location.pathname.split('create')[0] | |||
let nameMap,nameList | |||
$(".ui.button").attr('href',url_href) | |||
// 获取模型列表和模型名称对应的模型版本 | |||
$.get(`${RepoLink}/modelmanage/query_model_for_predict`, (data) => { | |||
nameMap = data.nameMap | |||
nameList = data.nameList | |||
let html = '' | |||
nameList.forEach(element => { | |||
html += `<div class="item" data-value=${element}>${element}</div>` | |||
}); | |||
const initModelVersion = nameMap[nameList[0]][0] | |||
const initTrainTaskInfo = JSON.parse(initModelVersion.TrainTaskInfo) | |||
$('#model_name').append(html) | |||
$('#select_model').removeClass("loading") | |||
$("#select_model").dropdown('set text',nameList[0]) | |||
$("#select_model").dropdown('set value',nameList[0],nameList[0]) | |||
}) | |||
// 根据选中的模型名称获取相应的模型版本 | |||
$(function(){ | |||
$('#select_model').dropdown({ | |||
onChange: function(value, text, $selectedItem) { | |||
$("#select_model_version").addClass("loading") | |||
$('#model_name_version').empty() | |||
let html = '' | |||
nameMap[value].forEach(element => { | |||
let {TrainTaskInfo} = element | |||
TrainTaskInfo = JSON.parse(TrainTaskInfo) | |||
html += `<div class="item" data-label="${element.Label}" data-id="${element.ID}" data-value="${TrainTaskInfo.TrainUrl}">${element.Version}</div>` | |||
}); | |||
$('#model_name_version').append(html) | |||
$("#select_model_version").removeClass("loading") | |||
const initVersionText = $('#model_name_version div.item:first-child').text() | |||
const initVersionValue = $('#model_name_version div.item:first-child').data('value') | |||
$("#select_model_version").dropdown('set text',initVersionText) | |||
$("#select_model_version").dropdown('set value',initVersionValue,initVersionText,$('#model_name_version div.item:first-child')) | |||
} | |||
}) | |||
}) | |||
// 根据选中的模型版本获取相应的模型权重文件 | |||
$(function(){ | |||
$('#select_model_version').dropdown({ | |||
onChange: function(value, text, $selectedItem) { | |||
const dataID = $selectedItem[0].getAttribute("data-id") | |||
const label = $selectedItem[0].getAttribute("data-label") | |||
$("#select_model_checkpoint").addClass("loading") | |||
$("#model_checkpoint").empty() | |||
let html = '' | |||
loadCheckpointList(dataID).then((res)=>{ | |||
res.forEach(element => { | |||
const ckptSuffix = element.FileName.split(".") | |||
if(!element.IsDir && ckptSuffix[ckptSuffix.length-1]==='ckpt'){ | |||
html += `<div class="item" data-value=${element.FileName}>${element.FileName}</div>` | |||
} | |||
}) | |||
$('#model_checkpoint').append(html) | |||
$("#select_model_checkpoint").removeClass("loading") | |||
const initVersionText = $('#model_checkpoint div.item:last-child').text() | |||
const initVersionValue = $('#model_checkpoint div.item:last-child').data('value') | |||
$("#select_model_checkpoint").dropdown('set text',initVersionText) | |||
$("#select_model_checkpoint").dropdown('set value',initVersionValue,initVersionText,$('#model_name_version div.item:first-child')) | |||
}) | |||
$("input#ai_model_version").val(text) | |||
$("input#ai_model_label").val(label) | |||
} | |||
}) | |||
}) | |||
function loadCheckpointList(value){ | |||
return new Promise((resolve,reject)=>{ | |||
$.get(`${RepoLink}/modelmanage/query_modelfile_for_predict`,{ID:value}, (data) => { | |||
resolve(data) | |||
}) | |||
}) | |||
} | |||
$('.question.circle.icon').hover(function(){ | |||
$(this).popup('show') | |||
}); | |||
// 参数增加、删除、修改、保存 | |||
function Add_parameter(i){ | |||
value = '<div class="two fields width84" id= "para'+ i +'">' + | |||
'<div class="field">' + | |||
'<input type="text" name="shipping_first-name" required placeholder={{.i18n.Tr "repo.modelarts.train_job.parameter_name"}}> ' + | |||
'</div> ' + | |||
'<div class="field"> ' + | |||
'<input type="text" name="shipping_last-name" required placeholder={{.i18n.Tr "repo.modelarts.train_job.parameter_value"}}>' + | |||
'</div>'+ | |||
'<span>' + | |||
'<i class="trash icon">' + | |||
'</i>' + | |||
'</span>' + | |||
'</div>' | |||
$(".dynamic.field").append(value) | |||
} | |||
$('#add_run_para').click(function(){ | |||
var len = $(".dynamic.field .two.fields").length | |||
Add_parameter(len) | |||
}); | |||
$(".dynamic.field").on("click",".trash.icon", function() { | |||
var index = $(this).parent().parent().index() | |||
$(this).parent().parent().remove() | |||
var len = $(".dynamic.field .two.fields").length | |||
$(".dynamic.field .two.fields").each(function(){ | |||
var cur_index = $(this).index() | |||
$(this).attr('id', 'para' + cur_index) | |||
}) | |||
}); | |||
function send_run_para(){ | |||
var run_parameters = [] | |||
var msg = {} | |||
$(".dynamic.field .two.fields").each(function(){ | |||
var para_name = $(this).find('input[name=shipping_first-name]').val() | |||
var para_value = $(this).find('input[name=shipping_last-name]').val() | |||
run_parameters.push({"label": para_name, "value": para_value}) | |||
}) | |||
msg["parameter"] = run_parameters | |||
msg = JSON.stringify(msg) | |||
$('#store_run_para').val(msg) | |||
} | |||
function get_name(){ | |||
let name1=$("#engine_name .text").text() | |||
let name2=$("#flaver_name .text").text() | |||
$("input#ai_engine_name").val(name1) | |||
$("input#ai_flaver_name").val(name2) | |||
} | |||
function validate(){ | |||
$('.ui.form') | |||
.form({ | |||
on: 'blur', | |||
fields: { | |||
boot_file: { | |||
identifier : 'boot_file', | |||
rules: [ | |||
{ | |||
type: 'regExp[/.+\.py$/g]', | |||
} | |||
] | |||
}, | |||
job_name:{ | |||
identifier : 'job_name', | |||
rules: [ | |||
{ | |||
type: 'regExp[/^[a-zA-Z0-9-_]{1,36}$/]', | |||
} | |||
] | |||
}, | |||
attachment:{ | |||
identifier : 'attachment', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
}, | |||
model_name:{ | |||
identifier : 'model_name', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
}, | |||
train_url:{ | |||
identifier : 'train_url', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
}, | |||
ckpt_name:{ | |||
identifier : 'ckpt_name', | |||
rules: [ | |||
{ | |||
type: 'empty', | |||
} | |||
] | |||
} | |||
}, | |||
onSuccess: function(){ | |||
document.getElementById("mask").style.display = "block" | |||
}, | |||
onFailure: function(e){ | |||
return false; | |||
} | |||
}) | |||
} | |||
document.onreadystatechange = function() { | |||
if (document.readyState === "complete") { | |||
document.getElementById("mask").style.display = "none" | |||
} | |||
} | |||
$('.ui.create_train_job.green.button').click(function(e) { | |||
send_run_para() | |||
get_name() | |||
validate() | |||
}) | |||
</script> |
@@ -0,0 +1,663 @@ | |||
{{template "base/head" .}} | |||
<style> | |||
.according-panel-heading{ | |||
box-sizing: border-box; | |||
padding: 8px 16px; | |||
color: #252b3a; | |||
background-color: #f2f5fc; | |||
line-height: 1.5; | |||
cursor: pointer; | |||
-moz-user-select: none; | |||
-webkit-user-select: none; | |||
-ms-user-select: none; | |||
-khtml-user-select: none; | |||
user-select: none; | |||
} | |||
.accordion-panel-title { | |||
margin-top: 0; | |||
margin-bottom: 0; | |||
color: #252b3a; | |||
} | |||
.accordion-panel-title-content{ | |||
vertical-align: middle; | |||
display: inline-block; | |||
width: calc(100% - 32px); | |||
cursor: default; | |||
} | |||
.acc-margin-bottom { | |||
margin-bottom: 5px; | |||
} | |||
.title_text { | |||
font-size: 12px; | |||
} | |||
.ac-display-inblock { | |||
display: inline-block; | |||
} | |||
.cti-mgRight-sm { | |||
margin-right: 8px; | |||
} | |||
.ac-text-normal { | |||
font-size: 14px; | |||
color: #575d6c; | |||
} | |||
.uc-accordionTitle-black { | |||
color: #333; | |||
} | |||
.accordion-border{ | |||
border:1px solid #cce2ff; | |||
} | |||
.padding0{ | |||
padding: 0 !important; | |||
} | |||
.content-pad{ | |||
padding: 15px 35px; | |||
} | |||
.content-margin{ | |||
margin:10px 5px ; | |||
} | |||
.tab_2_content { | |||
min-height: 360px; | |||
margin-left: 10px; | |||
} | |||
.ac-grid { | |||
display: block; | |||
*zoom: 1; | |||
} | |||
.ac-grid-col { | |||
float: left; | |||
width: 100%; | |||
} | |||
.ac-grid-col2 .ac-grid-col { | |||
width: 50%; | |||
} | |||
.ti-form { | |||
text-align: left; | |||
max-width: 100%; | |||
vertical-align: middle; | |||
} | |||
.ti-form>tbody { | |||
font-size: 12px; | |||
} | |||
.ti-form>tbody, .ti-form>tbody>tr { | |||
vertical-align: inherit; | |||
} | |||
.ti-text-form-label { | |||
padding-bottom: 20px; | |||
padding-right: 20px; | |||
color: #8a8e99; | |||
font-size: 12px; | |||
white-space: nowrap !important; | |||
width: 80px; | |||
line-height: 30px; | |||
} | |||
.ti-text-form-content{ | |||
line-height: 30px; | |||
padding-bottom: 20px; | |||
} | |||
.ti-form>tbody>tr>td { | |||
vertical-align: top; | |||
white-space: normal; | |||
} | |||
td, th { | |||
padding: 0; | |||
} | |||
.ac-grid-col .text-span { | |||
width: 450px; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.redo-color{ | |||
color: #3291F8; | |||
} | |||
.ti-action-menu-item:not(:last-child){ | |||
margin-right: 10px; | |||
padding-right: 11px; | |||
text-decoration: none!important; | |||
color: #526ecc; | |||
cursor: pointer; | |||
display: inline-block; | |||
-moz-user-select: none; | |||
-webkit-user-select: none; | |||
-ms-user-select: none; | |||
-khtml-user-select: none; | |||
user-select: none; | |||
position: relative; | |||
} | |||
.ti-action-menu-item:not(:last-child):after { | |||
content: ""; | |||
display: inline-block; | |||
position: absolute; | |||
height: 12px; | |||
right: 0; | |||
top: 50%; | |||
-webkit-transform: translateY(-6px); | |||
-ms-transform: translateY(-6px); | |||
-o-transform: translateY(-6px); | |||
transform: translateY(-6px); | |||
border-right: 1px solid #dfe1e6; | |||
} | |||
.text-width80{ | |||
width: 100px; | |||
line-height: 30px; | |||
} | |||
.border-according{ | |||
border: 1px solid #dfe1e6; | |||
} | |||
.disabled { | |||
cursor: default; | |||
pointer-events: none; | |||
color: rgba(0,0,0,.6) !important; | |||
opacity: .45 !important; | |||
} | |||
.pad20{ | |||
border:0px !important; | |||
} | |||
.model_file_bread{ | |||
margin-bottom: -0.5rem !important; | |||
padding-left: 1rem; | |||
padding-top: 0.5rem ; | |||
} | |||
</style> | |||
<div class="repository"> | |||
{{template "repo/header" .}} | |||
<div class="ui container"> | |||
<h4 class="ui header" id="vertical-segment"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/cloudbrain"> | |||
{{.i18n.Tr "repo.cloudbrain"}} | |||
</a> | |||
<div class="divider"> / </div> | |||
<a class="section" href="{{$.RepoLink}}/modelarts/inference-job"> | |||
{{$.i18n.Tr "repo.modelarts.infer_job"}} | |||
</a> | |||
<div class="divider"> / </div> | |||
<div class="active section">{{.jobName}}</div> | |||
</div> | |||
</h4> | |||
{{with .task}} | |||
<div class="content-pad" style="border: 1px solid #e2e2e2;margin-top: 24px;padding: 20px 60px 40px 60px;"> | |||
<div class="ui pointing secondary menu" style="border-bottom: 1px solid rgba(34,36,38,.15);"> | |||
<a class="active item" data-tab="first">{{$.i18n.Tr "repo.modelarts.train_job.config"}}</a> | |||
<a class="item" data-tab="second" onclick="loadLog({{.VersionName}})">{{$.i18n.Tr "repo.modelarts.log"}}</a> | |||
<a class="item" data-tab="third" onclick="loadModelFile({{.VersionName}},'','','init')">{{$.i18n.Tr "repo.model_download"}}</a> | |||
</div> | |||
<div class="ui tab active" data-tab="first"> | |||
<div style="padding-top: 10px;"> | |||
<div class="tab_2_content"> | |||
<div class="ac-grid ac-grid-col2"> | |||
<div class="ac-grid-col"> | |||
<table class="ti-form"> | |||
<tbody class="ti-text-form"> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.cloudbrain_task"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.JobName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.status"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-status"> | |||
{{.Status}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.run_version"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.VersionName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.start_time"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .CreatedUnix}}</span> | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-duration"> | |||
{{.TrainJobDuration}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.migrate_items_labels"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-labels"> | |||
{{if .LabelName}} | |||
{{range $.labelName}} | |||
<a class="ui label" title="{{.}}">{{.}}</a> | |||
{{end}} | |||
{{else}} | |||
<span>--</span> | |||
{{end}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.model.manage.description"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-desc"> | |||
{{if .Description}} | |||
<span>{{.Description}}</span> | |||
{{else}} | |||
<span>--</span> | |||
{{end}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
创建人 | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" id="{{.VersionName}}-creator"> | |||
{{$.userName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<!-- <tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.standard"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.FlavorName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.WorkServerNumber}} | |||
</div> | |||
</td> | |||
</tr> --> | |||
</tbody> | |||
</table> | |||
</div> | |||
<div class="ac-grid-col"> | |||
<table class="ti-form"> | |||
<tbody class="ti-text-form"> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
模型 | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
<span style="color: #8a8e99">名称:</span><span>testTrainJob1_model_5d21</span> | |||
<span style="color: #8a8e99">版本:</span><span>asdasdasd</span> | |||
<span style="color: #8a8e99">权重:</span><span title="{{.CkptName}}">{{.CkptName}}</span> | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.EngineName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.code_version"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.BranchName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.BootFile}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.train_dataset"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.DatasetName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80" > | |||
{{$.i18n.Tr "repo.modelarts.train_job.run_parameter"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w" title="{{.Parameters}}"> | |||
{{if .Parameters}} | |||
<span>{{.Parameters}}</span> | |||
{{else}} | |||
<span>--</span> | |||
{{end}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.standard"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.FlavorName}} | |||
</div> | |||
</td> | |||
</tr> | |||
<tr class="ti-no-ng-animate"> | |||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}} | |||
</td> | |||
<td class="ti-text-form-content"> | |||
<div class="text-span text-span-w"> | |||
{{.WorkServerNumber}} | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui tab" data-tab="second"> | |||
<div> | |||
<div class="ui message message{{.VersionName}}" style="display: none;"> | |||
<div id="header"></div> | |||
</div> | |||
<div class="ui attached log" onscroll="logScroll({{.VersionName}})" id="log{{.VersionName}}" style="height: 300px !important; overflow: auto;"> | |||
<input type="hidden" name="end_line" value> | |||
<input type="hidden" name="start_line" value> | |||
<pre id="log_file{{.VersionName}}"></pre> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui tab" data-tab="third"> | |||
<input type="hidden" name="model{{.VersionName}}" value="-1"> | |||
<input type="hidden" name="modelback{{.VersionName}}" value="-1"> | |||
<div class='ui breadcrumb model_file_bread' id='file_breadcrumb{{.VersionName}}'> | |||
<div class="active section">{{.VersionName}}</div> | |||
<div class="divider"> / </div> | |||
</div> | |||
<div id="dir_list{{.VersionName}}"> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
<!-- 确认模态框 --> | |||
<div id="deletemodel"> | |||
<div class="ui basic modal"> | |||
<div class="ui icon header"> | |||
<i class="trash icon"></i> 删除任务 | |||
</div> | |||
<div class="content"> | |||
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p> | |||
</div> | |||
<div class="actions"> | |||
<div class="ui red basic inverted cancel button"> | |||
<i class="remove icon"></i> 取消操作 | |||
</div> | |||
<div class="ui green basic inverted ok button"> | |||
<i class="checkmark icon"></i> 确定操作 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
console.log({{.task}}) | |||
$(document).ready(function(){ | |||
$('.secondary.menu .item').tab(); | |||
}); | |||
let userName | |||
let repoPath | |||
let jobID | |||
$(document).ready(function(){ | |||
let url = window.location.href; | |||
let urlArr = url.split('/') | |||
userName = urlArr.slice(-5)[0] | |||
repoPath = urlArr.slice(-4)[0] | |||
jobID = urlArr.slice(-1)[0] | |||
}) | |||
function loadLog(version_name){ | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&lines=50&order=asc`, (data) => { | |||
$('input[name=end_line]').val(data.EndLine) | |||
$('input[name=start_line]').val(data.StartLine) | |||
$(`#log_file${version_name}`).text(data.Content) | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
function logScroll(version_name) { | |||
let container = document.querySelector(`#log${version_name}`) | |||
let scrollTop = container.scrollTop | |||
let scrollHeight = container.scrollHeight | |||
let clientHeight = container.clientHeight | |||
let scrollLeft = container.scrollLeft | |||
if((parseInt(scrollTop) + clientHeight == scrollHeight || parseInt(scrollTop) + clientHeight +1 == scrollHeight || parseInt(scrollTop) + clientHeight - 1 == scrollHeight) && (scrollLeft===0)){ | |||
let end_line = $(`#log${version_name} input[name=end_line]`).val() | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&base_line=${end_line}&lines=50&order=desc`, (data) => { | |||
if (data.Lines == 0){ | |||
$(`.message${version_name} #header`).text('您已翻阅至日志底部') | |||
$(`.message${version_name}`).css('display', 'block') | |||
setTimeout(function(){ | |||
$(`.message${version_name}`).css('display', 'none') | |||
}, 1000) | |||
}else{ | |||
if(end_line===data.EndLine){ | |||
return | |||
} | |||
else{ | |||
$(`#log${version_name} input[name=end_line]`).val(data.EndLine) | |||
$(`#log${version_name}`).append('<pre>' + data.Content) | |||
} | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
if(scrollTop == 0 && scrollLeft==0){ | |||
let start_line = $(`#log${version_name} input[name=start_line]`).val() | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&base_line=${start_line}&lines=50&order=asc`, (data) => { | |||
if (data.Lines == 0){ | |||
$(`.message${version_name} #header`).text('您已翻阅至日志顶部') | |||
$(`.message${version_name}`).css('display', 'block') | |||
setTimeout(function(){ | |||
$(`.message${version_name}`).css('display', 'none') | |||
}, 1000) | |||
}else{ | |||
$(`#log${version_name} input[name=start_line]`).val(data.StartLine) //如果变动就改变所对应的值 | |||
$(`#log${version_name}`).prepend('<pre>' + data.Content) | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
} | |||
} | |||
function renderSize(value){ | |||
if(null==value||value==''){ | |||
return "0 Bytes"; | |||
} | |||
var unitArr = new Array("Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"); | |||
var index=0; | |||
var srcsize = parseFloat(value); | |||
index=Math.floor(Math.log(srcsize)/Math.log(1024)); | |||
var size =srcsize/Math.pow(1024,index); | |||
size=size.toFixed(2);//保留的小数位数 | |||
return size+unitArr[index]; | |||
} | |||
function loadModelFile(version_name,parents,filename,init){ | |||
parents = parents || '' | |||
filename = filename || '' | |||
init = init || '' | |||
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/result_list?version_name=${version_name}&parentDir=${parents}`, (data) => { | |||
$(`#dir_list${version_name}`).empty() | |||
renderDir(data,version_name) | |||
if(init==="init"){ | |||
$(`input[name=model${version_name}]`).val("") | |||
$(`input[name=modelback${version_name}]`).val(version_name) | |||
$(`#file_breadcrumb${version_name}`).empty() | |||
let htmlBread = "" | |||
htmlBread += `<div class='active section'>${version_name}</div>` | |||
htmlBread += "<div class='divider'> / </div>" | |||
$(`#file_breadcrumb${version_name}`).append(htmlBread) | |||
}else{ | |||
renderBrend(version_name,parents,filename,init) | |||
} | |||
}).fail(function(err) { | |||
console.log(err,version_name); | |||
}); | |||
} | |||
function renderBrend(version_name,parents,filename,init){ | |||
if(init=="folder"){ | |||
let htmlBrend = "" | |||
let sectionName=$(`#file_breadcrumb${version_name} .active.section`).text() | |||
let parents1 = $(`input[name=model${version_name}]`).val() | |||
let filename1 = $(`input[name=modelback${version_name}]`).val() | |||
if(parents1===""){ | |||
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','','init')">${sectionName}</a>`) | |||
}else{ | |||
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','${filename1}')">${sectionName}</a>`) | |||
} | |||
htmlBrend += `<div class='active section'>${filename}</div>` | |||
htmlBrend += "<div class='divider'> / </div>" | |||
$(`#file_breadcrumb${version_name}`).append(htmlBrend) | |||
$(`input[name=model${version_name}]`).val(parents) | |||
$(`input[name=modelback${version_name}]`).val(filename) | |||
}else{ | |||
$(`input[name=model${version_name}]`).val(parents) | |||
$(`input[name=modelback${version_name}]`).val(filename) | |||
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).nextAll().remove() | |||
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).replaceWith(`<div class='active section'>${filename}</div>`) | |||
$(`#file_breadcrumb${version_name} div.section:contains(${filename})`).append("<div class='divider'> / </div>") | |||
} | |||
} | |||
function renderDir(data,version_name){ | |||
let html="" | |||
html += "<div class='ui grid' style='margin:0;'>" | |||
html += "<div class='row' style='padding: 0;'>" | |||
html += "<div class='ui sixteen wide column' style='padding:1rem;'>" | |||
html += "<div class='dir list'>" | |||
html += "<table id='repo-files-table' class='ui single line table pad20'>" | |||
html += '<tbody>' | |||
// html += "</tbody>" | |||
for(let i=0;i<data.Dirs.length;i++){ | |||
let dirs_size = renderSize(data.Dirs[i].Size) | |||
html += "<tr>" | |||
html += "<td class='name six wid'>" | |||
html += "<span class='truncate'>" | |||
html += "<span class='octicon octicon-file-directory'>" | |||
html += "</span>" | |||
if(data.Dirs[i].IsDir){ | |||
html += `<a onclick="loadModelFile('${version_name}','${data.Dirs[i].ParenDir}','${data.Dirs[i].FileName}','folder')">` | |||
html += "<span class='fitted'><i class='folder icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>" | |||
}else{ | |||
html += `<a href="${location.href}/result_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">` | |||
html += "<span class='fitted'><i class='file icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>" | |||
} | |||
html += '</a>' | |||
html += "</span>" | |||
html += "</td>" | |||
html += "<td class='message seven wide'>" | |||
if(data.Dirs[i].IsDir){ | |||
html += "<span class='truncate has-emoji'></span>" | |||
}else{ | |||
html += "<span class='truncate has-emoji'>"+ `${dirs_size}` + "</span>" | |||
} | |||
html += "</td>" | |||
html += "<td class='text right age three wide'>" | |||
html += "<span class='truncate has-emoji'>" + data.Dirs[i].ModTime + "</span>" | |||
html += "</td>" | |||
html += "</tr>" | |||
} | |||
html += "</tbody>" | |||
html += "</table>" | |||
html += "</div>" | |||
html += "</div>" | |||
html += "</div>" | |||
html += "</div>" | |||
$(`#dir_list${version_name}`).append(html) | |||
} | |||
</script> |
@@ -34,6 +34,7 @@ | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> | |||
<a class="active item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
@@ -187,7 +187,7 @@ td, th { | |||
<span> | |||
<div style="float: right;"> | |||
{{$.CsrfTokenHtml}} | |||
{{if .CanModify}} | |||
{{if .CanDebug}} | |||
<a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | |||
{{else}} | |||
<a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | |||
@@ -17,16 +17,30 @@ | |||
<div class="repository release dataset-list view"> | |||
{{template "repo/header" .}} | |||
<!-- 列表容器 --> | |||
<div class="ui container active loader" id="loadContainer"> | |||
<div class="ui container {{if ne $.MODEL_COUNT 0}}active loader {{end}}" id="loadContainer"> | |||
{{template "base/alert" .}} | |||
<div class="ui two column stackable grid" style="display: none;"> | |||
<div class="ui two column stackable grid"> | |||
<div class="column"></div> | |||
<div class="column right aligned"> | |||
<!-- --> | |||
<a class="ui button {{if .Permission.CanWrite $.UnitTypeCloudBrain}} green {{else}} disabled {{end}}" onclick="showcreate(this)">{{$.i18n.Tr "repo.model.manage.import_new_model"}}</a> | |||
</div> | |||
</div> | |||
{{if eq $.MODEL_COUNT 0}} | |||
<div class="ui placeholder segment bgtask-none"> | |||
<div class="ui icon header bgtask-header-pic"></div> | |||
<div class="bgtask-content-header">未创建过模型</div> | |||
<div class="bgtask-content"> | |||
{{if $.RepoIsEmpty}} | |||
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div> | |||
{{end}} | |||
{{if eq $.TRAIN_COUNT 0}} | |||
<div class="bgtask-content-txt">训练任务:你还没创建过训练任务,请先创建训练任务。</div> | |||
{{end}} | |||
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||
</div> | |||
</div> | |||
{{else}} | |||
<!-- 中下列表展示区 --> | |||
<div class="ui grid" style="display: none;"> | |||
<div class="row" style="padding-top: 0;"> | |||
@@ -38,6 +52,7 @@ | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
@@ -348,6 +348,7 @@ export default { | |||
this.$axios.get(location.href+'_api',{ | |||
params:this.params | |||
}).then((res)=>{ | |||
console.log(res) | |||
$(".ui.grid").removeAttr("style") | |||
$("#loadContainer").removeClass("loader") | |||
let TrainTaskInfo | |||
@@ -4128,7 +4128,7 @@ function initDropDown() { | |||
} | |||
//云脑提示 | |||
$('.question.circle.icon').hover(function(){ | |||
$('.question.circle.icon.cloudbrain-question').hover(function(){ | |||
$(this).popup('show') | |||
$('.ui.popup.mini.top.center').css({"border-color":'rgba(50, 145, 248, 100)',"color":"rgba(3, 102, 214, 100)","border-radius":"5px","border-shadow":"none"}) | |||
}); | |||
Dear OpenI User
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.
For more agreement content, please refer to the《Openl Qizhi Community AI Collaboration Platform Usage Agreement》