ollama/server/images.go

1305 lines
30 KiB
Go
Raw Normal View History

package server
import (
"bytes"
"context"
"crypto/sha256"
2023-08-29 03:50:24 +00:00
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
2023-08-22 01:38:31 +00:00
"net/url"
"os"
"path/filepath"
2023-08-22 01:24:42 +00:00
"runtime"
"strconv"
"strings"
"text/template"
2023-12-22 22:07:05 +00:00
"text/template/parse"
2023-11-29 19:11:42 +00:00
"golang.org/x/exp/slices"
"github.com/jmorganca/ollama/api"
2023-07-21 20:33:56 +00:00
"github.com/jmorganca/ollama/llm"
"github.com/jmorganca/ollama/parser"
2023-08-22 01:24:42 +00:00
"github.com/jmorganca/ollama/version"
)
type RegistryOptions struct {
Insecure bool
Username string
Password string
2023-08-10 18:34:25 +00:00
Token string
}
type Model struct {
2023-11-30 18:30:23 +00:00
Name string `json:"name"`
2023-12-01 19:37:17 +00:00
Config ConfigV2
2023-11-30 18:30:23 +00:00
ShortName string
ModelPath string
OriginalModel string
AdapterPaths []string
ProjectorPaths []string
Template string
System string
License []string
Digest string
Size int64
2023-11-30 18:30:23 +00:00
Options map[string]interface{}
}
2023-12-05 19:57:33 +00:00
type PromptVars struct {
System string
Prompt string
Response string
First bool
}
2023-12-22 22:07:05 +00:00
// extractParts extracts the parts of the template before and after the {{.Response}} node.
func extractParts(tmplStr string) (pre string, post string, err error) {
tmpl, err := template.New("").Parse(tmplStr)
if err != nil {
return "", "", err
}
var foundResponse bool
for _, node := range tmpl.Tree.Root.Nodes {
if node.Type() == parse.NodeAction && node.String() == "{{.Response}}" {
foundResponse = true
}
if !foundResponse {
pre += node.String()
} else {
post += node.String()
}
}
return pre, post, nil
}
func Prompt(promptTemplate string, p PromptVars) (string, error) {
2023-12-05 19:57:33 +00:00
var prompt strings.Builder
// Use the "missingkey=zero" option to handle missing variables without panicking
2023-12-22 22:07:05 +00:00
tmpl, err := template.New("").Option("missingkey=zero").Parse(promptTemplate)
if err != nil {
return "", err
}
vars := map[string]any{
"System": p.System,
"Prompt": p.Prompt,
"Response": p.Response,
"First": p.First,
}
2023-12-05 19:57:33 +00:00
var sb strings.Builder
if err := tmpl.Execute(&sb, vars); err != nil {
2023-12-05 19:57:33 +00:00
return "", err
}
prompt.WriteString(sb.String())
2023-12-22 22:07:05 +00:00
if !strings.Contains(prompt.String(), p.Response) {
// if the response is not in the prompt template, append it to the end
prompt.WriteString(p.Response)
}
2023-12-05 19:57:33 +00:00
return prompt.String(), nil
}
2023-12-22 22:07:05 +00:00
// PreResponsePrompt returns the prompt before the response tag
func (m *Model) PreResponsePrompt(p PromptVars) (string, error) {
if p.System == "" {
// use the default system prompt for this model if one is not specified
p.System = m.System
}
pre, _, err := extractParts(m.Template)
if err != nil {
return "", err
}
return Prompt(pre, p)
}
// PostResponseTemplate returns the template after the response tag
func (m *Model) PostResponseTemplate(p PromptVars) (string, error) {
if p.System == "" {
// use the default system prompt for this model if one is not specified
p.System = m.System
}
_, post, err := extractParts(m.Template)
if err != nil {
return "", err
}
if post == "" {
// if there is no post-response template, return the provided response
return p.Response, nil
}
return Prompt(post, p)
}
func (m *Model) ChatPrompt(msgs []api.Message) (string, []api.ImageData, error) {
2023-12-05 19:57:33 +00:00
// build the prompt from the list of messages
var prompt strings.Builder
var currentImages []api.ImageData
2023-12-05 19:57:33 +00:00
currentVars := PromptVars{
2023-12-22 22:07:05 +00:00
First: true,
System: m.System,
}
2023-12-05 19:57:33 +00:00
writePrompt := func() error {
2023-12-22 22:07:05 +00:00
p, err := Prompt(m.Template, currentVars)
2023-12-05 19:57:33 +00:00
if err != nil {
return err
}
prompt.WriteString(p)
currentVars = PromptVars{}
return nil
}
for _, msg := range msgs {
switch strings.ToLower(msg.Role) {
2023-12-05 19:57:33 +00:00
case "system":
if currentVars.System != "" {
2023-12-05 19:57:33 +00:00
if err := writePrompt(); err != nil {
return "", nil, err
2023-12-05 19:57:33 +00:00
}
}
currentVars.System = msg.Content
case "user":
if currentVars.Prompt != "" {
2023-12-05 19:57:33 +00:00
if err := writePrompt(); err != nil {
return "", nil, err
2023-12-05 19:57:33 +00:00
}
}
currentVars.Prompt = msg.Content
currentImages = msg.Images
2023-12-05 19:57:33 +00:00
case "assistant":
currentVars.Response = msg.Content
if err := writePrompt(); err != nil {
return "", nil, err
2023-12-05 19:57:33 +00:00
}
default:
return "", nil, fmt.Errorf("invalid role: %s, role must be one of [system, user, assistant]", msg.Role)
2023-12-05 19:57:33 +00:00
}
}
2023-12-05 19:57:33 +00:00
// Append the last set of vars if they are non-empty
if currentVars.Prompt != "" || currentVars.System != "" {
2023-12-22 22:07:05 +00:00
p, err := m.PreResponsePrompt(currentVars)
if err != nil {
return "", nil, fmt.Errorf("pre-response template: %w", err)
2023-12-05 19:57:33 +00:00
}
2023-12-22 22:07:05 +00:00
prompt.WriteString(p)
2023-12-05 19:57:33 +00:00
}
return prompt.String(), currentImages, nil
}
type ManifestV2 struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config *Layer `json:"config"`
Layers []*Layer `json:"layers"`
}
type ConfigV2 struct {
ModelFormat string `json:"model_format"`
ModelFamily string `json:"model_family"`
ModelFamilies []string `json:"model_families"`
ModelType string `json:"model_type"`
FileType string `json:"file_type"`
2023-07-21 20:33:56 +00:00
// required by spec
Architecture string `json:"architecture"`
OS string `json:"os"`
2023-12-01 19:37:17 +00:00
RootFS RootFS `json:"rootfs"`
}
2023-11-29 19:11:42 +00:00
func (c *ConfigV2) SetModelFormat(format string) {
if c.ModelFormat == "" {
c.ModelFormat = format
}
}
func (c *ConfigV2) SetModelFamily(families ...string) {
for _, family := range families {
if c.ModelFamily == "" {
c.ModelFamily = family
}
if !slices.Contains(c.ModelFamilies, family) {
c.ModelFamilies = append(c.ModelFamilies, family)
}
}
}
func (c *ConfigV2) SetModelType(modelType string) {
if c.ModelType == "" {
c.ModelType = modelType
}
}
func (c *ConfigV2) SetFileType(fileType string) {
if c.FileType == "" {
c.FileType = fileType
}
}
type RootFS struct {
Type string `json:"type"`
DiffIDs []string `json:"diff_ids"`
}
2023-09-28 17:00:34 +00:00
func (m *ManifestV2) GetTotalSize() (total int64) {
2023-07-18 16:09:45 +00:00
for _, layer := range m.Layers {
total += layer.Size
}
2023-09-28 17:00:34 +00:00
2023-07-18 16:09:45 +00:00
total += m.Config.Size
return total
}
2023-08-29 03:50:24 +00:00
func GetManifest(mp ModelPath) (*ManifestV2, string, error) {
fp, err := mp.GetManifestPath()
if err != nil {
2023-08-29 03:50:24 +00:00
return nil, "", err
}
if _, err = os.Stat(fp); err != nil {
2023-08-29 03:50:24 +00:00
return nil, "", err
}
var manifest *ManifestV2
bts, err := os.ReadFile(fp)
if err != nil {
2023-08-29 03:50:24 +00:00
return nil, "", fmt.Errorf("couldn't open file '%s'", fp)
}
2023-08-29 03:50:24 +00:00
shaSum := sha256.Sum256(bts)
shaStr := hex.EncodeToString(shaSum[:])
if err := json.Unmarshal(bts, &manifest); err != nil {
2023-08-29 03:50:24 +00:00
return nil, "", err
}
2023-08-29 03:50:24 +00:00
return manifest, shaStr, nil
}
func GetModel(name string) (*Model, error) {
mp := ParseModelPath(name)
2023-08-29 03:50:24 +00:00
manifest, digest, err := GetManifest(mp)
if err != nil {
return nil, err
}
model := &Model{
Name: mp.GetFullTagname(),
ShortName: mp.GetShortTagname(),
Digest: digest,
Template: "{{ .Prompt }}",
License: []string{},
Size: manifest.GetTotalSize(),
}
2023-12-01 19:37:17 +00:00
filename, err := GetBlobsPath(manifest.Config.Digest)
if err != nil {
return nil, err
}
configFile, err := os.Open(filename)
if err != nil {
return nil, err
}
defer configFile.Close()
if err := json.NewDecoder(configFile).Decode(&model.Config); err != nil {
return nil, err
}
for _, layer := range manifest.Layers {
2023-07-18 05:44:21 +00:00
filename, err := GetBlobsPath(layer.Digest)
if err != nil {
return nil, err
}
switch layer.MediaType {
case "application/vnd.ollama.image.model":
model.ModelPath = filename
2023-09-06 18:04:17 +00:00
model.OriginalModel = layer.From
2023-08-04 22:56:40 +00:00
case "application/vnd.ollama.image.embed":
// Deprecated in versions > 0.1.2
// TODO: remove this warning in a future version
log.Print("WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored.")
case "application/vnd.ollama.image.adapter":
model.AdapterPaths = append(model.AdapterPaths, filename)
2023-11-30 18:30:23 +00:00
case "application/vnd.ollama.image.projector":
model.ProjectorPaths = append(model.ProjectorPaths, filename)
case "application/vnd.ollama.image.template":
bts, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
model.Template = string(bts)
case "application/vnd.ollama.image.system":
bts, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
model.System = string(bts)
case "application/vnd.ollama.image.prompt":
bts, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
model.Template = string(bts)
case "application/vnd.ollama.image.params":
2023-07-17 19:08:10 +00:00
params, err := os.Open(filename)
if err != nil {
return nil, err
}
defer params.Close()
// parse model options parameters into a map so that we can see which fields have been specified explicitly
if err = json.NewDecoder(params).Decode(&model.Options); err != nil {
return nil, err
}
2023-09-06 18:04:17 +00:00
case "application/vnd.ollama.image.license":
bts, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
model.License = append(model.License, string(bts))
}
}
return model, nil
}
2023-11-21 20:43:17 +00:00
func realpath(mfDir, from string) string {
abspath, err := filepath.Abs(from)
2023-11-14 20:30:34 +00:00
if err != nil {
2023-11-21 20:43:17 +00:00
return from
}
2023-11-14 20:30:34 +00:00
home, err := os.UserHomeDir()
if err != nil {
2023-11-14 20:30:34 +00:00
return abspath
}
2023-11-21 20:43:17 +00:00
if from == "~" {
2023-11-14 20:30:34 +00:00
return home
2023-11-21 20:43:17 +00:00
} else if strings.HasPrefix(from, "~/") {
return filepath.Join(home, from[2:])
}
if _, err := os.Stat(filepath.Join(mfDir, from)); err == nil {
// this is a file relative to the Modelfile
return filepath.Join(mfDir, from)
}
2023-11-14 20:30:34 +00:00
return abspath
}
2023-11-21 20:43:17 +00:00
func CreateModel(ctx context.Context, name, modelFileDir string, commands []parser.Command, fn func(resp api.ProgressResponse)) error {
2023-07-21 20:33:56 +00:00
config := ConfigV2{
OS: "linux",
2023-11-14 20:30:34 +00:00
Architecture: "amd64",
RootFS: RootFS{
Type: "layers",
},
2023-07-21 20:33:56 +00:00
}
2023-11-14 20:30:34 +00:00
deleteMap := make(map[string]struct{})
var layers Layers
2023-11-14 20:30:34 +00:00
2023-07-28 15:29:00 +00:00
params := make(map[string][]string)
2023-11-14 20:30:34 +00:00
fromParams := make(map[string]any)
for _, c := range commands {
2023-11-14 20:30:34 +00:00
log.Printf("[%s] - %s", c.Name, c.Args)
mediatype := fmt.Sprintf("application/vnd.ollama.image.%s", c.Name)
switch c.Name {
case "model":
2023-11-15 18:59:38 +00:00
if strings.HasPrefix(c.Args, "@") {
blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
if err != nil {
return err
}
c.Args = blobPath
}
2023-11-21 20:43:17 +00:00
bin, err := os.Open(realpath(modelFileDir, c.Args))
if err != nil {
2023-11-14 20:30:34 +00:00
// not a file on disk so must be a model reference
modelpath := ParseModelPath(c.Args)
manifest, _, err := GetManifest(modelpath)
switch {
case errors.Is(err, os.ErrNotExist):
fn(api.ProgressResponse{Status: "pulling model"})
if err := PullModel(ctx, c.Args, &RegistryOptions{}, fn); err != nil {
return err
}
2023-11-14 20:30:34 +00:00
manifest, _, err = GetManifest(modelpath)
2023-07-21 20:33:56 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
case err != nil:
return err
}
2023-07-21 20:33:56 +00:00
fn(api.ProgressResponse{Status: "reading model metadata"})
2023-11-14 20:30:34 +00:00
fromConfigPath, err := GetBlobsPath(manifest.Config.Digest)
2023-08-18 04:52:11 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
fromConfigFile, err := os.Open(fromConfigPath)
2023-08-18 04:52:11 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
defer fromConfigFile.Close()
2023-08-18 04:52:11 +00:00
2023-11-14 20:30:34 +00:00
var fromConfig ConfigV2
if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
2023-08-18 04:52:11 +00:00
return err
}
// if the model is not in gguf format, pull the base model to try and get it in gguf format
if fromConfig.ModelFormat != "gguf" {
fn(api.ProgressResponse{Status: "updating base model"})
2023-12-11 20:35:31 +00:00
parent, err := GetModel(c.Args)
if err != nil {
return err
}
if err := PullModel(ctx, parent.OriginalModel, &RegistryOptions{}, fn); err != nil {
log.Printf("error pulling model: %v", err)
}
// Reset the file pointer to the beginning of the file
_, err = fromConfigFile.Seek(0, 0)
if err != nil {
return fmt.Errorf("update from config after pull: %w", err)
}
if err := json.NewDecoder(fromConfigFile).Decode(&fromConfig); err != nil {
return err
}
}
// if the model is still not in gguf format, error out
if fromConfig.ModelFormat != "gguf" {
return fmt.Errorf("%s is not in gguf format, this base model is not compatible with this version of ollama", c.Args)
}
2023-11-29 19:11:42 +00:00
config.SetModelFormat(fromConfig.ModelFormat)
config.SetModelFamily(append(fromConfig.ModelFamilies, fromConfig.ModelFamily)...)
config.SetModelType(fromConfig.ModelType)
config.SetFileType(fromConfig.FileType)
2023-08-18 04:52:11 +00:00
2023-11-14 20:30:34 +00:00
for _, layer := range manifest.Layers {
deleteMap[layer.Digest] = struct{}{}
if layer.MediaType == "application/vnd.ollama.image.params" {
fromParamsPath, err := GetBlobsPath(layer.Digest)
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
fromParamsFile, err := os.Open(fromParamsPath)
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
defer fromParamsFile.Close()
2023-11-14 20:30:34 +00:00
if err := json.NewDecoder(fromParamsFile).Decode(&fromParams); err != nil {
return err
}
}
layer, err := NewLayerFromLayer(layer.Digest, layer.MediaType, modelpath.GetShortTagname())
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
layers.Add(layer)
}
2023-11-14 20:30:34 +00:00
deleteMap[manifest.Config.Digest] = struct{}{}
continue
}
2023-11-14 20:30:34 +00:00
defer bin.Close()
2023-11-24 19:57:20 +00:00
var offset int64
CREATE:
2023-11-24 19:57:20 +00:00
for {
fn(api.ProgressResponse{Status: "creating model layer"})
2023-11-24 19:57:20 +00:00
bin.Seek(offset, io.SeekStart)
ggml, err := llm.DecodeGGML(bin)
if err != nil {
switch {
case errors.Is(err, io.EOF):
break CREATE
case errors.Is(err, llm.ErrUnsupportedFormat):
return fmt.Errorf("model binary specified in FROM field is not a valid gguf format model, %w", err)
default:
return err
}
2023-11-24 19:57:20 +00:00
}
2023-11-14 20:30:34 +00:00
2023-11-29 19:11:42 +00:00
config.SetModelFormat(ggml.Name())
config.SetModelFamily(ggml.ModelFamily())
config.SetModelType(ggml.ModelType())
config.SetFileType(ggml.FileType())
2023-11-24 19:57:20 +00:00
mediatype := mediatype
if ggml.ModelFamily() == "clip" {
mediatype = "application/vnd.ollama.image.projector"
}
2023-11-24 19:57:20 +00:00
sr := io.NewSectionReader(bin, offset, ggml.Size)
layer, err := NewLayer(sr, mediatype)
if err != nil {
return err
}
layers.Add(layer)
offset += ggml.Size
}
2023-11-14 20:30:34 +00:00
case "adapter":
2023-12-01 18:50:55 +00:00
if strings.HasPrefix(c.Args, "@") {
blobPath, err := GetBlobsPath(strings.TrimPrefix(c.Args, "@"))
if err != nil {
return err
}
c.Args = blobPath
}
2023-12-05 19:57:33 +00:00
2023-11-14 20:30:34 +00:00
fn(api.ProgressResponse{Status: "creating adapter layer"})
2023-11-21 20:43:17 +00:00
bin, err := os.Open(realpath(modelFileDir, c.Args))
if err != nil {
2023-11-14 20:30:34 +00:00
return err
}
2023-11-14 20:30:34 +00:00
defer bin.Close()
layer, err := NewLayer(bin, mediatype)
if err != nil {
2023-11-14 20:30:34 +00:00
return err
}
layers.Add(layer)
2023-11-14 20:30:34 +00:00
case "license":
fn(api.ProgressResponse{Status: "creating license layer"})
bin := strings.NewReader(c.Args)
layer, err := NewLayer(bin, mediatype)
if err != nil {
return err
}
layers.Add(layer)
2023-11-14 20:30:34 +00:00
case "template", "system":
fn(api.ProgressResponse{Status: fmt.Sprintf("creating %s layer", c.Name)})
bin := strings.NewReader(c.Args)
layer, err := NewLayer(bin, mediatype)
if err != nil {
return err
}
layers.Replace(layer)
default:
2023-07-28 15:29:00 +00:00
params[c.Name] = append(params[c.Name], c.Args)
}
}
2023-07-17 19:08:10 +00:00
if len(params) > 0 {
2023-11-14 20:30:34 +00:00
fn(api.ProgressResponse{Status: "creating parameters layer"})
2023-09-02 18:38:51 +00:00
formattedParams, err := api.FormatParams(params)
if err != nil {
2023-11-14 20:30:34 +00:00
return err
}
2023-08-04 22:56:40 +00:00
2023-11-14 20:30:34 +00:00
for k, v := range fromParams {
if _, ok := formattedParams[k]; !ok {
formattedParams[k] = v
}
}
// xxx - can this be removed?
2023-09-12 17:52:57 +00:00
if config.ModelType == "65B" {
2023-11-14 20:30:34 +00:00
if gqa, ok := formattedParams["gqa"].(int); ok && gqa == 8 {
2023-09-12 17:52:57 +00:00
config.ModelType = "70B"
}
}
2023-11-14 20:30:34 +00:00
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(formattedParams); err != nil {
2023-08-04 22:56:40 +00:00
return err
}
2023-11-14 20:30:34 +00:00
fn(api.ProgressResponse{Status: "creating config layer"})
layer, err := NewLayer(&b, "application/vnd.ollama.image.params")
if err != nil {
2023-11-14 20:30:34 +00:00
return err
}
2023-11-14 20:30:34 +00:00
layers.Replace(layer)
2023-08-04 22:56:40 +00:00
}
digests := make([]string, len(layers.items))
for i, layer := range layers.items {
digests[i] = layer.Digest
}
config.RootFS.DiffIDs = digests
2023-11-14 20:30:34 +00:00
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(config); err != nil {
return err
}
configLayer, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json")
if err != nil {
return err
}
delete(deleteMap, configLayer.Digest)
for _, layer := range append(layers.items, configLayer) {
committed, err := layer.Commit()
if err != nil {
return err
}
status := "writing layer"
if !committed {
status = "using already created layer"
}
fn(api.ProgressResponse{Status: fmt.Sprintf("%s %s", status, layer.Digest)})
delete(deleteMap, layer.Digest)
}
fn(api.ProgressResponse{Status: "writing manifest"})
if err := WriteManifest(name, configLayer, layers.items); err != nil {
return err
}
if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
if err := deleteUnusedLayers(nil, deleteMap, false); err != nil {
return err
}
}
fn(api.ProgressResponse{Status: "success"})
return nil
}
2023-07-24 15:27:28 +00:00
func CopyModel(src, dest string) error {
srcModelPath := ParseModelPath(src)
srcPath, err := srcModelPath.GetManifestPath()
2023-08-22 04:56:56 +00:00
if err != nil {
return err
}
destModelPath := ParseModelPath(dest)
destPath, err := destModelPath.GetManifestPath()
2023-07-24 15:27:28 +00:00
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
return err
}
2023-07-24 15:27:28 +00:00
// copy the file
input, err := os.ReadFile(srcPath)
2023-07-24 15:27:28 +00:00
if err != nil {
fmt.Println("Error reading file:", err)
return err
}
err = os.WriteFile(destPath, input, 0o644)
2023-07-24 15:27:28 +00:00
if err != nil {
fmt.Println("Error reading file:", err)
return err
}
return nil
}
2023-11-14 20:30:34 +00:00
func deleteUnusedLayers(skipModelPath *ModelPath, deleteMap map[string]struct{}, dryRun bool) error {
2023-07-20 23:09:23 +00:00
fp, err := GetManifestPath()
if err != nil {
return err
}
2023-08-30 18:31:12 +00:00
walkFunc := func(path string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
2023-07-20 23:09:23 +00:00
}
2023-08-30 18:31:12 +00:00
dir, file := filepath.Split(path)
dir = strings.Trim(strings.TrimPrefix(dir, fp), string(os.PathSeparator))
tag := strings.Join([]string{dir, file}, ":")
fmp := ParseModelPath(tag)
2023-07-20 23:09:23 +00:00
2023-08-30 18:31:12 +00:00
// skip the manifest we're trying to delete
if skipModelPath != nil && skipModelPath.GetFullTagname() == fmp.GetFullTagname() {
2023-08-30 18:31:12 +00:00
return nil
2023-07-20 23:09:23 +00:00
}
2023-08-30 18:31:12 +00:00
// save (i.e. delete from the deleteMap) any files used in other manifests
manifest, _, err := GetManifest(fmp)
if err != nil {
return nil
}
for _, layer := range manifest.Layers {
delete(deleteMap, layer.Digest)
}
delete(deleteMap, manifest.Config.Digest)
2023-07-20 23:09:23 +00:00
return nil
2023-08-30 18:31:12 +00:00
}
if err := filepath.Walk(fp, walkFunc); err != nil {
2023-07-31 22:26:18 +00:00
return err
}
2023-07-20 23:09:23 +00:00
// only delete the files which are still in the deleteMap
2023-11-14 20:30:34 +00:00
for k := range deleteMap {
fp, err := GetBlobsPath(k)
if err != nil {
log.Printf("couldn't get file path for '%s': %v", k, err)
continue
}
if !dryRun {
if err := os.Remove(fp); err != nil {
log.Printf("couldn't remove file '%s': %v", fp, err)
continue
}
2023-11-14 20:30:34 +00:00
} else {
log.Printf("wanted to remove: %s", fp)
2023-07-20 23:09:23 +00:00
}
}
return nil
}
func PruneLayers() error {
2023-11-14 20:30:34 +00:00
deleteMap := make(map[string]struct{})
p, err := GetBlobsPath("")
if err != nil {
return err
}
blobs, err := os.ReadDir(p)
if err != nil {
log.Printf("couldn't read dir '%s': %v", p, err)
return err
}
for _, blob := range blobs {
name := blob.Name()
if runtime.GOOS == "windows" {
name = strings.ReplaceAll(name, "-", ":")
}
2023-11-14 22:27:51 +00:00
if strings.HasPrefix(name, "sha256:") {
deleteMap[name] = struct{}{}
}
}
log.Printf("total blobs: %d", len(deleteMap))
err = deleteUnusedLayers(nil, deleteMap, false)
if err != nil {
return err
}
log.Printf("total unused blobs removed: %d", len(deleteMap))
return nil
}
2023-09-27 00:28:14 +00:00
func PruneDirectory(path string) error {
info, err := os.Lstat(path)
if err != nil {
return err
}
if info.IsDir() && info.Mode()&os.ModeSymlink == 0 {
entries, err := os.ReadDir(path)
if err != nil {
return err
}
for _, entry := range entries {
if err := PruneDirectory(filepath.Join(path, entry.Name())); err != nil {
return err
}
}
entries, err = os.ReadDir(path)
if err != nil {
return err
}
if len(entries) > 0 {
return nil
}
return os.Remove(path)
}
return nil
}
func DeleteModel(name string) error {
mp := ParseModelPath(name)
manifest, _, err := GetManifest(mp)
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
deleteMap := make(map[string]struct{})
for _, layer := range manifest.Layers {
2023-11-14 20:30:34 +00:00
deleteMap[layer.Digest] = struct{}{}
}
2023-11-14 20:30:34 +00:00
deleteMap[manifest.Config.Digest] = struct{}{}
err = deleteUnusedLayers(&mp, deleteMap, false)
if err != nil {
return err
}
fp, err := mp.GetManifestPath()
2023-07-20 23:09:23 +00:00
if err != nil {
return err
}
err = os.Remove(fp)
if err != nil {
log.Printf("couldn't remove manifest file '%s': %v", fp, err)
return err
}
return nil
}
2023-09-06 18:04:17 +00:00
func ShowModelfile(model *Model) (string, error) {
2023-10-17 22:40:06 +00:00
var mt struct {
2023-09-06 18:04:17 +00:00
*Model
2023-10-17 22:40:06 +00:00
From string
2023-10-17 22:53:46 +00:00
Parameters map[string][]any
2023-09-06 18:04:17 +00:00
}
2023-10-17 22:53:46 +00:00
mt.Parameters = make(map[string][]any)
2023-09-06 18:04:17 +00:00
for k, v := range model.Options {
2023-10-17 22:53:46 +00:00
if s, ok := v.([]any); ok {
mt.Parameters[k] = s
continue
2023-09-06 18:04:17 +00:00
}
2023-10-17 22:53:46 +00:00
mt.Parameters[k] = []any{v}
2023-09-06 18:04:17 +00:00
}
2023-10-17 22:40:06 +00:00
mt.Model = model
mt.From = model.ModelPath
2023-09-06 18:04:17 +00:00
2023-10-17 22:40:06 +00:00
if model.OriginalModel != "" {
2023-11-10 20:21:35 +00:00
mt.From = model.OriginalModel
2023-09-06 18:04:17 +00:00
}
modelFile := `# Modelfile generated by "ollama show"
# To build a new Modelfile based on this one, replace the FROM line with:
# FROM {{ .ShortName }}
FROM {{ .From }}
TEMPLATE """{{ .Template }}"""
2023-10-17 22:25:43 +00:00
{{- if .System }}
2023-09-06 18:04:17 +00:00
SYSTEM """{{ .System }}"""
2023-10-17 22:25:43 +00:00
{{- end }}
{{- range $adapter := .AdapterPaths }}
ADAPTER {{ $adapter }}
{{- end }}
2023-10-17 22:40:06 +00:00
2023-10-17 22:53:46 +00:00
{{- range $k, $v := .Parameters }}
{{- range $parameter := $v }}
PARAMETER {{ $k }} {{ printf "%#v" $parameter }}
{{- end }}
2023-10-17 22:40:06 +00:00
{{- end }}`
2023-09-06 18:04:17 +00:00
tmpl, err := template.New("").Parse(modelFile)
if err != nil {
log.Printf("error parsing template: %q", err)
return "", err
}
var buf bytes.Buffer
if err = tmpl.Execute(&buf, mt); err != nil {
log.Printf("error executing template: %q", err)
return "", err
}
return buf.String(), nil
}
func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
mp := ParseModelPath(name)
2023-07-19 01:51:30 +00:00
fn(api.ProgressResponse{Status: "retrieving manifest"})
if mp.ProtocolScheme == "http" && !regOpts.Insecure {
return fmt.Errorf("insecure protocol http")
}
2023-08-29 03:50:24 +00:00
manifest, _, err := GetManifest(mp)
if err != nil {
2023-07-19 01:51:30 +00:00
fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
return err
}
var layers []*Layer
2023-08-01 01:37:40 +00:00
layers = append(layers, manifest.Layers...)
layers = append(layers, manifest.Config)
for _, layer := range layers {
2023-10-09 17:24:27 +00:00
if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
log.Printf("error uploading blob: %v", err)
if errors.Is(err, errUnauthorized) {
return fmt.Errorf("unable to push %s, make sure this namespace exists and you are authorized to push to it", ParseModelPath(name).GetNamespaceRepository())
}
return err
}
2023-07-19 01:51:30 +00:00
}
fn(api.ProgressResponse{Status: "pushing manifest"})
2023-08-22 01:38:31 +00:00
requestURL := mp.BaseURL()
requestURL = requestURL.JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
manifestJSON, err := json.Marshal(manifest)
if err != nil {
return err
}
2023-08-22 01:24:42 +00:00
headers := make(http.Header)
headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
2023-11-02 20:10:58 +00:00
resp, err := makeRequestWithRetry(ctx, http.MethodPut, requestURL, headers, bytes.NewReader(manifestJSON), regOpts)
if err != nil {
return err
}
defer resp.Body.Close()
fn(api.ProgressResponse{Status: "success"})
return nil
}
func PullModel(ctx context.Context, name string, regOpts *RegistryOptions, fn func(api.ProgressResponse)) error {
mp := ParseModelPath(name)
var manifest *ManifestV2
var err error
var noprune string
// build deleteMap to prune unused layers
2023-11-14 20:30:34 +00:00
deleteMap := make(map[string]struct{})
if noprune = os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
manifest, _, err = GetManifest(mp)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
if manifest != nil {
for _, l := range manifest.Layers {
2023-11-14 20:30:34 +00:00
deleteMap[l.Digest] = struct{}{}
}
2023-11-14 20:30:34 +00:00
deleteMap[manifest.Config.Digest] = struct{}{}
}
}
if mp.ProtocolScheme == "http" && !regOpts.Insecure {
return fmt.Errorf("insecure protocol http")
2023-08-22 04:56:56 +00:00
}
2023-07-19 01:51:30 +00:00
fn(api.ProgressResponse{Status: "pulling manifest"})
manifest, err = pullModelManifest(ctx, mp, regOpts)
if err != nil {
return fmt.Errorf("pull model manifest: %s", err)
}
var layers []*Layer
2023-07-20 18:18:00 +00:00
layers = append(layers, manifest.Layers...)
layers = append(layers, manifest.Config)
for _, layer := range layers {
2023-08-15 18:07:19 +00:00
if err := downloadBlob(
ctx,
downloadOpts{
mp: mp,
digest: layer.Digest,
regOpts: regOpts,
fn: fn,
}); err != nil {
return err
}
delete(deleteMap, layer.Digest)
}
delete(deleteMap, manifest.Config.Digest)
2023-07-20 18:44:05 +00:00
fn(api.ProgressResponse{Status: "verifying sha256 digest"})
for _, layer := range layers {
if err := verifyBlob(layer.Digest); err != nil {
2023-07-24 18:53:01 +00:00
if errors.Is(err, errDigestMismatch) {
// something went wrong, delete the blob
fp, err := GetBlobsPath(layer.Digest)
if err != nil {
return err
}
if err := os.Remove(fp); err != nil {
// log this, but return the original error
log.Printf("couldn't remove file with digest mismatch '%s': %v", fp, err)
}
}
2023-07-20 18:44:05 +00:00
return err
}
}
2023-07-19 01:51:30 +00:00
fn(api.ProgressResponse{Status: "writing manifest"})
manifestJSON, err := json.Marshal(manifest)
if err != nil {
return err
}
fp, err := mp.GetManifestPath()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(fp), 0o755); err != nil {
return err
}
2023-07-20 18:18:00 +00:00
err = os.WriteFile(fp, manifestJSON, 0o644)
if err != nil {
log.Printf("couldn't write to %s", fp)
return err
}
if noprune == "" {
fn(api.ProgressResponse{Status: "removing any unused layers"})
err = deleteUnusedLayers(nil, deleteMap, false)
if err != nil {
return err
}
}
2023-07-19 01:51:30 +00:00
fn(api.ProgressResponse{Status: "success"})
return nil
}
func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *RegistryOptions) (*ManifestV2, error) {
2023-08-22 01:38:31 +00:00
requestURL := mp.BaseURL().JoinPath("v2", mp.GetNamespaceRepository(), "manifests", mp.Tag)
2023-08-22 01:24:42 +00:00
headers := make(http.Header)
headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
2023-11-02 20:13:32 +00:00
resp, err := makeRequestWithRetry(ctx, http.MethodGet, requestURL, headers, nil, regOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var m *ManifestV2
if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
return nil, err
}
return m, err
}
// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
2023-09-28 17:00:34 +00:00
func GetSHA256Digest(r io.Reader) (string, int64) {
2023-07-19 00:14:12 +00:00
h := sha256.New()
n, err := io.Copy(h, r)
if err != nil {
log.Fatal(err)
}
2023-09-28 17:00:34 +00:00
return fmt.Sprintf("sha256:%x", h.Sum(nil)), n
}
var errUnauthorized = fmt.Errorf("unauthorized")
2023-08-22 01:38:31 +00:00
func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.ReadSeeker, regOpts *RegistryOptions) (*http.Response, error) {
resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
if err != nil {
if !errors.Is(err, context.Canceled) {
log.Printf("request failed: %v", err)
}
2023-11-19 05:19:53 +00:00
return nil, err
}
switch {
case resp.StatusCode == http.StatusUnauthorized:
// Handle authentication error with one retry
auth := resp.Header.Get("www-authenticate")
authRedir := ParseAuthRedirectString(auth)
token, err := getAuthToken(ctx, authRedir)
2023-08-17 19:35:29 +00:00
if err != nil {
return nil, err
}
regOpts.Token = token
if body != nil {
_, err = body.Seek(0, io.SeekStart)
2023-08-17 19:35:29 +00:00
if err != nil {
return nil, err
}
}
2023-11-19 05:19:53 +00:00
resp, err := makeRequest(ctx, method, requestURL, headers, body, regOpts)
if resp.StatusCode == http.StatusUnauthorized {
return nil, errUnauthorized
}
return resp, err
case resp.StatusCode == http.StatusNotFound:
return nil, os.ErrNotExist
case resp.StatusCode >= http.StatusBadRequest:
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("%d: %s", resp.StatusCode, err)
}
return nil, fmt.Errorf("%d: %s", resp.StatusCode, responseBody)
2023-08-17 19:35:29 +00:00
}
return resp, nil
2023-08-17 19:35:29 +00:00
}
2023-08-22 01:38:31 +00:00
func makeRequest(ctx context.Context, method string, requestURL *url.URL, headers http.Header, body io.Reader, regOpts *RegistryOptions) (*http.Response, error) {
2023-09-08 00:24:31 +00:00
if requestURL.Scheme != "http" && regOpts != nil && regOpts.Insecure {
2023-08-22 01:38:31 +00:00
requestURL.Scheme = "http"
}
2023-08-22 01:38:31 +00:00
req, err := http.NewRequestWithContext(ctx, method, requestURL.String(), body)
if err != nil {
return nil, err
}
2023-08-22 01:24:42 +00:00
if headers != nil {
req.Header = headers
}
2023-09-07 18:49:36 +00:00
if regOpts != nil {
if regOpts.Token != "" {
req.Header.Set("Authorization", "Bearer "+regOpts.Token)
} else if regOpts.Username != "" && regOpts.Password != "" {
req.SetBasicAuth(regOpts.Username, regOpts.Password)
}
}
2023-08-22 01:24:42 +00:00
req.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version()))
if s := req.Header.Get("Content-Length"); s != "" {
contentLength, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return nil, err
}
req.ContentLength = contentLength
}
2023-10-09 18:42:36 +00:00
proxyURL, err := http.ProxyFromEnvironment(req)
if err != nil {
return nil, err
}
client := http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
2023-07-20 18:44:05 +00:00
2023-08-10 18:34:25 +00:00
func getValue(header, key string) string {
startIdx := strings.Index(header, key+"=")
if startIdx == -1 {
return ""
}
// Move the index to the starting quote after the key.
startIdx += len(key) + 2
endIdx := startIdx
for endIdx < len(header) {
if header[endIdx] == '"' {
if endIdx+1 < len(header) && header[endIdx+1] != ',' { // If the next character isn't a comma, continue
endIdx++
continue
}
break
}
endIdx++
}
return header[startIdx:endIdx]
}
func ParseAuthRedirectString(authStr string) AuthRedirect {
authStr = strings.TrimPrefix(authStr, "Bearer ")
return AuthRedirect{
Realm: getValue(authStr, "realm"),
Service: getValue(authStr, "service"),
Scope: getValue(authStr, "scope"),
}
}
2023-07-24 18:53:01 +00:00
var errDigestMismatch = fmt.Errorf("digest mismatch, file must be downloaded again")
2023-07-20 18:44:05 +00:00
func verifyBlob(digest string) error {
fp, err := GetBlobsPath(digest)
if err != nil {
return err
}
f, err := os.Open(fp)
if err != nil {
return err
}
defer f.Close()
fileDigest, _ := GetSHA256Digest(f)
if digest != fileDigest {
2023-07-24 18:53:01 +00:00
return fmt.Errorf("%w: want %s, got %s", errDigestMismatch, digest, fileDigest)
2023-07-20 18:44:05 +00:00
}
return nil
}