Automated cherry pick of #23898: Feature/pre quick model (#23899)

* feat: add preinstall for quick-models

* fix: drop error when duplicate blobs
This commit is contained in:
cwz_eikoh
2025-12-05 10:33:55 +08:00
committed by GitHub
parent 4ca1fbfda5
commit 9ac509e55b
13 changed files with 281 additions and 70 deletions
+6 -6
View File
@@ -119,9 +119,9 @@ type LLMSkuDetails struct {
Template string
}
// type MountedAppResourceDetails struct {
// MountedApps []string `json:"mounted_apps"`
// }
type MountedAppResourceDetails struct {
MountedModels []string `json:"mounted_models"`
}
type LLMSKuBaseCreateInput struct {
apis.SharableVirtualResourceCreateInput
@@ -142,7 +142,6 @@ type LLMSKuBaseCreateInput struct {
type LLMSkuBaseUpdateInput struct {
apis.SharableVirtualResourceBaseUpdateInput
// MountedAppResourceUpdateInput
Cpu *int `json:"cpu"`
Memory *int `json:"memory"`
@@ -165,7 +164,7 @@ type LLMSkuBaseUpdateInput struct {
type LLMSkuListInput struct {
apis.SharableVirtualResourceListInput
// MountedAppResourceListInput
MountedModelResourceListInput
LLMType string `json:"llm_type"`
}
@@ -180,6 +179,7 @@ type LLMSkuCreateInput struct {
type LLMSkuUpdateInput struct {
LLMSkuBaseUpdateInput
MountedModelResourceUpdateInput
LLMImageId string `json:"llm_image_id"`
LLMModelName string `json:"llm_model_name"`
@@ -195,7 +195,7 @@ type LLMSkuUpdateInput struct {
type DifySkulListInput struct {
apis.SharableVirtualResourceListInput
// MountedAppResourceListInput
MountedModelResourceListInput
}
type DifySkuCreateInput struct {
+77 -17
View File
@@ -11,7 +11,6 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/apis"
commonapi "yunion.io/x/onecloud/pkg/apis"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
@@ -37,7 +36,7 @@ func (o *ollama) GetType() api.LLMContainerType {
func (o *ollama) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) *computeapi.PodContainerCreateInput {
spec := computeapi.ContainerSpec{
ContainerSpec: apis.ContainerSpec{
ContainerSpec: commonapi.ContainerSpec{
Image: image.ToContainerImage(),
ImageCredentialId: image.CredentialId,
EnableLxcfs: true,
@@ -49,7 +48,7 @@ func (o *ollama) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *
for i := range *sku.Devices {
index := i
spec.Devices = append(spec.Devices, &computeapi.ContainerDevice{
Type: apis.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
Type: commonapi.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
IsolatedDevice: &computeapi.ContainerIsolatedDevice{
Index: &index,
},
@@ -58,7 +57,7 @@ func (o *ollama) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *
} else if len(devices) > 0 {
for i := range devices {
spec.Devices = append(spec.Devices, &computeapi.ContainerDevice{
Type: apis.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
Type: commonapi.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
IsolatedDevice: &computeapi.ContainerIsolatedDevice{
Id: devices[i].Id,
},
@@ -87,29 +86,28 @@ func (o *ollama) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *
// }
// process volume mounts
vols := make([]*apis.ContainerVolumeMount, 0)
// appVolIndex := 2
// postOverlays, err := d.GetMountedAppsPostOverlay()
// if err != nil {
// log.Errorf("GetMountedAppsPostOverlay failed %s", err)
// }
// vols = append(spec.VolumeMounts, GetDiskVolumeMounts(sku.Volumes, appVolIndex, postOverlays)...)
postOverlays, err := llm.GetMountedModelsPostOverlay()
if err != nil {
log.Errorf("GetMountedModelsPostOverlay failed %s", err)
}
vols := spec.VolumeMounts
// udevPath := filepath.Join(GetTmpSocketsHostPath(d.GetName()), "udev")
diskIndex := 0
ctrVols := []*apis.ContainerVolumeMount{
ctrVols := []*commonapi.ContainerVolumeMount{
{
Disk: &apis.ContainerVolumeMountDisk{
Disk: &commonapi.ContainerVolumeMountDisk{
SubDirectory: api.LLM_OLLAMA,
Overlay: &apis.ContainerVolumeMountDiskOverlay{
Overlay: &commonapi.ContainerVolumeMountDiskOverlay{
LowerDir: []string{api.LLM_OLLAMA_HOST_PATH},
},
Index: &diskIndex,
PostOverlay: postOverlays,
Index: &diskIndex,
},
Type: apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
MountPath: api.LLM_OLLAMA_BASE_PATH,
ReadOnly: false,
Propagation: apis.MOUNTPROPAGATION_PROPAGATION_HOST_TO_CONTAINER,
Propagation: commonapi.MOUNTPROPAGATION_PROPAGATION_HOST_TO_CONTAINER,
},
}
vols = append(vols, ctrVols...)
@@ -360,6 +358,18 @@ func (o *ollama) ValidateMounts(mounts []string, mdlName string, mdlTag string)
return mounts, nil
}
func (o *ollama) CheckDuplicateMounts(errStr string, dupIndex int) string {
// Find the first model path before "duplicated container target dirs"
firstPath := extractModelPath(errStr[:dupIndex], api.LLM_OLLAMA_MANIFESTS_BASE_PATH, true)
firstModel := parseModelName(firstPath)
// Find the second model path after "duplicated container target dirs"
secondPath := extractModelPath(errStr[dupIndex:], api.LLM_OLLAMA_MANIFESTS_BASE_PATH, false)
secondModel := parseModelName(secondPath)
return fmt.Sprintf("Model %s and %s have duplicated container target dirs", firstModel, secondModel)
}
// func download(ctx context.Context, userCred mcclient.TokenCredential, containerId string, taskId string, webUrl string, path string) error {
// input := &computeapi.ContainerDownloadFileInput{
// WebUrl: webUrl,
@@ -451,3 +461,53 @@ func getManifests(ctx context.Context, containerId string, modelName string, mod
return manifests, nil
}
func extractModelPath(str, startMarker string, findLast bool) string {
var idx int
if findLast {
idx = strings.LastIndex(str, startMarker)
} else {
idx = strings.Index(str, startMarker)
}
if idx == -1 {
return ""
}
pathStart := idx
pathEnd := -1
for i := pathStart; i < len(str); i++ {
if str[i] == '\\' && i+4 < len(str) && str[i+1] == '\\' && str[i+2] == '\\' && str[i+3] == '\\' && str[i+4] == '"' { // for \\\\\"
pathEnd = i
break
}
if str[i] == '"' || str[i] == ',' || str[i] == ':' || str[i] == '}' {
pathEnd = i
break
}
}
var extracted string
if pathEnd != -1 {
extracted = str[pathStart:pathEnd]
} else {
extracted = str[pathStart:]
}
return extracted
}
func parseModelName(path string) string {
if !strings.HasPrefix(path, api.LLM_OLLAMA_MANIFESTS_BASE_PATH) {
return ""
}
model := strings.TrimPrefix(path, api.LLM_OLLAMA_MANIFESTS_BASE_PATH)
model = strings.TrimPrefix(model, "/")
lastSlash := strings.LastIndex(model, "/")
if lastSlash != -1 {
name := model[:lastSlash]
tag := model[lastSlash+1:]
tag = strings.TrimRight(tag, `\`)
return name + ":" + tag
}
return strings.TrimRight(model, `\`)
}
+31
View File
@@ -3,12 +3,14 @@ package models
import (
"context"
"database/sql"
"fmt"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
@@ -252,6 +254,35 @@ func (llm *SLLMBase) GetVolume() (*SVolume, error) {
return volume, nil
}
func GetDiskVolumeMounts(vols *api.Volumes, containerIndex int, postOverlays []*apis.ContainerVolumeMountDiskPostOverlay) []*apis.ContainerVolumeMount {
if vols == nil {
return nil
}
mounts := make([]*apis.ContainerVolumeMount, 0)
for idx, vol := range *vols {
volRelation := vol.GetVolumeByContainer(containerIndex)
if volRelation == nil {
continue
}
mounts = append(mounts, &apis.ContainerVolumeMount{
UniqueName: fmt.Sprintf("volume-%d-%d-%s", idx, containerIndex, volRelation.MountPath),
Type: apis.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
MountPath: volRelation.MountPath,
Propagation: apis.MOUNTPROPAGATION_PROPAGATION_HOST_TO_CONTAINER,
Disk: &apis.ContainerVolumeMountDisk{
Index: &idx,
SubDirectory: volRelation.SubDirectory,
Overlay: volRelation.Overlay,
PostOverlay: postOverlays,
},
FsUser: volRelation.FsUser,
FsGroup: volRelation.FsGroup,
})
}
return mounts
}
// 取消自动删除
func (llm *SLLMBase) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
return nil
+1
View File
@@ -75,6 +75,7 @@ type ILLMContainerInstantApp interface {
GetSaveDirectories(sApp *SInstantModel) (string, []string, error)
ValidateMounts(mounts []string, mdlName string, mdlTag string) ([]string, error)
CheckDuplicateMounts(errStr string, dupIndex int) string
GetInstantModelIdByPostOverlay(postOverlay *commonapi.ContainerVolumeMountDiskPostOverlay, mdlNameToId map[string]string) string
GetDirPostOverlay(dir llm.LLMMountDirInfo) *commonapi.ContainerVolumeMountDiskPostOverlay
+14
View File
@@ -72,6 +72,20 @@ func (man *SLLMInstantModelManager) fetchLLMInstantModel(llmId string, mdlId str
return &llmInstantModel, nil
}
func (man *SLLMInstantModelManager) getDeletedModelIds(llmId string) ([]string, error) {
q := man.RawQuery("model_id").Equals("llm_id", llmId).IsTrue("deleted").Distinct()
llmInstantModel := make([]SLLMInstantModel, 0)
err := db.FetchModelObjects(man, q, &llmInstantModel)
if err != nil {
return nil, errors.Wrap(err, "Query")
}
modelIds := make([]string, len(llmInstantModel))
for i := range llmInstantModel {
modelIds[i] = llmInstantModel[i].ModelId
}
return modelIds, nil
}
func (man *SLLMInstantModelManager) updateInstantModel(ctx context.Context, llmId string, mdlId string, mdlName string, tag string, probed, mounted *bool) (*SLLMInstantModel, error) {
mdl, err := man.fetchLLMInstantModel(llmId, mdlId)
if err != nil && errors.Cause(err) != errors.ErrNotFound {
+112
View File
@@ -3,7 +3,9 @@ package models
import (
"context"
"database/sql"
"fmt"
"net/http"
"slices"
"strings"
"time"
@@ -586,6 +588,116 @@ func (llm *SLLM) UpdateVolumeMountedModelFullNames(mdlFullNames []string) error
return volume.UpdateMountedModelFullNames(mdlFullNames)
}
type mdlFullNameInfo struct {
ModelId string
ModelFullName string
IsMounted bool
}
func (llm *SLLM) UpdateMountedModelFullNames(ctx context.Context, mdlinfos []string, isReset bool, imageId string, skuId string) error {
mdlFullNameInfos := make(map[string]*mdlFullNameInfo)
for i := range mdlinfos {
parts := strings.Split(mdlinfos[i], "@")
mdlFullNameInfos[parts[0]] = &mdlFullNameInfo{
ModelId: parts[0],
ModelFullName: parts[1],
IsMounted: false,
}
}
preinstallModel := true
if preinstallModel {
sku, err := llm.GetLLMSku(skuId)
if err != nil {
return errors.Wrap(err, "GetLLMSku")
}
var deletedModelIds []string
if !isReset {
deletedModelIds, err = GetLLMInstantModelManager().getDeletedModelIds(llm.Id)
if err != nil {
return errors.Wrap(err, "getDeletedModelIds")
}
}
for i := range sku.MountedModels {
parts := strings.Split(sku.MountedModels[i], "@")
if !isReset && slices.Contains(deletedModelIds, parts[0]) {
// if not reset, and the package is deleted, skip it
continue
}
if _, ok := mdlFullNameInfos[parts[0]]; !ok {
mdlFullNameInfos[parts[0]] = &mdlFullNameInfo{
ModelId: parts[0],
ModelFullName: parts[1],
IsMounted: false,
}
}
}
}
boolTrue := true
boolFalse := false
mountedModels, err := llm.FetchModels(nil, &boolTrue, nil)
if err != nil {
return errors.Wrap(err, "FetchApps")
}
for i := range mountedModels {
find := false
if _, ok := mdlFullNameInfos[mountedModels[i].ModelId]; ok {
find = true
mdlFullNameInfos[mountedModels[i].ModelId].IsMounted = true
}
if isReset && !find {
// remove instant model not in mdlInfos
mountedModel := mountedModels[i]
log.Debugf("UpdateMountedModelFullNames remove model %s", mountedModel.ModelId)
_, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mountedModel.ModelId, "", "", &boolFalse, &boolFalse)
if err != nil {
return errors.Wrap(err, "remove instant model")
}
}
}
installModelFullNames := make([]string, 0)
for _, mdlFullNameInfo := range mdlFullNameInfos {
installModelFullNames = append(installModelFullNames, fmt.Sprintf("%s-%s", mdlFullNameInfo.ModelFullName, mdlFullNameInfo.ModelId))
if !mdlFullNameInfo.IsMounted {
modelName, modelTag, _ := llm.GetLargeLanguageModelName(mdlFullNameInfo.ModelFullName)
_, err := GetLLMInstantModelManager().updateInstantModel(ctx, llm.Id, mdlFullNameInfo.ModelId, modelName, modelTag, &boolFalse, &boolTrue)
if err != nil {
return errors.Wrap(err, "install model")
}
}
}
volume, _ := llm.GetVolume()
if volume != nil {
err := llm.UpdateVolumeMountedModelFullNames(installModelFullNames)
if err != nil {
return errors.Wrap(err, "UpdateVolumeMountedModelFullNames")
}
}
return nil
}
func (llm *SLLM) GetMountedModelsPostOverlay() ([]*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
boolTrue := true
mdls, err := llm.FetchModels(nil, &boolTrue, nil)
if err != nil {
return nil, errors.Wrap(err, "fetchApps")
}
if len(mdls) == 0 {
return nil, nil
}
drv := llm.GetLLMContainerDriver()
overlays, err := models2overlays(drv, mdls, false)
if err != nil {
return nil, errors.Wrap(err, "models2overlays")
}
return overlays, nil
}
func (llm *SLLM) getMountingModelsPostOverlay(ctx context.Context, input apis.LLMSyncModelTaskInput, existingMdls []SLLMInstantModel) ([]SLLMInstantModel, []*commonapi.ContainerVolumeMountDiskPostOverlay, error) {
var models []SLLMInstantModel
for i := range input.Models {
+8
View File
@@ -24,6 +24,14 @@ func GetLLMPodCreateInput(
return nil, errors.Wrap(err, "GetLLMBasePodCreateInput: ")
}
// generate post overlay info
{
err = llm.UpdateMountedModelFullNames(ctx, nil, true, input.LLMImageId, input.LLMSkuId)
if err != nil {
return nil, errors.Wrap(err, "UpdateMountedModelFullNames")
}
}
lcd := llm.GetLLMContainerDriver()
llmContainer := lcd.GetContainerSpec(ctx, llm, llmImage, sku, nil, nil, "")
+6 -5
View File
@@ -41,11 +41,12 @@ func GetLLMSkuManager() *SLLMSkuManager {
type SLLMSkuManager struct {
SLLMSkuBaseManager
SMountedModelsResourceManager
}
type SLLMSku struct {
SLLMSkuBase
// SMountedAppsResource
SMountedModelsResource
LLMImageId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"`
LLMType string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"required"`
@@ -66,10 +67,10 @@ func (man *SLLMSkuManager) ListItemFilter(
if len(input.LLMType) > 0 {
q = q.Equals("llm_type", input.LLMType)
}
// q, err = man.SMountedAppsResourceManager.ListItemFilter(ctx, q, userCred, input.MountedAppResourceListInput)
// if err != nil {
// return nil, errors.Wrap(err, "SMountedAppsResourceManager")
// }
q, err = man.SMountedModelsResourceManager.ListItemFilter(ctx, q, userCred, input.MountedModelResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SMountedAppsResourceManager")
}
return q, nil
}
+6 -19
View File
@@ -91,6 +91,11 @@ func (task *LLMCreateTask) OnLLMRefreshStatusComplete(ctx context.Context, llm *
return
}
mountedModels, err := llm.FetchMountedModelFullName()
if err != nil {
task.taskFailed(ctx, llm, errors.Wrap(err, "FetchMountedModelFullName"))
}
// 创建磁盘
for _, disk := range server.DisksInfo {
volume := models.SVolume{}
@@ -106,7 +111,7 @@ func (task *LLMCreateTask) OnLLMRefreshStatusComplete(ctx context.Context, llm *
// if len(input.TemplateId) > 0 {
volume.TemplateId = disk.ImageId
// }
// volume.MountedApps = mountedApps
volume.MountedModels = mountedModels
err := models.GetVolumeManager().TableSpec().Insert(ctx, &volume)
if err != nil {
@@ -159,22 +164,4 @@ func (task *LLMCreateTask) OnLLMRefreshStatusComplete(ctx context.Context, llm *
}
task.taskComplete(ctx, llm, server.Status)
// // 调用子任务在容器中拉取模型
// params := jsonutils.NewDict()
// params.Set("status", jsonutils.NewString(server.Status))
// task.SetStage("OnLLMPullModel", params)
// if err := llm.StartPullModelTask(ctx, task.GetUserCred(), nil, task.GetId()); err != nil {
// task.taskFailed(ctx, llm, errors.Wrap(err, "StartPullModelTask"))
// return
// }
}
// func (task *LLMCreateTask) OnLLMPullModelFailed(ctx context.Context, llm *models.SLLM, err jsonutils.JSONObject) {
// task.taskFailed(ctx, llm, errors.Error(err.String()))
// }
// func (task *LLMCreateTask) OnLLMPullModel(ctx context.Context, llm *models.SLLM, body jsonutils.JSONObject) {
// status, _ := task.GetParams().GetString("status")
// task.taskComplete(ctx, llm, status)
// }
@@ -2,6 +2,7 @@ package llm
import (
"context"
"strings"
"time"
"yunion.io/x/jsonutils"
@@ -118,7 +119,7 @@ func (task *LLMInstantModelsSyncTask) OnModelsUnmountComplete(ctx context.Contex
return llm.TryContainerMountPaths(ctx, task.UserCred, s, overlays, 7200)
})
if err != nil {
task.OnModelsUnmountCompleteFailed(ctx, llm, jsonutils.NewString(errors.Wrap(err, "TryContainerMountPaths").Error()))
task.OnModelsMountCompleteFailed(ctx, llm, jsonutils.NewString(errors.Wrap(err, "TryContainerMountPaths").Error()))
}
} else {
task.OnModelsMountComplete(ctx, llm, nil)
@@ -184,8 +185,17 @@ func (task *LLMInstantModelsSyncTask) OnModelsMountComplete(ctx context.Context,
func (task *LLMInstantModelsSyncTask) OnModelsMountCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) {
llm := obj.(*models.SLLM)
task.taskFailed(ctx, llm, err.String())
// get duplicated error
errStr := err.String()
dupMarker := "duplicated container target dirs map"
dupIndex := strings.Index(errStr, dupMarker)
if dupIndex != -1 {
drv := llm.GetLLMContainerDriver()
errStr = drv.CheckDuplicateMounts(errStr, dupIndex)
}
task.taskFailed(ctx, llm, errStr)
// sync status to clear failed status of container
llm.StartSyncStatusTask(ctx, task.UserCred, "")
}
-16
View File
@@ -52,22 +52,6 @@ func (t *LLMStartTask) requestStart(ctx context.Context, llm *models.SLLM) {
t.taskFailed(ctx, llm, err.Error())
return
}
// worker.StartTaskRun(t, func() (jsonutils.JSONObject, error) {
// _, err := llm.WaitServerStatus(ctx, t.GetUserCred(), []string{computeapi.VM_RUNNING}, 900)
// if err != nil {
// return nil, errors.Wrap(err, "WaitServerStatus")
// }
// time.Sleep(time.Second)
// _, err = d.WaitServerStatus(ctx, task.UserCred, []string{computeapi.VM_RUNNING}, 900)
// if err != nil {
// return nil, errors.Wrap(err, "WaitServerStatus")
// }
// return nil, nil
// })
// if err := llm.RunModel(ctx, t.GetUserCred()); nil != err {
// t.OnStartedFailed(ctx, llm, jsonutils.NewString(err.Error()))
// return
// }
}
func (t *LLMStartTask) OnStartedFailed(ctx context.Context, llm *models.SLLM, err jsonutils.JSONObject) {
+3
View File
@@ -56,6 +56,8 @@ func (o *LLMSkuDeleteOptions) Params() (jsonutils.JSONObject, error) {
type LLMSkuUpdateOptions struct {
LLMSkuBaseUpdateOptions
MountedModels []string `help:"mounted models, <model_id>@<model_name>:<model_tag> e.g. 6f48b936a09f@qwen2:0.5b" json:"mounted_models"`
LlmImageId string
LlmModelName string
}
@@ -70,5 +72,6 @@ func (o *LLMSkuUpdateOptions) Params() (jsonutils.JSONObject, error) {
obj.Unmarshal(dict)
o.LLMSkuBaseUpdateOptions.Params(dict)
fetchMountedModels(o.MountedModels, dict)
return dict, nil
}
+5 -5
View File
@@ -217,8 +217,8 @@ func fetchProperties(propStrs []string, dict *jsonutils.JSONDict) {
}
}
// func fetchMountedApps(apps []string, dict *jsonutils.JSONDict) {
// if len(apps) > 0 {
// dict.Set("mounted_apps", jsonutils.Marshal(apps))
// }
// }
func fetchMountedModels(mdls []string, dict *jsonutils.JSONDict) {
if len(mdls) > 0 {
dict.Set("mounted_models", jsonutils.Marshal(mdls))
}
}