instantmodel.go 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. package models
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  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/util/httputils"
  13. "yunion.io/x/pkg/utils"
  14. "yunion.io/x/sqlchemy"
  15. commonapis "yunion.io/x/onecloud/pkg/apis"
  16. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  17. imageapi "yunion.io/x/onecloud/pkg/apis/image"
  18. apis "yunion.io/x/onecloud/pkg/apis/llm"
  19. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  20. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  21. "yunion.io/x/onecloud/pkg/httperrors"
  22. "yunion.io/x/onecloud/pkg/llm/options"
  23. "yunion.io/x/onecloud/pkg/mcclient"
  24. "yunion.io/x/onecloud/pkg/mcclient/auth"
  25. computemodules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  26. imagemodules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
  27. commonoptions "yunion.io/x/onecloud/pkg/mcclient/options"
  28. "yunion.io/x/onecloud/pkg/util/logclient"
  29. "yunion.io/x/onecloud/pkg/util/procutils"
  30. "yunion.io/x/onecloud/pkg/util/stringutils2"
  31. )
  32. var instantModelManager *SInstantModelManager
  33. func init() {
  34. GetInstantModelManager()
  35. }
  36. type SInstantModelManager struct {
  37. db.SSharableVirtualResourceBaseManager
  38. db.SEnabledResourceBaseManager
  39. }
  40. func GetInstantModelManager() *SInstantModelManager {
  41. if instantModelManager != nil {
  42. return instantModelManager
  43. }
  44. instantModelManager = &SInstantModelManager{
  45. SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
  46. SInstantModel{},
  47. "instant_models_tbl",
  48. "llm_instant_model",
  49. "llm_instant_models",
  50. ),
  51. }
  52. instantModelManager.SetVirtualObject(instantModelManager)
  53. return instantModelManager
  54. }
  55. type SInstantModel struct {
  56. db.SSharableVirtualResourceBase
  57. db.SEnabledResourceBase
  58. LlmType string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"`
  59. ModelId string `width:"128" charset:"ascii" list:"user" create:"optional"`
  60. ModelName string `width:"128" charset:"ascii" list:"user" create:"required"`
  61. ModelTag string `width:"64" charset:"ascii" list:"user" create:"required"`
  62. ImageId string `width:"128" charset:"ascii" list:"user" create:"optional" update:"user"`
  63. Mounts []string `charset:"ascii" list:"user" create:"optional" update:"user"`
  64. Size int64 `nullable:"true" list:"user" create:"optional"`
  65. ActualSizeMb int32 `nullable:"true" list:"user" update:"user"`
  66. AutoCache bool `list:"user"`
  67. }
  68. // climc instant-app-list
  69. func (man *SInstantModelManager) ListItemFilter(
  70. ctx context.Context,
  71. q *sqlchemy.SQuery,
  72. userCred mcclient.TokenCredential,
  73. input apis.InstantModelListInput,
  74. ) (*sqlchemy.SQuery, error) {
  75. var err error
  76. q, err = man.SSharableVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.SharableVirtualResourceListInput)
  77. if err != nil {
  78. return nil, errors.Wrap(err, "SSharableBaseResourceManager.ListItemFilter")
  79. }
  80. q, err = man.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledResourceBaseListInput)
  81. if err != nil {
  82. return nil, errors.Wrap(err, "SEnabledResourceBaseManager.ListItemFilter")
  83. }
  84. if len(input.ModelName) > 0 {
  85. q = q.In("model_name", input.ModelName)
  86. }
  87. if len(input.ModelTag) > 0 {
  88. q = q.In("model_tag", input.ModelTag)
  89. }
  90. if len(input.ModelId) > 0 {
  91. q = q.In("model_id", input.ModelId)
  92. }
  93. if len(input.Image) > 0 {
  94. s := auth.GetSession(ctx, userCred, options.Options.Region)
  95. params := commonoptions.BaseListOptions{}
  96. params.Scope = "max"
  97. boolFalse := false
  98. params.Details = &boolFalse
  99. limit := 2048
  100. params.Limit = &limit
  101. params.Filter = []string{fmt.Sprintf("name.contains(%s)", input.Image)}
  102. results, err := imagemodules.Images.List(s, jsonutils.Marshal(params))
  103. if err != nil {
  104. return nil, errors.Wrap(err, "List")
  105. }
  106. imageIds := make([]string, 0)
  107. for i := range results.Data {
  108. idstr, _ := results.Data[i].GetString("id")
  109. imageIds = append(imageIds, idstr)
  110. }
  111. q = q.In("image_id", imageIds)
  112. }
  113. if len(input.Mounts) > 0 {
  114. q = q.Contains("mounts", input.Mounts)
  115. }
  116. if input.AutoCache != nil {
  117. q = q.Equals("auto_cache", *input.AutoCache)
  118. }
  119. return q, nil
  120. }
  121. func (man *SInstantModelManager) FetchCustomizeColumns(
  122. ctx context.Context,
  123. userCred mcclient.TokenCredential,
  124. query jsonutils.JSONObject,
  125. objs []interface{},
  126. fields stringutils2.SSortedStrings,
  127. isList bool,
  128. ) []apis.InstantModelDetails {
  129. res := make([]apis.InstantModelDetails, len(objs))
  130. imageIds := make([]string, 0)
  131. mdlIds := make([]string, 0)
  132. virows := man.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  133. for i := range res {
  134. res[i].SharableVirtualResourceDetails = virows[i]
  135. instModel := objs[i].(*SInstantModel)
  136. if len(instModel.ImageId) > 0 {
  137. imageIds = append(imageIds, instModel.ImageId)
  138. }
  139. if len(instModel.ModelId) > 0 {
  140. mdlIds = append(mdlIds, instModel.ModelId)
  141. }
  142. }
  143. s := auth.GetSession(ctx, userCred, options.Options.Region)
  144. imageMap := make(map[string]imageapi.ImageDetails)
  145. if len(imageIds) > 0 {
  146. params := imageapi.ImageListInput{}
  147. params.Ids = imageIds
  148. params.VirtualResourceListInput.Scope = "max"
  149. details := false
  150. params.Details = &details
  151. limit := len(imageIds)
  152. params.Limit = &limit
  153. params.Field = []string{"id", "name"}
  154. imageList, err := imagemodules.Images.List(s, jsonutils.Marshal(params))
  155. if err != nil {
  156. log.Errorf("list image fail %s", err)
  157. } else {
  158. for i := range imageList.Data {
  159. imgDetails := imageapi.ImageDetails{}
  160. err := imageList.Data[i].Unmarshal(&imgDetails)
  161. if err != nil {
  162. log.Errorf("unmarshal image info %s fail %s", imageList.Data[i], err)
  163. } else {
  164. imageMap[imgDetails.Id] = imgDetails
  165. }
  166. }
  167. }
  168. }
  169. type imageCacheStatus struct {
  170. CachedCount int
  171. CacheCount int
  172. }
  173. imageCacheStatusTbl := make(map[string]*imageCacheStatus)
  174. if len(imageIds) > 0 {
  175. params := commonoptions.BaseListOptions{}
  176. params.Scope = "max"
  177. params.Filter = []string{fmt.Sprintf("cachedimage_id.in(%s)", strings.Join(imageIds, ","))}
  178. details := false
  179. params.Details = &details
  180. limit := 1024
  181. params.Limit = &limit
  182. params.Field = []string{"storagecache_id", "cachedimage_id", "status"}
  183. offset := -1
  184. total := 0
  185. for offset < 0 || offset < total {
  186. if offset > 0 {
  187. params.Offset = &offset
  188. } else {
  189. offset = 0
  190. }
  191. resp, err := computemodules.Storagecachedimages.List(s, jsonutils.Marshal(params))
  192. if err != nil {
  193. log.Errorf("Storagecachedimages.List fail %s", err)
  194. break
  195. }
  196. for i := range resp.Data {
  197. sci := computeapi.StoragecachedimageDetails{}
  198. err := resp.Data[i].Unmarshal(&sci)
  199. if err != nil {
  200. log.Errorf("unmarshal image info %s fail %s", resp.Data[i], err)
  201. } else {
  202. if _, ok := imageCacheStatusTbl[sci.CachedimageId]; !ok {
  203. imageCacheStatusTbl[sci.CachedimageId] = &imageCacheStatus{}
  204. }
  205. if sci.Status == computeapi.CACHED_IMAGE_STATUS_ACTIVE {
  206. imageCacheStatusTbl[sci.CachedimageId].CachedCount++
  207. }
  208. imageCacheStatusTbl[sci.CachedimageId].CacheCount++
  209. }
  210. }
  211. offset += len(resp.Data)
  212. total = resp.Total
  213. }
  214. }
  215. llmInstModelQ := GetLLMInstantModelManager().Query().In("model_id", mdlIds).IsFalse("deleted")
  216. llmInstModels := make([]SLLMInstantModel, 0)
  217. err := db.FetchModelObjects(GetLLMInstantModelManager(), llmInstModelQ, &llmInstModels)
  218. if err != nil {
  219. log.Errorf("fetch llm instant models fail %s", err)
  220. }
  221. llmIds := make([]string, 0)
  222. for i := range llmInstModels {
  223. if !utils.IsInArray(llmInstModels[i].LlmId, llmIds) {
  224. llmIds = append(llmIds, llmInstModels[i].LlmId)
  225. }
  226. }
  227. llmMap := make(map[string]SLLM)
  228. if len(llmIds) > 0 {
  229. err = db.FetchModelObjectsByIds(GetLLMManager(), "id", llmIds, &llmMap)
  230. if err != nil {
  231. log.Errorf("FetchModelObjectsByIds LLMManager fail %s", err)
  232. }
  233. }
  234. modelMountedByMap := make(map[string][]apis.MountedByLLMInfo)
  235. for i := range llmInstModels {
  236. llmInstModel := llmInstModels[i]
  237. llm, ok := llmMap[llmInstModel.LlmId]
  238. if !ok {
  239. continue
  240. }
  241. info := apis.MountedByLLMInfo{
  242. LlmId: llmInstModel.LlmId,
  243. LlmName: llm.Name,
  244. }
  245. instantModelId := llmInstModel.InstantModelId
  246. if _, ok := modelMountedByMap[instantModelId]; !ok {
  247. modelMountedByMap[instantModelId] = make([]apis.MountedByLLMInfo, 0)
  248. }
  249. modelMountedByMap[instantModelId] = append(modelMountedByMap[instantModelId], info)
  250. }
  251. for i := range res {
  252. instModel := objs[i].(*SInstantModel)
  253. if img, ok := imageMap[instModel.ImageId]; ok {
  254. res[i].Image = img.Name
  255. }
  256. if status, ok := imageCacheStatusTbl[instModel.ImageId]; ok {
  257. res[i].CacheCount = status.CacheCount
  258. res[i].CachedCount = status.CachedCount
  259. }
  260. if mountedBy, ok := modelMountedByMap[instModel.Id]; ok {
  261. res[i].MountedByLLMs = mountedBy
  262. }
  263. res[i].GPUMemoryRequired = instModel.GetEstimatedVramSizeMb()
  264. }
  265. return res
  266. }
  267. func (man *SInstantModelManager) GetLLMContainerInstantModelDriver(llmType apis.LLMContainerType) (ILLMContainerInstantModelDriver, error) {
  268. return GetLLMContainerInstantModelDriver(llmType)
  269. }
  270. func (man *SInstantModelManager) ValidateCreateData(
  271. ctx context.Context,
  272. userCred mcclient.TokenCredential,
  273. ownerId mcclient.IIdentityProvider,
  274. query jsonutils.JSONObject,
  275. input apis.InstantModelCreateInput,
  276. ) (apis.InstantModelCreateInput, error) {
  277. var err error
  278. input.SharableVirtualResourceCreateInput, err = man.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput)
  279. if err != nil {
  280. return input, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ValidateCreateData")
  281. }
  282. if !apis.IsLLMContainerType(string(input.LlmType)) {
  283. return input, errors.Wrapf(httperrors.ErrInvalidFormat, "invalid llm_type %s", input.LlmType)
  284. }
  285. if len(input.ImageId) > 0 {
  286. img, err := fetchImage(ctx, userCred, input.ImageId)
  287. if err != nil {
  288. return input, errors.Wrapf(err, "fetchImage %s", input.ImageId)
  289. }
  290. if img.DiskFormat != imageapi.IMAGE_DISK_FORMAT_TGZ {
  291. return input, errors.Wrapf(errors.ErrInvalidFormat, "cannot use image as template of format %s", img.DiskFormat)
  292. }
  293. {
  294. mdl, err := man.findInstantModelByImageId(img.Id)
  295. if err != nil {
  296. return input, errors.Wrap(err, "findInstantModelByImageId")
  297. }
  298. if mdl != nil {
  299. return input, errors.Wrapf(httperrors.ErrConflict, "image %s has been used by other model", input.ImageId)
  300. }
  301. }
  302. input.ImageId = img.Id
  303. input.Size = img.Size
  304. input.Status = img.Status
  305. input.ActualSizeMb = img.MinDiskMB
  306. }
  307. if len(input.Mounts) > 0 {
  308. drv, err := man.GetLLMContainerInstantModelDriver(input.LlmType)
  309. if err != nil {
  310. return input, errors.Wrap(err, "GetLLMContainerInstantModelDriver")
  311. }
  312. _, err = drv.ValidateMounts(input.Mounts, input.ModelName, input.ModelTag)
  313. if err != nil {
  314. return input, errors.Wrap(err, "validateMounts")
  315. }
  316. }
  317. input.Enabled = nil
  318. return input, nil
  319. }
  320. func (model *SInstantModel) ValidateUpdateData(
  321. ctx context.Context,
  322. userCred mcclient.TokenCredential,
  323. query jsonutils.JSONObject,
  324. input apis.InstantModelUpdateInput,
  325. ) (apis.InstantModelUpdateInput, error) {
  326. var err error
  327. input.SharableVirtualResourceBaseUpdateInput, err = model.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.SharableVirtualResourceBaseUpdateInput)
  328. if err != nil {
  329. return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData")
  330. }
  331. if len(input.ImageId) > 0 {
  332. img, err := fetchImage(ctx, userCred, input.ImageId)
  333. if err != nil {
  334. return input, errors.Wrapf(err, "fetchImage %s", input.ImageId)
  335. }
  336. if img.DiskFormat != imageapi.IMAGE_DISK_FORMAT_TGZ {
  337. return input, errors.Wrapf(errors.ErrInvalidFormat, "cannot use image as template of format %s", img.DiskFormat)
  338. }
  339. {
  340. findModel, err := GetInstantModelManager().findInstantModelByImageId(img.Id)
  341. if err != nil {
  342. return input, errors.Wrap(err, "findInstantModelByImageId")
  343. }
  344. if findModel != nil && findModel.Id != model.Id {
  345. return input, errors.Wrapf(httperrors.ErrConflict, "image %s has been used by other model", input.ImageId)
  346. }
  347. }
  348. input.ImageId = img.Id
  349. input.Size = img.Size
  350. input.ActualSizeMb = img.MinDiskMB
  351. }
  352. if len(input.Mounts) > 0 {
  353. drv, err := GetInstantModelManager().GetLLMContainerInstantModelDriver(apis.LLMContainerType(model.LlmType))
  354. if err != nil {
  355. return input, errors.Wrap(err, "GetLLMContainerInstantModelDriver")
  356. }
  357. input.Mounts, err = drv.ValidateMounts(input.Mounts, model.ModelName, model.ModelTag)
  358. if err != nil {
  359. return input, errors.Wrap(err, "validateMounts")
  360. }
  361. if len(input.Mounts) == 0 {
  362. return input, errors.Wrap(errors.ErrEmpty, "empty mounts")
  363. }
  364. }
  365. return input, nil
  366. }
  367. func (model *SInstantModel) PostCreate(
  368. ctx context.Context,
  369. userCred mcclient.TokenCredential,
  370. ownerId mcclient.IIdentityProvider,
  371. query jsonutils.JSONObject,
  372. data jsonutils.JSONObject,
  373. ) {
  374. model.syncImagePathMap(ctx, userCred)
  375. input := apis.InstantModelCreateInput{}
  376. err := data.Unmarshal(&input)
  377. if err != nil {
  378. return
  379. }
  380. if input.ImageId == "" && (input.DoNotImport == nil || !*input.DoNotImport) {
  381. model.startImportTask(ctx, userCred, apis.InstantModelImportInput{
  382. LlmType: input.LlmType,
  383. ModelName: input.ModelName,
  384. ModelTag: input.ModelTag,
  385. })
  386. }
  387. }
  388. func (model *SInstantModel) PostUpdate(
  389. ctx context.Context,
  390. userCred mcclient.TokenCredential,
  391. query jsonutils.JSONObject,
  392. data jsonutils.JSONObject,
  393. ) {
  394. model.syncImagePathMap(ctx, userCred)
  395. }
  396. func (model *SInstantModel) getImagePaths() map[string]string {
  397. drv, err := GetInstantModelManager().GetLLMContainerInstantModelDriver(apis.LLMContainerType(model.LlmType))
  398. if err != nil {
  399. log.Errorf("GetLLMContainerInstantModelDriver fail %s", err)
  400. return nil
  401. }
  402. return drv.GetImageInternalPathMounts(model)
  403. }
  404. func (model *SInstantModel) syncImagePathMap(ctx context.Context, userCred mcclient.TokenCredential) error {
  405. if len(model.ImageId) == 0 {
  406. return nil
  407. }
  408. imgPaths := model.getImagePaths()
  409. if len(imgPaths) == 0 {
  410. return nil
  411. }
  412. s := auth.GetSession(ctx, userCred, options.Options.Region)
  413. params := imageapi.ImageUpdateInput{
  414. Properties: map[string]string{
  415. "internal_path_map": jsonutils.Marshal(imgPaths).String(),
  416. "used_by_post_overlay": "true",
  417. },
  418. }
  419. _, err := imagemodules.Images.Update(s, model.ImageId, jsonutils.Marshal(params))
  420. if err != nil {
  421. return errors.Wrap(err, "Update")
  422. }
  423. return nil
  424. }
  425. func (model *SInstantModel) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.InstantModelSyncstatusInput) (jsonutils.JSONObject, error) {
  426. err := model.syncImageStatus(ctx, userCred)
  427. if err != nil {
  428. return nil, errors.Wrap(err, "syncImageStatus")
  429. }
  430. return nil, nil
  431. }
  432. func (model *SInstantModel) saveImageId(ctx context.Context, userCred mcclient.TokenCredential, imageId string) error {
  433. _, err := db.Update(model, func() error {
  434. model.ImageId = imageId
  435. return nil
  436. })
  437. if err != nil {
  438. logclient.AddActionLogWithContext(ctx, model, logclient.ACT_SAVE_IMAGE, err, userCred, false)
  439. return errors.Wrap(err, "update image_id")
  440. }
  441. logclient.AddActionLogWithContext(ctx, model, logclient.ACT_SAVE_IMAGE, imageId, userCred, true)
  442. return nil
  443. }
  444. func (model *SInstantModel) syncImageStatus(ctx context.Context, userCred mcclient.TokenCredential) error {
  445. img, err := fetchImage(ctx, userCred, model.ImageId)
  446. if err != nil {
  447. if httputils.ErrorCode(err) == 404 {
  448. model.SetStatus(ctx, userCred, imageapi.IMAGE_STATUS_DELETED, "not found")
  449. return nil
  450. }
  451. return errors.Wrapf(err, "fetchImage %s", model.ImageId)
  452. }
  453. model.SetStatus(ctx, userCred, img.Status, "syncStatus")
  454. if img.Status == imageapi.IMAGE_STATUS_ACTIVE && (model.Size != img.Size || model.ActualSizeMb != img.MinDiskMB) {
  455. _, err := db.Update(model, func() error {
  456. model.Size = img.Size
  457. model.ActualSizeMb = img.MinDiskMB
  458. return nil
  459. })
  460. if err != nil {
  461. return errors.Wrap(err, "update size")
  462. }
  463. }
  464. {
  465. err := model.syncImagePathMap(ctx, userCred)
  466. if err != nil {
  467. return errors.Wrap(err, "syncImagePathMap")
  468. }
  469. }
  470. return nil
  471. }
  472. func (man *SInstantModelManager) findInstantModelByImageId(imageId string) (*SInstantModel, error) {
  473. q := man.Query().Equals("image_id", imageId)
  474. mdls := make([]SInstantModel, 0)
  475. err := db.FetchModelObjects(man, q, &mdls)
  476. if err != nil {
  477. return nil, errors.Wrap(err, "FetchModelObjects")
  478. }
  479. if len(mdls) == 0 {
  480. return nil, nil
  481. }
  482. return &mdls[0], nil
  483. }
  484. func (man *SInstantModelManager) GetInstantModelById(id string) (*SInstantModel, error) {
  485. obj, err := man.FetchById(id)
  486. if err != nil {
  487. return nil, errors.Wrap(err, "FetchById")
  488. }
  489. return obj.(*SInstantModel), nil
  490. }
  491. func (man *SInstantModelManager) FindInstantModel(mdlId, tag string, isEnabled bool) (*SInstantModel, error) {
  492. q := man.Query().Equals("model_id", mdlId).Equals("status", imageapi.IMAGE_STATUS_ACTIVE)
  493. if isEnabled {
  494. q = q.IsTrue("enabled")
  495. }
  496. q = q.Desc("created_at")
  497. mdls := make([]SInstantModel, 0)
  498. err := db.FetchModelObjects(man, q, &mdls)
  499. if err != nil {
  500. return nil, errors.Wrap(err, "FetchModelObjects")
  501. }
  502. if len(mdls) == 0 {
  503. return nil, nil
  504. }
  505. if len(tag) > 0 {
  506. for i := range mdls {
  507. if mdls[i].ModelTag == tag {
  508. return &mdls[i], nil
  509. }
  510. }
  511. }
  512. return &mdls[0], nil
  513. }
  514. func (model *SInstantModel) PerformEnable(
  515. ctx context.Context,
  516. userCred mcclient.TokenCredential,
  517. query jsonutils.JSONObject,
  518. input commonapis.PerformEnableInput,
  519. ) (jsonutils.JSONObject, error) {
  520. if len(model.ImageId) == 0 {
  521. return nil, errors.Wrap(errors.ErrInvalidStatus, "empty image_id")
  522. }
  523. if len(model.Mounts) == 0 {
  524. return nil, errors.Wrap(errors.ErrInvalidStatus, "empty mounts")
  525. }
  526. {
  527. err := model.syncImageStatus(ctx, userCred)
  528. if err != nil {
  529. return nil, errors.Wrap(err, "syncImageStatus")
  530. }
  531. }
  532. if model.Status != imageapi.IMAGE_STATUS_ACTIVE {
  533. return nil, errors.Wrapf(errors.ErrInvalidStatus, "cannot enable model of status %s", model.Status)
  534. }
  535. // check duplicate
  536. // {
  537. // existing, err := GetInstantModelManager().findInstantModel(model.ModelId, model.ModelTag, true)
  538. // if err != nil {
  539. // return nil, errors.Wrap(err, "findInstantModel")
  540. // }
  541. // if existing != nil && existing.Id != model.Id {
  542. // return nil, errors.Wrapf(errors.ErrDuplicateId, "model of modelId %s tag %s has been enabled", model.ModelId, model.ModelTag)
  543. // }
  544. // }
  545. _, err := db.Update(model, func() error {
  546. model.SEnabledResourceBase.SetEnabled(true)
  547. return nil
  548. })
  549. if err != nil {
  550. return nil, errors.Wrap(err, "update")
  551. }
  552. return nil, nil
  553. }
  554. func (model *SInstantModel) PerformDisable(
  555. ctx context.Context,
  556. userCred mcclient.TokenCredential,
  557. query jsonutils.JSONObject,
  558. input commonapis.PerformDisableInput,
  559. ) (jsonutils.JSONObject, error) {
  560. _, err := db.Update(model, func() error {
  561. model.SEnabledResourceBase.SetEnabled(false)
  562. if model.AutoCache {
  563. model.AutoCache = false
  564. }
  565. return nil
  566. })
  567. if err != nil {
  568. return nil, errors.Wrap(err, "update")
  569. }
  570. return nil, nil
  571. }
  572. func (model *SInstantModel) PerformChangeOwner(
  573. ctx context.Context,
  574. userCred mcclient.TokenCredential,
  575. query jsonutils.JSONObject,
  576. input commonapis.PerformChangeProjectOwnerInput,
  577. ) (jsonutils.JSONObject, error) {
  578. // perform disk change owner
  579. if len(model.ImageId) > 0 {
  580. s := auth.GetSession(ctx, userCred, options.Options.Region)
  581. _, err := imagemodules.Images.PerformAction(s, model.ImageId, "change-owner", jsonutils.Marshal(input))
  582. if err != nil {
  583. return nil, errors.Wrap(err, "image change-owner")
  584. }
  585. }
  586. return model.SSharableVirtualResourceBase.PerformChangeOwner(ctx, userCred, query, input)
  587. }
  588. func (model *SInstantModel) PerformPublic(
  589. ctx context.Context,
  590. userCred mcclient.TokenCredential,
  591. query jsonutils.JSONObject,
  592. input commonapis.PerformPublicProjectInput,
  593. ) (jsonutils.JSONObject, error) {
  594. if len(model.ImageId) > 0 {
  595. s := auth.GetSession(ctx, userCred, options.Options.Region)
  596. _, err := imagemodules.Images.PerformAction(s, model.ImageId, "public", jsonutils.Marshal(input))
  597. if err != nil {
  598. return nil, errors.Wrap(err, "image public")
  599. }
  600. }
  601. return model.SSharableVirtualResourceBase.PerformPublic(ctx, userCred, query, input)
  602. }
  603. func (model *SInstantModel) PerformPrivate(
  604. ctx context.Context,
  605. userCred mcclient.TokenCredential,
  606. query jsonutils.JSONObject,
  607. input commonapis.PerformPrivateInput,
  608. ) (jsonutils.JSONObject, error) {
  609. if len(model.ImageId) > 0 {
  610. s := auth.GetSession(ctx, userCred, options.Options.Region)
  611. _, err := imagemodules.Images.PerformAction(s, model.ImageId, "private", jsonutils.Marshal(input))
  612. if err != nil {
  613. return nil, errors.Wrap(err, "image private")
  614. }
  615. }
  616. return model.SSharableVirtualResourceBase.PerformPrivate(ctx, userCred, query, input)
  617. }
  618. func (model *SInstantModel) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
  619. if model.Enabled.IsTrue() {
  620. // check if used by llm sku
  621. used, err := GetLLMSkuManager().IsPremountedModelName(model.Id)
  622. if err != nil {
  623. return errors.Wrap(err, "GetLLMSkuManager().IsPremountedModelName")
  624. }
  625. if used {
  626. return errors.Wrap(errors.ErrInvalidStatus, "cannot delete when model is used by llm sku")
  627. }
  628. // check if used by volume
  629. used, err = GetVolumeManager().IsPremountedModelName(model.Id)
  630. if err != nil {
  631. return errors.Wrap(err, "GetVolumeManager().IsPremountedModelName")
  632. }
  633. if used {
  634. return errors.Wrap(errors.ErrInvalidStatus, "cannot delete when model is used by volume")
  635. }
  636. // check if used by llm instance
  637. cnt, err := GetLLMInstantModelManager().Query().Equals("model_id", model.Id).IsFalse("deleted").CountWithError()
  638. if err != nil {
  639. return errors.Wrap(err, "GetLLMInstantModelManager().CountWithError")
  640. }
  641. if cnt > 0 {
  642. return errors.Wrap(errors.ErrInvalidStatus, "cannot delete when model is used by llm instance")
  643. }
  644. }
  645. return nil
  646. }
  647. func (model *SInstantModel) ValidateUpdateCondition(ctx context.Context) error {
  648. if model.Enabled.IsTrue() {
  649. return errors.Wrap(errors.ErrInvalidStatus, "cannot update when enabled")
  650. }
  651. return nil
  652. }
  653. func (model *SInstantModel) PerformEnableAutoCache(
  654. ctx context.Context,
  655. userCred mcclient.TokenCredential,
  656. query jsonutils.JSONObject,
  657. input apis.InstantModelEnableAutoCacheInput,
  658. ) (jsonutils.JSONObject, error) {
  659. if input.AutoCache && model.Enabled.IsFalse() {
  660. return nil, errors.Wrap(httperrors.ErrInvalidStatus, "cannot enable auto_cache for disabled app")
  661. }
  662. _, err := db.Update(model, func() error {
  663. model.AutoCache = input.AutoCache
  664. return nil
  665. })
  666. if err != nil {
  667. return nil, errors.Wrap(err, "update auto_cache")
  668. }
  669. if model.AutoCache {
  670. err := model.doCache(ctx, userCred)
  671. if err != nil {
  672. return nil, errors.Wrap(err, "doCache")
  673. }
  674. }
  675. return nil, nil
  676. }
  677. func (model *SInstantModel) doCache(ctx context.Context, userCred mcclient.TokenCredential) error {
  678. input := computeapi.CachedImageManagerCacheImageInput{}
  679. input.ImageId = model.ImageId
  680. input.AutoCache = true
  681. input.HostType = []string{computeapi.HOST_TYPE_CONTAINER}
  682. s := auth.GetSession(ctx, userCred, options.Options.Region)
  683. _, err := computemodules.Cachedimages.PerformClassAction(s, "cache-image", jsonutils.Marshal(input))
  684. if err != nil {
  685. return errors.Wrap(err, "PerformClassAction cache-image")
  686. }
  687. return nil
  688. }
  689. func (man *SInstantModelManager) PerformImport(
  690. ctx context.Context,
  691. userCred mcclient.TokenCredential,
  692. query jsonutils.JSONObject,
  693. input apis.InstantModelImportInput,
  694. ) (*SInstantModel, error) {
  695. // first create a temporary instant-app
  696. tempModel := &SInstantModel{}
  697. tempModel.SetModelManager(man, &SInstantModel{})
  698. tempModel.Name = fmt.Sprintf("tmp-instant-model-%s.%s", time.Now().Format("060102"), utils.GenRequestId(6))
  699. tempModel.ModelName = input.ModelName
  700. tempModel.ModelTag = input.ModelTag
  701. tempModel.LlmType = string(input.LlmType)
  702. tempModel.ProjectId = userCred.GetProjectId()
  703. err := man.TableSpec().Insert(ctx, tempModel)
  704. if err != nil {
  705. return nil, errors.Wrap(err, "Insert")
  706. }
  707. err = tempModel.startImportTask(ctx, userCred, input)
  708. if err != nil {
  709. return nil, errors.Wrap(err, "startImportTask")
  710. }
  711. return tempModel, nil
  712. }
  713. func (model *SInstantModel) startImportTask(ctx context.Context, userCred mcclient.TokenCredential, input apis.InstantModelImportInput) error {
  714. params := jsonutils.NewDict()
  715. params.Add(jsonutils.Marshal(input), "import_input")
  716. task, err := taskman.TaskManager.NewTask(ctx, "LLMInstantModelImportTask", model, userCred, params, "", "")
  717. if err != nil {
  718. return errors.Wrap(err, "NewTask")
  719. }
  720. task.ScheduleRun(nil)
  721. return nil
  722. }
  723. func (model *SInstantModel) DoImport(ctx context.Context, userCred mcclient.TokenCredential, s *mcclient.ClientSession, input apis.InstantModelImportInput) (tmpDir string, err error) {
  724. // ensure LLMWorkingDirectory exists
  725. if err = os.MkdirAll(options.Options.LLMWorkingDirectory, 0755); err != nil {
  726. err = errors.Wrap(err, "MkdirAll LLMWorkingDirectory")
  727. return
  728. }
  729. // create temp directory for download
  730. tmpDir, err = os.MkdirTemp(options.Options.LLMWorkingDirectory, "instant-model-*")
  731. if err != nil {
  732. err = errors.Wrap(err, "CreateTemp")
  733. return
  734. }
  735. defer func() {
  736. os.RemoveAll(tmpDir)
  737. }()
  738. drv, err := GetInstantModelManager().GetLLMContainerInstantModelDriver(input.LlmType)
  739. if err != nil {
  740. err = errors.Wrap(err, "GetLLMContainerInstantModelDriver")
  741. return
  742. }
  743. // download model from registry
  744. modelId, mounts, err := drv.DownloadModel(ctx, userCred, nil, tmpDir, input.ModelName, input.ModelTag)
  745. if err != nil {
  746. err = errors.Wrap(err, "DownloadModel")
  747. return
  748. }
  749. log.Infof("Downloaded model %s:%s with modelId: %s to %s", input.ModelName, input.ModelTag, modelId, tmpDir)
  750. // create tar.gz archive from downloaded files
  751. imagePath := fmt.Sprintf("%s/model.tgz", tmpDir)
  752. if err = createTarGz(tmpDir, imagePath); err != nil {
  753. err = errors.Wrap(err, "createTarGz")
  754. return
  755. }
  756. // upload the image
  757. imageId, err := func() (string, error) {
  758. imgFile, err := os.Open(imagePath)
  759. if err != nil {
  760. return "", errors.Wrap(err, "Open")
  761. }
  762. defer imgFile.Close()
  763. imgFileStat, err := imgFile.Stat()
  764. if err != nil {
  765. return "", errors.Wrap(err, "Stat")
  766. }
  767. imgFileSize := imgFileStat.Size()
  768. imgParams := imageapi.ImageCreateInput{}
  769. safeModelName := strings.ReplaceAll(strings.TrimSpace(input.ModelName), "/", "_")
  770. if safeModelName == "" {
  771. safeModelName = "instant-model"
  772. }
  773. imgParams.GenerateName = fmt.Sprintf("%s-%s", safeModelName, strings.TrimSpace(input.ModelTag))
  774. imgParams.DiskFormat = "tgz"
  775. imgParams.Size = &imgFileSize
  776. imgParams.Properties = map[string]string{
  777. "llm_type": string(input.LlmType),
  778. "model_name": input.ModelName,
  779. "model_tag": input.ModelTag,
  780. "model_id": modelId,
  781. }
  782. // upload the image
  783. imageObj, err := imagemodules.Images.Upload(s, jsonutils.Marshal(imgParams), imgFile, imgFileSize)
  784. if err != nil {
  785. return "", errors.Wrap(err, "Upload Image")
  786. }
  787. imageId, err := imageObj.GetString("id")
  788. if err != nil {
  789. return "", errors.Wrap(err, "Get Image Id")
  790. }
  791. return imageId, nil
  792. }()
  793. if err != nil {
  794. err = errors.Wrap(err, "upload image")
  795. return
  796. }
  797. // update the instant-model
  798. _, err = db.Update(model, func() error {
  799. // model.LlmType = string(input.LlmType)
  800. // model.ModelName = input.ModelName
  801. // model.Tag = input.ModelTag
  802. model.ModelId = modelId
  803. model.ImageId = imageId
  804. model.Mounts = mounts
  805. model.Status = imageapi.IMAGE_STATUS_SAVING
  806. return nil
  807. })
  808. if err != nil {
  809. err = errors.Wrap(err, "update instant-model")
  810. return
  811. }
  812. // wait image to be active
  813. imgDetails, err := model.WaitImageStatus(ctx, userCred, []string{imageapi.IMAGE_STATUS_ACTIVE}, 1800)
  814. if err != nil {
  815. log.Errorf("WaitImageStatus failed: %s", err)
  816. }
  817. // sync image status
  818. err = model.syncImageStatus(ctx, userCred)
  819. if err != nil {
  820. err = errors.Wrap(err, "syncImageStatus")
  821. return
  822. }
  823. if imgDetails.Status == imageapi.IMAGE_STATUS_KILLED || imgDetails.Status == imageapi.IMAGE_STATUS_DEACTIVATED {
  824. err = errors.Wrapf(httperrors.ErrInvalidStatus, "image status: %s", imgDetails.Status)
  825. return
  826. }
  827. return
  828. }
  829. // createTarGz creates a tar.gz archive from the source directory
  830. func createTarGz(srcDir string, dstPath string) error {
  831. // use -C to change directory, . to pack all contents
  832. // --exclude to exclude the output file itself (if in the same directory)
  833. dstBase := filepath.Base(dstPath)
  834. output, err := procutils.NewCommand("tar", "-czvf", dstPath, "-C", srcDir, "--exclude", dstBase, ".").Output()
  835. if err != nil {
  836. return errors.Wrapf(err, "tar -czvf %s -C %s: %s", dstPath, srcDir, output)
  837. }
  838. return nil
  839. }
  840. func (model *SInstantModel) GetImage(ctx context.Context, userCred mcclient.TokenCredential) (*imageapi.ImageDetails, error) {
  841. s := auth.GetSession(ctx, userCred, options.Options.Region)
  842. imageObj, err := imagemodules.Images.Get(s, model.ImageId, nil)
  843. if err != nil {
  844. return nil, errors.Wrap(err, "Get")
  845. }
  846. imgDetail := imageapi.ImageDetails{}
  847. err = imageObj.Unmarshal(&imgDetail)
  848. if err != nil {
  849. return nil, errors.Wrap(err, "Unmarshal")
  850. }
  851. return &imgDetail, nil
  852. }
  853. func (model *SInstantModel) WaitImageStatus(ctx context.Context, userCred mcclient.TokenCredential, targetStatus []string, timeoutSecs int) (*imageapi.ImageDetails, error) {
  854. expire := time.Now().Add(time.Second * time.Duration(timeoutSecs))
  855. for time.Now().Before(expire) {
  856. img, err := model.GetImage(ctx, userCred)
  857. if err != nil {
  858. return nil, errors.Wrap(err, "GetImage")
  859. }
  860. if utils.IsInArray(img.Status, targetStatus) {
  861. return img, nil
  862. }
  863. if strings.Contains(img.Status, "fail") || img.Status == imageapi.IMAGE_STATUS_KILLED || img.Status == imageapi.IMAGE_STATUS_DEACTIVATED {
  864. return nil, errors.Wrap(errors.ErrInvalidStatus, img.Status)
  865. }
  866. time.Sleep(2 * time.Second)
  867. }
  868. return nil, errors.Wrapf(httperrors.ErrTimeout, "wait image status %s timeout", targetStatus)
  869. }
  870. func (model *SInstantModel) GetActualSizeMb() int32 {
  871. if model.ActualSizeMb > 0 {
  872. return model.ActualSizeMb
  873. }
  874. return int32(model.Size / 1024 / 1024)
  875. }
  876. func (model *SInstantModel) GetEstimatedVramSizeBytes() int64 {
  877. if model.Size <= 0 {
  878. return 0
  879. }
  880. // 1.0x 基础权重 + 0.15x 动态开销(KV Cache) + 500MB 框架固定开销
  881. return int64(float64(model.Size)*1.15) + 500*1024*1024
  882. }
  883. func (model *SInstantModel) GetEstimatedVramSizeMb() int64 {
  884. return model.GetEstimatedVramSizeBytes() / 1024 / 1024
  885. }
  886. func (model *SInstantModel) CleanupImportTmpDir(ctx context.Context, userCred mcclient.TokenCredential, tmpDir string) error {
  887. // sync image status
  888. err := model.syncImageStatus(ctx, userCred)
  889. if err != nil {
  890. return errors.Wrap(err, "syncImageStatus")
  891. }
  892. if tmpDir == "" {
  893. return nil
  894. }
  895. log.Infof("Cleaning up tmpDir: %s", tmpDir)
  896. if err := procutils.NewCommand("rm", "-rf", tmpDir).Run(); err != nil {
  897. return errors.Wrapf(err, "Failed to remove tmpDir %s", tmpDir)
  898. }
  899. return nil
  900. }
  901. // GetOllamaRegistryYAML returns the Ollama registry YAML content
  902. func (man *SInstantModelManager) GetOllamaRegistryYAML() string {
  903. return apis.OLLAMA_REGISTRY_YAML
  904. }
  905. func (man *SInstantModelManager) GetPropertyCommunityRegistry(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  906. return jsonutils.Marshal(apis.OllamaRegistry), nil
  907. }