吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2012|回复: 23
收起左侧

[原创工具] ollama 单个模型备份迁移工具(go语言)

  [复制链接]
mr88fang 发表于 2025-2-14 16:42
本帖最后由 mr88fang 于 2025-2-18 09:40 编辑

背景

ollama 模型和相关配置文件默认都放在 models 文件夹下,想要把指定模型迁移到其他电脑比较麻烦,所以就有了该工具。还有就是模型下载本身就慢,一次下载多台使用减少下载次数。最重要的是公司电脑下载快,家里下载慢,在公司下载拷贝回家用 😄

如何使用

下载地址

命令

ollamab 模型名称+型号(和 ollama 名称列表一致) 输出路径 (可选)

ollamab.exe qwen:0.5b D:/

执行结果

D:\code\private\model-backup>ollamab.exe qwen:0.5b
模型路径: D:\ai\.ollama\models
开始打包,耐心等待…………
添加到 ZIP 中: blobs\sha256-fad2a06e4cc705c2fa8bec5477ddb00dc0c859ac184c34dcc5586663774161ca
添加到 ZIP 中: blobs\sha256-41c2cf8c272f6fb0080a97cd9d9bd7d4604072b80a0b10e7d65ca26ef5000c0c
添加到 ZIP 中: blobs\sha256-1da0581fd4ce92dcf5a66b1da737cf215d8dcf25aa1b98b44443aaf7173155f5
添加到 ZIP 中: blobs\sha256-f02dd72bb2423204352eabc5637b44d79d17f109fdb510a7c51455892aa2d216
添加到 ZIP 中: manifests\registry.ollama.ai\library\qwen\0.5b
zip文件创建成功: qwen-0.5b.zip

目标电脑

将打包的 zip 拷贝到目标电脑 models 下直接解压到当前目录即可

ollama

ollama list 输出所有模型命令

NAME                ID              SIZE      MODIFIED
gemma2:9b           ff02c3702f32    5.4 GB    5 hours ago
phi:2.7b            e2fd6321a5fe    1.6 GB    5 hours ago
qwen:7b             2091ee8c8d8f    4.5 GB    4 days ago
qwen:0.5b           b5dc5e784f2a    394 MB    4 days ago
llama3.1:8b         46e0c10c039e    4.9 GB    5 days ago
deepseek-r1:1.5b    a42b25d8c10a    1.1 GB    6 days ago
deepseek-r1:7b      0a8c26691023    4.7 GB    8 days ago

Ollama 在 Windows 上存储文件在几个不同的位置。您可以通过以下步骤查看它们:

  • <cmd>+R   键并输入:

    • explorer %LOCALAPPDATA%\Ollama  :包含日志和下载的更新

    • app.log:最近的 GUI 应用程序日志

    • server.log:最近的服务器日志

    • upgrade.log:升级日志输出

    • explorer %LOCALAPPDATA%\Programs\Ollama:包含二进制文件(安装器将其添加到用户 PATH 中)

    • explorer %HOMEPATH%\.ollama:包含模型和配置

    • explorer %TEMP%:包含临时执行文件,在一个或多个 ollama* 目录中

源码

/**
 * @Time : 2025/2/14 
 * @File : main.go
 * @Software: ollamab
 * @Author : Mr.Fang
 * @Description: 备份 ollama 模型
 */

package main

import (
        "archive/zip"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "os"
        "path/filepath"
        "runtime"
        "strings"
)

var modelPath = ""

// 初始化获取模型文件路径,优先从系统环境变量获取,其次获取默认路径
func init() {
        models := os.Getenv("OLLAMA_MODELS")
        if len(models) > 0 {
                modelPath = models
        } else {
                // Window
                if runtime.GOOS == "windows" {
                        home, err := os.UserHomeDir()
                        if err != nil {
                                log.Panicln("获取用户主目录失败:", err)
                        }
                        modelPath = filepath.Join(home, ".ollama", "models")
                } else { // Linux
                        modelPath = filepath.Join("/usr/share/ollama/", ".ollama", "models")
                }
        }
        fmt.Println("模型路径:", modelPath)
}

// 将文件或文件夹添加到 zip 中
func addToZip(zipWriter *zip.Writer, filePath, baseFolder string) error {
        // 计算文件的相对路径
        relativePath, err := filepath.Rel(baseFolder, filePath)
        if err != nil {
                return err
        }

        fmt.Println("添加到 ZIP 中:", relativePath)

        // 如果是目录,则创建空目录
        info, err := os.Stat(filePath)
        if err != nil {
                return err
        }

        // 如果是目录,创建空目录
        if info.IsDir() {
                _, err := zipWriter.Create(relativePath + "/")
                return err
        }

        // 如果是文件,创建文件条目
        fileInZip, err := zipWriter.Create(relativePath)
        if err != nil {
                return err
        }

        // 打开文件并复制内容到zip中
        file, err := os.Open(filePath)
        if err != nil {
                return err
        }
        defer file.Close()

        // 复制文件内容到zip
        _, err = io.Copy(fileInZip, file)
        return err
}

// 模型相关所有文件路径
func blobsPath(modelDataPath, basePath string) []string {
        file, err := os.ReadFile(modelDataPath)
        if err != nil {
                fmt.Println("model data 文件读取错误!", err)
        }
        // 转 map
        var modelData map[string]interface{}
        var blobsPath []string
        err = json.Unmarshal(file, &modelData)
        if err != nil {
                fmt.Println("model data 转换错误!", err)
        }
        // 层数据
        layers := modelData["layers"].([]interface{})
        // 模型详情信息
        layers = append(layers, modelData["config"].(interface{}))

        for _, layer := range layers {
                item := layer.(map[string]interface{})
                digest := item["digest"].(string) // sha256
                digest = strings.ReplaceAll(digest, ":", "-")
                join := filepath.Join(basePath, "blobs", digest)
                // 使用 os.Stat 检查文件是否存在
                fileInfo, _ := os.Stat(join)
                if fileInfo != nil {
                        blobsPath = append(blobsPath, join)
                }
        }
        return blobsPath
}

// build 打包 zip
func build(name string, output string, folderPaths []string) {
        fmt.Println("开始打包,耐心等待…………")
        // 创建目标zip文件
        zipFilePath := filepath.Join(output, name)
        zipFile, err := os.Create(zipFilePath)
        if err != nil {
                fmt.Println("创建zip文件失败:", err)
                return
        }
        defer zipFile.Close()

        // 创建zip写入器
        zipWriter := zip.NewWriter(zipFile)
        defer zipWriter.Close()

        // 逐个添加文件或目录到zip文件中
        for _, filePath := range folderPaths {
                // 注意:baseFolder 是我们希望在zip文件中根目录
                err := addToZip(zipWriter, filePath, modelPath)
                if err != nil {
                        fmt.Println("添加文件到zip失败:", err)
                        return
                }
        }
        fmt.Println("zip文件创建成功:", zipFilePath)
}

func main() {
        args := os.Args[1:]

        if len(args) == 0 {
                fmt.Println("参数: ollamab 名称:型号(必填) 指定输出路径,默认输出当前路径(可选)")
                fmt.Println("示例: ollamab deepseek-r1:1.5b ")
                fmt.Println("示例: ollamab deepseek-r1:1.5b D:/models")
                fmt.Println("示例: ollamab lrs33/bce-embedding-base_v1:latest")
                return
        }
        arg := strings.Split(args[0], ":")
        name := arg[0]
        version := arg[1]
        output := "./"
        if len(args) == 2 {
                output = args[1]
        }
        // 配置文件路径
        library := filepath.Join(modelPath, "manifests", "registry.ollama.ai", "library", name, version)
        // 特殊情况,用户自己分享的模型
        contains := strings.Contains(name, "/")
        if contains {
                libs := strings.Split(name, "/")
                library = filepath.Join(modelPath, "manifests", "registry.ollama.ai", libs[0], libs[1], version)
                // 替换 "/" 否则无法创建 zip
                name = strings.ReplaceAll(name, "/", "-")
        }
        folderPaths := blobsPath(library, modelPath)
        // 模型路径
        folderPaths = append(folderPaths, library)
        // 打包
        build(fmt.Sprintf("%s-%s.zip", name, version), output, folderPaths)

}

更新说明

详情见 #16

  • 解决用户自己分享的模型备份路径问题
  • 模型 config 配置文件未拷贝导致 ollama list 未显示迁移模型,需要执行 ollama run 模型
  • Linux 下默认模型路径未处理
ollamab.png

免费评分

参与人数 7吾爱币 +12 热心值 +5 收起 理由
woi1111 + 1 用心讨论,共获提升!
boycc1974 + 1 我很赞同!
jdhnp + 1 + 1 谢谢@Thanks!
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
bg_yx + 1 我很赞同!
ydy2654 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pojieit + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

dedaowangji 发表于 2025-2-14 19:27
环境变量 新增加一个OLLAMA_MODELS    重启。
tooooony 发表于 2025-2-17 22:36
这个脚本存在3个bug
1、manifest文件,未必在library目录下,比如lrs33/bce-embedding-base_v1:latest这个模型,其相对路径为manifests/registry.ollama.ai/lrs33/bce-embedding-base_v1/latest
2、对manifest文件,只处理了layers列表里的digest,没处理config下的digest,所以导出的zip文件,解压后ollama list不会显示模型;
3、对于linux系统,ollama模型默认目录是在/usr/share/ollama/.ollama/models

MRBANK 发表于 2025-2-14 16:47
学到了,最近在玩儿这个AI工具...部署还挺方便的
pojieit 发表于 2025-2-14 17:08
感谢分享!

看起来很有用,是不是解压后ollama就能识别到该模型?
 楼主| mr88fang 发表于 2025-2-14 17:11
pojieit 发表于 2025-2-14 17:08
感谢分享!

看起来很有用,是不是解压后ollama就能识别到该模型?

解压就能识别到了
qinluezhew3 发表于 2025-2-14 17:19
ollama安装后占用空间很大,LM Studio感觉方便很多
106link 发表于 2025-2-14 17:21
大佬及时雨啊~有这需求的就方便了
 楼主| mr88fang 发表于 2025-2-14 18:09
qinluezhew3 发表于 2025-2-14 17:19
ollama安装后占用空间很大,LM Studio感觉方便很多

ollama 自身应该就 4G 多,可以迁到其他盘符,默认c盘,模型文件应该都是一样大
qinluezhew3 发表于 2025-2-14 18:34
mr88fang 发表于 2025-2-14 18:09
ollama 自身应该就 4G 多,可以迁到其他盘符,默认c盘,模型文件应该都是一样大

是的,就是ollama自身太大,
 楼主| mr88fang 发表于 2025-2-14 20:19
补充一下,解压后默认 ollama list 可能不显示出来,需要执行一下 ollama run 模型+型号 就可以了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-3-14 10:53

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表