llm_instant_model_sync.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. package models
  2. import (
  3. "context"
  4. "database/sql"
  5. "net/http"
  6. "slices"
  7. "strings"
  8. "time"
  9. "yunion.io/x/jsonutils"
  10. "yunion.io/x/log"
  11. "yunion.io/x/pkg/errors"
  12. "yunion.io/x/pkg/utils"
  13. commonapi "yunion.io/x/onecloud/pkg/apis"
  14. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  15. apis "yunion.io/x/onecloud/pkg/apis/llm"
  16. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  17. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  18. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  19. "yunion.io/x/onecloud/pkg/httperrors"
  20. "yunion.io/x/onecloud/pkg/mcclient"
  21. "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  22. "yunion.io/x/onecloud/pkg/util/logclient"
  23. )
  24. func (llm *SLLM) getMountedInstantModels(ctx context.Context, probedExt map[string]apis.LLMInternalInstantMdlInfo) (map[string]struct{}, error) {
  25. container, err := llm.GetLLMSContainer(ctx)
  26. if err != nil {
  27. return nil, errors.Wrap(err, "GetSContainer")
  28. }
  29. if container.Spec == nil {
  30. return nil, errors.Wrap(errors.ErrEmpty, "no Spec")
  31. }
  32. if len(container.Spec.VolumeMounts) == 0 {
  33. return nil, errors.Wrap(errors.ErrEmpty, "no VolumeMounts")
  34. }
  35. if container.Spec.VolumeMounts[0].Disk == nil {
  36. return nil, errors.Wrap(errors.ErrEmpty, "no Disk")
  37. }
  38. if len(container.Spec.VolumeMounts[0].Disk.PostOverlay) == 0 {
  39. return nil, nil
  40. }
  41. mdlNameToId := make(map[string]string)
  42. for mdlId, model := range probedExt {
  43. mdlNameToId[model.Name+":"+model.Tag] = mdlId
  44. }
  45. mdlMap := make(map[string]struct{})
  46. postOverlays := container.Spec.VolumeMounts[0].Disk.PostOverlay
  47. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  48. if err != nil {
  49. return nil, nil
  50. }
  51. for i := range postOverlays {
  52. postOverlay := postOverlays[i]
  53. mdlId := drv.GetInstantModelIdByPostOverlay(postOverlay, mdlNameToId)
  54. if mdlId != "" {
  55. mdlMap[mdlId] = struct{}{}
  56. }
  57. }
  58. return mdlMap, nil
  59. }
  60. func (llm *SLLM) getProbedInstantModelsExt(ctx context.Context, userCred mcclient.TokenCredential, instantModelIds ...string) (map[string]apis.LLMInternalInstantMdlInfo, error) {
  61. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  62. if err != nil {
  63. return nil, nil
  64. }
  65. return drv.GetProbedInstantModelsExt(ctx, userCred, llm, instantModelIds...)
  66. }
  67. type sInstantModelStatus struct {
  68. apis.LLMInternalInstantMdlInfo
  69. Probed bool
  70. Mounted bool
  71. }
  72. func (llm *SLLM) getProbedMountedInstantModels(ctx context.Context, userCred mcclient.TokenCredential) (map[string]*sInstantModelStatus, error) {
  73. mdlMap := make(map[string]*sInstantModelStatus)
  74. probedExt, errExt := llm.getProbedInstantModelsExt(ctx, userCred)
  75. if errExt != nil {
  76. return nil, errors.Wrap(errExt, "getProbedInstantModelsExt")
  77. }
  78. for modelId := range probedExt {
  79. mdlMap[modelId] = &sInstantModelStatus{
  80. LLMInternalInstantMdlInfo: probedExt[modelId],
  81. Probed: true,
  82. }
  83. }
  84. mounted, err := llm.getMountedInstantModels(ctx, probedExt)
  85. if err != nil {
  86. return nil, errors.Wrap(err, "llm.getMountedInstantModels")
  87. }
  88. for instantModelId := range mounted {
  89. if _, ok := mdlMap[instantModelId]; ok {
  90. mdlMap[instantModelId].Mounted = true
  91. } else {
  92. // 仅 mounted 未 probed 时无 ollama model id,ModelId 留空
  93. mdlMap[instantModelId] = &sInstantModelStatus{
  94. LLMInternalInstantMdlInfo: apis.LLMInternalInstantMdlInfo{},
  95. Mounted: true,
  96. }
  97. }
  98. }
  99. return mdlMap, nil
  100. }
  101. func (llm *SLLM) uninstallInstantModel(ctx context.Context, userCred mcclient.TokenCredential, instantModelId string) error {
  102. boolFalse := false
  103. // uninstalled
  104. probed := &boolFalse
  105. mounted := &boolFalse
  106. _, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, instantModelId, "", "", probed, mounted)
  107. if err != nil {
  108. return errors.Wrap(err, "uninstallPackage")
  109. }
  110. return nil
  111. }
  112. func findInstantModelWithModelInfo(allModels []SLLMInstantModel, mdl apis.ModelInfo) *SLLMInstantModel {
  113. for i := range allModels {
  114. if allModels[i].InstantModelId == mdl.Id {
  115. return &allModels[i]
  116. }
  117. }
  118. return nil
  119. }
  120. func findModelsToUninstall(allModels []SLLMInstantModel, input apis.LLMSyncModelTaskInput) []SLLMInstantModel {
  121. ret := make([]SLLMInstantModel, 0)
  122. for i := range input.Models {
  123. existingModel := findInstantModelWithModelInfo(allModels, input.Models[i])
  124. if existingModel != nil && existingModel.IsMounted && (input.Method == apis.QuickModelUninstall || input.Method == apis.QuickModelReinstall || (!existingModel.IsProbed && input.Method == apis.QuickModelInstall)) {
  125. ret = append(ret, *existingModel)
  126. }
  127. }
  128. return ret
  129. }
  130. func findModelsToUnmount(allModels []SLLMInstantModel, input apis.LLMSyncModelTaskInput) []SLLMInstantModel {
  131. ret := make([]SLLMInstantModel, 0)
  132. for i := range input.Models {
  133. existingModel := findInstantModelWithModelInfo(allModels, input.Models[i])
  134. if existingModel != nil && existingModel.IsMounted && (input.Method == apis.QuickModelUninstall || input.Method == apis.QuickModelReinstall || (!existingModel.IsProbed && input.Method == apis.QuickModelInstall)) {
  135. ret = append(ret, *existingModel)
  136. }
  137. }
  138. return ret
  139. }
  140. func isImageInUnmountModels(imageId string, mdls []SLLMInstantModel) (bool, error) {
  141. instMdl, err := GetInstantModelManager().findInstantModelByImageId(imageId)
  142. if err != nil {
  143. return false, errors.Wrap(err, "findInstantAppByImageId")
  144. }
  145. if instMdl == nil {
  146. return false, nil
  147. }
  148. for i := range mdls {
  149. if mdls[i].InstantModelId == instMdl.Id {
  150. return true, nil
  151. }
  152. }
  153. return false, nil
  154. }
  155. func (llm *SLLM) RefreshInstantModels(ctx context.Context, userCred mcclient.TokenCredential, refresh bool) error {
  156. lockman.LockObject(ctx, llm)
  157. defer lockman.ReleaseObject(ctx, llm)
  158. if !llm.LastInstantModelProbe.IsZero() && time.Since(llm.LastInstantModelProbe) < apis.LLM_PROBE_INSTANT_MODEl_INTERVAL_SECOND*time.Second && (!refresh || time.Since(llm.LastInstantModelProbe) < apis.LLM_PROBE_INSTANT_MODEl_INTERVAL_SECOND*time.Second) {
  159. // already probed, null operation
  160. return nil
  161. }
  162. mdlMap, err := llm.getProbedMountedInstantModels(ctx, userCred)
  163. if err != nil {
  164. return errors.Wrap(err, "getProbedMountedInstantModels")
  165. }
  166. models, err := llm.FetchModels(nil, nil, nil)
  167. if err != nil {
  168. return errors.Wrap(err, "FetchModels")
  169. }
  170. var errs []error
  171. for i := range models {
  172. mdl := models[i]
  173. var probed *bool
  174. var mounted *bool
  175. if status, ok := mdlMap[mdl.InstantModelId]; ok {
  176. // probed
  177. probed = &status.Probed
  178. mounted = &status.Mounted
  179. _, err = GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mdl.InstantModelId, status.Name, status.Tag, probed, mounted)
  180. delete(mdlMap, mdl.InstantModelId)
  181. } else {
  182. // uninstalled
  183. err = llm.uninstallInstantModel(ctx, userCred, mdl.InstantModelId)
  184. }
  185. if err != nil {
  186. errs = append(errs, err)
  187. }
  188. }
  189. if len(mdlMap) > 0 {
  190. for instantModelId, status := range mdlMap {
  191. _, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, instantModelId, status.Name, status.Tag, &status.Probed, &status.Mounted)
  192. if err != nil {
  193. errs = append(errs, err)
  194. }
  195. }
  196. }
  197. if len(errs) > 0 {
  198. return errors.NewAggregate(errs)
  199. }
  200. // update timer
  201. _, err = db.Update(llm, func() error {
  202. llm.LastInstantModelProbe = time.Now()
  203. return nil
  204. })
  205. if err != nil {
  206. return errors.Wrap(err, "update last_instant_model_probe")
  207. }
  208. return nil
  209. }
  210. func (llm *SLLM) PerformQuickModels(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.LLMPerformQuickModelsInput) (*apis.LLMBatchPerformOutput, error) {
  211. if !utils.IsInArray(llm.Status, []string{apis.LLM_STATUS_RUNNING, apis.LLM_STATUS_READY}) {
  212. return nil, errors.Wrapf(errors.ErrInvalidStatus, "llm:%s(%s) status:%s", llm.Name, llm.Id, llm.Status)
  213. }
  214. llmStatus := llm.Status
  215. if len(input.Method) == 0 {
  216. input.Method = apis.QuickModelInstall
  217. }
  218. var toInstallSizeGb float64
  219. var errs []error
  220. for i := range input.Models {
  221. // specified by ID
  222. if len(input.Models[i].Id) > 0 {
  223. instModelObj, err := GetInstantModelManager().FetchByIdOrName(ctx, userCred, input.Models[i].Id)
  224. if err != nil {
  225. if errors.Cause(err) == sql.ErrNoRows {
  226. errs = append(errs, httperrors.NewResourceNotFoundError2(GetInstantModelManager().Keyword(), input.Models[i].Id))
  227. } else {
  228. errs = append(errs, errors.Wrap(err, "FetchByIdOrName"))
  229. }
  230. } else {
  231. instMdl := instModelObj.(*SInstantModel)
  232. input.Models[i].Id = instMdl.Id
  233. input.Models[i].ModelId = instMdl.ModelId
  234. input.Models[i].Tag = instMdl.ModelTag
  235. input.Models[i].LlmType = instMdl.LlmType
  236. if input.Method == apis.QuickModelInstall {
  237. toInstallSizeGb += float64(instMdl.GetActualSizeMb()) * 1024 * 1024 / 1000 / 1000 / 1000
  238. }
  239. }
  240. } else {
  241. mdl, err := GetInstantModelManager().FindInstantModel(input.Models[i].ModelId, input.Models[i].Tag, true)
  242. if err != nil {
  243. return nil, errors.Wrapf(err, "findInstantModel %s %s", input.Models[i].ModelId, input.Models[i].Tag)
  244. }
  245. if mdl == nil {
  246. errs = append(errs, httperrors.NewResourceNotFoundError2(GetInstantModelManager().Keyword(), input.Models[i].ModelId))
  247. } else {
  248. input.Models[i].Id = mdl.Id
  249. input.Models[i].Tag = mdl.ModelTag
  250. input.Models[i].ModelId = mdl.ModelId
  251. input.Models[i].LlmType = mdl.LlmType
  252. }
  253. }
  254. if !apis.IsLLMContainerType(input.Models[i].LlmType) || apis.LLMContainerType(input.Models[i].LlmType) != llm.GetLLMContainerDriver().GetType() {
  255. errs = append(errs, errors.Wrapf(httperrors.ErrInvalidStatus, "model %s is not of type %s", input.Models[i].Id, llm.GetLLMContainerDriver().GetType()))
  256. }
  257. }
  258. if len(errs) > 0 {
  259. return nil, errors.NewAggregate(errs)
  260. }
  261. if input.Method == apis.QuickModelInstall {
  262. if llm.InstantModelQuotaGb > 0 && toInstallSizeGb > float64(llm.InstantModelQuotaGb)-llm.GetTotalInstantModelSizeGb() {
  263. return nil, errors.Wrapf(httperrors.ErrOutOfQuota, "toInstallSizeGb %f > InstantAppQuotaGb %d - total %f Gb", toInstallSizeGb, llm.InstantModelQuotaGb, llm.GetTotalInstantModelSizeGb())
  264. }
  265. }
  266. task, err := llm.StartLLMInstantModelsSyncTask(ctx, userCred, llmStatus, input, "")
  267. if err != nil {
  268. return nil, errors.Wrap(err, "StartLLMInstantModelsSyncTask")
  269. }
  270. if input.Method == apis.QuickModelInstall {
  271. // save pending quota
  272. llm.insertPendingInstantModelQuota(task.Id, toInstallSizeGb)
  273. }
  274. output := apis.LLMBatchPerformOutput{
  275. Data: []apis.LLMPerformOutput{
  276. {
  277. Id: llm.Id,
  278. Name: llm.Name,
  279. RequestStatus: http.StatusOK,
  280. TaskId: task.Id,
  281. },
  282. },
  283. Task: task,
  284. }
  285. return &output, nil
  286. }
  287. func (llm *SLLM) StartLLMInstantModelsSyncTask(ctx context.Context, userCred mcclient.TokenCredential, llmStatus string, input apis.LLMPerformQuickModelsInput, parentTaskid string) (*taskman.STask, error) {
  288. if !utils.IsInArray(llmStatus, []string{apis.LLM_STATUS_RUNNING, apis.LLM_STATUS_READY}) {
  289. return nil, errors.Wrapf(errors.ErrInvalidStatus, "cannot sync models in status %s", llmStatus)
  290. }
  291. taskInput := apis.LLMSyncModelTaskInput{
  292. LLMPerformQuickModelsInput: input,
  293. LLMStatus: llmStatus,
  294. }
  295. task, err := taskman.TaskManager.NewTask(ctx, "LLMInstantModelsSyncTask", llm, userCred, jsonutils.Marshal(taskInput).(*jsonutils.JSONDict), parentTaskid, "")
  296. if err != nil {
  297. return nil, errors.Wrap(err, "NewTask")
  298. }
  299. err = task.ScheduleRun(nil)
  300. if err != nil {
  301. return nil, errors.Wrap(err, "ScheduleRun")
  302. }
  303. return task, nil
  304. }
  305. func (llm *SLLM) FetchModels(isProbed, isMounted, isSystem *bool) ([]SLLMInstantModel, error) {
  306. q := GetLLMInstantModelManager().Query().Equals("llm_id", llm.Id)
  307. q = GetLLMInstantModelManager().filterModels(q, isProbed, isMounted, isSystem)
  308. models := make([]SLLMInstantModel, 0)
  309. err := db.FetchModelObjects(GetLLMInstantModelManager(), q, &models)
  310. if err != nil {
  311. return nil, errors.Wrap(err, "FetchModelObjects")
  312. }
  313. return models, nil
  314. }
  315. func (llm *SLLM) FetchModelsFullName(isProbed, isMounted *bool) ([]string, error) {
  316. models, err := llm.FetchModels(isProbed, isMounted, nil)
  317. if err != nil {
  318. return nil, errors.Wrap(err, "FetchModels")
  319. }
  320. mdlFullNames := make([]string, len(models))
  321. for idx, mdl := range models {
  322. mdlFullNames[idx] = mdl.InstantModelId
  323. }
  324. return mdlFullNames, nil
  325. }
  326. func (llm *SLLM) FetchMountedModelFullName() ([]string, error) {
  327. boolTrue := true
  328. return llm.FetchModelsFullName(nil, &boolTrue)
  329. }
  330. func (llm *SLLM) FetchMountedModelInfo() ([]apis.MountedModelInfo, error) {
  331. boolTrue := true
  332. models, err := llm.FetchModels(nil, &boolTrue, nil)
  333. if err != nil {
  334. return nil, errors.Wrap(err, "FetchModels")
  335. }
  336. result := make([]apis.MountedModelInfo, len(models))
  337. for idx, mdl := range models {
  338. instMdl, _ := GetInstantModelManager().GetInstantModelById(mdl.InstantModelId)
  339. modelIdForDisplay := ""
  340. if instMdl != nil {
  341. modelIdForDisplay = instMdl.ModelId
  342. }
  343. result[idx] = apis.MountedModelInfo{
  344. FullName: mdl.ModelName + ":" + mdl.Tag,
  345. ModelId: modelIdForDisplay,
  346. Id: mdl.InstantModelId,
  347. }
  348. }
  349. return result, nil
  350. }
  351. func (llm *SLLM) RequestUnmountModel(ctx context.Context, userCred mcclient.TokenCredential, input apis.LLMSyncModelTaskInput) ([]string, []*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
  352. if input.LLMStatus == apis.LLM_STATUS_RUNNING {
  353. err := llm.RefreshInstantModels(ctx, userCred, true)
  354. if err != nil {
  355. return nil, nil, errors.Wrap(err, "RefreshInstantModels")
  356. }
  357. }
  358. allModels, err := llm.FetchModels(nil, nil, nil)
  359. if err != nil {
  360. return nil, nil, errors.Wrap(err, "FetchModels")
  361. }
  362. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  363. if err != nil {
  364. return nil, nil, nil
  365. }
  366. if input.LLMStatus == apis.LLM_STATUS_RUNNING {
  367. uninstallModels := findModelsToUninstall(allModels, input)
  368. for i := range uninstallModels {
  369. err := drv.UninstallModel(ctx, userCred, llm, &uninstallModels[i])
  370. if err != nil {
  371. log.Errorf("fail to uninstall %s", err)
  372. continue
  373. }
  374. }
  375. }
  376. // next found out models that need to unmount
  377. unmountModels := findModelsToUnmount(allModels, input)
  378. if len(unmountModels) == 0 {
  379. return nil, nil, nil
  380. }
  381. container, err := llm.GetLLMSContainer(ctx)
  382. if err != nil {
  383. return nil, nil, errors.Wrap(err, "GetContainer")
  384. }
  385. var unmountOverlays []*commonapi.ContainerVolumeMountDiskPostOverlay
  386. existingOverlays := container.Spec.VolumeMounts[0].Disk.PostOverlay
  387. for i := range existingOverlays {
  388. eOverlay := existingOverlays[i]
  389. if eOverlay.Image != nil && len(eOverlay.Image.Id) > 0 {
  390. find, err := isImageInUnmountModels(eOverlay.Image.Id, unmountModels)
  391. if err != nil {
  392. return nil, nil, errors.Wrap(err, "isImageInUnmountModels")
  393. }
  394. if find {
  395. unmountOverlays = append(unmountOverlays, eOverlay)
  396. }
  397. }
  398. }
  399. var modelIds []string
  400. for i := range unmountModels {
  401. modelIds = append(modelIds, unmountModels[i].InstantModelId)
  402. }
  403. return modelIds, unmountOverlays, nil
  404. }
  405. func (llm *SLLM) RequestMountModels(ctx context.Context, userCred mcclient.TokenCredential, input apis.LLMSyncModelTaskInput) ([]string, []string, []*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
  406. if input.LLMStatus == apis.LLM_STATUS_RUNNING {
  407. err := llm.RefreshInstantModels(ctx, userCred, true)
  408. if err != nil {
  409. return nil, nil, nil, errors.Wrap(err, "RefreshApps")
  410. }
  411. }
  412. existingMdls, err := llm.FetchModels(nil, nil, nil)
  413. if err != nil {
  414. return nil, nil, nil, errors.Wrap(err, "FetchApps")
  415. }
  416. log.Debugf("=======RequestMountModels input: %s", jsonutils.Marshal(input).PrettyString())
  417. models, overlays, err := llm.getMountingModelsPostOverlay(ctx, input, existingMdls)
  418. if err != nil {
  419. return nil, nil, nil, errors.Wrap(err, "getMountingModelsPostOverlay")
  420. }
  421. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  422. if err != nil {
  423. // return nil, nil, nil, nil
  424. log.Warningf("driver %s does not support instant model operations", llm.GetLLMContainerDriver().GetType())
  425. }
  426. var mdlIds []string
  427. for i := range models {
  428. model := models[i]
  429. if input.LLMStatus == apis.LLM_STATUS_RUNNING {
  430. if drv != nil {
  431. err := drv.PreInstallModel(ctx, userCred, llm, &model)
  432. if err != nil {
  433. log.Errorf("preinstallPackage fail %s", err)
  434. }
  435. }
  436. }
  437. mdlIds = append(mdlIds, model.InstantModelId)
  438. }
  439. targetDirs := make([]string, 0)
  440. for i := range overlays {
  441. if len(overlays[i].ContainerTargetDir) > 0 {
  442. targetDirs = append(targetDirs, overlays[i].ContainerTargetDir)
  443. }
  444. }
  445. return mdlIds, targetDirs, overlays, nil
  446. }
  447. func (llm *SLLM) TryContainerUnmountPaths(ctx context.Context, userCred mcclient.TokenCredential, s *mcclient.ClientSession, overlays []*commonapi.ContainerVolumeMountDiskPostOverlay, waitSecs int) error {
  448. start := time.Now()
  449. for time.Since(start) < time.Second*time.Duration(waitSecs) {
  450. err := llm.containerUnmountPaths(ctx, userCred, s, overlays)
  451. if err != nil {
  452. if strings.Contains(err.Error(), string(errors.ErrInvalidStatus)) {
  453. // wait
  454. time.Sleep(5 * time.Second)
  455. } else {
  456. return errors.Wrap(err, "containerMountPaths")
  457. }
  458. } else {
  459. // success
  460. return nil
  461. }
  462. }
  463. return errors.ErrTimeout
  464. }
  465. func (llm *SLLM) containerUnmountPaths(ctx context.Context, userCred mcclient.TokenCredential, s *mcclient.ClientSession, overlays []*commonapi.ContainerVolumeMountDiskPostOverlay) error {
  466. ctr, err := llm.GetLLMSContainer(ctx)
  467. if err != nil {
  468. return errors.Wrap(err, "GetSContainer")
  469. }
  470. if !computeapi.ContainerFinalStatus.Has(ctr.Status) {
  471. return errors.Wrapf(errors.ErrInvalidStatus, "cannot unmount post path in status %s", ctr.Status)
  472. }
  473. params := computeapi.ContainerVolumeMountRemovePostOverlayInput{
  474. Index: 0,
  475. PostOverlay: overlays,
  476. UseLazy: true,
  477. ClearLayers: true,
  478. }
  479. _, err = compute.Containers.PerformAction(s, ctr.Id, "remove-volume-mount-post-overlay", jsonutils.Marshal(params))
  480. if err != nil {
  481. return errors.Wrap(err, "PerformAction remove-volume-mount-post-overlay")
  482. }
  483. return nil
  484. }
  485. func (llm *SLLM) TryContainerMountPaths(ctx context.Context, userCred mcclient.TokenCredential, s *mcclient.ClientSession, overlays []*commonapi.ContainerVolumeMountDiskPostOverlay, waitSecs int) error {
  486. start := time.Now()
  487. for time.Since(start) < time.Second*time.Duration(waitSecs) {
  488. err := llm.containerMountPaths(ctx, userCred, s, overlays)
  489. if err != nil {
  490. if strings.Contains(err.Error(), string(errors.ErrInvalidStatus)) {
  491. log.Errorf("containerMountPaths error %s, retry", err)
  492. // retry
  493. time.Sleep(5 * time.Second)
  494. } else {
  495. return errors.Wrap(err, "containerMountPaths")
  496. }
  497. } else {
  498. // success
  499. return nil
  500. }
  501. }
  502. return errors.ErrTimeout
  503. }
  504. func (llm *SLLM) containerMountPaths(ctx context.Context, userCred mcclient.TokenCredential, s *mcclient.ClientSession, overlays []*commonapi.ContainerVolumeMountDiskPostOverlay) error {
  505. ctr, err := llm.GetLLMSContainer(ctx)
  506. if err != nil {
  507. return errors.Wrap(err, "GetLLMSContainer")
  508. }
  509. if !computeapi.ContainerFinalStatus.Has(ctr.Status) {
  510. return errors.Wrapf(errors.ErrInvalidStatus, "cannot mount post path in status %s", ctr.Status)
  511. }
  512. params := computeapi.ContainerVolumeMountAddPostOverlayInput{
  513. Index: 0,
  514. PostOverlay: overlays,
  515. }
  516. _, err = compute.Containers.PerformAction(s, ctr.Id, "add-volume-mount-post-overlay", jsonutils.Marshal(params))
  517. if err != nil {
  518. return errors.Wrap(err, "PerformAction add-volume-mount-post-overlay")
  519. }
  520. return nil
  521. }
  522. func (llm *SLLM) MarkInstantModelsUnmounted(ctx context.Context, userCred mcclient.TokenCredential, llmStatus string, mdlIds []string) error {
  523. return llm.markInstantModelsMounted(ctx, userCred, llmStatus, mdlIds, false)
  524. }
  525. func (llm *SLLM) MarkInstantModelsMounted(ctx context.Context, userCred mcclient.TokenCredential, llmStatus string, mdlIds []string) error {
  526. return llm.markInstantModelsMounted(ctx, userCred, llmStatus, mdlIds, true)
  527. }
  528. func (llm *SLLM) markInstantModelsMounted(ctx context.Context, userCred mcclient.TokenCredential, llmStatus string, mdlIds []string, mounted bool) error {
  529. boolFalse := false
  530. boolTrue := true
  531. var isProbed *bool
  532. if !mounted {
  533. isProbed = &boolFalse
  534. } else {
  535. isProbed = &boolTrue
  536. }
  537. var errs []error
  538. for i := range mdlIds {
  539. _, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mdlIds[i], "", "", isProbed, &mounted)
  540. if err != nil {
  541. errs = append(errs, err)
  542. }
  543. }
  544. if len(errs) > 0 {
  545. return errors.NewAggregate(errs)
  546. }
  547. if llmStatus == apis.LLM_STATUS_RUNNING {
  548. err := llm.RefreshInstantModels(ctx, userCred, true)
  549. if err != nil {
  550. return errors.Wrap(err, "RefreshApps")
  551. }
  552. }
  553. mountedModelsFullName, err := llm.FetchMountedModelFullName()
  554. if err != nil {
  555. return errors.Wrap(err, "FetchMountedModelFullName")
  556. }
  557. {
  558. // save mounted apps to volume
  559. err := llm.UpdateVolumeMountedModelFullNames(mountedModelsFullName)
  560. if err != nil {
  561. return errors.Wrap(err, "UpdateVolumeMountedModelFullNames")
  562. }
  563. }
  564. logclient.AddActionLogWithContext(ctx, llm, logclient.ACT_UPDATE, mountedModelsFullName, userCred, true)
  565. return nil
  566. }
  567. func (llm *SLLM) UpdateVolumeMountedModelFullNames(mdlFullNames []string) error {
  568. volume, err := llm.GetVolume()
  569. if err != nil {
  570. return errors.Wrap(err, "GetVolume")
  571. }
  572. return volume.UpdateMountedModelFullNames(mdlFullNames)
  573. }
  574. type mdlFullNameInfo struct {
  575. InstantModelId string // instant model 主键 id
  576. ModelFullName string
  577. IsMounted bool
  578. }
  579. func (llm *SLLM) UpdateMountedModelFullNames(ctx context.Context, userCred mcclient.TokenCredential, mdlinfos []string, isReset bool, imageId string, skuId string) error {
  580. mdlFullNameInfos := make(map[string]*mdlFullNameInfo)
  581. for i := range mdlinfos {
  582. parts := strings.Split(mdlinfos[i], "@")
  583. mdlFullNameInfos[parts[0]] = &mdlFullNameInfo{
  584. InstantModelId: parts[0],
  585. ModelFullName: parts[1],
  586. IsMounted: false,
  587. }
  588. }
  589. preinstallModel := true
  590. if preinstallModel {
  591. sku, err := llm.GetLLMSku(skuId)
  592. if err != nil {
  593. return errors.Wrap(err, "GetLLMSku")
  594. }
  595. var deletedModelIds []string
  596. if !isReset {
  597. deletedModelIds, err = GetLLMInstantModelManager().getDeletedModelIds(llm.Id)
  598. if err != nil {
  599. return errors.Wrap(err, "getDeletedModelIds")
  600. }
  601. }
  602. mountedModels := sku.GetMountedModels()
  603. for i := range mountedModels {
  604. instMdl, err := GetInstantModelManager().FetchByIdOrName(ctx, userCred, mountedModels[i])
  605. if err != nil {
  606. return errors.Wrap(err, "FetchByIdOrName")
  607. }
  608. instantModle := instMdl.(*SInstantModel)
  609. if !isReset && slices.Contains(deletedModelIds, instantModle.Id) {
  610. // if not reset, and the model is deleted, skip it
  611. continue
  612. }
  613. if _, ok := mdlFullNameInfos[instantModle.Id]; !ok {
  614. mdlFullNameInfos[instantModle.Id] = &mdlFullNameInfo{
  615. InstantModelId: instantModle.Id,
  616. ModelFullName: instantModle.ModelName + ":" + instantModle.ModelTag,
  617. IsMounted: false,
  618. }
  619. }
  620. }
  621. }
  622. boolTrue := true
  623. boolFalse := false
  624. mountedModels, err := llm.FetchModels(nil, &boolTrue, nil)
  625. if err != nil {
  626. return errors.Wrap(err, "FetchApps")
  627. }
  628. for i := range mountedModels {
  629. find := false
  630. if _, ok := mdlFullNameInfos[mountedModels[i].InstantModelId]; ok {
  631. find = true
  632. mdlFullNameInfos[mountedModels[i].InstantModelId].IsMounted = true
  633. }
  634. if isReset && !find {
  635. // remove instant model not in mdlInfos
  636. mountedModel := mountedModels[i]
  637. log.Debugf("UpdateMountedModelFullNames remove model %s", mountedModel.InstantModelId)
  638. _, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mountedModel.InstantModelId, "", "", &boolFalse, &boolFalse)
  639. if err != nil {
  640. return errors.Wrap(err, "remove instant model")
  641. }
  642. }
  643. }
  644. installModelFullNames := make([]string, 0)
  645. for _, mdlFullNameInfo := range mdlFullNameInfos {
  646. installModelFullNames = append(installModelFullNames, mdlFullNameInfo.InstantModelId)
  647. if !mdlFullNameInfo.IsMounted {
  648. modelName, modelTag, _ := llm.GetLargeLanguageModelName(mdlFullNameInfo.ModelFullName)
  649. _, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mdlFullNameInfo.InstantModelId, modelName, modelTag, &boolFalse, &boolTrue)
  650. if err != nil {
  651. return errors.Wrap(err, "install model")
  652. }
  653. }
  654. }
  655. volume, _ := llm.GetVolume()
  656. if volume != nil {
  657. err := llm.UpdateVolumeMountedModelFullNames(installModelFullNames)
  658. if err != nil {
  659. return errors.Wrap(err, "UpdateVolumeMountedModelFullNames")
  660. }
  661. }
  662. return nil
  663. }
  664. func (llm *SLLM) GetMountedModelsPostOverlay() ([]*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
  665. boolTrue := true
  666. mdls, err := llm.FetchModels(nil, &boolTrue, nil)
  667. if err != nil {
  668. return nil, errors.Wrap(err, "fetchApps")
  669. }
  670. if len(mdls) == 0 {
  671. return nil, nil
  672. }
  673. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  674. if err != nil {
  675. return nil, nil
  676. }
  677. overlays, err := models2overlays(drv, mdls, false)
  678. if err != nil {
  679. return nil, errors.Wrap(err, "models2overlays")
  680. }
  681. return overlays, nil
  682. }
  683. func (llm *SLLM) getMountingModelsPostOverlay(ctx context.Context, input apis.LLMSyncModelTaskInput, existingMdls []SLLMInstantModel) ([]SLLMInstantModel, []*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
  684. var models []SLLMInstantModel
  685. for i := range input.Models {
  686. if input.Method == apis.QuickModelInstall || input.Method == apis.QuickModelReinstall {
  687. mdl := input.Models[i]
  688. if input.Method == apis.QuickModelInstall {
  689. existingModel := findInstantModelWithModelInfo(existingMdls, mdl)
  690. if existingModel != nil && (existingModel.IsProbed || existingModel.IsMounted) {
  691. // if the model is already probed or mounted, skip mount
  692. continue
  693. }
  694. }
  695. model, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mdl.Id, mdl.DisplayName, mdl.Tag, nil, nil)
  696. if err != nil {
  697. return nil, nil, errors.Wrapf(err, "updateInstantModel %s", mdl.Id)
  698. }
  699. models = append(models, *model)
  700. }
  701. }
  702. if len(models) == 0 {
  703. return nil, nil, nil
  704. }
  705. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  706. if err != nil {
  707. return nil, nil, nil
  708. }
  709. overlays, err := models2overlays(drv, models, true)
  710. if err != nil {
  711. return nil, nil, errors.Wrap(err, "models2overlays")
  712. }
  713. return models, overlays, nil
  714. }
  715. func models2overlays(drv ILLMContainerInstantModelDriver, models []SLLMInstantModel, isInstall bool) ([]*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
  716. var errs []error
  717. var allDirs []apis.LLMMountDirInfo
  718. for i := range models {
  719. mdlDirs, err := models[i].getMountPaths(isInstall)
  720. if err != nil {
  721. errs = append(errs, err)
  722. continue
  723. }
  724. allDirs = append(allDirs, mdlDirs...)
  725. }
  726. if len(allDirs) == 0 {
  727. if len(errs) > 0 {
  728. return nil, errors.Wrap(errors.NewAggregate(errs), "getMountPaths")
  729. }
  730. return nil, nil
  731. }
  732. if len(errs) > 0 {
  733. log.Errorf("models2overlays getMountPaths error %s", errors.NewAggregate(errs))
  734. }
  735. var overlays []*commonapi.ContainerVolumeMountDiskPostOverlay
  736. for i := range allDirs {
  737. overlay := drv.GetDirPostOverlay(allDirs[i])
  738. overlays = append(overlays, overlay)
  739. }
  740. return overlays, nil
  741. }
  742. func (llm *SLLM) InstallInstantModels(ctx context.Context, userCred mcclient.TokenCredential, dirs []string, mdlIds []string) error {
  743. drv, err := GetLLMContainerInstantModelDriver(llm.GetLLMContainerDriver().GetType())
  744. if err != nil {
  745. return nil
  746. }
  747. return drv.InstallModel(ctx, userCred, llm, dirs, mdlIds)
  748. }
  749. func (llm *SLLM) EnsureInstantModelsInstalled(ctx context.Context, userCred mcclient.TokenCredential, mdlIds []string) error {
  750. mdlMap, err := llm.getProbedInstantModelsExt(ctx, userCred, mdlIds...)
  751. if err != nil {
  752. return errors.Wrap(err, "FetchApps")
  753. }
  754. if len(mdlIds) == 0 {
  755. return nil
  756. }
  757. instModels := make(map[string]SInstantModel)
  758. if err := db.FetchModelObjectsByIds(GetInstantModelManager(), "id", mdlIds, &instModels); err != nil {
  759. return errors.Wrap(err, "FetchModelObjectsByIds")
  760. }
  761. var errs []error
  762. for _, mdlId := range mdlIds {
  763. if _, ok := mdlMap[mdlId]; ok {
  764. continue
  765. }
  766. instModel, ok := instModels[mdlId]
  767. if !ok {
  768. errs = append(errs, errors.Wrapf(errors.ErrNotFound, "mdlId %s", mdlId))
  769. continue
  770. }
  771. found := false
  772. for _, info := range mdlMap {
  773. if info.ModelId == instModel.ModelId {
  774. found = true
  775. break
  776. }
  777. }
  778. if !found {
  779. errs = append(errs, errors.Wrapf(errors.ErrNotFound, "mdlId %s", mdlId))
  780. }
  781. }
  782. if len(errs) > 0 {
  783. return errors.NewAggregate(errs)
  784. }
  785. return nil
  786. }